diff --git a/Cargo.toml b/Cargo.toml index e0050ef..4847825 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,10 @@ secp256k1 = { version = "0.28", features = ["rand-std", "global-context"] } [[example]] name = "v0" required-features = ["std"] + +[[example]] +name = "v2" +required-features = ["std"] + +[[example]] +name = "v2-separate-creator-constructor" diff --git a/contrib/test.sh b/contrib/test.sh index 6760cc1..85ce2d9 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -24,6 +24,8 @@ if [ "$DO_LINT" = true ] then cargo clippy --all-features --all-targets -- -D warnings cargo clippy --example v0 -- -D warnings + cargo clippy --example v2 -- -D warnings + cargo clippy --example v2-separate-creator-constructor -- -D warnings fi # Test without any features other than std first (same as default) @@ -38,6 +40,8 @@ do done cargo run --example v0 +cargo run --example v2 +cargo clippy --example v2-separate-creator-constructor if [ "$DO_NO_STD" = true ] then diff --git a/examples/v2-separate-creator-constructor.rs b/examples/v2-separate-creator-constructor.rs new file mode 100644 index 0000000..df0b676 --- /dev/null +++ b/examples/v2-separate-creator-constructor.rs @@ -0,0 +1,62 @@ +//! PSBT v2 - Creator a PSBT and hand it around to various different entities to add inputs and outputs. + +use psbt::bitcoin::hashes::Hash as _; +use psbt::bitcoin::{Amount, OutPoint, ScriptBuf, TxOut, Txid}; +use psbt::v2::{ + Constructor, Creator, InputBuilder, InputsOnlyModifiable, OutputBuilder, OutputsOnlyModifiable, + Psbt, +}; + +fn main() -> anyhow::Result<()> { + // Create the PSBT. + let created = Creator::new().inputs_modifiable().outputs_modifiable().psbt(); + + let ser = created.serialize(); + + // The first constructor entity receives the PSBT and adds an input. + let psbt = Psbt::deserialize(&ser)?; + let in_0 = dummy_out_point(); + let ser = Constructor::::new(psbt)? + .input(InputBuilder::new(in_0).build()) + .psbt() + .serialize(); + + // The second constructor entity receives the PSBT with one input and adds a second input. + let psbt = Psbt::deserialize(&ser)?; + let in_1 = dummy_out_point(); + let ser = Constructor::::new(psbt)? + .input(InputBuilder::new(in_1).build()) + .no_more_inputs() + .psbt() + .serialize(); + + // The third constructor entity receives the PSBT with inputs and adds an output. + let psbt = Psbt::deserialize(&ser)?; + let output = dummy_tx_out(); + let ser = Constructor::::new(psbt)? + .output(OutputBuilder::new(output).build()) + .no_more_outputs() + .psbt() + .serialize(); + + // The PSBT is now ready for handling with the updater role. + let _updatable_psbt = Psbt::deserialize(&ser)?; + + Ok(()) +} + +/// A dummy `OutPoint`, this would usually be the unspent transaction that we are spending. +fn dummy_out_point() -> OutPoint { + let txid = Txid::hash(b"some arbitrary bytes"); + let vout = 0x15; + OutPoint { txid, vout } +} + +/// A dummy `TxOut`, this would usually be the output we are creating with this transaction. +fn dummy_tx_out() -> TxOut { + // Arbitrary script, may not even be a valid scriptPubkey. + let script = ScriptBuf::from_hex("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac") + .expect("failed to parse script form hex"); + let value = Amount::from_sat(123_456_789); + TxOut { value, script_pubkey: script } +} diff --git a/examples/v2.rs b/examples/v2.rs new file mode 100644 index 0000000..0483c46 --- /dev/null +++ b/examples/v2.rs @@ -0,0 +1,231 @@ +//! PSBT v2 2 of 2 multisig example. +//! +//! An example of using PSBT v0 to create a 2 of 2 multisig by spending two native segwit v0 inputs +//! to a native segwit v0 output (the multisig output). +//! +//! We sign invalid inputs, this code is not run against Bitcoin Core so everything here should be +//! taken as NOT PROVEN CORRECT. +//! +//! This code is similar to `v0.rs` on purpose to show the differences between the APIs. + +use std::collections::BTreeMap; + +use psbt::bitcoin::hashes::Hash as _; +use psbt::bitcoin::locktime::absolute; +use psbt::bitcoin::opcodes::all::OP_CHECKMULTISIG; +use psbt::bitcoin::secp256k1::{self, rand, SECP256K1}; +use psbt::bitcoin::{ + script, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, TxOut, Txid, +}; +use psbt::v2::{self, Constructor, Input, InputBuilder, Modifiable, Output, OutputBuilder, Psbt}; + +pub const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000); +pub const SPEND_AMOUNT: Amount = Amount::from_sat(20_000_000); + +const MAINNET: Network = Network::Bitcoin; // Bitcoin mainnet network. +const FEE: Amount = Amount::from_sat(1_000); // Usually this would be calculated. +const DUMMY_CHANGE_AMOUNT: Amount = Amount::from_sat(100_000); + +fn main() -> anyhow::Result<()> { + // Mimic two people, Alice and Bob, who wish to create a 2-of-2 multisig output together. + let alice = Alice::new(); + let bob = Bob::new(); + + // Each person provides their pubkey. + let pk_a = alice.public_key(); + let pk_b = bob.public_key(); + + // Use of a locktime is of course optional. + let min_required_height = absolute::Height::from_consensus(800_000).expect("valid height"); + + // Each party will be contributing 20,000,000 sats to the mulitsig output, as such each party + // provides an unspent input to create the multisig output (and any change details if needed). + + // Alice has a UTXO that is too big, she needs change. + let (previous_output_a, change_address_a, change_value_a) = alice.contribute_to_multisig(); + + // Bob has a UTXO the right size so no change needed. + let previous_output_b = bob.contribute_to_multisig(); + + // In PSBT v1 the creator and constructor roles can be the same entity, for an example of having + // them separate see `./v2-separate-creator-constructor.rs`. + + // The constructor role. + + let constructor = Constructor::::default(); + + let input_a = InputBuilder::new(previous_output_a) + .minimum_required_height_based_lock_time(min_required_height) + .build(); + + // If no lock time is required we can just create the `Input` directly. + let input_b = Input::new(previous_output_b); + + // Build Alice's change output. + let change = TxOut { value: change_value_a, script_pubkey: change_address_a.script_pubkey() }; + + // Create the witness script, receive address, and the locking script. + let witness_script = multisig_witness_script(&pk_a, &pk_b); + let address = Address::p2wsh(&witness_script, MAINNET); + let value = SPEND_AMOUNT * 2 - FEE; + // The spend output is locked by the witness script. + let multi = TxOut { value, script_pubkey: address.script_pubkey() }; + + let psbt = constructor + .input(input_a) + .input(input_b) + .output(OutputBuilder::new(multi).build()) // Use of the `OutputBuilder` is identical + .output(Output::new(change)) // to just creating the `Output`. + .psbt(); + + // The updater role. + + let mut psbt = psbt.set_sequence(Sequence::ENABLE_LOCKTIME_NO_RBF, 1)?; + + // Update the PSBT with the inputs described by `previous_output_a` and `previous_output_b` + // above, here we get them from Alice and Bob, typically the update would have access to chain + // data and would get them from there. + psbt.inputs[0].witness_utxo = Some(alice.input_utxo()); + psbt.inputs[1].witness_utxo = Some(bob.input_utxo()); + + // Each party signs a copy of the PSBT. + let signed_by_a = alice.sign(psbt.clone()); + let signed_by_b = bob.sign(psbt); + + let _signed = v2::combine(signed_by_a, signed_by_b); + + // At this stage we would usually finalize with miniscript and extract the transaction. + + Ok(()) +} + +/// Creates a 2-of-2 multisig script locking to a and b's keys. +fn multisig_witness_script(a: &PublicKey, b: &PublicKey) -> ScriptBuf { + script::Builder::new() + .push_int(2) + .push_key(a) + .push_key(b) + .push_int(2) + .push_opcode(OP_CHECKMULTISIG) + .into_script() +} + +/// Party 1 in a 2-of-2 multisig. +pub struct Alice(Entity); + +impl Alice { + /// Creates a new actor with random keys. + pub fn new() -> Self { Self(Entity::new_random()) } + + /// Returns the public key for this entity. + pub fn public_key(&self) -> bitcoin::PublicKey { self.0.public_key() } + + /// Alice provides an input to be used to create the multisig and the details required to get + /// some change back (change address and amount). + pub fn contribute_to_multisig(&self) -> (OutPoint, Address, Amount) { + // An obviously invalid output, we just use all zeros then use the `vout` to differentiate + // Alice's output from Bob's. + let out = OutPoint { txid: Txid::all_zeros(), vout: 0 }; + + // The usual caveat about reusing addresses applies here, this is just an example. + let address = + Address::p2wpkh(&self.public_key(), Network::Bitcoin).expect("uncompressed key"); + + // This is a made up value, it is supposed to represent the outpoints value minus the value + // contributed to the multisig. + let amount = DUMMY_CHANGE_AMOUNT; + + (out, address, amount) + } + + /// Provides the actual UTXO that Alice is contributing, this would usually come from the chain. + pub fn input_utxo(&self) -> TxOut { + // A dummy script_pubkey representing a UTXO that is locked to a pubkey that Alice controls. + let script_pubkey = + ScriptBuf::new_p2wpkh(&self.public_key().wpubkey_hash().expect("uncompressed key")); + TxOut { value: DUMMY_UTXO_AMOUNT, script_pubkey } + } + + /// Signs `psbt`. + pub fn sign(&self, psbt: Psbt) -> Psbt { self.0.sign_ecdsa(psbt) } +} + +impl Default for Alice { + fn default() -> Self { Self::new() } +} + +/// Party 2 in a 2-of-2 multisig. +pub struct Bob(Entity); + +impl Bob { + /// Creates a new actor with random keys. + pub fn new() -> Self { Self(Entity::new_random()) } + + /// Returns the public key for this entity. + pub fn public_key(&self) -> bitcoin::PublicKey { self.0.public_key() } + + /// Bob provides an input to be used to create the multisig, its the right size so no change. + pub fn contribute_to_multisig(&self) -> OutPoint { + // An obviously invalid output, we just use all zeros then use the `vout` to differentiate + // Alice's output from Bob's. + OutPoint { txid: Txid::all_zeros(), vout: 1 } + } + + /// Provides the actual UTXO that Alice is contributing, this would usually come from the chain. + pub fn input_utxo(&self) -> TxOut { + // A dummy script_pubkey representing a UTXO that is locked to a pubkey that Bob controls. + let script_pubkey = + ScriptBuf::new_p2wpkh(&self.public_key().wpubkey_hash().expect("uncompressed key")); + TxOut { value: DUMMY_UTXO_AMOUNT, script_pubkey } + } + + /// Signs `psbt`. + pub fn sign(&self, psbt: Psbt) -> Psbt { self.0.sign_ecdsa(psbt) } +} + +impl Default for Bob { + fn default() -> Self { Self::new() } +} + +/// An entity that can take on one of the PSBT roles. +pub struct Entity { + sk: secp256k1::SecretKey, + pk: secp256k1::PublicKey, +} + +impl Entity { + /// Creates a new entity with random keys. + pub fn new_random() -> Self { + let (sk, pk) = random_keys(); + Entity { sk, pk } + } + + /// Returns the private key for this entity. + fn private_key(&self) -> bitcoin::PrivateKey { bitcoin::PrivateKey::new(self.sk, MAINNET) } + + /// Returns the public key for this entity. + /// + /// All examples use segwit so this key is serialize in compressed form. + pub fn public_key(&self) -> bitcoin::PublicKey { bitcoin::PublicKey::new(self.pk) } + + /// Signs any ECDSA inputs for which we have keys. + pub fn sign_ecdsa(&self, mut psbt: Psbt) -> Psbt { + let sk = self.private_key(); + let pk = self.public_key(); + + let mut keys = BTreeMap::new(); + keys.insert(pk, sk); + psbt.sign(&keys, &SECP256K1).expect("failed to sign psbt"); + + psbt + } +} + +/// Creates a set of random secp256k1 keys. +/// +/// In a real application these would come from actual secrets. +fn random_keys() -> (secp256k1::SecretKey, secp256k1::PublicKey) { + let sk = secp256k1::SecretKey::new(&mut rand::thread_rng()); + let pk = sk.public_key(SECP256K1); + (sk, pk) +} diff --git a/src/consts.rs b/src/consts.rs index ccfa1b7..88fa495 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -10,19 +10,14 @@ pub(crate) const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00; /// Type: Extended Public Key PSBT_GLOBAL_XPUB = 0x01 pub(crate) const PSBT_GLOBAL_XPUB: u8 = 0x01; /// Type: Transaction Version PSBT_GLOBAL_TX_VERSION = 0x02 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_GLOBAL_TX_VERSION: u8 = 0x02; /// Type: Fallback Locktime PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_GLOBAL_FALLBACK_LOCKTIME: u8 = 0x03; /// Type: Input Count PSBT_GLOBAL_INPUT_COUNT = 0x04 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_GLOBAL_INPUT_COUNT: u8 = 0x04; /// Type: Output Count PSBT_GLOBAL_OUTPUT_COUNT = 0x05 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_GLOBAL_OUTPUT_COUNT: u8 = 0x05; /// Type: Transaction Modifiable Flags PSBT_GLOBAL_TX_MODIFIABLE = 0x06 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_GLOBAL_TX_MODIFIABLE: u8 = 0x06; /// Type: Version Number PSBT_GLOBAL_VERSION = 0xFB pub(crate) const PSBT_GLOBAL_VERSION: u8 = 0xFB; @@ -59,19 +54,14 @@ pub(crate) const PSBT_IN_HASH160: u8 = 0x0c; /// Type: HASH256 preimage PSBT_IN_HASH256 = 0x0d pub(crate) const PSBT_IN_HASH256: u8 = 0x0d; /// Type: Previous TXID PSBT_IN_PREVIOUS_TXID = 0x0e -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_IN_PREVIOUS_TXID: u8 = 0x0e; /// Type: Spent Output Index PSBT_IN_OUTPUT_INDEX = 0x0f -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_IN_OUTPUT_INDEX: u8 = 0x0f; /// Type: Sequence Number PSBT_IN_SEQUENCE = 0x10 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_IN_SEQUENCE: u8 = 0x10; /// Type: Required Time-based Locktime PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_IN_REQUIRED_TIME_LOCKTIME: u8 = 0x11; /// Type: Required Height-based Locktime PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_IN_REQUIRED_HEIGHT_LOCKTIME: u8 = 0x12; /// Type: Taproot Signature in Key Spend PSBT_IN_TAP_KEY_SIG = 0x13 pub(crate) const PSBT_IN_TAP_KEY_SIG: u8 = 0x13; @@ -95,10 +85,8 @@ pub(crate) const PSBT_OUT_WITNESS_SCRIPT: u8 = 0x01; /// Type: BIP 32 Derivation Path PSBT_OUT_BIP32_DERIVATION = 0x02 pub(crate) const PSBT_OUT_BIP32_DERIVATION: u8 = 0x02; /// Type: Output Amount PSBT_OUT_AMOUNT = 0x03 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_OUT_AMOUNT: u8 = 0x03; /// Type: Output Script PSBT_OUT_SCRIPT = 0x04 -#[allow(unused)] // PSBT v2 pub(crate) const PSBT_OUT_SCRIPT: u8 = 0x04; /// Type: Taproot Internal Key PSBT_OUT_TAP_INTERNAL_KEY = 0x05 pub(crate) const PSBT_OUT_TAP_INTERNAL_KEY: u8 = 0x05; diff --git a/src/error.rs b/src/error.rs index 59d9c66..5e838e3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,10 +6,10 @@ use bitcoin::bip32::Xpub; // TODO: This should be exposed like this in rust-bitcoin. use bitcoin::consensus::encode as consensus; use bitcoin::transaction::Transaction; -use bitcoin::{hashes, secp256k1, taproot}; +use bitcoin::{absolute, hashes, secp256k1, taproot}; use crate::prelude::*; -use crate::{io, raw}; +use crate::{io, raw, version}; /// Enum for marking psbt hash error. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -104,6 +104,12 @@ pub enum Error { PartialDataConsumption, /// I/O error. Io(io::Error), + /// Couldn't converting parsed u32 to a lock time. + LockTime(absolute::Error), + /// Found a keypair type that is explicitly excluded. + ExcludedKey(u8), + /// Unsupported PSBT version. + UnsupportedVersion(version::UnsupportedVersionError), } impl fmt::Display for Error { @@ -158,6 +164,10 @@ impl fmt::Display for Error { PartialDataConsumption => f.write_str("data not consumed entirely when explicitly deserializing"), Io(ref e) => write_err!(f, "I/O error"; e), + LockTime(ref e) => write_err!(f, "parsed locktime invalid"; e), + ExcludedKey(t) => + write!(f, "found a keypair type that is explicitly excluded: {:x}", t), + UnsupportedVersion(ref e) => write_err!(f, "unsupported version"; e), } } } @@ -171,6 +181,8 @@ impl std::error::Error for Error { InvalidHash(ref e) => Some(e), ConsensusEncoding(ref e) => Some(e), Io(ref e) => Some(e), + LockTime(ref e) => Some(e), + UnsupportedVersion(ref e) => Some(e), InvalidMagic | MissingUtxo | InvalidSeparator @@ -199,7 +211,8 @@ impl std::error::Error for Error { | TapTree(_) | XPubKey(_) | Version(_) - | PartialDataConsumption => None, + | PartialDataConsumption + | ExcludedKey(_) => None, } } } @@ -216,6 +229,14 @@ impl From for Error { fn from(e: io::Error) -> Self { Error::Io(e) } } +impl From for Error { + fn from(e: absolute::Error) -> Self { Error::LockTime(e) } +} + +impl From for Error { + fn from(e: version::UnsupportedVersionError) -> Self { Error::UnsupportedVersion(e) } +} + /// Formats error. /// /// If `std` feature is OFF appends error source (delimited by `: `). We do this because diff --git a/src/lib.rs b/src/lib.rs index 41e4ecd..b23dedc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,8 @@ mod sighash_type; pub mod raw; pub mod serialize; pub mod v0; +pub mod v2; +mod version; #[cfg(feature = "std")] use std::io; @@ -57,12 +59,19 @@ use std::io; #[cfg(not(feature = "std"))] use core2::io; +use crate::version::Version; + #[rustfmt::skip] // Keep pubic re-exports separate pub use crate::{ error::Error, sighash_type::PsbtSighashType, }; +/// PSBT version 0 - the original PSBT version. +pub const V0: Version = Version::ZERO; +/// PSBT version 2 - the second PSBT version. +pub const V2: Version = Version::TWO; + #[rustfmt::skip] mod prelude { #[cfg(all(not(feature = "std"), not(test)))] diff --git a/src/raw.rs b/src/raw.rs index e936afc..a014568 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -12,11 +12,11 @@ use core::convert::TryFrom; use core::fmt; -// TODO: This should be exposed like this in rust-bitcoin. use bitcoin::consensus::encode as consensus; use bitcoin::consensus::encode::{ deserialize, serialize, Decodable, Encodable, ReadExt, VarInt, WriteExt, MAX_VEC_SIZE, }; +use bitcoin::hex::DisplayHex; use crate::prelude::*; use crate::serialize::{Deserialize, Serialize}; diff --git a/src/serialize.rs b/src/serialize.rs index 2c9695e..afd3e7f 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -18,7 +18,10 @@ use bitcoin::secp256k1::{self, XOnlyPublicKey}; use bitcoin::taproot::{ ControlBlock, LeafVersion, TapLeafHash, TapNodeHash, TapTree, TaprootBuilder, }; -use bitcoin::{ecdsa, taproot, ScriptBuf, Transaction, TxOut, VarInt, Witness}; +use bitcoin::{ + absolute, ecdsa, taproot, transaction, Amount, ScriptBuf, Sequence, Transaction, TxOut, Txid, + VarInt, Witness, +}; use crate::prelude::*; use crate::sighash_type::PsbtSighashType; @@ -37,13 +40,18 @@ pub(crate) trait Deserialize: Sized { fn deserialize(bytes: &[u8]) -> Result; } +impl_psbt_de_serialize!(absolute::LockTime); +impl_psbt_de_serialize!(Amount); impl_psbt_de_serialize!(Transaction); +impl_psbt_de_serialize!(transaction::Version); impl_psbt_de_serialize!(TxOut); impl_psbt_de_serialize!(Witness); +impl_psbt_de_serialize!(VarInt); impl_psbt_hash_de_serialize!(ripemd160::Hash); impl_psbt_hash_de_serialize!(sha256::Hash); impl_psbt_hash_de_serialize!(TapLeafHash); impl_psbt_hash_de_serialize!(TapNodeHash); +impl_psbt_hash_de_serialize!(Txid); impl_psbt_hash_de_serialize!(hash160::Hash); impl_psbt_hash_de_serialize!(sha256d::Hash); @@ -146,6 +154,52 @@ impl Deserialize for KeySource { } } +impl Serialize for u32 { + fn serialize(&self) -> Vec { serialize(&self) } +} + +impl Deserialize for u32 { + fn deserialize(bytes: &[u8]) -> Result { + let val: u32 = consensus::deserialize(bytes)?; + Ok(val) + } +} + +impl Serialize for Sequence { + fn serialize(&self) -> Vec { serialize(&self) } +} + +impl Deserialize for Sequence { + fn deserialize(bytes: &[u8]) -> Result { + let n: Sequence = consensus::deserialize(bytes)?; + Ok(n) + } +} + +impl Serialize for absolute::Height { + fn serialize(&self) -> Vec { serialize(&self.to_consensus_u32()) } +} + +impl Deserialize for absolute::Height { + fn deserialize(bytes: &[u8]) -> Result { + let n: u32 = consensus::deserialize(bytes)?; + let lock = absolute::Height::from_consensus(n)?; + Ok(lock) + } +} + +impl Serialize for absolute::Time { + fn serialize(&self) -> Vec { serialize(&self.to_consensus_u32()) } +} + +impl Deserialize for absolute::Time { + fn deserialize(bytes: &[u8]) -> Result { + let n: u32 = consensus::deserialize(bytes)?; + let lock = absolute::Time::from_consensus(n)?; + Ok(lock) + } +} + // partial sigs impl Serialize for Vec { fn serialize(&self) -> Vec { self.clone() } diff --git a/src/v0/map/global.rs b/src/v0/map/global.rs index 036920a..109d0c1 100644 --- a/src/v0/map/global.rs +++ b/src/v0/map/global.rs @@ -10,16 +10,15 @@ use bitcoin::consensus::encode::MAX_VEC_SIZE; use bitcoin::consensus::Decodable; use bitcoin::transaction::Transaction; +use crate::consts::{ + PSBT_GLOBAL_FALLBACK_LOCKTIME, PSBT_GLOBAL_INPUT_COUNT, PSBT_GLOBAL_OUTPUT_COUNT, + PSBT_GLOBAL_PROPRIETARY, PSBT_GLOBAL_TX_MODIFIABLE, PSBT_GLOBAL_TX_VERSION, + PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_VERSION, PSBT_GLOBAL_XPUB, +}; use crate::io::{self, Cursor, Read}; use crate::prelude::*; use crate::v0::map::Map; use crate::{raw, Error}; -use crate::consts::{ -PSBT_GLOBAL_UNSIGNED_TX, -PSBT_GLOBAL_XPUB, -PSBT_GLOBAL_VERSION, -PSBT_GLOBAL_PROPRIETARY, -}; /// The global key-value map. #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -54,7 +53,7 @@ impl Global { match raw::Pair::decode(&mut r) { Ok(pair) => { match pair.key.type_value { - PSBT_GLOBAL_UNSIGNED_TX => { + v if v == PSBT_GLOBAL_UNSIGNED_TX => { // key has to be empty if pair.key.key.is_empty() { // there can only be one unsigned transaction @@ -82,7 +81,7 @@ impl Global { return Err(Error::InvalidKey(pair.key)); } } - PSBT_GLOBAL_XPUB => { + v if v == PSBT_GLOBAL_XPUB => { if !pair.key.key.is_empty() { let xpub = Xpub::decode(&pair.key.key) .map_err(|_| Error::XPubKey( @@ -119,7 +118,7 @@ impl Global { )); } } - PSBT_GLOBAL_VERSION => { + v if v == PSBT_GLOBAL_VERSION => { // key has to be empty if pair.key.key.is_empty() { // there can only be one version @@ -146,7 +145,7 @@ impl Global { return Err(Error::InvalidKey(pair.key)); } } - PSBT_GLOBAL_PROPRIETARY => match proprietary + v if v == PSBT_GLOBAL_PROPRIETARY => match proprietary .entry(raw::ProprietaryKey::try_from(pair.key.clone())?) { btree_map::Entry::Vacant(empty_key) => { @@ -155,6 +154,22 @@ impl Global { btree_map::Entry::Occupied(_) => return Err(Error::DuplicateKey(pair.key)), }, + // PSBT v2 explicit excludes. + v if v == PSBT_GLOBAL_TX_VERSION => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_GLOBAL_TX_MODIFIABLE => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_GLOBAL_FALLBACK_LOCKTIME => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_GLOBAL_INPUT_COUNT => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_GLOBAL_OUTPUT_COUNT => { + return Err(Error::ExcludedKey(v)); + } _ => match unknowns.entry(pair.key) { btree_map::Entry::Vacant(empty_key) => { empty_key.insert(pair.value); @@ -213,6 +228,7 @@ impl Global { Ok(()) } + /// Combines this [`Psbt`] with `other` PSBT as described by BIP 174. /// /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` diff --git a/src/v0/map/input.rs b/src/v0/map/input.rs index 9ec4129..5749f1b 100644 --- a/src/v0/map/input.rs +++ b/src/v0/map/input.rs @@ -11,11 +11,12 @@ use bitcoin::{ecdsa, secp256k1, taproot, ScriptBuf, Transaction, TxOut, Witness} use crate::consts::{ PSBT_IN_BIP32_DERIVATION, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, - PSBT_IN_HASH160, PSBT_IN_HASH256, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_PARTIAL_SIG, - PSBT_IN_PROPRIETARY, PSBT_IN_REDEEM_SCRIPT, PSBT_IN_RIPEMD160, PSBT_IN_SHA256, - PSBT_IN_SIGHASH_TYPE, PSBT_IN_TAP_BIP32_DERIVATION, PSBT_IN_TAP_INTERNAL_KEY, - PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_LEAF_SCRIPT, PSBT_IN_TAP_MERKLE_ROOT, PSBT_IN_TAP_SCRIPT_SIG, - PSBT_IN_WITNESS_SCRIPT, PSBT_IN_WITNESS_UTXO, + PSBT_IN_HASH160, PSBT_IN_HASH256, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_OUTPUT_INDEX, + PSBT_IN_PARTIAL_SIG, PSBT_IN_PREVIOUS_TXID, PSBT_IN_PROPRIETARY, PSBT_IN_REDEEM_SCRIPT, + PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, PSBT_IN_REQUIRED_TIME_LOCKTIME, PSBT_IN_RIPEMD160, + PSBT_IN_SEQUENCE, PSBT_IN_SHA256, PSBT_IN_SIGHASH_TYPE, PSBT_IN_TAP_BIP32_DERIVATION, + PSBT_IN_TAP_INTERNAL_KEY, PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_LEAF_SCRIPT, + PSBT_IN_TAP_MERKLE_ROOT, PSBT_IN_TAP_SCRIPT_SIG, PSBT_IN_WITNESS_SCRIPT, PSBT_IN_WITNESS_UTXO, }; use crate::prelude::*; use crate::serialize::Deserialize; @@ -122,52 +123,52 @@ impl Input { let raw::Pair { key: raw_key, value: raw_value } = pair; match raw_key.type_value { - PSBT_IN_NON_WITNESS_UTXO => { + v if v == PSBT_IN_NON_WITNESS_UTXO => { impl_psbt_insert_pair! { self.non_witness_utxo <= | } } - PSBT_IN_WITNESS_UTXO => { + v if v == PSBT_IN_WITNESS_UTXO => { impl_psbt_insert_pair! { self.witness_utxo <= | } } - PSBT_IN_PARTIAL_SIG => { + v if v == PSBT_IN_PARTIAL_SIG => { impl_psbt_insert_pair! { self.partial_sigs <= | } } - PSBT_IN_SIGHASH_TYPE => { + v if v == PSBT_IN_SIGHASH_TYPE => { impl_psbt_insert_pair! { self.sighash_type <= | } } - PSBT_IN_REDEEM_SCRIPT => { + v if v == PSBT_IN_REDEEM_SCRIPT => { impl_psbt_insert_pair! { self.redeem_script <= | } } - PSBT_IN_WITNESS_SCRIPT => { + v if v == PSBT_IN_WITNESS_SCRIPT => { impl_psbt_insert_pair! { self.witness_script <= | } } - PSBT_IN_BIP32_DERIVATION => { + v if v == PSBT_IN_BIP32_DERIVATION => { impl_psbt_insert_pair! { self.bip32_derivation <= | } } - PSBT_IN_FINAL_SCRIPTSIG => { + v if v == PSBT_IN_FINAL_SCRIPTSIG => { impl_psbt_insert_pair! { self.final_script_sig <= | } } - PSBT_IN_FINAL_SCRIPTWITNESS => { + v if v == PSBT_IN_FINAL_SCRIPTWITNESS => { impl_psbt_insert_pair! { self.final_script_witness <= | } } - PSBT_IN_RIPEMD160 => { + v if v == PSBT_IN_RIPEMD160 => { psbt_insert_hash_pair( &mut self.ripemd160_preimages, raw_key, @@ -175,7 +176,7 @@ impl Input { error::PsbtHash::Ripemd, )?; } - PSBT_IN_SHA256 => { + v if v == PSBT_IN_SHA256 => { psbt_insert_hash_pair( &mut self.sha256_preimages, raw_key, @@ -183,7 +184,7 @@ impl Input { error::PsbtHash::Sha256, )?; } - PSBT_IN_HASH160 => { + v if v == PSBT_IN_HASH160 => { psbt_insert_hash_pair( &mut self.hash160_preimages, raw_key, @@ -191,7 +192,7 @@ impl Input { error::PsbtHash::Hash160, )?; } - PSBT_IN_HASH256 => { + v if v == PSBT_IN_HASH256 => { psbt_insert_hash_pair( &mut self.hash256_preimages, raw_key, @@ -199,37 +200,37 @@ impl Input { error::PsbtHash::Hash256, )?; } - PSBT_IN_TAP_KEY_SIG => { + v if v == PSBT_IN_TAP_KEY_SIG => { impl_psbt_insert_pair! { self.tap_key_sig <= | } } - PSBT_IN_TAP_SCRIPT_SIG => { + v if v == PSBT_IN_TAP_SCRIPT_SIG => { impl_psbt_insert_pair! { self.tap_script_sigs <= | } } - PSBT_IN_TAP_LEAF_SCRIPT => { + v if v == PSBT_IN_TAP_LEAF_SCRIPT => { impl_psbt_insert_pair! { self.tap_scripts <= |< raw_value: (ScriptBuf, LeafVersion)> } } - PSBT_IN_TAP_BIP32_DERIVATION => { + v if v == PSBT_IN_TAP_BIP32_DERIVATION => { impl_psbt_insert_pair! { self.tap_key_origins <= |< raw_value: (Vec, KeySource)> } } - PSBT_IN_TAP_INTERNAL_KEY => { + v if v == PSBT_IN_TAP_INTERNAL_KEY => { impl_psbt_insert_pair! { self.tap_internal_key <= |< raw_value: XOnlyPublicKey> } } - PSBT_IN_TAP_MERKLE_ROOT => { + v if v == PSBT_IN_TAP_MERKLE_ROOT => { impl_psbt_insert_pair! { self.tap_merkle_root <= |< raw_value: TapNodeHash> } } - PSBT_IN_PROPRIETARY => { + v if v == PSBT_IN_PROPRIETARY => { let key = raw::ProprietaryKey::try_from(raw_key.clone())?; match self.proprietary.entry(key) { btree_map::Entry::Vacant(empty_key) => { @@ -238,6 +239,22 @@ impl Input { btree_map::Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key)), } } + // PSBT v2 explicit excludes. + v if v == PSBT_IN_PREVIOUS_TXID => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_IN_OUTPUT_INDEX => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_IN_SEQUENCE => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_IN_REQUIRED_TIME_LOCKTIME => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_IN_REQUIRED_HEIGHT_LOCKTIME => { + return Err(Error::ExcludedKey(v)); + } _ => match self.unknown.entry(raw_key) { btree_map::Entry::Vacant(empty_key) => { empty_key.insert(raw_value); diff --git a/src/v0/map/output.rs b/src/v0/map/output.rs index d033feb..e25131d 100644 --- a/src/v0/map/output.rs +++ b/src/v0/map/output.rs @@ -8,8 +8,8 @@ use bitcoin::taproot::{TapLeafHash, TapTree}; use bitcoin::{secp256k1, ScriptBuf}; use crate::consts::{ - PSBT_OUT_BIP32_DERIVATION, PSBT_OUT_PROPRIETARY, PSBT_OUT_REDEEM_SCRIPT, - PSBT_OUT_TAP_BIP32_DERIVATION, PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE, + PSBT_OUT_AMOUNT, PSBT_OUT_BIP32_DERIVATION, PSBT_OUT_PROPRIETARY, PSBT_OUT_REDEEM_SCRIPT, + PSBT_OUT_SCRIPT, PSBT_OUT_TAP_BIP32_DERIVATION, PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE, PSBT_OUT_WITNESS_SCRIPT, }; use crate::prelude::*; @@ -50,22 +50,22 @@ impl Output { let raw::Pair { key: raw_key, value: raw_value } = pair; match raw_key.type_value { - PSBT_OUT_REDEEM_SCRIPT => { + v if v == PSBT_OUT_REDEEM_SCRIPT => { impl_psbt_insert_pair! { self.redeem_script <= | } } - PSBT_OUT_WITNESS_SCRIPT => { + v if v == PSBT_OUT_WITNESS_SCRIPT => { impl_psbt_insert_pair! { self.witness_script <= | } } - PSBT_OUT_BIP32_DERIVATION => { + v if v == PSBT_OUT_BIP32_DERIVATION => { impl_psbt_insert_pair! { self.bip32_derivation <= | } } - PSBT_OUT_PROPRIETARY => { + v if v == PSBT_OUT_PROPRIETARY => { let key = raw::ProprietaryKey::try_from(raw_key.clone())?; match self.proprietary.entry(key) { btree_map::Entry::Vacant(empty_key) => { @@ -74,21 +74,29 @@ impl Output { btree_map::Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key)), } } - PSBT_OUT_TAP_INTERNAL_KEY => { + v if v == PSBT_OUT_TAP_INTERNAL_KEY => { impl_psbt_insert_pair! { self.tap_internal_key <= | } } - PSBT_OUT_TAP_TREE => { + v if v == PSBT_OUT_TAP_TREE => { impl_psbt_insert_pair! { self.tap_tree <= | } } - PSBT_OUT_TAP_BIP32_DERIVATION => { + v if v == PSBT_OUT_TAP_BIP32_DERIVATION => { impl_psbt_insert_pair! { self.tap_key_origins <= |< raw_value: (Vec, KeySource)> } } + // PSBT v2 explicit excludes. + v if v == PSBT_OUT_AMOUNT => { + return Err(Error::ExcludedKey(v)); + } + v if v == PSBT_OUT_SCRIPT => { + return Err(Error::ExcludedKey(v)); + } + _ => match self.unknown.entry(raw_key) { btree_map::Entry::Vacant(empty_key) => { empty_key.insert(raw_value); diff --git a/src/v0/mod.rs b/src/v0/mod.rs index f7dd7c7..a21da67 100644 --- a/src/v0/mod.rs +++ b/src/v0/mod.rs @@ -24,11 +24,15 @@ use bitcoin::sighash::{EcdsaSighashType, SighashCache}; use bitcoin::transaction::{Transaction, TxOut}; use bitcoin::{ecdsa, Amount, ScriptBuf}; -use crate::error::write_err; +use crate::error::{write_err, Error}; use crate::prelude::*; -use crate::v0::error::{IndexOutOfBoundsError, SignError, SignerChecksError}; -use crate::v0::map::{Global, Input, Map, Output}; -use crate::Error; +use crate::v0::map::Map; + +#[rustfmt::skip] // Keep pubic re-exports separate +pub use self::{ + error::{IndexOutOfBoundsError, SignerChecksError, SignError}, + map::{Input, Output, Global}, +}; #[rustfmt::skip] // Keep public re-exports separate. #[cfg(feature = "base64")] @@ -1165,7 +1169,6 @@ mod tests { use bitcoin::witness::Witness; use super::*; - use crate::raw; use crate::v0::map::{Input, Map, Output}; #[test] @@ -1430,28 +1433,6 @@ mod tests { assert_eq!(redeem_script.to_p2sh(), expected_out); } - - #[test] - fn valid_vector_6() { - let psbt: Psbt = hex_psbt("70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000").unwrap(); - - assert_eq!(psbt.inputs.len(), 1); - assert_eq!(psbt.outputs.len(), 1); - - let tx = &psbt.global.unsigned_tx; - assert_eq!( - tx.txid(), - "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115".parse().unwrap(), - ); - - let mut unknown: BTreeMap> = BTreeMap::new(); - let key: raw::Key = raw::Key { type_value: 0x0fu8, key: hex!("010203040506070809") }; - let value: Vec = hex!("0102030405060708090a0b0c0d0e0f"); - - unknown.insert(key, value); - - assert_eq!(psbt.inputs[0].unknown, unknown) - } } mod bip_371_vectors { diff --git a/src/v2/error.rs b/src/v2/error.rs new file mode 100644 index 0000000..6b5f928 --- /dev/null +++ b/src/v2/error.rs @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! PSBT v0 errors. + +use core::fmt; + +use bitcoin::{sighash, FeeRate, Transaction}; + +use crate::error::write_err; +use crate::v2::Psbt; + +/// Input index out of bounds (actual index, maximum index allowed). +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum IndexOutOfBoundsError { + /// The index is out of bounds for the `psbt.inputs` vector. + Inputs { + /// Attempted index access. + index: usize, + /// Length of the PBST inputs vector. + length: usize, + }, + /// The index greater than the `psbt.global.input_count`. + Count { + /// Attempted index access. + index: usize, + /// Global input count. + count: usize, + }, +} + +impl fmt::Display for IndexOutOfBoundsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use IndexOutOfBoundsError::*; + + match *self { + Inputs { ref index, ref length } => write!( + f, + "index {} is out-of-bounds for PSBT inputs vector length {}", + index, length + ), + Count { ref index, ref count } => + write!(f, "index {} is greater global.input_count {}", index, count), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for IndexOutOfBoundsError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use IndexOutOfBoundsError::*; + + match *self { + Inputs { .. } | Count { .. } => None, + } + } +} + +/// This error is returned when extracting a [`Transaction`] from a [`Psbt`]. +/// +/// [`Psbt`]: crate::vo::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] +pub enum SignError { + /// Input index out of bounds. + IndexOutOfBounds(IndexOutOfBoundsError), + /// Invalid Sighash type. + InvalidSighashType, + /// Missing input utxo. + MissingInputUtxo, + /// Missing Redeem script. + MissingRedeemScript, + /// Missing spending utxo. + MissingSpendUtxo, + /// Missing witness script. + MissingWitnessScript, + /// Signing algorithm and key type does not match. + MismatchedAlgoKey, + /// Attempted to ECDSA sign an non-ECDSA input. + NotEcdsa, + /// The `scriptPubkey` is not a P2WPKH script. + NotWpkh, + /// Sighash computation error. + SighashComputation(sighash::Error), + /// Unable to determine the output type. + UnknownOutputType, + /// Unable to find key. + KeyNotFound, + /// Attempt to sign an input with the wrong signing algorithm. + WrongSigningAlgorithm, + /// Signing request currently unsupported. + Unsupported, +} + +impl fmt::Display for SignError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use SignError::*; + + match *self { + IndexOutOfBounds(ref e) => write_err!(f, "index out of bounds"; e), + InvalidSighashType => write!(f, "invalid sighash type"), + MissingInputUtxo => write!(f, "missing input utxo in PBST"), + MissingRedeemScript => write!(f, "missing redeem script"), + MissingSpendUtxo => write!(f, "missing spend utxo in PSBT"), + MissingWitnessScript => write!(f, "missing witness script"), + MismatchedAlgoKey => write!(f, "signing algorithm and key type does not match"), + NotEcdsa => write!(f, "attempted to ECDSA sign an non-ECDSA input"), + NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"), + SighashComputation(ref e) => write!(f, "sighash: {}", e), + UnknownOutputType => write!(f, "unable to determine the output type"), + KeyNotFound => write!(f, "unable to find key"), + WrongSigningAlgorithm => + write!(f, "attempt to sign an input with the wrong signing algorithm"), + Unsupported => write!(f, "signing request currently unsupported"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SignError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use SignError::*; + + match *self { + SighashComputation(ref e) => Some(e), + IndexOutOfBounds(ref e) => Some(e), + InvalidSighashType + | MissingInputUtxo + | MissingRedeemScript + | MissingSpendUtxo + | MissingWitnessScript + | MismatchedAlgoKey + | NotEcdsa + | NotWpkh + | UnknownOutputType + | KeyNotFound + | WrongSigningAlgorithm + | Unsupported => None, + } + } +} + +impl From for SignError { + fn from(e: sighash::Error) -> Self { SignError::SighashComputation(e) } +} + +impl From for SignError { + fn from(e: IndexOutOfBoundsError) -> Self { SignError::IndexOutOfBounds(e) } +} + +/// Error when passing an un-modifiable PSBT to a `Constructor`. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum PsbtNotModifiableError { + /// The outputs modifiable flag is not set. + Outputs(OutputsNotModifiableError), + /// The inputs modifiable flag is not set. + Inputs(InputsNotModifiableError), +} + +impl fmt::Display for PsbtNotModifiableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use PsbtNotModifiableError::*; + + match *self { + Outputs(ref e) => write_err!(f, "outputs not modifiable"; e), + Inputs(ref e) => write_err!(f, "inputs not modifiable"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for PsbtNotModifiableError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use PsbtNotModifiableError::*; + + match *self { + Outputs(ref e) => Some(e), + Inputs(ref e) => Some(e), + } + } +} + +impl From for PsbtNotModifiableError { + fn from(e: InputsNotModifiableError) -> Self { Self::Inputs(e) } +} + +impl From for PsbtNotModifiableError { + fn from(e: OutputsNotModifiableError) -> Self { Self::Outputs(e) } +} + +/// Error when passing an PSBT with inputs not modifiable to an input adding `Constructor`. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct InputsNotModifiableError; + +impl fmt::Display for InputsNotModifiableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("PSBT does not have the inputs modifiable flag set") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InputsNotModifiableError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// Error when passing an PSBT with outputs not modifiable to an output adding `Constructor`. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct OutputsNotModifiableError; + +impl fmt::Display for OutputsNotModifiableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("PSBT does not have the outputs modifiable flag set") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for OutputsNotModifiableError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// Cannot build an unsigned transaction from PSBT. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum UnsignedError { + /// Cannot determine lock time. + DetermineLockTime(DetermineLockTimeError), + /// PSBT is not unsigned. + NotUnsigned(NotUnsignedError), +} + +impl fmt::Display for UnsignedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use UnsignedError::*; + + match *self { + DetermineLockTime(ref e) => write_err!(f, "cannot determine lock time"; e), + NotUnsigned(ref e) => write_err!(f, "PSBT is not unsigned"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for UnsignedError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use UnsignedError::*; + + match *self { + DetermineLockTime(ref e) => Some(e), + NotUnsigned(ref e) => Some(e), + } + } +} + +impl From for UnsignedError { + fn from(e: NotUnsignedError) -> Self { Self::NotUnsigned(e) } +} + +impl From for UnsignedError { + fn from(e: DetermineLockTimeError) -> Self { Self::DetermineLockTime(e) } +} + +/// The input is not 100% unsigned. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum NotUnsignedError { + /// Input has already been finalized. + Finalized, + /// Input already has signature data. + SigData, +} + +impl fmt::Display for NotUnsignedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use NotUnsignedError::*; + + match *self { + Finalized => f.write_str("input has already been finalized"), + SigData => f.write_str("input already has signature data"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for NotUnsignedError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// Unable to determine lock time, multiple inputs have conflicting locking requirements. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct DetermineLockTimeError; + +impl fmt::Display for DetermineLockTimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str( + "unable to determine lock time, multiple inputs have conflicting locking requirements", + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DetermineLockTimeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} diff --git a/src/v2/extractor.rs b/src/v2/extractor.rs new file mode 100644 index 0000000..f828e41 --- /dev/null +++ b/src/v2/extractor.rs @@ -0,0 +1,96 @@ +// 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::v2::{ExtractTxError, Psbt}; +use crate::Error; + +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 { + let fee = match self.fee() { + Ok(fee) => fee, + Err(Error::MissingUtxo) => + return Err(ExtractTxError::MissingInputValue { tx: self.internal_extract_tx() }), + Err(Error::NegativeFee) => return Err(ExtractTxError::SendingTooMuch { psbt: self }), + Err(Error::FeeOverflow) => + return Err(ExtractTxError::AbsurdFeeRate { + fee_rate: FeeRate::MAX, + tx: self.internal_extract_tx(), + }), + _ => unreachable!(), + }; + + // 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/global.rs b/src/v2/map/global.rs new file mode 100644 index 0000000..60fe948 --- /dev/null +++ b/src/v2/map/global.rs @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: CC0-1.0 + +#![allow(unused)] + +use core::convert::TryFrom; +use core::{cmp, fmt}; + +use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, KeySource, Xpub}; +use bitcoin::consensus::encode::MAX_VEC_SIZE; +use bitcoin::consensus::{self, Decodable}; +use bitcoin::locktime::absolute; +use bitcoin::{bip32, transaction, Transaction, VarInt}; + +use crate::consts::{ + PSBT_GLOBAL_FALLBACK_LOCKTIME, PSBT_GLOBAL_INPUT_COUNT, PSBT_GLOBAL_OUTPUT_COUNT, + PSBT_GLOBAL_PROPRIETARY, PSBT_GLOBAL_TX_MODIFIABLE, PSBT_GLOBAL_TX_VERSION, + PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_VERSION, PSBT_GLOBAL_XPUB, +}; +use crate::error::write_err; +use crate::io::{self, Cursor, Read}; +use crate::prelude::*; +use crate::serialize::{Deserialize, Serialize}; +use crate::v2::map::Map; +use crate::version::Version; +use crate::{raw, Error, V2}; + +/// The Inputs Modifiable Flag, set to 1 to indicate whether inputs can be added or removed. +const INPUTS_MODIFIABLE: u8 = 0x01 << 0; +/// The Outputs Modifiable Flag, set to 1 to indicate whether outputs can be added or removed. +const OUTPUTS_MODIFIABLE: u8 = 0x01 << 1; +/// The Has SIGHASH_SINGLE flag, set to 1 to indicate whether the transaction has a SIGHASH_SINGLE +/// signature who's input and output pairing must be preserved. Essentially indicates that the +/// Constructor must iterate the inputs to determine whether and how to add or remove an input. +const SIGHASH_SINGLE: u8 = 0x01 << 2; + +/// The global key-value map. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Global { + /// The version number of this PSBT. + pub version: Version, + + /// The version number of the transaction being built. + pub tx_version: transaction::Version, + + /// The transaction locktime to use if no inputs specify a required locktime. + pub fallback_lock_time: Option, + + /// A bitfield for various transaction modification flags. + pub tx_modifiable_flags: u8, + + /// The number of inputs in this PSBT. + pub input_count: usize, // Serialized in compact form as a u64 (VarInt). + + /// The number of outputs in this PSBT. + pub output_count: usize, // Serialized in compact form as a u64 (VarInt). + + /// A map from xpub to the used key fingerprint and derivation path as defined by BIP 32. + pub xpubs: BTreeMap, + + /// Global proprietary key-value pairs. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] + pub proprietary: BTreeMap>, + + /// Unknown global key-value pairs. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] + pub unknowns: BTreeMap>, +} + +impl Global { + fn new() -> Self { + Global { + version: V2, + tx_version: transaction::Version::TWO, + fallback_lock_time: None, + tx_modifiable_flags: 0x00, + input_count: 0, + output_count: 0, + xpubs: Default::default(), + proprietary: Default::default(), + unknowns: Default::default(), + } + } + + pub(crate) fn set_inputs_modifiable_flag(&mut self) { + self.tx_modifiable_flags |= INPUTS_MODIFIABLE; + } + + pub(crate) fn set_outputs_modifiable_flag(&mut self) { + self.tx_modifiable_flags |= OUTPUTS_MODIFIABLE; + } + + pub(crate) fn set_sighash_single_flag(&mut self) { self.tx_modifiable_flags |= SIGHASH_SINGLE; } + + pub(crate) fn clear_inputs_modifiable_flag(&mut self) { + self.tx_modifiable_flags &= !INPUTS_MODIFIABLE; + } + + pub(crate) fn clear_outputs_modifiable_flag(&mut self) { + self.tx_modifiable_flags &= !OUTPUTS_MODIFIABLE; + } + + pub(crate) fn clear_sighash_single_flag(&mut self) { + self.tx_modifiable_flags &= !SIGHASH_SINGLE; + } + + pub(crate) fn is_inputs_modifiable(&self) -> bool { + self.tx_modifiable_flags & INPUTS_MODIFIABLE > 0 + } + + pub(crate) fn is_outputs_modifiable(&self) -> bool { + self.tx_modifiable_flags & OUTPUTS_MODIFIABLE > 0 + } + + pub(crate) fn has_sighash_single(&self) -> bool { + self.tx_modifiable_flags & SIGHASH_SINGLE > 0 + } + + pub(crate) fn decode(r: &mut R) -> Result { + let mut r = r.take(MAX_VEC_SIZE as u64); + let mut version: Option = None; + let mut tx_version: Option = None; + let mut fallback_lock_time: Option = None; + let mut tx_modifiable_flags: Option = None; + let mut input_count: Option = None; + let mut output_count: Option = None; + let mut xpubs: BTreeMap = Default::default(); + let mut proprietary: BTreeMap> = Default::default(); + let mut unknowns: BTreeMap> = Default::default(); + + loop { + match raw::Pair::decode(&mut r) { + Ok(pair) => { + match pair.key.type_value { + v if v == PSBT_GLOBAL_VERSION => + if pair.key.key.is_empty() { + if version.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + if vlen != 4 { + return Err(DecodeError::ValueWrongLength(vlen, 4)); + } + let v = Decodable::consensus_decode(&mut decoder)?; + if v != 2 { + return Err(DecodeError::WrongVersion(v)); + } + version = Some(v); + } else { + return Err(DecodeError::DuplicateKey(pair.key)); + } + } else { + return Err(DecodeError::InvalidKey(pair.key)); + }, + v if v == PSBT_GLOBAL_TX_VERSION => + if pair.key.key.is_empty() { + if tx_version.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + if vlen != 4 { + return Err(DecodeError::ValueWrongLength(vlen, 4)); + } + tx_version = Some(Decodable::consensus_decode(&mut decoder)?); + } else { + return Err(DecodeError::DuplicateKey(pair.key)); + } + } else { + return Err(DecodeError::InvalidKey(pair.key)); + }, + v if v == PSBT_GLOBAL_FALLBACK_LOCKTIME => + if pair.key.key.is_empty() { + if fallback_lock_time.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + if vlen != 4 { + return Err(DecodeError::ValueWrongLength(vlen, 4)); + } + fallback_lock_time = + Some(Decodable::consensus_decode(&mut decoder)?); + } else { + return Err(DecodeError::DuplicateKey(pair.key)); + } + } else { + return Err(DecodeError::InvalidKey(pair.key)); + }, + v if v == PSBT_GLOBAL_INPUT_COUNT => + if pair.key.key.is_empty() { + if output_count.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + let count: VarInt = Decodable::consensus_decode(&mut decoder)?; + input_count = Some(count.0); + } else { + return Err(DecodeError::DuplicateKey(pair.key)); + } + } else { + return Err(DecodeError::InvalidKey(pair.key)); + }, + v if v == PSBT_GLOBAL_OUTPUT_COUNT => + if pair.key.key.is_empty() { + if output_count.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + let count: VarInt = Decodable::consensus_decode(&mut decoder)?; + output_count = Some(count.0); + } else { + return Err(DecodeError::DuplicateKey(pair.key)); + } + } else { + return Err(DecodeError::InvalidKey(pair.key)); + }, + v if v == PSBT_GLOBAL_TX_MODIFIABLE => + if pair.key.key.is_empty() { + if tx_modifiable_flags.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + if vlen != 1 { + return Err(DecodeError::ValueWrongLength(vlen, 1)); + } + tx_modifiable_flags = + Some(Decodable::consensus_decode(&mut decoder)?); + } else { + return Err(DecodeError::DuplicateKey(pair.key)); + } + } else { + return Err(DecodeError::InvalidKey(pair.key)); + }, + v if v == PSBT_GLOBAL_XPUB => { + if !pair.key.key.is_empty() { + let xpub = Xpub::decode(&pair.key.key)?; + if pair.value.is_empty() || pair.value.len() % 4 != 0 { + // TODO: Add better error here. + return Err(DecodeError::PathNotMod4(pair.value.len())); + } + + let child_count = pair.value.len() / 4 - 1; + let mut decoder = Cursor::new(pair.value); + let mut fingerprint = [0u8; 4]; + decoder.read_exact(&mut fingerprint[..])?; + let mut path = Vec::::with_capacity(child_count); + while let Ok(index) = u32::consensus_decode(&mut decoder) { + path.push(ChildNumber::from(index)) + } + let derivation = DerivationPath::from(path); + // Keys, according to BIP-174, must be unique + if let Some(key_source) = + xpubs.insert(xpub, (Fingerprint::from(fingerprint), derivation)) + { + return Err(DecodeError::DuplicateXpub(key_source)); + } + } else { + return Err(DecodeError::DuplicateKey(pair.key)); + } + } + v if v == PSBT_GLOBAL_PROPRIETARY => match proprietary + .entry(raw::ProprietaryKey::try_from(pair.key.clone())?) + { + btree_map::Entry::Vacant(empty_key) => { + empty_key.insert(pair.value); + } + btree_map::Entry::Occupied(_) => + return Err(DecodeError::DuplicateKey(pair.key)), + }, + v if v == PSBT_GLOBAL_UNSIGNED_TX => return Err(DecodeError::UnsignedTx), + _ => match unknowns.entry(pair.key) { + btree_map::Entry::Vacant(empty_key) => { + empty_key.insert(pair.value); + } + btree_map::Entry::Occupied(k) => + return Err(DecodeError::DuplicateKey(k.key().clone())), + }, + } + } + Err(crate::Error::NoMorePairs) => break, + Err(e) => return Err(DecodeError::Error(e)), + } + } + let tx_version = tx_version.ok_or(DecodeError::MissingTxVersion)?; + // TODO: Do checks for standard transaction version? + let tx_version = transaction::Version(tx_version); + + let input_count = input_count.ok_or(DecodeError::MissingInputCount)?; + // TODO: Is this a valid assumption, that a valid PSBT cannot have an input count that overflows a usize? + let input_count: usize = + usize::try_from(input_count).map_err(|_| DecodeError::CountOverflow(input_count))?; + + let output_count = output_count.ok_or(DecodeError::MissingOutputCount)?; + // TODO: Same as for input_count. + let output_count: usize = + usize::try_from(output_count).map_err(|_| DecodeError::CountOverflow(output_count))?; + + // TODO: Check this default is correct. + let tx_modifiable_flags = tx_modifiable_flags.unwrap_or(0_u8); + + let version = version.ok_or(DecodeError::MissingVersion)?; + // TODO: Handle decoding either psbt v0 or psbt v2. + if version != 2 { + return Err(DecodeError::WrongVersion(version)); + } + let version = Version::try_from(version).expect("checked above"); + + Ok(Global { + tx_version, + fallback_lock_time, + input_count, + output_count, + tx_modifiable_flags, + version, + xpubs, + proprietary, + unknowns, + }) + } + + /// Combines this [`Psbt`] with `other` PSBT as described by BIP 174. + /// + /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` + pub fn combine(&mut self, other: Self) -> Result<(), Error> { + // BIP 174: The Combiner must remove any duplicate key-value pairs, in accordance with + // the specification. It can pick arbitrarily when conflicts occur. + + // Keeping the highest version + self.version = cmp::max(self.version, other.version); + + // Merging xpubs + for (xpub, (fingerprint1, derivation1)) in other.xpubs { + match self.xpubs.entry(xpub) { + btree_map::Entry::Vacant(entry) => { + entry.insert((fingerprint1, derivation1)); + } + btree_map::Entry::Occupied(mut entry) => { + // Here in case of the conflict we select the version with algorithm: + // 1) if everything is equal we do nothing + // 2) report an error if + // - derivation paths are equal and fingerprints are not + // - derivation paths are of the same length, but not equal + // - derivation paths has different length, but the shorter one + // is not the strict suffix of the longer one + // 3) choose longest derivation otherwise + + let (fingerprint2, derivation2) = entry.get().clone(); + + if (derivation1 == derivation2 && fingerprint1 == fingerprint2) + || (derivation1.len() < derivation2.len() + && derivation1[..] + == derivation2[derivation2.len() - derivation1.len()..]) + { + continue; + } else if derivation2[..] + == derivation1[derivation1.len() - derivation2.len()..] + { + entry.insert((fingerprint1, derivation1)); + continue; + } + return Err(Error::CombineInconsistentKeySources(Box::new(xpub))); + } + } + } + + self.proprietary.extend(other.proprietary); + self.unknowns.extend(other.unknowns); + Ok(()) + } +} + +impl Default for Global { + fn default() -> Self { Self::new() } +} + +impl Map for Global { + fn get_pairs(&self) -> Vec { + let mut rv: Vec = Default::default(); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_GLOBAL_VERSION, key: vec![] }, + value: self.version.serialize(), + }); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_GLOBAL_TX_VERSION, key: vec![] }, + value: self.tx_version.serialize(), + }); + + impl_psbt_get_pair! { + rv.push(self.fallback_lock_time, PSBT_GLOBAL_FALLBACK_LOCKTIME) + } + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_GLOBAL_INPUT_COUNT, key: vec![] }, + value: VarInt::from(self.input_count).serialize(), + }); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_GLOBAL_OUTPUT_COUNT, key: vec![] }, + value: VarInt::from(self.output_count).serialize(), + }); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_GLOBAL_TX_MODIFIABLE, key: vec![] }, + value: vec![self.tx_modifiable_flags], + }); + + for (xpub, (fingerprint, derivation)) in &self.xpubs { + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_GLOBAL_XPUB, key: xpub.encode().to_vec() }, + value: { + let mut ret = Vec::with_capacity(4 + derivation.len() * 4); + ret.extend(fingerprint.as_bytes()); + derivation.into_iter().for_each(|n| ret.extend(&u32::from(*n).to_le_bytes())); + ret + }, + }); + } + + for (key, value) in self.proprietary.iter() { + rv.push(raw::Pair { key: key.to_key(), value: value.clone() }); + } + + for (key, value) in self.unknowns.iter() { + rv.push(raw::Pair { key: key.clone(), value: value.clone() }); + } + + rv + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum DecodeError { + /// I/O error. + Io(io::Error), + /// Error consensus deserializing type. + Consensus(consensus::encode::Error), + /// Serialized PSBT is missing the version number. + MissingVersion, + /// PSBT v2 expects the version to be 2. + WrongVersion(u32), + /// Serialized PSBT is missing the transaction version number. + MissingTxVersion, + /// Value (keyvalue pair) was not the correct length (got, want). + ValueWrongLength(usize, usize), + // TODO: Should we split this up? + /// Failed to decode a BIP-32 type. + Bip32(bip32::Error), + /// xpub derivation path must be a list of 32 byte varints i.e., mod 4. + PathNotMod4(usize), + /// Serialized PSBT is missing the input count. + MissingInputCount, + /// Serialized PSBT is missing the output count. + MissingOutputCount, + /// Known keys must be according to spec. + InvalidKey(raw::Key), + /// Non-proprietary key type found when proprietary key was expected + InvalidProprietaryKey, + /// Keys within key-value map should never be duplicated. + DuplicateKey(raw::Key), + /// Count overflows word size for current architecture. + CountOverflow(u64), + /// xpubs must be unique. + DuplicateXpub(KeySource), + /// PSBT v2 requires exclusion of unsigned transaction. + UnsignedTx, + /// TODO: Remove this crate error + Error(crate::error::Error), +} + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use DecodeError::*; + + match *self { + Io(ref e) => write_err!(f, "I/O error decoding global map"; e), + Consensus(ref e) => write_err!(f, "error consensus deserializing type"; e), + MissingVersion => write!(f, "serialized PSBT is missing the version number"), + WrongVersion(v) => write!(f, "PSBT v2 expects the version to be 2, found: {}", v), + MissingTxVersion => + write!(f, "serialized PSBT is missing the transaction version number"), + ValueWrongLength(got, want) => + write!(f, "value (keyvalue pair) wrong length (got, want) {} {}", got, want), + Bip32(ref e) => write_err!(f, "BIP-32 error"; e), + PathNotMod4(len) => + write!(f, "derivation path should be a list of u32s i.e., modulo 4"), + MissingInputCount => write!(f, "serialized PSBT is missing the input count"), + MissingOutputCount => write!(f, "serialized PSBT is missing the output count"), + InvalidKey(ref key) => write!(f, "invalid key: {}", key), + InvalidProprietaryKey => + write!(f, "non-proprietary key type found when proprietary key was expected"), + DuplicateKey(ref key) => write!(f, "duplicate key: {}", key), + CountOverflow(u64) => write!(f, "count overflows word size for current architecture"), + // TODO: Use tuple instead of KeySource because this is ugly. + DuplicateXpub(ref key_source) => + write!(f, "found duplicate xpub: ({}, {})", key_source.0, key_source.1), + UnsignedTx => write!(f, "PSBT v2 requires exclusion of unsigned transaction"), + Error(ref e) => write!(f, "TODO: Remove this"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecodeError::*; + + match *self { + Io(ref e) => Some(e), + Consensus(ref e) => Some(e), + Bip32(ref e) => Some(e), + Error(ref e) => Some(e), + MissingVersion + | WrongVersion(_) + | MissingTxVersion + | ValueWrongLength(..) + | PathNotMod4(_) + | MissingInputCount + | MissingOutputCount + | InvalidKey(_) + | InvalidProprietaryKey + | DuplicateKey(_) + | CountOverflow(_) + | DuplicateXpub(_) + | UnsignedTx => None, + } + } +} + +impl From for DecodeError { + fn from(e: io::Error) -> Self { Self::Io(e) } +} + +impl From for DecodeError { + fn from(e: consensus::encode::Error) -> Self { Self::Consensus(e) } +} + +impl From for DecodeError { + fn from(e: bip32::Error) -> Self { Self::Bip32(e) } +} + +impl From for DecodeError { + fn from(e: crate::error::Error) -> Self { Self::Error(e) } +} diff --git a/src/v2/map/input.rs b/src/v2/map/input.rs new file mode 100644 index 0000000..fee0cc0 --- /dev/null +++ b/src/v2/map/input.rs @@ -0,0 +1,751 @@ +// SPDX-License-Identifier: CC0-1.0 + +use core::convert::TryFrom; +use core::fmt; + +use bitcoin::bip32::KeySource; +use bitcoin::consensus::encode as consensus; +use bitcoin::hashes::{self, hash160, ripemd160, sha256, sha256d, Hash as _}; +use bitcoin::key::{PublicKey, XOnlyPublicKey}; +use bitcoin::locktime::absolute; +use bitcoin::sighash::{EcdsaSighashType, NonStandardSighashTypeError, TapSighashType}; +use bitcoin::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash}; +use bitcoin::{ + ecdsa, secp256k1, taproot, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, + Witness, +}; + +use crate::consts::{ + PSBT_IN_BIP32_DERIVATION, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, + PSBT_IN_HASH160, PSBT_IN_HASH256, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_OUTPUT_INDEX, + PSBT_IN_PARTIAL_SIG, PSBT_IN_PREVIOUS_TXID, PSBT_IN_PROPRIETARY, PSBT_IN_REDEEM_SCRIPT, + PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, PSBT_IN_REQUIRED_TIME_LOCKTIME, PSBT_IN_RIPEMD160, + PSBT_IN_SEQUENCE, PSBT_IN_SHA256, PSBT_IN_SIGHASH_TYPE, PSBT_IN_TAP_BIP32_DERIVATION, + PSBT_IN_TAP_INTERNAL_KEY, PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_LEAF_SCRIPT, + PSBT_IN_TAP_MERKLE_ROOT, PSBT_IN_TAP_SCRIPT_SIG, PSBT_IN_WITNESS_SCRIPT, PSBT_IN_WITNESS_UTXO, +}; +use crate::error::write_err; +use crate::prelude::*; +use crate::serialize::{Deserialize, Serialize}; +use crate::sighash_type::{InvalidSighashTypeError, PsbtSighashType}; +use crate::v2::error::NotUnsignedError; +use crate::v2::map::Map; +use crate::{error, io, raw, Error}; + +/// A key-value map for an input of the corresponding index in the unsigned +/// transaction. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Input { + /// The txid of the previous transaction whose output at `self.spent_output_index` is being spent. + /// + /// In other words, the output being spent by this `Input` is: + /// + /// `OutPoint { txid: self.previous_txid, vout: self.spent_output_index }` + pub previous_txid: Txid, + + /// The index of the output being spent in the transaction with the txid of `self.previous_txid`. + pub spent_output_index: u32, + + /// The sequence number of this input. + /// + /// If omitted, assumed to be the final sequence number ([`Sequence::MAX`]). + pub sequence: Option, + + /// The minimum Unix timestamp that this input requires to be set as the transaction's lock time. + pub min_time: Option, + + /// The minimum block height that this input requires to be set as the transaction's lock time. + pub min_height: Option, + + /// The non-witness transaction this input spends from. Should only be + /// `Option::Some` for inputs which spend non-segwit outputs or + /// if it is unknown whether an input spends a segwit output. + pub non_witness_utxo: Option, + /// The transaction output this input spends from. Should only be + /// `Option::Some` for inputs which spend segwit outputs, + /// including P2SH embedded ones. + pub witness_utxo: Option, + /// A map from public keys to their corresponding signature as would be + /// pushed to the stack from a scriptSig or witness for a non-taproot inputs. + pub partial_sigs: BTreeMap, + /// The sighash type to be used for this input. Signatures for this input + /// must use the sighash type. + pub sighash_type: Option, + /// The redeem script for this input. + pub redeem_script: Option, + /// The witness script for this input. + pub witness_script: Option, + /// A map from public keys needed to sign this input to their corresponding + /// master key fingerprints and derivation paths. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] + pub bip32_derivation: BTreeMap, + /// The finalized, fully-constructed scriptSig with signatures and any other + /// scripts necessary for this input to pass validation. + pub final_script_sig: Option, + /// The finalized, fully-constructed scriptWitness with signatures and any + /// other scripts necessary for this input to pass validation. + pub final_script_witness: Option, + /// TODO: Proof of reserves commitment + /// RIPEMD160 hash to preimage map. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] + pub ripemd160_preimages: BTreeMap>, + /// SHA256 hash to preimage map. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] + pub sha256_preimages: BTreeMap>, + /// HSAH160 hash to preimage map. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] + pub hash160_preimages: BTreeMap>, + /// HAS256 hash to preimage map. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] + pub hash256_preimages: BTreeMap>, + /// Serialized taproot signature with sighash type for key spend. + pub tap_key_sig: Option, + /// Map of `|` with signature. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] + pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), taproot::Signature>, + /// Map of Control blocks to Script version pair. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] + pub tap_scripts: BTreeMap, + /// Map of tap root x only keys to origin info and leaf hashes contained in it. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] + pub tap_key_origins: BTreeMap, KeySource)>, + /// Taproot Internal key. + pub tap_internal_key: Option, + /// Taproot Merkle root. + pub tap_merkle_root: Option, + /// Proprietary key-value pairs for this input. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] + pub proprietary: BTreeMap>, + /// Unknown key-value pairs for this input. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] + pub unknown: BTreeMap>, +} + +impl Input { + /// Creates a new `Input` that spends the `previous_output`. + pub fn new(previous_output: OutPoint) -> Self { + Input { + previous_txid: previous_output.txid, + spent_output_index: previous_output.vout, + sequence: None, + min_time: None, + min_height: None, + non_witness_utxo: None, + witness_utxo: None, + partial_sigs: BTreeMap::new(), + sighash_type: None, + redeem_script: None, + witness_script: None, + bip32_derivation: BTreeMap::new(), + final_script_sig: None, + final_script_witness: None, + 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, + proprietary: BTreeMap::new(), + unknown: BTreeMap::new(), + } + } + + pub(crate) fn has_lock_time(&self) -> bool { + self.min_time.is_some() || self.min_height.is_some() + } + + pub(crate) fn is_satisfied_with_height_based_lock_time(&self) -> bool { + self.requires_height_based_lock_time() + || self.min_time.is_some() && self.min_height.is_some() + || self.min_time.is_none() && self.min_height.is_none() + } + + pub(crate) fn requires_time_based_lock_time(&self) -> bool { + self.min_time.is_some() && self.min_height.is_none() + } + + pub(crate) fn requires_height_based_lock_time(&self) -> bool { + self.min_height.is_some() && self.min_time.is_none() + } + + /// Returns a [`TxIn`] that can be used when calculating a PSBT's unique identifier. + pub fn id_tx_in(&self) -> TxIn { + TxIn { + previous_output: self.out_point(), + script_sig: ScriptBuf::default(), + sequence: Sequence::ZERO, + witness: Witness::default(), + } + } + + /// Returns the [`TxIn`] ready for signing. + pub fn unsigned_tx_in(&self) -> Result { + if self.is_finalized() { + return Err(NotUnsignedError::Finalized); + } + + if self.has_sig_data() { + return Err(NotUnsignedError::SigData); + } + + Ok(TxIn { + previous_output: self.out_point(), + script_sig: ScriptBuf::default(), + sequence: self.sequence.unwrap_or(Sequence::ZERO), + witness: Witness::default(), + }) + } + + fn is_finalized(&self) -> bool { + // TODO: Confirm this covers taproot sigs? + self.final_script_sig.is_some() || self.final_script_witness.is_some() + } + + fn has_sig_data(&self) -> bool { + !(self.partial_sigs.is_empty() + && self.tap_key_sig.is_none() + && self.tap_script_sigs.is_empty()) + } + + fn out_point(&self) -> OutPoint { + OutPoint { txid: self.previous_txid, vout: self.spent_output_index } + } + + /// Obtains the [`EcdsaSighashType`] for this input if one is specified. If no sighash type is + /// specified, returns [`EcdsaSighashType::All`]. + /// + /// # Errors + /// + /// If the `sighash_type` field is set to a non-standard ECDSA sighash value. + pub fn ecdsa_hash_ty(&self) -> Result { + self.sighash_type + .map(|sighash_type| sighash_type.ecdsa_hash_ty()) + .unwrap_or(Ok(EcdsaSighashType::All)) + } + + /// Obtains the [`TapSighashType`] for this input if one is specified. If no sighash type is + /// specified, returns [`TapSighashType::Default`]. + /// + /// # Errors + /// + /// If the `sighash_type` field is set to a invalid Taproot sighash value. + pub fn taproot_hash_ty(&self) -> Result { + self.sighash_type + .map(|sighash_type| sighash_type.taproot_hash_ty()) + .unwrap_or(Ok(TapSighashType::Default)) + } + + pub(crate) fn decode(r: &mut R) -> Result { + let invalid = OutPoint { txid: Txid::all_zeros(), vout: u32::MAX }; + let mut rv = Self::new(invalid); + + loop { + match raw::Pair::decode(r) { + Ok(pair) => rv.insert_pair(pair)?, + Err(crate::Error::NoMorePairs) => { + if rv.previous_txid == Txid::all_zeros() { + return Err(DecodeError::MissingPreviousTxid); + } + + if rv.spent_output_index == u32::MAX { + return Err(DecodeError::MissingSpentOutputIndex); + } + return Ok(rv); + } + Err(e) => return Err(DecodeError::Crate(e)), + } + } + } + + pub(super) fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), Error> { + let raw::Pair { key: raw_key, value: raw_value } = pair; + + match raw_key.type_value { + v if v == PSBT_IN_PREVIOUS_TXID => { + if self.previous_txid != Txid::all_zeros() { + return Err(Error::DuplicateKey(raw_key)); + } + let txid: Txid = Deserialize::deserialize(&raw_value)?; + self.previous_txid = txid; + } + v if v == PSBT_IN_OUTPUT_INDEX => { + if self.spent_output_index != u32::MAX { + return Err(Error::DuplicateKey(raw_key)); + } + let vout: u32 = Deserialize::deserialize(&raw_value)?; + self.spent_output_index = vout; + } + v if v == PSBT_IN_SEQUENCE => { + impl_psbt_insert_pair! { + self.sequence <= | + } + } + v if v == PSBT_IN_REQUIRED_TIME_LOCKTIME => { + impl_psbt_insert_pair! { + self.min_time <= | + } + } + v if v == PSBT_IN_REQUIRED_HEIGHT_LOCKTIME => { + impl_psbt_insert_pair! { + self.min_height <= | + } + } + v if v == PSBT_IN_WITNESS_UTXO => { + impl_psbt_insert_pair! { + self.witness_utxo <= | + } + } + v if v == PSBT_IN_PARTIAL_SIG => { + impl_psbt_insert_pair! { + self.partial_sigs <= | + } + } + v if v == PSBT_IN_SIGHASH_TYPE => { + impl_psbt_insert_pair! { + self.sighash_type <= | + } + } + v if v == PSBT_IN_REDEEM_SCRIPT => { + impl_psbt_insert_pair! { + self.redeem_script <= | + } + } + v if v == PSBT_IN_WITNESS_SCRIPT => { + impl_psbt_insert_pair! { + self.witness_script <= | + } + } + v if v == PSBT_IN_BIP32_DERIVATION => { + impl_psbt_insert_pair! { + self.bip32_derivation <= | + } + } + v if v == PSBT_IN_FINAL_SCRIPTSIG => { + impl_psbt_insert_pair! { + self.final_script_sig <= | + } + } + v if v == PSBT_IN_FINAL_SCRIPTWITNESS => { + impl_psbt_insert_pair! { + self.final_script_witness <= | + } + } + v if v == PSBT_IN_RIPEMD160 => { + psbt_insert_hash_pair( + &mut self.ripemd160_preimages, + raw_key, + raw_value, + error::PsbtHash::Ripemd, + )?; + } + v if v == PSBT_IN_SHA256 => { + psbt_insert_hash_pair( + &mut self.sha256_preimages, + raw_key, + raw_value, + error::PsbtHash::Sha256, + )?; + } + v if v == PSBT_IN_HASH160 => { + psbt_insert_hash_pair( + &mut self.hash160_preimages, + raw_key, + raw_value, + error::PsbtHash::Hash160, + )?; + } + v if v == PSBT_IN_HASH256 => { + psbt_insert_hash_pair( + &mut self.hash256_preimages, + raw_key, + raw_value, + error::PsbtHash::Hash256, + )?; + } + v if v == PSBT_IN_TAP_KEY_SIG => { + impl_psbt_insert_pair! { + self.tap_key_sig <= | + } + } + v if v == PSBT_IN_TAP_SCRIPT_SIG => { + impl_psbt_insert_pair! { + self.tap_script_sigs <= | + } + } + v if v == PSBT_IN_TAP_LEAF_SCRIPT => { + impl_psbt_insert_pair! { + self.tap_scripts <= |< raw_value: (ScriptBuf, LeafVersion)> + } + } + v if v == PSBT_IN_TAP_BIP32_DERIVATION => { + impl_psbt_insert_pair! { + self.tap_key_origins <= |< raw_value: (Vec, KeySource)> + } + } + v if v == PSBT_IN_TAP_INTERNAL_KEY => { + impl_psbt_insert_pair! { + self.tap_internal_key <= |< raw_value: XOnlyPublicKey> + } + } + v if v == PSBT_IN_TAP_MERKLE_ROOT => { + impl_psbt_insert_pair! { + self.tap_merkle_root <= |< raw_value: TapNodeHash> + } + } + v if v == PSBT_IN_PROPRIETARY => { + let key = raw::ProprietaryKey::try_from(raw_key.clone())?; + match self.proprietary.entry(key) { + btree_map::Entry::Vacant(empty_key) => { + empty_key.insert(raw_value); + } + btree_map::Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key)), + } + } + _ => match self.unknown.entry(raw_key) { + btree_map::Entry::Vacant(empty_key) => { + empty_key.insert(raw_value); + } + btree_map::Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone())), + }, + } + + Ok(()) + } + + /// Combines this [`Input`] with `other` `Input` (as described by BIP 174). + pub fn combine(&mut self, other: Self) { + combine!(non_witness_utxo, self, other); + + if let (&None, Some(witness_utxo)) = (&self.witness_utxo, other.witness_utxo) { + self.witness_utxo = Some(witness_utxo); + self.non_witness_utxo = None; // Clear out any non-witness UTXO when we set a witness one + } + + self.partial_sigs.extend(other.partial_sigs); + self.bip32_derivation.extend(other.bip32_derivation); + self.ripemd160_preimages.extend(other.ripemd160_preimages); + self.sha256_preimages.extend(other.sha256_preimages); + self.hash160_preimages.extend(other.hash160_preimages); + self.hash256_preimages.extend(other.hash256_preimages); + self.tap_script_sigs.extend(other.tap_script_sigs); + self.tap_scripts.extend(other.tap_scripts); + self.tap_key_origins.extend(other.tap_key_origins); + self.proprietary.extend(other.proprietary); + self.unknown.extend(other.unknown); + + combine!(redeem_script, self, other); + combine!(witness_script, self, other); + combine!(final_script_sig, self, other); + combine!(final_script_witness, self, other); + combine!(tap_key_sig, self, other); + combine!(tap_internal_key, self, other); + combine!(tap_merkle_root, self, other); + } +} + +impl Map for Input { + fn get_pairs(&self) -> Vec { + let mut rv: Vec = Default::default(); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_IN_PREVIOUS_TXID, key: vec![] }, + value: self.previous_txid.serialize(), + }); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_IN_OUTPUT_INDEX, key: vec![] }, + value: self.spent_output_index.serialize(), + }); + + impl_psbt_get_pair! { + rv.push(self.sequence, PSBT_IN_SEQUENCE) + } + impl_psbt_get_pair! { + rv.push(self.min_time, PSBT_IN_REQUIRED_TIME_LOCKTIME) + } + impl_psbt_get_pair! { + rv.push(self.min_height, PSBT_IN_REQUIRED_HEIGHT_LOCKTIME) + } + + impl_psbt_get_pair! { + rv.push(self.non_witness_utxo, PSBT_IN_NON_WITNESS_UTXO) + } + + impl_psbt_get_pair! { + rv.push(self.witness_utxo, PSBT_IN_WITNESS_UTXO) + } + + impl_psbt_get_pair! { + rv.push_map(self.partial_sigs, PSBT_IN_PARTIAL_SIG) + } + + impl_psbt_get_pair! { + rv.push(self.sighash_type, PSBT_IN_SIGHASH_TYPE) + } + + impl_psbt_get_pair! { + rv.push(self.redeem_script, PSBT_IN_REDEEM_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push(self.witness_script, PSBT_IN_WITNESS_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push_map(self.bip32_derivation, PSBT_IN_BIP32_DERIVATION) + } + + impl_psbt_get_pair! { + rv.push(self.final_script_sig, PSBT_IN_FINAL_SCRIPTSIG) + } + + impl_psbt_get_pair! { + rv.push(self.final_script_witness, PSBT_IN_FINAL_SCRIPTWITNESS) + } + + impl_psbt_get_pair! { + rv.push_map(self.ripemd160_preimages, PSBT_IN_RIPEMD160) + } + + impl_psbt_get_pair! { + rv.push_map(self.sha256_preimages, PSBT_IN_SHA256) + } + + impl_psbt_get_pair! { + rv.push_map(self.hash160_preimages, PSBT_IN_HASH160) + } + + impl_psbt_get_pair! { + rv.push_map(self.hash256_preimages, PSBT_IN_HASH256) + } + + impl_psbt_get_pair! { + rv.push(self.tap_key_sig, PSBT_IN_TAP_KEY_SIG) + } + + impl_psbt_get_pair! { + rv.push_map(self.tap_script_sigs, PSBT_IN_TAP_SCRIPT_SIG) + } + + impl_psbt_get_pair! { + rv.push_map(self.tap_scripts, PSBT_IN_TAP_LEAF_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push_map(self.tap_key_origins, PSBT_IN_TAP_BIP32_DERIVATION) + } + + impl_psbt_get_pair! { + rv.push(self.tap_internal_key, PSBT_IN_TAP_INTERNAL_KEY) + } + + impl_psbt_get_pair! { + rv.push(self.tap_merkle_root, PSBT_IN_TAP_MERKLE_ROOT) + } + for (key, value) in self.proprietary.iter() { + rv.push(raw::Pair { key: key.to_key(), value: value.clone() }); + } + + for (key, value) in self.unknown.iter() { + rv.push(raw::Pair { key: key.clone(), value: value.clone() }); + } + + rv + } +} + +fn psbt_insert_hash_pair( + map: &mut BTreeMap>, + raw_key: raw::Key, + raw_value: Vec, + hash_type: error::PsbtHash, +) -> Result<(), Error> +where + H: hashes::Hash + Deserialize, +{ + if raw_key.key.is_empty() { + return Err(crate::Error::InvalidKey(raw_key)); + } + let key_val: H = Deserialize::deserialize(&raw_key.key)?; + match map.entry(key_val) { + btree_map::Entry::Vacant(empty_key) => { + let val: Vec = Deserialize::deserialize(&raw_value)?; + if ::hash(&val) != key_val { + return Err(crate::Error::InvalidPreimageHashPair { + preimage: val.into_boxed_slice(), + hash: Box::from(key_val.borrow()), + hash_type, + }); + } + empty_key.insert(val); + Ok(()) + } + btree_map::Entry::Occupied(_) => Err(crate::Error::DuplicateKey(raw_key)), + } +} + +/// Enables building an [`Input`] using the standard builder pattern. +pub struct InputBuilder(Input); + +impl InputBuilder { + /// Creates a new builder that can be used to build an [`Input`] that spends `previous_output`. + pub fn new(previous_output: OutPoint) -> Self { Self(Input::new(previous_output)) } + + /// Sets the [`Input::min_time`] field. + pub fn minimum_required_time_based_lock_time(mut self, lock: absolute::Time) -> Self { + self.0.min_time = Some(lock); + self + } + + /// Sets the [`Input::min_height`] field. + pub fn minimum_required_height_based_lock_time(mut self, lock: absolute::Height) -> Self { + self.0.min_height = Some(lock); + self + } + + /// Builds the [`Input`]. + pub fn build(self) -> Input { self.0 } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum DecodeError { + /// Error consensus deserializing type. + Consensus(consensus::Error), + /// Known keys must be according to spec. + InvalidKey(raw::Key), + /// Non-proprietary key type found when proprietary key was expected + InvalidProprietaryKey, + /// Keys within key-value map should never be duplicated. + DuplicateKey(raw::Key), + /// Input must contain a previous txid. + MissingPreviousTxid, + /// Input must contain a spent output index. + MissingSpentOutputIndex, + /// TODO: Remove this variant, its a kludge + Crate(crate::error::Error), +} + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use DecodeError::*; + + match *self { + Consensus(ref e) => write_err!(f, "error consensus deserializing type"; e), + InvalidKey(ref key) => write!(f, "invalid key: {}", key), + InvalidProprietaryKey => + write!(f, "non-proprietary key type found when proprietary key was expected"), + DuplicateKey(ref key) => write!(f, "duplicate key: {}", key), + MissingPreviousTxid => write!(f, "input must contain a previous txid"), + MissingSpentOutputIndex => write!(f, "input must contain a spent output index"), + Crate(ref e) => write_err!(f, "kludge"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecodeError::*; + + match *self { + Consensus(ref e) => Some(e), + Crate(ref e) => Some(e), + InvalidKey(_) + | InvalidProprietaryKey + | DuplicateKey(_) + | MissingPreviousTxid + | MissingSpentOutputIndex => None, + } + } +} + +impl From for DecodeError { + fn from(e: consensus::Error) -> Self { Self::Consensus(e) } +} + +// TODO: Remove this. +impl From for DecodeError { + fn from(e: crate::error::Error) -> Self { Self::Crate(e) } +} + +#[cfg(test)] +mod test { + use core::str::FromStr; + + use super::*; + + #[test] + fn psbt_sighash_type_ecdsa() { + for ecdsa in &[ + EcdsaSighashType::All, + EcdsaSighashType::None, + EcdsaSighashType::Single, + EcdsaSighashType::AllPlusAnyoneCanPay, + EcdsaSighashType::NonePlusAnyoneCanPay, + EcdsaSighashType::SinglePlusAnyoneCanPay, + ] { + let sighash = PsbtSighashType::from(*ecdsa); + let s = format!("{}", sighash); + let back = PsbtSighashType::from_str(&s).unwrap(); + assert_eq!(back, sighash); + assert_eq!(back.ecdsa_hash_ty().unwrap(), *ecdsa); + } + } + + #[test] + fn psbt_sighash_type_taproot() { + for tap in &[ + TapSighashType::Default, + TapSighashType::All, + TapSighashType::None, + TapSighashType::Single, + TapSighashType::AllPlusAnyoneCanPay, + TapSighashType::NonePlusAnyoneCanPay, + TapSighashType::SinglePlusAnyoneCanPay, + ] { + let sighash = PsbtSighashType::from(*tap); + let s = format!("{}", sighash); + let back = PsbtSighashType::from_str(&s).unwrap(); + assert_eq!(back, sighash); + assert_eq!(back.taproot_hash_ty().unwrap(), *tap); + } + } + + #[test] + fn psbt_sighash_type_notstd() { + let nonstd = 0xdddddddd; + let sighash = PsbtSighashType { inner: nonstd }; + let s = format!("{}", sighash); + let back = PsbtSighashType::from_str(&s).unwrap(); + + assert_eq!(back, sighash); + // TODO: Uncomment this stuff. + // assert_eq!(back.ecdsa_hash_ty(), Err(NonStandardSighashTypeError(nonstd))); + // assert_eq!(back.taproot_hash_ty(), Err(InvalidSighashTypeError(nonstd))); + } + + fn out_point() -> OutPoint { + let txid = Txid::hash(b"some arbitrary bytes"); + let vout = 0xab; + OutPoint { txid, vout } + } + + #[test] + fn serialize_roundtrip() { + let input = Input::new(out_point()); + + let ser = input.serialize_map(); + let mut d = std::io::Cursor::new(ser); + + let decoded = Input::decode(&mut d).expect("failed to decode"); + + assert_eq!(decoded, input); + } +} diff --git a/src/v2/map/mod.rs b/src/v2/map/mod.rs new file mode 100644 index 0000000..f3cbd31 --- /dev/null +++ b/src/v2/map/mod.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: CC0-1.0 + +// TODO: These are pubic just so we can write global::DecodeError, is that a good choice? +pub mod global; +pub mod input; +pub mod output; + +use crate::prelude::*; +use crate::raw; +use crate::serialize::Serialize; + +#[rustfmt::skip] // Keep pubic re-exports separate +pub use self::{ + input::{Input, InputBuilder}, + output::{Output, OutputBuilder}, + global::Global, +}; + +/// A trait that describes a PSBT key-value map. +pub(crate) trait Map { + /// Attempt to get all key-value pairs. + fn get_pairs(&self) -> Vec; + + /// Serialize Psbt binary map data according to BIP-174 specification. + /// + /// := * 0x00 + /// + /// Why is the separator here 0x00 instead of 0xff? The separator here is used to distinguish between each chunk of data. + /// A separator of 0x00 would mean that the unserializer can read it as a key length of 0, which would never occur with + /// actual keys. It can thus be used as a separator and allow for easier unserializer implementation. + fn serialize_map(&self) -> Vec { + let mut buf = Vec::new(); + for pair in Map::get_pairs(self) { + buf.extend(&pair.serialize()); + } + buf.push(0x00_u8); + buf + } +} diff --git a/src/v2/map/output.rs b/src/v2/map/output.rs new file mode 100644 index 0000000..a3f891e --- /dev/null +++ b/src/v2/map/output.rs @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: CC0-1.0 + +use core::convert::TryFrom; +use core::fmt; + +use bitcoin::bip32::KeySource; +use bitcoin::consensus::encode as consensus; +use bitcoin::key::XOnlyPublicKey; +use bitcoin::taproot::{TapLeafHash, TapTree}; +use bitcoin::{secp256k1, Amount, ScriptBuf, TxOut}; + +use crate::consts::{ + PSBT_OUT_AMOUNT, PSBT_OUT_BIP32_DERIVATION, PSBT_OUT_PROPRIETARY, PSBT_OUT_REDEEM_SCRIPT, + PSBT_OUT_SCRIPT, PSBT_OUT_TAP_BIP32_DERIVATION, PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE, + PSBT_OUT_WITNESS_SCRIPT, +}; +use crate::error::write_err; +use crate::prelude::*; +use crate::serialize::{Deserialize, Serialize}; +use crate::v2::map::Map; +use crate::{io, raw, Error}; + +/// A key-value map for an output of the corresponding index in the unsigned +/// transaction. +#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Output { + /// The output's amount (serialized as satoshis). + pub amount: Amount, + + /// The script for this output, also known as the scriptPubKey. + pub script_pubkey: ScriptBuf, + + /// The redeem script for this output. + pub redeem_script: Option, + /// The witness script for this output. + pub witness_script: Option, + /// A map from public keys needed to spend this output to their + /// corresponding master key fingerprints and derivation paths. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] + pub bip32_derivation: BTreeMap, + /// The internal pubkey. + pub tap_internal_key: Option, + /// Taproot Output tree. + pub tap_tree: Option, + /// Map of tap root x only keys to origin info and leaf hashes contained in it. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] + pub tap_key_origins: BTreeMap, KeySource)>, + /// Proprietary key-value pairs for this output. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] + pub proprietary: BTreeMap>, + /// Unknown key-value pairs for this output. + #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] + pub unknown: BTreeMap>, +} + +impl Output { + /// Creates a new [`Output`] using `utxo`. + pub fn new(utxo: TxOut) -> Self { + Output { + amount: utxo.value, + script_pubkey: utxo.script_pubkey, + redeem_script: None, + witness_script: None, + bip32_derivation: BTreeMap::new(), + tap_internal_key: None, + tap_tree: None, + tap_key_origins: BTreeMap::new(), + proprietary: BTreeMap::new(), + unknown: BTreeMap::new(), + } + } + + /// Creates the [`TxOut`] associated with this `Output`. + pub(crate) fn to_tx_out(&self) -> TxOut { + TxOut { value: self.amount, script_pubkey: self.script_pubkey.clone() } + } + + pub(crate) fn decode(r: &mut R) -> Result { + let invalid = TxOut { value: Amount::ZERO, script_pubkey: ScriptBuf::default() }; + let mut rv = Self::new(invalid); + + loop { + match raw::Pair::decode(r) { + Ok(pair) => rv.insert_pair(pair)?, + Err(crate::Error::NoMorePairs) => { + if rv.amount == Amount::ZERO { + return Err(DecodeError::ZeroValue); + } + + if rv.script_pubkey == ScriptBuf::default() { + return Err(DecodeError::EmptyScriptPubkey); + } + return Ok(rv); + } + Err(e) => return Err(DecodeError::Crate(e)), + } + } + } + + pub(super) fn insert_pair(&mut self, pair: raw::Pair) -> Result<(), Error> { + let raw::Pair { key: raw_key, value: raw_value } = pair; + + match raw_key.type_value { + v if v == PSBT_OUT_AMOUNT => { + if self.amount != Amount::ZERO { + return Err(Error::DuplicateKey(raw_key)); + } + let amount: Amount = Deserialize::deserialize(&raw_value)?; + self.amount = amount; + } + v if v == PSBT_OUT_SCRIPT => { + if self.script_pubkey != ScriptBuf::default() { + return Err(Error::DuplicateKey(raw_key)); + } + let script: ScriptBuf = Deserialize::deserialize(&raw_value)?; + self.script_pubkey = script; + } + + v if v == PSBT_OUT_REDEEM_SCRIPT => { + impl_psbt_insert_pair! { + self.redeem_script <= | + } + } + v if v == PSBT_OUT_WITNESS_SCRIPT => { + impl_psbt_insert_pair! { + self.witness_script <= | + } + } + v if v == PSBT_OUT_BIP32_DERIVATION => { + impl_psbt_insert_pair! { + self.bip32_derivation <= | + } + } + v if v == PSBT_OUT_PROPRIETARY => { + let key = raw::ProprietaryKey::try_from(raw_key.clone())?; + match self.proprietary.entry(key) { + btree_map::Entry::Vacant(empty_key) => { + empty_key.insert(raw_value); + } + btree_map::Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key)), + } + } + v if v == PSBT_OUT_TAP_INTERNAL_KEY => { + impl_psbt_insert_pair! { + self.tap_internal_key <= | + } + } + v if v == PSBT_OUT_TAP_TREE => { + impl_psbt_insert_pair! { + self.tap_tree <= | + } + } + v if v == PSBT_OUT_TAP_BIP32_DERIVATION => { + impl_psbt_insert_pair! { + self.tap_key_origins <= |< raw_value: (Vec, KeySource)> + } + } + _ => match self.unknown.entry(raw_key) { + btree_map::Entry::Vacant(empty_key) => { + empty_key.insert(raw_value); + } + btree_map::Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone())), + }, + } + + Ok(()) + } + + /// Combines this [`Output`] with `other` `Output` (as described by BIP 174). + pub fn combine(&mut self, other: Self) { + self.bip32_derivation.extend(other.bip32_derivation); + self.proprietary.extend(other.proprietary); + self.unknown.extend(other.unknown); + self.tap_key_origins.extend(other.tap_key_origins); + + combine!(redeem_script, self, other); + combine!(witness_script, self, other); + combine!(tap_internal_key, self, other); + combine!(tap_tree, self, other); + } +} + +impl Map for Output { + fn get_pairs(&self) -> Vec { + let mut rv: Vec = Default::default(); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_OUT_AMOUNT, key: vec![] }, + value: self.amount.serialize(), + }); + + rv.push(raw::Pair { + key: raw::Key { type_value: PSBT_OUT_SCRIPT, key: vec![] }, + value: self.script_pubkey.serialize(), + }); + + impl_psbt_get_pair! { + rv.push(self.redeem_script, PSBT_OUT_REDEEM_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push(self.witness_script, PSBT_OUT_WITNESS_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push_map(self.bip32_derivation, PSBT_OUT_BIP32_DERIVATION) + } + + impl_psbt_get_pair! { + rv.push(self.tap_internal_key, PSBT_OUT_TAP_INTERNAL_KEY) + } + + impl_psbt_get_pair! { + rv.push(self.tap_tree, PSBT_OUT_TAP_TREE) + } + + impl_psbt_get_pair! { + rv.push_map(self.tap_key_origins, PSBT_OUT_TAP_BIP32_DERIVATION) + } + + for (key, value) in self.proprietary.iter() { + rv.push(raw::Pair { key: key.to_key(), value: value.clone() }); + } + + for (key, value) in self.unknown.iter() { + rv.push(raw::Pair { key: key.clone(), value: value.clone() }); + } + + rv + } +} + +/// Enables building an [`Output`] using the standard builder pattern. +// This is only provided for uniformity with the `InputBuilder`. +pub struct OutputBuilder(Output); + +impl OutputBuilder { + /// Creates a new builder that can be used to build an [`Output`] around `utxo`. + pub fn new(utxo: TxOut) -> Self { OutputBuilder(Output::new(utxo)) } + + /// Build the [`Output`]. + pub fn build(self) -> Output { self.0 } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum DecodeError { + /// Error consensus deserializing type. + Consensus(consensus::Error), + /// Known keys must be according to spec. + InvalidKey(raw::Key), + /// Non-proprietary key type found when proprietary key was expected + InvalidProprietaryKey, + /// Keys within key-value map should never be duplicated. + DuplicateKey(raw::Key), + /// Output has zero value. + ZeroValue, + /// Output has an empty script pubkey. + EmptyScriptPubkey, + /// TODO: Remove this variant, its a kludge + Crate(crate::error::Error), +} + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use DecodeError::*; + + match *self { + Consensus(ref e) => write_err!(f, "error consensus deserializing type"; e), + InvalidKey(ref key) => write!(f, "invalid key: {}", key), + InvalidProprietaryKey => + write!(f, "non-proprietary key type found when proprietary key was expected"), + DuplicateKey(ref key) => write!(f, "duplicate key: {}", key), + ZeroValue => write!(f, "output has zero value"), + EmptyScriptPubkey => write!(f, "output has an empty script pubkey"), + Crate(ref e) => write_err!(f, "kludge"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecodeError::*; + + match *self { + Consensus(ref e) => Some(e), + Crate(ref e) => Some(e), + InvalidKey(_) + | InvalidProprietaryKey + | DuplicateKey(_) + | ZeroValue + | EmptyScriptPubkey => None, + } + } +} + +impl From for DecodeError { + fn from(e: consensus::Error) -> Self { Self::Consensus(e) } +} + +// TODO: Remove this. +impl From for DecodeError { + fn from(e: crate::error::Error) -> Self { Self::Crate(e) } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tx_out() -> TxOut { + // Arbitrary script, may not even be a valid scriptPubkey. + let script = ScriptBuf::from_hex("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac") + .expect("failed to parse script form hex"); + let value = Amount::from_sat(123_456_789); + TxOut { value, script_pubkey: script } + } + + #[test] + fn serialize_roundtrip() { + let output = Output::new(tx_out()); + println!("out: {:?}", output); + + let ser = output.serialize_map(); + let mut d = std::io::Cursor::new(ser); + + let decoded = Output::decode(&mut d).expect("failed to decode"); + + assert_eq!(decoded, output); + } +} diff --git a/src/v2/miniscript/mod.rs b/src/v2/miniscript/mod.rs new file mode 100644 index 0000000..ad5999c --- /dev/null +++ b/src/v2/miniscript/mod.rs @@ -0,0 +1,1467 @@ +// 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, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +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 new file mode 100644 index 0000000..953fb7f --- /dev/null +++ b/src/v2/miniscript/satisfy.rs @@ -0,0 +1,124 @@ +// 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 + } +} diff --git a/src/v2/mod.rs b/src/v2/mod.rs new file mode 100644 index 0000000..fa6b26c --- /dev/null +++ b/src/v2/mod.rs @@ -0,0 +1,1166 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! PSBT Version 2. +//! +//! A second version of the Partially Signed Bitcoin Transaction format implemented by +//! [`crate::v0::Psbt`] and described in [BIP-174]. +//! +//! Allows for inputs and outputs to be added to the PSBT after creation. +//! +//! [BIP-174]: +//! [BIP-370]: + +mod error; +mod extractor; +mod map; + +use core::fmt; +use core::marker::PhantomData; +#[cfg(feature = "std")] +use std::collections::{HashMap, HashSet}; + +use bitcoin::bip32::{self, KeySource, Xpriv}; +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::{ecdsa, transaction, Amount, Sequence, Transaction, TxIn, TxOut, Txid}; + +use crate::error::{write_err, Error}; +use crate::prelude::*; +use crate::v2::map::{global, input, output, Map}; + +#[rustfmt::skip] // Keep public exports separate. +pub use self::{ + error::{IndexOutOfBoundsError, ExtractTxError, SignError, PsbtNotModifiableError, NotUnsignedError, OutputsNotModifiableError, InputsNotModifiableError, DetermineLockTimeError, UnsignedError}, + map::{Input, InputBuilder, Output, OutputBuilder, Global}, +}; +#[cfg(feature = "base64")] +pub use self::display_from_str::PsbtParseError; + +/// Combines these two PSBTs as described by BIP-174 (i.e. combine is the same for BIP-370). +pub fn combine(mut this: Psbt, that: Psbt) -> Result { + this.combine_with(that)?; + Ok(this) +} + +/// Implements the BIP-370 Creator role. +/// +/// The `Creator` type is only directly needed if one of the following holds: +/// +/// - The creator and constructor are separate entities. +/// - You need to set the fallback lock time. +/// - You need to set the sighash single flag. +/// +/// If not use [`Constructor::default()`] to carry out both roles. +/// +/// See `examples/v2-separate-creator-constructor.rs`. +pub struct Creator(Psbt); + +impl Creator { + /// Creates a new PSBT Creator. + pub fn new() -> Self { + let psbt = Psbt { + global: Global::default(), + inputs: Default::default(), + outputs: Default::default(), + }; + Creator(psbt) + } + + /// Sets the fallback lock time. + pub fn fallback_lock_time(mut self, fallback: absolute::LockTime) -> Self { + self.0.global.fallback_lock_time = Some(fallback); + self + } + + /// Sets the "has sighash single" flag in then transaction modifiable flags. + pub fn sighash_single(mut self) -> Self { + self.0.global.set_sighash_single_flag(); + self + } + + /// Sets the inputs modifiable bit in the transaction modifiable flags. + pub fn inputs_modifiable(mut self) -> Self { + self.0.global.set_inputs_modifiable_flag(); + self + } + + /// Sets the outputs modifiable bit in the transaction modifiable flags. + pub fn outputs_modifiable(mut self) -> Self { + self.0.global.set_outputs_modifiable_flag(); + self + } + + /// Sets the transaction version. + /// + /// You likely do not need this, it is provided for completeness. + /// + /// The default is [`transaction::Version::TWO`]. + pub fn transaction_version(mut self, version: transaction::Version) -> Self { + self.0.global.tx_version = version; + self + } + + /// Builds a [`Constructor`] that can add inputs and outputs. + /// + /// # Examples + /// + /// ``` + /// use psbt::v2::{Creator, Constructor, Modifiable}; + /// + /// // Creator role separate from Constructor role. + /// let psbt = Creator::new() + /// .inputs_modifiable() + /// .outputs_modifiable() + /// .psbt(); + /// let _constructor = Constructor::::new(psbt); + /// + /// // However, since a single entity is likely to be both a Creator and Constructor. + /// let _constructor = Creator::new().constructor_modifiable(); + /// + /// // Or the more terse: + /// let _constructor = Constructor::::default(); + /// ``` + pub fn constructor_modifiable(self) -> Constructor { + let mut psbt = self.0; + psbt.global.set_inputs_modifiable_flag(); + psbt.global.set_outputs_modifiable_flag(); + Constructor(psbt, PhantomData) + } + + /// Builds a [`Constructor`] that can only add inputs. + /// + /// # Examples + /// + /// ``` + /// use psbt::v2::{Creator, Constructor, InputsOnlyModifiable}; + /// + /// // Creator role separate from Constructor role. + /// let psbt = Creator::new() + /// .inputs_modifiable() + /// .psbt(); + /// let _constructor = Constructor::::new(psbt); + /// + /// // However, since a single entity is likely to be both a Creator and Constructor. + /// let _constructor = Creator::new().constructor_inputs_only_modifiable(); + /// + /// // Or the more terse: + /// let _constructor = Constructor::::default(); + /// ``` + pub fn constructor_inputs_only_modifiable(self) -> Constructor { + let mut psbt = self.0; + psbt.global.set_inputs_modifiable_flag(); + psbt.global.clear_outputs_modifiable_flag(); + Constructor(psbt, PhantomData) + } + + /// Builds a [`Constructor`] that can only add outputs. + /// + /// # Examples + /// + /// ``` + /// use psbt::v2::{Creator, Constructor, OutputsOnlyModifiable}; + /// + /// // Creator role separate from Constructor role. + /// let psbt = Creator::new() + /// .inputs_modifiable() + /// .psbt(); + /// let _constructor = Constructor::::new(psbt); + /// + /// // However, since a single entity is likely to be both a Creator and Constructor. + /// let _constructor = Creator::new().constructor_outputs_only_modifiable(); + /// + /// // Or the more terse: + /// let _constructor = Constructor::::default(); + /// ``` + pub fn constructor_outputs_only_modifiable(self) -> Constructor { + let mut psbt = self.0; + psbt.global.clear_inputs_modifiable_flag(); + psbt.global.set_outputs_modifiable_flag(); + Constructor(psbt, PhantomData) + } + + /// Returns the created [`Psbt`]. + /// + /// This is only required if the Creator and Constructor are separate entities. If the Creator + /// is also acting as the Constructor use one of the `Self::constructor_foo` functions. + pub fn psbt(self) -> Psbt { self.0 } +} + +impl Default for Creator { + fn default() -> Self { Self::new() } +} + +/// Marker for a `Constructor` with both inputs and outputs modifiable. +pub enum Modifiable {} +/// Marker for a `Constructor` with inputs modifiable. +pub enum InputsOnlyModifiable {} +/// Marker for a `Constructor` with outputs modifiable. +pub enum OutputsOnlyModifiable {} + +mod sealed { + pub trait Mod {} + impl Mod for super::Modifiable {} + impl Mod for super::InputsOnlyModifiable {} + impl Mod for super::OutputsOnlyModifiable {} +} + +/// Marker for if either inputs or outputs are modifiable, or both. +pub trait Mod: sealed::Mod + Sync + Send + Sized + Unpin {} + +impl Mod for Modifiable {} +impl Mod for InputsOnlyModifiable {} +impl Mod for OutputsOnlyModifiable {} + +/// Implements the BIP-370 Constructor role. +/// +/// Uses the builder pattern, and generics to make adding inputs and outputs infallible. +pub struct Constructor(Psbt, PhantomData); + +impl Constructor { + /// Marks that the `Psbt` can not have any more inputs added to it. + pub fn no_more_inputs(mut self) -> Self { + self.0.global.clear_inputs_modifiable_flag(); + self + } + + /// Marks that the `Psbt` can not have any more outputs added to it. + pub fn no_more_outputs(mut self) -> Self { + self.0.global.clear_outputs_modifiable_flag(); + self + } + + /// Returns the [`Psbt`] ready to be passed to the entity acting in the updater role. + pub fn finish(self) -> Psbt { self.no_more_inputs().no_more_outputs().psbt() } + + /// Returns the [`Psbt`] in its current state. + /// + /// This function can be used either to get the [`Psbt`] to pass to another constructor or to + /// get the [`Psbt`] ready for update if `no_more_inputs` and `no_more_outputs` have already + /// explicitly been called. + pub fn psbt(self) -> Psbt { self.0 } +} + +impl Constructor { + /// Creates a new Constructor. + /// + /// This function should only be needed if the PSBT Creator and Constructor roles are being + /// performed by separate entities, if not use one of the builder functions on the [`Creator`] + /// e.g., `constructor_modifiable()`. + pub fn new(psbt: Psbt) -> Result { + if !psbt.global.is_inputs_modifiable() { + Err(InputsNotModifiableError.into()) + } else if !psbt.global.is_outputs_modifiable() { + Err(OutputsNotModifiableError.into()) + } else { + Ok(Self(psbt, PhantomData)) + } + } + + /// Adds an input to the PSBT. + pub fn input(mut self, input: Input) -> Self { + self.0.inputs.push(input); + self.0.global.input_count += 1; + self + } + + /// Adds an output to the PSBT. + pub fn output(mut self, output: Output) -> Self { + self.0.outputs.push(output); + self.0.global.output_count += 1; + self + } +} +// Useful if the Creator and Constructor are a single entity. +impl Default for Constructor { + fn default() -> Self { Creator::new().constructor_modifiable() } +} + +impl Constructor { + /// Creates a new Constructor. + /// + /// This function should only be needed if the PSBT Creator and Constructor roles are being + /// performed by separate entities, if not use one of the builder functions on the [`Creator`] + /// e.g., `constructor_modifiable()`. + pub fn new(psbt: Psbt) -> Result { + if psbt.global.is_inputs_modifiable() { + Ok(Self(psbt, PhantomData)) + } else { + Err(InputsNotModifiableError) + } + } + + /// Adds an input to the PSBT. + pub fn input(mut self, input: Input) -> Self { + self.0.inputs.push(input); + self.0.global.input_count += 1; + self + } +} + +// Useful if the Creator and Constructor are a single entity. +impl Default for Constructor { + fn default() -> Self { Creator::new().constructor_inputs_only_modifiable() } +} + +impl Constructor { + /// Creates a new Constructor. + /// + /// This function should only be needed if the PSBT Creator and Constructor roles are being + /// performed by separate entities, if not use one of the builder functions on the [`Creator`] + /// e.g., `constructor_modifiable()`. + pub fn new(psbt: Psbt) -> Result { + if psbt.global.is_outputs_modifiable() { + Ok(Self(psbt, PhantomData)) + } else { + Err(OutputsNotModifiableError) + } + } + + /// Adds an output to the PSBT. + pub fn output(mut self, output: Output) -> Self { + self.0.outputs.push(output); + self.0.global.output_count += 1; + self + } +} + +// Useful if the Creator and Constructor are a single entity. +impl Default for Constructor { + fn default() -> Self { Creator::new().constructor_outputs_only_modifiable() } +} + +// TODO: Consider adding an iter API that combines the yielded pbsts. + +/// A Partially Signed Transaction. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Psbt { + /// The global map. + pub global: Global, + /// The corresponding key-value map for each input in the unsigned transaction. + pub inputs: Vec, + /// The corresponding key-value map for each output in the unsigned transaction. + pub outputs: Vec, +} + +impl Psbt { + /// Determines the lock time as specified in [BIP-370] if it is possible to do so. + /// + /// [BIP-370]: + pub fn determine_lock_time(&self) -> Result { + let require_time_based_lock_time = + self.inputs.iter().any(|input| input.requires_time_based_lock_time()); + let require_height_based_lock_time = + self.inputs.iter().any(|input| input.requires_height_based_lock_time()); + + if require_time_based_lock_time && require_height_based_lock_time { + return Err(DetermineLockTimeError); + } + + let have_lock_time = self.inputs.iter().any(|input| input.has_lock_time()); + + let lock = if have_lock_time { + let all_inputs_satisfied_with_height_based_lock_time = + self.inputs.iter().all(|input| input.is_satisfied_with_height_based_lock_time()); + + // > The lock time chosen is then the maximum value of the chosen type of lock time. + if all_inputs_satisfied_with_height_based_lock_time { + // We either have only height based or we have both, in which case we must use height based. + let height = self + .inputs + .iter() + .map(|input| input.min_height) + .max() + .expect("we know we have at least one non-none min_height field") + .expect("so we know that max is non-none"); + absolute::LockTime::from(height) + } else { + let time = self + .inputs + .iter() + .map(|input| input.min_time) + .max() + .expect("we know we have at least one non-none min_height field") + .expect("so we know that max is non-none"); + absolute::LockTime::from(time) + } + } else { + // > If none of the inputs have a PSBT_IN_REQUIRED_TIME_LOCKTIME and + // > PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, then PSBT_GLOBAL_FALLBACK_LOCKTIME must be used. + // > If PSBT_GLOBAL_FALLBACK_LOCKTIME is not provided, then it is assumed to be 0. + self.global.fallback_lock_time.unwrap_or(absolute::LockTime::ZERO) + }; + + Ok(lock) + } + + /// Returns this PSBT's unique identification. + /// + /// The identifier calculation includes inputs and outputs. This means that the ID of a PSBT + /// still under construction will differ if inputs or outputs are added to it. + /// + /// This identifier is stable once you have called `Constructor::finish()`. + pub fn id(&self) -> Result { + let tx = Transaction { + version: transaction::Version::TWO, + lock_time: self.determine_lock_time()?, + input: self.inputs.iter().map(|input| input.id_tx_in()).collect(), + output: self.outputs.iter().map(|output| output.to_tx_out()).collect(), + }; + Ok(tx.txid()) + } + + /// Updater role, update the sequence number for input at `index`. + pub fn set_sequence( + mut self, + n: Sequence, + input_index: usize, + ) -> Result { + let input = self.checked_input_mut(input_index)?; + input.sequence = Some(n); + Ok(self) + } + + /// Serialize a value as bytes in hex. + pub fn serialize_hex(&self) -> String { self.serialize().to_lower_hex_string() } + + /// Serialize as raw binary data + pub fn serialize(&self) -> Vec { + let mut buf: Vec = Vec::new(); + + // + buf.extend_from_slice(b"psbt"); + + buf.push(0xff_u8); + + buf.extend(self.global.serialize_map()); + + for i in &self.inputs { + buf.extend(i.serialize_map()); + } + + for i in &self.outputs { + buf.extend(i.serialize_map()); + } + + buf + } + + /// Deserialize a value from raw binary data. + pub fn deserialize(bytes: &[u8]) -> Result { + const MAGIC_BYTES: &[u8] = b"psbt"; + if bytes.get(0..MAGIC_BYTES.len()) != Some(MAGIC_BYTES) { + return Err(DecodeError::InvalidMagic); + } + + const PSBT_SERPARATOR: u8 = 0xff_u8; + if bytes.get(MAGIC_BYTES.len()) != Some(&PSBT_SERPARATOR) { + return Err(DecodeError::InvalidSeparator); + } + + let mut d = bytes.get(5..).ok_or(DecodeError::NoMorePairs)?; + + let global = Global::decode(&mut d)?; + + let inputs: Vec = { + let inputs_len: usize = global.input_count; + let mut inputs: Vec = Vec::with_capacity(inputs_len); + + for _ in 0..inputs_len { + inputs.push(Input::decode(&mut d)?); + } + + inputs + }; + + let outputs: Vec = { + let outputs_len: usize = global.output_count; + let mut outputs: Vec = Vec::with_capacity(outputs_len); + + for _ in 0..outputs_len { + outputs.push(Output::decode(&mut d)?) + } + + outputs + }; + + Ok(Psbt { global, inputs, outputs }) + } + + /// Returns an iterator for the funding UTXOs of the psbt + /// + /// For each PSBT input that contains UTXO information `Ok` is returned containing that information. + /// The order of returned items is same as the order of inputs. + /// + /// ## Errors + /// + /// The function returns error when UTXO information is not present or is invalid. + pub fn iter_funding_utxos(&self) -> impl Iterator> { + self.inputs.iter().map(|input| match (&input.witness_utxo, &input.non_witness_utxo) { + (Some(witness_utxo), _) => Ok(witness_utxo), + (None, Some(non_witness_utxo)) => { + let vout = input.spent_output_index as usize; + non_witness_utxo.output.get(vout).ok_or(Error::PsbtUtxoOutOfbounds) + } + (None, None) => Err(Error::MissingUtxo), + }) + } + + /// Combines this [`Psbt`] with `other` PSBT as described by BIP 174. + /// + /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` + pub fn combine_with(&mut self, other: Self) -> Result<(), Error> { + self.global.combine(other.global)?; + + for (self_input, other_input) in self.inputs.iter_mut().zip(other.inputs.into_iter()) { + self_input.combine(other_input); + } + + for (self_output, other_output) in self.outputs.iter_mut().zip(other.outputs.into_iter()) { + self_output.combine(other_output); + } + + Ok(()) + } + + /// Sets the PSBT_GLOBAL_TX_MODIFIABLE as required after signing an ECDSA input. + /// + /// > For PSBTv2s, a signer must update the PSBT_GLOBAL_TX_MODIFIABLE field after signing + /// > inputs so that it accurately reflects the state of the PSBT. + pub fn ecdsa_signer_clear_tx_modifiable(&mut self, ty: EcdsaSighashType) { + self.signer_clear_tx_modifiable(ty as u8) + } + + /// Sets the PSBT_GLOBAL_TX_MODIFIABLE as required after signing. + // TODO: Consider using consts instead of magic numbers. + fn signer_clear_tx_modifiable(&mut self, sighash_type: u8) { + let ty = sighash_type; + // If the Signer added a signature that does not use SIGHASH_ANYONECANPAY, + // the Input Modifiable flag must be set to False. + if !(ty == 0x81 || ty == 0x82 || ty == 0x83) { + self.global.clear_inputs_modifiable_flag(); + } + + // If the Signer added a signature that does not use SIGHASH_NONE, + // the Outputs Modifiable flag must be set to False. + if !(ty == 0x02 || ty == 0x82) { + self.global.clear_outputs_modifiable_flag(); + } + + // If the Signer added a signature that uses SIGHASH_SINGLE, + // the Has SIGHASH_SINGLE flag must be set to True. + if ty == 0x03 || ty == 0x83 { + self.global.set_sighash_single_flag(); + } + } + + /// Attempts to create _all_ the required signatures for this PSBT using `k`. + /// + /// **NOTE**: Taproot inputs are, as yet, not supported by this function. We currently only + /// attempt to sign ECDSA inputs. + /// + /// If you just want to sign an input with one specific key consider using `sighash_ecdsa`. This + /// function does not support scripts that contain `OP_CODESEPARATOR`. + /// + /// # Returns + /// + /// Either Ok(SigningKeys) or Err((SigningKeys, SigningErrors)), where + /// - SigningKeys: A map of input index -> pubkey associated with secret key used to sign. + /// - SigningKeys: A map of input index -> the error encountered while attempting to sign. + /// + /// If an error is returned some signatures may already have been added to the PSBT. Since + /// `partial_sigs` is a [`BTreeMap`] it is safe to retry, previous sigs will be overwritten. + pub fn sign( + &mut self, + k: &K, + secp: &Secp256k1, + ) -> Result + where + C: Signing, + K: GetKey, + { + let tx = self.unsigned_transaction().expect("TODO: Handle this error"); + let mut cache = SighashCache::new(&tx); + + let mut used = BTreeMap::new(); + let mut errors = BTreeMap::new(); + + for i in 0..self.global.input_count { + if let Ok(SigningAlgorithm::Ecdsa) = self.signing_algorithm(i) { + match self.bip32_sign_ecdsa(k, i, &mut cache, secp) { + Ok(v) => { + used.insert(i, v); + } + Err(e) => { + errors.insert(i, e); + } + } + }; + } + if errors.is_empty() { + Ok(used) + } else { + Err((used, errors)) + } + } + + /// Gets the fully updated, unsigned transaction ready to sign. + /// + /// # Errors + /// + /// If the PSBT has any signatures already. + fn unsigned_transaction(&self) -> Result { + let mut inputs: Vec = Vec::with_capacity(self.global.input_count); + for input in self.inputs.iter() { + let tx_in = input.unsigned_tx_in()?; + inputs.push(tx_in) + } + + Ok(Transaction { + version: self.global.tx_version, + lock_time: self.determine_lock_time()?, + input: inputs, + output: self.outputs.iter().map(|ouput| ouput.to_tx_out()).collect(), + }) + } + + /// Attempts to create all signatures required by this PSBT's `bip32_derivation` field, adding + /// them to `partial_sigs`. + /// + /// # Returns + /// + /// - Ok: A list of the public keys used in signing. + /// - Err: Error encountered trying to calculate the sighash AND we had the signing key. + fn bip32_sign_ecdsa( + &mut self, + k: &K, + input_index: usize, + cache: &mut SighashCache, + secp: &Secp256k1, + ) -> Result, SignError> + where + C: Signing, + T: Borrow, + K: GetKey, + { + let msg_sighash_ty_res = self.sighash_ecdsa(input_index, cache); + + let input = &mut self.inputs[input_index]; // Index checked in call to `sighash_ecdsa`. + let mut sighash_types = vec![]; + + let mut used = vec![]; // List of pubkeys used to sign the input. + + for (pk, key_source) in input.bip32_derivation.iter() { + let sk = if let Ok(Some(sk)) = k.get_key(KeyRequest::Bip32(key_source.clone()), secp) { + sk + } else if let Ok(Some(sk)) = k.get_key(KeyRequest::Pubkey(PublicKey::new(*pk)), secp) { + sk + } else { + continue; + }; + + // Only return the error if we have a secret key to sign this input. + let (msg, sighash_ty) = match msg_sighash_ty_res { + Err(e) => return Err(e), + Ok((msg, sighash_ty)) => (msg, sighash_ty), + }; + + let sig = + ecdsa::Signature { sig: secp.sign_ecdsa(&msg, &sk.inner), hash_ty: sighash_ty }; + sighash_types.push(sighash_ty); + + let pk = sk.public_key(secp); + + input.partial_sigs.insert(pk, sig); + used.push(pk); + } + + for ty in sighash_types { + self.ecdsa_signer_clear_tx_modifiable(ty); + } + + Ok(used) + } + + /// Returns the sighash message to sign an ECDSA input along with the sighash type. + /// + /// Uses the [`EcdsaSighashType`] from this input if one is specified. If no sighash type is + /// specified uses [`EcdsaSighashType::All`]. This function does not support scripts that + /// contain `OP_CODESEPARATOR`. + pub fn sighash_ecdsa>( + &self, + input_index: usize, + cache: &mut SighashCache, + ) -> Result<(Message, EcdsaSighashType), SignError> { + use OutputType::*; + + if self.signing_algorithm(input_index)? != SigningAlgorithm::Ecdsa { + return Err(SignError::WrongSigningAlgorithm); + } + + let input = self.checked_input(input_index)?; + let utxo = self.spend_utxo(input_index)?; + let spk = &utxo.script_pubkey; // scriptPubkey for input spend utxo. + + let hash_ty = input.ecdsa_hash_ty().map_err(|_| SignError::InvalidSighashType)?; // Only support standard sighash types. + + match self.output_type(input_index)? { + Bare => { + let sighash = cache.legacy_signature_hash(input_index, spk, hash_ty.to_u32())?; + Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + } + Sh => { + let script_code = + input.redeem_script.as_ref().ok_or(SignError::MissingRedeemScript)?; + let sighash = + cache.legacy_signature_hash(input_index, script_code, hash_ty.to_u32())?; + Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + } + Wpkh => { + let sighash = cache.p2wpkh_signature_hash(input_index, spk, utxo.value, hash_ty)?; + Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + } + ShWpkh => { + let redeem_script = input.redeem_script.as_ref().expect("checked above"); + let sighash = + cache.p2wpkh_signature_hash(input_index, redeem_script, utxo.value, hash_ty)?; + Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + } + Wsh | ShWsh => { + let witness_script = + input.witness_script.as_ref().ok_or(SignError::MissingWitnessScript)?; + let sighash = + cache.p2wsh_signature_hash(input_index, witness_script, utxo.value, hash_ty)?; + Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + } + Tr => { + // This PSBT signing API is WIP, taproot to come shortly. + Err(SignError::Unsupported) + } + } + } + + /// Returns the spending utxo for this PSBT's input at `input_index`. + pub fn spend_utxo(&self, input_index: usize) -> Result<&TxOut, SignError> { + let input = self.checked_input(input_index)?; + let utxo = if let Some(witness_utxo) = &input.witness_utxo { + witness_utxo + } else if let Some(non_witness_utxo) = &input.non_witness_utxo { + let vout = input.spent_output_index as usize; + &non_witness_utxo.output[vout] + } else { + return Err(SignError::MissingSpendUtxo); + }; + Ok(utxo) + } + + /// Gets a reference to the input at `input_index` after checking that it is a valid index. + fn checked_input(&self, index: usize) -> Result<&Input, IndexOutOfBoundsError> { + self.check_input_index(index)?; + Ok(&self.inputs[index]) + } + + /// Gets a mutable reference to the input at `input_index` after checking that it is a valid index. + fn checked_input_mut(&mut self, index: usize) -> Result<&mut Input, IndexOutOfBoundsError> { + self.check_input_index(index)?; + Ok(&mut self.inputs[index]) + } + /// Checks that `index` is valid for this PSBT. + fn check_input_index(&self, index: usize) -> Result<(), IndexOutOfBoundsError> { + if index >= self.inputs.len() { + return Err(IndexOutOfBoundsError::Inputs { index, length: self.inputs.len() }); + } + if index >= self.global.input_count { + return Err(IndexOutOfBoundsError::Count { index, count: self.global.input_count }); + } + Ok(()) + } + + /// Returns the algorithm used to sign this PSBT's input at `input_index`. + fn signing_algorithm(&self, input_index: usize) -> Result { + let output_type = self.output_type(input_index)?; + Ok(output_type.signing_algorithm()) + } + + /// Returns the [`OutputType`] of the spend utxo for this PBST's input at `input_index`. + fn output_type(&self, input_index: usize) -> Result { + let input = self.checked_input(input_index)?; + let utxo = self.spend_utxo(input_index)?; + let spk = utxo.script_pubkey.clone(); + + // Anything that is not segwit and is not p2sh is `Bare`. + if !(spk.is_witness_program() || spk.is_p2sh()) { + return Ok(OutputType::Bare); + } + + if spk.is_p2wpkh() { + return Ok(OutputType::Wpkh); + } + + if spk.is_p2wsh() { + return Ok(OutputType::Wsh); + } + + if spk.is_p2sh() { + if input.redeem_script.as_ref().map(|s| s.is_p2wpkh()).unwrap_or(false) { + return Ok(OutputType::ShWpkh); + } + if input.redeem_script.as_ref().map(|x| x.is_p2wsh()).unwrap_or(false) { + return Ok(OutputType::ShWsh); + } + return Ok(OutputType::Sh); + } + + if spk.is_p2tr() { + return Ok(OutputType::Tr); + } + + // Something is wrong with the input scriptPubkey or we do not know how to sign + // because there has been a new softfork that we do not yet support. + Err(SignError::UnknownOutputType) + } + + /// Calculates transaction fee. + /// + /// 'Fee' being the amount that will be paid for mining a transaction with the current inputs + /// and outputs i.e., the difference in value of the total inputs and the total outputs. + /// + /// ## Errors + /// + /// - [`Error::MissingUtxo`] when UTXO information for any input is not present or is invalid. + /// - [`Error::NegativeFee`] if calculated value is negative. + /// - [`Error::FeeOverflow`] if an integer overflow occurs. + pub fn fee(&self) -> Result { + // For the inputs we have to get the value from the input UTXOs. + let mut input_value: u64 = 0; + for input in self.iter_funding_utxos() { + input_value = + input_value.checked_add(input?.value.to_sat()).ok_or(Error::FeeOverflow)?; + } + // For the outputs we have the value directly in the `Output`. + let mut output_value: u64 = 0; + for output in &self.outputs { + output_value = + output_value.checked_add(output.amount.to_sat()).ok_or(Error::FeeOverflow)?; + } + + input_value.checked_sub(output_value).map(Amount::from_sat).ok_or(Error::NegativeFee) + } +} + +/// Data required to call [`GetKey`] to get the private key to sign an input. +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum KeyRequest { + /// Request a private key using the associated public key. + Pubkey(PublicKey), + /// Request a private key using BIP-32 fingerprint and derivation path. + Bip32(KeySource), +} + +/// Trait to get a private key from a key request, key is then used to sign an input. +pub trait GetKey { + /// An error occurred while getting the key. + type Error: core::fmt::Debug; + + /// Attempts to get the private key for `key_request`. + /// + /// # Returns + /// - `Some(key)` if the key is found. + /// - `None` if the key was not found but no error was encountered. + /// - `Err` if an error was encountered while looking for the key. + fn get_key( + &self, + key_request: KeyRequest, + secp: &Secp256k1, + ) -> Result, Self::Error>; +} + +impl GetKey for Xpriv { + type Error = GetKeyError; + + fn get_key( + &self, + key_request: KeyRequest, + secp: &Secp256k1, + ) -> Result, Self::Error> { + match key_request { + KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), + KeyRequest::Bip32((fingerprint, path)) => { + let key = if self.fingerprint(secp) == fingerprint { + let k = self.derive_priv(secp, &path)?; + Some(k.to_priv()) + } else { + None + }; + Ok(key) + } + } + } +} + +/// Map of input index -> pubkey associated with secret key used to create signature for that input. +pub type SigningKeys = BTreeMap>; + +/// Map of input index -> the error encountered while attempting to sign that input. +pub type SigningErrors = BTreeMap; + +#[rustfmt::skip] +macro_rules! impl_get_key_for_set { + ($set:ident) => { + +impl GetKey for $set { + type Error = GetKeyError; + + fn get_key( + &self, + key_request: KeyRequest, + secp: &Secp256k1 + ) -> Result, Self::Error> { + match key_request { + KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), + KeyRequest::Bip32((fingerprint, path)) => { + for xpriv in self.iter() { + if xpriv.parent_fingerprint == fingerprint { + let k = xpriv.derive_priv(secp, &path)?; + return Ok(Some(k.to_priv())); + } + } + Ok(None) + } + } + } +}}} + +impl_get_key_for_set!(BTreeSet); +#[cfg(feature = "std")] +impl_get_key_for_set!(HashSet); + +#[rustfmt::skip] +macro_rules! impl_get_key_for_map { + ($map:ident) => { + +impl GetKey for $map { + type Error = GetKeyError; + + fn get_key( + &self, + key_request: KeyRequest, + _: &Secp256k1, + ) -> Result, Self::Error> { + match key_request { + KeyRequest::Pubkey(pk) => Ok(self.get(&pk).cloned()), + KeyRequest::Bip32(_) => Err(GetKeyError::NotSupported), + } + } +}}} +impl_get_key_for_map!(BTreeMap); +#[cfg(feature = "std")] +impl_get_key_for_map!(HashMap); + +/// Errors when getting a key. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum GetKeyError { + /// A bip32 error. + Bip32(bip32::Error), + /// The GetKey operation is not supported for this key request. + NotSupported, +} + +impl fmt::Display for GetKeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetKeyError::*; + + match *self { + Bip32(ref e) => write_err!(f, "a bip23 error"; e), + NotSupported => + f.write_str("the GetKey operation is not supported for this key request"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for GetKeyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetKeyError::*; + + match *self { + NotSupported => None, + Bip32(ref e) => Some(e), + } + } +} + +impl From for GetKeyError { + fn from(e: bip32::Error) -> Self { GetKeyError::Bip32(e) } +} + +/// The various output types supported by the Bitcoin network. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum OutputType { + /// An output of type: pay-to-pubkey or pay-to-pubkey-hash. + Bare, + /// A pay-to-witness-pubkey-hash output (P2WPKH). + Wpkh, + /// A pay-to-witness-script-hash output (P2WSH). + Wsh, + /// A nested segwit output, pay-to-witness-pubkey-hash nested in a pay-to-script-hash. + ShWpkh, + /// A nested segwit output, pay-to-witness-script-hash nested in a pay-to-script-hash. + ShWsh, + /// A pay-to-script-hash output excluding wrapped segwit (P2SH). + Sh, + /// A taproot output (P2TR). + Tr, +} + +impl OutputType { + /// The signing algorithm used to sign this output type. + pub fn signing_algorithm(&self) -> SigningAlgorithm { + use OutputType::*; + + match self { + Bare | Wpkh | Wsh | ShWpkh | ShWsh | Sh => SigningAlgorithm::Ecdsa, + Tr => SigningAlgorithm::Schnorr, + } + } +} + +/// Signing algorithms supported by the Bitcoin network. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum SigningAlgorithm { + /// The Elliptic Curve Digital Signature Algorithm (see [wikipedia]). + /// + /// [wikipedia]: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm + Ecdsa, + /// The Schnorr signature algorithm (see [wikipedia]). + /// + /// [wikipedia]: https://en.wikipedia.org/wiki/Schnorr_signature + Schnorr, +} + +/// An error occurred while decoding a v2 PSBT. +#[derive(Debug)] +#[non_exhaustive] +pub enum DecodeError { + /// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most + /// significant byte order. + InvalidMagic, + /// The separator for a PSBT must be `0xff`. + InvalidSeparator, + /// Signals that there are no more key-value pairs in a key-value map. + NoMorePairs, + /// Error decoding global map. + Global(global::DecodeError), + /// Error decoding input map. + Input(input::DecodeError), + /// Error decoding output map. + Output(output::DecodeError), +} + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use DecodeError::*; + + match *self { + InvalidMagic => f.write_str("invalid magic"), + InvalidSeparator => f.write_str("invalid separator"), + NoMorePairs => f.write_str("no more key-value pairs for this psbt map"), + Global(ref e) => write_err!(f, "global map decode error"; e), + Input(ref e) => write_err!(f, "input map decode error"; e), + Output(ref e) => write_err!(f, "output map decode error"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for DecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecodeError::*; + + match *self { + InvalidMagic | InvalidSeparator | NoMorePairs => None, + Global(ref e) => Some(e), + Input(ref e) => Some(e), + Output(ref e) => Some(e), + } + } +} + +impl From for DecodeError { + fn from(e: global::DecodeError) -> Self { Self::Global(e) } +} + +impl From for DecodeError { + fn from(e: input::DecodeError) -> Self { Self::Input(e) } +} + +impl From for DecodeError { + fn from(e: output::DecodeError) -> Self { Self::Output(e) } +} + +/// If the "base64" feature is enabled we implement `Display` and `FromStr` using base64 encoding. +#[cfg(feature = "base64")] +mod display_from_str { + use core::fmt::{self, Display, Formatter}; + use core::str::FromStr; + + use bitcoin::base64::display::Base64Display; + use bitcoin::base64::prelude::{Engine as _, BASE64_STANDARD}; + + use super::*; + + impl Display for Psbt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", Base64Display::new(&self.serialize(), &BASE64_STANDARD)) + } + } + + impl FromStr for Psbt { + type Err = PsbtParseError; + + fn from_str(s: &str) -> Result { + let data = BASE64_STANDARD.decode(s).map_err(PsbtParseError::Base64Encoding)?; + Psbt::deserialize(&data).map_err(PsbtParseError::PsbtEncoding) + } + } + + /// Error encountered during PSBT decoding from Base64 string. + #[derive(Debug)] + #[non_exhaustive] + pub enum PsbtParseError { + /// Error in internal PSBT data structure. + PsbtEncoding(DecodeError), + /// Error in PSBT Base64 encoding. + Base64Encoding(bitcoin::base64::DecodeError), + } + + impl Display for PsbtParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use self::PsbtParseError::*; + + match *self { + PsbtEncoding(ref e) => write_err!(f, "error in internal PSBT data structure"; e), + Base64Encoding(ref e) => write_err!(f, "error in PSBT base64 encoding"; e), + } + } + } + + #[cfg(feature = "std")] + impl std::error::Error for PsbtParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use self::PsbtParseError::*; + + match self { + PsbtEncoding(e) => Some(e), + Base64Encoding(e) => Some(e), + } + } + } +} diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..1b57501 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: CC0-1.0 + +use core::convert::TryFrom; +use core::fmt; + +use bitcoin::consensus::encode as consensus; + +use crate::serialize::{Deserialize, Serialize}; + +/// The PSBT version. +#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Version(u32); + +impl Version { + /// The original PSBT format [BIP-174]. + pub const ZERO: Self = Self(0); + + /// The second PSBT version [BIP-370]. + pub const TWO: Self = Self(2); +} + +impl Version { + /// Returns the version number as a `u32`. + pub fn to_u32(self) -> u32 { self.0 } +} + +impl From for u32 { + fn from(v: Version) -> u32 { v.to_u32() } +} + +impl TryFrom for Version { + type Error = UnsupportedVersionError; + + fn try_from(n: u32) -> Result { + match n { + 0 => Ok(Version::ZERO), + 2 => Ok(Version::TWO), + n => Err(UnsupportedVersionError(n)), + } + } +} + +impl Serialize for Version { + fn serialize(&self) -> Vec { consensus::serialize(&self.to_u32()) } +} + +impl Deserialize for Version { + fn deserialize(bytes: &[u8]) -> Result { + let n: u32 = consensus::deserialize(bytes)?; + let version = Version::try_from(n)?; + Ok(version) + } +} + +/// Unsupported PSBT version. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct UnsupportedVersionError(u32); + +impl fmt::Display for UnsupportedVersionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "unsupported version, we only support v0 and v2: {}", self.0) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for UnsupportedVersionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} diff --git a/tests/bip370-determine-lock-time.rs b/tests/bip370-determine-lock-time.rs new file mode 100644 index 0000000..f979b75 --- /dev/null +++ b/tests/bip370-determine-lock-time.rs @@ -0,0 +1,86 @@ +//! BIP-370 test vectors - determine lock time. + +#![cfg(all(feature = "std", feature = "base64"))] + +mod util; + +use core::str::FromStr; + +use psbt::bitcoin::locktime::absolute; +use psbt::v2; + +#[track_caller] +fn assert_determine_lock_time(hex: &str, base64: &str, want: absolute::LockTime) { + let psbt = util::hex_psbt_v2(hex).expect("failed to deserialize PSBT from hex"); + assert_eq!(psbt, v2::Psbt::from_str(base64).expect("failed to deserialize PSBT from base64")); + + let got = psbt.determine_lock_time().expect("valid lock time"); + assert_eq!(got, want); +} + +// The following tests are the timelock determination algorithm. +#[test] +fn bip370_time_lock_determination() { + // The timelock for the following PSBTs should be computed to be 0: + let want = absolute::LockTime::ZERO; + + // Case: No locktimes specified + let hex = "70736274ff01020402000000010401010105010201fb040200000000010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + assert_determine_lock_time(hex, base64, want); + + // Case: Fallback locktime of 0 + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f040100000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f0400000000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="; + assert_determine_lock_time(hex, base64, want); + + // The timelock for the following PSBTs should be computed to be 10000: + let want = absolute::LockTime::from_consensus(10000); + + // Case: Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000, Input 2 has no locktime fields + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f0400000000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA"; + assert_determine_lock_time(hex, base64, want); + + // Case: Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000 + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f040000000001120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAESBCgjAAAAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA=="; + assert_determine_lock_time(hex, base64, want); + + // Case: Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460 + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc46201120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="; + assert_determine_lock_time(hex, base64, want); + + // Case: Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048459, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460 + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000111048b8dc4620112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc46201120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiARIEKCMAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA"; + assert_determine_lock_time(hex, base64, want); + + // The timelock for the following PSBTs should be computed to be 1657048460: + let want = absolute::LockTime::from_consensus(1657048460); + + // Case: Input 1 has PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048459, Input 2 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 9000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460 + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000111048b8dc46200010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc46201120428230000000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="; + assert_determine_lock_time(hex, base64, want); + + // Case: Input 1 has PSBT_IN_REQUIRED_HEIGHT_LOCKTIME of 10000 and PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048459, Input 2 has PSBT_IN_REQUIRED_TIME_LOCKTIME of 1657048460 + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000111048b8dc4620112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc462000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA="; + assert_determine_lock_time(hex, base64, want); + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f040100000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc462000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAREEjI3EYgABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA"; + assert_determine_lock_time(hex, base64, want); +} + +// The timelock for the following PSBTs cannot be computed: +#[test] +fn bip370_time_lock_indeterminate() { + let hex = "70736274ff0102040200000001030400000000010401020105010101fb040200000000010e200f758dbfbd4da7c16c8a3309c3c81e1100f561ea646db5b01752c485e1bdde9f010f04010000000112041027000000010e203a1b3b3c837d6489ea7a31d8e6c7dd503c001bef3e06958e7574808d68ca78a5010f04000000000111048c8dc462000103084f9335770000000001041600140b1352cacd03cf6aa1b7f3c8d6388671b34a5e1100"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA=="; + let psbt = util::hex_psbt_v2(hex).expect("failed to deserialize PSBT from hex"); + assert_eq!(psbt, v2::Psbt::from_str(base64).expect("failed to deserialize PSBT from base64")); + + assert!(psbt.determine_lock_time().is_err()); +} diff --git a/tests/bip370-parse-invalid.rs b/tests/bip370-parse-invalid.rs new file mode 100644 index 0000000..e535604 --- /dev/null +++ b/tests/bip370-parse-invalid.rs @@ -0,0 +1,143 @@ +//! BIP-370 test vectors - parse invalid PSBT. + +#![cfg(all(feature = "std", feature = "base64"))] + +mod util; + +#[test] +fn bip370_invalid() { + // Case: PSBTv0 but with PSBT_GLOBAL_VERSION set to 2. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_GLOBAL_TX_VERSION. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001020402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAECBAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_GLOBAL_FALLBACK_LOCKTIME. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001030402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEDBAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA== +"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_GLOBAL_INPUT_COUNT. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001040102000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEEAQIAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA== +"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_GLOBAL_OUTPUT_COUNT. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001050102000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEFAQIAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA== +"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_GLOBAL_TX_MODIFIABLE. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc68850000000001060100000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAEGAQAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BCGsCRzBEAiAFJ1pIVzTgrh87lxI3WG8OctyFgz0njA5HTNIxEsD6XgIgawSMg868PEHQuTzH2nYYXO29Aw0AWwgBi+K5i7rL33sBIQN2DcygXzmX3GWykwYPfynxUUyMUnBI4SgCsEHU/DQKJwAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA== +"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_IN_PREVIOUS_TXID. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a27010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc800220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAACICA27+LCVWIZhlU7qdZcPdxkFlyhQ24FqjWkxusCRRz3ltGPadhz5UAACAAQAAgAAAAIABAAAAYgAAAAA="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_IN_OUTPUT_INDEX. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a27010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_IN_SEQUENCE. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a27011004ffffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonARAE/////wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA== +"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_IN_REQUIRED_TIME_LOCKTIME. + + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a270111048c8dc46200220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonAREEjI3EYgAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA== +"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_IN_REQUIRED_HEIGHT_LOCKTIME. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a270112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonARIEECcAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAAAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_OUT_AMOUNT. + + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f00000000002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAACICA27+LCVWIZhlU7qdZcPdxkFlyhQ24FqjWkxusCRRz3ltGPadhz5UAACAAQAAgAAAAIABAAAAYgAAAAA="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv0 but with PSBT_OUT_SCRIPT. + let hex = "70736274ff01007102000000010b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc80000000000feffffff020008af2f00000000160014c430f64c4756da310dbd1a085572ef299926272c8bbdeb0b00000000160014a07dac8ab6ca942d379ed795f835ba71c9cc688500000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e01086b02473044022005275a485734e0ae1f3b971237586f0e72dc85833d278c0e474cd23112c0fa5e02206b048c83cebc3c41d0b93cc7da76185cedbd030d005b08018be2b98bbacbdf7b012103760dcca05f3997dc65b293060f7f29f1514c8c527048e12802b041d4fc340a2700220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000104160014a07dac8ab6ca942d379ed795f835ba71c9cc6885002202036efe2c255621986553ba9d65c3ddc64165ca1436e05aa35a4c6eb02451cf796d18f69d873e540000800100008000000080010000006200000000"; + let base64 = "cHNidP8BAHECAAAAAQsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAAAAAAD+////AgAIry8AAAAAFgAUxDD2TEdW2jENvRoIVXLvKZkmJyyLvesLAAAAABYAFKB9rIq2ypQtN57Xlfg1unHJzGiFAAAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEIawJHMEQCIAUnWkhXNOCuHzuXEjdYbw5y3IWDPSeMDkdM0jESwPpeAiBrBIyDzrw8QdC5PMfadhhc7b0DDQBbCAGL4rmLusvfewEhA3YNzKBfOZfcZbKTBg9/KfFRTIxScEjhKAKwQdT8NAonACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEEFgAUoH2sirbKlC03nteV+DW6ccnMaIUAIgIDbv4sJVYhmGVTup1lw93GQWXKFDbgWqNaTG6wJFHPeW0Y9p2HPlQAAIABAACAAAAAgAEAAABiAAAAAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 missing PSBT_GLOBAL_INPUT_COUNT. + + let hex = "70736274ff01020402000000010304000000000105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 missing PSBT_GLOBAL_OUTPUT_COUNT. + let hex = "70736274ff01020402000000010304000000000104010101fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 missing PSBT_IN_PREVIOUS_TXID. + let hex = "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEPBAAAAAABEAT+////ACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 missing PSBT_IN_OUTPUT_INDEX. + let hex = "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 missing PSBT_OUT_AMOUNT. + let hex = "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8AIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 missing PSBT_OUT_SCRIPT. + let hex = "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f0000000000220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8AIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAAAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 with PSBT_IN_REQUIRED_TIME_LOCKTIME less than 500000000. + let hex = "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011104ff64cd1d00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAAREE/2TNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); + + // Case: PSBTv2 with PSBT_IN_REQUIRED_HEIGHT_LOCKTIME greater than or equal to 500000000. + let hex = "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f04000000000112040065cd1d00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARIEAGXNHQAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_invalid_v0(hex, base64); + util::assert_invalid_v2(hex, base64); +} diff --git a/tests/bip370-parse-valid.rs b/tests/bip370-parse-valid.rs new file mode 100644 index 0000000..12b4076 --- /dev/null +++ b/tests/bip370-parse-valid.rs @@ -0,0 +1,92 @@ +//! BIP-370 test vectors - parse valid PSBT. + +#![cfg(all(feature = "std", feature = "base64"))] + +mod util; + +#[test] +fn bip370_valid() { + // Case: 1 input, 2 output PSBTv2, required fields only. + let hex = "70736274ff01020402000000010401010105010201fb040200000000010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2. + let hex = "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA"; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with PSBT_IN_SEQUENCE. + let hex = "70736274ff01020402000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff00220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with PSBT_IN_SEQUENCE, and all locktime fields + let hex = "70736274ff0102040200000001030400000000010401010105010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff0111048c8dc4620112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8BEQSMjcRiARIEECcAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with Inputs Modifiable Flag (bit 0) of PSBT_GLOBAL_TX_MODIFIABLE set + let hex = "70736274ff0102040200000001040101010501020106010101fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEBAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with Outputs Modifiable Flag (bit 1) of PSBT_GLOBAL_TX_MODIFIABLE set + let hex = "70736274ff0102040200000001040101010501020106010201fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with Has SIGHASH_SINGLE Flag (bit 2) of PSBT_GLOBAL_TX_MODIFIABLE set + let hex = "70736274ff0102040200000001040101010501020106010401fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEEAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with an undefined flag (bit 3) of PSBT_GLOBAL_TX_MODIFIABLE set + let hex = "70736274ff0102040200000001040101010501020106010801fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEIAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with both Inputs Modifiable Flag (bit 0) and Outputs Modifiable Flag (bit 1) of PSBT_GLOBAL_TX_MODIFIABLE set + let hex = "70736274ff0102040200000001040101010501020106010301fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEDAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with both Inputs Modifiable Flag (bit 0) and Has SIGHASH_SINGLE Flag (bit 2) of PSBT_GLOBAL_TX_MODIFIABLE set + let hex = "70736274ff0102040200000001040101010501020106010501fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEFAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with both Outputs Modifiable Flag (bit 1) and Has SIGHASH_SINGLE FLag (bit 2) of PSBT_GLOBAL_TX_MODIFIABLE set + let hex = "70736274ff0102040200000001040101010501020106010601fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEGAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with all defined PSBT_GLOBAL_TX_MODIFIABLE flags set + let hex = "70736274ff0102040200000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEHAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with all possible PSBT_GLOBAL_TX_MODIFIABLE flags set + let hex = "70736274ff010204020000000104010101050102010601ff01fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f040000000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgH/AfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA=="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); + + // Case: 1 input, 2 output updated PSBTv2, with all PSBTv2 fields + let hex = "70736274ff010204020000000103040000000001040101010501020106010701fb0402000000000100520200000001c1aa256e214b96a1822f93de42bff3b5f3ff8d0519306e3515d7515a5e805b120000000000ffffffff0118c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e0000000001011f18c69a3b00000000160014b0a3af144208412693ca7d166852b52db0aef06e010e200b0ad921419c1c8719735d72dc739f9ea9e0638d1fe4c1eef0f9944084815fc8010f0400000000011004feffffff0111048c8dc4620112041027000000220202d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab79218f69d873e540000800100008000000080000000002a0000000103080008af2f000000000104160014c430f64c4756da310dbd1a085572ef299926272c00220202e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b34018f69d873e54000080010000800000008001000000640000000103088bbdeb0b0000000001041600144dd193ac964a56ac1b9e1cca8454fe2f474f851300"; + let base64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAQYBBwH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BDiALCtkhQZwchxlzXXLcc5+eqeBjjR/kwe7w+ZRAhIFfyAEPBAAAAAABEAT+////AREEjI3EYgESBBAnAAAAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAABBBYAFMQw9kxHVtoxDb0aCFVy7ymZJicsACICAuNvv/U91TQHDPj9OWYUaA81epuF23NAvxz6dF0q17NAGPadhz5UAACAAQAAgAAAAIABAAAAZAAAAAEDCIu96wsAAAAAAQQWABRN0ZOslkpWrBueHMqEVP4vR0+FEwA="; + util::assert_valid_v2(hex, base64); + util::assert_invalid_v0(hex, base64); +} diff --git a/tests/util.rs b/tests/util.rs index d29a524..3b13c38 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -5,7 +5,7 @@ use core::str::FromStr; use psbt::bitcoin::hex::{self, FromHex}; -use psbt::v0; +use psbt::{v0, v2}; #[track_caller] pub fn hex_psbt_v0(s: &str) -> Result { @@ -16,6 +16,15 @@ pub fn hex_psbt_v0(s: &str) -> Result { } } +#[track_caller] +pub fn hex_psbt_v2(s: &str) -> Result { + let r: Result, hex::HexToBytesError> = Vec::from_hex(s); + match r { + Err(_e) => panic!("unable to parse PSBT v2 from hex string {}", s), + Ok(v) => v2::Psbt::deserialize(&v), + } +} + #[track_caller] pub fn assert_valid_v0(hex: &str, base64: &str) { if let Err(e) = hex_psbt_v0(hex) { @@ -26,8 +35,24 @@ pub fn assert_valid_v0(hex: &str, base64: &str) { assert!(v0::Psbt::from_str(base64).is_ok()); } +#[track_caller] +pub fn assert_valid_v2(hex: &str, base64: &str) { + if let Err(e) = hex_psbt_v2(hex) { + println!("Parse PSBT v2 (from hex) error: {:?}\n\n{}\n", e, hex); + panic!() + } + // If we got this far decoding works so this is basically just a sanity check. + assert!(v2::Psbt::from_str(base64).is_ok()); +} + #[track_caller] pub fn assert_invalid_v0(hex: &str, base64: &str) { assert!(hex_psbt_v0(hex).is_err()); assert!(v0::Psbt::from_str(base64).is_err()); } + +#[track_caller] +pub fn assert_invalid_v2(hex: &str, base64: &str) { + assert!(hex_psbt_v2(hex).is_err()); + assert!(v2::Psbt::from_str(base64).is_err()); +}