From 0aa5252508949f74f4f8d5113465d6e447f5caa2 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 23 Jan 2024 10:13:18 +1100 Subject: [PATCH] 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 | 60 +--- src/v2/extract.rs | 218 ++++++++++++ src/v2/extractor.rs | 97 ------ src/v2/map/input.rs | 139 +++++++- src/v2/miniscript/finalize.rs | 631 ++++++++++++++++++++++++++++++++++ src/v2/miniscript/mod.rs | 217 ++++++++++++ src/v2/miniscript/satisfy.rs | 136 ++++++++ src/v2/mod.rs | 117 ++++++- 10 files changed, 1451 insertions(+), 173 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..98000b6 100644 --- a/src/v2/error.rs +++ b/src/v2/error.rs @@ -4,11 +4,10 @@ use core::fmt; -use bitcoin::{sighash, FeeRate, Transaction}; +use bitcoin::sighash; use crate::error::{write_err, FundingUtxoError}; use crate::v2::map::{global, input, output}; -use crate::v2::Psbt; /// Error while deserializing a PSBT. /// @@ -102,59 +101,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 +248,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)] diff --git a/src/v2/extract.rs b/src/v2/extract.rs new file mode 100644 index 0000000..0e97866 --- /dev/null +++ b/src/v2/extract.rs @@ -0,0 +1,218 @@ +// 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 [`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) -> 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..3db82a2 100644 --- a/src/v2/map/input.rs +++ b/src/v2/map/input.rs @@ -182,6 +182,81 @@ 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 + 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 +276,37 @@ 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(), + } + } + + 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 +318,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 +899,31 @@ impl fmt::Display for HashType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", stringify!(self)) } } +/// Error finalizing an input. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FinalizeError { + /// Failed to create a final witness. + EmptyWitness, + /// Unexpected witness data. + UnexpectedWitness, +} + +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(feature = "std")] +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..f74d30d 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; @@ -36,7 +39,7 @@ use bitcoin::hashes::Hash; use bitcoin::key::{PrivateKey, PublicKey}; use bitcoin::locktime::absolute; use bitcoin::secp256k1::{Message, Secp256k1, Signing}; -use bitcoin::sighash::{EcdsaSighashType, SighashCache}; +use bitcoin::sighash::{EcdsaSighashType, NonStandardSighashTypeError, SighashCache}; use bitcoin::{ecdsa, transaction, Amount, Sequence, Transaction, TxOut, Txid}; use crate::error::{write_err, FeeError, FundingUtxoError, InconsistentKeySourcesError}; @@ -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}, 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. @@ -1190,6 +1234,69 @@ impl From for DecodeError { fn from(e: output::DecodeError) -> Self { Self::Output(e) } } +// 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, + } + } +} + /// If the "base64" feature is enabled we implement `Display` and `FromStr` using base64 encoding. #[cfg(feature = "base64")] mod display_from_str {