Feat/silentpayments multi signer ecdh#135
Draft
Sosthene00 wants to merge 22 commits into
Draft
Conversation
a494bf4 to
63802df
Compare
…nputsHash from public API Replace the parallel `outpoints: NonEmptyArray<OutPoint>` + `script_pubkeys: NonEmptyArray<(Vec<u8>, Option<PublicKey>)>` pattern with a single `TransactionInputs` struct that owns all three fields (outpoints, script pubkeys, extracted input pubkeys) and exposes `push` for incremental construction. Remove the now-redundant `eligible_input_pubkey_refs` free function and the `InputsHash` public struct (input hash computation moves into `TransactionSharedSecret` constructors in the next commit). Make `LabelTag` and `LabelHash` module-private in `utils/hash.rs`; they have no legitimate callers outside the hash module. Co-authored-by: Cursor <cursoragent@cursor.com>
…ansactionSharedSecret Add `rust-dleq` as a workspace dependency behind two mutually exclusive feature flags: `dleq-standalone` (pure Rust) and `dleq-native` (libsecp256k1 C backend). A `compile_error!` guard enforces that exactly one backend is selected whenever `sending` is enabled. API changes ----------- * `PartialSenderEcdhShare::new` now requires `secp` and `aux_rand`; it generates a BIP374 DLEQ proof at construction time. The old two-step `apply_input_hash` method is removed. * `GlobalSenderEcdhShare::new_from_summed_keys` likewise requires `secp` and `aux_rand` and generates a proof; `apply_input_hash` and `from_partial_shares` are removed. * Both share types expose `verify_dleq_proof` for the receiving side to verify correctness before trusting the share. * `TransactionSharedSecret` now carries `recipient_scan_key` alongside the ECDH point; input-hash computation moves inside `new_from_global_share(secp, share, inputs)` and `new_from_public_tweak_data(secp, tweak, scan_key)`. * `DleqProof` is re-exported from the crate root. * Unit tests for DLEQ round-trips added to `utils/sending.rs`. Co-authored-by: Cursor <cursoragent@cursor.com>
P2SH-P2WPKH: validate the redeem-script hash160 against the output script pubkey before trusting it. Previously any push in script_sig was accepted without checking the commitment, which would silently allow malformed inputs to pass. P2TR: the old check for a script-path spend indexed the last witness item as `[1..33]` without verifying length, which panics on items shorter than 33 bytes. Tighten the guard to `control_block.len() >= 33` and only check `effective_stack >= 2` (key-path spends have a single witness item). Extract `last_push` (returns the last data push in a push-only script) and `witness_compressed_pubkey` (verifies compressed pubkey + hash160 match) as helpers, removing three near-identical match blocks. Also includes `PublicTweakData::new` signature change from the two-`NonEmptyArray` pattern to `&TransactionInputs` (companion to the TransactionInputs commit). Co-authored-by: Cursor <cursoragent@cursor.com>
Mechanical update of the vector test suite, find_output example, and spdk-wallet spend/client code: * Replace the parallel `outpoints` + `script_pubkeys` vectors with `TransactionInputs::push` at each call site. * Pass `secp` to `TransactionSharedSecret::new_from_public_tweak_data` and `new_from_global_share`. * Thread an `aux_rand: &[u8; 32]` parameter through `SpClient::finalize_transaction` and the private `shared_secrets_by_scan_key` helper. * Remove now-deleted imports (`InputsHash`, `NonEmptyArray` where no longer needed). Co-authored-by: Cursor <cursoragent@cursor.com>
…ial_shares` Requirements that all shares should rely to the same scan key used to be implicit, we're making it explicit.
…ubkeys - `generate_recipient_pubkeys` now takes `&[[u8; SILENT_PAYMENT_ADDRESS_BYTE_LEN]]` instead of `&[SilentPaymentAddress]`, and accepts an injected `&Secp256k1` rather than constructing one internally. This makes the function usable from contexts (e.g. PSBT signing) where the caller already has a secp context and addresses stored as raw bytes rather than the typed wrapper. - Export `SILENT_PAYMENT_ADDRESS_BYTE_LEN` from the crate root. - Add `TransactionInputs::with_capacity` constructor. - Widen `eligible_vins` visibility to `pub` for use in the new psbt crate. - Add `PartialSenderEcdhShare::new_unchecked` and `GlobalSenderEcdhShare::new_unchecked` for reconstructing shares from serialized PSBT fields without re-running DLEQ generation (verification still happens in `new_from_partial_shares` / `new_from_global_share`). - Update vector tests to the new signature. Co-authored-by: Cursor <cursoragent@cursor.com>
`generate_recipient_pubkeys` no longer accepts `&[SilentPaymentAddress]` directly. Convert the SP address vec to `Vec<[u8; SILENT_PAYMENT_ADDRESS_BYTE_LEN]>` before the call, and look up results with `s.to_byte_array()` instead of the typed address key. Co-authored-by: Cursor <cursoragent@cursor.com>
Implements the BIP-375 signer roles as a new workspace crate built on top of rust-psbt (psbt-v2, from a fork at branch taproot_sign) and the silentpayments crate. Core roles implemented: - Constructor: create PSBT, add inputs/outputs (with output-order randomisation). - Updater: set BIP32 derivation paths and SP tweaks on inputs. - Signer (single-signer path): compute one global ECDH share + DLEQ proof per scan key, then derive output scriptPubKeys via generate_recipient_pubkeys. - Signer (multi-signer path): each co-signer contributes one per-eligible-input ECDH share + DLEQ proof; compute_sp_outputs aggregates and verifies them. - Input witness finalizer: moves tap_key_sig into final_script_witness. - Extractor: extract the final bitcoin::Transaction. Key design points: - `compute_sp_outputs` uses `eligible_vins` to skip non-eligible inputs in the multi-signer aggregation loop, fixing the case where a PSBT mixes eligible and non-eligible inputs. - `collect_scan_keys` correctly slices bytes [1..34] from the 67-byte SP address array to obtain the 33-byte scan key. - `extract_eligible_input_pubkey` is public, covering P2WPKH, P2PKH, P2SH-P2WPKH, plain P2TR, and SP-tweaked P2TR; returns None for NUMS_H key-path-less outputs. Integration tests (psbt/tests/signer.rs): - Full single-signer end-to-end round-trip. - Two outputs to the same scan key produce distinct n=0/n=1 keys. - Two outputs to different scan keys each get their own share. - Multi-signer share generation skips non-eligible inputs. - Single-signer ignores non-eligible inputs in compute_sp_outputs. - Unit tests for all extract_eligible_input_pubkey branches. - One #[ignore] test documenting the outstanding is_partial detection bug for multi-signer PSBTs with non-eligible inputs. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alternative to #125. Big change is to restructure the sending flow around 2 new types,
GlobalSenderEcdhShareandPartialSenderEcdhShare. Dleq proof logic is still missing to be implemented if the general idea is validated