diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index c113c447c..f46eed174 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -19,6 +19,7 @@ //! `https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki` //! +use core::convert::TryFrom; use core::fmt; use core::ops::Deref; #[cfg(feature = "std")] @@ -26,6 +27,7 @@ use std::error; use bitcoin::hashes::{hash160, sha256d, Hash}; use bitcoin::secp256k1::{self, Secp256k1, VerifyOnly}; +use bitcoin::util::bip32; use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt}; use bitcoin::util::sighash::SighashCache; use bitcoin::util::taproot::{self, ControlBlock, LeafVersion, TapLeafHash}; @@ -547,10 +549,9 @@ pub trait PsbtExt { /// /// This is the checked version of [`update_with_descriptor_unchecked`]. It checks that the /// `witness_utxo` and `non_witness_utxo` are sane and have a `script_pubkey` that matches the - /// descriptor. In particular, it makes sure segwit descriptors always have `witness_utxo` - /// present and pre-segwit descriptors always have `non_witness_utxo` present (and the txid - /// matches). If both `witness_utxo` and `non_witness_utxo` are present then it also checks they - /// are consistent with each other. + /// descriptor. In particular, it makes sure pre-segwit descriptors always have `non_witness_utxo` + /// present (and the txid matches). If both `witness_utxo` and `non_witness_utxo` are present + /// then it also checks they are consistent with each other. /// /// Hint: because of the *[segwit bug]* some PSBT signers require that `non_witness_utxo` is /// present on segwitv0 inputs regardless but this function doesn't enforce this so you will @@ -568,6 +569,21 @@ pub trait PsbtExt { descriptor: &Descriptor, ) -> Result<(), UtxoUpdateError>; + /// Update PSBT output with a descriptor and check consistency of the output's `script_pubkey` + /// + /// This is the checked version of [`update_with_descriptor_unchecked`]. It checks that the + /// output's `script_pubkey` matches the descriptor. + /// + /// The `descriptor` **must not have any wildcards** in it + /// otherwise an error will be returned however it can (and should) have extended keys in it. + /// + /// [`update_with_descriptor_unchecked`]: PsbtOutputExt::update_with_descriptor_unchecked + fn update_output_with_descriptor( + &mut self, + output_index: usize, + descriptor: &Descriptor, + ) -> Result<(), OutputUpdateError>; + /// Get the sighash message(data to sign) at input index `idx` based on the sighash /// flag specified in the [`Psbt`] sighash field. If the input sighash flag psbt field is `None` /// the [`SchnorrSighashType::Default`](bitcoin::util::sighash::SchnorrSighashType::Default) is chosen @@ -762,18 +778,12 @@ impl PsbtExt for Psbt { return Err(UtxoUpdateError::UtxoCheck); } } - (None, Some(non_witness_utxo)) => { - if desc_type.segwit_version().is_some() { - return Err(UtxoUpdateError::UtxoCheck); - } - - non_witness_utxo - .output - .get(txin.previous_output.vout as usize) - .ok_or(UtxoUpdateError::UtxoCheck)? - .script_pubkey - .clone() - } + (None, Some(non_witness_utxo)) => non_witness_utxo + .output + .get(txin.previous_output.vout as usize) + .ok_or(UtxoUpdateError::UtxoCheck)? + .script_pubkey + .clone(), (Some(witness_utxo), Some(non_witness_utxo)) => { if witness_utxo != non_witness_utxo @@ -791,7 +801,7 @@ impl PsbtExt for Psbt { }; let (_, spk_check_passed) = - update_input_with_descriptor_helper(input, desc, Some(expected_spk)) + update_item_with_descriptor_helper(input, desc, Some(&expected_spk)) .map_err(UtxoUpdateError::DerivationError)?; if !spk_check_passed { @@ -801,6 +811,33 @@ impl PsbtExt for Psbt { Ok(()) } + fn update_output_with_descriptor( + &mut self, + output_index: usize, + desc: &Descriptor, + ) -> Result<(), OutputUpdateError> { + let n_outputs = self.outputs.len(); + let output = self + .outputs + .get_mut(output_index) + .ok_or(OutputUpdateError::IndexOutOfBounds(output_index, n_outputs))?; + let txout = self + .unsigned_tx + .output + .get(output_index) + .ok_or(OutputUpdateError::MissingTxOut)?; + + let (_, spk_check_passed) = + update_item_with_descriptor_helper(output, desc, Some(&txout.script_pubkey)) + .map_err(OutputUpdateError::DerivationError)?; + + if !spk_check_passed { + return Err(OutputUpdateError::MismatchedScriptPubkey); + } + + Ok(()) + } + fn sighash_msg>( &self, idx: usize, @@ -922,7 +959,41 @@ impl PsbtInputExt for psbt::Input { &mut self, descriptor: &Descriptor, ) -> Result, descriptor::ConversionError> { - let (derived, _) = update_input_with_descriptor_helper(self, descriptor, None)?; + let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; + Ok(derived) + } +} + +/// Extension trait for PSBT outputs +pub trait PsbtOutputExt { + /// Given the descriptor of a PSBT output populate the relevant metadata + /// + /// If the descriptor contains wildcards or otherwise cannot be transformed into a concrete + /// descriptor an error will be returned. The descriptor *can* (and should) have extended keys in + /// it so PSBT fields like `bip32_derivation` and `tap_key_origins` can be populated. + /// + /// Note that this method doesn't check that the `script_pubkey` of the output being + /// updated matches the descriptor. To do that see [`update_output_with_descriptor`]. + /// + /// ## Return value + /// + /// For convenience, this returns the concrete descriptor that is computed internally to fill + /// out the PSBT output fields. This can be used to manually check that the `script_pubkey` is + /// consistent with the descriptor. + /// + /// [`update_output_with_descriptor`]: PsbtExt::update_output_with_descriptor + fn update_with_descriptor_unchecked( + &mut self, + descriptor: &Descriptor, + ) -> Result, descriptor::ConversionError>; +} + +impl PsbtOutputExt for psbt::Output { + fn update_with_descriptor_unchecked( + &mut self, + descriptor: &Descriptor, + ) -> Result, descriptor::ConversionError> { + let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; Ok(derived) } } @@ -959,7 +1030,7 @@ impl PkTranslator (XonlyPublicKey)/PublicKey struct KeySourceLookUp( - pub BTreeMap, + pub BTreeMap, pub secp256k1::Secp256k1, ); @@ -986,10 +1057,100 @@ impl PkTranslator &mut Option