-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bitcoin: support sending to silent payment addresses #1220
Conversation
This currently adds 9228 bytes to the binary, luckily less than this PR saves 😄 #1215 |
Rebased. Need to double-check if payment requests to silent payment addresses work safely (an output that is both attached to a payment request and is generated by a SP address) and add unit tests for it, or if we should disable that combination. Edit: I don't see a reason silent payment addresses could not safely be used within payment requests. The payment request signs for address strings, not pubkeyScripts, so it can simply contain the silent payment address. |
877215a
to
ade896c
Compare
Rebased. |
Host side that verifies the generated output correctness using the DLEQ proof and returns them: BitBoxSwiss/bitbox02-api-go#105 This includes a simulator test the complete integration is tested. |
@NickeZ one way to test it (except the integration/unit tests included in this PR) is an integration test on the host side with the simulator:
Then in the api-go client repo at BitBoxSwiss/bitbox02-api-go#105, run
|
1368d52
to
77a5079
Compare
420fb57
to
e14f3db
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general approved, left some minor comments.
|
||
const DLEQ_PROOF_SIZE: usize = 33 + 64; | ||
|
||
pub struct Output { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps name it TransactionOutput
since Output
can be a bit generic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
impl Network { | ||
fn sp_hrp(&self) -> &str { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrp
is maybe a bit cryptic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a comment what this is, but HRP is the name for that part of a bech32 address:
- Referred to as HRP in https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
- Used everywhere, like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment
SecretKey::from_slice(&hash).map_err(|_| ()) | ||
} | ||
|
||
fn decode_address(address: &str, expected_hrp: &str) -> Result<(PublicKey, PublicKey), ()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider returning a struct so that you can name the fields. You can still do destructuring at the call site if you like. https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring/destructure_structures.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
fn decode_address(address: &str, expected_hrp: &str) -> Result<(PublicKey, PublicKey), ()> { | ||
let mut decoded_addr = | ||
bech32::primitives::decode::CheckedHrpstring::new::<bech32::Bech32m>(address) | ||
.map_err(|_| ())?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For error handling, thiserror
is a common crate for creating library level error enums. It has a convenient macro for wrapping dependencies errors as well. https://docs.rs/thiserror/latest/thiserror/
Another, simpler, option is to use &'static str
as error type.
Throwing away errors with Err(())
feels a bit awkward. If you think it is programmer errors and not user errors, consider using .expect(reason)
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Skpping for now
// - re-computing the output (https://github.com/bitcoin/bips/blob/ad1d3bc2a7b0d84247c29f847e85c35283094e2f/bip-0352.mediawiki#creating-outputs) | ||
// using ecdsa_shared_secret = input_hash·a_sum·scan_pubkey = input_hash·c_pubkey | ||
// - verifying that the created output matches the re-computed output. | ||
fn create_dleq_proof<C: secp256k1::Verification>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to make your function non-generic and instead put Secp256k1<secp256k1::All>
as the type in the argument list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, worked.
} | ||
|
||
let data: Vec<u8> = decoded_addr.byte_iter().collect(); | ||
if data.len() != 66 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should magic number 66
be a const?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added const PUBKEY_LEN: usize = 33;
, and used 2 * PUBKEYLEN
here.
if data.len() != 66 { | ||
return Err(()); | ||
} | ||
let scan_pubkey = PublicKey::from_slice(&data[..33]).map_err(|_| ())?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should magic number 33
be a const?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
input_type: InputType, | ||
input_key: &SecretKey, | ||
prevout: bitcoin::OutPoint, | ||
) -> Result<(), ()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using an error type other than ()
.
@@ -303,6 +303,12 @@ USE_RESULT bool keystore_secp256k1_schnorr_bip86_sign( | |||
const uint8_t* msg32, | |||
uint8_t* sig64_out); | |||
|
|||
USE_RESULT bool keystore_secp256k1_get_private_key( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing doc comment for this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
messages/btc.proto
Outdated
@@ -137,6 +138,9 @@ message BTCSignNextResponse { | |||
// Previous tx's input/output index in case of PREV_INPUT or PREV_OUTPUT, for the input at `index`. | |||
uint32 prev_index = 5; | |||
AntiKleptoSignerCommitment anti_klepto_signer_commitment = 6; | |||
// Generated output. The host *must* verify its correctness using silent_payment_dleq_proof. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you write silent_payment_dleq_proof
(with ticks `) it will be nicer formatted in the generated docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
BIP-352: https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki A silent payment address contains two pubkeys, B_scan and B_spend. From that, Taproot outputs can be created by using a ECDH shared secret - see the BIP Overview section. The created output is returned to the host along with a DLEQ proof the host can use to independently verify the output was created correctly. This mitigates bugs, potential memory corruption (bit-flips), etc. in the firmware leading to catastrophic failure. The added vendored deps serde/serde_json etc. are dev-dependencies to run the tests in the new crate, not used for firmware builds. **A note about multiple silent payment outputs per transaction:** In general, a transaction can send to an arbitrary amount of silent payment addresses. If there are multiple, then for each unique B_scan (multiple outputs to the same recipient/B_scan), a counter `k` is incremented. See https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#creating-outputs. To be able to verify the silent payment address, we need to derive the output and check that it matches the provided output. For each SP output, we'd then need to know the SP address and the counter `k`. The problem with this is that we also need to check, per B_scan, that each `k=0, 1, ...` is used starting at zero with no holes. This requires non-constant memory. We could still do it and support a limited (but likely high) number of SP outputs per transaction, but that complicates the code. For this reason, we restrict to only one SP output per transaction.
No description provided.