What changed, and why it matters
This commit removes the entire PSBT (Partially Signed Bitcoin Transaction) module from the rust-bitcoin library on the master branch. It is a planned code cleanup, not a security fix. The PSBT functionality has been moved to a separate crate called psbt-v2, and the old module was already deprecated in a previous release. Users who relied on the built-in PSBT support will need to migrate to the new crate when upgrading.
No immediate security action is required. Downstream maintainers should plan migration to the `psbt-v2` crate for PSBT functionality and review the breaking changes before upgrading rust-bitcoin on master. Monitor the project's migration guide and release notes for step-by-step upgrade instructions.
Security signals we found
Large-scale deletion of a previously deprecated public API module
Removal of PSBT serialization/deserialization, signing, and finalization code from the main crate
No changes to cryptographic primitives, consensus logic, or transaction validation
No vendor mention of a security bug, CVE, or vulnerability report
Evidence from the diff
The commit deletes the bitcoin/src/psbt/ module, all PSBT-related examples, integration tests, and fuzz targets, and removes the psbt re-exports from bitcoin/src/lib.rs and bitcoin/Cargo.toml. This is a breaking API change on the master branch following deprecation in PR #6055 on the 0.32.x branch. The functionality is replaced by the external psbt-v2 crate hosted on the project’s Forgejo instance. No vulnerability is patched or introduced by this diff; it is a structural refactor.
Changed components
bitcoin/src/psbt/*bitcoin/src/lib.rs PSBT re-exportsbitcoin/Cargo.toml PSBT examples and feature referencesbitcoin/examples/ecdsa-psbt*.rsbitcoin/examples/taproot-psbt*.rsbitcoin/tests/bip_174.rsbitcoin/tests/psbt-sign-taproot.rsbitcoin/tests/serde.rs PSBT testsfuzz/fuzz_targets/bitcoin/*psbt*.rsInspect captured patch +3 / −7568
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index f995a117..11153811 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -60,14 +60,6 @@ rustdoc-args = ["--cfg", "docsrs"]
[[example]]
name = "bip32"
-[[example]]
-name = "ecdsa-psbt"
-required-features = ["std", "bitcoinconsensus"]
-
-[[example]]
-name = "ecdsa-psbt-simple"
-required-features = ["rand", "std"]
-
[[example]]
name = "create-p2wpkh-address"
required-features = ["rand", "std"]
@@ -80,14 +72,6 @@ required-features = ["rand", "std"]
name = "sign-tx-taproot"
required-features = ["rand", "std"]
-[[example]]
-name = "taproot-psbt"
-required-features = ["rand", "std", "bitcoinconsensus"]
-
-[[example]]
-name = "taproot-psbt-simple"
-required-features = ["rand", "std"]
-
[[example]]
name = "sighash"
@@ -114,10 +98,8 @@ use_self = "warn"
examples = [
"bip32",
"bip32:-",
- "ecdsa-psbt:std bitcoinconsensus",
"sign-tx-segwit-v0:rand std",
"sign-tx-taproot:rand std",
- "taproot-psbt:bitcoinconsensus rand std",
"sighash:std",
"serde:std serde",
]
diff --git a/bitcoin/examples/ecdsa-psbt-simple.rs b/bitcoin/examples/ecdsa-psbt-simple.rs
deleted file mode 100644
index 72442950..00000000
--- a/bitcoin/examples/ecdsa-psbt-simple.rs
+++ /dev/null
@@ -1,240 +0,0 @@
-//! Implements a simple multi-input PSBT signing example
-//!
-//! The purpose of this section is to construct a PSBT that
-//! spends multiple inputs and signs it.
-//! We'll cover the following
-//! [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)
-//! roles:
-//!
-//! - **Creator**: Creates a PSBT with multiple inputs and outputs.
-//! - **Updater**: Adds Witness and SegWit V0 data to the PSBT.
-//! - **Signer**: Signs the PSBT.
-//! - **Finalizer**: Finalizes the PSBT.
-//!
-//! The example will focus on spending two SegWit V0 inputs:
-//!
-//! 1. 20,000,000 satoshi UTXO, the first receiving ("external") address.
-//! 1. 10,000,000 satoshi UTXO, the first change ("internal") address.
-//!
-//! We'll be sending this to two outputs:
-//!
-//! 1. 25,000,000 satoshis to a receivers' address.
-//! 1. 4,990,000 satoshis back to us as change.
-//!
-//! The miner's fee will be 10,000 satoshis.
-use std::collections::BTreeMap;
-
-use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv, Xpub};
-use bitcoin::ext::*;
-use bitcoin::key::WPubkeyHash;
-use bitcoin::locktime::absolute;
-use bitcoin::psbt::Input;
-use bitcoin::{
- consensus, transaction, Address, Amount, EcdsaSighashType, Network, OutPoint, Psbt,
- RedeemScriptBuf, ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Txid,
- Witness,
-};
-
-// The master xpriv, from which we derive the keys we control.
-const XPRIV: &str = "xprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq";
-
-// The derivation path for the keys we control.
-// This follows the BIP 84 derivation path for Bitcoin.
-const BIP84_DERIVATION_PATH: &str = "m/84'/0'/0'";
-
-// The master fingerprint of the master xpriv.
-const MASTER_FINGERPRINT: &str = "9680603f";
-
-// The dummy UTXO amounts we are spending.
-const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_u32(20_000_000);
-const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_u32(10_000_000);
-
-// The amounts we are sending to someone, and receiving back as change.
-const SPEND_AMOUNT: Amount = Amount::from_sat_u32(25_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(4_990_000); // 10_000 sat fee.
-
-// Derive the external address xpriv.
-fn get_external_address_xpriv(master_xpriv: Xpriv, index: u32) -> Xpriv {
- let derivation_path =
- BIP84_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
- let child_xpriv =
- master_xpriv.derive_xpriv(&derivation_path).expect("only deriving three steps");
- let external_index = ChildNumber::ZERO_NORMAL;
- let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
-
- child_xpriv.derive_xpriv([external_index, idx]).expect("only deriving two more steps")
-}
-
-// Derive the internal address xpriv.
-fn get_internal_address_xpriv(master_xpriv: Xpriv, index: u32) -> Xpriv {
- let derivation_path =
- BIP84_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
- let child_xpriv =
- master_xpriv.derive_xpriv(&derivation_path).expect("only deriving three steps");
- let internal_index = ChildNumber::ONE_NORMAL;
- let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
-
- child_xpriv.derive_xpriv([internal_index, idx]).expect("only deriving two more steps")
-}
-
-// The address to send to.
-fn receivers_address() -> Address {
- "bc1q7cyrfmck2ffu2ud3rn5l5a8yv6f0chkp0zpemf"
- .parse::<Address<_>>()
- .expect("a valid address")
- .require_network(Network::Bitcoin)
- .expect("valid address for mainnet")
-}
-
-// The dummy unspent transaction outputs that we control.
-fn dummy_unspent_transaction_outputs() -> Vec<(OutPoint, TxOut)> {
- let script_pubkey_1 = "bc1qrwuu3ydv0jfza4a0ehtfd03m9l4vw3fy0hfm50"
- .parse::<Address<_>>()
- .expect("a valid address")
- .require_network(Network::Bitcoin)
- .expect("valid address for mainnet")
- .script_pubkey();
-
- let out_point_1 = OutPoint {
- txid: Txid::from_byte_array([0xFF; 32]), // Arbitrary invalid dummy value.
- vout: 0,
- };
-
- let utxo_1 = TxOut { amount: DUMMY_UTXO_AMOUNT_INPUT_1, script_pubkey: script_pubkey_1 };
-
- let script_pubkey_2 = "bc1qy7swwpejlw7a2rp774pa8rymh8tw3xvd2x2xkd"
- .parse::<Address<_>>()
- .expect("a valid address")
- .require_network(Network::Bitcoin)
- .expect("valid address for mainnet")
- .script_pubkey();
-
- let out_point_2 = OutPoint {
- txid: Txid::from_byte_array([0xFF; 32]), // Arbitrary invalid dummy value.
- vout: 1,
- };
-
- let utxo_2 = TxOut { amount: DUMMY_UTXO_AMOUNT_INPUT_2, script_pubkey: script_pubkey_2 };
- vec![(out_point_1, utxo_1), (out_point_2, utxo_2)]
-}
-
-fn main() {
- // Get the individual xprivs we control. In a real application these would come from a stored secret.
- let master_xpriv = XPRIV.parse::<Xpriv>().expect("valid xpriv");
- let xpriv_input_1 = get_external_address_xpriv(master_xpriv, 0);
- let xpriv_input_2 = get_internal_address_xpriv(master_xpriv, 0);
- let xpriv_change = get_internal_address_xpriv(master_xpriv, 1);
-
- // Get the PKs
- let pk_input_1 = Xpub::from_xpriv(&xpriv_input_1).to_public_key();
- let pk_input_2 = Xpub::from_xpriv(&xpriv_input_2).to_public_key();
- let pk_inputs = [pk_input_1, pk_input_2];
- let pk_change = Xpub::from_xpriv(&xpriv_change).to_public_key();
-
- // Get the Witness Public Key Hashes (WPKHs)
- let wpkhs: Vec<WPubkeyHash> = pk_inputs.iter().map(|pk| pk.wpubkey_hash()).collect();
-
- // Get the unspent outputs that are locked to the key above that we control.
- // In a real application these would come from the chain.
- let utxos: Vec<TxOut> =
- dummy_unspent_transaction_outputs().into_iter().map(|(_, utxo)| utxo).collect();
-
- // Get the addresses to send to.
- let address = receivers_address();
-
- // The inputs for the transaction we are constructing.
- let inputs: Vec<TxIn> = dummy_unspent_transaction_outputs()
- .into_iter()
- .map(|(outpoint, _)| TxIn {
- previous_output: outpoint,
- script_sig: ScriptSigBuf::default(),
- sequence: Sequence::ENABLE_LOCKTIME_AND_RBF,
- witness: Witness::default(),
- })
- .collect();
-
- // The spend output is locked to a key controlled by the receiver.
- let spend = TxOut { amount: SPEND_AMOUNT, script_pubkey: address.script_pubkey() };
-
- // The change output is locked to a key controlled by us.
- let change = TxOut {
- amount: CHANGE_AMOUNT,
- script_pubkey: ScriptPubKeyBuf::new_p2wpkh(pk_change.wpubkey_hash()), // Change comes back to us.
- };
-
- // The transaction we want to sign and broadcast.
- let unsigned_tx = Transaction {
- version: transaction::Version::TWO, // Post BIP 68.
- lock_time: absolute::LockTime::ZERO, // Ignore the locktime.
- inputs, // Input is 0-indexed.
- outputs: vec![spend, change], // Outputs, order does not matter.
- };
-
- // Now we'll start the PSBT workflow.
- // Step 1: Creator role; that creates,
- // and add inputs and outputs to the PSBT.
- let mut psbt = Psbt::from_unsigned_tx(unsigned_tx).expect("could not create PSBT");
-
- // Step 2:Updater role; that adds additional
- // information to the PSBT.
- let ty = EcdsaSighashType::All.into();
- let derivation_paths = [
- "m/84'/0'/0'/0/0".parse::<DerivationPath>().expect("valid derivation path"),
- "m/84'/0'/0'/1/0".parse::<DerivationPath>().expect("valid derivation path"),
- ];
- let mut bip32_derivations = Vec::new();
- for (idx, pk) in pk_inputs.iter().enumerate() {
- let mut map = BTreeMap::new();
- let fingerprint = MASTER_FINGERPRINT.parse::<Fingerprint>().expect("valid fingerprint");
- map.insert(pk.to_inner(), (fingerprint, derivation_paths[idx].clone()));
- bip32_derivations.push(map);
- }
- psbt.inputs = vec![
- Input {
- witness_utxo: Some(utxos[0].clone()),
- redeem_script: Some(RedeemScriptBuf::new_p2wpkh(wpkhs[0])),
- bip32_derivation: bip32_derivations[0].clone(),
- sighash_type: Some(ty),
- ..Default::default()
- },
- Input {
- witness_utxo: Some(utxos[1].clone()),
- redeem_script: Some(RedeemScriptBuf::new_p2wpkh(wpkhs[1])),
- bip32_derivation: bip32_derivations[1].clone(),
- sighash_type: Some(ty),
- ..Default::default()
- },
- ];
-
- // Step 3: Signer role; that signs the PSBT.
- psbt.sign(&master_xpriv).expect("valid signature");
-
- // Step 4: Finalizer role; that finalizes the PSBT.
- println!("PSBT Inputs: {:#?}", psbt.inputs);
- let final_script_witness: Vec<Witness> = psbt
- .inputs
- .iter()
- .enumerate()
- .map(|(idx, input)| {
- let (_, sig) = input.partial_sigs.iter().next().expect("we have one sig");
- Witness::p2wpkh(*sig, pk_inputs[idx])
- })
- .collect();
- psbt.inputs.iter_mut().enumerate().for_each(|(idx, input)| {
- // Clear all the data fields as per the spec.
- input.final_script_witness = Some(final_script_witness[idx].clone());
- input.partial_sigs = BTreeMap::new();
- input.sighash_type = None;
- input.redeem_script = None;
- input.witness_script = None;
- input.bip32_derivation = BTreeMap::new();
- });
-
- // BOOM! Transaction signed and ready to broadcast.
- let signed_tx = psbt.extract_tx().expect("valid transaction");
- let serialized_signed_tx = consensus::encode::serialize_hex(&signed_tx);
- println!("Transaction Details: {signed_tx:#?}");
- // check with:
- // bitcoin-cli decoderawtransaction <RAW_TX> true
- println!("Raw Transaction: {serialized_signed_tx}");
-}
diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs
deleted file mode 100644
index 9a43d1ff..00000000
--- a/bitcoin/examples/ecdsa-psbt.rs
+++ /dev/null
@@ -1,281 +0,0 @@
-//! Implements an example PSBT workflow.
-//!
-//! The workflow we simulate is that of a setup using a watch-only online wallet (contains only
-//! public keys) and a cold-storage signing wallet (contains the private keys).
-//!
-//! You can verify the workflow using `bitcoind` and `bitcoin-cli`.
-//!
-//! # Example Setup
-//!
-//! 1. Start Bitcoin Core in Regtest mode, for example:
-//!
-//! `bitcoind -regtest -server -daemon -fallbackfee=0.0002 -rpcuser=admin -rpcpassword=pass -rpcallowip=127.0.0.1/0 -rpcbind=127.0.0.1 -blockfilterindex=1 -peerblockfilters=1`
-//!
-//! 2. Define a shell alias to `bitcoin-cli`, for example:
-//!
-//! `alias bt=bitcoin-cli -rpcuser=admin -rpcpassword=pass -rpcport=18443`
-//!
-//! 3. Create (or load) a default wallet, for example:
-//!
-//! `bt createwallet <wallet-name>`
-//!
-//! 4. Mine some blocks, for example:
-//!
-//! `bt generatetoaddress 110 $(bt getnewaddress)`
-//!
-//! 5. Get the details for a UTXO to fund the PSBT with:
-//!
-//! `bt listunspent`
-//!
-
-use std::collections::BTreeMap;
-use std::fmt;
-
-use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv, Xpub};
-use bitcoin::consensus::encode;
-use bitcoin::ext::*;
-use bitcoin::locktime::absolute;
-use bitcoin::psbt::{self, Input, Psbt, PsbtSighashType};
-use bitcoin::{
- transaction, Address, Amount, FullPublicKey, Network, OutPoint, RedeemScriptBuf,
- ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
-};
-
-type Result<T> = std::result::Result<T, Error>;
-
-// Get this from the output of `bt dumpwallet <file>`.
-const EXTENDED_MASTER_PRIVATE_KEY: &str = "tprv8ZgxMBicQKsPeSHZFZWT8zxie2dXWcwemnTkf4grVzMvP2UABUxqbPTCHzZ4ztwhBghpfFw27sJqEgW6y1ZTZcfvCUdtXE1L6qMF7TBdbqQ";
-
-// Set these with valid data from output of step 5 above. Please note, input utxo must be a p2wpkh.
-const INPUT_UTXO_TXID: &str = "295f06639cde6039bf0c3dbf4827f0e3f2b2c2b476408e2f9af731a8d7a9c7fb";
-const INPUT_UTXO_VOUT: u32 = 0;
-const INPUT_UTXO_SCRIPT_PUBKEY: &str = "00149891eeb8891b3e80a2a1ade180f143add23bf5de";
-const INPUT_UTXO_AMOUNT: &str = "50 BTC";
-// Get this from the descriptor,
-// "wpkh([97f17dca/0'/0'/0']02749483607dafb30c66bd93ece4474be65745ce538c2d70e8e246f17e7a4e0c0c)#m9n56cx0".
-const INPUT_UTXO_DERIVATION_PATH: &str = "0h/0h/0h";
-
-// Grab an address to receive on: `bt generatenewaddress` (obviously contrived but works as an example).
-const RECEIVE_ADDRESS: &str = "bcrt1qcmnpjjjw78yhyjrxtql6lk7pzpujs3h244p7ae"; // The address to receive the coins we send.
-
-// These should be correct if the UTXO above should is for 50 BTC.
-const OUTPUT_AMOUNT_BTC: &str = "1 BTC";
-const CHANGE_AMOUNT_BTC: &str = "48.99999 BTC"; // 1000 sat transaction fee.
-
-const NETWORK: Network = Network::Regtest;
-
-fn main() -> Result<()> {
- let (offline, fingerprint, account_0_xpub, input_xpub) =
- ColdStorage::new(EXTENDED_MASTER_PRIVATE_KEY)?;
-
- let online = WatchOnly::new(account_0_xpub, input_xpub, fingerprint);
-
- let created = online.create_psbt()?;
- let updated = online.update_psbt(created)?;
-
- let signed = offline.sign_psbt(updated)?;
-
- let finalized = online.finalize_psbt(signed)?;
-
- // You can use `bt sendrawtransaction` to broadcast the extracted transaction.
- let tx = finalized.extract_tx_unchecked_fee_rate();
- tx.verify(|_| Some(previous_output())).expect("failed to verify transaction");
-
- let hex = encode::serialize_hex(&tx);
- println!("You should now be able to broadcast the following transaction: \n\n{hex}");
-
- Ok(())
-}
-
-// We cache the pubkeys for convenience because it requires a secp256k1 context to convert the private key.
-/// An example of an offline signer i.e., a cold-storage device.
-struct ColdStorage {
- /// The master extended private key.
- master_xpriv: Xpriv,
- /// The master extended public key.
- master_xpub: Xpub,
-}
-
-/// The data exported from an offline wallet to enable creation of a watch-only online wallet.
-/// (wallet, fingerprint, account_0_xpub, input_utxo_xpub)
-type ExportData = (ColdStorage, Fingerprint, Xpub, Xpub);
-
-impl ColdStorage {
- /// Constructs a new `ColdStorage` signer.
- ///
- /// # Returns
- ///
- /// The newly created signer along with the data needed to configure a watch-only wallet.
- fn new(xpriv: &str) -> Result<ExportData> {
- let master_xpriv = xpriv.parse::<Xpriv>()?;
- let master_xpub = Xpub::from_xpriv(&master_xpriv);
-
- // Hardened children require secret data to derive.
-
- let path = "84h/0h/0h".into_derivation_path()?;
- let account_0_xpriv = master_xpriv.derive_xpriv(&path).expect("derivation path is short");
- let account_0_xpub = Xpub::from_xpriv(&account_0_xpriv);
-
- let path = INPUT_UTXO_DERIVATION_PATH.into_derivation_path()?;
- let input_xpriv = master_xpriv.derive_xpriv(&path).expect("derivation path is short");
- let input_xpub = Xpub::from_xpriv(&input_xpriv);
-
- let wallet = Self { master_xpriv, master_xpub };
- let fingerprint = wallet.master_fingerprint();
-
- Ok((wallet, fingerprint, account_0_xpub, input_xpub))
- }
-
- /// Returns the fingerprint for the master extended public key.
- fn master_fingerprint(&self) -> Fingerprint { self.master_xpub.fingerprint() }
-
- /// Signs `psbt` with this signer.
- fn sign_psbt(&self, mut psbt: Psbt) -> Result<Psbt> {
- match psbt.sign(&self.master_xpriv) {
- Ok(keys) => assert_eq!(keys.len(), 1),
- Err((_, e)) => {
- let e = e.get(&0).expect("at least one error");
- return Err(e.clone().into());
- }
- };
- Ok(psbt)
- }
-}
-
-/// An example of a watch-only online wallet.
-struct WatchOnly {
- /// The xpub for account 0 derived from derivation path "m/84h/0h/0h".
- account_0_xpub: Xpub,
- /// The xpub derived from `INPUT_UTXO_DERIVATION_PATH`.
- input_xpub: Xpub,
- /// The master extended pubkey fingerprint.
- master_fingerprint: Fingerprint,
-}
-
-impl WatchOnly {
- /// Constructs a new watch-only wallet.
- ///
- /// A watch-only wallet would typically be online and connected to the Bitcoin network. We
- /// 'import' into the wallet the `account_0_xpub` and `master_fingerprint`.
- ///
- /// The reason for importing the `input_xpub` is so one can use bitcoind to grab a valid input
- /// to verify the workflow presented in this file.
- fn new(account_0_xpub: Xpub, input_xpub: Xpub, master_fingerprint: Fingerprint) -> Self {
- Self { account_0_xpub, input_xpub, master_fingerprint }
- }
-
- /// Creates the PSBT, in BIP-0174 parlance this is the 'Creator'.
- fn create_psbt(&self) -> Result<Psbt> {
- let to_address =
- RECEIVE_ADDRESS.parse::<Address<_>>()?.require_network(Network::Regtest)?;
- let to_amount = OUTPUT_AMOUNT_BTC.parse::<Amount>()?;
-
- let (_, change_address, _) = self.change_address()?;
- let change_amount = CHANGE_AMOUNT_BTC.parse::<Amount>()?;
-
- let tx = Transaction {
- version: transaction::Version::TWO,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![TxIn {
- previous_output: OutPoint { txid: INPUT_UTXO_TXID.parse()?, vout: INPUT_UTXO_VOUT },
- script_sig: ScriptSigBuf::new(),
- sequence: Sequence::MAX, // Disable LockTime and RBF.
- witness: Witness::default(),
- }],
- outputs: vec![
- TxOut { amount: to_amount, script_pubkey: to_address.script_pubkey() },
- TxOut { amount: change_amount, script_pubkey: change_address.script_pubkey() },
- ],
- };
-
- let psbt = Psbt::from_unsigned_tx(tx)?;
-
- Ok(psbt)
- }
-
- /// Updates the PSBT, in BIP-0174 parlance this is the 'Updater'.
- fn update_psbt(&self, mut psbt: Psbt) -> Result<Psbt> {
- let mut input = Input { witness_utxo: Some(previous_output()), ..Default::default() };
-
- let pk = self.input_xpub.to_public_key();
- let wpkh = pk.wpubkey_hash();
-
- let redeem_script = RedeemScriptBuf::new_p2wpkh(wpkh);
- input.redeem_script = Some(redeem_script);
-
- let fingerprint = self.master_fingerprint;
- let path = input_derivation_path()?;
- let mut map = BTreeMap::new();
- map.insert(pk.to_inner(), (fingerprint, path));
- input.bip32_derivation = map;
-
- let ty = "SIGHASH_ALL".parse::<PsbtSighashType>()?;
- input.sighash_type = Some(ty);
-
- psbt.inputs = vec![input];
-
- Ok(psbt)
- }
-
- /// Finalizes the PSBT, in BIP-0174 parlance this is the 'Finalizer'.
- /// This is just an example. For a production-ready PSBT Finalizer, use [rust-miniscript](https://docs.rs/miniscript/latest/miniscript/psbt/trait.PsbtExt.html#tymethod.finalize)
- fn finalize_psbt(&self, mut psbt: Psbt) -> Result<Psbt> {
- if psbt.inputs.is_empty() {
- return Err(psbt::SignError::MissingInputUtxo.into());
- }
-
- let sigs: Vec<_> = psbt.inputs[0].partial_sigs.values().collect();
- let mut script_witness: Witness = Witness::new();
- script_witness.push(sigs[0].serialize());
- script_witness.push(self.input_xpub.to_public_key().to_bytes());
-
- psbt.inputs[0].final_script_witness = Some(script_witness);
-
- // Clear all the data fields as per the spec.
- psbt.inputs[0].partial_sigs = BTreeMap::new();
- psbt.inputs[0].sighash_type = None;
- psbt.inputs[0].redeem_script = None;
- psbt.inputs[0].witness_script = None;
- psbt.inputs[0].bip32_derivation = BTreeMap::new();
-
- Ok(psbt)
- }
-
- /// Returns data for the first change address (standard BIP-0084 derivation path
- /// "m/84h/0h/0h/1/0"). A real wallet would have access to the chain so could determine if an
- /// address has been used or not. We ignore this detail and just re-use the first change address
- /// without loss of generality.
- fn change_address(&self) -> Result<(FullPublicKey, Address, DerivationPath)> {
- let path = [ChildNumber::ONE_NORMAL, ChildNumber::ZERO_NORMAL];
- let derived = self.account_0_xpub.derive_xpub(path)?;
-
- let pk = derived.to_public_key();
- let addr = Address::p2wpkh(pk, NETWORK);
- let path = path.into_derivation_path()?;
-
- Ok((pk, addr, path))
- }
-}
-
-fn input_derivation_path() -> Result<DerivationPath> {
- let path = INPUT_UTXO_DERIVATION_PATH.into_derivation_path()?;
- Ok(path)
-}
-
-fn previous_output() -> TxOut {
- let script_pubkey = ScriptPubKeyBuf::from_hex_no_length_prefix(INPUT_UTXO_SCRIPT_PUBKEY)
- .expect("failed to parse input utxo scriptPubkey");
- let amount = INPUT_UTXO_AMOUNT.parse::<Amount>().expect("failed to parse input utxo amount");
-
- TxOut { amount, script_pubkey }
-}
-
-struct Error(Box<dyn std::error::Error>);
-
-impl<T: std::error::Error + 'static> From<T> for Error {
- fn from(e: T) -> Self { Self(Box::new(e)) }
-}
-
-impl fmt::Debug for Error {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
-}
diff --git a/bitcoin/examples/taproot-psbt-simple.rs b/bitcoin/examples/taproot-psbt-simple.rs
deleted file mode 100644
index 6bb28580..00000000
--- a/bitcoin/examples/taproot-psbt-simple.rs
+++ /dev/null
@@ -1,241 +0,0 @@
-//! Implements a simple multi-input PSBT signing example
-//!
-//! The purpose of this section is to construct a PSBT that spends multiple inputs and signs it.
-//! We'll cover the following [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)
-//! roles:
-//!
-//! - **Creator**: Creates a PSBT with multiple inputs and outputs.
-//! - **Updater**: Adds Witness and Taproot data to the PSBT.
-//! - **Signer**: Signs the PSBT.
-//! - **Finalizer**: Finalizes the PSBT.
-//!
-//! The example will focus on spending two Taproot inputs:
-//!
-//! 1. 20,000,000 satoshi UTXO, the first receiving ("external") address.
-//! 1. 10,000,000 satoshi UTXO, the first change ("internal") address.
-//!
-//! We'll be sending this to two outputs:
-//!
-//! 1. 25,000,000 satoshis to a receivers' address.
-//! 1. 4,990,000 satoshis back to us as change.
-//!
-//! The miner's fee will be 10,000 satoshis.
-use std::collections::BTreeMap;
-
-use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv, Xpub};
-use bitcoin::ext::*;
-use bitcoin::key::UntweakedPublicKey;
-use bitcoin::locktime::absolute;
-use bitcoin::psbt::Input;
-use bitcoin::{
- consensus, transaction, Address, Amount, Network, OutPoint, Psbt, ScriptPubKeyBuf,
- ScriptSigBuf, Sequence, TapLeafHash, TapSighashType, Transaction, TxIn, TxOut, Txid, Witness,
- XOnlyPublicKey,
-};
-
-// The master xpriv, from which we derive the keys we control.
-const XPRIV: &str = "xprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq";
-
-// The derivation path for the keys we control.
-// This follows the BIP 86 derivation path for Bitcoin.
-const BIP86_DERIVATION_PATH: &str = "m/86'/0'/0'";
-
-// The master fingerprint of the master xpriv.
-const MASTER_FINGERPRINT: &str = "9680603f";
-
-// The dummy UTXO amounts we are spending.
-const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_u32(20_000_000);
-const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_u32(10_000_000);
-
-// The amounts we are sending to someone, and receiving back as change.
-const SPEND_AMOUNT: Amount = Amount::from_sat_u32(25_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(4_990_000); // 10_000 sat fee.
-
-// Derive the external address xpriv.
-fn get_external_address_xpriv(master_xpriv: Xpriv, index: u32) -> Xpriv {
- let derivation_path =
- BIP86_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
- let child_xpriv =
- master_xpriv.derive_xpriv(&derivation_path).expect("only deriving three steps");
- let external_index = ChildNumber::ZERO_NORMAL;
- let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
-
- child_xpriv.derive_xpriv([external_index, idx]).expect("only deriving two more steps")
-}
-
-// Derive the internal address xpriv.
-fn get_internal_address_xpriv(master_xpriv: Xpriv, index: u32) -> Xpriv {
- let derivation_path =
- BIP86_DERIVATION_PATH.into_derivation_path().expect("valid derivation path");
- let child_xpriv =
- master_xpriv.derive_xpriv(&derivation_path).expect("only deriving three steps");
- let internal_index = ChildNumber::ONE_NORMAL;
- let idx = ChildNumber::from_normal_idx(index).expect("valid index number");
-
- child_xpriv.derive_xpriv([internal_index, idx]).expect("only deriving two more steps")
-}
-
-// Get the Taproot Key Origin.
-fn get_tap_key_origin<K: Into<UntweakedPublicKey> + std::cmp::Ord>(
- x_only_key: K,
- master_fingerprint: Fingerprint,
- path: DerivationPath,
-) -> BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, (Fingerprint, DerivationPath))> {
- let x_only_key = x_only_key.into();
- let mut map = BTreeMap::new();
- map.insert(x_only_key, (vec![], (master_fingerprint, path)));
- map
-}
-
-// The address to send to.
-fn receivers_address() -> Address {
- "bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va"
- .parse::<Address<_>>()
- .expect("a valid address")
- .require_network(Network::Bitcoin)
- .expect("valid address for mainnet")
-}
-
-// The dummy unspent transaction outputs that we control.
-fn dummy_unspent_transaction_outputs() -> Vec<(OutPoint, TxOut)> {
- let script_pubkey_1 = "bc1p80lanj0xee8q667aqcnn0xchlykllfsz3gu5skfv9vjsytaujmdqtv52vu"
- .parse::<Address<_>>()
- .unwrap()
- .require_network(Network::Bitcoin)
- .unwrap()
- .script_pubkey();
-
- let out_point_1 = OutPoint {
- txid: Txid::from_byte_array([0xFF; 32]), // Arbitrary invalid dummy value.
- vout: 0,
- };
-
- let utxo_1 = TxOut { amount: DUMMY_UTXO_AMOUNT_INPUT_1, script_pubkey: script_pubkey_1 };
-
- let script_pubkey_2 = "bc1pfd0jmmdnp278vppcw68tkkmquxtq50xchy7f6wdmjtjm7fgsr8dszdcqce"
- .parse::<Address<_>>()
- .unwrap()
- .require_network(Network::Bitcoin)
- .unwrap()
- .script_pubkey();
-
- let out_point_2 = OutPoint {
- txid: Txid::from_byte_array([0xFF; 32]), // Arbitrary invalid dummy value.
- vout: 1,
- };
-
- let utxo_2 = TxOut { amount: DUMMY_UTXO_AMOUNT_INPUT_2, script_pubkey: script_pubkey_2 };
- vec![(out_point_1, utxo_1), (out_point_2, utxo_2)]
-}
-
-fn main() {
- // Get the individual xprivs we control. In a real application these would come from a stored secret.
- let master_xpriv = XPRIV.parse::<Xpriv>().expect("valid xpriv");
- let xpriv_input_1 = get_external_address_xpriv(master_xpriv, 0);
- let xpriv_input_2 = get_internal_address_xpriv(master_xpriv, 0);
- let xpriv_change = get_internal_address_xpriv(master_xpriv, 1);
-
- // Get the PKs
- let (pk_input_1, _) = Xpub::from_xpriv(&xpriv_input_1).public_key.x_only_public_key();
- let (pk_input_2, _) = Xpub::from_xpriv(&xpriv_input_2).public_key.x_only_public_key();
- let (pk_change, _) = Xpub::from_xpriv(&xpriv_change).public_key.x_only_public_key();
-
- // Get the Tap Key Origins
- // Map of tap root X-only keys to origin info and leaf hashes contained in it.
- let origin_input_1 = get_tap_key_origin(
- pk_input_1,
- MASTER_FINGERPRINT.parse::<Fingerprint>().unwrap(),
- "m/86'/0'/0'/0/0".parse::<DerivationPath>().unwrap(),
- );
- let origin_input_2 = get_tap_key_origin(
- pk_input_2,
- MASTER_FINGERPRINT.parse::<Fingerprint>().unwrap(),
- "m/86'/0'/0'/1/0".parse::<DerivationPath>().unwrap(),
- );
- let origins = [origin_input_1, origin_input_2];
-
- // Get the unspent outputs that are locked to the key above that we control.
- // In a real application these would come from the chain.
- let utxos: Vec<TxOut> =
- dummy_unspent_transaction_outputs().into_iter().map(|(_, utxo)| utxo).collect();
-
- // Get the addresses to send to.
- let address = receivers_address();
-
- // The inputs for the transaction we are constructing.
- let inputs: Vec<TxIn> = dummy_unspent_transaction_outputs()
- .into_iter()
- .map(|(outpoint, _)| TxIn {
- previous_output: outpoint,
- script_sig: ScriptSigBuf::default(),
- sequence: Sequence::ENABLE_LOCKTIME_AND_RBF,
- witness: Witness::default(),
- })
- .collect();
-
- // The spend output is locked to a key controlled by the receiver.
- let spend = TxOut { amount: SPEND_AMOUNT, script_pubkey: address.script_pubkey() };
-
- // The change output is locked to a key controlled by us.
- let change = TxOut {
- amount: CHANGE_AMOUNT,
- script_pubkey: ScriptPubKeyBuf::new_p2tr(pk_change, None), // Change comes back to us.
- };
-
- // The transaction we want to sign and broadcast.
- let unsigned_tx = Transaction {
- version: transaction::Version::TWO, // Post BIP 68.
- lock_time: absolute::LockTime::ZERO, // Ignore the locktime.
- inputs, // Input is 0-indexed.
- outputs: vec![spend, change], // Outputs, order does not matter.
- };
-
- // Now we'll start the PSBT workflow.
- // Step 1: Creator role; that creates,
- // and add inputs and outputs to the PSBT.
- let mut psbt = Psbt::from_unsigned_tx(unsigned_tx).expect("could not create PSBT");
-
- // Step 2:Updater role; that adds additional
- // information to the PSBT.
- let ty = TapSighashType::All.into();
- psbt.inputs = vec![
- Input {
- witness_utxo: Some(utxos[0].clone()),
- tap_key_origins: origins[0].clone(),
- tap_internal_key: Some(pk_input_1.into()),
- sighash_type: Some(ty),
- ..Default::default()
- },
- Input {
- witness_utxo: Some(utxos[1].clone()),
- tap_key_origins: origins[1].clone(),
- tap_internal_key: Some(pk_input_2.into()),
- sighash_type: Some(ty),
- ..Default::default()
- },
- ];
-
- // Step 3: Signer role; that signs the PSBT.
- psbt.sign(&master_xpriv).expect("valid signature");
-
- // Step 4: Finalizer role; that finalizes the PSBT.
- psbt.inputs.iter_mut().for_each(|input| {
- let script_witness = Witness::p2tr_key_spend(&input.tap_key_sig.unwrap());
- input.final_script_witness = Some(script_witness);
-
- // Clear all the data fields as per the spec.
- input.partial_sigs = BTreeMap::new();
- input.sighash_type = None;
- input.redeem_script = None;
- input.witness_script = None;
- input.bip32_derivation = BTreeMap::new();
- });
-
- // BOOM! Transaction signed and ready to broadcast.
- let signed_tx = psbt.extract_tx().expect("valid transaction");
- let serialized_signed_tx = consensus::encode::serialize_hex(&signed_tx);
- println!("Transaction Details: {signed_tx:#?}");
- // check with:
- // bitcoin-cli decoderawtransaction <RAW_TX> true
- println!("Raw Transaction: {serialized_signed_tx}");
-}
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
deleted file mode 100644
index 4028af37..00000000
--- a/bitcoin/examples/taproot-psbt.rs
+++ /dev/null
@@ -1,767 +0,0 @@
-//! Example of Taproot PSBT workflow
-
-// We use the alias `alias bt='bitcoin-cli -regtest'` for brevity.
-
-// Step 0 - Wipe the `regtest` data directory to start from a clean slate.
-
-// Step 1 - Run `bitcoind -regtest -daemon` to start the daemon. Bitcoin Core 23.0+ is required.
-
-// Step 2 -
-// 2.1) Run `bt -named createwallet wallet_name=benefactor blank=true` to create a blank wallet with the name "benefactor"
-// 2.2) Run `bt -named createwallet wallet_name=beneficiary blank=true` to create a blank wallet with the name "beneficiary"
-// 2.3) Create the two aliases:
-// alias bt-benefactor='bitcoin-cli -regtest -rpcwallet=benefactor'
-// alias bt-beneficiary='bitcoin-cli -regtest -rpcwallet=beneficiary'
-//
-// 2.4) Import the example descriptors:
-// bt-benefactor importdescriptors '[
-// { "desc": "tr(tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/86\'/1\'/0\'/1/*)#jzyeered", "active": true, "timestamp": "now", "internal": true },
-// { "desc": "tr(tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/86\'/1\'/0\'/0/*)#rkpcykf4", "active": true, "timestamp": "now" }
-// ]'
-// bt-beneficiary importdescriptors '[
-// { "desc": "tr(tprv8ZgxMBicQKsPe72C5c3cugP8b7AzEuNjP4NSC17Dkpqk5kaAmsL6FHwPsVxPpURVqbNwdLAbNqi8Cvdq6nycDwYdKHDjDRYcsMzfshimAUq/86\'/1\'/0\'/1/*)#w4ehwx46", "active": true, "timestamp": "now", "internal": true },
-// { "desc": "tr(tprv8ZgxMBicQKsPe72C5c3cugP8b7AzEuNjP4NSC17Dkpqk5kaAmsL6FHwPsVxPpURVqbNwdLAbNqi8Cvdq6nycDwYdKHDjDRYcsMzfshimAUq/86\'/1\'/0\'/0/*)#lpuknn9z", "active": true, "timestamp": "now" }
-// ]'
-//
-// The xpriv and derivation path from the imported descriptors
-const BENEFACTOR_XPRIV_STR: &str = "tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7";
-const BENEFICIARY_XPRIV_STR: &str = "tprv8ZgxMBicQKsPe72C5c3cugP8b7AzEuNjP4NSC17Dkpqk5kaAmsL6FHwPsVxPpURVqbNwdLAbNqi8Cvdq6nycDwYdKHDjDRYcsMzfshimAUq";
-const BIP86_DERIVATION_PATH: &str = "m/86'/1'/0'/0/0";
-
-// Step 3 -
-// Run `bt generatetoaddress 103 $(bt-benefactor getnewaddress '' bech32m)` to generate 103 new blocks
-// with block reward being sent to a newly created P2TR address in the `benefactor` wallet.
-// This will leave us with 3 mature UTXOs that can be spent. Each will be used in a different example below.
-
-// Step 4 - Run `bt-benefactor listunspent` to display our three spendable UTXOs. Check that everything is the same as below
-// - otherwise modify it. The txids should be deterministic on regtest:
-
-const UTXO_SCRIPT_PUBKEY: &str =
- "5120be27fa8b1f5278faf82cab8da23e8761f8f9bd5d5ebebbb37e0e12a70d92dd16";
-const UTXO_PUBKEY: &str = "a6ac32163539c16b6b5dbbca01b725b8e8acaa5f821ba42c80e7940062140d19";
-const UTXO_MASTER_FINGERPRINT: &str = "e61b318f";
-const ABSOLUTE_FEES: Amount = Amount::from_sat_u32(1_000);
-
-// UTXO_1 will be used for spending example 1
-const UTXO_1: P2trUtxo = P2trUtxo {
- txid: "a85d89b4666fed622281d3589474aa1f87971b54bd5d9c1899ed2e8e0447cc06",
- vout: 0,
- script_pubkey: UTXO_SCRIPT_PUBKEY,
- pubkey: UTXO_PUBKEY,
- master_fingerprint: UTXO_MASTER_FINGERPRINT,
- amount: Amount::FIFTY_BTC,
- derivation_path: BIP86_DERIVATION_PATH,
-};
-
-// UTXO_2 will be used for spending example 2
-const UTXO_2: P2trUtxo = P2trUtxo {
- txid: "6f1c1df5862a67f4b6d1cde9a87e3c441b483ba6a140fbec2815f03aa3a5309d",
- vout: 0,
- script_pubkey: UTXO_SCRIPT_PUBKEY,
- pubkey: UTXO_PUBKEY,
- master_fingerprint: UTXO_MASTER_FINGERPRINT,
- amount: Amount::FIFTY_BTC,
- derivation_path: BIP86_DERIVATION_PATH,
-};
-
-// UTXO_3 will be used for spending example 3
-const UTXO_3: P2trUtxo = P2trUtxo {
- txid: "9795fed5aedca219244a396dfd7bce55c851274418383c3ab43530e3f74e5dcc",
- vout: 0,
- script_pubkey: UTXO_SCRIPT_PUBKEY,
- pubkey: UTXO_PUBKEY,
- master_fingerprint: UTXO_MASTER_FINGERPRINT,
- amount: Amount::FIFTY_BTC,
- derivation_path: BIP86_DERIVATION_PATH,
-};
-
-use std::collections::BTreeMap;
-
-use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv, Xpub};
-use bitcoin::consensus::encode;
-use bitcoin::ext::*;
-use bitcoin::key::{Keypair, PrivateKey, TapTweak, XOnlyPublicKey};
-use bitcoin::opcodes::all::{OP_CHECKSIG, OP_CLTV, OP_DROP};
-use bitcoin::psbt::{self, Input, Output, Psbt, PsbtSighashType};
-use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
-use bitcoin::taproot::{self, LeafVersion, TapLeafHash, TaprootBuilder, TaprootSpendInfo};
-use bitcoin::{
- absolute, script, transaction, Address, Amount, Network, OutPoint, ScriptPubKeyBuf,
- ScriptSigBuf, TapScriptBuf, Transaction, TxIn, TxOut, Witness,
-};
-
-fn main() -> Result<(), Box<dyn std::error::Error>> {
- println!("\n----------------");
- println!("\nSTART EXAMPLE 1 - P2TR with a BIP-0086 commitment, signed with internal key\n");
-
- // Just some addresses for outputs from our wallets. Not really important.
- let to_address = "bcrt1p0p3rvwww0v9znrclp00uneq8ytre9kj922v8fxhnezm3mgsmn9usdxaefc"
- .parse::<Address<_>>()?
- .require_network(Network::Regtest)?;
- let change_address = "bcrt1pz449kexzydh2kaypatup5ultru3ej284t6eguhnkn6wkhswt0l7q3a7j76"
- .parse::<Address<_>>()?
- .require_network(Network::Regtest)?;
- let amount_to_send = Amount::ONE_BTC;
- let change_amount = UTXO_1
- .amount
- .checked_sub(amount_to_send)
- .and_then(|x| x.checked_sub(ABSOLUTE_FEES))
- .ok_or("fees more than input amount!")?;
-
- let tx_hex_string = encode::serialize_hex(&generate_bip86_key_spend_tx(
- // The master extended private key from the descriptor in step 4
- BENEFACTOR_XPRIV_STR.parse::<Xpriv>()?,
- // Set these fields with valid data for the UTXO from step 5 above
- UTXO_1,
- vec![
- TxOut { amount: amount_to_send, script_pubkey: to_address.script_pubkey() },
- TxOut { amount: change_amount, script_pubkey: change_address.script_pubkey() },
- ],
- )?);
- println!(
- "\nYou should now be able to broadcast the following transaction: \n\n{tx_hex_string}"
- );
-
- println!("\nEND EXAMPLE 1\n");
- println!("----------------\n");
-
- println!("START EXAMPLE 2 - Script path spending of inheritance UTXO\n");
-
- {
- let beneficiary = BeneficiaryWallet::new(BENEFICIARY_XPRIV_STR.parse::<Xpriv>()?)?;
-
- let mut benefactor = BenefactorWallet::new(
- BENEFACTOR_XPRIV_STR.parse::<Xpriv>()?,
- beneficiary.master_xpub(),
- )?;
- let (tx, psbt) = benefactor.create_inheritance_funding_tx(
- absolute::LockTime::from_height(1000).unwrap(),
- UTXO_2,
- )?;
- let tx_hex = encode::serialize_hex(&tx);
-
- println!("Inheritance funding tx hex:\n\n{tx_hex}");
- // You can now broadcast the transaction hex:
- // bt sendrawtransaction ...
- //
- // And mine a block to confirm the transaction:
- // bt generatetoaddress 1 $(bt-benefactor getnewaddress '' 'bech32m')
-
- let spending_tx = beneficiary.spend_inheritance(
- psbt,
- absolute::LockTime::from_height(1000).unwrap(),
- to_address,
- )?;
- let spending_tx_hex = encode::serialize_hex(&spending_tx);
- println!("\nInheritance spending tx hex:\n\n{spending_tx_hex}");
- // If you try to broadcast now, the transaction will be rejected as it is timelocked.
- // First mine 900 blocks so we're sure we are over the 1000 block locktime:
- // bt generatetoaddress 900 $(bt-benefactor getnewaddress '' 'bech32m')
- // Then broadcast the transaction with `bt sendrawtransaction ...`
- }
-
- println!("\nEND EXAMPLE 2\n");
- println!("----------------\n");
-
- println!("START EXAMPLE 3 - Key path spending of inheritance UTXO\n");
-
- {
- let beneficiary = BeneficiaryWallet::new(BENEFICIARY_XPRIV_STR.parse::<Xpriv>()?)?;
-
- let mut benefactor = BenefactorWallet::new(
- BENEFACTOR_XPRIV_STR.parse::<Xpriv>()?,
- beneficiary.master_xpub(),
- )?;
- let (tx, _) = benefactor.create_inheritance_funding_tx(
- absolute::LockTime::from_height(2000).unwrap(),
- UTXO_3,
- )?;
- let tx_hex = encode::serialize_hex(&tx);
-
- println!("Inheritance funding tx hex:\n\n{tx_hex}");
- // You can now broadcast the transaction hex:
- // bt sendrawtransaction ...
- //
- // And mine a block to confirm the transaction:
- // bt generatetoaddress 1 $(bt-benefactor getnewaddress '' 'bech32m')
-
- // At some point we may want to extend the locktime further into the future for the beneficiary.
- // We can do this by "refreshing" the inheritance transaction as the benefactor. This effectively
- // spends the inheritance transaction via the key path of the Taproot output, and is not encumbered
- // by the timelock so we can spend it immediately. We set up a new output similar to the first with
- // a locktime that is 'locktime_delta' blocks greater.
- let (tx, _) = benefactor.refresh_tx(1000)?;
- let tx_hex = encode::serialize_hex(&tx);
-
- println!("\nRefreshed inheritance tx hex:\n\n{tx_hex}\n");
-
- println!("\nEND EXAMPLE 3\n");
- println!("----------------\n");
- }
-
- Ok(())
-}
-
-struct P2trUtxo<'a> {
- txid: &'a str,
- vout: u32,
- script_pubkey: &'a str,
- pubkey: &'a str,
- master_fingerprint: &'a str,
- amount: Amount,
- derivation_path: &'a str,
-}
-
-#[allow(clippy::single_element_loop)]
-fn generate_bip86_key_spend_tx(
- master_xpriv: Xpriv,
- input_utxo: P2trUtxo,
- outputs: Vec<TxOut>,
-) -> Result<Transaction, Box<dyn std::error::Error>> {
- let from_amount = input_utxo.amount;
- let input_pubkey = input_utxo.pubkey.parse::<XOnlyPublicKey>()?;
-
- // CREATOR + UPDATER
- let tx1 = Transaction {
- version: transaction::Version::TWO,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![TxIn {
- previous_output: OutPoint { txid: input_utxo.txid.parse()?, vout: input_utxo.vout },
- script_sig: ScriptSigBuf::new(),
- sequence: bitcoin::Sequence(0xFFFFFFFF), // Ignore nSequence.
- witness: Witness::default(),
- }],
- outputs,
- };
- let mut psbt = Psbt::from_unsigned_tx(tx1)?;
-
- let mut origins = BTreeMap::new();
- origins.insert(
- input_pubkey,
- (
- vec![],
- (
- input_utxo.master_fingerprint.parse::<Fingerprint>()?,
- input_utxo.derivation_path.parse::<DerivationPath>()?,
- ),
- ),
- );
-
- let mut input = Input {
- witness_utxo: {
- let script_pubkey =
- ScriptPubKeyBuf::from_hex_no_length_prefix(input_utxo.script_pubkey)
- .expect("failed to parse input utxo scriptPubkey");
- Some(TxOut { amount: from_amount, script_pubkey })
- },
- tap_key_origins: origins,
- ..Default::default()
- };
- let ty = "SIGHASH_ALL".parse::<PsbtSighashType>()?;
- input.sighash_type = Some(ty);
- input.tap_internal_key = Some(input_pubkey);
- psbt.inputs = vec![input];
-
- // The `Prevouts::All` array is used to create the sighash to sign for each input in the
- // `psbt.inputs` array, as such it must be the same length and in the same order as the inputs.
- let mut input_txouts = Vec::<TxOut>::new();
- for input in [&input_utxo].iter() {
- input_txouts.push(TxOut {
- amount: input.amount,
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix(input.script_pubkey)?,
- });
- }
-
- // SIGNER
- let unsigned_tx = psbt.unsigned_tx.clone();
- psbt.inputs.iter_mut().enumerate().try_for_each::<_, Result<(), Box<dyn std::error::Error>>>(
- |(vout, input)| {
- let sighash_type = input
- .sighash_type
- .and_then(|psbt_sighash_type| psbt_sighash_type.taproot_hash_ty().ok())
- .unwrap_or(TapSighashType::All);
- let hash = SighashCache::new(&unsigned_tx).taproot_key_spend_signature_hash(
- vout,
- &sighash::Prevouts::All(input_txouts.as_slice()),
- sighash_type,
- )?;
-
- let (_, (_, derivation_path)) = input
- .tap_key_origins
- .get(&input.tap_internal_key.ok_or("internal key missing in PSBT")?)
- .ok_or("missing Taproot key origin")?;
-
- let secret_key = master_xpriv.derive_xpriv(derivation_path)?.to_private_key();
- sign_psbt_taproot(
- &secret_key,
- input.tap_internal_key.unwrap(),
- None,
- input,
- hash,
- sighash_type,
- );
-
- Ok(())
- },
- )?;
-
- // FINALIZER
- psbt.inputs.iter_mut().for_each(|input| {
- let mut script_witness: Witness = Witness::new();
- script_witness.push(input.tap_key_sig.unwrap().to_vec());
- input.final_script_witness = Some(script_witness);
-
- // Clear all the data fields as per the spec.
- input.partial_sigs = BTreeMap::new();
- input.sighash_type = None;
- input.redeem_script = None;
- input.witness_script = None;
- input.bip32_derivation = BTreeMap::new();
- });
-
- // EXTRACTOR
- let tx = psbt.extract_tx_unchecked_fee_rate();
- tx.verify(|_| {
- Some(TxOut {
- amount: from_amount,
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix(input_utxo.script_pubkey)
- .unwrap(),
- })
- })
- .expect("failed to verify transaction");
-
- Ok(tx)
-}
-
-/// A wallet that allows creating and spending from an inheritance directly via the key path for purposes
-/// of refreshing the inheritance timelock or changing other spending conditions.
-struct BenefactorWallet {
- master_xpriv: Xpriv,
- beneficiary_xpub: Xpub,
- current_spend_info: Option<TaprootSpendInfo>,
- next_psbt: Option<Psbt>,
- next: ChildNumber,
-}
-
-impl BenefactorWallet {
- fn new(
- master_xpriv: Xpriv,
- beneficiary_xpub: Xpub,
- ) -> Result<Self, Box<dyn std::error::Error>> {
- Ok(Self {
- master_xpriv,
- beneficiary_xpub,
- current_spend_info: None,
- next_psbt: None,
- next: ChildNumber::ZERO_NORMAL,
- })
- }
-
- fn time_lock_script(
- locktime: absolute::LockTime,
- beneficiary_key: XOnlyPublicKey,
- ) -> TapScriptBuf {
- script::Builder::new()
- .push_lock_time(locktime)
- .push_opcode(OP_CLTV)
- .push_opcode(OP_DROP)
- .push_x_only_key(beneficiary_key)
- .push_opcode(OP_CHECKSIG)
- .into_script()
- }
-
- fn create_inheritance_funding_tx(
- &mut self,
- lock_time: absolute::LockTime,
- input_utxo: P2trUtxo,
- ) -> Result<(Transaction, Psbt), Box<dyn std::error::Error>> {
- if let ChildNumber::Normal { index } = self.next {
- if index > 0 && self.current_spend_info.is_some() {
- return Err(
- "transaction already exists, use refresh_tx to refresh the timelock".into()
- );
- }
- }
- // We use some other derivation path in this example for our inheritance protocol. The important thing is to ensure
- // that we use an unhardened path so we can make use of xpubs.
- let derivation_path = format!("101/1/0/0/{}", self.next).parse::<DerivationPath>()?;
- let internal_keypair = self
- .master_xpriv
- .derive_xpriv(&derivation_path)
- .expect("derivation path is short")
- .to_keypair();
- let beneficiary_key =
- self.beneficiary_xpub.derive_xpub(&derivation_path)?.to_x_only_public_key();
-
- // Build up the leaf script and combine with internal key into a Taproot commitment
- let script = Self::time_lock_script(lock_time, beneficiary_key);
- let leaf_hash = script.tapscript_leaf_hash();
-
- let taproot_spend_info = TaprootBuilder::new()
- .add_leaf(0, script.clone())?
- .finalize(internal_keypair.to_x_only_public_key())
- .expect("should be finalizable");
- self.current_spend_info = Some(taproot_spend_info.clone());
- let script_pubkey = ScriptPubKeyBuf::new_p2tr(
- taproot_spend_info.internal_key(),
- taproot_spend_info.merkle_root(),
- );
- let amount = (input_utxo.amount - ABSOLUTE_FEES)
- .expect("ABSOLUTE_FEES must be set below input amount");
-
- // Spend a normal BIP-0086-like output as an input in our inheritance funding transaction
- let tx = generate_bip86_key_spend_tx(
- self.master_xpriv,
- input_utxo,
- vec![TxOut { script_pubkey: script_pubkey.clone(), amount }],
- )?;
-
- // CREATOR + UPDATER
- let next_tx = Transaction {
- version: transaction::Version::TWO,
- lock_time,
- inputs: vec![TxIn {
- previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
- script_sig: ScriptSigBuf::new(),
- sequence: bitcoin::Sequence(0xFFFFFFFD), // enable locktime and opt-in RBF
- witness: Witness::default(),
- }],
- outputs: vec![],
- };
- let mut next_psbt = Psbt::from_unsigned_tx(next_tx)?;
- let mut origins = BTreeMap::new();
- origins.insert(
- beneficiary_key,
- (vec![leaf_hash], (self.beneficiary_xpub.fingerprint(), derivation_path.clone())),
- );
- origins.insert(
- internal_keypair.to_x_only_public_key(),
- (vec![], (self.master_xpriv.fingerprint(), derivation_path)),
- );
- let ty = "SIGHASH_ALL".parse::<PsbtSighashType>()?;
- let mut tap_scripts = BTreeMap::new();
- tap_scripts.insert(
- taproot_spend_info.control_block(&(script.clone(), LeafVersion::TapScript)).unwrap(),
- (script, LeafVersion::TapScript),
- );
-
- let input = Input {
- witness_utxo: { Some(TxOut { amount, script_pubkey }) },
- tap_key_origins: origins,
- tap_merkle_root: taproot_spend_info.merkle_root(),
- sighash_type: Some(ty),
- tap_internal_key: Some(internal_keypair.to_x_only_public_key()),
- tap_scripts,
- ..Default::default()
- };
-
- next_psbt.inputs = vec![input];
- self.next_psbt = Some(next_psbt.clone());
-
- self.next.increment()?;
- Ok((tx, next_psbt))
- }
-
- fn refresh_tx(
- &mut self,
- lock_time_delta: u32,
- ) -> Result<(Transaction, Psbt), Box<dyn std::error::Error>> {
- if let Some(ref spend_info) = self.current_spend_info.clone() {
- let mut psbt = self.next_psbt.clone().expect("should have next_psbt");
- let input = &mut psbt.inputs[0];
- let input_amount = input.witness_utxo.as_ref().unwrap().amount;
- let output_amount = (input_amount - ABSOLUTE_FEES).into_result()?;
-
- // We use some other derivation path in this example for our inheritance protocol. The important thing is to ensure
- // that we use an unhardened path so we can make use of xpubs.
- let new_derivation_path =
- format!("101/1/0/0/{}", self.next).parse::<DerivationPath>()?;
- let new_internal_keypair = self
- .master_xpriv
- .derive_xpriv(&new_derivation_path)
- .expect("derivation path is short")
- .to_keypair();
- let beneficiary_key =
- self.beneficiary_xpub.derive_xpub(&new_derivation_path)?.to_x_only_public_key();
-
- // Build up the leaf script and combine with internal key into a Taproot commitment
- let lock_time = absolute::LockTime::from_height(
- psbt.unsigned_tx.lock_time.to_consensus_u32() + lock_time_delta,
- )
- .unwrap();
- let script = Self::time_lock_script(lock_time, beneficiary_key);
- let leaf_hash = script.tapscript_leaf_hash();
-
- let taproot_spend_info = TaprootBuilder::new()
- .add_leaf(0, script.clone())?
- .finalize(new_internal_keypair.to_x_only_public_key())
- .expect("should be finalizable");
- self.current_spend_info = Some(taproot_spend_info.clone());
- let prevout_script_pubkey = input.witness_utxo.as_ref().unwrap().script_pubkey.clone();
- let output_script_pubkey = ScriptPubKeyBuf::new_p2tr(
- taproot_spend_info.internal_key(),
- taproot_spend_info.merkle_root(),
- );
-
- psbt.unsigned_tx.outputs =
- vec![TxOut { script_pubkey: output_script_pubkey.clone(), amount: output_amount }];
- psbt.outputs = vec![Output::default()];
- psbt.unsigned_tx.lock_time = absolute::LockTime::ZERO;
-
- let sighash_type = input
- .sighash_type
- .and_then(|psbt_sighash_type| psbt_sighash_type.taproot_hash_ty().ok())
- .unwrap_or(TapSighashType::All);
- let hash = SighashCache::new(&psbt.unsigned_tx).taproot_key_spend_signature_hash(
- 0,
- &sighash::Prevouts::All(&[TxOut {
- amount: input_amount,
- script_pubkey: prevout_script_pubkey,
- }]),
- sighash_type,
- )?;
-
- {
- let (_, (_, derivation_path)) = input
- .tap_key_origins
- .get(&input.tap_internal_key.ok_or("internal key missing in PSBT")?)
- .ok_or("missing Taproot key origin")?;
- let secret_key = self
- .master_xpriv
- .derive_xpriv(derivation_path)
- .expect("derivation path is short")
- .to_private_key();
- sign_psbt_taproot(
- &secret_key,
- spend_info.internal_key(),
- None,
- input,
- hash,
- sighash_type,
- );
- }
-
- // FINALIZER
- psbt.inputs.iter_mut().for_each(|input| {
- let mut script_witness: Witness = Witness::new();
- script_witness.push(input.tap_key_sig.unwrap().to_vec());
- input.final_script_witness = Some(script_witness);
-
- // Clear all the data fields as per the spec.
- input.partial_sigs = BTreeMap::new();
- input.sighash_type = None;
- input.redeem_script = None;
- input.witness_script = None;
- input.bip32_derivation = BTreeMap::new();
- });
-
- // EXTRACTOR
- let tx = psbt.extract_tx_unchecked_fee_rate();
- tx.verify(|_| {
- Some(TxOut { amount: input_amount, script_pubkey: output_script_pubkey.clone() })
- })
- .expect("failed to verify transaction");
-
- let next_tx = Transaction {
- version: transaction::Version::TWO,
- lock_time,
- inputs: vec![TxIn {
- previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
- script_sig: ScriptSigBuf::new(),
- sequence: bitcoin::Sequence(0xFFFFFFFD), // enable locktime and opt-in RBF
- witness: Witness::default(),
- }],
- outputs: vec![],
- };
- let mut next_psbt = Psbt::from_unsigned_tx(next_tx)?;
- let mut origins = BTreeMap::new();
- origins.insert(
- beneficiary_key,
- (vec![leaf_hash], (self.beneficiary_xpub.fingerprint(), new_derivation_path)),
- );
- let ty = "SIGHASH_ALL".parse::<PsbtSighashType>()?;
- let mut tap_scripts = BTreeMap::new();
- tap_scripts.insert(
- taproot_spend_info
- .control_block(&(script.clone(), LeafVersion::TapScript))
- .unwrap(),
- (script, LeafVersion::TapScript),
- );
-
- let input = Input {
- witness_utxo: {
- let script_pubkey = output_script_pubkey;
- let amount = output_amount;
-
- Some(TxOut { amount, script_pubkey })
- },
- tap_key_origins: origins,
- tap_merkle_root: taproot_spend_info.merkle_root(),
- sighash_type: Some(ty),
- tap_internal_key: Some(new_internal_keypair.to_x_only_public_key()),
- tap_scripts,
- ..Default::default()
- };
-
- next_psbt.inputs = vec![input];
- self.next_psbt = Some(next_psbt.clone());
-
- self.next.increment()?;
- Ok((tx, next_psbt))
- } else {
- Err("no current_spend_info available. Create an inheritance tx first.".into())
- }
- }
-}
-
-/// A wallet that allows spending from an inheritance locked to a P2TR UTXO via a script path
-/// after some expiry using CLTV.
-struct BeneficiaryWallet {
- master_xpriv: Xpriv,
-}
-
-impl BeneficiaryWallet {
- fn new(master_xpriv: Xpriv) -> Result<Self, Box<dyn std::error::Error>> {
- Ok(Self { master_xpriv })
- }
-
- fn master_xpub(&self) -> Xpub { Xpub::from_xpriv(&self.master_xpriv) }
-
- fn spend_inheritance(
- &self,
- mut psbt: Psbt,
- lock_time: absolute::LockTime,
- to_address: Address,
- ) -> Result<Transaction, Box<dyn std::error::Error>> {
- let input_amount = psbt.inputs[0].witness_utxo.as_ref().unwrap().amount;
- let input_script_pubkey =
- psbt.inputs[0].witness_utxo.as_ref().unwrap().script_pubkey.clone();
- psbt.unsigned_tx.lock_time = lock_time;
- psbt.unsigned_tx.outputs = vec![TxOut {
- script_pubkey: to_address.script_pubkey(),
- amount: (input_amount - ABSOLUTE_FEES)
- .expect("ABSOLUTE_FEES must be set below input amount"),
- }];
- psbt.outputs = vec![Output::default()];
- let unsigned_tx = psbt.unsigned_tx.clone();
-
- // SIGNER
- for (x_only_pubkey, (leaf_hashes, (_, derivation_path))) in
- &psbt.inputs[0].tap_key_origins.clone()
- {
- let secret_key = self.master_xpriv.derive_xpriv(derivation_path)?.to_private_key();
- for lh in leaf_hashes {
- let sighash_type = TapSighashType::All;
- let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash(
- 0,
- &sighash::Prevouts::All(&[TxOut {
- amount: input_amount,
- script_pubkey: input_script_pubkey.clone(),
- }]),
- *lh,
- sighash_type,
- )?;
- sign_psbt_taproot(
- &secret_key,
- *x_only_pubkey,
- Some(*lh),
- &mut psbt.inputs[0],
- hash,
- sighash_type,
- );
- }
- }
-
- // FINALIZER
- psbt.inputs.iter_mut().for_each(|input| {
- let mut script_witness: Witness = Witness::new();
- for (control_block, (script, _)) in input.tap_scripts.iter() {
- // Extract 32-byte script pushes that validate as pubkeys, preserving script order
- let mut pubkeys_in_order = Vec::new();
- for instruction in script.instructions().flatten() {
- if let script::Instruction::PushBytes(push_bytes) = instruction {
- if push_bytes.len() == 32 {
- let candidate_bytes: [u8; 32] =
- push_bytes.as_bytes().try_into().expect("length checked above");
- if let Ok(pubkey) = XOnlyPublicKey::from_byte_array(&candidate_bytes) {
- pubkeys_in_order.push(pubkey);
- }
- }
- }
- }
-
- let leaf_hash = script.tapscript_leaf_hash();
-
- // Push signatures in reverse order
- for pubkey in pubkeys_in_order.iter().rev() {
- if let Some(sig) = input.tap_script_sigs.get(&(*pubkey, leaf_hash)) {
- script_witness.push(sig.to_vec());
- }
- }
-
- // Push script and control block
- script_witness.push(script.to_vec());
- script_witness.push(control_block.serialize());
- }
- input.final_script_witness = Some(script_witness);
-
- // Clear all the data fields as per the spec.
- input.partial_sigs = BTreeMap::new();
- input.sighash_type = None;
- input.redeem_script = None;
- input.witness_script = None;
- input.bip32_derivation = BTreeMap::new();
- input.tap_script_sigs = BTreeMap::new();
- input.tap_scripts = BTreeMap::new();
- input.tap_key_sig = None;
- });
-
- // EXTRACTOR
- let tx = psbt.extract_tx_unchecked_fee_rate();
- tx.verify(|_| {
- Some(TxOut { amount: input_amount, script_pubkey: input_script_pubkey.clone() })
- })
- .expect("failed to verify transaction");
-
- Ok(tx)
- }
-}
-
-// Lifted and modified from BDK at https://github.com/bitcoindevkit/bdk/blob/8fbe40a9181cc9e22cabfc04d57dac5d459da87d/src/wallet/signer.rs#L469-L503
-
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-// Calling this with `leaf_hash` = `None` will sign for key-spend
-fn sign_psbt_taproot(
- secret_key: &PrivateKey,
- pubkey: XOnlyPublicKey,
- leaf_hash: Option<TapLeafHash>,
- psbt_input: &mut psbt::Input,
- hash: TapSighash,
- sighash_type: TapSighashType,
-) {
- let keypair = Keypair::from_private_key(secret_key);
- let keypair = match leaf_hash {
- None => keypair.tap_tweak(psbt_input.tap_merkle_root).into_keypair(),
- Some(_) => keypair, // no tweak for script spend
- };
-
- let signature = keypair.raw_bip340_sign(&hash.to_byte_array());
-
- let final_signature = taproot::Signature { signature, sighash_type };
-
- if let Some(lh) = leaf_hash {
- psbt_input.tap_script_sigs.insert((pubkey, lh), final_signature);
- } else {
- psbt_input.tap_key_sig = Some(final_signature);
- }
-}
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index aa786963..69b1205b 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -128,7 +128,6 @@ pub mod merkle_tree;
pub mod network;
pub mod policy;
pub mod pow;
-pub mod psbt;
pub mod sign_message;
pub mod taproot;
@@ -191,7 +190,6 @@ pub use crate::{
network::params::{self, Params},
network::{Network, NetworkKind, TestnetVersion},
pow::{Target, Work},
- psbt::Psbt,
sighash::{EcdsaSighashType, TapSighashType},
taproot::{TapBranchTag, TapLeafHash, TapLeafTag, TapNodeHash, TapTweakHash, TapTweakTag},
};
diff --git a/bitcoin/src/psbt/error.rs b/bitcoin/src/psbt/error.rs
deleted file mode 100644
index 5e178c75..00000000
--- a/bitcoin/src/psbt/error.rs
+++ /dev/null
@@ -1,272 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-use core::convert::Infallible;
-use core::fmt;
-
-use internals::write_err;
-
-use crate::bip32::Xpub;
-use crate::consensus::encode;
-use crate::prelude::Box;
-use crate::psbt::raw;
-use crate::{ecdsa, key, taproot, OutPoint, Transaction, Txid};
-
-/// Enum for marking psbt hash error.
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
-pub enum PsbtHash {
- Ripemd,
- Sha256,
- Hash160,
- Hash256,
-}
-/// Ways that a Partially Signed Transaction might fail.
-#[derive(Debug)]
-#[non_exhaustive]
-pub enum Error {
- /// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most
- /// significant byte order.
- InvalidMagic,
- /// Missing both the witness and non-witness utxo.
- MissingUtxo,
- /// The separator for a PSBT must be `0xff`.
- InvalidSeparator,
- /// Returned when output index is out of bounds in relation to the output in non-witness UTXO.
- PsbtUtxoOutOfbounds,
- /// 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),
- /// The scriptSigs for the unsigned transaction must be empty.
- UnsignedTxHasScriptSigs,
- /// The scriptWitnesses for the unsigned transaction must be empty.
- UnsignedTxHasScriptWitnesses,
- /// A PSBT must have an unsigned transaction.
- MustHaveUnsignedTx,
- /// Signals that there are no more key-value pairs in a key-value map.
- NoMorePairs,
- /// Attempting to combine with a PSBT describing a different unsigned
- /// transaction.
- UnexpectedUnsignedTx {
- /// Expected
- expected: Box<Transaction>,
- /// Actual
- actual: Box<Transaction>,
- },
- /// Unable to parse as a standard sighash type.
- NonStandardSighashType(u32),
- /// Invalid hash when parsing slice.
- InvalidHash(core::array::TryFromSliceError),
- /// The pre-image must hash to the corresponding psbt hash
- InvalidPreimageHashPair {
- /// Hash-type
- hash_type: PsbtHash,
- /// Pre-image
- preimage: Box<[u8]>,
- /// Hash value
- hash: Box<[u8]>,
- },
- /// Conflicting data during combine procedure:
- /// global extended public key has inconsistent key sources
- CombineInconsistentKeySources(Box<Xpub>),
- /// Serialization error in bitcoin consensus-encoded structures
- ConsensusEncoding(encode::Error),
- /// Deserialization error in bitcoin consensus-encoded structures.
- ConsensusDeserialize(encode::DeserializeError),
- /// Error parsing bitcoin consensus-encoded object.
- ConsensusParse(encode::ParseError),
- /// Negative fee
- NegativeFee,
- /// Integer overflow in fee calculation
- FeeOverflow,
- /// Non-witness UTXO (which is a complete transaction) has `Txid` that
- /// does not match the transaction input.
- IncorrectNonWitnessUtxo {
- /// The index of the input in question.
- index: usize,
- /// The outpoint of the input, as it appears in the unsigned transaction.
- input_outpoint: OutPoint,
- /// The [`Txid`] of the non-witness UTXO.
- non_witness_utxo_txid: Txid,
- },
- /// Non-witness UTXO does not have enough outputs for the `vout` specified
- /// in the transaction input.
- NonWitnessUtxoOutOfBounds {
- /// The index of the input in question.
- index: usize,
- /// The vout of the input, as it appears in the unsigned transaction.
- vout: u32,
- /// The number of outputs in the non-witness UTXO.
- non_witness_utxo_output_count: usize,
- },
- /// Parsing error indicating invalid public keys
- InvalidPublicKey(key::FromSliceError),
- /// Parsing error indicating invalid secp256k1 public keys
- InvalidSecp256k1PublicKey(secp256k1::Error),
- /// Parsing error indicating invalid xonly public keys
- InvalidXOnlyPublicKey,
- /// Parsing error indicating invalid ECDSA signatures
- InvalidEcdsaSignature(ecdsa::DecodeError),
- /// Parsing error indicating invalid Taproot signatures
- InvalidTaprootSignature(taproot::SigFromSliceError),
- /// Parsing error indicating invalid control block
- InvalidControlBlock,
- /// Parsing error indicating invalid leaf version
- InvalidLeafVersion,
- /// Parsing error indicating a Taproot error
- Taproot(&'static str),
- /// Taproot tree deserialization error
- TapTree(taproot::IncompleteBuilderError),
- /// Error related to an xpub key
- XPubKey(&'static str),
- /// Error related to PSBT version
- Version(&'static str),
- /// PSBT data is not consumed entirely
- PartialDataConsumption,
- /// I/O error.
- Io(io::Error),
-}
-
-impl From<Infallible> for Error {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::InvalidMagic => f.write_str("invalid magic"),
- Self::MissingUtxo => f.write_str("UTXO information is not present in PSBT"),
- Self::InvalidSeparator => f.write_str("invalid separator"),
- Self::PsbtUtxoOutOfbounds =>
- f.write_str("output index is out of bounds of non witness script output array"),
- Self::InvalidKey(ref rkey) => write!(f, "invalid key: {}", rkey),
- Self::InvalidProprietaryKey =>
- write!(f, "non-proprietary key type found when proprietary key was expected"),
- Self::DuplicateKey(ref rkey) => write!(f, "duplicate key: {}", rkey),
- Self::UnsignedTxHasScriptSigs =>
- f.write_str("the unsigned transaction has script sigs"),
- Self::UnsignedTxHasScriptWitnesses =>
- f.write_str("the unsigned transaction has script witnesses"),
- Self::MustHaveUnsignedTx =>
- f.write_str("partially signed transactions must have an unsigned transaction"),
- Self::NoMorePairs => f.write_str("no more key-value pairs for this psbt map"),
- Self::UnexpectedUnsignedTx { expected: ref e, actual: ref a } => write!(
- f,
- "different unsigned transaction: expected {}, actual {}",
- e.compute_txid(),
- a.compute_txid()
- ),
- Self::NonStandardSighashType(ref sht) =>
- write!(f, "non-standard sighash type: {}", sht),
- Self::InvalidHash(ref e) => write_err!(f, "invalid hash when parsing slice"; e),
- Self::InvalidPreimageHashPair { ref preimage, ref hash, ref hash_type } => {
- // directly using debug forms of psbthash enums
- write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash)
- }
- Self::CombineInconsistentKeySources(ref s) => {
- write!(f, "combine conflict: {}", s)
- }
- Self::ConsensusEncoding(ref e) => write_err!(f, "bitcoin consensus encoding error"; e),
- Self::ConsensusDeserialize(ref e) =>
- write_err!(f, "bitcoin consensus deserialization error"; e),
- Self::ConsensusParse(ref e) =>
- write_err!(f, "error parsing bitcoin consensus encoded object"; e),
- Self::NegativeFee => f.write_str("PSBT has a negative fee which is not allowed"),
- Self::FeeOverflow => f.write_str("integer overflow in fee calculation"),
- Self::IncorrectNonWitnessUtxo { index, input_outpoint, non_witness_utxo_txid } => {
- write!(
- f,
- "non-witness utxo txid is {}, which does not match input {}'s outpoint {}",
- non_witness_utxo_txid, index, input_outpoint
- )
- }
- Self::NonWitnessUtxoOutOfBounds { index, vout, non_witness_utxo_output_count } => {
- write!(
- f,
- "input {} references vout {}, but non-witness UTXO only has {} outputs",
- index, vout, non_witness_utxo_output_count
- )
- }
- Self::InvalidPublicKey(ref e) => write_err!(f, "invalid public key"; e),
- Self::InvalidSecp256k1PublicKey(ref e) =>
- write_err!(f, "invalid secp256k1 public key"; e),
- Self::InvalidXOnlyPublicKey => f.write_str("invalid xonly public key"),
- Self::InvalidEcdsaSignature(ref e) => write_err!(f, "invalid ECDSA signature"; e),
- Self::InvalidTaprootSignature(ref e) => write_err!(f, "invalid Taproot signature"; e),
- Self::InvalidControlBlock => f.write_str("invalid control block"),
- Self::InvalidLeafVersion => f.write_str("invalid leaf version"),
- Self::Taproot(s) => write!(f, "Taproot error - {}", s),
- Self::TapTree(ref e) => write_err!(f, "Taproot tree error"; e),
- Self::XPubKey(s) => write!(f, "xpub key error - {}", s),
- Self::Version(s) => write!(f, "version error {}", s),
- Self::PartialDataConsumption =>
- f.write_str("data not consumed entirely when explicitly deserializing"),
- Self::Io(ref e) => write_err!(f, "I/O error"; e),
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for Error {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Self::InvalidHash(ref e) => Some(e),
- Self::ConsensusEncoding(ref e) => Some(e),
- Self::ConsensusDeserialize(ref e) => Some(e),
- Self::ConsensusParse(ref e) => Some(e),
- Self::Io(ref e) => Some(e),
- Self::InvalidMagic
- | Self::MissingUtxo
- | Self::InvalidSeparator
- | Self::PsbtUtxoOutOfbounds
- | Self::InvalidKey(_)
- | Self::InvalidProprietaryKey
- | Self::DuplicateKey(_)
- | Self::UnsignedTxHasScriptSigs
- | Self::UnsignedTxHasScriptWitnesses
- | Self::MustHaveUnsignedTx
- | Self::NoMorePairs
- | Self::UnexpectedUnsignedTx { .. }
- | Self::NonStandardSighashType(_)
- | Self::InvalidPreimageHashPair { .. }
- | Self::CombineInconsistentKeySources(_)
- | Self::NegativeFee
- | Self::FeeOverflow
- | Self::IncorrectNonWitnessUtxo { .. }
- | Self::NonWitnessUtxoOutOfBounds { .. }
- | Self::InvalidPublicKey(_)
- | Self::InvalidSecp256k1PublicKey(_)
- | Self::InvalidXOnlyPublicKey
- | Self::InvalidEcdsaSignature(_)
- | Self::InvalidTaprootSignature(_)
- | Self::InvalidControlBlock
- | Self::InvalidLeafVersion
- | Self::Taproot(_)
- | Self::TapTree(_)
- | Self::XPubKey(_)
- | Self::Version(_)
- | Self::PartialDataConsumption => None,
- }
- }
-}
-
-impl From<core::array::TryFromSliceError> for Error {
- fn from(e: core::array::TryFromSliceError) -> Self { Self::InvalidHash(e) }
-}
-
-impl From<encode::Error> for Error {
- fn from(e: encode::Error) -> Self { Self::ConsensusEncoding(e) }
-}
-
-impl From<encode::DeserializeError> for Error {
- fn from(e: encode::DeserializeError) -> Self { Self::ConsensusDeserialize(e) }
-}
-
-impl From<encode::ParseError> for Error {
- fn from(e: encode::ParseError) -> Self { Self::ConsensusParse(e) }
-}
-
-impl From<io::Error> for Error {
- fn from(e: io::Error) -> Self { Self::Io(e) }
-}
diff --git a/bitcoin/src/psbt/macros.rs b/bitcoin/src/psbt/macros.rs
deleted file mode 100644
index 4044a5e5..00000000
--- a/bitcoin/src/psbt/macros.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-#[allow(unused_macros)]
-macro_rules! combine {
- ($thing:ident, $slf:ident, $other:ident) => {
- if let (&None, Some($thing)) = (&$slf.$thing, $other.$thing) {
- $slf.$thing = Some($thing);
- }
- };
-}
-
-macro_rules! impl_psbt_de_serialize {
- ($thing:ty) => {
- impl_psbt_serialize!($thing);
- impl_psbt_deserialize!($thing);
- };
-}
-
-macro_rules! impl_psbt_deserialize {
- ($thing:ty) => {
- impl $crate::psbt::serialize::Deserialize for $thing {
- fn deserialize(bytes: &[u8]) -> core::result::Result<Self, $crate::psbt::Error> {
- $crate::consensus::deserialize(&bytes[..]).map_err(|e| $crate::psbt::Error::from(e))
- }
- }
- };
-}
-
-macro_rules! impl_psbt_serialize {
- ($thing:ty) => {
- impl $crate::psbt::serialize::Serialize for $thing {
- fn serialize(&self) -> $crate::prelude::Vec<u8> { $crate::consensus::serialize(self) }
- }
- };
-}
-
-macro_rules! impl_psbtmap_serialize {
- ($thing:ty) => {
- impl $crate::psbt::serialize::Serialize for $thing {
- fn serialize(&self) -> Vec<u8> { self.serialize_map() }
- }
- };
-}
-
-macro_rules! impl_psbtmap_deserialize {
- ($thing:ty) => {
- impl $crate::psbt::serialize::Deserialize for $thing {
- fn deserialize(bytes: &[u8]) -> core::result::Result<Self, $crate::psbt::Error> {
- let mut decoder = bytes;
- Self::decode(&mut decoder)
- }
- }
- };
-}
-
-macro_rules! impl_psbtmap_decoding {
- ($thing:ty) => {
- impl $thing {
- pub(crate) fn decode<R: $crate::io::BufRead + ?Sized>(
- r: &mut R,
- ) -> core::result::Result<Self, $crate::psbt::Error> {
- let mut rv: Self = core::default::Default::default();
-
- loop {
- match $crate::psbt::raw::Pair::decode(r) {
- Ok(pair) => rv.insert_pair(pair)?,
- Err($crate::psbt::Error::NoMorePairs) => return Ok(rv),
- Err(e) => return Err(e),
- }
- }
- }
- }
- };
-}
-
-macro_rules! impl_psbtmap_ser_de_serialize {
- ($thing:ty) => {
- impl_psbtmap_decoding!($thing);
- impl_psbtmap_serialize!($thing);
- impl_psbtmap_deserialize!($thing);
- };
-}
-
-#[rustfmt::skip]
-macro_rules! impl_psbt_insert_pair {
- ($slf:ident.$unkeyed_name:ident <= <$raw_key:ident: _>|<$raw_value:ident: $unkeyed_value_type:ty>) => {
- if $raw_key.key_data.is_empty() {
- if $slf.$unkeyed_name.is_none() {
- let val: $unkeyed_value_type = $crate::psbt::serialize::Deserialize::deserialize(&$raw_value)?;
- $slf.$unkeyed_name = Some(val)
- } else {
- return Err($crate::psbt::Error::DuplicateKey($raw_key).into());
- }
- } else {
- return Err($crate::psbt::Error::InvalidKey($raw_key).into());
- }
- };
- ($slf:ident.$keyed_name:ident <= <$raw_key:ident: $keyed_key_type:ty>|<$raw_value:ident: $keyed_value_type:ty>) => {
- if !$raw_key.key_data.is_empty() {
- let key_val: $keyed_key_type = $crate::psbt::serialize::Deserialize::deserialize(&$raw_key.key_data)?;
- match $slf.$keyed_name.entry(key_val) {
- $crate::prelude::btree_map::Entry::Vacant(empty_key) => {
- let val: $keyed_value_type = $crate::psbt::serialize::Deserialize::deserialize(&$raw_value)?;
- empty_key.insert(val);
- }
- $crate::prelude::btree_map::Entry::Occupied(_) => return Err($crate::psbt::Error::DuplicateKey($raw_key).into()),
- }
- } else {
- return Err($crate::psbt::Error::InvalidKey($raw_key).into());
- }
- };
-}
-
-#[rustfmt::skip]
-macro_rules! psbt_insert_hash_pair {
- (&mut $slf:ident.$map:ident <= $raw_key:ident|$raw_value:ident|$hash:ident|$hash_type_error:path) => {
- if $raw_key.key_data.is_empty() {
- return Err($crate::psbt::Error::InvalidKey($raw_key));
- }
- let key_val: $hash::Hash = Deserialize::deserialize(&$raw_key.key_data)?;
- match $slf.$map.entry(key_val) {
- btree_map::Entry::Vacant(empty_key) => {
- let val: Vec<u8> = Deserialize::deserialize(&$raw_value)?;
- if $hash::hash(&val) != key_val {
- return Err($crate::psbt::Error::InvalidPreimageHashPair {
- preimage: val.into_boxed_slice(),
- hash: Box::from(key_val.borrow()),
- hash_type: $hash_type_error,
- });
- }
- empty_key.insert(val);
- }
- btree_map::Entry::Occupied(_) => return Err($crate::psbt::Error::DuplicateKey($raw_key)),
- }
- }
-}
-
-#[rustfmt::skip]
-macro_rules! impl_psbt_get_pair {
- ($rv:ident.push($slf:ident.$unkeyed_name:ident, $unkeyed_typeval:ident)) => {
- if let Some(ref $unkeyed_name) = $slf.$unkeyed_name {
- $rv.push($crate::psbt::raw::Pair {
- key: $crate::psbt::raw::Key {
- type_value: $unkeyed_typeval,
- key_data: vec![],
- },
- value: $crate::psbt::serialize::Serialize::serialize($unkeyed_name),
- });
- }
- };
- ($rv:ident.push_map($slf:ident.$keyed_name:ident, $keyed_typeval:ident)) => {
- for (key, val) in &$slf.$keyed_name {
- $rv.push($crate::psbt::raw::Pair {
- key: $crate::psbt::raw::Key {
- type_value: $keyed_typeval,
- key_data: $crate::psbt::serialize::Serialize::serialize(key),
- },
- value: $crate::psbt::serialize::Serialize::serialize(val),
- });
- }
- };
-}
-
-// macros for serde of hashes
-macro_rules! impl_psbt_hash_de_serialize {
- ($hash_type:ty) => {
- impl_psbt_hash_serialize!($hash_type);
- impl_psbt_hash_deserialize!($hash_type);
- };
-}
-
-macro_rules! impl_psbt_hash_deserialize {
- ($hash_type:ty) => {
- impl $crate::psbt::serialize::Deserialize for $hash_type {
- fn deserialize(bytes: &[u8]) -> core::result::Result<Self, $crate::psbt::Error> {
- const LEN: usize = <$hash_type as hashes::Hash>::LEN;
- let bytes =
- <[u8; LEN]>::try_from(bytes).map_err(|e| $crate::psbt::Error::from(e))?;
- Ok(<$hash_type>::from_byte_array(bytes))
- }
- }
- };
-}
-
-macro_rules! impl_psbt_hash_serialize {
- ($hash_type:ty) => {
- impl $crate::psbt::serialize::Serialize for $hash_type {
- fn serialize(&self) -> $crate::prelude::Vec<u8> { self.as_byte_array().to_vec() }
- }
- };
-}
diff --git a/bitcoin/src/psbt/map/global.rs b/bitcoin/src/psbt/map/global.rs
deleted file mode 100644
index 6a24c131..00000000
--- a/bitcoin/src/psbt/map/global.rs
+++ /dev/null
@@ -1,215 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-use internals::ToU64 as _;
-use io::{BufRead, Cursor, Read};
-
-use crate::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpub};
-use crate::consensus::encode::MAX_VEC_SIZE;
-use crate::consensus::{encode, Decodable};
-use crate::prelude::{btree_map, BTreeMap, Vec};
-use crate::psbt::map::Map;
-use crate::psbt::{raw, Error, Psbt};
-use crate::transaction::Transaction;
-
-/// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00
-const PSBT_GLOBAL_UNSIGNED_TX: u64 = 0x00;
-/// Type: Extended Public Key PSBT_GLOBAL_XPUB = 0x01
-const PSBT_GLOBAL_XPUB: u64 = 0x01;
-/// Type: Version Number PSBT_GLOBAL_VERSION = 0xFB
-const PSBT_GLOBAL_VERSION: u64 = 0xFB;
-/// Type: Proprietary Use Type PSBT_GLOBAL_PROPRIETARY = 0xFC
-const PSBT_GLOBAL_PROPRIETARY: u64 = 0xFC;
-
-impl Map for Psbt {
- fn get_pairs(&self) -> Vec<raw::Pair> {
- let mut rv: Vec<raw::Pair> = Default::default();
-
- rv.push(raw::Pair {
- key: raw::Key { type_value: PSBT_GLOBAL_UNSIGNED_TX, key_data: vec![] },
- value: {
- // Manually serialized to ensure 0-input txs are serialized
- // without witnesses.
- let mut ret = Vec::new();
- ret.extend(encode::serialize(&self.unsigned_tx.version));
- ret.extend(encode::serialize(&self.unsigned_tx.inputs));
- ret.extend(encode::serialize(&self.unsigned_tx.outputs));
- ret.extend(encode::serialize(&self.unsigned_tx.lock_time));
- ret
- },
- });
-
- for (xpub, (fingerprint, derivation)) in &self.xpub {
- rv.push(raw::Pair {
- key: raw::Key { type_value: PSBT_GLOBAL_XPUB, key_data: 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
- },
- });
- }
-
- // Serializing version only for non-default value; otherwise test vectors fail
- if self.version > 0 {
- rv.push(raw::Pair {
- key: raw::Key { type_value: PSBT_GLOBAL_VERSION, key_data: vec![] },
- value: self.version.to_le_bytes().to_vec(),
- });
- }
-
- 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
- }
-}
-
-impl Psbt {
- pub(crate) fn decode_global<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
- let mut r = r.take(MAX_VEC_SIZE.to_u64());
- let mut tx: Option<Transaction> = None;
- let mut version: Option<u32> = None;
- let mut unknowns: BTreeMap<raw::Key, Vec<u8>> = Default::default();
- let mut xpub_map: BTreeMap<Xpub, (Fingerprint, DerivationPath)> = Default::default();
- let mut proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>> = Default::default();
-
- loop {
- match raw::Pair::decode(&mut r) {
- Ok(pair) => {
- match pair.key.type_value {
- PSBT_GLOBAL_UNSIGNED_TX => {
- // key has to be empty
- if pair.key.key_data.is_empty() {
- // there can only be one unsigned transaction
- if tx.is_none() {
- let vlen: usize = pair.value.len();
- let mut decoder = Cursor::new(pair.value);
-
- // Manually deserialized to ensure 0-input
- // txs without witnesses are deserialized
- // properly.
- tx = Some(Transaction {
- version: Decodable::consensus_decode(&mut decoder)?,
- inputs: Decodable::consensus_decode(&mut decoder)?,
- outputs: Decodable::consensus_decode(&mut decoder)?,
- lock_time: Decodable::consensus_decode(&mut decoder)?,
- });
-
- if decoder.position() != vlen.to_u64() {
- return Err(Error::PartialDataConsumption);
- }
- } else {
- return Err(Error::DuplicateKey(pair.key));
- }
- } else {
- return Err(Error::InvalidKey(pair.key));
- }
- }
- PSBT_GLOBAL_XPUB => {
- if !pair.key.key_data.is_empty() {
- let xpub = Xpub::decode(&pair.key.key_data)
- .map_err(|_| Error::XPubKey(
- "can't deserialize ExtendedPublicKey from global XPUB key data"
- ))?;
-
- if pair.value.is_empty() || pair.value.len() % 4 != 0 {
- return Err(Error::XPubKey(
- "incorrect length of global xpub derivation data",
- ));
- }
-
- 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[..]).map_err(|_| {
- Error::XPubKey("can't read global xpub fingerprint")
- })?;
- let mut path = Vec::<ChildNumber>::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-0174, must be unique
- if xpub_map
- .insert(xpub, (Fingerprint::from(fingerprint), derivation))
- .is_some()
- {
- return Err(Error::XPubKey("repeated global xpub key"));
- }
- } else {
- return Err(Error::XPubKey(
- "Xpub global key must contain serialized Xpub data",
- ));
- }
- }
- PSBT_GLOBAL_VERSION => {
- // key has to be empty
- if pair.key.key_data.is_empty() {
- // there can only be one version
- if version.is_none() {
- let vlen: usize = pair.value.len();
- let mut decoder = Cursor::new(pair.value);
- if vlen != 4 {
- return Err(Error::Version(
- "invalid global version value length (must be 4 bytes)",
- ));
- }
- version = Some(Decodable::consensus_decode(&mut decoder)?);
- // We only understand version 0 PSBTs. According to BIP-0174 we
- // should throw an error if we see anything other than version 0.
- if version != Some(0) {
- return Err(Error::Version(
- "PSBT versions greater than 0 are not supported",
- ));
- }
- } else {
- return Err(Error::DuplicateKey(pair.key));
- }
- } else {
- return Err(Error::InvalidKey(pair.key));
- }
- }
- 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(Error::DuplicateKey(pair.key)),
- },
- _ => match unknowns.entry(pair.key) {
- btree_map::Entry::Vacant(empty_key) => {
- empty_key.insert(pair.value);
- }
- btree_map::Entry::Occupied(k) =>
- return Err(Error::DuplicateKey(k.key().clone())),
- },
- }
- }
- Err(crate::psbt::Error::NoMorePairs) => break,
- Err(e) => return Err(e),
- }
- }
-
- if let Some(tx) = tx {
- Ok(Self {
- unsigned_tx: tx,
- version: version.unwrap_or(0),
- xpub: xpub_map,
- proprietary,
- unknown: unknowns,
- inputs: vec![],
- outputs: vec![],
- })
- } else {
- Err(Error::MustHaveUnsignedTx)
- }
- }
-}
diff --git a/bitcoin/src/psbt/map/input.rs b/bitcoin/src/psbt/map/input.rs
deleted file mode 100644
index a77f7d7c..00000000
--- a/bitcoin/src/psbt/map/input.rs
+++ /dev/null
@@ -1,610 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-use core::fmt;
-use core::str::FromStr;
-
-#[cfg(feature = "arbitrary")]
-use arbitrary::{Arbitrary, Unstructured};
-use hashes::{hash160, ripemd160, sha256, sha256d};
-
-use crate::bip32::KeySource;
-use crate::crypto::key::{LegacyPublicKey, XOnlyPublicKey};
-use crate::crypto::{ecdsa, taproot};
-use crate::prelude::{btree_map, BTreeMap, Borrow, Box, Vec};
-use crate::psbt::map::Map;
-use crate::psbt::serialize::Deserialize;
-use crate::psbt::{error, raw, Error};
-use crate::script::{RedeemScriptBuf, ScriptSigBuf, TapScriptBuf, WitnessScriptBuf};
-use crate::sighash::{
- EcdsaSighashType, InvalidSighashTypeError, NonStandardSighashTypeError, SighashTypeParseError,
- TapSighashType,
-};
-use crate::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash};
-use crate::transaction::{Transaction, TxOut};
-use crate::witness::Witness;
-
-/// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00
-const PSBT_IN_NON_WITNESS_UTXO: u64 = 0x00;
-/// Type: Witness UTXO PSBT_IN_WITNESS_UTXO = 0x01
-const PSBT_IN_WITNESS_UTXO: u64 = 0x01;
-/// Type: Partial Signature PSBT_IN_PARTIAL_SIG = 0x02
-const PSBT_IN_PARTIAL_SIG: u64 = 0x02;
-/// Type: Sighash Type PSBT_IN_SIGHASH_TYPE = 0x03
-const PSBT_IN_SIGHASH_TYPE: u64 = 0x03;
-/// Type: Redeem Script PSBT_IN_REDEEM_SCRIPT = 0x04
-const PSBT_IN_REDEEM_SCRIPT: u64 = 0x04;
-/// Type: Witness Script PSBT_IN_WITNESS_SCRIPT = 0x05
-const PSBT_IN_WITNESS_SCRIPT: u64 = 0x05;
-/// Type: BIP-0032 Derivation Path PSBT_IN_BIP32_DERIVATION = 0x06
-const PSBT_IN_BIP32_DERIVATION: u64 = 0x06;
-/// Type: Finalized scriptSig PSBT_IN_FINAL_SCRIPTSIG = 0x07
-const PSBT_IN_FINAL_SCRIPTSIG: u64 = 0x07;
-/// Type: Finalized scriptWitness PSBT_IN_FINAL_SCRIPTWITNESS = 0x08
-const PSBT_IN_FINAL_SCRIPTWITNESS: u64 = 0x08;
-/// Type: RIPEMD160 preimage PSBT_IN_RIPEMD160 = 0x0a
-const PSBT_IN_RIPEMD160: u64 = 0x0a;
-/// Type: SHA256 preimage PSBT_IN_SHA256 = 0x0b
-const PSBT_IN_SHA256: u64 = 0x0b;
-/// Type: HASH160 preimage PSBT_IN_HASH160 = 0x0c
-const PSBT_IN_HASH160: u64 = 0x0c;
-/// Type: HASH256 preimage PSBT_IN_HASH256 = 0x0d
-const PSBT_IN_HASH256: u64 = 0x0d;
-/// Type: Taproot Signature in Key Spend PSBT_IN_TAP_KEY_SIG = 0x13
-const PSBT_IN_TAP_KEY_SIG: u64 = 0x13;
-/// Type: Taproot Signature in Script Spend PSBT_IN_TAP_SCRIPT_SIG = 0x14
-const PSBT_IN_TAP_SCRIPT_SIG: u64 = 0x14;
-/// Type: Taproot Leaf Script PSBT_IN_TAP_LEAF_SCRIPT = 0x15
-const PSBT_IN_TAP_LEAF_SCRIPT: u64 = 0x15;
-/// Type: Taproot Key BIP-0032 Derivation Path PSBT_IN_TAP_BIP32_DERIVATION = 0x16
-const PSBT_IN_TAP_BIP32_DERIVATION: u64 = 0x16;
-/// Type: Taproot Internal Key PSBT_IN_TAP_INTERNAL_KEY = 0x17
-const PSBT_IN_TAP_INTERNAL_KEY: u64 = 0x17;
-/// Type: Taproot Merkle Root PSBT_IN_TAP_MERKLE_ROOT = 0x18
-const PSBT_IN_TAP_MERKLE_ROOT: u64 = 0x18;
-/// Type: MuSig2 Public Keys Participating in Aggregate Input PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a
-const PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS: u64 = 0x1a;
-/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC
-const PSBT_IN_PROPRIETARY: u64 = 0xFC;
-
-/// A key-value map for an input of the corresponding index in the unsigned
-/// transaction.
-#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
-pub struct Input {
- /// 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<Transaction>,
- /// 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<TxOut>,
- /// 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<LegacyPublicKey, ecdsa::Signature>,
- /// The sighash type to be used for this input. Signatures for this input
- /// must use the sighash type.
- pub sighash_type: Option<PsbtSighashType>,
- /// The redeem script for this input.
- pub redeem_script: Option<RedeemScriptBuf>,
- /// The witness script for this input.
- pub witness_script: Option<WitnessScriptBuf>,
- /// A map from public keys needed to sign this input to their corresponding
- /// master key fingerprints and derivation paths.
- pub bip32_derivation: BTreeMap<secp256k1::PublicKey, KeySource>,
- /// The finalized, fully-constructed scriptSig with signatures and any other
- /// scripts necessary for this input to pass validation.
- pub final_script_sig: Option<ScriptSigBuf>,
- /// The finalized, fully-constructed scriptWitness with signatures and any
- /// other scripts necessary for this input to pass validation.
- pub final_script_witness: Option<Witness>,
- /// RIPEMD160 hash to preimage map.
- pub ripemd160_preimages: BTreeMap<ripemd160::Hash, Vec<u8>>,
- /// SHA256 hash to preimage map.
- pub sha256_preimages: BTreeMap<sha256::Hash, Vec<u8>>,
- /// HASH160 hash to preimage map.
- pub hash160_preimages: BTreeMap<hash160::Hash, Vec<u8>>,
- /// HASH256 hash to preimage map.
- pub hash256_preimages: BTreeMap<sha256d::Hash, Vec<u8>>,
- /// Serialized Taproot signature with sighash type for key spend.
- pub tap_key_sig: Option<taproot::Signature>,
- /// Map of `<xonlypubkey>|<leafhash>` with signature.
- pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), taproot::Signature>,
- /// Map of Control blocks to Script version pair.
- pub tap_scripts: BTreeMap<ControlBlock, (TapScriptBuf, LeafVersion)>,
- /// Map of tap root x only keys to origin info and leaf hashes contained in it.
- pub tap_key_origins: BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, KeySource)>,
- /// Taproot Internal key.
- pub tap_internal_key: Option<XOnlyPublicKey>,
- /// Taproot Merkle root.
- pub tap_merkle_root: Option<TapNodeHash>,
- /// Mapping from MuSig2 aggregate keys to the participant keys from which they were aggregated.
- pub musig2_participant_pubkeys: BTreeMap<secp256k1::PublicKey, Vec<secp256k1::PublicKey>>,
- /// Proprietary key-value pairs for this input.
- pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
- /// Unknown key-value pairs for this input.
- pub unknown: BTreeMap<raw::Key, Vec<u8>>,
-}
-
-/// A Signature hash type for the corresponding input.
-///
-/// As of Taproot upgrade, the signature hash type can be either [`EcdsaSighashType`] or
-/// [`TapSighashType`] but it is not possible to know directly which signature hash type the user is
-/// dealing with. Therefore, the user is responsible for converting to/from [`PsbtSighashType`]
-/// from/to the desired signature hash type they need.
-///
-/// # Examples
-///
-/// ```
-/// use bitcoin::{EcdsaSighashType, TapSighashType};
-/// use bitcoin::psbt::PsbtSighashType;
-///
-/// let _ecdsa_sighash_all: PsbtSighashType = EcdsaSighashType::All.into();
-/// let _tap_sighash_all: PsbtSighashType = TapSighashType::All.into();
-/// ```
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct PsbtSighashType {
- pub(in crate::psbt) inner: u32,
-}
-
-impl fmt::Display for PsbtSighashType {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self.taproot_hash_ty() {
- Err(_) => write!(f, "{:#x}", self.inner),
- Ok(taproot_hash_ty) => fmt::Display::fmt(&taproot_hash_ty, f),
- }
- }
-}
-
-impl FromStr for PsbtSighashType {
- type Err = SighashTypeParseError;
-
- #[inline]
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- // We accept strings of form: "SIGHASH_ALL" etc.
- //
- // NB: some of Taproot sighash types are non-standard for pre-Taproot
- // inputs. We also do not support SIGHASH_RESERVED in verbatim form
- // ("0xFF" string should be used instead).
- let parse_res = match s.parse::<TapSighashType>() {
- Ok(ty) => return Ok(ty.into()),
- Err(e) => e,
- };
-
- // We accept non-standard sighash values.
- if let Ok(inner) = u32::from_str_radix(s.trim_start_matches("0x"), 16) {
- return Ok(Self { inner });
- }
-
- // TapSighashType returns the SighashTypeParseError with unconsumed as the `s` string
- Err(parse_res)
- }
-}
-impl From<EcdsaSighashType> for PsbtSighashType {
- fn from(ecdsa_hash_ty: EcdsaSighashType) -> Self { Self { inner: ecdsa_hash_ty as u32 } }
-}
-
-impl From<TapSighashType> for PsbtSighashType {
- fn from(taproot_hash_ty: TapSighashType) -> Self { Self { inner: taproot_hash_ty as u32 } }
-}
-
-impl PsbtSighashType {
- /// Ambiguous `ALL` sighash type, may refer to either [`EcdsaSighashType::All`]
- /// or [`TapSighashType::All`].
- ///
- /// This is equivalent to either `EcdsaSighashType::All.into()` or `TapSighashType::All.into()`.
- /// For sighash types other than `ALL` use the ECDSA or Taproot sighash type directly.
- ///
- /// # Examples
- ///
- /// ```
- /// use bitcoin::{EcdsaSighashType, TapSighashType};
- /// use bitcoin::psbt::PsbtSighashType;
- /// let _ecdsa_sighash_anyone_can_pay: PsbtSighashType = EcdsaSighashType::AllPlusAnyoneCanPay.into();
- /// let _tap_sighash_anyone_can_pay: PsbtSighashType = TapSighashType::AllPlusAnyoneCanPay.into();
- /// ```
- pub const ALL: Self = Self { inner: 0x01 };
-
- /// Returns the [`EcdsaSighashType`] if the [`PsbtSighashType`] can be
- /// converted to one.
- pub fn ecdsa_hash_ty(self) -> Result<EcdsaSighashType, NonStandardSighashTypeError> {
- EcdsaSighashType::from_standard(self.inner)
- }
-
- /// Returns the [`TapSighashType`] if the [`PsbtSighashType`] can be
- /// converted to one.
- pub fn taproot_hash_ty(self) -> Result<TapSighashType, InvalidSighashTypeError> {
- if self.inner > 0xffu32 {
- Err(InvalidSighashTypeError(self.inner))
- } else {
- TapSighashType::from_consensus_u8(self.inner as u8)
- }
- }
-
- /// Constructs a new [`PsbtSighashType`] from a raw `u32`.
- ///
- /// Allows construction of a non-standard or non-valid sighash flag
- /// ([`EcdsaSighashType`], [`TapSighashType`] respectively).
- pub fn from_u32(n: u32) -> Self { Self { inner: n } }
-
- /// Converts [`PsbtSighashType`] to a raw `u32` sighash flag.
- ///
- /// No guarantees are made as to the standardness or validity of the returned value.
- pub fn to_u32(self) -> u32 { self.inner }
-}
-
-impl Input {
- /// 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<EcdsaSighashType, NonStandardSighashTypeError> {
- 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 an invalid Taproot sighash value.
- pub fn taproot_hash_ty(&self) -> Result<TapSighashType, InvalidSighashTypeError> {
- self.sighash_type
- .map(|sighash_type| sighash_type.taproot_hash_ty())
- .unwrap_or(Ok(TapSighashType::Default))
- }
-
- 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 {
- PSBT_IN_NON_WITNESS_UTXO => {
- impl_psbt_insert_pair! {
- self.non_witness_utxo <= <raw_key: _>|<raw_value: Transaction>
- }
- }
- PSBT_IN_WITNESS_UTXO => {
- impl_psbt_insert_pair! {
- self.witness_utxo <= <raw_key: _>|<raw_value: TxOut>
- }
- }
- PSBT_IN_PARTIAL_SIG => {
- impl_psbt_insert_pair! {
- self.partial_sigs <= <raw_key: LegacyPublicKey>|<raw_value: ecdsa::Signature>
- }
- }
- PSBT_IN_SIGHASH_TYPE => {
- impl_psbt_insert_pair! {
- self.sighash_type <= <raw_key: _>|<raw_value: PsbtSighashType>
- }
- }
- PSBT_IN_REDEEM_SCRIPT => {
- impl_psbt_insert_pair! {
- self.redeem_script <= <raw_key: _>|<raw_value: RedeemScriptBuf>
- }
- }
- PSBT_IN_WITNESS_SCRIPT => {
- impl_psbt_insert_pair! {
- self.witness_script <= <raw_key: _>|<raw_value: WitnessScriptBuf>
- }
- }
- PSBT_IN_BIP32_DERIVATION => {
- impl_psbt_insert_pair! {
- self.bip32_derivation <= <raw_key: secp256k1::PublicKey>|<raw_value: KeySource>
- }
- }
- PSBT_IN_FINAL_SCRIPTSIG => {
- impl_psbt_insert_pair! {
- self.final_script_sig <= <raw_key: _>|<raw_value: ScriptSigBuf>
- }
- }
- PSBT_IN_FINAL_SCRIPTWITNESS => {
- impl_psbt_insert_pair! {
- self.final_script_witness <= <raw_key: _>|<raw_value: Witness>
- }
- }
- PSBT_IN_RIPEMD160 => {
- psbt_insert_hash_pair! {
- &mut self.ripemd160_preimages <= raw_key|raw_value|ripemd160|error::PsbtHash::Ripemd
- }
- }
- PSBT_IN_SHA256 => {
- psbt_insert_hash_pair! {
- &mut self.sha256_preimages <= raw_key|raw_value|sha256|error::PsbtHash::Sha256
- }
- }
- PSBT_IN_HASH160 => {
- psbt_insert_hash_pair! {
- &mut self.hash160_preimages <= raw_key|raw_value|hash160|error::PsbtHash::Hash160
- }
- }
- PSBT_IN_HASH256 => {
- psbt_insert_hash_pair! {
- &mut self.hash256_preimages <= raw_key|raw_value|sha256d|error::PsbtHash::Hash256
- }
- }
- PSBT_IN_TAP_KEY_SIG => {
- impl_psbt_insert_pair! {
- self.tap_key_sig <= <raw_key: _>|<raw_value: taproot::Signature>
- }
- }
- PSBT_IN_TAP_SCRIPT_SIG => {
- impl_psbt_insert_pair! {
- self.tap_script_sigs <= <raw_key: (XOnlyPublicKey, TapLeafHash)>|<raw_value: taproot::Signature>
- }
- }
- PSBT_IN_TAP_LEAF_SCRIPT => {
- impl_psbt_insert_pair! {
- self.tap_scripts <= <raw_key: ControlBlock>|< raw_value: (TapScriptBuf, LeafVersion)>
- }
- }
- PSBT_IN_TAP_BIP32_DERIVATION => {
- impl_psbt_insert_pair! {
- self.tap_key_origins <= <raw_key: XOnlyPublicKey>|< raw_value: (Vec<TapLeafHash>, KeySource)>
- }
- }
- PSBT_IN_TAP_INTERNAL_KEY => {
- impl_psbt_insert_pair! {
- self.tap_internal_key <= <raw_key: _>|< raw_value: XOnlyPublicKey>
- }
- }
- PSBT_IN_TAP_MERKLE_ROOT => {
- impl_psbt_insert_pair! {
- self.tap_merkle_root <= <raw_key: _>|< raw_value: TapNodeHash>
- }
- }
- PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS => {
- impl_psbt_insert_pair! {
- self.musig2_participant_pubkeys <= <raw_key: secp256k1::PublicKey>|< raw_value: Vec<secp256k1::PublicKey> >
- }
- }
- 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.musig2_participant_pubkeys.extend(other.musig2_participant_pubkeys);
- 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<raw::Pair> {
- let mut rv: Vec<raw::Pair> = Default::default();
-
- 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)
- }
-
- impl_psbt_get_pair! {
- rv.push_map(self.musig2_participant_pubkeys, PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS)
- }
-
- 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
- }
-}
-
-impl_psbtmap_ser_de_serialize!(Input);
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for PsbtSighashType {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- Ok(Self::from_u32(u.arbitrary()?))
- }
-}
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for Input {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- Ok(Self {
- non_witness_utxo: u.arbitrary()?,
- witness_utxo: u.arbitrary()?,
- partial_sigs: u.arbitrary()?,
- sighash_type: u.arbitrary()?,
- redeem_script: u.arbitrary()?,
- witness_script: u.arbitrary()?,
- bip32_derivation: u.arbitrary()?,
- final_script_sig: u.arbitrary()?,
- final_script_witness: u.arbitrary()?,
- ripemd160_preimages: u.arbitrary()?,
- sha256_preimages: u.arbitrary()?,
- hash160_preimages: u.arbitrary()?,
- hash256_preimages: u.arbitrary()?,
- tap_key_sig: u.arbitrary()?,
- tap_script_sigs: u.arbitrary()?,
- tap_scripts: u.arbitrary()?,
- tap_key_origins: u.arbitrary()?,
- tap_internal_key: u.arbitrary()?,
- tap_merkle_root: u.arbitrary()?,
- musig2_participant_pubkeys: u.arbitrary()?,
- proprietary: u.arbitrary()?,
- unknown: u.arbitrary()?,
- })
- }
-}
-
-#[cfg(test)]
-mod test {
- 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 = s.parse::<PsbtSighashType>().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 = s.parse::<PsbtSighashType>().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 = s.parse::<PsbtSighashType>().unwrap();
-
- assert_eq!(back, sighash);
- assert_eq!(back.ecdsa_hash_ty(), Err(NonStandardSighashTypeError(nonstd)));
- assert_eq!(back.taproot_hash_ty(), Err(InvalidSighashTypeError(nonstd)));
- }
-
- #[test]
- fn psbt_sighash_const_all() {
- assert_eq!(PsbtSighashType::ALL.to_u32(), 0x01);
- assert_eq!(PsbtSighashType::ALL.ecdsa_hash_ty().unwrap(), EcdsaSighashType::All);
- assert_eq!(PsbtSighashType::ALL.taproot_hash_ty().unwrap(), TapSighashType::All);
- }
-}
diff --git a/bitcoin/src/psbt/map/mod.rs b/bitcoin/src/psbt/map/mod.rs
deleted file mode 100644
index 820ab40e..00000000
--- a/bitcoin/src/psbt/map/mod.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-mod global;
-mod input;
-mod output;
-
-use crate::prelude::Vec;
-use crate::psbt::raw;
-use crate::psbt::serialize::Serialize;
-
-#[rustfmt::skip] // Keep public re-exports separate.
-#[doc(inline)]
-pub use self::{
- input::{Input, PsbtSighashType},
- output::Output,
-};
-
-/// A trait that describes a PSBT key-value map.
-pub(super) trait Map {
- /// Attempt to get all key-value pairs.
- fn get_pairs(&self) -> Vec<raw::Pair>;
-
- /// Serialize Psbt binary map data according to BIP-0174 specification.
- ///
- /// <map> := <keypair>* 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 deserializer 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 deserializer implementation.
- fn serialize_map(&self) -> Vec<u8> {
- let mut buf = Vec::new();
- for pair in Map::get_pairs(self) {
- buf.extend(&pair.serialize());
- }
- buf.push(0x00_u8);
- buf
- }
-}
diff --git a/bitcoin/src/psbt/map/output.rs b/bitcoin/src/psbt/map/output.rs
deleted file mode 100644
index eaed1b83..00000000
--- a/bitcoin/src/psbt/map/output.rs
+++ /dev/null
@@ -1,192 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-#[cfg(feature = "arbitrary")]
-use arbitrary::{Arbitrary, Unstructured};
-
-use crate::bip32::KeySource;
-use crate::crypto::key::XOnlyPublicKey;
-use crate::prelude::{btree_map, BTreeMap, Vec};
-use crate::psbt::map::Map;
-use crate::psbt::{raw, Error};
-use crate::script::{RedeemScriptBuf, WitnessScriptBuf};
-use crate::taproot::{TapLeafHash, TapTree};
-
-/// Type: Redeem ScriptBuf PSBT_OUT_REDEEM_SCRIPT = 0x00
-const PSBT_OUT_REDEEM_SCRIPT: u64 = 0x00;
-/// Type: Witness ScriptBuf PSBT_OUT_WITNESS_SCRIPT = 0x01
-const PSBT_OUT_WITNESS_SCRIPT: u64 = 0x01;
-/// Type: BIP-0032 Derivation Path PSBT_OUT_BIP32_DERIVATION = 0x02
-const PSBT_OUT_BIP32_DERIVATION: u64 = 0x02;
-/// Type: Taproot Internal Key PSBT_OUT_TAP_INTERNAL_KEY = 0x05
-const PSBT_OUT_TAP_INTERNAL_KEY: u64 = 0x05;
-/// Type: Taproot Tree PSBT_OUT_TAP_TREE = 0x06
-const PSBT_OUT_TAP_TREE: u64 = 0x06;
-/// Type: Taproot Key BIP-0032 Derivation Path PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
-const PSBT_OUT_TAP_BIP32_DERIVATION: u64 = 0x07;
-/// Type: MuSig2 Public Keys Participating in Aggregate Output PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08
-const PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS: u64 = 0x08;
-/// Type: Proprietary Use Type PSBT_OUT_PROPRIETARY = 0xFC
-const PSBT_OUT_PROPRIETARY: u64 = 0xFC;
-
-/// A key-value map for an output of the corresponding index in the unsigned
-/// transaction.
-#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
-pub struct Output {
- /// The redeem script for this output.
- pub redeem_script: Option<RedeemScriptBuf>,
- /// The witness script for this output.
- pub witness_script: Option<WitnessScriptBuf>,
- /// A map from public keys needed to spend this output to their
- /// corresponding master key fingerprints and derivation paths.
- pub bip32_derivation: BTreeMap<secp256k1::PublicKey, KeySource>,
- /// The internal pubkey.
- pub tap_internal_key: Option<XOnlyPublicKey>,
- /// Taproot Output tree.
- pub tap_tree: Option<TapTree>,
- /// Map of tap root x only keys to origin info and leaf hashes contained in it.
- pub tap_key_origins: BTreeMap<XOnlyPublicKey, (Vec<TapLeafHash>, KeySource)>,
- /// Mapping from MuSig2 aggregate keys to the participant keys from which they were aggregated.
- pub musig2_participant_pubkeys: BTreeMap<secp256k1::PublicKey, Vec<secp256k1::PublicKey>>,
- /// Proprietary key-value pairs for this output.
- pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
- /// Unknown key-value pairs for this output.
- pub unknown: BTreeMap<raw::Key, Vec<u8>>,
-}
-
-impl Output {
- 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 {
- PSBT_OUT_REDEEM_SCRIPT => {
- impl_psbt_insert_pair! {
- self.redeem_script <= <raw_key: _>|<raw_value: RedeemScriptBuf>
- }
- }
- PSBT_OUT_WITNESS_SCRIPT => {
- impl_psbt_insert_pair! {
- self.witness_script <= <raw_key: _>|<raw_value: WitnessScriptBuf>
- }
- }
- PSBT_OUT_BIP32_DERIVATION => {
- impl_psbt_insert_pair! {
- self.bip32_derivation <= <raw_key: secp256k1::PublicKey>|<raw_value: KeySource>
- }
- }
- 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)),
- }
- }
- PSBT_OUT_TAP_INTERNAL_KEY => {
- impl_psbt_insert_pair! {
- self.tap_internal_key <= <raw_key: _>|<raw_value: XOnlyPublicKey>
- }
- }
- PSBT_OUT_TAP_TREE => {
- impl_psbt_insert_pair! {
- self.tap_tree <= <raw_key: _>|<raw_value: TapTree>
- }
- }
- PSBT_OUT_TAP_BIP32_DERIVATION => {
- impl_psbt_insert_pair! {
- self.tap_key_origins <= <raw_key: XOnlyPublicKey>|< raw_value: (Vec<TapLeafHash>, KeySource)>
- }
- }
- PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS => {
- impl_psbt_insert_pair! {
- self.musig2_participant_pubkeys <= <raw_key: secp256k1::PublicKey>|< raw_value: Vec<secp256k1::PublicKey> >
- }
- }
- _ => 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);
- self.musig2_participant_pubkeys.extend(other.musig2_participant_pubkeys);
-
- 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<raw::Pair> {
- let mut rv: Vec<raw::Pair> = Default::default();
-
- 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)
- }
-
- impl_psbt_get_pair! {
- rv.push_map(self.musig2_participant_pubkeys, PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS)
- }
-
- 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
- }
-}
-
-impl_psbtmap_ser_de_serialize!(Output);
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for Output {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- Ok(Self {
- redeem_script: u.arbitrary()?,
- witness_script: u.arbitrary()?,
- bip32_derivation: u.arbitrary()?,
- tap_internal_key: u.arbitrary()?,
- tap_tree: u.arbitrary()?,
- tap_key_origins: u.arbitrary()?,
- musig2_participant_pubkeys: u.arbitrary()?,
- proprietary: u.arbitrary()?,
- unknown: u.arbitrary()?,
- })
- }
-}
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
deleted file mode 100644
index 73708b14..00000000
--- a/bitcoin/src/psbt/mod.rs
+++ /dev/null
@@ -1,2741 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-//! Partially Signed Bitcoin Transactions.
-//!
-//! Implementation of BIP-0174 Partially Signed Bitcoin Transaction Format as
-//! defined at <https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki>
-//! except we define PSBTs containing non-standard sighash types as invalid.
-
-#[macro_use]
-mod macros;
-mod error;
-mod map;
-pub mod raw;
-pub mod serialize;
-
-use core::convert::Infallible;
-use core::{cmp, fmt};
-#[cfg(feature = "std")]
-use std::collections::{HashMap, HashSet};
-
-#[cfg(feature = "arbitrary")]
-use arbitrary::{Arbitrary, Unstructured};
-use internals::write_err;
-use secp256k1::Message;
-
-use crate::bip32::{self, KeySource, Xpriv, Xpub};
-use crate::crypto::key::{LegacyPublicKey, PrivateKey};
-use crate::crypto::{ecdsa, taproot};
-use crate::key::{Keypair, TapTweak, XOnlyPublicKey};
-use crate::prelude::{btree_map, BTreeMap, BTreeSet, Borrow, Box, Vec};
-use crate::script::{ScriptExt as _, ScriptPubKeyExt as _};
-use crate::sighash::{self, EcdsaSighashType, Prevouts, SighashCache};
-use crate::transaction::{self, Transaction, TransactionExt as _, TxOut};
-use crate::{Amount, FeeRate, TapLeafHash, TapSighash, TapSighashType};
-
-#[rustfmt::skip] // Keep public re-exports separate.
-#[doc(inline)]
-pub use self::{
- map::{Input, Output, PsbtSighashType},
- error::Error,
-};
-
-/// A Partially Signed Transaction.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct Psbt {
- /// The unsigned transaction, scriptSigs and witnesses for each input must be empty.
- pub unsigned_tx: Transaction,
- /// The version number of this PSBT. If omitted, the version number is 0.
- pub version: u32,
- /// A global map from extended public keys to the used key fingerprint and
- /// derivation path as defined by BIP 32.
- pub xpub: BTreeMap<Xpub, KeySource>,
- /// Global proprietary key-value pairs.
- pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
- /// Unknown global key-value pairs.
- pub unknown: BTreeMap<raw::Key, Vec<u8>>,
-
- /// The corresponding key-value map for each input in the unsigned transaction.
- pub inputs: Vec<Input>,
- /// The corresponding key-value map for each output in the unsigned transaction.
- pub outputs: Vec<Output>,
-}
-
-impl Psbt {
- /// 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.
- ///
- /// # Panics
- ///
- /// The function panics if the length of transaction inputs is not equal to the length of PSBT inputs.
- pub fn iter_funding_utxos(&self) -> impl Iterator<Item = Result<&TxOut, Error>> {
- assert_eq!(self.inputs.len(), self.unsigned_tx.inputs.len());
- self.unsigned_tx.inputs.iter().zip(&self.inputs).map(|(tx_input, psbt_input)| {
- match (&psbt_input.witness_utxo, &psbt_input.non_witness_utxo) {
- (Some(witness_utxo), _) => Ok(witness_utxo),
- (None, Some(non_witness_utxo)) => {
- let vout = tx_input.previous_output.vout as usize;
- non_witness_utxo.outputs.get(vout).ok_or(Error::PsbtUtxoOutOfbounds)
- }
- (None, None) => Err(Error::MissingUtxo),
- }
- })
- }
-
- /// Checks that unsigned transaction does not have scriptSig's or witness data.
- fn unsigned_tx_checks(&self) -> Result<(), Error> {
- for txin in &self.unsigned_tx.inputs {
- if !txin.script_sig.is_empty() {
- return Err(Error::UnsignedTxHasScriptSigs);
- }
-
- if !txin.witness.is_empty() {
- return Err(Error::UnsignedTxHasScriptWitnesses);
- }
- }
-
- Ok(())
- }
-
- /// Constructs a new PSBT from an unsigned transaction.
- ///
- /// # Errors
- ///
- /// If transactions is not unsigned.
- pub fn from_unsigned_tx(tx: Transaction) -> Result<Self, Error> {
- let psbt = Self {
- inputs: vec![Default::default(); tx.inputs.len()],
- outputs: vec![Default::default(); tx.outputs.len()],
-
- unsigned_tx: tx,
- xpub: Default::default(),
- version: 0,
- proprietary: Default::default(),
- unknown: Default::default(),
- };
- psbt.unsigned_tx_checks()?;
- Ok(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(25_000);
-
- /// An alias for [`extract_tx_fee_rate_limit`].
- ///
- /// [`extract_tx_fee_rate_limit`]: Psbt::extract_tx_fee_rate_limit
- #[allow(clippy::result_large_err)] // The PSBT returned in `SendingToomuch` is large.
- pub fn extract_tx(self) -> Result<Transaction, ExtractTxError> {
- 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.
- #[allow(clippy::result_large_err)] // The PSBT returned in `SendingToomuch` is large.
- pub fn extract_tx_fee_rate_limit(self) -> Result<Transaction, ExtractTxError> {
- 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
- #[allow(clippy::result_large_err)] // The PSBT returned in `SendingToomuch` is large.
- pub fn extract_tx_with_fee_rate_limit(
- self,
- max_fee_rate: FeeRate,
- ) -> Result<Transaction, ExtractTxError> {
- 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 {
- let mut tx: Transaction = self.unsigned_tx;
-
- for (vin, psbtin) in tx.inputs.iter_mut().zip(self.inputs) {
- vin.script_sig = psbtin.final_script_sig.unwrap_or_default();
- vin.witness = psbtin.final_script_witness.unwrap_or_default();
- }
-
- tx
- }
-
- #[inline]
- #[allow(clippy::result_large_err)] // The PSBT returned in `SendingToomuch` is large.
- fn internal_extract_tx_with_fee_rate_limit(
- self,
- max_fee_rate: FeeRate,
- ) -> Result<Transaction, ExtractTxError> {
- let fee = match self.fee() {
- Ok(fee) => fee,
- Err(Error::MissingUtxo) | Err(Error::PsbtUtxoOutOfbounds) =>
- return Err(ExtractTxError::MissingInputAmount { 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();
-
- let fee_rate = (fee / tx.weight()).unwrap_or(FeeRate::MAX);
- if fee_rate > max_fee_rate {
- Err(ExtractTxError::AbsurdFeeRate { fee_rate, tx })
- } else {
- Ok(tx)
- }
- }
-
- /// Combines this [`Psbt`] with `other` PSBT as described by BIP-0174.
- ///
- /// In accordance with BIP-0174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
- pub fn combine(&mut self, other: Self) -> Result<(), Error> {
- if self.unsigned_tx != other.unsigned_tx {
- return Err(Error::UnexpectedUnsignedTx {
- expected: Box::new(self.unsigned_tx.clone()),
- actual: Box::new(other.unsigned_tx),
- });
- }
-
- // BIP-0174: 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.xpub {
- match self.xpub.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.len() <= derivation1.len()
- && derivation2[..] == derivation1[derivation1.len() - derivation2.len()..]
- {
- entry.insert((fingerprint1, derivation1));
- continue;
- }
- return Err(Error::CombineInconsistentKeySources(Box::new(xpub)));
- }
- }
- }
-
- self.proprietary.extend(other.proprietary);
- self.unknown.extend(other.unknown);
-
- for (self_input, other_input) in self.inputs.iter_mut().zip(other.inputs) {
- self_input.combine(other_input);
- }
-
- for (self_output, other_output) in self.outputs.iter_mut().zip(other.outputs) {
- self_output.combine(other_output);
- }
-
- Ok(())
- }
-
- /// Attempts to create _all_ the required signatures for this PSBT using `k`.
- ///
- /// If you just want to sign an input with one specific key consider using `sighash_ecdsa` or
- /// `sighash_taproot`. This function does not support scripts that contain `OP_CODESEPARATOR`.
- ///
- /// # Returns
- ///
- /// A map of input index -> keys used to sign, for Taproot specifics please see [`SigningKeys`].
- ///
- /// 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<K>(&mut self, k: &K) -> Result<SigningKeysMap, (SigningKeysMap, SigningErrors)>
- where
- K: GetKey,
- {
- let tx = self.unsigned_tx.clone(); // clone because we need to mutably borrow when signing.
- let mut cache = SighashCache::new(&tx);
-
- let mut used = BTreeMap::new();
- let mut errors = BTreeMap::new();
-
- for i in 0..self.inputs.len() {
- match self.signing_algorithm(i) {
- Ok(SigningAlgorithm::Ecdsa) => match self.bip32_sign_ecdsa(k, i, &mut cache) {
- Ok(v) => {
- used.insert(i, SigningKeys::Ecdsa(v));
- }
- Err(e) => {
- errors.insert(i, e);
- }
- },
- Ok(SigningAlgorithm::Schnorr) => match self.bip32_sign_schnorr(k, i, &mut cache) {
- Ok(v) => {
- used.insert(i, SigningKeys::Schnorr(v));
- }
- Err(e) => {
- errors.insert(i, e);
- }
- },
- Err(e) => {
- errors.insert(i, e);
- }
- }
- }
- if errors.is_empty() {
- Ok(used)
- } else {
- Err((used, errors))
- }
- }
-
- /// 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<K, T>(
- &mut self,
- k: &K,
- input_index: usize,
- cache: &mut SighashCache<T>,
- ) -> Result<Vec<LegacyPublicKey>, SignError>
- where
- T: Borrow<Transaction>,
- 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 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())) {
- sk
- } else if let Ok(Some(sk)) =
- k.get_key(&KeyRequest::Pubkey(LegacyPublicKey::from_secp(*pk)))
- {
- sk
- } else {
- continue;
- };
-
- // Only return the error if we have a secret key to sign this input.
- let (msg, sighash_type) = match msg_sighash_ty_res {
- Err(e) => return Err(e),
- Ok((msg, sighash_ty)) => (msg, sighash_ty),
- };
-
- let sig = ecdsa::Signature { signature: sk.raw_ecdsa_sign(msg), sighash_type };
-
- let pk = sk.to_public_key();
-
- input.partial_sigs.insert(pk, sig);
- used.push(pk);
- }
-
- Ok(used)
- }
-
- /// Attempts to create all signatures required by this PSBT's `tap_key_origins` field, adding
- /// them to `tap_key_sig` or `tap_script_sigs`.
- ///
- /// # Returns
- ///
- /// - Ok: A list of the xonly public keys used in signing. When signing a key path spend we
- /// return the internal key.
- /// - Err: Error encountered trying to calculate the sighash AND we had the signing key.
- fn bip32_sign_schnorr<K, T>(
- &mut self,
- k: &K,
- input_index: usize,
- cache: &mut SighashCache<T>,
- ) -> Result<Vec<XOnlyPublicKey>, SignError>
- where
- T: Borrow<Transaction>,
- K: GetKey,
- {
- let mut input = self.checked_input(input_index)?.clone();
-
- let mut used = vec![]; // List of pubkeys used to sign the input.
-
- for (&xonly, (leaf_hashes, key_source)) in input.tap_key_origins.iter() {
- let sk = if let Ok(Some(secret_key)) = k.get_key(&KeyRequest::Bip32(key_source.clone()))
- {
- secret_key
- } else if let Ok(Some(sk)) = k.get_key(&KeyRequest::XOnlyPubkey(xonly)) {
- sk
- } else {
- continue;
- };
-
- // Considering the responsibility of the PSBT's finalizer to extract valid signatures,
- // the goal of this algorithm is to provide signatures to the best of our ability:
- // 1) If the conditions for key path spend are met, proceed to provide the signature for key path spend
- // 2) If the conditions for script path spend are met, proceed to provide the signature for script path spend
-
- // key path spend
- if let Some(internal_key) = input.tap_internal_key {
- // BIP-0371: The internal key does not have leaf hashes, so can be indicated with a hashes len of 0.
-
- // Based on input.tap_internal_key.is_some() alone, it is not sufficient to determine whether it is a key path spend.
- // According to BIP-0371, we also need to consider the condition leaf_hashes.is_empty() for a more accurate determination.
- if internal_key == xonly && leaf_hashes.is_empty() && input.tap_key_sig.is_none() {
- let (sighash, sighash_type) = self.sighash_taproot(input_index, cache, None)?;
- let key_pair = Keypair::from_private_key(&sk)
- .tap_tweak(input.tap_merkle_root)
- .into_keypair();
-
- let signature = key_pair.raw_bip340_sign(&sighash.to_byte_array());
-
- let signature = taproot::Signature { signature, sighash_type };
- input.tap_key_sig = Some(signature);
-
- used.push(internal_key);
- }
- }
-
- // script path spend
- if let Some((leaf_hashes, _)) = input.tap_key_origins.get(&xonly) {
- let leaf_hashes = leaf_hashes
- .iter()
- .filter(|lh| !input.tap_script_sigs.contains_key(&(xonly, **lh)))
- .cloned()
- .collect::<Vec<_>>();
-
- if !leaf_hashes.is_empty() {
- let key_pair = Keypair::from_private_key(&sk);
-
- for lh in leaf_hashes {
- let (sighash, sighash_type) =
- self.sighash_taproot(input_index, cache, Some(lh))?;
-
- let signature = key_pair.raw_bip340_sign(&sighash.to_byte_array());
-
- let signature = taproot::Signature { signature, sighash_type };
- input.tap_script_sigs.insert((xonly, lh), signature);
- }
-
- used.push(sk.to_public_key().into());
- }
- }
- }
-
- self.inputs[input_index] = input; // input_index is checked above.
-
- 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<T: Borrow<Transaction>>(
- &self,
- input_index: usize,
- cache: &mut SighashCache<T>,
- ) -> 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())
- .expect("input checked above");
- Ok((Message::from(sighash), 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())
- .expect("input checked above");
- Ok((Message::from(sighash), hash_ty))
- }
- Wpkh => {
- let sighash =
- cache.p2wpkh_signature_hash(input_index, spk, utxo.amount, hash_ty)?;
- Ok((Message::from(sighash), 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.amount,
- hash_ty,
- )?;
- Ok((Message::from(sighash), 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.amount, hash_ty)
- .map_err(SignError::SegwitV0Sighash)?;
- Ok((Message::from(sighash), hash_ty))
- }
- Tr => {
- // This PSBT signing API is WIP, Taproot to come shortly.
- Err(SignError::Unsupported)
- }
- }
- }
-
- /// Returns the sighash to sign a Taproot input along with the sighash type.
- ///
- /// Uses the [`TapSighashType`] from this input if one is specified. If no sighash type is
- /// specified uses [`TapSighashType::Default`].
- fn sighash_taproot<T: Borrow<Transaction>>(
- &self,
- input_index: usize,
- cache: &mut SighashCache<T>,
- leaf_hash: Option<TapLeafHash>,
- ) -> Result<(TapSighash, TapSighashType), SignError> {
- use OutputType::*;
-
- if self.signing_algorithm(input_index)? != SigningAlgorithm::Schnorr {
- return Err(SignError::WrongSigningAlgorithm);
- }
-
- let input = self.checked_input(input_index)?;
-
- match self.output_type(input_index)? {
- Tr => {
- let hash_ty = input
- .sighash_type
- .unwrap_or_else(|| TapSighashType::Default.into())
- .taproot_hash_ty()
- .map_err(|_| SignError::InvalidSighashType)?;
-
- let spend_utxos =
- (0..self.inputs.len()).map(|i| self.spend_utxo(i).ok()).collect::<Vec<_>>();
- let all_spend_utxos;
-
- let is_anyone_can_pay = PsbtSighashType::from(hash_ty).to_u32() & 0x80 != 0;
-
- let prev_outs = if is_anyone_can_pay {
- Prevouts::One(
- input_index,
- spend_utxos[input_index].ok_or(SignError::MissingSpendUtxo)?,
- )
- } else if spend_utxos.iter().all(Option::is_some) {
- all_spend_utxos = spend_utxos.iter().filter_map(|x| *x).collect::<Vec<_>>();
- Prevouts::All(&all_spend_utxos)
- } else {
- return Err(SignError::MissingSpendUtxo);
- };
-
- let sighash = if let Some(leaf_hash) = leaf_hash {
- cache.taproot_script_spend_signature_hash(
- input_index,
- &prev_outs,
- leaf_hash,
- hash_ty,
- )?
- } else {
- cache.taproot_key_spend_signature_hash(input_index, &prev_outs, hash_ty)?
- };
- Ok((sighash, hash_ty))
- }
- _ => 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 = self.unsigned_tx.inputs[input_index].previous_output.vout;
- non_witness_utxo.outputs.get(vout as usize).ok_or(SignError::MissingSpendUtxo)?
- } else {
- return Err(SignError::MissingSpendUtxo);
- };
- Ok(utxo)
- }
-
- /// Gets the input at `input_index` after checking that it is a valid index.
- fn checked_input(&self, input_index: usize) -> Result<&Input, IndexOutOfBoundsError> {
- // No `?` operator in const context.
- match self.check_index_is_within_bounds(input_index) {
- Ok(_) => Ok(&self.inputs[input_index]),
- Err(e) => Err(e),
- }
- }
-
- /// Checks `input_index` is within bounds for the PSBT `inputs` array and
- /// for the PSBT `unsigned_tx` `input` array.
- fn check_index_is_within_bounds(
- &self,
- input_index: usize,
- ) -> Result<(), IndexOutOfBoundsError> {
- if input_index >= self.inputs.len() {
- return Err(IndexOutOfBoundsError::Inputs {
- index: input_index,
- length: self.inputs.len(),
- });
- }
-
- if input_index >= self.unsigned_tx.inputs.len() {
- return Err(IndexOutOfBoundsError::TxInput {
- index: input_index,
- length: self.unsigned_tx.inputs.len(),
- });
- }
-
- Ok(())
- }
-
- /// Returns the algorithm used to sign this PSBT's input at `input_index`.
- fn signing_algorithm(&self, input_index: usize) -> Result<SigningAlgorithm, SignError> {
- let output_type = self.output_type(input_index)?;
- Ok(output_type.signing_algorithm())
- }
-
- /// Returns the [`OutputType`] of the spend utxo for this PSBT's input at `input_index`.
- fn output_type(&self, input_index: usize) -> Result<OutputType, SignError> {
- 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<Amount, Error> {
- let mut inputs = Amount::ZERO;
- for utxo in self.iter_funding_utxos() {
- inputs = inputs.checked_add(utxo?.amount).ok_or(Error::FeeOverflow)?;
- }
- let mut outputs = Amount::ZERO;
- for out in &self.unsigned_tx.outputs {
- outputs = outputs.checked_add(out.amount).ok_or(Error::FeeOverflow)?;
- }
- inputs.checked_sub(outputs).ok_or(Error::NegativeFee)
- }
-}
-
-#[cfg(feature = "serde")]
-impl serde::Serialize for Psbt {
- fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
- use crate::prelude::ToString;
-
- if serializer.is_human_readable() {
- serializer.serialize_str(&self.to_string())
- } else {
- serializer.serialize_bytes(&self.serialize())
- }
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> serde::Deserialize<'de> for Psbt {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- struct Visitor;
-
- impl serde::de::Visitor<'_> for Visitor {
- type Value = Psbt;
-
- fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
- write!(f, "a psbt")
- }
-
- fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
- Psbt::deserialize(bytes).map_err(|e| serde::de::Error::custom(e))
- }
-
- fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
- s.parse().map_err(|e| serde::de::Error::custom(e))
- }
- }
-
- if deserializer.is_human_readable() {
- deserializer.deserialize_str(Visitor)
- } else {
- deserializer.deserialize_bytes(Visitor)
- }
- }
-}
-
-/// 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(LegacyPublicKey),
- /// Request a private key using BIP-0032 fingerprint and derivation path.
- Bip32(KeySource),
- /// Request a private key using the associated x-only public key.
- XOnlyPubkey(XOnlyPublicKey),
-}
-
-/// 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) -> Result<Option<PrivateKey>, Self::Error>;
-}
-
-impl GetKey for Xpriv {
- type Error = GetKeyError;
-
- fn get_key(&self, key_request: &KeyRequest) -> Result<Option<PrivateKey>, Self::Error> {
- match key_request {
- KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported),
- KeyRequest::XOnlyPubkey(_) => Err(GetKeyError::NotSupported),
- KeyRequest::Bip32((fingerprint, path)) => {
- let key = if self.fingerprint() == *fingerprint {
- let k = self.derive_xpriv(path).map_err(GetKeyError::Bip32)?;
- Some(k.to_private_key())
- } else if self.parent_fingerprint == *fingerprint
- && !path.is_empty()
- && path[0] == self.child_number
- {
- let k = self.derive_xpriv(&path[1..]).map_err(GetKeyError::Bip32)?;
- Some(k.to_private_key())
- } else {
- None
- };
- Ok(key)
- }
- }
- }
-}
-
-/// Map of input index -> signing key for that input (see [`SigningKeys`]).
-pub type SigningKeysMap = BTreeMap<usize, SigningKeys>;
-
-/// A list of keys used to sign an input.
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub enum SigningKeys {
- /// Keys used to sign an ECDSA input.
- Ecdsa(Vec<LegacyPublicKey>),
- /// Keys used to sign a Taproot input.
- ///
- /// - Key path spend: This is the internal key.
- /// - Script path spend: This is the pubkey associated with the secret key that signed.
- Schnorr(Vec<XOnlyPublicKey>),
-}
-
-/// Map of input index -> the error encountered while attempting to sign that input.
-pub type SigningErrors = BTreeMap<usize, SignError>;
-
-#[rustfmt::skip]
-macro_rules! impl_get_key_for_set {
- ($set:ident) => {
-
-impl GetKey for $set<Xpriv> {
- type Error = GetKeyError;
-
- fn get_key(
- &self,
- key_request: &KeyRequest,
- ) -> Result<Option<PrivateKey>, Self::Error> {
- // OK to stop at the first error because Xpriv::get_key() can only fail
- // if this isn't a KeyRequest::Bip32, which would fail for all Xprivs.
- self.iter()
- .find_map(|xpriv| xpriv.get_key(key_request).transpose())
- .transpose()
- }
-}}}
-impl_get_key_for_set!(Vec);
-impl_get_key_for_set!(BTreeSet);
-#[cfg(feature = "std")]
-impl_get_key_for_set!(HashSet);
-
-#[rustfmt::skip]
-macro_rules! impl_get_key_for_pubkey_map {
- ($map:ident) => {
-
-impl GetKey for $map<LegacyPublicKey, PrivateKey> {
- type Error = GetKeyError;
-
- fn get_key(
- &self,
- key_request: &KeyRequest,
- ) -> Result<Option<PrivateKey>, Self::Error> {
- match key_request {
- KeyRequest::Pubkey(pk) => Ok(self.get(&pk).cloned()),
- KeyRequest::XOnlyPubkey(xonly) => {
- let pubkey_even = xonly.with_parity(secp256k1::Parity::Even).to_public_key();
- let key = self.get(&pubkey_even).cloned();
-
- if key.is_some() {
- return Ok(key);
- }
-
- let pubkey_odd = xonly.with_parity(secp256k1::Parity::Odd).to_public_key();
- if let Some(priv_key) = self.get(&pubkey_odd) {
- let negated_priv_key = priv_key.negate();
- return Ok(Some(negated_priv_key));
- }
-
- Ok(None)
- },
- KeyRequest::Bip32(_) => Err(GetKeyError::NotSupported),
- }
- }
-}}}
-impl_get_key_for_pubkey_map!(BTreeMap);
-#[cfg(feature = "std")]
-impl_get_key_for_pubkey_map!(HashMap);
-
-#[rustfmt::skip]
-macro_rules! impl_get_key_for_xonly_map {
- ($map:ident) => {
-
-impl GetKey for $map<XOnlyPublicKey, PrivateKey> {
- type Error = GetKeyError;
-
- fn get_key(
- &self,
- key_request: &KeyRequest,
- ) -> Result<Option<PrivateKey>, Self::Error> {
- match key_request {
- KeyRequest::XOnlyPubkey(xonly) => Ok(self.get(xonly).cloned()),
- KeyRequest::Pubkey(pk) => {
- let xonly = XOnlyPublicKey::from(*pk);
-
- if let Some(mut priv_key) = self.get(&XOnlyPublicKey::from(xonly)).cloned() {
- let computed_pk = priv_key.to_public_key();
- let computed_parity = XOnlyPublicKey::from(computed_pk).parity();
-
- if computed_parity != xonly.parity() {
- priv_key = priv_key.negate();
- }
-
- return Ok(Some(priv_key));
- }
-
- Ok(None)
- },
- KeyRequest::Bip32(_) => Err(GetKeyError::NotSupported),
- }
- }
-}}}
-impl_get_key_for_xonly_map!(BTreeMap);
-#[cfg(feature = "std")]
-impl_get_key_for_xonly_map!(HashMap);
-
-/// Errors when getting a key.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub enum GetKeyError {
- /// A bip32 derivation error.
- Bip32(bip32::DerivationError),
- /// The GetKey operation is not supported for this key request.
- NotSupported,
-}
-
-impl From<Infallible> for GetKeyError {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for GetKeyError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::Bip32(ref e) => write_err!(f, "bip32 derivation"; e),
- Self::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)> {
- match self {
- Self::NotSupported => None,
- Self::Bip32(ref e) => Some(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,
-}
-
-/// 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 a non-ECDSA input.
- NotEcdsa,
- /// The `scriptPubkey` is not a P2WPKH script.
- NotWpkh,
- /// Sighash computation error (SegWit v0 input).
- SegwitV0Sighash(transaction::InputsIndexError),
- /// Sighash computation error (p2wpkh input).
- P2wpkhSighash(sighash::P2wpkhError),
- /// Sighash computation error (Taproot input).
- TaprootError(sighash::TaprootError),
- /// 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 From<Infallible> for SignError {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for SignError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::IndexOutOfBounds(ref e) => write_err!(f, "index out of bounds"; e),
- Self::InvalidSighashType => write!(f, "invalid sighash type"),
- Self::MissingInputUtxo => write!(f, "missing input utxo in PSBT"),
- Self::MissingRedeemScript => write!(f, "missing redeem script"),
- Self::MissingSpendUtxo => write!(f, "missing spend utxo in PSBT"),
- Self::MissingWitnessScript => write!(f, "missing witness script"),
- Self::MismatchedAlgoKey => write!(f, "signing algorithm and key type does not match"),
- Self::NotEcdsa => write!(f, "attempted to ECDSA sign a non-ECDSA input"),
- Self::NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"),
- Self::SegwitV0Sighash(ref e) => write_err!(f, "SegWit v0 sighash"; e),
- Self::P2wpkhSighash(ref e) => write_err!(f, "p2wpkh sighash"; e),
- Self::TaprootError(ref e) => write_err!(f, "Taproot sighash"; e),
- Self::UnknownOutputType => write!(f, "unable to determine the output type"),
- Self::KeyNotFound => write!(f, "unable to find key"),
- Self::WrongSigningAlgorithm =>
- write!(f, "attempt to sign an input with the wrong signing algorithm"),
- Self::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)> {
- match self {
- Self::SegwitV0Sighash(ref e) => Some(e),
- Self::P2wpkhSighash(ref e) => Some(e),
- Self::TaprootError(ref e) => Some(e),
- Self::IndexOutOfBounds(ref e) => Some(e),
- Self::InvalidSighashType
- | Self::MissingInputUtxo
- | Self::MissingRedeemScript
- | Self::MissingSpendUtxo
- | Self::MissingWitnessScript
- | Self::MismatchedAlgoKey
- | Self::NotEcdsa
- | Self::NotWpkh
- | Self::UnknownOutputType
- | Self::KeyNotFound
- | Self::WrongSigningAlgorithm
- | Self::Unsupported => None,
- }
- }
-}
-
-impl From<sighash::P2wpkhError> for SignError {
- fn from(e: sighash::P2wpkhError) -> Self { Self::P2wpkhSighash(e) }
-}
-
-impl From<IndexOutOfBoundsError> for SignError {
- fn from(e: IndexOutOfBoundsError) -> Self { Self::IndexOutOfBounds(e) }
-}
-
-impl From<sighash::TaprootError> for SignError {
- fn from(e: sighash::TaprootError) -> Self { Self::TaprootError(e) }
-}
-
-/// This error is returned when extracting a [`Transaction`] from a [`Psbt`].
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub enum ExtractTxError {
- /// The [`FeeRate`] is too high
- AbsurdFeeRate {
- /// The [`FeeRate`]
- fee_rate: FeeRate,
- /// The extracted [`Transaction`] (use this to ignore the error)
- tx: Transaction,
- },
- /// One or more of the inputs lacks amount information (witness_utxo or non_witness_utxo)
- MissingInputAmount {
- /// The extracted [`Transaction`] (use this to ignore the error)
- tx: Transaction,
- },
- /// Input amount is less than output amount, and the [`Transaction`] would be invalid.
- SendingTooMuch {
- /// The original [`Psbt`] is returned untouched.
- psbt: Psbt,
- },
-}
-
-impl From<Infallible> for ExtractTxError {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for ExtractTxError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::AbsurdFeeRate { fee_rate, .. } => write!(
- f,
- "an absurdly high fee rate of {} sat/kwu",
- fee_rate.to_sat_per_kwu_floor()
- ),
- Self::MissingInputAmount { .. } => write!(
- f,
- "one of the inputs lacked amount information (witness_utxo or non_witness_utxo)"
- ),
- Self::SendingTooMuch { .. } => write!(
- f,
- "transaction would be invalid due to output amount being greater than input amount."
- ),
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for ExtractTxError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Self::AbsurdFeeRate { .. }
- | Self::MissingInputAmount { .. }
- | Self::SendingTooMuch { .. } => None,
- }
- }
-}
-
-/// 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 PSBT inputs vector.
- length: usize,
- },
- /// The index is out of bounds for the `psbt.unsigned_tx.input` vector.
- TxInput {
- /// Attempted index access.
- index: usize,
- /// Length of the PSBT's unsigned transaction input vector.
- length: usize,
- },
-}
-
-impl From<Infallible> for IndexOutOfBoundsError {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for IndexOutOfBoundsError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Inputs { ref index, ref length } => write!(
- f,
- "index {} is out-of-bounds for PSBT inputs vector length {}",
- index, length
- ),
- Self::TxInput { ref index, ref length } => write!(
- f,
- "index {} is out-of-bounds for PSBT unsigned tx input vector length {}",
- index, length
- ),
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for IndexOutOfBoundsError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Self::Inputs { .. } | Self::TxInput { .. } => None,
- }
- }
-}
-
-#[cfg(feature = "base64")]
-mod display_from_str {
- use core::convert::Infallible;
- use core::fmt;
- use core::str::FromStr;
-
- use base64::display::Base64Display;
- use base64::prelude::{Engine as _, BASE64_STANDARD};
- use internals::write_err;
-
- use super::{Error, Psbt};
-
- /// Error encountered during PSBT decoding from Base64 string.
- #[derive(Debug)]
- #[non_exhaustive]
- pub enum PsbtParseError {
- /// Error in internal PSBT data structure.
- PsbtEncoding(Error),
- /// Error in PSBT Base64 encoding.
- Base64Encoding(::base64::DecodeError),
- }
-
- impl From<Infallible> for PsbtParseError {
- fn from(never: Infallible) -> Self { match never {} }
- }
-
- impl fmt::Display for PsbtParseError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::PsbtEncoding(ref e) =>
- write_err!(f, "error in internal PSBT data structure"; e),
- Self::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)> {
- match self {
- Self::PsbtEncoding(e) => Some(e),
- Self::Base64Encoding(e) => Some(e),
- }
- }
- }
-
- impl fmt::Display for Psbt {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", Base64Display::new(&self.serialize(), &BASE64_STANDARD))
- }
- }
-
- impl FromStr for Psbt {
- type Err = PsbtParseError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let data = BASE64_STANDARD.decode(s).map_err(PsbtParseError::Base64Encoding)?;
- Self::deserialize(&data).map_err(PsbtParseError::PsbtEncoding)
- }
- }
-}
-#[cfg(feature = "base64")]
-pub use self::display_from_str::PsbtParseError;
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for Psbt {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- Ok(Self {
- unsigned_tx: u.arbitrary()?,
- version: u.arbitrary()?,
- xpub: u.arbitrary()?,
- proprietary: u.arbitrary()?,
- unknown: u.arbitrary()?,
- inputs: u.arbitrary()?,
- outputs: u.arbitrary()?,
- })
- }
-}
-
-#[cfg(test)]
-mod tests {
- use alloc::string::ToString;
- use core::str::FromStr;
-
- use hashes::{hash160, ripemd160, sha256};
- use hex_unstable::hex;
- #[cfg(feature = "rand")]
- #[cfg(feature = "std")]
- use {
- crate::bip32::Fingerprint, crate::locktime, crate::script::ScriptPubKeyBufExt as _,
- crate::witness_version::WitnessVersion, crate::WitnessProgram, secp256k1::SecretKey,
- };
-
- use super::*;
- use crate::bip32::{ChildNumber, DerivationPath};
- use crate::locktime::absolute;
- use crate::network::NetworkKind;
- use crate::psbt::serialize::{Deserialize, Serialize};
- #[cfg(feature = "rand")]
- #[cfg(feature = "std")]
- use crate::script::ScriptBufExt as _;
- use crate::script::{RedeemScriptBuf, ScriptPubKeyBuf, ScriptSigBuf, WitnessScriptBuf};
- use crate::transaction::{self, OutPoint, TxIn};
- use crate::witness::Witness;
- use crate::{hex, Sequence};
-
- #[track_caller]
- pub fn hex_psbt(s: &str) -> Result<Psbt, crate::psbt::error::Error> {
- let r = hex::decode_to_vec(s);
- match r {
- Err(_e) => panic!("unable to parse hex string {}", s),
- Ok(v) => Psbt::deserialize(&v),
- }
- }
-
- #[track_caller]
- fn psbt_with_amounts(input: u64, output: u64) -> Psbt {
- Psbt {
- unsigned_tx: Transaction {
- version: transaction::Version::TWO,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![TxIn {
- previous_output: OutPoint {
- txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126"
- .parse()
- .unwrap(),
- vout: 0,
- },
- script_sig: ScriptSigBuf::new(),
- sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
- witness: Witness::default(),
- }],
- outputs: vec![TxOut {
- amount: Amount::from_sat(output).unwrap(),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix(
- "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
- )
- .unwrap(),
- }],
- },
- xpub: Default::default(),
- version: 0,
- proprietary: BTreeMap::new(),
- unknown: BTreeMap::new(),
-
- inputs: vec![Input {
- witness_utxo: Some(TxOut {
- amount: Amount::from_sat(input).unwrap(),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix(
- "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
- )
- .unwrap(),
- }),
- ..Default::default()
- }],
- outputs: vec![],
- }
- }
-
- #[test]
- fn trivial_psbt() {
- let psbt = Psbt {
- unsigned_tx: Transaction {
- version: transaction::Version::TWO,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![],
- outputs: vec![],
- },
- xpub: Default::default(),
- version: 0,
- proprietary: BTreeMap::new(),
- unknown: BTreeMap::new(),
-
- inputs: vec![],
- outputs: vec![],
- };
- assert_eq!(psbt.serialize_hex(), "70736274ff01000a0200000000000000000000");
- }
-
- #[test]
- fn psbt_uncompressed_key() {
- let psbt = hex_psbt("70736274ff01003302000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff000000000000420204bb0d5d0cca36e7b9c80f63bc04c1240babb83bcd2803ef7ac8b6e2af594291daec281e856c98d210c5ab14dfd5828761f8ee7d5f45ca21ad3e4c4b41b747a3a047304402204f67e2afb76142d44fae58a2495d33a3419daa26cd0db8d04f3452b63289ac0f022010762a9fb67e94cc5cad9026f6dc99ff7f070f4278d30fbc7d0c869dd38c7fe70100").unwrap();
- assert!(psbt.inputs[0].partial_sigs.len() == 1);
- let pk = psbt.inputs[0].partial_sigs.iter().next().unwrap().0;
- assert!(!pk.compressed());
- }
-
- #[test]
- fn psbt_insufficient_byte_size() {
- // construct a key where the key byte size (0x02) is less than even the type value length (in this case, 3)
- let key_data = hex!("02fd07ffababababab");
- let got = super::raw::Key::decode(&mut key_data.as_slice()).unwrap_err();
- assert!(matches!(got, Error::InvalidKey(_)));
- }
-
- #[test]
- fn psbt_high_fee_checks() {
- let psbt = psbt_with_amounts(Amount::MAX.to_sat(), 1000);
-
- // We cannot create an expected fee rate to test against because `FeeRate::from_sat_per_mvb` is private.
- // Large fee rate errors if we pass in 1 sat/vb so just use this to get the error fee rate returned.
- let error_fee_rate = psbt
- .clone()
- .extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_vb(1))
- .map_err(|e| match e {
- ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
- other => panic!("expected AbsurdFeeRate error, got {other:?}"),
- })
- .unwrap_err();
-
- // In `internal_extract_tx_with_fee_rate_limit` when we do fee / weight
- // we manually saturate to `FeeRate::MAX`.
- assert!(psbt.clone().extract_tx_with_fee_rate_limit(FeeRate::MAX).is_ok());
-
- // These error because the fee rate is above the limit as expected.
- assert_eq!(
- psbt.clone().extract_tx().map_err(|e| match e {
- ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
- other => panic!("expected AbsurdFeeRate error, got {other:?}"),
- }),
- Err(error_fee_rate)
- );
- assert_eq!(
- psbt.extract_tx_fee_rate_limit().map_err(|e| match e {
- ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
- other => panic!("expected AbsurdFeeRate error, got {other:?}"),
- }),
- Err(error_fee_rate)
- );
-
- // No one is using an ~50 BTC fee so if we can handle this
- // then the `FeeRate` restrictions are fine for PSBT usage.
- let psbt = psbt_with_amounts(Amount::from_btc_u16(50).to_sat(), 1000); // fee = 50 BTC - 1000 sats
- assert!(psbt.extract_tx_with_fee_rate_limit(FeeRate::MAX).is_ok());
-
- // Testing that extract_tx will error at 25k sat/vbyte (6250000 sat/kwu)
- assert_eq!(
- psbt_with_amounts(2076001, 1000).extract_tx().map_err(|e| match e {
- ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
- other => panic!("expected AbsurdFeeRate error, got {other:?}"),
- }),
- Err(FeeRate::from_sat_per_kwu(6250003)) // 6250000 is 25k sat/vbyte
- );
-
- // Lowering the input satoshis by 1 lowers the sat/kwu by 3
- // Putting it exactly at 25k sat/vbyte
- assert!(psbt_with_amounts(2076000, 1000).extract_tx().is_ok());
- }
-
- #[test]
- fn serialize_then_deserialize_output() {
- let seed = hex!("000102030405060708090a0b0c0d0e0f");
-
- let mut hd_keypaths: BTreeMap<secp256k1::PublicKey, KeySource> = Default::default();
-
- let mut sk: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed);
-
- let fprint = sk.fingerprint();
-
- let dpath: Vec<ChildNumber> = vec![
- ChildNumber::ZERO_NORMAL,
- ChildNumber::ONE_NORMAL,
- ChildNumber::from_normal_idx(2).unwrap(),
- ChildNumber::from_normal_idx(4).unwrap(),
- ChildNumber::from_normal_idx(42).unwrap(),
- ChildNumber::from_hardened_idx(69).unwrap(),
- ChildNumber::from_normal_idx(420).unwrap(),
- ChildNumber::from_normal_idx(31337).unwrap(),
- ];
-
- sk = sk.derive_xpriv(&dpath).unwrap();
-
- let pk = Xpub::from_xpriv(&sk);
-
- hd_keypaths.insert(pk.public_key, (fprint, dpath.into()));
-
- let expected: Output = Output {
- redeem_script: Some(
- RedeemScriptBuf::from_hex_no_length_prefix(
- "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
- )
- .unwrap(),
- ),
- witness_script: Some(
- WitnessScriptBuf::from_hex_no_length_prefix(
- "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
- )
- .unwrap(),
- ),
- bip32_derivation: hd_keypaths,
- ..Default::default()
- };
-
- let actual = Output::deserialize(&expected.serialize()).unwrap();
-
- assert_eq!(expected, actual);
- }
-
- #[test]
- fn serialize_then_deserialize_global() {
- let expected = Psbt {
- unsigned_tx: Transaction {
- version: transaction::Version::TWO,
- lock_time: absolute::LockTime::from_consensus(1257139),
- inputs: vec![TxIn {
- previous_output: OutPoint {
- txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126"
- .parse()
- .unwrap(),
- vout: 0,
- },
- script_sig: ScriptSigBuf::new(),
- sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
- witness: Witness::default(),
- }],
- outputs: vec![
- TxOut {
- amount: Amount::from_sat_u32(99_999_699),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix(
- "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
- )
- .unwrap(),
- },
- TxOut {
- amount: Amount::from_sat_u32(100_000_000),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix(
- "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
- )
- .unwrap(),
- },
- ],
- },
- xpub: Default::default(),
- version: 0,
- proprietary: Default::default(),
- unknown: Default::default(),
- inputs: vec![Input::default()],
- outputs: vec![Output::default(), Output::default()],
- };
-
- let actual: Psbt = Psbt::deserialize(&expected.serialize()).unwrap();
- assert_eq!(expected, actual);
- }
-
- #[test]
- fn serialize_then_deserialize_psbtkvpair() {
- let expected = raw::Pair {
- key: raw::Key { type_value: 0u64, key_data: vec![42u8, 69u8] },
- value: vec![69u8, 42u8, 4u8],
- };
-
- let actual = raw::Pair::deserialize(&expected.serialize()).unwrap();
-
- assert_eq!(expected, actual);
- }
-
- #[test]
- fn deserialize_and_serialize_psbt_with_two_partial_sigs() {
- let hex = "70736274ff0100890200000001207ae985d787dfe6143d5c58fad79cc7105e0e799fcf033b7f2ba17e62d7b3200000000000ffffffff02563d03000000000022002019899534b9a011043c0dd57c3ff9a381c3522c5f27c6a42319085b56ca543a1d6adc020000000000220020618b47a07ebecca4e156edb1b9ea7c24bdee0139fc049237965ffdaf56d5ee73000000000001012b801a0600000000002200201148e93e9315e37dbed2121be5239257af35adc03ffdfc5d914b083afa44dab82202025fe7371376d53cf8a2783917c28bf30bd690b0a4d4a207690093ca2b920ee076473044022007e06b362e89912abd4661f47945430739b006a85d1b2a16c01dc1a4bd07acab022061576d7aa834988b7ab94ef21d8eebd996ea59ea20529a19b15f0c9cebe3d8ac01220202b3fe93530020a8294f0e527e33fbdff184f047eb6b5a1558a352f62c29972f8a473044022002787f926d6817504431ee281183b8119b6845bfaa6befae45e13b6d430c9d2f02202859f149a6cd26ae2f03a107e7f33c7d91730dade305fe077bae677b5d44952a01010547522102b3fe93530020a8294f0e527e33fbdff184f047eb6b5a1558a352f62c29972f8a21025fe7371376d53cf8a2783917c28bf30bd690b0a4d4a207690093ca2b920ee07652ae0001014752210283ef76537f2d58ae3aa3a4bd8ae41c3f230ccadffb1a0bd3ca504d871cff05e7210353d79cc0cb1396f4ce278d005f16d948e02a6aec9ed1109f13747ecb1507b37b52ae00010147522102b3937241777b6665e0d694e52f9c1b188433641df852da6fc42187b5d8a368a321034cdd474f01cc5aa7ff834ad8bcc882a87e854affc775486bc2a9f62e8f49bd7852ae00";
- let psbt = hex_psbt(hex).unwrap();
- assert_eq!(hex, psbt.serialize_hex());
- }
-
- #[cfg(feature = "serde")]
- #[test]
- fn serde_psbt() {
- //! Create a full PSBT value with various fields filled and make sure it can be JSONized.
- use hashes::sha256d;
-
- use crate::psbt::map::Input;
-
- // create some values to use in the PSBT
- let tx = Transaction {
- version: transaction::Version::ONE,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![TxIn {
- previous_output: OutPoint {
- txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389"
- .parse()
- .unwrap(),
- vout: 0,
- },
- script_sig: ScriptSigBuf::from_hex_no_length_prefix(
- "160014be18d152a9b012039daf3da7de4f53349eecb985",
- )
- .unwrap(),
- sequence: Sequence::MAX,
- witness: Witness::from_slice(&[hex!(
- "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"
- )]),
- }],
- outputs: vec![TxOut {
- amount: Amount::from_sat(190_303_501_938).unwrap(),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix(
- "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
- )
- .unwrap(),
- }],
- };
- let unknown: BTreeMap<raw::Key, Vec<u8>> =
- vec![(raw::Key { type_value: 42, key_data: vec![0, 1] }, vec![3, 4, 5])]
- .into_iter()
- .collect();
- let key_source = ("deadbeef".parse().unwrap(), "0'/1".parse().unwrap());
- let keypaths: BTreeMap<secp256k1::PublicKey, KeySource> = vec![(
- "0339880dc92394b7355e3d0439fa283c31de7590812ea011c4245c0674a685e883".parse().unwrap(),
- key_source.clone(),
- )]
- .into_iter()
- .collect();
-
- let proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>> = vec![(
- raw::ProprietaryKey {
- prefix: "prefx".as_bytes().to_vec(),
- subtype: 42,
- key: "test_key".as_bytes().to_vec(),
- },
- vec![5, 6, 7],
- )]
- .into_iter()
- .collect();
-
- let psbt = Psbt {
- version: 0,
- xpub: {
- let xpub: Xpub =
- "xpub661MyMwAqRbcGoRVtwfvzZsq2VBJR1LAHfQstHUoxqDorV89vRoMxUZ27kLrraAj6MPi\
- QfrDb27gigC1VS1dBXi5jGpxmMeBXEkKkcXUTg4".parse().unwrap();
- vec![(xpub, key_source)].into_iter().collect()
- },
- unsigned_tx: {
- let mut unsigned = tx.clone();
- unsigned.inputs[0].previous_output.txid = tx.compute_txid();
- unsigned.inputs[0].script_sig = ScriptSigBuf::new();
- unsigned.inputs[0].witness = Witness::default();
- unsigned
- },
- proprietary: proprietary.clone(),
- unknown: unknown.clone(),
-
- inputs: vec![
- Input {
- non_witness_utxo: Some(tx),
- witness_utxo: Some(TxOut {
- amount: Amount::from_sat(190_303_501_938).unwrap(),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
- }),
- sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<PsbtSighashType>().unwrap()),
- redeem_script: Some(vec![0x51].into()),
- witness_script: None,
- partial_sigs: vec![(
- "0339880dc92394b7355e3d0439fa283c31de7590812ea011c4245c0674a685e883".parse().unwrap(),
- "304402204f67e2afb76142d44fae58a2495d33a3419daa26cd0db8d04f3452b63289ac0f022010762a9fb67e94cc5cad9026f6dc99ff7f070f4278d30fbc7d0c869dd38c7fe701".parse().unwrap(),
- )].into_iter().collect(),
- bip32_derivation: keypaths.clone(),
- final_script_witness: Some(Witness::from_slice(&[vec![1, 3], vec![5]])),
- ripemd160_preimages: vec![(ripemd160::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(),
- sha256_preimages: vec![(sha256::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(),
- hash160_preimages: vec![(hash160::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(),
- hash256_preimages: vec![(sha256d::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(),
- proprietary: proprietary.clone(),
- unknown: unknown.clone(),
- ..Default::default()
- }
- ],
- outputs: vec![
- Output {
- bip32_derivation: keypaths,
- proprietary,
- unknown,
- ..Default::default()
- }
- ],
- };
- let encoded = serde_json::to_string(&psbt).unwrap();
- let decoded: Psbt = serde_json::from_str(&encoded).unwrap();
- assert_eq!(psbt, decoded);
- }
-
- mod bip_vectors {
- use super::*;
- use crate::psbt::map::Map;
-
- #[test]
- #[should_panic(expected = "InvalidMagic")]
- fn invalid_vector_1() {
- hex_psbt("0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300").unwrap();
- }
-
- #[cfg(feature = "base64")]
- #[test]
- #[should_panic(expected = "InvalidMagic")]
- fn invalid_vector_1_base64() {
- "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==".parse::<Psbt>().unwrap();
- }
-
- #[test]
- #[should_panic(expected = "ConsensusEncoding")]
- fn invalid_vector_2() {
- hex_psbt("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000")
- .unwrap();
- }
-
- #[cfg(feature = "base64")]
- #[test]
- #[should_panic(expected = "ConsensusEncoding")]
- fn invalid_vector_2_base64() {
- "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==".parse::<Psbt>()
- .unwrap();
- }
-
- #[test]
- #[should_panic(expected = "UnsignedTxHasScriptSigs")]
- fn invalid_vector_3() {
- hex_psbt("70736274ff0100fd0a010200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be4000000006a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000").unwrap();
- }
-
- #[cfg(feature = "base64")]
- #[test]
- #[should_panic(expected = "UnsignedTxHasScriptSigs")]
- fn invalid_vector_3_base64() {
- "cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA=".parse::<Psbt>().unwrap();
- }
-
- #[test]
- #[should_panic(expected = "MustHaveUnsignedTx")]
- fn invalid_vector_4() {
- hex_psbt("70736274ff000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000000").unwrap();
- }
-
- #[cfg(feature = "base64")]
- #[test]
- #[should_panic(expected = "MustHaveUnsignedTx")]
- fn invalid_vector_4_base64() {
- "cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==".parse::<Psbt>().unwrap();
- }
-
- #[test]
- #[should_panic(expected = "DuplicateKey(Key { type_value: 0, key_data: [] })")]
- fn invalid_vector_5() {
- hex_psbt("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000").unwrap();
- }
-
- #[cfg(feature = "base64")]
- #[test]
- #[should_panic(expected = "DuplicateKey(Key { type_value: 0, key_data: [] })")]
- fn invalid_vector_5_base64() {
- "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQA/AgAAAAH//////////////////////////////////////////wAAAAAA/////wEAAAAAAAAAAANqAQAAAAAAAAAA".parse::<Psbt>().unwrap();
- }
-
- #[test]
- fn valid_vector_1() {
- let unserialized = Psbt {
- unsigned_tx: Transaction {
- version: transaction::Version::TWO,
- lock_time: absolute::LockTime::from_consensus(1257139),
- inputs: vec![
- TxIn {
- previous_output: OutPoint {
- txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126".parse().unwrap(),
- vout: 0,
- },
- script_sig: ScriptSigBuf::new(),
- sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
- witness: Witness::default(),
- }
- ],
- outputs: vec![
- TxOut {
- amount: Amount::from_sat_u32(99_999_699),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
- },
- TxOut {
- amount: Amount::from_sat_u32(100_000_000),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
- },
- ],
- },
- xpub: Default::default(),
- version: 0,
- proprietary: BTreeMap::new(),
- unknown: BTreeMap::new(),
-
- inputs: vec![
- Input {
- non_witness_utxo: Some(Transaction {
- version: transaction::Version::ONE,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![
- TxIn {
- previous_output: OutPoint {
- txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse().unwrap(),
- vout: 1,
- },
- script_sig: ScriptSigBuf::from_hex_no_length_prefix("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(),
- sequence: Sequence::MAX,
- witness: Witness::from_slice(&[
- hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").as_slice(),
- hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").as_slice(),
- ]),
- },
- TxIn {
- previous_output: OutPoint {
- txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886".parse().unwrap(),
- vout: 1,
- },
- script_sig: ScriptSigBuf::from_hex_no_length_prefix("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(),
- sequence: Sequence::MAX,
- witness: Witness::from_slice(&[
- hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").as_slice(),
- hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").as_slice(),
- ]),
- }
- ],
- outputs: vec![
- TxOut {
- amount: Amount::from_sat_u32(200_000_000),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
- },
- TxOut {
- amount: Amount::from_sat(190_303_501_938).unwrap(),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
- },
- ],
- }),
- ..Default::default()
- },
- ],
- outputs: vec![
- Output {
- ..Default::default()
- },
- Output {
- ..Default::default()
- },
- ],
- };
-
- let base16str = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000";
-
- assert_eq!(unserialized.serialize_hex(), base16str);
- assert_eq!(unserialized, hex_psbt(base16str).unwrap());
-
- #[cfg(feature = "base64")]
- {
- let base64str = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA";
- assert_eq!(base64str.parse::<Psbt>().unwrap(), unserialized);
- assert_eq!(base64str, unserialized.to_string());
- assert_eq!(base64str.parse::<Psbt>().unwrap(), hex_psbt(base16str).unwrap());
- }
- }
-
- #[test]
- fn valid_vector_2() {
- let psbt = hex_psbt("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000").unwrap();
-
- assert_eq!(psbt.inputs.len(), 2);
- assert_eq!(psbt.outputs.len(), 2);
-
- assert!(&psbt.inputs[0].final_script_sig.is_some());
-
- let redeem_script = psbt.inputs[1].redeem_script.as_ref().unwrap();
- let expected_out = ScriptPubKeyBuf::from_hex_no_length_prefix(
- "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
- )
- .unwrap();
-
- assert!(redeem_script.is_p2wpkh());
- assert_eq!(
- redeem_script.to_p2sh().unwrap(),
- psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey
- );
- assert_eq!(redeem_script.to_p2sh().unwrap(), expected_out);
-
- for output in psbt.outputs {
- assert_eq!(output.get_pairs().len(), 0)
- }
- }
-
- #[test]
- fn valid_vector_3() {
- let psbt = hex_psbt("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000").unwrap();
-
- assert_eq!(psbt.inputs.len(), 1);
- assert_eq!(psbt.outputs.len(), 2);
-
- let tx_input = &psbt.unsigned_tx.inputs[0];
- let psbt_non_witness_utxo = psbt.inputs[0].non_witness_utxo.as_ref().unwrap();
-
- assert_eq!(tx_input.previous_output.txid, psbt_non_witness_utxo.compute_txid());
- assert!(psbt_non_witness_utxo.outputs[tx_input.previous_output.vout as usize]
- .script_pubkey
- .is_p2pkh());
- assert_eq!(
- psbt.inputs[0].sighash_type.as_ref().unwrap().ecdsa_hash_ty().unwrap(),
- EcdsaSighashType::All
- );
- }
-
- #[test]
- fn valid_vector_4() {
- let psbt = hex_psbt("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000").unwrap();
-
- assert_eq!(psbt.inputs.len(), 2);
- assert_eq!(psbt.outputs.len(), 2);
-
- assert!(&psbt.inputs[0].final_script_sig.is_none());
- assert!(&psbt.inputs[1].final_script_sig.is_none());
-
- let redeem_script = psbt.inputs[1].redeem_script.as_ref().unwrap();
- let expected_out = ScriptPubKeyBuf::from_hex_no_length_prefix(
- "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
- )
- .unwrap();
-
- assert!(redeem_script.is_p2wpkh());
- assert_eq!(
- redeem_script.to_p2sh().unwrap(),
- psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey
- );
- assert_eq!(redeem_script.to_p2sh().unwrap(), expected_out);
-
- for output in psbt.outputs {
- assert!(!output.get_pairs().is_empty())
- }
- }
-
- #[test]
- fn valid_vector_5() {
- let psbt = hex_psbt("70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000").unwrap();
-
- assert_eq!(psbt.inputs.len(), 1);
- assert_eq!(psbt.outputs.len(), 1);
-
- assert!(&psbt.inputs[0].final_script_sig.is_none());
-
- let redeem_script = psbt.inputs[0].redeem_script.as_ref().unwrap();
- let expected_out = ScriptPubKeyBuf::from_hex_no_length_prefix(
- "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87",
- )
- .unwrap();
-
- assert!(redeem_script.is_p2wsh());
- assert_eq!(
- redeem_script.to_p2sh().unwrap(),
- psbt.inputs[0].witness_utxo.as_ref().unwrap().script_pubkey
- );
-
- assert_eq!(redeem_script.to_p2sh().unwrap(), expected_out);
- }
-
- #[test]
- fn valid_vector_6() {
- let psbt = hex_psbt("70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000").unwrap();
-
- assert_eq!(psbt.inputs.len(), 1);
- assert_eq!(psbt.outputs.len(), 1);
-
- let tx = &psbt.unsigned_tx;
- assert_eq!(
- tx.compute_txid(),
- "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115".parse().unwrap(),
- );
-
- let mut unknown: BTreeMap<raw::Key, Vec<u8>> = BTreeMap::new();
- let key: raw::Key =
- raw::Key { type_value: 0x0fu64, key_data: hex!("010203040506070809").to_vec() };
- let value = hex!("0102030405060708090a0b0c0d0e0f").to_vec();
-
- unknown.insert(key, value);
-
- assert_eq!(psbt.inputs[0].unknown, unknown)
- }
- }
-
- mod bip_371_vectors {
- use super::*;
-
- #[test]
- fn invalid_vectors() {
- let err = hex_psbt("70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a075701172102fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232000000").unwrap_err();
- assert_eq!(err.to_string(), "invalid xonly public key");
- let err = hex_psbt("70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757011342173bb3d36c074afb716fec6307a069a2e450b995f3c82785945ab8df0e24260dcd703b0cbf34de399184a9481ac2b3586db6601f026a77f7e4938481bc34751701aa000000").unwrap_err();
- #[cfg(feature = "std")]
- assert_eq!(err.to_string(), "invalid Taproot signature");
- #[cfg(not(feature = "std"))]
- assert_eq!(
- err.to_string(),
- "invalid Taproot signature: invalid Taproot signature size: 66"
- );
- let err = hex_psbt("70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757221602fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000000000").unwrap_err();
- assert_eq!(err.to_string(), "invalid xonly public key");
- let err = hex_psbt("70736274ff01007d020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02887b0100000000001600142382871c7e8421a00093f754d91281e675874b9f606b042a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000001052102fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa23200").unwrap_err();
- assert_eq!(err.to_string(), "invalid xonly public key");
- let err = hex_psbt("70736274ff01007d020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02887b0100000000001600142382871c7e8421a00093f754d91281e675874b9f606b042a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07570000220702fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da7560000800100008000000080010000000000000000").unwrap_err();
- assert_eq!(err.to_string(), "invalid xonly public key");
- let err = hex_psbt("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6924214022cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b094089756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb0000").unwrap_err();
- #[cfg(feature = "std")]
- assert_eq!(err.to_string(), "invalid hash when parsing slice");
- #[cfg(not(feature = "std"))]
- assert_eq!(
- err.to_string(),
- "invalid hash when parsing slice: could not convert slice to array"
- );
- let err = hex_psbt("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b094289756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb01010000").unwrap_err();
- #[cfg(feature = "std")]
- assert_eq!(err.to_string(), "invalid Taproot signature");
- #[cfg(not(feature = "std"))]
- assert_eq!(
- err.to_string(),
- "invalid Taproot signature: invalid Taproot signature size: 66"
- );
- let err = hex_psbt("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b093989756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb0000").unwrap_err();
- #[cfg(feature = "std")]
- assert_eq!(err.to_string(), "invalid Taproot signature");
- #[cfg(not(feature = "std"))]
- assert_eq!(
- err.to_string(),
- "invalid Taproot signature: invalid Taproot signature size: 57"
- );
- let err = hex_psbt("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926315c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f80023202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc00000").unwrap_err();
- assert_eq!(err.to_string(), "invalid control block");
- let err = hex_psbt("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926115c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e123202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc00000").unwrap_err();
- assert_eq!(err.to_string(), "invalid control block");
- }
-
- fn rtt_psbt(psbt: Psbt) {
- let enc = Psbt::serialize(&psbt);
- let psbt2 = Psbt::deserialize(&enc).unwrap();
- assert_eq!(psbt, psbt2);
- }
-
- #[test]
- fn valid_psbt_vectors() {
- let psbt = hex_psbt("70736274ff010052020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a01000000160014768e1eeb4cf420866033f80aceff0f9720744969000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232002202036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d818772b2da7540000800100008000000080000000000000000000").unwrap();
- let internal_key = psbt.inputs[0].tap_internal_key.unwrap();
- assert!(psbt.inputs[0].tap_key_origins.contains_key(&internal_key));
- rtt_psbt(psbt);
-
- // vector 2
- let psbt = hex_psbt("70736274ff010052020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a01000000160014768e1eeb4cf420866033f80aceff0f9720744969000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757011340bb53ec917bad9d906af1ba87181c48b86ace5aae2b53605a725ca74625631476fc6f5baedaf4f2ee0f477f36f58f3970d5b8273b7e497b97af2e3f125c97af342116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232002202036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d818772b2da7540000800100008000000080000000000000000000").unwrap();
- let internal_key = psbt.inputs[0].tap_internal_key.unwrap();
- assert!(psbt.inputs[0].tap_key_origins.contains_key(&internal_key));
- assert!(psbt.inputs[0].tap_key_sig.is_some());
- rtt_psbt(psbt);
-
- // vector 3
- let psbt = hex_psbt("70736274ff01005e020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000").unwrap();
- let internal_key = psbt.outputs[0].tap_internal_key.unwrap();
- assert!(psbt.outputs[0].tap_key_origins.contains_key(&internal_key));
- rtt_psbt(psbt);
-
- // vector 4
- let psbt = hex_psbt("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f823202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc04215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac097c6e6fea5ff714ff5724499990810e406e98aa10f5bf7e5f6784bc1d0a9a6ce23204320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2acc06215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f82320fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9acc021162cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d23901cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09772b2da7560000800100008002000080000000000000000021164320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b23901115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f8772b2da75600008001000080010000800000000000000000211650929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2116fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca939016f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970772b2da7560000800100008003000080000000000000000001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0011820f0362e2f75a6f420a5bde3eb221d96ae6720cf25f81890c95b1d775acb515e65000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000").unwrap();
- assert!(psbt.inputs[0].tap_internal_key.is_some());
- assert!(psbt.inputs[0].tap_merkle_root.is_some());
- assert!(!psbt.inputs[0].tap_key_origins.is_empty());
- assert!(!psbt.inputs[0].tap_scripts.is_empty());
- rtt_psbt(psbt);
-
- // vector 5
- let psbt = hex_psbt("70736274ff01005e020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a010000002251200a8cbdc86de1ce1c0f9caeb22d6df7ced3683fe423e05d1e402a879341d6f6f5000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2320001052050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac001066f02c02220736e572900fe1252589a2143c8f3c79f71a0412d2353af755e9701c782694a02ac02c02220631c5f3b5832b8fbdebfb19704ceeb323c21f40f7a24f43d68ef0cc26b125969ac01c0222044faa49a0338de488c8dfffecdfb6f329f380bd566ef20c8df6d813eab1c4273ac210744faa49a0338de488c8dfffecdfb6f329f380bd566ef20c8df6d813eab1c42733901f06b798b92a10ed9a9d0bbfd3af173a53b1617da3a4159ca008216cd856b2e0e772b2da75600008001000080010000800000000003000000210750929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2107631c5f3b5832b8fbdebfb19704ceeb323c21f40f7a24f43d68ef0cc26b125969390118ace409889785e0ea70ceebb8e1ca892a7a78eaede0f2e296cf435961a8f4ca772b2da756000080010000800200008000000000030000002107736e572900fe1252589a2143c8f3c79f71a0412d2353af755e9701c782694a02390129a5b4915090162d759afd3fe0f93fa3326056d0b4088cb933cae7826cb8d82c772b2da7560000800100008003000080000000000300000000").unwrap();
- assert!(psbt.outputs[0].tap_internal_key.is_some());
- assert!(!psbt.outputs[0].tap_key_origins.is_empty());
- assert!(psbt.outputs[0].tap_tree.is_some());
- rtt_psbt(psbt);
-
- // vector 6
- let psbt = hex_psbt("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b0940bf818d9757d6ffeb538ba057fb4c1fc4e0f5ef186e765beb564791e02af5fd3d5e2551d4e34e33d86f276b82c99c79aed3f0395a081efcd2cc2c65dd7e693d7941144320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f840e1f1ab6fabfa26b236f21833719dc1d428ab768d80f91f9988d8abef47bfb863bb1f2a529f768c15f00ce34ec283cdc07e88f8428be28f6ef64043c32911811a4114fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca96f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae97040ec1f0379206461c83342285423326708ab031f0da4a253ee45aafa5b8c92034d8b605490f8cd13e00f989989b97e215faa36f12dee3693d2daccf3781c1757f66215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f823202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc04215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac097c6e6fea5ff714ff5724499990810e406e98aa10f5bf7e5f6784bc1d0a9a6ce23204320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2acc06215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f82320fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9acc021162cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d23901cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09772b2da7560000800100008002000080000000000000000021164320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b23901115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f8772b2da75600008001000080010000800000000000000000211650929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2116fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca939016f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970772b2da7560000800100008003000080000000000000000001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0011820f0362e2f75a6f420a5bde3eb221d96ae6720cf25f81890c95b1d775acb515e65000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000").unwrap();
- assert!(psbt.inputs[0].tap_internal_key.is_some());
- assert!(psbt.inputs[0].tap_merkle_root.is_some());
- assert!(!psbt.inputs[0].tap_scripts.is_empty());
- assert!(!psbt.inputs[0].tap_script_sigs.is_empty());
- assert!(!psbt.inputs[0].tap_key_origins.is_empty());
- rtt_psbt(psbt);
- }
- }
-
- #[test]
- fn invalid_vector_4617() {
- let err = hex_psbt("70736274ff01007374ff0103010000000000000000002e2873007374ff0107736205000000000000000000000000000000000006060005feffffff74ff01000a000000000000002cc760008530b38dac0100030500000074ff01070100000000000000000000000000c0316888e006000600050000736274ff00d90001007374ff41030100000000000a0a06002e2873007374ff01070100000000000000000000000000000000ff0000060600050000736274ff01000a0080000000000024c7600005193b1e400700030500000074ff0107010000000000a9c7df3f07000570ed62c76004c3ca95c5f90200010742420a0a000000000000").unwrap_err();
- match err {
- Error::IncorrectNonWitnessUtxo { index: 0, input_outpoint, non_witness_utxo_txid } => {
- assert_eq!(
- input_outpoint,
- "00000000000000000000000562730701ff74730073282e000000000000000000:0"
- .parse()
- .unwrap(),
- );
- assert_eq!(
- non_witness_utxo_txid,
- "9ed45fd3f73b038649bee6e763dbd70868745c48a0d2b0299f42c68f957995f4"
- .parse()
- .unwrap(),
- );
- }
- _ => panic!("expected output hash mismatch error, got {}", err),
- }
- }
-
- // Test vector from Bitcoin Core.
- // https://github.com/bitcoin/bitcoin/commit/9e13ccc50eec9d2efe0f472e6d50dc822df70d84
- #[test]
- fn non_witness_utxo_vout_out_of_bounds() {
- let err = hex_psbt("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000200feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000").unwrap_err();
- match err {
- Error::NonWitnessUtxoOutOfBounds { index, vout, non_witness_utxo_output_count } => {
- assert_eq!(index, 0);
- assert_eq!(vout, 33554432);
- assert_eq!(non_witness_utxo_output_count, 2);
- }
- _ => panic!("expected NonWitnessUtxoOutOfBounds error, got: {}", err),
- }
- }
-
- #[test]
- fn serialize_and_deserialize_preimage_psbt() {
- // create a sha preimage map
- let mut sha256_preimages = BTreeMap::new();
- sha256_preimages.insert(sha256::Hash::hash(&[1u8, 2u8]), vec![1u8, 2u8]);
- sha256_preimages.insert(sha256::Hash::hash(&[1u8]), vec![1u8]);
-
- // same for hash160
- let mut hash160_preimages = BTreeMap::new();
- hash160_preimages.insert(hash160::Hash::hash(&[1u8, 2u8]), vec![1u8, 2u8]);
- hash160_preimages.insert(hash160::Hash::hash(&[1u8]), vec![1u8]);
-
- // same vector as valid_vector_1 from BIPs with added
- let mut unserialized = Psbt {
- unsigned_tx: Transaction {
- version: transaction::Version::TWO,
- lock_time: absolute::LockTime::from_consensus(1257139),
- inputs: vec![
- TxIn {
- previous_output: OutPoint {
- txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126".parse().unwrap(),
- vout: 0,
- },
- script_sig: ScriptSigBuf::new(),
- sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
- witness: Witness::default(),
- }
- ],
- outputs: vec![
- TxOut {
- amount: Amount::from_sat_u32(99_999_699),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
- },
- TxOut {
-
- amount: Amount::from_sat_u32(100_000_000),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
- },
- ],
- },
- version: 0,
- xpub: Default::default(),
- proprietary: Default::default(),
- unknown: BTreeMap::new(),
-
- inputs: vec![
- Input {
- non_witness_utxo: Some(Transaction {
- version: transaction::Version::ONE,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![
- TxIn {
- previous_output: OutPoint {
- txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse().unwrap(),
- vout: 1,
- },
- script_sig: ScriptSigBuf::from_hex_no_length_prefix("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(),
- sequence: Sequence::MAX,
- witness: Witness::from_slice(&[
- hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").as_slice(),
- hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").as_slice(),
- ]),
- },
- TxIn {
- previous_output: OutPoint {
- txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886".parse().unwrap(),
- vout: 1,
- },
- script_sig: ScriptSigBuf::from_hex_no_length_prefix("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(),
- sequence: Sequence::MAX,
- witness: Witness::from_slice(&[
- hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").as_slice(),
- hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").as_slice(),
- ]),
- }
- ],
- outputs: vec![
- TxOut {
- amount: Amount::from_sat_u32(200_000_000),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
- },
- TxOut {
- amount: Amount::from_sat(190_303_501_938).unwrap(),
- script_pubkey: ScriptPubKeyBuf::from_hex_no_length_prefix("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
- },
- ],
- }),
- ..Default::default()
- },
- ],
- outputs: vec![
- Output {
- ..Default::default()
- },
- Output {
- ..Default::default()
- },
- ],
- };
- unserialized.inputs[0].hash160_preimages = hash160_preimages;
- unserialized.inputs[0].sha256_preimages = sha256_preimages;
-
- let rtt = hex_psbt(&unserialized.serialize_hex()).unwrap();
- assert_eq!(rtt, unserialized);
-
- // Now add a ripemd160 with incorrect preimage
- let mut ripemd160_preimages = BTreeMap::new();
- ripemd160_preimages.insert(ripemd160::Hash::hash(&[17u8]), vec![18u8]);
- unserialized.inputs[0].ripemd160_preimages = ripemd160_preimages;
-
- // Now the roundtrip should fail as the preimage is incorrect.
- let rtt: Result<Psbt, _> = hex_psbt(&unserialized.serialize_hex());
- assert!(rtt.is_err());
- }
-
-Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.