From 03c25ec73e29a7558d69f0e6833f09cb76c527bc Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Thu, 29 Sep 2022 11:32:01 +0200 Subject: [PATCH 1/6] Add `PsbtOutputExt::update_with_descriptor` This essentially mirrors `PsbtInputExt::update_with_descriptor` but it works on PSBT outputs. To avoid code duplication, the current logic that deals with PSBT inputs has been generalized to handle both input and outputs with a new internal `PsbtFields` trait. --- src/psbt/mod.rs | 279 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 254 insertions(+), 25 deletions(-) diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index c113c447c..881474708 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}; @@ -568,6 +570,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 @@ -791,7 +808,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 +818,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 +966,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 +1037,7 @@ impl PkTranslator (XonlyPublicKey)/PublicKey struct KeySourceLookUp( - pub BTreeMap, + pub BTreeMap, pub secp256k1::Secp256k1, ); @@ -986,10 +1064,100 @@ impl PkTranslator &mut Option