From c2b84938e0644612b3c67ea26b2970cb96df0960 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 23 Jan 2024 08:12:30 +1100 Subject: [PATCH 1/4] v0: Comment incomplete extractor The current v0 extractor code only works for the happy path, it does not do checks on the input PSBT as specified by the bip. Add a TODO and also copy the relevant text from bip-174. --- src/v0/extractor.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/v0/extractor.rs b/src/v0/extractor.rs index a8b2bad..633e905 100644 --- a/src/v0/extractor.rs +++ b/src/v0/extractor.rs @@ -60,8 +60,23 @@ impl Psbt { /// [`extract_tx_fee_rate_limit`]: Psbt::extract_tx_fee_rate_limit pub fn extract_tx_unchecked_fee_rate(self) -> Transaction { self.internal_extract_tx() } + // TODO: This is incomplete, it does not do the checks specified in the bip. #[inline] fn internal_extract_tx(self) -> Transaction { + // The Transaction Extractor must only accept a PSBT. It checks whether all inputs have + // complete scriptSigs and scriptWitnesses by checking for the presence of 0x07 Finalized + // scriptSig and 0x08 Finalized scriptWitness typed records. If they do, the Transaction + // Extractor should construct complete scriptSigs and scriptWitnesses and encode them into + // network serialized transactions. Otherwise the Extractor must not modify the PSBT. The + // Extractor should produce a fully valid, network serialized transaction if all inputs are + // complete. + + // The Transaction Extractor does not need to know how to interpret scripts in order to + // extract the network serialized transaction. However it may be able to in order to + // validate the network serialized transaction at the same time. + + // A single entity is likely to be both a Transaction Extractor and an Input Finalizer. + let mut tx: Transaction = self.global.unsigned_tx; for (vin, psbtin) in tx.input.iter_mut().zip(self.inputs.into_iter()) { From 950307c10a95866140839bb3e674a5106747f85b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 23 Jan 2024 08:28:12 +1100 Subject: [PATCH 2/4] v2: Remove non-built miniscript code This code was just cut'n'paste and isn't currently being built. Just delete it and we will start from scratch. --- src/v2/miniscript/mod.rs | 1467 ---------------------------------- src/v2/miniscript/satisfy.rs | 124 --- 2 files changed, 1591 deletions(-) delete mode 100644 src/v2/miniscript/mod.rs delete mode 100644 src/v2/miniscript/satisfy.rs diff --git a/src/v2/miniscript/mod.rs b/src/v2/miniscript/mod.rs deleted file mode 100644 index 90027b9..0000000 --- a/src/v2/miniscript/mod.rs +++ /dev/null @@ -1,1467 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Implementation of the Finalizer and Extractor roles defined in [BIP-174]. -//! -//! [BIP-174]: - -mod error; -mod extractor; -mod satisfy; - -use core::convert::TryFrom; -use core::mem; - -use crate::bitcoin::hashes::{hash160, Hash}; -use crate::bitcoin::key::XOnlyPublicKey; -use crate::bitcoin::secp256k1::{self, Message, Secp256k1, Verification, VerifyOnly}; -use crate::bitcoin::sighash::{self, EcdsaSighashType, Prevouts, SighashCache, TapSighashType}; -use crate::bitcoin::taproot::{ - ControlBlock, LeafVersion, TapLeafHash, TapNodeHash, TapTree, TaprootBuilder, -}; -use crate::bitcoin::{bip32, Address, Network, Script, ScriptBuf, TxOut, VarInt, Witness}; -use crate::miniscript::miniscript::satisfy::Placeholder; -use crate::miniscript::{ - descriptor, interpreter, translate_hash_clone, BareCtx, DefiniteDescriptorKey, Descriptor, - DescriptorPublicKey, ExtParams, Interpreter, Legacy, Miniscript, MiniscriptKey, Satisfier, - Segwitv0, SigType, Tap, ToPublicKey, TranslatePk, Translator, -}; -use crate::prelude::*; -use crate::raw; -use crate::v2::map::{Input, Output}; -use crate::v2::Psbt; - -const ALLOW_MAL: bool = true; -const NO_MAL: bool = false; - -#[rustfmt::skip] // Keep public re-exports separate. -pub use self::{ - error::{Error, InputError, OutputUpdateError, SighashError, UtxoUpdateError}, - satisfy::PsbtInputSatisfier, -}; - -impl Psbt { - /// Same as [`Psbt::finalize_mut`], but does not mutate the input psbt and - /// returns a new psbt - /// - /// # Errors: - /// - /// - Returns a mutated psbt with all inputs `finalize_mut` could finalize - /// - A vector of input errors, one of each of failed finalized input - pub fn finalize( - mut self, - secp: &Secp256k1, - ) -> Result)> { - match self.finalize_mut(secp) { - Ok(..) => Ok(self), - Err(e) => Err((self, e)), - } - } - - /// Finalize the psbt. This function takes in a mutable reference to psbt - /// and populates the final_witness and final_scriptsig - /// for all miniscript inputs. - /// - /// Finalizes all inputs that it can finalize, and returns an error for each input - /// that it cannot finalize. Also performs a sanity interpreter check on the - /// finalized psbt which involves checking the signatures/ preimages/timelocks. - /// - /// Input finalization also fails if it is not possible to satisfy any of the inputs non-malleably - /// See [finalizer::finalize_mall] if you want to allow malleable satisfactions - /// - /// For finalizing individual inputs, see also [`Psbt::finalize_inp`] - /// - /// # Errors: - /// - /// - A vector of errors, one of each of failed finalized input - pub fn finalize_mut(&mut self, secp: &Secp256k1) -> Result<(), Vec> { - // Actually construct the witnesses - let mut errors = vec![]; - for index in 0..self.inputs.len() { - match self._finalize_input(index, secp, NO_MAL) { - Ok(..) => {} - Err(e) => { - errors.push(e); - } - } - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } - - /// Same as [Psbt::finalize], but allows for malleable satisfactions - pub fn finalize_mall( - mut self, - secp: &Secp256k1, - ) -> Result)> { - match self.finalize_mall_mut(secp) { - Ok(..) => Ok(self), - Err(e) => Err((self, e)), - } - } - - /// Same as [Psbt::finalize_mut], but allows for malleable satisfactions - pub fn finalize_mall_mut( - &mut self, - secp: &Secp256k1, - ) -> Result<(), Vec> { - let mut errors = vec![]; - for index in 0..self.inputs.len() { - match self._finalize_input(index, secp, ALLOW_MAL) { - Ok(..) => {} - Err(e) => { - errors.push(e); - } - } - } - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } - - /// Same as [`Psbt::finalize_inp_mut`], but does not mutate the psbt and returns a new one - /// - /// # Errors: - /// Returns a tuple containing - /// - Original psbt - /// - Input Error detailing why the input finalization failed - pub fn finalize_inp( - mut self, - secp: &Secp256k1, - index: usize, - ) -> Result { - match self.finalize_inp_mut(secp, index) { - Ok(..) => Ok(self), - Err(e) => Err((self, e)), - } - } - - /// Same as [`Psbt::finalize_mut`], but only tries to finalize a single input leaving other - /// inputs as is. Use this when not all of inputs that you are trying to - /// satisfy are miniscripts - /// - /// # Errors: - /// - /// - Input error detailing why the finalization failed. The psbt is not mutated when the finalization fails - pub fn finalize_inp_mut( - &mut self, - secp: &Secp256k1, - index: usize, - ) -> Result<(), Error> { - if index >= self.inputs.len() { - return Err(Error::InputIdxOutofBounds { psbt_inp: self.inputs.len(), index }); - } - self._finalize_input(index, secp, NO_MAL) - } - - /// Same as [`Psbt::finalize_inp`], but allows for malleable satisfactions - pub fn finalize_inp_mall( - mut self, - secp: &Secp256k1, - index: usize, - ) -> Result { - match self.finalize_inp_mall_mut(secp, index) { - Ok(..) => Ok(self), - Err(e) => Err((self, e)), - } - } - - /// Same as [`Psbt::finalize_inp_mut`], but allows for malleable satisfactions - pub fn finalize_inp_mall_mut( - &mut self, - secp: &Secp256k1, - index: usize, - ) -> Result<(), Error> { - if index >= self.inputs.len() { - return Err(Error::InputIdxOutofBounds { psbt_inp: self.inputs.len(), index }); - } - self._finalize_input(index, secp, NO_MAL) - } - - fn _finalize( - &mut self, - secp: &Secp256k1, - allow_mall: bool, - ) -> Result<(), Error> { - self.sanity_check()?; - - // Actually construct the witnesses - for index in 0..self.inputs.len() { - self._finalize_input(index, secp, allow_mall)?; - } - // Interpreter is already run inside finalize_input for each input - Ok(()) - } - - fn _finalize_input( - &mut self, - index: usize, - secp: &Secp256k1, - allow_mall: bool, - ) -> Result<(), Error> { todo!() } - - // Helper function to obtain psbt final_witness/final_script_sig. - // Does not add fields to the psbt, only returns the values. - fn __finalize_input( - &self, - index: usize, - secp: &Secp256k1, - allow_mall: bool, - ) -> Result<(Witness, ScriptBuf), Error> { - let (witness, script_sig) = { - let spk = self.get_scriptpubkey(index).map_err(|e| Error::InputError(e, index))?; - let sat = PsbtInputSatisfier::new(self, index); - - if spk.is_p2tr() { - // Deal with tr case separately, unfortunately we cannot infer the full descriptor for Tr - let wit = construct_tap_witness(&spk, &sat, allow_mall) - .map_err(|e| Error::InputError(e, index))?; - (wit, ScriptBuf::new()) - } else { - // Get a descriptor for this input. - let desc = self.get_descriptor(index).map_err(|e| Error::InputError(e, index))?; - - //generate the satisfaction witness and scriptsig - let sat = PsbtInputSatisfier::new(self, index); - if !allow_mall { - desc.get_satisfaction(sat) - } else { - desc.get_satisfaction_mall(sat) - } - .map_err(|e| Error::InputError(InputError::MiniscriptError(e), index))? - } - }; - - let witness = Witness::from_slice(&witness); - let utxos = self.prevouts()?; - let utxos = &Prevouts::All(&utxos); - self.interpreter_inp_check(secp, index, utxos, &witness, &script_sig)?; - - Ok((witness, script_sig)) - } - - /// Update PSBT input with a descriptor and check consistency of `*_utxo` fields. - /// - /// 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 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 - /// have to do this check its presence manually (if it is present this *will* check its - /// validity). - /// - /// 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`]: PsbtInputExt::update_with_descriptor_unchecked - /// [segwit bug]: https://bitcoinhackers.org/@lukedashjr/104287698361196952 - pub fn update_input_with_descriptor( - &mut self, - input_index: usize, - desc: &Descriptor, - ) -> Result<(), UtxoUpdateError> { - let n_inputs = self.inputs.len(); - let input = self - .inputs - .get_mut(input_index) - .ok_or(UtxoUpdateError::IndexOutOfBounds(input_index, n_inputs))?; - let txin = self - .global - .unsigned_tx - .input - .get(input_index) - .ok_or(UtxoUpdateError::MissingInputUtxo)?; - - let desc_type = desc.desc_type(); - - if let Some(non_witness_utxo) = &input.non_witness_utxo { - if txin.previous_output.txid != non_witness_utxo.txid() { - return Err(UtxoUpdateError::UtxoCheck); - } - } - - let expected_spk = { - match (&input.witness_utxo, &input.non_witness_utxo) { - (Some(witness_utxo), None) => - if desc_type.segwit_version().is_some() { - witness_utxo.script_pubkey.clone() - } else { - return Err(UtxoUpdateError::UtxoCheck); - }, - (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 - .output - .get(txin.previous_output.vout as usize) - .ok_or(UtxoUpdateError::UtxoCheck)? - { - return Err(UtxoUpdateError::UtxoCheck); - } - - witness_utxo.script_pubkey.clone() - } - (None, None) => return Err(UtxoUpdateError::UtxoCheck), - } - }; - - let (_, spk_check_passed) = - update_item_with_descriptor_helper(input, desc, Some(&expected_spk)) - .map_err(UtxoUpdateError::DerivationError)?; - - if !spk_check_passed { - return Err(UtxoUpdateError::MismatchedScriptPubkey); - } - - Ok(()) - } - - /// 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 - pub 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 - .global - .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(()) - } - - /// 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 [`sighash::TapSighashType::Default`](bitcoin::sighash::TapSighashType::Default) is chosen - /// for for taproot spends, otherwise [`EcdsaSighashType::All`](bitcoin::sighash::EcdsaSighashType::All) is chosen. - /// If the utxo at `idx` is a taproot output, returns a [`PsbtSighashMsg::TapSighash`] variant. - /// If the utxo at `idx` is a pre-taproot segwit output, returns a [`PsbtSighashMsg::SegwitV0Sighash`] variant. - /// For legacy outputs, returns a [`PsbtSighashMsg::LegacySighash`] variant. - /// The `tapleaf_hash` parameter can be used to specify which tapleaf script hash has to be computed. If - /// `tapleaf_hash` is [`None`], and the output is taproot output, the key spend hash is computed. This parameter must be - /// set to [`None`] while computing sighash for pre-taproot outputs. - /// The function also updates the sighash cache with transaction computed during sighash computation of this input - /// - /// # Arguments: - /// - /// * `idx`: The input index of psbt to sign - /// * `cache`: The [`SighashCache`] for used to cache/read previously cached computations - /// * `tapleaf_hash`: If the output is taproot, compute the sighash for this particular leaf. - /// - /// [`SighashCache`]: bitcoin::sighash::SighashCache - #[allow(deprecated)] // Still using segwit_signature_hash - pub fn sighash_msg>( - &self, - idx: usize, - cache: &mut SighashCache, - tapleaf_hash: Option, - ) -> Result { - // Infer a descriptor at idx - if idx >= self.inputs.len() { - return Err(SighashError::IndexOutOfBounds(idx, self.inputs.len())); - } - let inp = &self.inputs[idx]; - let prevouts = self.prevouts().map_err(|_e| SighashError::MissingSpendUtxos)?; - // Note that as per Psbt spec we should have access to spent_utxos for the transaction - // Even if the transaction does not require SighashAll, we create `Prevouts::All` for code simplicity - let prevouts = sighash::Prevouts::All(&prevouts); - let inp_spk = self.get_scriptpubkey(idx).map_err(|_e| SighashError::MissingInputUtxo)?; - if inp_spk.is_p2tr() { - let hash_ty = inp - .sighash_type - .map(|sighash_type| sighash_type.taproot_hash_ty()) - .unwrap_or(Ok(TapSighashType::Default)) - .map_err(|_e| SighashError::InvalidSighashType)?; - match tapleaf_hash { - Some(leaf_hash) => { - let tap_sighash_msg = cache - .taproot_script_spend_signature_hash(idx, &prevouts, leaf_hash, hash_ty)?; - Ok(PsbtSighashMsg::TapSighash(tap_sighash_msg)) - } - None => { - let tap_sighash_msg = - cache.taproot_key_spend_signature_hash(idx, &prevouts, hash_ty)?; - Ok(PsbtSighashMsg::TapSighash(tap_sighash_msg)) - } - } - } else { - let hash_ty = inp - .sighash_type - .map(|sighash_type| sighash_type.ecdsa_hash_ty()) - .unwrap_or(Ok(EcdsaSighashType::All)) - .map_err(|_e| SighashError::InvalidSighashType)?; - let amt = self.get_utxo(idx).map_err(|_e| SighashError::MissingInputUtxo)?.value; - let is_nested_wpkh = inp_spk.is_p2sh() - && inp.redeem_script.as_ref().map(|x| x.is_p2wpkh()).unwrap_or(false); - let is_nested_wsh = inp_spk.is_p2sh() - && inp.redeem_script.as_ref().map(|x| x.is_p2wsh()).unwrap_or(false); - if inp_spk.is_p2wpkh() || inp_spk.is_p2wsh() || is_nested_wpkh || is_nested_wsh { - let msg = if inp_spk.is_p2wpkh() { - let script_code = - inp_spk.p2wpkh_script_code().expect("checked is p2wpkh above"); - cache.segwit_signature_hash(idx, &script_code, amt, hash_ty)? - } else if is_nested_wpkh { - let script_code = inp - .redeem_script - .as_ref() - .expect("redeem script non-empty checked earlier") - .p2wpkh_script_code() - .expect("checked is p2wpkh above"); - cache.segwit_signature_hash(idx, &script_code, amt, hash_ty)? - } else { - // wsh and nested wsh, script code is witness script - let script_code = - inp.witness_script.as_ref().ok_or(SighashError::MissingWitnessScript)?; - cache.segwit_signature_hash(idx, script_code, amt, hash_ty)? - }; - Ok(PsbtSighashMsg::SegwitV0Sighash(msg)) - } else { - // legacy sighash case - let script_code = if inp_spk.is_p2sh() { - inp.redeem_script.as_ref().ok_or(SighashError::MissingRedeemScript)? - } else { - &inp_spk - }; - let msg = cache.legacy_signature_hash(idx, script_code, hash_ty.to_u32())?; - Ok(PsbtSighashMsg::LegacySighash(msg)) - } - } - } - - // Basic sanity checks on psbts. - // rust-bitcoin TODO: (Long term) - // Brainstorm about how we can enforce these in type system while having a nice API - fn sanity_check(&self) -> Result<(), Error> { - if self.global.unsigned_tx.input.len() != self.inputs.len() { - return Err(Error::WrongInputCount { - in_tx: self.global.unsigned_tx.input.len(), - in_map: self.inputs.len(), - }); - } - - // Check well-formedness of input data - for (index, input) in self.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, - }; - for (key, ecdsa_sig) in &input.partial_sigs { - let flag = sighash::EcdsaSighashType::from_standard(ecdsa_sig.hash_ty as u32) - .map_err(|_| { - Error::InputError( - InputError::Interpreter(interpreter::Error::NonStandardSighash( - ecdsa_sig.to_vec(), - )), - index, - ) - })?; - if target_ecdsa_sighash_ty != flag { - return Err(Error::InputError( - InputError::WrongSighashFlag { - required: target_ecdsa_sighash_ty, - got: flag, - pubkey: *key, - }, - index, - )); - } - // Signatures are well-formed in psbt partial sigs - } - } - - Ok(()) - } - - /// Gets the scriptpubkey for the psbt input. - fn get_scriptpubkey(&self, index: usize) -> Result { - self.get_utxo(index).map(|utxo| utxo.script_pubkey.clone()) - } - - /// Gets the spending utxo for this psbt input. - fn get_utxo(&self, index: usize) -> Result<&TxOut, InputError> { - let inp = &self.inputs[index]; - let utxo = if let Some(ref witness_utxo) = inp.witness_utxo { - witness_utxo - } else if let Some(ref non_witness_utxo) = inp.non_witness_utxo { - let vout = self.global.unsigned_tx.input[index].previous_output.vout; - &non_witness_utxo.output[vout as usize] - } else { - return Err(InputError::MissingUtxo); - }; - Ok(utxo) - } - - /// Gets the Prevouts for this psbt. - fn prevouts(&self) -> Result, Error> { - let mut utxos = vec![]; - for i in 0..self.inputs.len() { - let utxo_ref = self.get_utxo(i).map_err(|e| Error::InputError(e, i))?; - utxos.push(utxo_ref); - } - Ok(utxos) - } - - /// Creates a descriptor from unfinalized PSBT input. - /// - /// Panics on out of bound input index for psbt Also sanity checks that the witness script and - /// redeem script are consistent with the script pubkey. Does *not* check signatures We parse - /// the insane version while satisfying because we want to move the script is probably already - /// created and we want to satisfy it in any way possible. - fn get_descriptor(&self, index: usize) -> Result, InputError> { - let mut map: BTreeMap = BTreeMap::new(); - let psbt_inputs = &self.inputs; - for psbt_input in psbt_inputs { - // Use BIP32 Derviation to get set of all possible keys. - let public_keys = psbt_input.bip32_derivation.keys(); - for key in public_keys { - let bitcoin_key = bitcoin::PublicKey::new(*key); - let hash = bitcoin_key.pubkey_hash().to_raw_hash(); - map.insert(hash, bitcoin_key); - } - } - - // Figure out Scriptpubkey - let script_pubkey = self.get_scriptpubkey(index)?; - let inp = &self.inputs[index]; - // 1. `PK`: creates a `Pk` descriptor(does not check if partial sig is given) - if script_pubkey.is_p2pk() { - let script_pubkey_len = script_pubkey.len(); - let pk_bytes = &script_pubkey.to_bytes(); - match bitcoin::PublicKey::from_slice(&pk_bytes[1..script_pubkey_len - 1]) { - Ok(pk) => Ok(Descriptor::new_pk(pk)), - Err(e) => Err(InputError::from(e)), - } - } 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 = Address::p2pkh(&pk, 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), - } - } 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)| { - // Indirect way to check the equivalence of pubkey-hashes. - // Create a pubkey hash and check if they are the same. - let addr = Address::p2wpkh(&pk, Network::Bitcoin) - .expect("Address corresponding to valid pubkey"); - *script_pubkey == addr.script_pubkey() - }); - match partial_sig_contains_pk { - Some((pk, _sig)) => Ok(Descriptor::new_wpkh(*pk)?), - None => Err(InputError::MissingPubkey), - } - } else if script_pubkey.is_p2wsh() { - // 4. `Wsh`: creates a `Wsh` descriptor - if inp.redeem_script.is_some() { - return Err(InputError::NonEmptyRedeemScript); - } - if let Some(ref witness_script) = inp.witness_script { - if witness_script.to_p2wsh() != *script_pubkey { - return Err(InputError::InvalidWitnessScript { - witness_script: witness_script.clone(), - p2wsh_expected: script_pubkey.clone(), - }); - } - let ms = Miniscript::::parse_with_ext( - witness_script, - &ExtParams::allow_all(), - )?; - Ok(Descriptor::new_wsh(ms.substitute_raw_pkh(&map))?) - } else { - Err(InputError::MissingWitnessScript) - } - } else if script_pubkey.is_p2sh() { - match inp.redeem_script { - None => Err(InputError::MissingRedeemScript), - Some(ref redeem_script) => { - if redeem_script.to_p2sh() != *script_pubkey { - return Err(InputError::InvalidRedeemScript { - redeem: redeem_script.clone(), - p2sh_expected: script_pubkey.clone(), - }); - } - if redeem_script.is_p2wsh() { - // 5. `ShWsh` case - if let Some(ref witness_script) = inp.witness_script { - if witness_script.to_p2wsh() != *redeem_script { - return Err(InputError::InvalidWitnessScript { - witness_script: witness_script.clone(), - p2wsh_expected: redeem_script.clone(), - }); - } - let ms = Miniscript::::parse_with_ext( - witness_script, - &ExtParams::allow_all(), - )?; - Ok(Descriptor::new_sh_wsh(ms.substitute_raw_pkh(&map))?) - } else { - Err(InputError::MissingWitnessScript) - } - } else if redeem_script.is_p2wpkh() { - // 6. `ShWpkh` case - let partial_sig_contains_pk = - inp.partial_sigs.iter().find(|&(&pk, _sig)| { - let addr = Address::p2wpkh(&pk, Network::Bitcoin) - .expect("Address corresponding to valid pubkey"); - *redeem_script == addr.script_pubkey() - }); - match partial_sig_contains_pk { - Some((pk, _sig)) => Ok(Descriptor::new_sh_wpkh(*pk)?), - None => Err(InputError::MissingPubkey), - } - } else { - //7. regular p2sh - if inp.witness_script.is_some() { - return Err(InputError::NonEmptyWitnessScript); - } - if let Some(ref redeem_script) = inp.redeem_script { - let ms = Miniscript::::parse_with_ext( - redeem_script, - &ExtParams::allow_all(), - )?; - Ok(Descriptor::new_sh(ms)?) - } else { - Err(InputError::MissingWitnessScript) - } - } - } - } - } else { - // 8. Bare case - if inp.witness_script.is_some() { - return Err(InputError::NonEmptyWitnessScript); - } - if inp.redeem_script.is_some() { - return Err(InputError::NonEmptyRedeemScript); - } - let ms = Miniscript::::parse_with_ext( - &script_pubkey, - &ExtParams::allow_all(), - )?; - Ok(Descriptor::new_bare(ms.substitute_raw_pkh(&map))?) - } - } - - /// Interprets all psbt inputs and checks whether the - /// script is correctly interpreted according to the context - /// The psbt must have included final script sig and final witness. - /// In other words, this checks whether the finalized psbt interprets - /// correctly - pub fn interpreter_check(&self, secp: &Secp256k1) -> Result<(), Error> { - let utxos = self.prevouts()?; - let utxos = &Prevouts::All(&utxos); - for (index, input) in self.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); - - self.interpreter_inp_check(secp, index, utxos, &witness, script_sig)?; - } - Ok(()) - } - - // Runs the miniscript interpreter on a single psbt input. - fn interpreter_inp_check>( - &self, - secp: &Secp256k1, - index: usize, - utxos: &Prevouts, - witness: &Witness, - script_sig: &Script, - ) -> Result<(), Error> { - let spk = self.get_scriptpubkey(index).map_err(|e| Error::InputError(e, index))?; - - // Now look at all the satisfied constraints. If everything is filled in - // corrected, there should be no errors - // Interpreter check - { - let cltv = self.global.unsigned_tx.lock_time; - let csv = self.global.unsigned_tx.input[index].sequence; - let interpreter = Interpreter::from_txdata(&spk, script_sig, witness, csv, cltv) - .map_err(|e| Error::InputError(InputError::Interpreter(e), index))?; - let iter = interpreter.iter(secp, &self.global.unsigned_tx, index, utxos); - if let Some(error) = iter.filter_map(Result::err).next() { - return Err(Error::InputError(InputError::Interpreter(error), index)); - }; - } - Ok(()) - } -} - -impl Input { - /// Given the descriptor for a utxo being spent populate the PSBT input's fields so it can be signed. - /// - /// 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 his method doesn't check that the `witness_utxo` or `non_witness_utxo` is - /// consistent with the descriptor. To do that see [`update_input_with_descriptor`]. - /// - /// ## Return value - /// - /// For convenience, this returns the concrete descriptor that is computed internally to fill - /// out the PSBT input fields. This can be used to manually check that the `script_pubkey` in - /// `witness_utxo` and/or `non_witness_utxo` is consistent with the descriptor. - /// - /// [`update_input_with_descriptor`]: Psbt::update_input_with_descriptor - pub fn update_with_descriptor_unchecked( - &mut self, - descriptor: &Descriptor, - ) -> Result, descriptor::ConversionError> { - let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; - Ok(derived) - } -} - -impl Output { - /// 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`]: Psbt::update_output_with_descriptor - pub fn update_with_descriptor_unchecked( - &mut self, - descriptor: &Descriptor, - ) -> Result, descriptor::ConversionError> { - let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; - Ok(derived) - } -} - -// Traverse the pkh lookup while maintaining a reverse map for storing the map -// hash160 -> (XonlyPublicKey)/PublicKey -struct KeySourceLookUp( - pub BTreeMap, - pub Secp256k1, -); - -impl Translator - for KeySourceLookUp -{ - fn pk( - &mut self, - xpk: &DefiniteDescriptorKey, - ) -> Result { - let derived = xpk.derive_public_key(&self.1)?; - self.0.insert( - derived.to_public_key().inner, - ( - xpk.master_fingerprint(), - xpk.full_derivation_path().ok_or(descriptor::ConversionError::MultiKey)?, - ), - ); - Ok(derived) - } - - translate_hash_clone!(DescriptorPublicKey, bitcoin::PublicKey, descriptor::ConversionError); -} - -// Provides generalized access to PSBT fields common to inputs and outputs -trait PsbtFields { - // Common fields are returned as a mutable ref of the same type - fn redeem_script(&mut self) -> &mut Option; - fn witness_script(&mut self) -> &mut Option; - fn bip32_derivation(&mut self) -> &mut BTreeMap; - fn tap_internal_key(&mut self) -> &mut Option; - fn tap_key_origins( - &mut self, - ) -> &mut BTreeMap, bip32::KeySource)>; - fn proprietary(&mut self) -> &mut BTreeMap>; - fn unknown(&mut self) -> &mut BTreeMap>; - - // `tap_tree` only appears in Output, so it's returned as an option of a mutable ref - fn tap_tree(&mut self) -> Option<&mut Option> { None } - - // `tap_scripts` and `tap_merkle_root` only appear in psbt::Input - fn tap_scripts(&mut self) -> Option<&mut BTreeMap> { - None - } - fn tap_merkle_root(&mut self) -> Option<&mut Option> { None } -} - -impl PsbtFields for Input { - fn redeem_script(&mut self) -> &mut Option { &mut self.redeem_script } - fn witness_script(&mut self) -> &mut Option { &mut self.witness_script } - fn bip32_derivation(&mut self) -> &mut BTreeMap { - &mut self.bip32_derivation - } - fn tap_internal_key(&mut self) -> &mut Option { &mut self.tap_internal_key } - fn tap_key_origins( - &mut self, - ) -> &mut BTreeMap, bip32::KeySource)> { - &mut self.tap_key_origins - } - fn proprietary(&mut self) -> &mut BTreeMap> { - &mut self.proprietary - } - fn unknown(&mut self) -> &mut BTreeMap> { &mut self.unknown } - - fn tap_scripts(&mut self) -> Option<&mut BTreeMap> { - Some(&mut self.tap_scripts) - } - fn tap_merkle_root(&mut self) -> Option<&mut Option> { - Some(&mut self.tap_merkle_root) - } -} - -impl PsbtFields for Output { - fn redeem_script(&mut self) -> &mut Option { &mut self.redeem_script } - fn witness_script(&mut self) -> &mut Option { &mut self.witness_script } - fn bip32_derivation(&mut self) -> &mut BTreeMap { - &mut self.bip32_derivation - } - fn tap_internal_key(&mut self) -> &mut Option { - &mut self.tap_internal_key - } - fn tap_key_origins( - &mut self, - ) -> &mut BTreeMap, bip32::KeySource)> { - &mut self.tap_key_origins - } - fn proprietary(&mut self) -> &mut BTreeMap> { - &mut self.proprietary - } - fn unknown(&mut self) -> &mut BTreeMap> { &mut self.unknown } - - fn tap_tree(&mut self) -> Option<&mut Option> { Some(&mut self.tap_tree) } -} - -// Satisfy the taproot descriptor. It is not possible to infer the complete -// descriptor from psbt because the information about all the scripts might not -// be present. Also, currently the spec does not support hidden branches, so -// inferring a descriptor is not possible -fn construct_tap_witness( - spk: &Script, - sat: &PsbtInputSatisfier, - allow_mall: bool, -) -> Result>, InputError> { - // 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 { - // 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); - } - } - 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()]); - } - // Next script spends - let (mut min_wit, mut min_wit_len) = (None, None); - if let Some(block_map) = - >::lookup_tap_control_block_map(sat) - { - for (control_block, (script, ver)) in block_map { - if *ver != LeafVersion::TapScript { - // We don't know how to satisfy non default version scripts yet - continue; - } - let ms = match Miniscript::::parse_with_ext( - script, - &ExtParams::allow_all(), - ) { - Ok(ms) => ms.substitute_raw_pkh(&map), - Err(..) => continue, // try another script - }; - let mut wit = if allow_mall { - match ms.satisfy_malleable(sat) { - Ok(ms) => ms, - Err(..) => continue, - } - } else { - match ms.satisfy(sat) { - Ok(ms) => ms, - Err(..) => continue, - } - }; - wit.push(ms.encode().into_bytes()); - wit.push(control_block.serialize()); - let wit_len = Some(witness_size(&wit)); - if min_wit_len.is_some() && wit_len > min_wit_len { - continue; - } else { - // store the minimum - min_wit = Some(wit); - min_wit_len = wit_len; - } - } - min_wit.ok_or(InputError::CouldNotSatisfyTr) - } else { - // No control blocks found - Err(InputError::CouldNotSatisfyTr) - } -} - -fn update_item_with_descriptor_helper( - item: &mut F, - descriptor: &Descriptor, - check_script: Option<&Script>, - // the return value is a tuple here since the two internal calls to it require different info. - // One needs the derived descriptor and the other needs to know whether the script_pubkey check - // failed. -) -> Result<(Descriptor, bool), descriptor::ConversionError> { - let secp = Secp256k1::verification_only(); - - let derived = if let Descriptor::Tr(_) = &descriptor { - let derived = descriptor.derived_descriptor(&secp)?; - - if let Some(check_script) = check_script { - if check_script != &derived.script_pubkey() { - return Ok((derived, false)); - } - } - - // NOTE: they will both always be Tr - if let (Descriptor::Tr(tr_derived), Descriptor::Tr(tr_xpk)) = (&derived, descriptor) { - let spend_info = tr_derived.spend_info(); - let ik_derived = spend_info.internal_key(); - let ik_xpk = tr_xpk.internal_key(); - if let Some(merkle_root) = item.tap_merkle_root() { - *merkle_root = spend_info.merkle_root(); - } - *item.tap_internal_key() = Some(ik_derived); - item.tap_key_origins().insert( - ik_derived, - ( - vec![], - ( - ik_xpk.master_fingerprint(), - ik_xpk - .full_derivation_path() - .ok_or(descriptor::ConversionError::MultiKey)?, - ), - ), - ); - - let mut builder = TaprootBuilder::new(); - - for ((_depth_der, ms_derived), (depth, ms)) in - tr_derived.iter_scripts().zip(tr_xpk.iter_scripts()) - { - debug_assert_eq!(_depth_der, depth); - let leaf_script = (ms_derived.encode(), LeafVersion::TapScript); - let tapleaf_hash = TapLeafHash::from_script(&leaf_script.0, leaf_script.1); - builder = builder - .add_leaf(depth, leaf_script.0.clone()) - .expect("Computing spend data on a valid tree should always succeed"); - if let Some(tap_scripts) = item.tap_scripts() { - let control_block = spend_info - .control_block(&leaf_script) - .expect("Control block must exist in script map for every known leaf"); - tap_scripts.insert(control_block, leaf_script); - } - - for (pk_pkh_derived, pk_pkh_xpk) in ms_derived.iter_pk().zip(ms.iter_pk()) { - let (xonly, xpk) = (pk_pkh_derived.to_x_only_pubkey(), pk_pkh_xpk); - - let xpk_full_derivation_path = - xpk.full_derivation_path().ok_or(descriptor::ConversionError::MultiKey)?; - item.tap_key_origins() - .entry(xonly) - .and_modify(|(tapleaf_hashes, _)| { - if tapleaf_hashes.last() != Some(&tapleaf_hash) { - tapleaf_hashes.push(tapleaf_hash); - } - }) - .or_insert_with(|| { - ( - vec![tapleaf_hash], - (xpk.master_fingerprint(), xpk_full_derivation_path), - ) - }); - } - } - - // Ensure there are no duplicated leaf hashes. This can happen if some of them were - // already present in the map when this function is called, since this only appends new - // data to the psbt without checking what's already present. - for (tapleaf_hashes, _) in item.tap_key_origins().values_mut() { - tapleaf_hashes.sort(); - tapleaf_hashes.dedup(); - } - - match item.tap_tree() { - // Only set the tap_tree if the item supports it (it's an output) and the descriptor actually - // contains one, otherwise it'll just be empty - Some(tap_tree) if tr_derived.tap_tree().is_some() => { - *tap_tree = - Some(TapTree::try_from(builder).expect("The tree should always be valid")); - } - _ => {} - } - } - - derived - } else { - let mut bip32_derivation = KeySourceLookUp(BTreeMap::new(), Secp256k1::verification_only()); - let derived = descriptor - .translate_pk(&mut bip32_derivation) - .map_err(|e| e.expect_translator_err("No Outer Context errors in translations"))?; - - if let Some(check_script) = check_script { - if check_script != &derived.script_pubkey() { - return Ok((derived, false)); - } - } - - item.bip32_derivation().append(&mut bip32_derivation.0); - - match &derived { - Descriptor::Bare(_) | Descriptor::Pkh(_) | Descriptor::Wpkh(_) => {} - Descriptor::Sh(sh) => match sh.as_inner() { - descriptor::ShInner::Wsh(wsh) => { - *item.witness_script() = Some(wsh.inner_script()); - *item.redeem_script() = Some(wsh.inner_script().to_p2wsh()); - } - descriptor::ShInner::Wpkh(..) => *item.redeem_script() = Some(sh.inner_script()), - descriptor::ShInner::SortedMulti(_) | descriptor::ShInner::Ms(_) => - *item.redeem_script() = Some(sh.inner_script()), - }, - Descriptor::Wsh(wsh) => *item.witness_script() = Some(wsh.inner_script()), - Descriptor::Tr(_) => unreachable!("Tr is dealt with separately"), - } - - derived - }; - - Ok((derived, true)) -} - -/// Sighash message(signing data) for a given psbt transaction input. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum PsbtSighashMsg { - /// Taproot Signature hash - TapSighash(sighash::TapSighash), - /// Legacy ECDSA sighash message. - LegacySighash(sighash::LegacySighash), - /// Segwit v0 ECDSA sighash message. - SegwitV0Sighash(sighash::SegwitV0Sighash), -} - -impl PsbtSighashMsg { - /// Convert the message to a [`secp256k1::Message`]. - pub fn to_secp_msg(&self) -> Message { - match *self { - PsbtSighashMsg::TapSighash(msg) => Message::from_digest(msg.to_byte_array()), - PsbtSighashMsg::LegacySighash(msg) => Message::from_digest(msg.to_byte_array()), - PsbtSighashMsg::SegwitV0Sighash(msg) => Message::from_digest(msg.to_byte_array()), - } - } -} - -pub(crate) trait ItemSize { - fn size(&self) -> usize; -} - -impl ItemSize for Placeholder { - fn size(&self) -> usize { - match self { - Placeholder::Pubkey(_, size) => *size, - Placeholder::PubkeyHash(_, size) => *size, - Placeholder::EcdsaSigPk(_) | Placeholder::EcdsaSigPkHash(_) => 73, - Placeholder::SchnorrSigPk(_, _, size) | Placeholder::SchnorrSigPkHash(_, _, size) => - size + 1, // +1 for the OP_PUSH - Placeholder::HashDissatisfaction - | Placeholder::Sha256Preimage(_) - | Placeholder::Hash256Preimage(_) - | Placeholder::Ripemd160Preimage(_) - | Placeholder::Hash160Preimage(_) => 33, - Placeholder::PushOne => 2, // On legacy this should be 1 ? - Placeholder::PushZero => 1, - Placeholder::TapScript(s) => s.len(), - Placeholder::TapControlBlock(cb) => cb.serialize().len(), - } - } -} - -impl ItemSize for Vec { - fn size(&self) -> usize { self.len() } -} - -// Helper function to calculate witness size -pub(crate) fn witness_size(wit: &[T]) -> usize { - wit.iter().map(T::size).sum::() + varint_len(wit.len()) -} - -pub(crate) fn varint_len(n: usize) -> usize { VarInt(n as u64).size() } - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::*; - use crate::bitcoin::bip32::{DerivationPath, Xpub}; - use crate::bitcoin::consensus::encode::deserialize; - use crate::bitcoin::hashes::hex::FromHex; - use crate::bitcoin::key::XOnlyPublicKey; - use crate::bitcoin::secp256k1::PublicKey; - use crate::bitcoin::{absolute, transaction, Amount, OutPoint, TxIn, TxOut}; - use crate::miniscript::Miniscript; - - #[test] - fn test_extract_bip174() { - let psbt = Psbt::deserialize(&Vec::::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap(); - let secp = Secp256k1::verification_only(); - let tx = psbt.extract(&secp).unwrap(); - let expected: bitcoin::Transaction = deserialize(&Vec::::from_hex("0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000").unwrap()).unwrap(); - assert_eq!(tx, expected); - } - - #[test] - fn test_update_item_tr_no_script() { - // keys taken from: https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#Specifications - let root_xpub = Xpub::from_str("xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8").unwrap(); - let fingerprint = root_xpub.fingerprint(); - let desc = format!("tr([{}/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/0)", fingerprint); - let desc = Descriptor::from_str(&desc).unwrap(); - let mut psbt_input = Input::default(); - psbt_input.update_with_descriptor_unchecked(&desc).unwrap(); - let mut psbt_output = Output::default(); - psbt_output.update_with_descriptor_unchecked(&desc).unwrap(); - let internal_key = XOnlyPublicKey::from_str( - "cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115", - ) - .unwrap(); - assert_eq!(psbt_input.tap_internal_key, Some(internal_key)); - assert_eq!( - psbt_input.tap_key_origins.get(&internal_key), - Some(&(vec![], (fingerprint, DerivationPath::from_str("m/86'/0'/0'/0/0").unwrap()))) - ); - assert_eq!(psbt_input.tap_key_origins.len(), 1); - assert_eq!(psbt_input.tap_scripts.len(), 0); - assert_eq!(psbt_input.tap_merkle_root, None); - - assert_eq!(psbt_output.tap_internal_key, psbt_input.tap_internal_key); - assert_eq!(psbt_output.tap_key_origins, psbt_input.tap_key_origins); - assert_eq!(psbt_output.tap_tree, None); - } - - #[test] - fn test_update_item_tr_with_tapscript() { - use crate::miniscript::Tap; - // keys taken from: https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#Specifications - let root_xpub = Xpub::from_str("xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8").unwrap(); - let fingerprint = root_xpub.fingerprint(); - let xpub = format!("[{}/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ", fingerprint); - let desc = - format!("tr({}/0/0,{{pkh({}/0/1),multi_a(2,{}/0/1,{}/1/0)}})", xpub, xpub, xpub, xpub); - - let desc = Descriptor::from_str(&desc).unwrap(); - let internal_key = XOnlyPublicKey::from_str( - "cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115", - ) - .unwrap(); - let mut psbt_input = Input::default(); - psbt_input.update_with_descriptor_unchecked(&desc).unwrap(); - let mut psbt_output = Output::default(); - psbt_output.update_with_descriptor_unchecked(&desc).unwrap(); - assert_eq!(psbt_input.tap_internal_key, Some(internal_key)); - assert_eq!( - psbt_input.tap_key_origins.get(&internal_key), - Some(&(vec![], (fingerprint, DerivationPath::from_str("m/86'/0'/0'/0/0").unwrap()))) - ); - assert_eq!(psbt_input.tap_key_origins.len(), 3); - assert_eq!(psbt_input.tap_scripts.len(), 2); - assert!(psbt_input.tap_merkle_root.is_some()); - - assert_eq!(psbt_output.tap_internal_key, psbt_input.tap_internal_key); - assert_eq!(psbt_output.tap_key_origins, psbt_input.tap_key_origins); - assert!(psbt_output.tap_tree.is_some()); - - let key_0_1 = XOnlyPublicKey::from_str( - "83dfe85a3151d2517290da461fe2815591ef69f2b18a2ce63f01697a8b313145", - ) - .unwrap(); - let first_leaf_hash = { - let ms = - Miniscript::::from_str(&format!("pkh({})", &key_0_1)).unwrap(); - let first_script = ms.encode(); - assert!(psbt_input - .tap_scripts - .values() - .any(|value| *value == (first_script.clone(), LeafVersion::TapScript))); - TapLeafHash::from_script(&first_script, LeafVersion::TapScript) - }; - - { - // check 0/1 - let (leaf_hashes, (key_fingerprint, deriv_path)) = - psbt_input.tap_key_origins.get(&key_0_1).unwrap(); - assert_eq!(key_fingerprint, &fingerprint); - assert_eq!(&deriv_path.to_string(), "m/86'/0'/0'/0/1"); - assert_eq!(leaf_hashes.len(), 2); - assert!(leaf_hashes.contains(&first_leaf_hash)); - } - - { - // check 1/0 - let key_1_0 = XOnlyPublicKey::from_str( - "399f1b2f4393f29a18c937859c5dd8a77350103157eb880f02e8c08214277cef", - ) - .unwrap(); - let (leaf_hashes, (key_fingerprint, deriv_path)) = - psbt_input.tap_key_origins.get(&key_1_0).unwrap(); - assert_eq!(key_fingerprint, &fingerprint); - assert_eq!(&deriv_path.to_string(), "m/86'/0'/0'/1/0"); - assert_eq!(leaf_hashes.len(), 1); - assert!(!leaf_hashes.contains(&first_leaf_hash)); - } - } - - #[test] - fn test_update_item_non_tr_multi() { - // values taken from https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki (after removing zpub thingy) - let root_xpub = Xpub::from_str("xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8").unwrap(); - let fingerprint = root_xpub.fingerprint(); - let xpub = format!("[{}/84'/0'/0']xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V", fingerprint); - let pubkeys = [ - "0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c", - "03e775fd51f0dfb8cd865d9ff1cca2a158cf651fe997fdc9fee9c1d3b5e995ea77", - "03025324888e429ab8e3dbaf1f7802648b9cd01e9b418485c5fa4c1b9b5700e1a6", - ]; - - let expected_bip32 = pubkeys - .iter() - .zip(["0/0", "0/1", "1/0"].iter()) - .map(|(pubkey, path)| { - ( - PublicKey::from_str(pubkey).unwrap(), - ( - fingerprint, - DerivationPath::from_str(&format!("m/84'/0'/0'/{}", path)).unwrap(), - ), - ) - }) - .collect::>(); - - { - // test segwit - let desc = format!("wsh(multi(2,{}/0/0,{}/0/1,{}/1/0))", xpub, xpub, xpub); - let desc = Descriptor::from_str(&desc).unwrap(); - let derived = format!("wsh(multi(2,{}))", pubkeys.join(",")); - let derived = Descriptor::::from_str(&derived).unwrap(); - - let mut psbt_input = Input::default(); - psbt_input.update_with_descriptor_unchecked(&desc).unwrap(); - - let mut psbt_output = Output::default(); - psbt_output.update_with_descriptor_unchecked(&desc).unwrap(); - - assert_eq!(expected_bip32, psbt_input.bip32_derivation); - assert_eq!(psbt_input.witness_script, Some(derived.explicit_script().unwrap())); - - assert_eq!(psbt_output.bip32_derivation, psbt_input.bip32_derivation); - assert_eq!(psbt_output.witness_script, psbt_input.witness_script); - } - - { - // test non-segwit - let desc = format!("sh(multi(2,{}/0/0,{}/0/1,{}/1/0))", xpub, xpub, xpub); - let desc = Descriptor::from_str(&desc).unwrap(); - let derived = format!("sh(multi(2,{}))", pubkeys.join(",")); - let derived = Descriptor::::from_str(&derived).unwrap(); - - let mut psbt_input = Input::default(); - psbt_input.update_with_descriptor_unchecked(&desc).unwrap(); - - let mut psbt_output = Output::default(); - psbt_output.update_with_descriptor_unchecked(&desc).unwrap(); - - assert_eq!(psbt_input.bip32_derivation, expected_bip32); - assert_eq!(psbt_input.witness_script, None); - assert_eq!(psbt_input.redeem_script, Some(derived.explicit_script().unwrap())); - - assert_eq!(psbt_output.bip32_derivation, psbt_input.bip32_derivation); - assert_eq!(psbt_output.witness_script, psbt_input.witness_script); - assert_eq!(psbt_output.redeem_script, psbt_input.redeem_script); - } - } - - #[test] - fn test_update_input_checks() { - let desc = "tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/0)"; - let desc = Descriptor::::from_str(desc).unwrap(); - - let mut non_witness_utxo = bitcoin::Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - value: Amount::from_sat(1_000), - script_pubkey: ScriptBuf::from_hex( - "5120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c", - ) - .unwrap(), - }], - }; - - let tx = bitcoin::Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { txid: non_witness_utxo.txid(), vout: 0 }, - ..Default::default() - }], - output: vec![], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - assert_eq!( - psbt.update_input_with_descriptor(0, &desc), - Err(UtxoUpdateError::UtxoCheck), - "neither *_utxo are not set" - ); - psbt.inputs[0].witness_utxo = Some(non_witness_utxo.output[0].clone()); - assert_eq!( - psbt.update_input_with_descriptor(0, &desc), - Ok(()), - "witness_utxo is set which is ok" - ); - psbt.inputs[0].non_witness_utxo = Some(non_witness_utxo.clone()); - assert_eq!( - psbt.update_input_with_descriptor(0, &desc), - Ok(()), - "matching non_witness_utxo" - ); - non_witness_utxo.version = transaction::Version::non_standard(0); - psbt.inputs[0].non_witness_utxo = Some(non_witness_utxo); - assert_eq!( - psbt.update_input_with_descriptor(0, &desc), - Err(UtxoUpdateError::UtxoCheck), - "non_witness_utxo no longer matches" - ); - psbt.inputs[0].non_witness_utxo = None; - psbt.inputs[0].witness_utxo.as_mut().unwrap().script_pubkey = ScriptBuf::default(); - assert_eq!( - psbt.update_input_with_descriptor(0, &desc), - Err(UtxoUpdateError::MismatchedScriptPubkey), - "non_witness_utxo no longer matches" - ); - } - - #[test] - fn test_update_output_checks() { - let desc = "tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/0)"; - let desc = Descriptor::::from_str(desc).unwrap(); - - let tx = bitcoin::Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - value: Amount::from_sat(1_000), - script_pubkey: ScriptBuf::from_hex( - "5120a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c", - ) - .unwrap(), - }], - }; - - let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); - assert_eq!( - psbt.update_output_with_descriptor(1, &desc), - Err(OutputUpdateError::IndexOutOfBounds(1, 1)), - "output index doesn't exist" - ); - assert_eq!( - psbt.update_output_with_descriptor(0, &desc), - Ok(()), - "script_pubkey should match" - ); - psbt.global.unsigned_tx.output[0].script_pubkey = ScriptBuf::default(); - assert_eq!( - psbt.update_output_with_descriptor(0, &desc), - Err(OutputUpdateError::MismatchedScriptPubkey), - "output script_pubkey no longer matches" - ); - } - - #[test] - fn tests_from_bip174() { - let mut psbt = Psbt::deserialize(&Vec::::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap(); - - let secp = Secp256k1::verification_only(); - psbt.finalize_mut(&secp).unwrap(); - - let expected = Psbt::deserialize(&Vec::::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap(); - assert_eq!(psbt, expected); - } -} diff --git a/src/v2/miniscript/satisfy.rs b/src/v2/miniscript/satisfy.rs deleted file mode 100644 index 953fb7f..0000000 --- a/src/v2/miniscript/satisfy.rs +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -use crate::bitcoin::hashes::{hash160, sha256d, Hash}; -use crate::bitcoin::key::XOnlyPublicKey; -use crate::bitcoin::taproot::{self, ControlBlock, LeafVersion, TapLeafHash}; -use crate::bitcoin::{absolute, ecdsa, ScriptBuf, Sequence}; -use crate::miniscript::{MiniscriptKey, Preimage32, Satisfier, SigType, ToPublicKey}; -use crate::prelude::*; -use crate::v2::Psbt; - -// TODO: Make the fields private and enforce invariant that index -// is within range, thereby removing potential panics. - -/// A PSBT [`Satisfier`] for an input at a particular index. -/// -/// Contains reference to the [`Psbt`] because multiple inputs will share the same PSBT. All -/// operations on this structure will panic if index is more than number of inputs in pbst -/// -/// [`Satisfier`]: crate::miniscript::Satisfier -pub struct PsbtInputSatisfier<'a> { - /// Reference to the [`Psbt`]. - pub psbt: &'a Psbt, - /// Index of the input we are satisfying. - pub index: usize, -} - -impl<'a> PsbtInputSatisfier<'a> { - /// Creates a new `PsbtInputSatisfier` from `psbt` and `index`. - pub fn new(psbt: &'a Psbt, index: usize) -> Self { Self { psbt, index } } -} - -impl<'a, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfier<'a> { - fn lookup_tap_key_spend_sig(&self) -> Option { - self.psbt.inputs[self.index].tap_key_sig - } - - fn lookup_tap_leaf_script_sig(&self, pk: &Pk, lh: &TapLeafHash) -> Option { - self.psbt.inputs[self.index].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] - .bip32_derivation - .iter() - .find(|&(pubkey, _)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) - .map(|(pubkey, _)| bitcoin::PublicKey::new(*pubkey)) - } - - fn lookup_tap_control_block_map( - &self, - ) -> Option<&BTreeMap> { - Some(&self.psbt.inputs[self.index].tap_scripts) - } - - fn lookup_raw_pkh_tap_leaf_script_sig( - &self, - pkh: &(hash160::Hash, TapLeafHash), - ) -> Option<(XOnlyPublicKey, taproot::Signature)> { - self.psbt.inputs[self.index] - .tap_script_sigs - .iter() - .find(|&((pubkey, lh), _sig)| { - pubkey.to_pubkeyhash(SigType::Schnorr) == pkh.0 && *lh == pkh.1 - }) - .map(|((x_only_pk, _leaf_hash), sig)| (*x_only_pk, *sig)) - } - - fn lookup_ecdsa_sig(&self, pk: &Pk) -> Option { - self.psbt.inputs[self.index].partial_sigs.get(&pk.to_public_key()).copied() - } - - fn lookup_raw_pkh_ecdsa_sig( - &self, - pkh: &hash160::Hash, - ) -> Option<(bitcoin::PublicKey, ecdsa::Signature)> { - self.psbt.inputs[self.index] - .partial_sigs - .iter() - .find(|&(pubkey, _sig)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) - .map(|(pk, sig)| (*pk, *sig)) - } - - fn check_after(&self, n: absolute::LockTime) -> bool { todo!() } - - fn check_older(&self, n: Sequence) -> bool { todo!() } - - fn lookup_hash160(&self, h: &Pk::Hash160) -> Option { - self.psbt.inputs[self.index] - .hash160_preimages - .get(&Pk::to_hash160(h)) - .and_then(try_vec_as_preimage32) - } - - fn lookup_sha256(&self, h: &Pk::Sha256) -> Option { - self.psbt.inputs[self.index] - .sha256_preimages - .get(&Pk::to_sha256(h)) - .and_then(try_vec_as_preimage32) - } - - fn lookup_hash256(&self, h: &Pk::Hash256) -> Option { - self.psbt.inputs[self.index] - .hash256_preimages - .get(&sha256d::Hash::from_byte_array(Pk::to_hash256(h).to_byte_array())) // upstream psbt operates on hash256 - .and_then(try_vec_as_preimage32) - } - - fn lookup_ripemd160(&self, h: &Pk::Ripemd160) -> Option { - self.psbt.inputs[self.index] - .ripemd160_preimages - .get(&Pk::to_ripemd160(h)) - .and_then(try_vec_as_preimage32) - } -} - -fn try_vec_as_preimage32(vec: &Vec) -> Option { - if vec.len() == 32 { - let mut arr = [0u8; 32]; - arr.copy_from_slice(vec); - Some(arr) - } else { - None - } -} From 4295d7ad810420d8a6040edb29b3427adeaf3770 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 23 Jan 2024 09:20:31 +1100 Subject: [PATCH 3/4] v2: Add funding methods to InputBuilder Add two methods to the `InputBuilder` to add funding utxos. Note that it would really be better to use the type system to enforce existence of the funding utxo but doing so would take us away from `Input` being a 1-1 representation of the bip. --- examples/v2.rs | 6 ++++-- src/v2/map/input.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/examples/v2.rs b/examples/v2.rs index 333898e..90bfe58 100644 --- a/examples/v2.rs +++ b/examples/v2.rs @@ -19,7 +19,7 @@ use psbt::bitcoin::{ script, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, TxOut, Txid, }; use psbt::v2::{ - self, Constructor, Input, InputBuilder, Modifiable, Output, OutputBuilder, Psbt, Signer, + self, Constructor, InputBuilder, Modifiable, Output, OutputBuilder, Psbt, Signer, Updater, }; @@ -63,7 +63,9 @@ fn main() -> anyhow::Result<()> { .build(); // If no lock time is required we can just create the `Input` directly. - let input_b = Input::new(previous_output_b); + let input_b = InputBuilder::new(previous_output_b) + // .segwit_fund(txout); TODO: Add funding utxo. + .build(); // Build Alice's change output. let change = TxOut { value: change_value_a, script_pubkey: change_address_a.script_pubkey() }; diff --git a/src/v2/map/input.rs b/src/v2/map/input.rs index 05c7e6c..0d17db7 100644 --- a/src/v2/map/input.rs +++ b/src/v2/map/input.rs @@ -211,6 +211,8 @@ impl Input { } /// Returns a reference to the funding utxo for this input. + // TODO: This should not need an error path, it would be better to maintain an invariant that + // there exists a funding utxo. pub fn funding_utxo(&self) -> Result<&TxOut, FundingUtxoError> { if let Some(ref utxo) = self.witness_utxo { Ok(utxo) @@ -640,6 +642,22 @@ impl InputBuilder { self } + /// Funds this input with a segwit UTXO. + pub fn segwit_fund(mut self, utxo: TxOut) -> Self { + self.0.witness_utxo = Some(utxo); + self + } + + /// Funds this input with a legacy UTXO. + /// + /// Caller to guarantee that this `tx` is correct for this input (i.e., has a txid equal to + /// `self.previous_txid`). + // TODO: Consider adding error checks that tx is correct. + pub fn legacy_fund(mut self, tx: Transaction) -> Self { + self.0.non_witness_utxo = Some(tx); + self + } + /// Builds the [`Input`]. pub fn build(self) -> Input { self.0 } } From 54fa5da3f3592db4027113070f15e08363a91e1b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 23 Jan 2024 10:13:18 +1100 Subject: [PATCH 4/4] v2: Add Finalizer and Extractor Roles Add types for the two new roles. Feature gate the `Finalizer` on "miniscript". Because another entity may be the Finalizer do not feature gate the `Extractor` oven thought transaction extraction is only possible for a finalized PSBT. --- Cargo.toml | 4 +- examples/v2.rs | 5 +- src/v2/error.rs | 124 +++---- src/v2/extract.rs | 206 +++++++++++ src/v2/extractor.rs | 97 ------ src/v2/map/input.rs | 143 +++++++- src/v2/miniscript/finalize.rs | 631 ++++++++++++++++++++++++++++++++++ src/v2/miniscript/mod.rs | 217 ++++++++++++ src/v2/miniscript/satisfy.rs | 136 ++++++++ src/v2/mod.rs | 52 ++- 10 files changed, 1443 insertions(+), 172 deletions(-) create mode 100644 src/v2/extract.rs delete mode 100644 src/v2/extractor.rs create mode 100644 src/v2/miniscript/finalize.rs create mode 100644 src/v2/miniscript/mod.rs create mode 100644 src/v2/miniscript/satisfy.rs diff --git a/Cargo.toml b/Cargo.toml index fa65041..417be6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,15 +23,15 @@ no-std = ["bitcoin/no-std", "core2"] serde = ["actual-serde", "bitcoin/serde"] base64 = ["bitcoin/base64"] -# TODO: There is curently no way to turn on miniscript/serde and base64 miniscript-std = ["std", "miniscript/std"] miniscript-no-std = ["no-std", "miniscript/no-std"] [dependencies] bitcoin = { version = "0.31.0", default-features = false, features = [] } -# Consider using "miniscript-std" or "miniscript-no-std" +# Currenty miniscript only works in with "std" enabled. miniscript = { version = "11.0.0", default-features = false, optional = true } + # Do NOT use this as a feature! Use the `serde` feature instead. actual-serde = { package = "serde", version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true } # There is no reason to use this dependency directly, it is activated by the "no-std" feature. diff --git a/examples/v2.rs b/examples/v2.rs index 90bfe58..2f250ce 100644 --- a/examples/v2.rs +++ b/examples/v2.rs @@ -19,8 +19,7 @@ use psbt::bitcoin::{ script, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, TxOut, Txid, }; use psbt::v2::{ - self, Constructor, InputBuilder, Modifiable, Output, OutputBuilder, Psbt, Signer, - Updater, + self, Constructor, InputBuilder, Modifiable, Output, OutputBuilder, Psbt, Signer, Updater, }; pub const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000); @@ -64,7 +63,7 @@ fn main() -> anyhow::Result<()> { // If no lock time is required we can just create the `Input` directly. let input_b = InputBuilder::new(previous_output_b) - // .segwit_fund(txout); TODO: Add funding utxo. + // .segwit_fund(txout); TODO: Add funding utxo. .build(); // Build Alice's change output. diff --git a/src/v2/error.rs b/src/v2/error.rs index e50c9e7..62f8aa5 100644 --- a/src/v2/error.rs +++ b/src/v2/error.rs @@ -4,11 +4,11 @@ use core::fmt; -use bitcoin::{sighash, FeeRate, Transaction}; +use bitcoin::sighash::{self, EcdsaSighashType, NonStandardSighashTypeError}; +use bitcoin::PublicKey; use crate::error::{write_err, FundingUtxoError}; use crate::v2::map::{global, input, output}; -use crate::v2::Psbt; /// Error while deserializing a PSBT. /// @@ -102,59 +102,6 @@ impl std::error::Error for IndexOutOfBoundsError { } } -/// This error is returned when extracting a [`Transaction`] from a PSBT.. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub enum ExtractTxError { - /// The [`FeeRate`] is too high - AbsurdFeeRate { - /// The [`FeeRate`] - fee_rate: FeeRate, - /// The extracted [`Transaction`] (use this to ignore the error) - tx: Transaction, - }, - /// One or more of the inputs lacks value information (witness_utxo or non_witness_utxo) - MissingInputValue { - /// The extracted [`Transaction`] (use this to ignore the error) - tx: Transaction, - }, - /// Input value is less than Output Value, and the [`Transaction`] would be invalid. - SendingTooMuch { - /// The original `Psbt` is returned untouched. - psbt: Psbt, - }, -} - -impl fmt::Display for ExtractTxError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ExtractTxError::*; - - match *self { - AbsurdFeeRate { fee_rate, .. } => - write!(f, "An absurdly high fee rate of {}", fee_rate), - MissingInputValue { .. } => write!( - f, - "One of the inputs lacked value information (witness_utxo or non_witness_utxo)" - ), - SendingTooMuch { .. } => write!( - f, - "Transaction would be invalid due to output value being greater than input value." - ), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ExtractTxError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use ExtractTxError::*; - - match *self { - AbsurdFeeRate { .. } | MissingInputValue { .. } | SendingTooMuch { .. } => None, - } - } -} - /// Errors encountered while calculating the sighash message. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] @@ -302,9 +249,7 @@ impl fmt::Display for InputsNotModifiableError { } #[cfg(feature = "std")] -impl std::error::Error for InputsNotModifiableError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } -} +impl std::error::Error for InputsNotModifiableError {} /// Error when passing an PSBT with outputs not modifiable to an output adding `Constructor`. #[derive(Debug, Clone, PartialEq, Eq)] @@ -365,3 +310,66 @@ impl fmt::Display for DetermineLockTimeError { impl std::error::Error for DetermineLockTimeError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } + +// TODO: Consider creating a type that has input_index and E and simplify all these similar error types? +/// Error checking the partials sigs have correct sighash types. +#[derive(Debug)] +pub enum PartialSigsSighashTypeError { + /// Non-standard sighash type found in `input.sighash_type` field. + NonStandardInputSighashType { + /// The input index with the non-standard sighash type. + input_index: usize, + /// The non-standard sighash type error. + error: NonStandardSighashTypeError, + }, + /// Non-standard sighash type found in `input.partial_sigs`. + NonStandardPartialSigsSighashType { + /// The input index with the non-standard sighash type. + input_index: usize, + /// The non-standard sighash type error. + error: NonStandardSighashTypeError, + }, + /// Wrong sighash flag in partial signature. + WrongSighashFlag { + /// The input index with the wrong sighash flag. + input_index: usize, + /// The sighash type we got. + got: EcdsaSighashType, + /// The sighash type we require. + required: EcdsaSighashType, + /// The associated pubkey (key into the `input.partial_sigs` map). + pubkey: PublicKey, + }, +} + +impl fmt::Display for PartialSigsSighashTypeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use PartialSigsSighashTypeError::*; + + match *self { + NonStandardInputSighashType { input_index, ref error } => + write_err!(f, "non-standard sighash type for input {} in sighash_type field", input_index; error), + NonStandardPartialSigsSighashType { input_index, ref error } => + write_err!(f, "non-standard sighash type for input {} in partial_sigs", input_index; error), + WrongSighashFlag { input_index, got, required, pubkey } => write!( + f, + "wrong sighash flag for input {} (got: {}, required: {}) pubkey: {}", + input_index, got, required, pubkey + ), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for PartialSigsSighashTypeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use PartialSigsSighashTypeError::*; + + // TODO: Is this correct for a struct error fields? + match *self { + NonStandardInputSighashType { input_index: _, ref error } => Some(error), + NonStandardPartialSigsSighashType { input_index: _, ref error } => Some(error), + WrongSighashFlag { .. } => None, + } + } +} diff --git a/src/v2/extract.rs b/src/v2/extract.rs new file mode 100644 index 0000000..8cffc7a --- /dev/null +++ b/src/v2/extract.rs @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Implementation of the Extractor role as defined in [BIP-174]. +//! +//! # Extractor Role +//! +//! > The Transaction Extractor does not need to know how to interpret scripts in order +//! > to extract the network serialized transaction. +//! +//! It is only possible to extract a transaction from a PSBT _after_ it has been finalized. However +//! the Extractor role may be fulfilled by a separate entity to the Finalizer hence this is a +//! separate module and does not require `rust-miniscript`. +//! +//! [BIP-174]: + +use core::fmt; + +use bitcoin::{FeeRate, Transaction}; + +use crate::error::{write_err, FeeError}; +use crate::v2::{DetermineLockTimeError, Psbt}; + +/// Implements the BIP-370 Finalized role. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Extractor(Psbt); + +impl Extractor { + /// Creates an `Extractor`. + /// + /// An extractor can only accept a PSBT that has been finalized. + pub fn new(psbt: Psbt) -> Result { + if psbt.inputs.iter().any(|input| !input.is_finalized()) { + return Err(PsbtNotFinalizedError); + } + + Ok(Self(psbt)) + } +} + +impl Extractor { + /// The default `max_fee_rate` value used for extracting transactions with [`Self::extract_tx`]. + /// + /// As of 2023, even the biggest overpayers during the highest fee markets only paid around + /// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point. + pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_unchecked(25_000); + + /// An alias for [`Self::extract_tx_fee_rate_limit`]. + pub fn extract_tx(&self) -> Result { + self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE) + } + + /// Extracts the [`Transaction`] from a [`Psbt`] by filling in the available signature information. + /// + /// ## Errors + /// + /// `ExtractTxError` variants will contain either the [`Psbt`] itself or the [`Transaction`] + /// that was extracted. These can be extracted from the Errors in order to recover. + /// See the error documentation for info on the variants. In general, it covers large fees. + pub fn extract_tx_fee_rate_limit(&self) -> Result { + self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE) + } + + /// Extracts the [`Transaction`] from a [`Psbt`] by filling in the available signature information. + pub fn extract_tx_with_fee_rate_limit( + &self, + max_fee_rate: FeeRate, + ) -> Result { + self.internal_extract_tx_with_fee_rate_limit(max_fee_rate) + } + + /// Perform [`Self::extract_tx_fee_rate_limit`] without the fee rate check. + /// + /// This can result in a transaction with absurdly high fees. Use with caution. + pub fn extract_tx_unchecked_fee_rate(&self) -> Result { + self.internal_extract_tx() + } + + #[inline] + fn internal_extract_tx_with_fee_rate_limit( + &self, + max_fee_rate: FeeRate, + ) -> Result { + let fee = self.0.fee()?; + let tx = self.internal_extract_tx()?; + + // Now that the extracted Transaction is made, decide how to return it. + let fee_rate = + FeeRate::from_sat_per_kwu(fee.to_sat().saturating_mul(1000) / tx.weight().to_wu()); + // Prefer to return an AbsurdFeeRate error when both trigger. + if fee_rate > max_fee_rate { + return Err(ExtractTxFeeRateError::FeeTooHigh { fee: fee_rate, max: max_fee_rate }); + } + + Ok(tx) + } + + /// Extracts a finalized transaction from the [`Psbt`]. + /// + /// Uses `miniscript` to do interpreter checks. + #[inline] + fn internal_extract_tx(&self) -> Result { + if !self.0.is_finalized() { + return Err(ExtractTxError::Unfinalized); + } + + let lock_time = self.0.determine_lock_time()?; + + let tx = Transaction { + version: self.0.global.tx_version, + lock_time, + input: self.0.inputs.iter().map(|input| input.signed_tx_in()).collect(), + output: self.0.outputs.iter().map(|ouput| ouput.tx_out()).collect(), + }; + + Ok(tx) + } +} + +/// Attempted to extract tx from an unfinalized PSBT. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct PsbtNotFinalizedError; + +impl fmt::Display for PsbtNotFinalizedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "attempted to extract tx from an unfinalized PSBT") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for PsbtNotFinalizedError {} + +/// Error caused by fee calculation when extracting a [`Transaction`] from a PSBT. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum ExtractTxFeeRateError { + /// Error calculating the fee rate. + Fee(FeeError), + /// The calculated fee rate exceeds max. + FeeTooHigh { + /// Calculated fee. + fee: FeeRate, + /// Maximum allowable fee. + max: FeeRate, + }, + /// Error extracting the transaction. + ExtractTx(ExtractTxError), +} + +impl fmt::Display for ExtractTxFeeRateError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use ExtractTxFeeRateError::*; + + match *self { + Fee(ref e) => write_err!(f, "fee calculation"; e), + FeeTooHigh { fee, max } => write!(f, "fee {} is greater than max {}", fee, max), + ExtractTx(ref e) => write_err!(f, "extract"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ExtractTxFeeRateError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ExtractTxFeeRateError::*; + + match *self { + Fee(ref e) => Some(e), + ExtractTx(ref e) => Some(e), + FeeTooHigh { .. } => None, + } + } +} + +impl From for ExtractTxFeeRateError { + fn from(e: FeeError) -> Self { Self::Fee(e) } +} + +impl From for ExtractTxFeeRateError { + fn from(e: ExtractTxError) -> Self { Self::ExtractTx(e) } +} + +/// Error extracting a [`Transaction`] from a PSBT. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum ExtractTxError { + /// Attempted to extract transaction from an unfinalized PSBT. + Unfinalized, + /// Failed to determine lock time. + DetermineLockTime(DetermineLockTimeError), +} + +impl fmt::Display for ExtractTxError { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { todo!() } +} + +#[cfg(feature = "std")] +impl std::error::Error for ExtractTxError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { todo!() } +} + +impl From for ExtractTxError { + fn from(e: DetermineLockTimeError) -> Self { Self::DetermineLockTime(e) } +} diff --git a/src/v2/extractor.rs b/src/v2/extractor.rs deleted file mode 100644 index b186367..0000000 --- a/src/v2/extractor.rs +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! WIP: Partial implementation of the Extractor role as defined in [BIP-174]. -//! -//! See also `crate::v2::miniscript::extractor.rs`. -//! -//! [BIP-174]: - -use bitcoin::{FeeRate, Transaction}; - -use crate::error::FeeError; -use crate::v2::{ExtractTxError, Psbt}; - -impl Psbt { - /// The default `max_fee_rate` value used for extracting transactions with [`extract_tx`] - /// - /// As of 2023, even the biggest overpayers during the highest fee markets only paid around - /// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point. - /// - /// [`extract_tx`]: Psbt::extract_tx - pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_unchecked(25_000); - - /// An alias for [`extract_tx_fee_rate_limit`]. - /// - /// [`extract_tx_fee_rate_limit`]: Psbt::extract_tx_fee_rate_limit - pub fn extract_tx(self) -> Result { - self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE) - } - - /// Extracts the [`Transaction`] from a [`Psbt`] by filling in the available signature information. - /// - /// ## Errors - /// - /// `ExtractTxError` variants will contain either the [`Psbt`] itself or the [`Transaction`] - /// that was extracted. These can be extracted from the Errors in order to recover. - /// See the error documentation for info on the variants. In general, it covers large fees. - pub fn extract_tx_fee_rate_limit(self) -> Result { - self.internal_extract_tx_with_fee_rate_limit(Self::DEFAULT_MAX_FEE_RATE) - } - - /// Extracts the [`Transaction`] from a [`Psbt`] by filling in the available signature information. - /// - /// ## Errors - /// - /// See [`extract_tx`]. - /// - /// [`extract_tx`]: Psbt::extract_tx - pub fn extract_tx_with_fee_rate_limit( - self, - max_fee_rate: FeeRate, - ) -> Result { - self.internal_extract_tx_with_fee_rate_limit(max_fee_rate) - } - - /// Perform [`extract_tx_fee_rate_limit`] without the fee rate check. - /// - /// This can result in a transaction with absurdly high fees. Use with caution. - /// - /// [`extract_tx_fee_rate_limit`]: Psbt::extract_tx_fee_rate_limit - pub fn extract_tx_unchecked_fee_rate(self) -> Transaction { self.internal_extract_tx() } - - #[inline] - fn internal_extract_tx(self) -> Transaction { todo!() } - - #[inline] - fn internal_extract_tx_with_fee_rate_limit( - self, - max_fee_rate: FeeRate, - ) -> Result { - use FeeError::*; - - let fee = match self.fee() { - Ok(fee) => fee, - Err(FundingUtxo(_)) => - return Err(ExtractTxError::MissingInputValue { tx: self.internal_extract_tx() }), - Err(Negative) => return Err(ExtractTxError::SendingTooMuch { psbt: self }), - Err(InputOverflow | OutputOverflow) => - return Err(ExtractTxError::AbsurdFeeRate { - fee_rate: FeeRate::MAX, - tx: self.internal_extract_tx(), - }), - }; - - // Note: Move prevents usage of &self from now on. - let tx = self.internal_extract_tx(); - - // Now that the extracted Transaction is made, decide how to return it. - let fee_rate = - FeeRate::from_sat_per_kwu(fee.to_sat().saturating_mul(1000) / tx.weight().to_wu()); - // Prefer to return an AbsurdFeeRate error when both trigger. - if fee_rate > max_fee_rate { - return Err(ExtractTxError::AbsurdFeeRate { fee_rate, tx }); - } - - Ok(tx) - } -} diff --git a/src/v2/map/input.rs b/src/v2/map/input.rs index 0d17db7..6e8f284 100644 --- a/src/v2/map/input.rs +++ b/src/v2/map/input.rs @@ -182,6 +182,82 @@ impl Input { } } + /// Creates a new finalized input. + /// + /// Note the `Witness` is not optional because `miniscript` returns an empty `Witness` in the + /// case that this is a legacy input. + /// + /// The `final_script_sig` and `final_script_witness` should come from `miniscript`. + #[cfg(feature = "miniscript")] + pub(crate) fn finalize( + &self, + final_script_sig: ScriptBuf, + final_script_witness: Witness, + ) -> Result { + debug_assert!(self.has_funding_utxo()); + + let mut ret = Input { + previous_txid: self.previous_txid, + spent_output_index: self.spent_output_index, + non_witness_utxo: self.non_witness_utxo.clone(), + witness_utxo: self.witness_utxo.clone(), + + // Set below. + final_script_sig: None, + final_script_witness: None, + + // Clear everything else. + sequence: None, + min_time: None, + min_height: None, + partial_sigs: BTreeMap::new(), + sighash_type: None, + redeem_script: None, + witness_script: None, + bip32_derivations: BTreeMap::new(), + ripemd160_preimages: BTreeMap::new(), + sha256_preimages: BTreeMap::new(), + hash160_preimages: BTreeMap::new(), + hash256_preimages: BTreeMap::new(), + tap_key_sig: None, + tap_script_sigs: BTreeMap::new(), + tap_scripts: BTreeMap::new(), + tap_key_origins: BTreeMap::new(), + tap_internal_key: None, + tap_merkle_root: None, + proprietaries: BTreeMap::new(), + unknowns: BTreeMap::new(), + }; + + // TODO: These errors should only trigger if there are bugs in this crate or miniscript. + // Is there an infallible way to do this? + if self.witness_utxo.is_some() { + if final_script_witness.is_empty() { + return Err(FinalizeError::EmptyWitness); + } + ret.final_script_sig = Some(final_script_sig); + ret.final_script_witness = Some(final_script_witness); + } else { + // TODO: Any checks should do here? + ret.final_script_sig = Some(final_script_sig); + } + + Ok(ret) + } + + // TODO: Work out if this is in line with bip-370 + #[cfg(feature = "miniscript")] + pub(crate) fn lock_time(&self) -> absolute::LockTime { + match (self.min_height, self.min_time) { + // If we have both, bip says use height. + (Some(height), Some(_)) => height.into(), + (Some(height), None) => height.into(), + (None, Some(time)) => time.into(), + // TODO: Check this is correct. + (None, None) => absolute::LockTime::ZERO, + } + } + pub(crate) fn has_lock_time(&self) -> bool { self.min_time.is_some() || self.min_height.is_some() } @@ -201,18 +277,38 @@ impl Input { } /// Constructs a [`TxIn`] for this input, excluding any signature material. - pub(crate) fn tx_in(&self) -> TxIn { + pub(crate) fn unsigned_tx_in(&self) -> TxIn { TxIn { previous_output: self.out_point(), script_sig: ScriptBuf::default(), + // TODO: Check this ZERO is correct. sequence: self.sequence.unwrap_or(Sequence::ZERO), witness: Witness::default(), } } + /// Constructs a signed [`TxIn`] for this input. + /// + /// Should only be called on a finalized PSBT. + pub(crate) fn signed_tx_in(&self) -> TxIn { + debug_assert!(self.is_finalized()); + + let script_sig = self.final_script_sig.as_ref().expect("checked by is_finalized"); + let witness = self.final_script_witness.as_ref().expect("checked by is_finalized"); + + TxIn { + previous_output: self.out_point(), + script_sig: script_sig.clone(), + // TODO: Check this MAX is correct. + sequence: self.sequence.unwrap_or(Sequence::MAX), + witness: witness.clone(), + } + } + + #[cfg(feature = "miniscript")] + pub(crate) fn has_funding_utxo(&self) -> bool { self.funding_utxo().is_ok() } + /// Returns a reference to the funding utxo for this input. - // TODO: This should not need an error path, it would be better to maintain an invariant that - // there exists a funding utxo. pub fn funding_utxo(&self) -> Result<&TxOut, FundingUtxoError> { if let Some(ref utxo) = self.witness_utxo { Ok(utxo) @@ -224,11 +320,15 @@ impl Input { } } - /// TODO: Use this. - #[allow(dead_code)] - fn is_finalized(&self) -> bool { - // TODO: Confirm this covers taproot sigs? - self.final_script_sig.is_some() || self.final_script_witness.is_some() + /// Returns true if this input has been finalized. + /// + /// > It checks whether all inputs have complete scriptSigs and scriptWitnesses by checking for + /// the presence of 0x07 Finalized scriptSig and 0x08 Finalized scriptWitness typed records. + /// + /// Therefore a finalized input must have both `final_script_sig` and `final_script_witness` + /// fields set. For legacy transactions the `final_script_witness` will be an empty [`Witness`]. + pub fn is_finalized(&self) -> bool { + self.final_script_sig.is_some() && self.final_script_witness.is_some() } /// TODO: Use this. @@ -801,6 +901,33 @@ impl fmt::Display for HashType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", stringify!(self)) } } +/// Error finalizing an input. +#[cfg(feature = "miniscript")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FinalizeError { + /// Failed to create a final witness. + EmptyWitness, + /// Unexpected witness data. + UnexpectedWitness, +} + +#[cfg(feature = "miniscript")] +impl fmt::Display for FinalizeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use FinalizeError::*; + + match *self { + EmptyWitness => write!(f, "failed to create a final witness"), + UnexpectedWitness => write!(f, "unexpected witness data"), + } + } +} + +#[cfg(all(feature = "std", feature = "miniscript"))] +impl std::error::Error for FinalizeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + #[cfg(test)] mod test { use core::str::FromStr; diff --git a/src/v2/miniscript/finalize.rs b/src/v2/miniscript/finalize.rs new file mode 100644 index 0000000..a21024d --- /dev/null +++ b/src/v2/miniscript/finalize.rs @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Implementation of the Finalizer role as defined in [BIP-174]. +//! +//! [BIP-174]: + +use alloc::collections::BTreeMap; +use core::fmt; + +use bitcoin::hashes::hash160; +use bitcoin::secp256k1::{Secp256k1, Verification}; +use bitcoin::taproot::LeafVersion; +use bitcoin::{sighash, Address, Network, Script, ScriptBuf, Witness, XOnlyPublicKey}; +use miniscript::{ + interpreter, BareCtx, Descriptor, ExtParams, Legacy, Miniscript, Satisfier, Segwitv0, SigType, + Tap, ToPublicKey, +}; + +use crate::error::{write_err, FundingUtxoError}; +use crate::v2::map::input::{self, Input}; +use crate::v2::miniscript::satisfy::InputSatisfier; +use crate::v2::miniscript::InterpreterCheckError; +use crate::v2::{PartialSigsSighashTypeError, Psbt}; + +/// Implements the BIP-370 Finalized role. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Finalizer(Psbt); + +impl Finalizer { + /// Creates an `Finalizer`. + /// + /// A finalizer can only be created if all inputs have a funding UTXO. + pub fn new(psbt: Psbt) -> Result { + // TODO: Consider doing this with combinators. + for input in psbt.inputs.iter() { + let _ = input.funding_utxo()?; + } + psbt.check_partial_sigs_sighash_type()?; + + Ok(Self(psbt)) + } + + /// Finalize the PSBT. + /// + /// # Returns + /// + /// Returns the finalized PSBT without modifying the original. + #[must_use = "returns the finalized PSBT without modifying the original"] + pub fn finalize(&self, secp: &Secp256k1) -> Result { + let mut inputs = vec![]; + for (input_index, input) in self.0.inputs.iter().enumerate() { + match self.finalize_input(input) { + Ok(input) => inputs.push(input), + // TODO: Do we want to continue loop and return a vector of errors? + Err(error) => return Err(FinalizeError::FinalizeInput { input_index, error }), + } + } + + let finalized = + Psbt { global: self.0.global.clone(), inputs, outputs: self.0.outputs.to_vec() }; + + finalized.interpreter_check(secp)?; + Ok(finalized) + } + + // `index` must be the input index of `input` which references an input in `self` - this gets rid of out-of-bounds error path. + fn finalize_input(&self, input: &Input) -> Result { + let allow_mall = true; // TODO: Add mall and no-mall versions. + let (script_sig, witness) = self.final_script_sig_and_witness(input, allow_mall)?; + + Ok(input.finalize(script_sig, witness)?.clone()) + } + + /// Returns the final script_sig and final witness for this input. + /// + /// Note if this is a legacy input the returned `Witness` will be empty. + // TODO: Think harder about this. + // + // Input finalizer should only set script sig and witness iff one is required + // + // > The Input Finalizer must only accept a PSBT. For each input, the Input Finalizer determines + // > if the input has enough data to pass validation. If it does, it must construct the 0x07 + // > Finalized scriptSig and 0x08 Finalized scriptWitness and place them into the input key-value + // > map. If scriptSig is empty for an input, 0x07 should remain unset rather than assigned an + // > empty array. Likewise, if no scriptWitness exists for an input, 0x08 should remain unset + // > rather than assigned an empty array. + // + // However a finalized input _must_ have them both set. + // + // > It checks whether all inputs have complete scriptSigs and scriptWitnesses by checking for + // > the presence of 0x07 Finalized scriptSig and 0x08 Finalized scriptWitness typed records. If + // > they do, the Transaction Extractor should ... + // + // TODO: Check that we are doing the right thing at the right time between finalization and extraction. + fn final_script_sig_and_witness( + &self, + input: &Input, + allow_mall: bool, + ) -> Result<(ScriptBuf, Witness), InputError> { + let (witness, script_sig) = { + let spk = + &input.funding_utxo().expect("guaranteed by Finalizer invariant").script_pubkey; + let sat = InputSatisfier { input }; + + if spk.is_p2tr() { + // Deal with taproot case separately, we cannot infer the full descriptor for taproot. + let wit = construct_tap_witness(spk, sat, allow_mall)?; + (wit, ScriptBuf::new()) + } else { + // Get a descriptor for this input. + let desc = self.get_descriptor(input)?; + + // Generate the satisfaction witness and scriptsig. + if !allow_mall { + desc.get_satisfaction(sat)? + } else { + desc.get_satisfaction_mall(sat)? + } + } + }; + + let witness = Witness::from_slice(&witness); + Ok((script_sig, witness)) + } + + /// Creates a descriptor from an unfinalized PSBT input. + /// + /// Panics on out of bound input index for psbt Also sanity checks that the witness script and + /// redeem script are consistent with the script pubkey. Does *not* check signatures We parse + /// the insane version while satisfying because we want to move the script is probably already + /// created and we want to satisfy it in any way possible. + fn get_descriptor(&self, input: &Input) -> Result, InputError> { + let mut map: BTreeMap = BTreeMap::new(); + + // TODO(Tobin): Understand why we use keys from all inputs? + let psbt_inputs = &self.0.inputs; + for psbt_input in psbt_inputs { + // Use BIP32 Derviation to get set of all possible keys. + let public_keys = psbt_input.bip32_derivations.keys(); + for key in public_keys { + let bitcoin_key = bitcoin::PublicKey::new(*key); + let hash = bitcoin_key.pubkey_hash().to_raw_hash(); + map.insert(hash, bitcoin_key); + } + } + + // Figure out Scriptpubkey + let script_pubkey = &input.funding_utxo().expect("guaranteed by Finalizer").script_pubkey; + // 1. `PK`: creates a `Pk` descriptor(does not check if partial sig is given) + if script_pubkey.is_p2pk() { + let script_pubkey_len = script_pubkey.len(); + let pk_bytes = &script_pubkey.to_bytes(); + match bitcoin::PublicKey::from_slice(&pk_bytes[1..script_pubkey_len - 1]) { + Ok(pk) => Ok(Descriptor::new_pk(pk)), + Err(e) => Err(InputError::from(e)), + } + } else if script_pubkey.is_p2pkh() { + // 2. `Pkh`: creates a `PkH` descriptor if partial_sigs has the corresponding pk + let partial_sig_contains_pk = input.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 = Address::p2pkh(&pk, 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), + } + } else if script_pubkey.is_p2wpkh() { + // 3. `Wpkh`: creates a `wpkh` descriptor if the partial sig has corresponding pk. + let partial_sig_contains_pk = input.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. + let addr = Address::p2wpkh(&pk, Network::Bitcoin) + .expect("Address corresponding to valid pubkey"); + *script_pubkey == addr.script_pubkey() + }); + match partial_sig_contains_pk { + Some((pk, _sig)) => Ok(Descriptor::new_wpkh(*pk)?), + None => Err(InputError::MissingPubkey), + } + } else if script_pubkey.is_p2wsh() { + // 4. `Wsh`: creates a `Wsh` descriptor + if input.redeem_script.is_some() { + return Err(InputError::NonEmptyRedeemScript); + } + if let Some(ref witness_script) = input.witness_script { + if witness_script.to_p2wsh() != *script_pubkey { + return Err(InputError::InvalidWitnessScript { + witness_script: witness_script.clone(), + p2wsh_expected: script_pubkey.clone(), + }); + } + let ms = Miniscript::::parse_with_ext( + witness_script, + &ExtParams::allow_all(), + )?; + Ok(Descriptor::new_wsh(ms.substitute_raw_pkh(&map))?) + } else { + Err(InputError::MissingWitnessScript) + } + } else if script_pubkey.is_p2sh() { + match input.redeem_script { + None => Err(InputError::MissingRedeemScript), + Some(ref redeem_script) => { + if redeem_script.to_p2sh() != *script_pubkey { + return Err(InputError::InvalidRedeemScript { + redeem: redeem_script.clone(), + p2sh_expected: script_pubkey.clone(), + }); + } + if redeem_script.is_p2wsh() { + // 5. `ShWsh` case + if let Some(ref witness_script) = input.witness_script { + if witness_script.to_p2wsh() != *redeem_script { + return Err(InputError::InvalidWitnessScript { + witness_script: witness_script.clone(), + p2wsh_expected: redeem_script.clone(), + }); + } + let ms = Miniscript::::parse_with_ext( + witness_script, + &ExtParams::allow_all(), + )?; + Ok(Descriptor::new_sh_wsh(ms.substitute_raw_pkh(&map))?) + } else { + Err(InputError::MissingWitnessScript) + } + } else if redeem_script.is_p2wpkh() { + // 6. `ShWpkh` case + let partial_sig_contains_pk = + input.partial_sigs.iter().find(|&(&pk, _sig)| { + let addr = Address::p2wpkh(&pk, Network::Bitcoin) + .expect("Address corresponding to valid pubkey"); + *redeem_script == addr.script_pubkey() + }); + match partial_sig_contains_pk { + Some((pk, _sig)) => Ok(Descriptor::new_sh_wpkh(*pk)?), + None => Err(InputError::MissingPubkey), + } + } else { + //7. regular p2sh + if input.witness_script.is_some() { + return Err(InputError::NonEmptyWitnessScript); + } + if let Some(ref redeem_script) = input.redeem_script { + let ms = Miniscript::::parse_with_ext( + redeem_script, + &ExtParams::allow_all(), + )?; + Ok(Descriptor::new_sh(ms)?) + } else { + Err(InputError::MissingWitnessScript) + } + } + } + } + } else { + // 8. Bare case + if input.witness_script.is_some() { + return Err(InputError::NonEmptyWitnessScript); + } + if input.redeem_script.is_some() { + return Err(InputError::NonEmptyRedeemScript); + } + let ms = Miniscript::::parse_with_ext( + script_pubkey, + &ExtParams::allow_all(), + )?; + Ok(Descriptor::new_bare(ms.substitute_raw_pkh(&map))?) + } + } +} + +// Satisfy the taproot descriptor. It is not possible to infer the complete descriptor from psbt +// because the information about all the scripts might not be present. Also, currently the spec does +// not support hidden branches, so inferring a descriptor is not possible. +fn construct_tap_witness( + spk: &Script, + sat: InputSatisfier, + allow_mall: bool, +) -> Result>, InputError> { + assert!(spk.is_p2tr()); + // 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(); + + // 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 = sat.input.tap_key_origins.keys(); + for key in public_keys { + // TODO: How is this key converting to a miniscript::interpreter::BitcoinKey? + // let hash = key.to_pubkeyhash(SigType::Schnorr); + let bitcoin_key = *key; + let hash = bitcoin_key.to_pubkeyhash(SigType::Schnorr); + + map.insert(hash, *key); + } + + // try the key spend path first + if let Some(sig) = >::lookup_tap_key_spend_sig(&sat) + { + return Ok(vec![sig.to_vec()]); + } + // Next script spends + let (mut min_wit, mut min_wit_len) = (None, None); + if let Some(block_map) = + >::lookup_tap_control_block_map(&sat) + { + for (control_block, (script, ver)) in block_map { + if *ver != LeafVersion::TapScript { + // We don't know how to satisfy non default version scripts yet + continue; + } + let ms = match Miniscript::::parse_with_ext( + script, + &ExtParams::allow_all(), + ) { + Ok(ms) => ms.substitute_raw_pkh(&map), + Err(..) => continue, // try another script + }; + let mut wit = if allow_mall { + match ms.satisfy_malleable(&sat) { + Ok(ms) => ms, + Err(..) => continue, + } + } else { + match ms.satisfy(&sat) { + Ok(ms) => ms, + Err(..) => continue, + } + }; + wit.push(ms.encode().into_bytes()); + wit.push(control_block.serialize()); + let wit_len = Some(super::witness_size(&wit)); + if min_wit_len.is_some() && wit_len > min_wit_len { + continue; + } else { + // store the minimum + min_wit = Some(wit); + min_wit_len = wit_len; + } + } + min_wit.ok_or(InputError::CouldNotSatisfyTr) + } else { + // No control blocks found + Err(InputError::CouldNotSatisfyTr) + } +} + +/// Error constructing a [`Finalizer`]. +#[derive(Debug)] +pub enum Error { + /// An input is missing its funding UTXO. + FundingUtxo(FundingUtxoError), + /// An input has incorrect sighash type for its partial sigs (ECDSA). + PartialSigsSighashType(PartialSigsSighashTypeError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Error::*; + + match *self { + FundingUtxo(ref e) => write_err!(f, "Finalizer missing funding UTXO"; e), + PartialSigsSighashType(ref e) => write_err!(f, "Finalizer sighash type error"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use Error::*; + + match *self { + FundingUtxo(ref e) => Some(e), + PartialSigsSighashType(ref e) => Some(e), + } + } +} + +impl From for Error { + fn from(e: FundingUtxoError) -> Self { Self::FundingUtxo(e) } +} + +impl From for Error { + fn from(e: PartialSigsSighashTypeError) -> Self { Self::PartialSigsSighashType(e) } +} + +/// Error finalizing an input. +#[derive(Debug)] +pub enum FinalizeError { + /// Error finalizing an input. + FinalizeInput { + /// The associated input index for `error`. + input_index: usize, + /// Error finalizing input. + error: FinalizeInputError, + }, + /// Error running the interpreter checks. + InterpreterCheck(InterpreterCheckError), +} + +impl fmt::Display for FinalizeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use FinalizeError::*; + + match *self { + FinalizeInput { input_index, ref error } => + write_err!(f, "failed to finalize input at index {}", input_index; error), + InterpreterCheck(ref e) => write_err!(f, "error running the interpreter checks"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FinalizeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use FinalizeError::*; + + match *self { + FinalizeInput { input_index: _, ref error } => Some(error), + InterpreterCheck(ref error) => Some(error), + } + } +} + +impl From for FinalizeError { + fn from(e: InterpreterCheckError) -> Self { Self::InterpreterCheck(e) } +} + +/// Error finalizing an input. +#[derive(Debug)] +pub enum FinalizeInputError { + /// Failed to get final script_sig and final witness. + Final(InputError), + /// Failed to create a finalized input from final fields. + Input(input::FinalizeError), +} + +impl fmt::Display for FinalizeInputError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use FinalizeInputError::*; + + match *self { + Final(ref e) => write_err!(f, "final"; e), + Input(ref e) => write_err!(f, "input"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FinalizeInputError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use FinalizeInputError::*; + + match *self { + Final(ref e) => Some(e), + Input(ref e) => Some(e), + } + } +} + +impl From for FinalizeInputError { + fn from(e: InputError) -> Self { Self::Final(e) } +} + +impl From for FinalizeInputError { + fn from(e: input::FinalizeError) -> Self { Self::Input(e) } +} + +/// Error type for Pbst Input +#[derive(Debug)] +pub enum InputError { + /// Get the secp Errors directly + SecpErr(bitcoin::secp256k1::Error), + /// Key errors + KeyErr(bitcoin::key::Error), + /// Could not satisfy taproot descriptor + /// This error is returned when both script path and key paths could not be + /// satisfied. We cannot return a detailed error because we try all miniscripts + /// in script spend path, we cannot know which miniscript failed. + CouldNotSatisfyTr, + /// Error doing an interpreter-check on a finalized psbt + Interpreter(interpreter::Error), + /// Redeem script does not match the p2sh hash + InvalidRedeemScript { + /// Redeem script + redeem: ScriptBuf, + /// Expected p2sh Script + p2sh_expected: ScriptBuf, + }, + /// Witness script does not match the p2wsh hash + InvalidWitnessScript { + /// Witness Script + witness_script: ScriptBuf, + /// Expected p2wsh script + p2wsh_expected: ScriptBuf, + }, + /// Invalid sig + InvalidSignature { + /// The bitcoin public key + pubkey: bitcoin::PublicKey, + /// The (incorrect) signature + sig: Vec, + }, + /// Pass through the underlying errors in miniscript + MiniscriptError(miniscript::Error), + /// Missing redeem script for p2sh + MissingRedeemScript, + /// Missing witness + MissingWitness, + /// used for public key corresponding to pkh/wpkh + MissingPubkey, + /// Missing witness script for segwit descriptors + MissingWitnessScript, + ///Missing both the witness and non-witness utxo + MissingUtxo, + /// Non empty Witness script for p2sh + NonEmptyWitnessScript, + /// Non empty Redeem script + NonEmptyRedeemScript, + /// Non Standard sighash type + NonStandardSighashType(sighash::NonStandardSighashTypeError), + /// Sighash did not match + WrongSighashFlag { + /// required sighash type + required: sighash::EcdsaSighashType, + /// the sighash type we got + got: sighash::EcdsaSighashType, + /// the corresponding publickey + pubkey: bitcoin::PublicKey, + }, +} + +#[cfg(feature = "std")] +impl std::error::Error for InputError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use self::InputError::*; + + match self { + CouldNotSatisfyTr + | InvalidRedeemScript { .. } + | InvalidWitnessScript { .. } + | InvalidSignature { .. } + | MissingRedeemScript + | MissingWitness + | MissingPubkey + | MissingWitnessScript + | MissingUtxo + | NonEmptyWitnessScript + | NonEmptyRedeemScript + | NonStandardSighashType(_) + | WrongSighashFlag { .. } => None, + SecpErr(e) => Some(e), + KeyErr(e) => Some(e), + Interpreter(e) => Some(e), + MiniscriptError(e) => Some(e), + } + } +} + +impl fmt::Display for InputError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + InputError::InvalidSignature { ref pubkey, ref sig } => { + write!(f, "PSBT: bad signature {} for key {:?}", pubkey, sig) + } + InputError::KeyErr(ref e) => write!(f, "Key Err: {}", e), + InputError::Interpreter(ref e) => write!(f, "Interpreter: {}", e), + InputError::SecpErr(ref e) => write!(f, "Secp Err: {}", e), + InputError::InvalidRedeemScript { ref redeem, ref p2sh_expected } => write!( + f, + "Redeem script {} does not match the p2sh script {}", + redeem, p2sh_expected + ), + InputError::InvalidWitnessScript { ref witness_script, ref p2wsh_expected } => write!( + f, + "Witness script {} does not match the p2wsh script {}", + witness_script, p2wsh_expected + ), + InputError::MiniscriptError(ref e) => write!(f, "Miniscript Error: {}", e), + InputError::MissingWitness => write!(f, "PSBT is missing witness"), + InputError::MissingRedeemScript => write!(f, "PSBT is Redeem script"), + InputError::MissingUtxo => { + write!(f, "PSBT is missing both witness and non-witness UTXO") + } + InputError::MissingWitnessScript => write!(f, "PSBT is missing witness script"), + InputError::MissingPubkey => write!(f, "Missing pubkey for a pkh/wpkh"), + InputError::NonEmptyRedeemScript => { + write!(f, "PSBT has non-empty redeem script at for legacy transactions") + } + InputError::NonEmptyWitnessScript => { + write!(f, "PSBT has non-empty witness script at for legacy input") + } + InputError::WrongSighashFlag { required, got, pubkey } => write!( + f, + "PSBT: signature with key {:?} had \ + sighashflag {:?} rather than required {:?}", + pubkey, got, required + ), + InputError::CouldNotSatisfyTr => write!(f, "Could not satisfy Tr descriptor"), + InputError::NonStandardSighashType(ref e) => + write!(f, "Non-standard sighash type {}", e), + } + } +} + +impl From for InputError { + fn from(e: crate::miniscript::Error) -> Self { Self::MiniscriptError(e) } +} + +impl From for InputError { + fn from(e: interpreter::Error) -> Self { Self::Interpreter(e) } +} + +impl From for InputError { + fn from(e: bitcoin::secp256k1::Error) -> Self { Self::SecpErr(e) } +} + +impl From for InputError { + fn from(e: bitcoin::key::Error) -> Self { Self::KeyErr(e) } +} diff --git a/src/v2/miniscript/mod.rs b/src/v2/miniscript/mod.rs new file mode 100644 index 0000000..46c497d --- /dev/null +++ b/src/v2/miniscript/mod.rs @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Implementation of the Finalizer role as defined in [BIP-174]. +//! +//! # Finalizer Role +//! +//! > For each input, the Input Finalizer determines if the input has enough data to pass validation. +//! +//! Determining if a PSBT has enough data to satisfy the spending conditions of all its inputs +//! requires usage of `rust-miniscript`. +//! +//! # Extractor Role +//! +//! > The Transaction Extractor does not need to know how to interpret scripts in order +//! > to extract the network serialized transaction. +//! +//! The Extractor role does not technically require `rust-miniscript` but since a PSBT must be + +//! [BIP-174]: + +mod finalize; +mod satisfy; + +use core::fmt; + +use bitcoin::consensus::encode::VarInt; +use bitcoin::secp256k1::{Secp256k1, Verification}; +use bitcoin::sighash::Prevouts; +use bitcoin::{Script, Sequence, Transaction, TxOut, Witness}; +use miniscript::miniscript::satisfy::Placeholder; +use miniscript::{interpreter, Interpreter, MiniscriptKey}; + +use crate::error::write_err; +use crate::prelude::Borrow; +use crate::v2::map::Input; +use crate::v2::{DetermineLockTimeError, Psbt}; + +#[rustfmt::skip] // Keep public exports separate. +pub use self::finalize::{Finalizer, InputError, FinalizeError, FinalizeInputError}; + +impl Psbt { + // TODO: Should this be on a Role? Finalizer/Extractor? Then we can remove the debug_assert + /// Interprets all PSBT inputs and checks whether the script is correctly interpreted according + /// to the context. + /// + /// The psbt must have included final script sig and final witness. In other words, this checks + /// whether the finalized psbt interprets correctly + pub fn interpreter_check( + &self, + secp: &Secp256k1, + ) -> Result<(), InterpreterCheckError> { + debug_assert!(self.is_finalized()); + + let unsigned_tx = self.unsigned_tx()?; // Used to verify signatures. + + let utxos: Vec<&TxOut> = self + .iter_funding_utxos() + .map(|res| res.expect("finalized PSBT has funding utxos")) + .collect(); + let utxos = &Prevouts::All(&utxos); + for (index, input) in self.inputs.iter().enumerate() { + self.interpreter_check_input( + secp, + &unsigned_tx, + index, + input, + utxos, + input.final_script_witness.as_ref().expect("checked in is_finalized"), + input.final_script_sig.as_ref().expect("checked in is_finalized"), + )?; + } + Ok(()) + } + + /// Runs the miniscript interpreter on a single psbt input. + #[allow(clippy::too_many_arguments)] // TODO: Remove this. + fn interpreter_check_input>( + &self, + secp: &Secp256k1, + unsigned_tx: &Transaction, + index: usize, + input: &Input, + utxos: &Prevouts, + witness: &Witness, + script_sig: &Script, + ) -> Result<(), InterpreterCheckInputError> { + use InterpreterCheckInputError::*; + + let spk = &input.funding_utxo().expect("have funding utxo").script_pubkey; + + // TODO: Check that this is correct? + let cltv = input.lock_time(); + // TODO: is this usage of MAX correct? + let csv = input.sequence.unwrap_or(Sequence::MAX); + + let interpreter = Interpreter::from_txdata(spk, script_sig, witness, csv, cltv) + .map_err(|error| Constructor { input_index: index, error })?; + + let iter = interpreter.iter(secp, unsigned_tx, index, utxos); + // TODO: Ok to just return the first satisfaction error? + if let Some(error) = iter.filter_map(Result::err).next() { + return Err(Satisfaction { input_index: index, error }); + }; + + Ok(()) + } +} + +pub(crate) trait ItemSize { + fn size(&self) -> usize; +} + +impl ItemSize for Placeholder { + fn size(&self) -> usize { + match self { + Placeholder::Pubkey(_, size) => *size, + Placeholder::PubkeyHash(_, size) => *size, + Placeholder::EcdsaSigPk(_) | Placeholder::EcdsaSigPkHash(_) => 73, + Placeholder::SchnorrSigPk(_, _, size) | Placeholder::SchnorrSigPkHash(_, _, size) => + size + 1, // +1 for the OP_PUSH + Placeholder::HashDissatisfaction + | Placeholder::Sha256Preimage(_) + | Placeholder::Hash256Preimage(_) + | Placeholder::Ripemd160Preimage(_) + | Placeholder::Hash160Preimage(_) => 33, + Placeholder::PushOne => 2, // On legacy this should be 1 ? + Placeholder::PushZero => 1, + Placeholder::TapScript(s) => s.len(), + Placeholder::TapControlBlock(cb) => cb.serialize().len(), + } + } +} + +impl ItemSize for Vec { + fn size(&self) -> usize { self.len() } +} + +// Helper function to calculate witness size +pub(crate) fn witness_size(wit: &[T]) -> usize { + wit.iter().map(T::size).sum::() + varint_len(wit.len()) +} + +pub(crate) fn varint_len(n: usize) -> usize { VarInt(n as u64).size() } + +/// Error type for Pbst Input +#[derive(Debug)] +pub enum InterpreterCheckError { + /// Failed to determine lock time for unsigned transaction. + DetermineLockTime(DetermineLockTimeError), + /// Interpreter check failed for an input. + InterpreterCheckInput(InterpreterCheckInputError), +} + +impl fmt::Display for InterpreterCheckError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use InterpreterCheckError::*; + + match *self { + DetermineLockTime(ref e) => write_err!(f, "interpreter check determine locktime"; e), + InterpreterCheckInput(ref e) => write_err!(f, "interpreter check failed for input"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InterpreterCheckError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use InterpreterCheckError::*; + + match *self { + DetermineLockTime(ref e) => Some(e), + InterpreterCheckInput(ref e) => Some(e), + } + } +} + +impl From for InterpreterCheckError { + fn from(e: DetermineLockTimeError) -> Self { Self::DetermineLockTime(e) } +} + +impl From for InterpreterCheckError { + fn from(e: InterpreterCheckInputError) -> Self { Self::InterpreterCheckInput(e) } +} + +/// Error type for Pbst Input +#[derive(Debug)] +pub enum InterpreterCheckInputError { + /// Failed to construct a [`miniscript::Interpreter`]. + Constructor { input_index: usize, error: interpreter::Error }, + /// Interpreter satisfaction failed for input. + Satisfaction { input_index: usize, error: interpreter::Error }, +} + +impl fmt::Display for InterpreterCheckInputError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use InterpreterCheckInputError::*; + + match *self { + Constructor { input_index, ref error } => + write_err!(f, "Interpreter constructor failed for input {}", input_index; error), + Satisfaction { input_index, ref error } => + write_err!(f, "Interpreter satisfaction failed for input {}", input_index; error), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InterpreterCheckInputError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use InterpreterCheckInputError::*; + + match *self { + Constructor { input_index: _, ref error } => Some(error), + Satisfaction { input_index: _, ref error } => Some(error), + } + } +} diff --git a/src/v2/miniscript/satisfy.rs b/src/v2/miniscript/satisfy.rs new file mode 100644 index 0000000..5e1394b --- /dev/null +++ b/src/v2/miniscript/satisfy.rs @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: CC0-1.0 + +use crate::bitcoin::hashes::{hash160, sha256d, Hash}; +use crate::bitcoin::key::XOnlyPublicKey; +use crate::bitcoin::taproot::{self, ControlBlock, LeafVersion, TapLeafHash}; +use crate::bitcoin::{absolute, ecdsa, ScriptBuf, Sequence}; +use crate::miniscript::{MiniscriptKey, Preimage32, Satisfier, SigType, ToPublicKey}; +use crate::prelude::*; +use crate::v2::map::input::Input; + +/// A PSBT [`Satisfier`] for an input. +/// +/// Contains reference to the [`Psbt`] because multiple inputs will share the same PSBT. All +/// operations on this structure will panic if index is more than number of inputs in pbst +/// +/// [`Satisfier`]: crate::miniscript::Satisfier +pub struct InputSatisfier<'a> { + pub(crate) input: &'a Input, +} + +impl<'a, Pk: MiniscriptKey + ToPublicKey> Satisfier for InputSatisfier<'a> { + fn lookup_tap_key_spend_sig(&self) -> Option { self.input.tap_key_sig } + + fn lookup_tap_leaf_script_sig(&self, pk: &Pk, lh: &TapLeafHash) -> Option { + self.input.tap_script_sigs.get(&(pk.to_x_only_pubkey(), *lh)).copied() + } + + fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { + self.input + .bip32_derivations + .iter() + .find(|&(pubkey, _)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) + .map(|(pubkey, _)| bitcoin::PublicKey::new(*pubkey)) + } + + fn lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { + Some(&self.input.tap_scripts) + } + + fn lookup_raw_pkh_tap_leaf_script_sig( + &self, + pkh: &(hash160::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, taproot::Signature)> { + self.input + .tap_script_sigs + .iter() + .find(|&((pubkey, lh), _sig)| { + pubkey.to_pubkeyhash(SigType::Schnorr) == pkh.0 && *lh == pkh.1 + }) + .map(|((x_only_pk, _leaf_hash), sig)| (*x_only_pk, *sig)) + } + + fn lookup_ecdsa_sig(&self, pk: &Pk) -> Option { + self.input.partial_sigs.get(&pk.to_public_key()).copied() + } + + fn lookup_raw_pkh_ecdsa_sig( + &self, + pkh: &hash160::Hash, + ) -> Option<(bitcoin::PublicKey, ecdsa::Signature)> { + self.input + .partial_sigs + .iter() + .find(|&(pubkey, _sig)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) + .map(|(pk, sig)| (*pk, *sig)) + } + + // TODO: Verify this is correct. + fn check_after(&self, n: absolute::LockTime) -> bool { + use absolute::LockTime::*; + + match n { + Blocks(height) => + if let Some(lock_time) = self.input.min_height { + return height <= lock_time; + }, + Seconds(time) => + if let Some(lock_time) = self.input.min_time { + return time <= lock_time; + }, + } + true + } + + // TODO: Verify this is correct. + fn check_older(&self, n: Sequence) -> bool { + // https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki + // Disable flag set => return true. + if !n.is_relative_lock_time() { + return true; + } + + match self.input.sequence { + Some(sequence) => { + // TODO: Do we need to check the tx version? + if !sequence.is_relative_lock_time() { + return false; + } + >::check_older(&sequence, n) + } + // TODO: What to check here? + None => true, + } + } + + fn lookup_hash160(&self, h: &Pk::Hash160) -> Option { + self.input.hash160_preimages.get(&Pk::to_hash160(h)).and_then(try_vec_as_preimage32) + } + + fn lookup_sha256(&self, h: &Pk::Sha256) -> Option { + self.input.sha256_preimages.get(&Pk::to_sha256(h)).and_then(try_vec_as_preimage32) + } + + fn lookup_hash256(&self, h: &Pk::Hash256) -> Option { + self.input + .hash256_preimages + .get(&sha256d::Hash::from_byte_array(Pk::to_hash256(h).to_byte_array())) // upstream psbt operates on hash256 + .and_then(try_vec_as_preimage32) + } + + fn lookup_ripemd160(&self, h: &Pk::Ripemd160) -> Option { + self.input.ripemd160_preimages.get(&Pk::to_ripemd160(h)).and_then(try_vec_as_preimage32) + } +} + +fn try_vec_as_preimage32(vec: &Vec) -> Option { + if vec.len() == 32 { + let mut arr = [0u8; 32]; + arr.copy_from_slice(vec); + Some(arr) + } else { + None + } +} diff --git a/src/v2/mod.rs b/src/v2/mod.rs index 540d32b..b154705 100644 --- a/src/v2/mod.rs +++ b/src/v2/mod.rs @@ -15,7 +15,8 @@ //! - The **Constructor**: Use the [`Constructor`] type. //! - The **Updater** role: Use the [`Updater`] type and then update additional fields of the [`Psbt`] directly. //! - The **Signer** role: Use the [`Signer`] type. -//! - The **Transaction Extractor** role: TODO +//! - The **Finalizer** role: Use the `Finalizer` type (requires "miniscript" feature). +//! - The **Extractor** role: Use the [`Extractor`] type. //! //! To combine PSBTs use either `psbt.combine_with(other)` or `v2::combine(this, that)`. //! @@ -23,8 +24,10 @@ //! [BIP-370]: mod error; -mod extractor; +mod extract; mod map; +#[cfg(feature = "miniscript")] +mod miniscript; use core::fmt; use core::marker::PhantomData; @@ -45,12 +48,16 @@ use crate::v0; use crate::v2::map::{global, input, output, Map}; #[rustfmt::skip] // Keep public exports separate. +#[doc(inline)] pub use self::{ - error::{IndexOutOfBoundsError, ExtractTxError, SignError, PsbtNotModifiableError, NotUnsignedError, OutputsNotModifiableError, InputsNotModifiableError, DetermineLockTimeError, DeserializePsbtError}, + error::{IndexOutOfBoundsError, SignError, PsbtNotModifiableError, NotUnsignedError, OutputsNotModifiableError, InputsNotModifiableError, DetermineLockTimeError, DeserializePsbtError, PartialSigsSighashTypeError}, map::{Input, InputBuilder, Output, OutputBuilder, Global}, + extract::{ExtractTxError, ExtractTxFeeRateError, Extractor} }; #[cfg(feature = "base64")] pub use self::display_from_str::PsbtParseError; +#[cfg(feature = "miniscript")] +pub use self::miniscript::{FinalizeError, FinalizeInputError, Finalizer, InputError}; /// Combines these two PSBTs as described by BIP-174 (i.e. combine is the same for BIP-370). pub fn combine(this: Psbt, that: Psbt) -> Result { @@ -490,6 +497,8 @@ pub struct Psbt { } impl Psbt { + // TODO: Add inherent methods to get each of the role types. + /// Returns this PSBT's unique identification. fn id(&self) -> Result { let mut tx = self.unsigned_tx()?; @@ -509,7 +518,7 @@ impl Psbt { Ok(Transaction { version: self.global.tx_version, lock_time, - input: self.inputs.iter().map(|input| input.tx_in()).collect(), + input: self.inputs.iter().map(|input| input.unsigned_tx_in()).collect(), output: self.outputs.iter().map(|ouput| ouput.tx_out()).collect(), }) } @@ -565,6 +574,9 @@ impl Psbt { Ok(lock) } + /// Returns true if all inputs for this PSBT have been finalized. + pub fn is_finalized(&self) -> bool { self.inputs.iter().all(|input| input.is_finalized()) } + /// Serialize a value as bytes in hex. pub fn serialize_hex(&self) -> String { self.serialize().to_lower_hex_string() } @@ -935,6 +947,38 @@ impl Psbt { input_value.checked_sub(output_value).map(Amount::from_sat).ok_or(Negative) } + + /// Checks the sighash types of input partial sigs (ECDSA). + /// + /// This can be used at anytime but is primarily used during PSBT finalizing. + // TODO: Would pub(crate) be better? + // TODO: It would be nice if this was enforced by the typesystem and fields if the `Input`. + pub fn check_partial_sigs_sighash_type(&self) -> Result<(), PartialSigsSighashTypeError> { + use PartialSigsSighashTypeError::*; + + for (input_index, input) in self.inputs.iter().enumerate() { + let target_ecdsa_sighash_ty = match input.sighash_type { + Some(psbt_hash_ty) => psbt_hash_ty + .ecdsa_hash_ty() + .map_err(|error| NonStandardInputSighashType { input_index, error })?, + None => EcdsaSighashType::All, + }; + + for (key, ecdsa_sig) in &input.partial_sigs { + let flag = EcdsaSighashType::from_standard(ecdsa_sig.hash_ty as u32) + .map_err(|error| NonStandardPartialSigsSighashType { input_index, error })?; + if target_ecdsa_sighash_ty != flag { + return Err(WrongSighashFlag { + input_index, + required: target_ecdsa_sighash_ty, + got: flag, + pubkey: *key, + }); + } + } + } + Ok(()) + } } /// Data required to call [`GetKey`] to get the private key to sign an input.