diff --git a/src/psbt/finalizer.rs b/src/psbt/finalizer.rs index e36df3138..48009a866 100644 --- a/src/psbt/finalizer.rs +++ b/src/psbt/finalizer.rs @@ -38,26 +38,20 @@ fn construct_tap_witness( // When miniscript tries to finalize the PSBT, it doesn't have the full descriptor (which contained a pkh() fragment) // and instead resorts to parsing the raw script sig, which is translated into a "expr_raw_pkh" internally. let mut map: BTreeMap = BTreeMap::new(); - let psbt_inputs = &sat.psbt.inputs; - for psbt_input in psbt_inputs { + sat.psbt.inputs.iter().for_each(|input| { // We need to satisfy or dissatisfy any given key. `tap_key_origin` is the only field of PSBT Input which consist of // all the keys added on a descriptor and thus we get keys from it. - let public_keys = psbt_input.tap_key_origins.keys(); - for key in public_keys { - let bitcoin_key = *key; - let hash = bitcoin_key.to_pubkeyhash(SigType::Schnorr); - map.insert(hash, bitcoin_key); - } - } + input.tap_key_origins.keys().for_each(|key| { + map.insert(key.to_pubkeyhash(SigType::Schnorr), *key); + }) + }); assert!(spk.is_p2tr()); // 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()]); - } + if let Some(sig) = sat.psbt_input().tap_internal_key.as_ref().and_then(|key| { + >::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); @@ -165,37 +159,26 @@ fn get_descriptor(psbt: &Psbt, index: usize) -> Result, In } } else if script_pubkey.is_p2pkh() { // 2. `Pkh`: creates a `PkH` descriptor if partial_sigs has the corresponding pk - let partial_sig_contains_pk = inp.partial_sigs.iter().find(|&(&pk, _sig)| { - // Indirect way to check the equivalence of pubkey-hashes. - // Create a pubkey hash and check if they are the same. - // THIS IS A BUG AND *WILL* PRODUCE WRONG SATISFACTIONS FOR UNCOMPRESSED KEYS - // Partial sigs loses the compressed flag that is necessary - // TODO: See https://github.com/rust-bitcoin/rust-bitcoin/pull/836 - // The type checker will fail again after we update to 0.28 and this can be removed - let addr = bitcoin::Address::p2pkh(pk, bitcoin::Network::Bitcoin); - *script_pubkey == addr.script_pubkey() - }); - match partial_sig_contains_pk { - Some((pk, _sig)) => Descriptor::new_pkh(*pk).map_err(InputError::from), - None => Err(InputError::MissingPubkey), - } + let (pk, _) = inp + .partial_sigs + .iter() + .find(|&(pk, _sig)| script_pubkey.as_bytes()[3..23] == pk.pubkey_hash()[..]) + .ok_or(InputError::MissingPubkey)?; + Descriptor::new_pkh(*pk).map_err(InputError::from) } else if script_pubkey.is_p2wpkh() { // 3. `Wpkh`: creates a `wpkh` descriptor if the partial sig has corresponding pk. - let partial_sig_contains_pk = inp.partial_sigs.iter().find(|&(&pk, _sig)| { - match bitcoin::key::CompressedPublicKey::try_from(pk) { - Ok(compressed) => { - // Indirect way to check the equivalence of pubkey-hashes. - // Create a pubkey hash and check if they are the same. - let addr = bitcoin::Address::p2wpkh(&compressed, bitcoin::Network::Bitcoin); - *script_pubkey == addr.script_pubkey() - } - Err(_) => false, - } - }); - match partial_sig_contains_pk { - Some((pk, _sig)) => Ok(Descriptor::new_wpkh(*pk)?), - None => Err(InputError::MissingPubkey), - } + let (pk, _) = inp + .partial_sigs + .iter() + .find(|&(pk, _)| { + bitcoin::key::CompressedPublicKey::try_from(*pk) + .map(|compressed| { + compressed.pubkey_hash()[..] == script_pubkey.as_bytes()[2..22] + }) + .unwrap_or(false) + }) + .ok_or(InputError::MissingPubkey)?; + Descriptor::new_wpkh(*pk).map_err(InputError::from) } else if script_pubkey.is_p2wsh() { // 4. `Wsh`: creates a `Wsh` descriptor if inp.redeem_script.is_some() { @@ -241,22 +224,18 @@ fn get_descriptor(psbt: &Psbt, index: usize) -> Result, In } } else if redeem_script.is_p2wpkh() { // 6. `ShWpkh` case - let partial_sig_contains_pk = inp.partial_sigs.iter().find(|&(&pk, _sig)| { - match bitcoin::key::CompressedPublicKey::try_from(pk) { - Ok(compressed) => { - let addr = bitcoin::Address::p2wpkh( - &compressed, - bitcoin::Network::Bitcoin, - ); - *redeem_script == addr.script_pubkey() - } - Err(_) => false, - } - }); - match partial_sig_contains_pk { - Some((pk, _sig)) => Ok(Descriptor::new_sh_wpkh(*pk)?), - None => Err(InputError::MissingPubkey), - } + let (pk, _) = inp + .partial_sigs + .iter() + .find(|&(&pk, _sig)| { + bitcoin::key::CompressedPublicKey::try_from(pk) + .map(|compressed| { + compressed.pubkey_hash()[..] == redeem_script.as_bytes()[2..22] + }) + .unwrap_or(false) + }) + .ok_or(InputError::MissingPubkey)?; + Ok(Descriptor::new_sh_wpkh(*pk)?) } else { //7. regular p2sh if inp.witness_script.is_some() { @@ -298,17 +277,16 @@ pub fn interpreter_check( ) -> Result<(), Error> { let utxos = prevouts(psbt)?; let utxos = &Prevouts::All(&utxos); + let empty_script_sig = ScriptBuf::new(); + let empty_witness = Witness::default(); for (index, input) in psbt.inputs.iter().enumerate() { - let empty_script_sig = ScriptBuf::new(); - let empty_witness = Witness::default(); let script_sig = input.final_script_sig.as_ref().unwrap_or(&empty_script_sig); let witness = input .final_script_witness .as_ref() - .map(|wit_slice| Witness::from_slice(&wit_slice.to_vec())) // TODO: Update rust-bitcoin psbt API to use witness - .unwrap_or(empty_witness); + .unwrap_or(&empty_witness); - interpreter_inp_check(psbt, secp, index, utxos, &witness, script_sig)?; + interpreter_inp_check(psbt, secp, index, utxos, witness, script_sig)?; } Ok(()) } @@ -445,16 +423,8 @@ pub(super) fn finalize_input( let input = &mut psbt.inputs[index]; input.non_witness_utxo = original.non_witness_utxo; input.witness_utxo = original.witness_utxo; - input.final_script_sig = if script_sig.is_empty() { - None - } else { - Some(script_sig) - }; - input.final_script_witness = if witness.is_empty() { - None - } else { - Some(witness) - }; + input.final_script_sig = (!script_sig.is_empty()).then_some(script_sig); + input.final_script_witness = (!witness.is_empty()).then_some(witness); } Ok(()) diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 47c0600bb..fd3d419f4 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -233,7 +233,7 @@ impl From for InputError { fn from(e: bitcoin::key::FromSliceError) -> Self { Self::KeyErr(e) } } -/// Psbt satisfier for at inputs at a particular index. +/// Psbt satisfier for inputs at a particular index. /// /// Holds a `&psbt` because multiple inputs may share /// the same psbt structure @@ -283,8 +283,9 @@ impl Satisfier for PsbtInputSatisfier<'_> { self.psbt_input() .bip32_derivation .iter() - .find(|&(pubkey, _)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) - .map(|(pubkey, _)| bitcoin::PublicKey::new(*pubkey)) + .find_map(|(&pk, _)| { + (pk.to_pubkeyhash(SigType::Ecdsa) == *pkh).then_some(bitcoin::PublicKey::new(pk)) + }) } fn lookup_tap_control_block_map( @@ -300,10 +301,9 @@ impl Satisfier for PsbtInputSatisfier<'_> { self.psbt_input() .tap_script_sigs .iter() - .find(|&((pubkey, lh), _sig)| { - pubkey.to_pubkeyhash(SigType::Schnorr) == pkh.0 && *lh == pkh.1 + .find_map(|(&(pk, lh), &sig)| { + (pk.to_pubkeyhash(SigType::Schnorr) == pkh.0 && lh == pkh.1).then_some((pk, sig)) }) - .map(|((x_only_pk, _leaf_hash), sig)| (*x_only_pk, *sig)) } fn lookup_ecdsa_sig(&self, pk: &Pk) -> Option { @@ -320,8 +320,7 @@ impl Satisfier for PsbtInputSatisfier<'_> { self.psbt_input() .partial_sigs .iter() - .find(|&(pubkey, _sig)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) - .map(|(pk, sig)| (*pk, *sig)) + .find_map(|(&pk, &sig)| (pk.to_pubkeyhash(SigType::Ecdsa) == *pkh).then_some((pk, sig))) } fn check_after(&self, n: absolute::LockTime) -> bool { @@ -387,13 +386,9 @@ fn sanity_check(psbt: &Psbt) -> Result<(), Error> { // Check well-formedness of input data for (index, input) in psbt.inputs.iter().enumerate() { - // TODO: fix this after https://github.com/rust-bitcoin/rust-bitcoin/issues/838 - let target_ecdsa_sighash_ty = match input.sighash_type { - Some(psbt_hash_ty) => psbt_hash_ty - .ecdsa_hash_ty() - .map_err(|e| Error::InputError(InputError::NonStandardSighashType(e), index))?, - None => sighash::EcdsaSighashType::All, - }; + let target_ecdsa_sighash_ty = input + .ecdsa_hash_ty() + .map_err(|e| Error::InputError(InputError::NonStandardSighashType(e), index))?; for (key, ecdsa_sig) in &input.partial_sigs { let flag = sighash::EcdsaSighashType::from_standard(ecdsa_sig.sighash_type as u32) .map_err(|_| { @@ -599,20 +594,10 @@ impl PsbtExt for Psbt { secp: &secp256k1::Secp256k1, ) -> Result<(), Vec> { // Actually construct the witnesses - let mut errors = vec![]; - for index in 0..self.inputs.len() { - match finalizer::finalize_input(self, index, secp, /*allow_mall*/ false) { - Ok(..) => {} - Err(e) => { - errors.push(e); - } - } - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } + let errors = (0..self.inputs.len()) + .filter_map(|i| finalizer::finalize_input(self, i, secp, /*allow_mall*/ false).err()) + .collect::>(); + errors.is_empty().then_some(()).ok_or(errors) } fn finalize( @@ -629,20 +614,10 @@ impl PsbtExt for Psbt { &mut self, secp: &secp256k1::Secp256k1, ) -> Result<(), Vec> { - let mut errors = vec![]; - for index in 0..self.inputs.len() { - match finalizer::finalize_input(self, index, secp, /*allow_mall*/ true) { - Ok(..) => {} - Err(e) => { - errors.push(e); - } - } - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } + let errors = (0..self.inputs.len()) + .filter_map(|i| finalizer::finalize_input(self, i, secp, /*allow_mall*/ true).err()) + .collect::>(); + errors.is_empty().then_some(()).ok_or(errors) } fn finalize_mall(