From 8a93d0695b868382f9ed78929fd1bd4a11e0f80b Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 11 Jun 2025 21:17:06 +0000 Subject: [PATCH 1/3] psbt: add accessor to PsbtInputSatisfier for the input it holds Currently we have ugly and repeated code self.psbt.input[self.index]. Replace this with an accessor function which is easier to read and easier to use outside of the module. --- src/psbt/mod.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 84924f42e..33e6c84bc 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -252,11 +252,14 @@ impl<'psbt> PsbtInputSatisfier<'psbt> { /// create a new PsbtInputsatisfier from /// psbt and index pub fn new(psbt: &'psbt Psbt, index: usize) -> Self { Self { psbt, index } } + + /// Accessor for the input this satisfier is associated with. + pub fn psbt_input(&self) -> &psbt::Input { &self.psbt.inputs[self.index] } } impl Satisfier for PsbtInputSatisfier<'_> { fn lookup_tap_key_spend_sig(&self) -> Option { - self.psbt.inputs[self.index].tap_key_sig + self.psbt_input().tap_key_sig } fn lookup_tap_leaf_script_sig( @@ -264,14 +267,14 @@ impl Satisfier for PsbtInputSatisfier<'_> { pk: &Pk, lh: &TapLeafHash, ) -> Option { - self.psbt.inputs[self.index] + self.psbt_input() .tap_script_sigs .get(&(pk.to_x_only_pubkey(), *lh)) .copied() } fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { - self.psbt.inputs[self.index] + self.psbt_input() .bip32_derivation .iter() .find(|&(pubkey, _)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) @@ -281,14 +284,14 @@ impl Satisfier for PsbtInputSatisfier<'_> { fn lookup_tap_control_block_map( &self, ) -> Option<&BTreeMap> { - Some(&self.psbt.inputs[self.index].tap_scripts) + Some(&self.psbt_input().tap_scripts) } fn lookup_raw_pkh_tap_leaf_script_sig( &self, pkh: &(hash160::Hash, TapLeafHash), ) -> Option<(bitcoin::secp256k1::XOnlyPublicKey, bitcoin::taproot::Signature)> { - self.psbt.inputs[self.index] + self.psbt_input() .tap_script_sigs .iter() .find(|&((pubkey, lh), _sig)| { @@ -298,7 +301,7 @@ impl Satisfier for PsbtInputSatisfier<'_> { } fn lookup_ecdsa_sig(&self, pk: &Pk) -> Option { - self.psbt.inputs[self.index] + self.psbt_input() .partial_sigs .get(&pk.to_public_key()) .copied() @@ -308,7 +311,7 @@ impl Satisfier for PsbtInputSatisfier<'_> { &self, pkh: &hash160::Hash, ) -> Option<(bitcoin::PublicKey, bitcoin::ecdsa::Signature)> { - self.psbt.inputs[self.index] + self.psbt_input() .partial_sigs .iter() .find(|&(pubkey, _sig)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) @@ -337,28 +340,28 @@ impl Satisfier for PsbtInputSatisfier<'_> { } fn lookup_hash160(&self, h: &Pk::Hash160) -> Option { - self.psbt.inputs[self.index] + self.psbt_input() .hash160_preimages .get(&Pk::to_hash160(h)) .and_then(|x: &Vec| <[u8; 32]>::try_from(&x[..]).ok()) } fn lookup_sha256(&self, h: &Pk::Sha256) -> Option { - self.psbt.inputs[self.index] + self.psbt_input() .sha256_preimages .get(&Pk::to_sha256(h)) .and_then(|x: &Vec| <[u8; 32]>::try_from(&x[..]).ok()) } fn lookup_hash256(&self, h: &Pk::Hash256) -> Option { - self.psbt.inputs[self.index] + self.psbt_input() .hash256_preimages .get(&sha256d::Hash::from_byte_array(Pk::to_hash256(h).to_byte_array())) // upstream psbt operates on hash256 .and_then(|x: &Vec| <[u8; 32]>::try_from(&x[..]).ok()) } fn lookup_ripemd160(&self, h: &Pk::Ripemd160) -> Option { - self.psbt.inputs[self.index] + self.psbt_input() .ripemd160_preimages .get(&Pk::to_ripemd160(h)) .and_then(|x: &Vec| <[u8; 32]>::try_from(&x[..]).ok()) From 97ef17ab50f4f2303e2d53b1eb6df696d0fff690 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 11 Jun 2025 21:55:36 +0000 Subject: [PATCH 2/3] psbt: make PsbtInputSatisfier internals private We don't need access to the internals anywhere, and this is just a POD type with two Copy objects inside so the user can just keep them around after construction if they need access. Add an accessor for the PSBT anyway since it might be convenient. Making these fields private does not break compilation at all. --- src/psbt/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 33e6c84bc..f3ae9b25d 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -243,9 +243,9 @@ impl From for InputError { /// is more than number of inputs in pbst pub struct PsbtInputSatisfier<'psbt> { /// pbst - pub psbt: &'psbt Psbt, + psbt: &'psbt Psbt, /// input index - pub index: usize, + index: usize, } impl<'psbt> PsbtInputSatisfier<'psbt> { @@ -253,6 +253,9 @@ impl<'psbt> PsbtInputSatisfier<'psbt> { /// psbt and index pub fn new(psbt: &'psbt Psbt, index: usize) -> Self { Self { psbt, index } } + /// Accessor for the PSBT this satisfier is associated with. + pub fn psbt(&self) -> &'psbt Psbt { self.psbt } + /// Accessor for the input this satisfier is associated with. pub fn psbt_input(&self) -> &psbt::Input { &self.psbt.inputs[self.index] } } From 1c2800e2c5eeb96ae779fb5d1f506e6d53bffdd0 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 11 Jun 2025 21:05:47 +0000 Subject: [PATCH 3/3] satisfy: change `lookup_tap_key_spend_sig` to take the public key There is only one public key possible, but this makes the API more consistent between keyspends and script-spends, and makes it much easier for users who have a key->sig lookup table to implement satisfiers. Fixes #825 --- fuzz/fuzz_targets/miniscript_satisfy.rs | 8 ++++---- src/miniscript/satisfy.rs | 23 ++++++++++++----------- src/plan.rs | 4 ++-- src/psbt/finalizer.rs | 12 +++++++----- src/psbt/mod.rs | 9 +++++++-- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/fuzz/fuzz_targets/miniscript_satisfy.rs b/fuzz/fuzz_targets/miniscript_satisfy.rs index 98dc6f1bf..34590f539 100644 --- a/fuzz/fuzz_targets/miniscript_satisfy.rs +++ b/fuzz/fuzz_targets/miniscript_satisfy.rs @@ -23,7 +23,7 @@ impl FuzzSatisfier<'_> { } impl Satisfier for FuzzSatisfier<'_> { - fn lookup_tap_key_spend_sig(&self) -> Option { + fn lookup_tap_key_spend_sig(&self, _: &FuzzPk) -> Option { let b = self.read_byte()?; if b & 1 == 1 { // FIXME in later version of rust-secp we can use from_byte_array @@ -34,8 +34,8 @@ impl Satisfier for FuzzSatisfier<'_> { } } - fn lookup_tap_leaf_script_sig(&self, _: &FuzzPk, _: &TapLeafHash) -> Option { - self.lookup_tap_key_spend_sig() + fn lookup_tap_leaf_script_sig(&self, pk: &FuzzPk, _: &TapLeafHash) -> Option { + self.lookup_tap_key_spend_sig(pk) } // todo @@ -85,7 +85,7 @@ impl Satisfier for FuzzSatisfier<'_> { (h, _): &(hash160::Hash, TapLeafHash), ) -> Option<(XOnlyPublicKey, Signature)> { self.lookup_raw_pkh_x_only_pk(h) - .zip(self.lookup_tap_key_spend_sig()) + .zip(self.lookup_tap_key_spend_sig(&FuzzPk::new_from_control_byte(0))) } fn lookup_sha256(&self, b: &u8) -> Option<[u8; 32]> { diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index c6a0bd3bf..25c781798 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -35,7 +35,7 @@ pub trait Satisfier { fn lookup_ecdsa_sig(&self, _: &Pk) -> Option { None } /// Lookup the tap key spend sig - fn lookup_tap_key_spend_sig(&self) -> Option { None } + fn lookup_tap_key_spend_sig(&self, _: &Pk) -> Option { None } /// Given a public key and a associated leaf hash, look up an schnorr signature with that key fn lookup_tap_leaf_script_sig( @@ -290,8 +290,8 @@ impl> Satisfier for &S { (**self).lookup_raw_pkh_ecdsa_sig(pkh) } - fn lookup_tap_key_spend_sig(&self) -> Option { - (**self).lookup_tap_key_spend_sig() + fn lookup_tap_key_spend_sig(&self, pk: &Pk) -> Option { + (**self).lookup_tap_key_spend_sig(pk) } fn lookup_raw_pkh_tap_leaf_script_sig( @@ -335,8 +335,8 @@ impl> Satisfier for &mut S (**self).lookup_tap_leaf_script_sig(p, h) } - fn lookup_tap_key_spend_sig(&self) -> Option { - (**self).lookup_tap_key_spend_sig() + fn lookup_tap_key_spend_sig(&self, pk: &Pk) -> Option { + (**self).lookup_tap_key_spend_sig(pk) } fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { @@ -400,10 +400,10 @@ macro_rules! impl_tuple_satisfier { None } - fn lookup_tap_key_spend_sig(&self) -> Option { + fn lookup_tap_key_spend_sig(&self, pk: &Pk) -> Option { let &($(ref $ty,)*) = self; $( - if let Some(result) = $ty.lookup_tap_key_spend_sig() { + if let Some(result) = $ty.lookup_tap_key_spend_sig(pk) { return Some(result); } )* @@ -678,12 +678,13 @@ impl Placeholder { debug_assert!(s.len() == *size); s }), - Placeholder::SchnorrSigPk(_, _, size) => { - sat.lookup_tap_key_spend_sig().map(|s| s.to_vec()).map(|s| { + Placeholder::SchnorrSigPk(pk, _, size) => sat + .lookup_tap_key_spend_sig(pk) + .map(|s| s.to_vec()) + .map(|s| { debug_assert!(s.len() == *size); s - }) - } + }), Placeholder::SchnorrSigPkHash(pkh, tap_leaf_hash, size) => sat .lookup_raw_pkh_tap_leaf_script_sig(&(*pkh, *tap_leaf_hash)) .map(|(_, s)| { diff --git a/src/plan.rs b/src/plan.rs index 52eebbed5..384ca2f7a 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -147,8 +147,8 @@ where Satisfier::lookup_ecdsa_sig(self, pk).is_some() } - fn provider_lookup_tap_key_spend_sig(&self, _: &Pk) -> Option { - Satisfier::lookup_tap_key_spend_sig(self).map(|s| s.to_vec().len()) + fn provider_lookup_tap_key_spend_sig(&self, pk: &Pk) -> Option { + Satisfier::lookup_tap_key_spend_sig(self, pk).map(|s| s.to_vec().len()) } fn provider_lookup_tap_leaf_script_sig( diff --git a/src/psbt/finalizer.rs b/src/psbt/finalizer.rs index af25bfd33..2bfb24a88 100644 --- a/src/psbt/finalizer.rs +++ b/src/psbt/finalizer.rs @@ -53,11 +53,13 @@ fn construct_tap_witness( } assert!(spk.is_p2tr()); - // try the key spend path first - if let Some(sig) = - >::lookup_tap_key_spend_sig(sat) - { - return Ok(vec![sig.to_vec()]); + // try the key spend path firsti + if let Some(ref key) = sat.psbt_input().tap_internal_key { + if let Some(sig) = + >::lookup_tap_key_spend_sig(sat, key) + { + return Ok(vec![sig.to_vec()]); + } } // Next script spends let (mut min_wit, mut min_wit_len) = (None, None); diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index f3ae9b25d..bf698e1dc 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -261,8 +261,13 @@ impl<'psbt> PsbtInputSatisfier<'psbt> { } impl Satisfier for PsbtInputSatisfier<'_> { - fn lookup_tap_key_spend_sig(&self) -> Option { - self.psbt_input().tap_key_sig + fn lookup_tap_key_spend_sig(&self, pk: &Pk) -> Option { + if let Some(key) = self.psbt_input().tap_internal_key { + if pk.to_x_only_pubkey() == key { + return self.psbt_input().tap_key_sig; + } + } + None } fn lookup_tap_leaf_script_sig(