From b45a13cc92f8d9ca29b27a5b561d5dc0e64228a7 Mon Sep 17 00:00:00 2001 From: Borja Castellano Date: Sun, 20 Sep 2026 16:46:32 +0000 Subject: [PATCH] chore(key-wallet): drop the unused vendored PSBT implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A security report pointed out that `PartiallySignedTransaction::sign()` signs each input with whatever sighash type the input declares, with no BIP174 whitelist: a hostile coordinator can set SIGHASH_NONE|ANYONECANPAY (0x82) on an input and the victim's signature is then valid for any other transaction spending that same input. The claim is accurate. `sighash_ecdsa()` only checked that the value was one of the six standard types, and 0x82 is standard. The behaviour is not ours, though: the whole module is a verbatim copy of rust-bitcoin's psbt (same `sign()`, same `sighash_ecdsa()`, same doc comments as bitcoin 0.32.5), which leaves the check to the BIP174 Signer role. Rather than add the whitelist, the module goes away, because nothing used it: - No other module in this workspace referenced `key_wallet::psbt`. `TransactionBuilder` does its own signing and hardcodes `EcdsaSighashType::All`. - `key-wallet-ffi` and `dash-spv-ffi` expose no PSBT symbol, so the mobile wallets could never reach it. - dashpay/platform, the one repository that depends on key-wallet by git, has zero occurrences of `psbt` in any .rs/.toml/.ts/.js file. - The two examples that did use it, `ecdsa-psbt` and `taproot-psbt`, declared `required-features = ["bitcoinconsensus"]` — a feature that does not exist in dash/Cargo.toml — so they have never once compiled. Most of it was meaningless here anyway: `output_type()` resolved inputs to `Wpkh`, `Wsh`, `ShWpkh` and `Tr`, computing segwit and taproot sighashes for a chain that has neither. Only `Bare` and `Sh` were reachable. Removed with it: the BIP174 vector test and its fixtures, the two examples plus the dash -> key-wallet dev-dependency cycle they created, the `dash_deserialize_psbt` fuzz target and key-wallet as a dash-fuzz dependency, key-wallet's now-unused optional `base64` dependency, and `ScriptBuf::p2wpkh_script_code()` with `Script::v0_p2wpkh()`, whose only callers in the workspace were the PSBT signer and each other. `walletcreatefundedpsbt` and friends in rpc-client are Dash Core RPC methods, not this code, and stay. Verified: cargo build --workspace --all-targets, cargo clippy --workspace --all-targets --all-features with no warnings, cargo fmt --check, cargo test -p key-wallet --all-features (646 passed), cargo test -p dashcore --all-features (628 + 37 + 12 + 1 passed), cargo doc with no broken intra-doc links. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 1 - dash/Cargo.toml | 9 - dash/examples/ecdsa-psbt.rs | 314 ---- dash/examples/taproot-psbt.rs | 780 -------- dash/src/blockdata/script/borrowed.rs | 11 +- dash/src/blockdata/script/owned.rs | 17 - dash/src/blockdata/transaction/mod.rs | 9 +- dash/src/lib.rs | 2 +- fuzz/Cargo.toml | 5 - fuzz/fuzz-util.sh | 2 +- fuzz/fuzz_targets/dash/deserialize_psbt.rs | 50 - fuzz/generate-files.sh | 1 - key-wallet/Cargo.toml | 1 - key-wallet/README.md | 1 - key-wallet/TODO.md | 7 - key-wallet/src/lib.rs | 1 - key-wallet/src/psbt/error.rs | 239 --- key-wallet/src/psbt/macros.rs | 179 -- key-wallet/src/psbt/map/global.rs | 251 --- key-wallet/src/psbt/map/input.rs | 614 ------- key-wallet/src/psbt/map/mod.rs | 33 - key-wallet/src/psbt/map/output.rs | 185 -- key-wallet/src/psbt/mod.rs | 1856 -------------------- key-wallet/src/psbt/raw.rs | 227 --- key-wallet/src/psbt/serialize.rs | 499 ------ key-wallet/tests/data/combine_psbt_hex | 1 - key-wallet/tests/data/create_psbt_hex | 1 - key-wallet/tests/data/extract_tx_hex | 1 - key-wallet/tests/data/finalize_psbt_hex | 1 - key-wallet/tests/data/lex_combine_psbt_hex | 1 - key-wallet/tests/data/lex_psbt_1_hex | 1 - key-wallet/tests/data/lex_psbt_2_hex | 1 - key-wallet/tests/data/previous_tx_0_hex | 1 - key-wallet/tests/data/previous_tx_1_hex | 1 - key-wallet/tests/data/psbt1.hex | 1 - key-wallet/tests/data/psbt2.hex | 1 - key-wallet/tests/data/psbt_combined.hex | 1 - key-wallet/tests/data/sign_1_psbt_hex | 1 - key-wallet/tests/data/sign_2_psbt_hex | 1 - key-wallet/tests/data/update_1_psbt_hex | 1 - key-wallet/tests/data/update_2_psbt_hex | 1 - key-wallet/tests/psbt.rs | 477 ----- 42 files changed, 4 insertions(+), 5783 deletions(-) delete mode 100644 dash/examples/ecdsa-psbt.rs delete mode 100644 dash/examples/taproot-psbt.rs delete mode 100644 fuzz/fuzz_targets/dash/deserialize_psbt.rs delete mode 100644 key-wallet/src/psbt/error.rs delete mode 100644 key-wallet/src/psbt/macros.rs delete mode 100644 key-wallet/src/psbt/map/global.rs delete mode 100644 key-wallet/src/psbt/map/input.rs delete mode 100644 key-wallet/src/psbt/map/mod.rs delete mode 100644 key-wallet/src/psbt/map/output.rs delete mode 100644 key-wallet/src/psbt/mod.rs delete mode 100644 key-wallet/src/psbt/raw.rs delete mode 100644 key-wallet/src/psbt/serialize.rs delete mode 100644 key-wallet/tests/data/combine_psbt_hex delete mode 100644 key-wallet/tests/data/create_psbt_hex delete mode 100644 key-wallet/tests/data/extract_tx_hex delete mode 100644 key-wallet/tests/data/finalize_psbt_hex delete mode 100644 key-wallet/tests/data/lex_combine_psbt_hex delete mode 100644 key-wallet/tests/data/lex_psbt_1_hex delete mode 100644 key-wallet/tests/data/lex_psbt_2_hex delete mode 100644 key-wallet/tests/data/previous_tx_0_hex delete mode 100644 key-wallet/tests/data/previous_tx_1_hex delete mode 100644 key-wallet/tests/data/psbt1.hex delete mode 100644 key-wallet/tests/data/psbt2.hex delete mode 100644 key-wallet/tests/data/psbt_combined.hex delete mode 100644 key-wallet/tests/data/sign_1_psbt_hex delete mode 100644 key-wallet/tests/data/sign_2_psbt_hex delete mode 100644 key-wallet/tests/data/update_1_psbt_hex delete mode 100644 key-wallet/tests/data/update_2_psbt_hex delete mode 100644 key-wallet/tests/psbt.rs diff --git a/README.md b/README.md index 609d965e9..bc4374d06 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Supports (or should support) * De/serialization of blocks and transactions * Script de/serialization * Private keys and address creation, de/serialization and validation (including full BIP32 support) -* PSBT creation, manipulation, merging and finalization * Pay-to-contract support as in Appendix A of the [Blockstream sidechains whitepaper](https://www.blockstream.com/sidechains.pdf) * JSONRPC interaction with Dash Core * FFI bindings for C/Swift integration (dash-spv-ffi, key-wallet-ffi) diff --git a/dash/Cargo.toml b/dash/Cargo.toml index 65ebd8e99..cb958f9be 100644 --- a/dash/Cargo.toml +++ b/dash/Cargo.toml @@ -72,19 +72,10 @@ bincode = { workspace = true, features = ["serde"] } assert_matches = "1.5.0" dashcore = { path = ".", features = ["core-block-hash-use-x11", "message_verification", "quorum_validation", "signer"] } criterion = "0.5" -key-wallet = { path = "../key-wallet" } [[example]] name = "handshake" -[[example]] -name = "ecdsa-psbt" -required-features = ["bitcoinconsensus"] - -[[example]] -name = "taproot-psbt" -required-features = ["rand-std", "bitcoinconsensus"] - [[bench]] name = "transaction" harness = false diff --git a/dash/examples/ecdsa-psbt.rs b/dash/examples/ecdsa-psbt.rs deleted file mode 100644 index e958165fd..000000000 --- a/dash/examples/ecdsa-psbt.rs +++ /dev/null @@ -1,314 +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 `dashd` and `dash-cli`. -//! -//! ## Example Setup -//! -//! 1. Start dash Core in Regtest mode, for example: -//! -//! `dashd -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 `dash-cli`, for example: -//! -//! `alias bt=dash-cli -rpcuser=admin -rpcpassword=pass -rpcport=18443` -//! -//! 3. Create (or load) a default wallet, for example: -//! -//! `bt createwallet ` -//! -//! 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::boxed::Box; -use std::collections::BTreeMap; -use std::fmt; -use std::str::FromStr; - -use dashcore::consensus::encode; -use dashcore::{ - Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Transaction, TxIn, TxOut, Witness, -}; -use key_wallet::bip32::{ - ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, IntoDerivationPath, -}; -use key_wallet::psbt::{self, Input, Psbt, PsbtSighashType}; - -type Result = std::result::Result; - -// Get this from the output of `bt dumpwallet `. -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_VALUE: &str = "50 BTC"; -// Get this from the descriptor, -// "wpkh([97f17dca/0'/0'/0']02749483607dafb30c66bd93ece4474be65745ce538c2d70e8e246f17e7a4e0c0c)#m9n56cx0". -const INPUT_UTXO_DERIVATION_PATH: &str = "m/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(); - 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 scep 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: ExtendedPrivKey, - /// The master extended public key. - master_xpub: ExtendedPubKey, -} - -/// 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, ExtendedPubKey, ExtendedPubKey); - -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 { - let master_xpriv = ExtendedPrivKey::from_str(xpriv)?; - let master_xpub = ExtendedPubKey::from_priv(&master_xpriv); - - // Hardened children require secret data to derive. - - let path = "m/84h/0h/0h".into_derivation_path()?; - let account_0_xpriv = master_xpriv.derive_priv(&path)?; - let account_0_xpub = ExtendedPubKey::from_priv(&account_0_xpriv); - - let path = INPUT_UTXO_DERIVATION_PATH.into_derivation_path()?; - let input_xpriv = master_xpriv.derive_priv(&path)?; - let input_xpub = ExtendedPubKey::from_priv(&input_xpriv); - - let wallet = ColdStorage { - 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 { - 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 an watch-only online wallet. -struct WatchOnly { - /// The xpub for account 0 derived from derivation path "m/84h/0h/0h". - account_0_xpub: ExtendedPubKey, - /// The xpub derived from `INPUT_UTXO_DERIVATION_PATH`. - input_xpub: ExtendedPubKey, - /// 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 dash 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 dashd to grab a valid input - /// to verify the workflow presented in this file. - fn new( - account_0_xpub: ExtendedPubKey, - input_xpub: ExtendedPubKey, - master_fingerprint: Fingerprint, - ) -> Self { - WatchOnly { - account_0_xpub, - input_xpub, - master_fingerprint, - } - } - - /// Creates the PSBT, in BIP174 parlance this is the 'Creator'. - fn create_psbt(&self) -> Result { - let to_address = Address::from_str(RECEIVE_ADDRESS)?.require_network(Network::Regtest)?; - let to_amount = Amount::from_str(OUTPUT_AMOUNT_BTC)?; - - let (_, change_address, _) = self.change_address()?; - let change_amount = Amount::from_str(CHANGE_AMOUNT_BTC)?; - - let tx = Transaction { - version: 2, - lock_time: 0, - input: vec![TxIn { - previous_output: OutPoint { - txid: INPUT_UTXO_TXID.parse()?, - vout: INPUT_UTXO_VOUT, - }, - script_sig: ScriptBuf::new(), - sequence: u32::MAX, // Disable LockTime and RBF. - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: to_amount.to_sat(), - script_pubkey: to_address.script_pubkey(), - }, - TxOut { - value: change_amount.to_sat(), - script_pubkey: change_address.script_pubkey(), - }, - ], - special_transaction_payload: None, - }; - - let psbt = Psbt::from_unsigned_tx(tx)?; - - Ok(psbt) - } - - /// Updates the PSBT, in BIP174 parlance this is the 'Updater'. - fn update_psbt(&self, mut psbt: Psbt) -> Result { - let mut input = Input { - witness_utxo: Some(previous_output()), - ..Default::default() - }; - - let pk = self.input_xpub.to_pub(); - let wpkh = pk.wpubkey_hash().expect("a compressed pubkey"); - - let redeem_script = ScriptBuf::new_v0_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.inner, (fingerprint, path)); - input.bip32_derivation = map; - - let ty = PsbtSighashType::from_str("SIGHASH_ALL")?; - input.sighash_type = Some(ty); - - psbt.inputs = vec![input]; - - Ok(psbt) - } - - /// Finalizes the PSBT, in BIP174 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 { - 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].to_vec()); - script_witness.push(self.input_xpub.to_pub().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 BIP84 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<(PublicKey, Address, DerivationPath)> { - let path = [ChildNumber::from_normal_idx(1)?, ChildNumber::from_normal_idx(0)?]; - let derived = self.account_0_xpub.derive_pub(&path)?; - - let pk = derived.to_pub(); - let addr = Address::p2wpkh(&pk, NETWORK)?; - let path = path.into_derivation_path()?; - - Ok((pk, addr, path)) - } -} - -fn input_derivation_path() -> Result { - let path = INPUT_UTXO_DERIVATION_PATH.into_derivation_path()?; - Ok(path) -} - -fn previous_output() -> TxOut { - let script_pubkey = ScriptBuf::from_hex(INPUT_UTXO_SCRIPT_PUBKEY) - .expect("failed to parse input utxo scriptPubkey"); - let amount = Amount::from_str(INPUT_UTXO_VALUE).expect("failed to parse input utxo value"); - - TxOut { - value: amount.to_sat(), - script_pubkey, - } -} - -struct Error(Box); - -impl From for Error { - fn from(e: T) -> Self { - Error(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/dash/examples/taproot-psbt.rs b/dash/examples/taproot-psbt.rs deleted file mode 100644 index 2df815009..000000000 --- a/dash/examples/taproot-psbt.rs +++ /dev/null @@ -1,780 +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_IN_SATS: u64 = 1000; - -// 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_in_sats: 50 * COIN_VALUE, // 50 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_in_sats: 50 * COIN_VALUE, - 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_in_sats: 50 * COIN_VALUE, - derivation_path: BIP86_DERIVATION_PATH, -}; - -use std::collections::BTreeMap; -use std::str::FromStr; - -use dashcore::consensus::encode; -use dashcore::constants::COIN_VALUE; -use dashcore::hashes::Hash; -use dashcore::key::{TapTweak, XOnlyPublicKey}; -use dashcore::opcodes::all::{OP_CHECKSIG, OP_CLTV, OP_DROP}; -use dashcore::sighash::{self, SighashCache, TapSighash, TapSighashType}; -use dashcore::taproot::{self, LeafVersion, TapLeafHash, TaprootBuilder, TaprootSpendInfo}; -use dashcore::{ - Address, Amount, Network, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Witness, absolute, - script, -}; -use key_wallet::bip32::{ - ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, -}; -use key_wallet::psbt::{self, Input, Output, Psbt, PsbtSighashType}; - -fn main() -> Result<(), Box> { - println!("\n----------------"); - println!("\nSTART EXAMPLE 1 - P2TR with a BIP86 commitment, signed with internal key\n"); - - // Just some addresses for outputs from our wallets. Not really important. - let to_address = - Address::from_str("bcrt1p0p3rvwww0v9znrclp00uneq8ytre9kj922v8fxhnezm3mgsmn9usdxaefc")? - .require_network(Network::Regtest)?; - let change_address = - Address::from_str("bcrt1pz449kexzydh2kaypatup5ultru3ej284t6eguhnkn6wkhswt0l7q3a7j76")? - .require_network(Network::Regtest)?; - let amount_to_send_in_sats = COIN_VALUE; - let change_amount = UTXO_1 - .amount_in_sats - .checked_sub(amount_to_send_in_sats) - .and_then(|x| x.checked_sub(ABSOLUTE_FEES_IN_SATS)) - .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 - &ExtendedPrivKey::from_str(BENEFACTOR_XPRIV_STR)?, - // Set these fields with valid data for the UTXO from step 5 above - UTXO_1, - vec![ - TxOut { - value: amount_to_send_in_sats, - script_pubkey: to_address.script_pubkey(), - }, - TxOut { - value: 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(ExtendedPrivKey::from_str(BENEFICIARY_XPRIV_STR)?)?; - - let mut benefactor = BenefactorWallet::new( - ExtendedPrivKey::from_str(BENEFACTOR_XPRIV_STR)?, - 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(ExtendedPrivKey::from_str(BENEFICIARY_XPRIV_STR)?)?; - - let mut benefactor = BenefactorWallet::new( - ExtendedPrivKey::from_str(BENEFACTOR_XPRIV_STR)?, - 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{}\n", tx_hex); - - 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_in_sats: u64, - derivation_path: &'a str, -} - -fn generate_bip86_key_spend_tx( - master_xpriv: &ExtendedPrivKey, - input_utxo: P2trUtxo, - outputs: Vec, -) -> Result> { - let from_amount = input_utxo.amount_in_sats; - let input_pubkey = XOnlyPublicKey::from_str(input_utxo.pubkey)?; - - // CREATOR + UPDATER - let tx1 = Transaction { - version: 2, - lock_time: 0, - input: vec![TxIn { - previous_output: OutPoint { - txid: input_utxo.txid.parse()?, - vout: input_utxo.vout, - }, - script_sig: ScriptBuf::new(), - sequence: 0xFFFFFFFF, // Ignore nSequence. - witness: Witness::default(), - }], - output: outputs, - special_transaction_payload: None, - }; - let mut psbt = Psbt::from_unsigned_tx(tx1)?; - - let mut origins = BTreeMap::new(); - origins.insert( - input_pubkey, - ( - vec![], - ( - Fingerprint::from_str(input_utxo.master_fingerprint)?, - DerivationPath::from_str(input_utxo.derivation_path)?, - ), - ), - ); - - let mut input = Input { - witness_utxo: { - let script_pubkey = ScriptBuf::from_hex(input_utxo.script_pubkey) - .expect("failed to parse input utxo scriptPubkey"); - let amount = Amount::from_sat(from_amount); - - Some(TxOut { - value: amount.to_sat(), - script_pubkey, - }) - }, - tap_key_origins: origins, - ..Default::default() - }; - let ty = PsbtSighashType::from_str("SIGHASH_ALL")?; - input.sighash_type = Some(ty); - input.tap_internal_key = Some(input_pubkey); - psbt.inputs = vec![input]; - - // SIGNER - let unsigned_tx = psbt.unsigned_tx.clone(); - psbt.inputs.iter_mut().enumerate().try_for_each::<_, Result<(), Box>>( - |(vout, input)| { - let hash_ty = 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(&[TxOut { - value: from_amount, - script_pubkey: ScriptBuf::from_hex(input_utxo.script_pubkey)?, - }]), - hash_ty, - )?; - - 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_priv(&derivation_path)?.to_priv().inner; - sign_psbt_taproot( - &secret_key, - input.tap_internal_key.unwrap(), - None, - input, - hash, - hash_ty, - ); - - 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(); - tx.verify(|_| { - Some(TxOut { - value: from_amount, - script_pubkey: ScriptBuf::from_hex(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: ExtendedPrivKey, - beneficiary_xpub: ExtendedPubKey, - current_spend_info: Option, - next_psbt: Option, - next: ChildNumber, -} - -impl BenefactorWallet { - fn new( - master_xpriv: ExtendedPrivKey, - beneficiary_xpub: ExtendedPubKey, - ) -> Result> { - Ok(Self { - master_xpriv, - beneficiary_xpub, - current_spend_info: None, - next_psbt: None, - next: ChildNumber::from_normal_idx(0).expect("Zero is a valid child number"), - }) - } - - fn time_lock_script( - locktime: absolute::LockTime, - beneficiary_key: XOnlyPublicKey, - ) -> ScriptBuf { - script::Builder::new() - .push_int(locktime.to_consensus_u32() as i64) - .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> { - if let ChildNumber::Normal { - index, - } = self.next - && index > 0 - && self.current_spend_info.is_some() - { - return Err("Transaction already exists, use refresh_inheritance_timelock 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 = DerivationPath::from_str(&format!("m/101/1/0/0/{}", self.next))?; - let internal_keypair = self.master_xpriv.derive_priv(&derivation_path)?.to_keypair(); - let beneficiary_key = self.beneficiary_xpub.derive_pub(&derivation_path)?.to_x_only_pub(); - - // 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.x_only_public_key().0) - .expect("Should be finalizable"); - self.current_spend_info = Some(taproot_spend_info.clone()); - let script_pubkey = ScriptBuf::new_v1_p2tr( - taproot_spend_info.internal_key(), - taproot_spend_info.merkle_root(), - ); - let value = input_utxo.amount_in_sats - ABSOLUTE_FEES_IN_SATS; - - // Spend a normal BIP86-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(), - value, - }], - )?; - - // CREATOR + UPDATER - let next_tx = Transaction { - version: 2, - lock_time: lock_time.to_consensus_u32(), - input: vec![TxIn { - previous_output: OutPoint { - txid: tx.txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: 0xFFFFFFFD, // enable locktime and opt-in RBF - witness: Witness::default(), - }], - output: vec![], - special_transaction_payload: None, - }; - 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.x_only_public_key().0, - (vec![], (self.master_xpriv.fingerprint(), derivation_path)), - ); - let ty = PsbtSighashType::from_str("SIGHASH_ALL")?; - 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 amount = Amount::from_sat(value); - - Some(TxOut { - value: amount.to_sat(), - script_pubkey, - }) - }, - tap_key_origins: origins, - tap_merkle_root: taproot_spend_info.merkle_root(), - sighash_type: Some(ty), - tap_internal_key: Some(internal_keypair.x_only_public_key().0), - 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> { - 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_value = input.witness_utxo.as_ref().unwrap().value; - let output_value = input_value - ABSOLUTE_FEES_IN_SATS; - - // 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 = - DerivationPath::from_str(&format!("m/101/1/0/0/{}", self.next))?; - let new_internal_keypair = - self.master_xpriv.derive_priv(&new_derivation_path)?.to_keypair(); - let beneficiary_key = - self.beneficiary_xpub.derive_pub(&new_derivation_path)?.to_x_only_pub(); - - // 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 + 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.x_only_public_key().0) - .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 = ScriptBuf::new_v1_p2tr( - taproot_spend_info.internal_key(), - taproot_spend_info.merkle_root(), - ); - - psbt.unsigned_tx.output = vec![TxOut { - script_pubkey: output_script_pubkey.clone(), - value: output_value, - }]; - psbt.outputs = vec![Output::default()]; - psbt.unsigned_tx.lock_time = 0; - - let hash_ty = 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 { - value: input_value, - script_pubkey: prevout_script_pubkey, - }]), - hash_ty, - )?; - - { - 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_priv(&derivation_path)?.to_priv().inner; - sign_psbt_taproot( - &secret_key, - spend_info.internal_key(), - None, - input, - hash, - hash_ty, - ); - } - - // 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(); - tx.verify(|_| { - Some(TxOut { - value: input_value, - script_pubkey: output_script_pubkey.clone(), - }) - }) - .expect("failed to verify transaction"); - - let next_tx = Transaction { - version: 2, - lock_time: lock_time.to_consensus_u32(), - input: vec![TxIn { - previous_output: OutPoint { - txid: tx.txid(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: 0xFFFFFFFD, // enable locktime and opt-in RBF - witness: Witness::default(), - }], - output: vec![], - special_transaction_payload: None, - }; - 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 = PsbtSighashType::from_str("SIGHASH_ALL")?; - 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 = Amount::from_sat(output_value); - - Some(TxOut { - value: amount.to_sat(), - 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.x_only_public_key().0), - 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: ExtendedPrivKey, -} - -impl BeneficiaryWallet { - fn new(master_xpriv: ExtendedPrivKey) -> Result> { - Ok(Self { - master_xpriv, - }) - } - - fn master_xpub(&self) -> ExtendedPubKey { - ExtendedPubKey::from_priv(&self.master_xpriv) - } - - fn spend_inheritance( - &self, - mut psbt: Psbt, - lock_time: absolute::LockTime, - to_address: Address, - ) -> Result> { - let input_value = psbt.inputs[0].witness_utxo.as_ref().unwrap().value; - let input_script_pubkey = - psbt.inputs[0].witness_utxo.as_ref().unwrap().script_pubkey.clone(); - psbt.unsigned_tx.lock_time = lock_time.to_consensus_u32(); - psbt.unsigned_tx.output = vec![TxOut { - script_pubkey: to_address.script_pubkey(), - value: input_value - ABSOLUTE_FEES_IN_SATS, - }]; - 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_priv(&derivation_path)?.to_priv().inner; - for lh in leaf_hashes { - let hash_ty = TapSighashType::All; - let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash( - 0, - &sighash::Prevouts::All(&[TxOut { - value: input_value, - script_pubkey: input_script_pubkey.clone(), - }]), - *lh, - hash_ty, - )?; - sign_psbt_taproot( - &secret_key, - *x_only_pubkey, - Some(*lh), - &mut psbt.inputs[0], - hash, - hash_ty, - ); - } - } - - // FINALIZER - psbt.inputs.iter_mut().for_each(|input| { - let mut script_witness: Witness = Witness::new(); - for signature in input.tap_script_sigs.values() { - script_witness.push(signature.to_vec()); - } - for (control_block, (script, _)) in input.tap_scripts.iter() { - script_witness.push(script.to_bytes()); - 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(); - tx.verify(|_| { - Some(TxOut { - value: input_value, - 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 -// -// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , 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: &secp256k1::SecretKey, - pubkey: XOnlyPublicKey, - leaf_hash: Option, - psbt_input: &mut psbt::Input, - hash: TapSighash, - hash_ty: TapSighashType, -) { - let keypair = secp256k1::Keypair::from_secret_bytes(secret_key.to_secret_bytes()).unwrap(); - let keypair = match leaf_hash { - None => keypair.tap_tweak(psbt_input.tap_merkle_root).to_inner(), - Some(_) => keypair, // no tweak for script spend - }; - - let sig = secp256k1::schnorr::sign(hash.as_byte_array(), &keypair); - - let final_signature = taproot::Signature { - sig, - hash_ty, - }; - - 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/dash/src/blockdata/script/borrowed.rs b/dash/src/blockdata/script/borrowed.rs index 49ed0ba71..a5f1812bc 100644 --- a/dash/src/blockdata/script/borrowed.rs +++ b/dash/src/blockdata/script/borrowed.rs @@ -1,7 +1,7 @@ // Written in 2014 by Andrew Poelstra // SPDX-License-Identifier: CC0-1.0 -use core::convert::{TryFrom, TryInto}; +use core::convert::TryFrom; use core::fmt; use core::ops::{Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; @@ -288,15 +288,6 @@ impl Script { && self.0[1] == OP_PUSHBYTES_20.to_u8() } - /// Returns P2WPKH byte slice if this script is P2WPKH, otherwise None. - pub fn v0_p2wpkh(&self) -> Option<&[u8; 20]> { - if self.is_v0_p2wpkh() { - Some(self.0[2..].try_into().expect("is_v0_p2wpkh checks the length")) - } else { - None - } - } - /// Checks whether a script pubkey is a P2TR output. #[inline] pub fn is_v1_p2tr(&self) -> bool { diff --git a/dash/src/blockdata/script/owned.rs b/dash/src/blockdata/script/owned.rs index 08d651900..9ba6c3753 100644 --- a/dash/src/blockdata/script/owned.rs +++ b/dash/src/blockdata/script/owned.rs @@ -194,23 +194,6 @@ impl ScriptBuf { ScriptBuf::new_p2sh(&self.script_hash()) } - /// Returns the script code used for spending a P2WPKH output if this script is a script pubkey - /// for a P2WPKH output. The `scriptCode` is described in [BIP143]. - /// - /// [BIP143]: - pub fn p2wpkh_script_code(&self) -> Option { - self.v0_p2wpkh().map(|wpkh| { - Builder::new() - .push_opcode(OP_DUP) - .push_opcode(OP_HASH160) - // The `self` script is 0x00, 0x14, - .push_slice(wpkh) - .push_opcode(OP_EQUALVERIFY) - .push_opcode(OP_CHECKSIG) - .into_script() - }) - } - /// Adds a single opcode to the script. pub fn push_opcode(&mut self, data: opcodes::All) { self.0.push(data.to_u8()); diff --git a/dash/src/blockdata/transaction/mod.rs b/dash/src/blockdata/transaction/mod.rs index d812630ad..77063cae3 100644 --- a/dash/src/blockdata/transaction/mod.rs +++ b/dash/src/blockdata/transaction/mod.rs @@ -131,10 +131,7 @@ impl EncodeSigningDataResult { /// format. /// /// There is one major exception to this: to avoid deserialization ambiguity, -/// if the transaction has no inputs, it is serialized in the BIP141 style. Be -/// aware that this differs from the transaction format in PSBT, which _never_ -/// uses BIP141. (Ordinarily there is no conflict, since in PSBT transactions -/// are always unsigned and therefore their inputs have empty witnesses.) +/// if the transaction has no inputs, it is serialized in the BIP141 style. /// /// The specific ambiguity is that Segwit uses the flag bytes `0001` where an old /// serializer would read the number of transaction inputs. The old serializer @@ -334,10 +331,6 @@ impl Transaction { /// cannot be unambiguously serialized; we make a choice that adds two extra bytes. For more /// details see [BIP 141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) /// which uses a "input count" of `0x00` as a `marker` for a Segwit-encoded transaction. - /// - /// If you need to use 0-input transactions, we strongly recommend you do so using the PSBT - /// API. The unsigned transaction encoded within PSBT is always a non-segwit transaction - /// and can therefore avoid this ambiguity. #[inline] pub fn weight(&self) -> Weight { Weight::from_wu(self.scaled_size(WITNESS_SCALE_FACTOR) as u64) diff --git a/dash/src/lib.rs b/dash/src/lib.rs index 65b45d09a..c9ef51379 100644 --- a/dash/src/lib.rs +++ b/dash/src/lib.rs @@ -32,7 +32,7 @@ //! //! * `secp-recovery` - enables calculating public key from a signature and message. //! * `signer` - enables singing and validation ECDSA helpers. -//! * `base64` - (dependency), enables encoding of PSBTs and message signatures. +//! * `base64` - (dependency), enables encoding of message signatures. //! * `unstable` - enables unstable features for testing. //! * `rand` - (dependency), makes it more convenient to generate random values. //! * `bincode` - (dependency), implements bincode serialization and deserialization. diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index f93bdf390..77f84f70d 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -15,7 +15,6 @@ cargo-fuzz = true [dependencies] honggfuzz = { version = "0.5", default-features = false } dashcore = { path = "../dash", features = [ "serde" ] } -key-wallet = { path = "../key-wallet" } serde = { version = "1.0.219", features = [ "derive" ] } serde_json = "1.0" @@ -53,10 +52,6 @@ path = "fuzz_targets/dash/outpoint_string.rs" name = "dash_deserialize_transaction" path = "fuzz_targets/dash/deserialize_transaction.rs" -[[bin]] -name = "dash_deserialize_psbt" -path = "fuzz_targets/dash/deserialize_psbt.rs" - [[bin]] name = "dash_deserialize_script" path = "fuzz_targets/dash/deserialize_script.rs" diff --git a/fuzz/fuzz-util.sh b/fuzz/fuzz-util.sh index e09844207..742f1140c 100755 --- a/fuzz/fuzz-util.sh +++ b/fuzz/fuzz-util.sh @@ -12,7 +12,7 @@ listAllTargetFiles() { listTargetFiles() { # Exclude targets that don't work in CI - listAllTargetFiles | grep -v 'deserialize_transaction\|deserialize_prefilled_transaction\|deserialize_psbt' + listAllTargetFiles | grep -v 'deserialize_transaction\|deserialize_prefilled_transaction' } targetFileToName() { diff --git a/fuzz/fuzz_targets/dash/deserialize_psbt.rs b/fuzz/fuzz_targets/dash/deserialize_psbt.rs deleted file mode 100644 index 7159b9982..000000000 --- a/fuzz/fuzz_targets/dash/deserialize_psbt.rs +++ /dev/null @@ -1,50 +0,0 @@ -use honggfuzz::fuzz; - -fn do_test(data: &[u8]) { - let psbt: Result = - key_wallet::psbt::Psbt::deserialize(data); - match psbt { - Err(_) => {} - Ok(psbt) => { - let ser = key_wallet::psbt::Psbt::serialize(&psbt); - let deser = key_wallet::psbt::Psbt::deserialize(&ser).unwrap(); - // Since the fuzz data could order psbt fields differently, we compare to our deser/ser instead of data - assert_eq!(ser, key_wallet::psbt::Psbt::serialize(&deser)); - } - } -} - -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} - -#[cfg(all(test, fuzzing))] -mod tests { - fn extend_vec_from_hex(hex: &str, out: &mut Vec) { - let mut b = 0; - for (idx, c) in hex.as_bytes().iter().enumerate() { - b <<= 4; - match *c { - b'A'..=b'F' => b |= c - b'A' + 10, - b'a'..=b'f' => b |= c - b'a' + 10, - b'0'..=b'9' => b |= c - b'0', - _ => panic!("Bad hex"), - } - if (idx & 1) == 1 { - out.push(b); - b = 0; - } - } - } - - #[test] - fn duplicate_crash() { - let mut a = Vec::new(); - extend_vec_from_hex("00", &mut a); - super::do_test(&a); - } -} diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh index 9b1a63bb5..b6c239f6c 100755 --- a/fuzz/generate-files.sh +++ b/fuzz/generate-files.sh @@ -26,7 +26,6 @@ cargo-fuzz = true [dependencies] honggfuzz = { version = "0.5", default-features = false } dashcore = { path = "../dash", features = [ "serde" ] } -key-wallet = { path = "../key-wallet" } serde = { version = "1.0.219", features = [ "derive" ] } serde_json = "1.0" diff --git a/key-wallet/Cargo.toml b/key-wallet/Cargo.toml index 67c0f17f6..a2974752a 100644 --- a/key-wallet/Cargo.toml +++ b/key-wallet/Cargo.toml @@ -50,7 +50,6 @@ rand = { version = "0.9", default-features = false, features = ["std", "std_rng" # Serialization bincode = { workspace = true, optional = true } bincode_derive = { workspace = true, optional = true } -base64 = { version = "0.22", optional = true } serde_json = { version = "1.0", optional = true } hex = { version = "0.4"} zeroize = { version = "1.8", features = ["derive"] } diff --git a/key-wallet/README.md b/key-wallet/README.md index 8e0d3a880..039e39a98 100644 --- a/key-wallet/README.md +++ b/key-wallet/README.md @@ -68,7 +68,6 @@ The library is organized into several key modules: - **Address Pool Management**: Pre-generated address pools for performance - **Transaction Checking**: Efficient transaction ownership detection - **UTXO Management**: Track unspent outputs and calculate balances -- **PSBT Support**: Partially Signed Bitcoin Transaction format - **Multi-Network**: Support for mainnet, testnet, and other networks ## Usage Examples diff --git a/key-wallet/TODO.md b/key-wallet/TODO.md index 8742212c7..546005cfd 100644 --- a/key-wallet/TODO.md +++ b/key-wallet/TODO.md @@ -14,13 +14,6 @@ - `address_metadata_tests.rs` - Tests need updating for new architecture - `wallet_comprehensive_tests.rs` - Advanced tests need reimplementation -### 2. PSBT (Partially Signed Bitcoin Transaction) Support -**Location**: `psbt/serialize.rs`, `psbt/map/input.rs` -**Priority**: MEDIUM -**TODOs**: -- Add support for writing into a writer for key-source -- Implement Proof of reserves commitment - ## Enhanced Wallet Manager ### 1. Real Address Derivation diff --git a/key-wallet/src/lib.rs b/key-wallet/src/lib.rs index b777b8b9d..f589e5ae3 100644 --- a/key-wallet/src/lib.rs +++ b/key-wallet/src/lib.rs @@ -36,7 +36,6 @@ pub mod error; pub mod gap_limit; pub mod managed_account; pub mod mnemonic; -pub mod psbt; pub mod seed; pub mod signer; pub mod transaction_checking; diff --git a/key-wallet/src/psbt/error.rs b/key-wallet/src/psbt/error.rs deleted file mode 100644 index fafc5b4d9..000000000 --- a/key-wallet/src/psbt/error.rs +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -use core::fmt; - -use crate::bip32::ExtendedPubKey; -use crate::psbt::raw; -use dashcore::blockdata::transaction::Transaction; -use dashcore::consensus::encode; -use dashcore::io; -use dashcore_hashes as hashes; - -/// 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, - /// Actual - actual: Box, - }, - /// Unable to parse as a standard sighash type. - NonStandardSighashType(u32), - /// Parsing errors from bitcoin_hashes - HashParse(hashes::Error), - /// 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), - /// Serialization error in dash consensus-encoded structures - ConsensusEncoding(encode::Error), - /// Negative fee - NegativeFee, - /// Integer overflow in fee calculation - FeeOverflow, - /// Parsing error indicating invalid public keys - InvalidPublicKey(dashcore::crypto::key::Error), - /// 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(dashcore::crypto::ecdsa::Error), - /// Parsing error indicating invalid taproot signatures - InvalidTaprootSignature(dashcore::crypto::taproot::Error), - /// 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(dashcore::taproot::IncompleteBuilder), - /// Error related to a 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 fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::InvalidMagic => f.write_str("invalid magic"), - Error::MissingUtxo => f.write_str("UTXO information is not present in PSBT"), - Error::InvalidSeparator => f.write_str("invalid separator"), - Error::PsbtUtxoOutOfBounds => { - f.write_str("output index is out of bounds of non witness script output array") - } - Error::InvalidKey(ref rkey) => write!(f, "invalid key: {}", rkey), - Error::InvalidProprietaryKey => { - write!(f, "non-proprietary key type found when proprietary key was expected") - } - Error::DuplicateKey(ref rkey) => write!(f, "duplicate key: {}", rkey), - Error::UnsignedTxHasScriptSigs => { - f.write_str("the unsigned transaction has script sigs") - } - Error::UnsignedTxHasScriptWitnesses => { - f.write_str("the unsigned transaction has script witnesses") - } - Error::MustHaveUnsignedTx => { - f.write_str("partially signed transactions must have an unsigned transaction") - } - Error::NoMorePairs => f.write_str("no more key-value pairs for this psbt map"), - Error::UnexpectedUnsignedTx { - expected: ref e, - actual: ref a, - } => write!( - f, - "different unsigned transaction: expected {}, actual {}", - e.txid(), - a.txid() - ), - Error::NonStandardSighashType(ref sht) => { - write!(f, "non-standard sighash type: {}", sht) - } - Error::HashParse(ref e) => write!(f, "hash parse error: {}", e), - Error::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) - } - Error::CombineInconsistentKeySources(ref s) => { - write!(f, "combine conflict: {}", s) - } - Error::ConsensusEncoding(ref e) => write!(f, "dash consensus encoding error: {}", e), - Error::NegativeFee => f.write_str("PSBT has a negative fee which is not allowed"), - Error::FeeOverflow => f.write_str("integer overflow in fee calculation"), - Error::InvalidPublicKey(ref e) => write!(f, "invalid public key: {}", e), - Error::InvalidSecp256k1PublicKey(ref e) => { - write!(f, "invalid secp256k1 public key: {}", e) - } - Error::InvalidXOnlyPublicKey => f.write_str("invalid xonly public key"), - Error::InvalidEcdsaSignature(ref e) => write!(f, "invalid ECDSA signature: {}", e), - Error::InvalidTaprootSignature(ref e) => write!(f, "invalid taproot signature: {}", e), - Error::InvalidControlBlock => f.write_str("invalid control block"), - Error::InvalidLeafVersion => f.write_str("invalid leaf version"), - Error::Taproot(s) => write!(f, "taproot error - {}", s), - Error::TapTree(ref e) => write!(f, "taproot tree error: {}", e), - Error::XPubKey(s) => write!(f, "xpub key error - {}", s), - Error::Version(s) => write!(f, "version error {}", s), - Error::PartialDataConsumption => { - f.write_str("data not consumed entirely when explicitly deserializing") - } - Error::Io(ref e) => write!(f, "I/O error: {}", e), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use self::Error::*; - - match self { - HashParse(e) => Some(e), - ConsensusEncoding(e) => Some(e), - Io(e) => Some(e), - InvalidMagic - | MissingUtxo - | InvalidSeparator - | PsbtUtxoOutOfBounds - | InvalidKey(_) - | InvalidProprietaryKey - | DuplicateKey(_) - | UnsignedTxHasScriptSigs - | UnsignedTxHasScriptWitnesses - | MustHaveUnsignedTx - | NoMorePairs - | UnexpectedUnsignedTx { - .. - } - | NonStandardSighashType(_) - | InvalidPreimageHashPair { - .. - } - | CombineInconsistentKeySources(_) - | NegativeFee - | FeeOverflow - | InvalidPublicKey(_) - | InvalidSecp256k1PublicKey(_) - | InvalidXOnlyPublicKey - | InvalidEcdsaSignature(_) - | InvalidTaprootSignature(_) - | InvalidControlBlock - | InvalidLeafVersion - | Taproot(_) - | TapTree(_) - | XPubKey(_) - | Version(_) - | PartialDataConsumption => None, - } - } -} - -#[doc(hidden)] -impl From for Error { - fn from(e: hashes::Error) -> Error { - Error::HashParse(e) - } -} - -impl From for Error { - fn from(e: encode::Error) -> Self { - Error::ConsensusEncoding(e) - } -} - -impl From for Error { - fn from(e: io::Error) -> Self { - Error::Io(e) - } -} diff --git a/key-wallet/src/psbt/macros.rs b/key-wallet/src/psbt/macros.rs deleted file mode 100644 index af1a74035..000000000 --- a/key-wallet/src/psbt/macros.rs +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -#[allow(unused_macros)] -macro_rules! hex_psbt { - ($s:expr) => { - <$crate::psbt::PartiallySignedTransaction>::deserialize( - &<$crate::prelude::Vec as dashcore::hashes::hex::FromHex>::from_hex($s).unwrap(), - ) - }; -} - -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]) -> Result { - $crate::dashcore::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 { - $crate::dashcore::consensus::serialize(self) - } - } - }; -} - -macro_rules! impl_psbtmap_serialize { - ($thing:ty) => { - impl $crate::psbt::serialize::Serialize for $thing { - fn serialize(&self) -> Vec { - self.serialize_map() - } - } - }; -} - -macro_rules! impl_psbtmap_deserialize { - ($thing:ty) => { - impl $crate::psbt::serialize::Deserialize for $thing { - fn deserialize(bytes: &[u8]) -> Result { - let mut decoder = bytes; - Self::decode(&mut decoder) - } - } - }; -} - -macro_rules! impl_psbtmap_decoding { - ($thing:ty) => { - impl $thing { - pub(crate) fn decode( - r: &mut R, - ) -> Result { - 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.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.is_empty() { - let key_val: $keyed_key_type = $crate::psbt::serialize::Deserialize::deserialize(&$raw_key.key)?; - 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! 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: 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: $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]) -> Result { - <$hash_type>::from_slice(&bytes[..]).map_err(|e| $crate::psbt::Error::from(e)) - } - } - }; -} - -macro_rules! impl_psbt_hash_serialize { - ($hash_type:ty) => { - impl $crate::psbt::serialize::Serialize for $hash_type { - fn serialize(&self) -> $crate::prelude::Vec { - self.as_byte_array().to_vec() - } - } - }; -} diff --git a/key-wallet/src/psbt/map/global.rs b/key-wallet/src/psbt/map/global.rs deleted file mode 100644 index 39505e4cf..000000000 --- a/key-wallet/src/psbt/map/global.rs +++ /dev/null @@ -1,251 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -use core::convert::TryFrom; - -use crate::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint}; -use crate::psbt::map::Map; -use crate::psbt::{raw, Error, PartiallySignedTransaction}; -use dashcore::blockdata::transaction::Transaction; -use dashcore::consensus::encode::MAX_VEC_SIZE; -use dashcore::consensus::{encode, Decodable, Encodable}; -use dashcore::io::{Cursor, Read}; -use dashcore::transaction::special_transaction::TransactionType; -use std::collections::{btree_map, BTreeMap}; - -/// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00 -const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00; -/// Type: Extended Public Key PSBT_GLOBAL_XPUB = 0x01 -const PSBT_GLOBAL_XPUB: u8 = 0x01; -/// Type: Version Number PSBT_GLOBAL_VERSION = 0xFB -const PSBT_GLOBAL_VERSION: u8 = 0xFB; -/// Type: Proprietary Use Type PSBT_GLOBAL_PROPRIETARY = 0xFC -const PSBT_GLOBAL_PROPRIETARY: u8 = 0xFC; - -impl Map for PartiallySignedTransaction { - fn get_pairs(&self) -> Vec { - let mut rv: Vec = Default::default(); - - rv.push(raw::Pair { - key: raw::Key { - type_value: PSBT_GLOBAL_UNSIGNED_TX, - key: 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)); - self.unsigned_tx - .tx_type() - .to_u16() - .consensus_encode(&mut ret) - .expect("can't encode tx type"); - let input = encode::serialize(&self.unsigned_tx.input); - ret.extend(input); - ret.extend(encode::serialize(&self.unsigned_tx.output)); - ret.extend(encode::serialize(&self.unsigned_tx.lock_time)); - if let Some(payload) = &self.unsigned_tx.special_transaction_payload { - payload.consensus_encode(&mut ret).expect("can't encode special tx payload"); - } - ret - }, - }); - - for (xpub, (fingerprint, derivation)) in &self.xpub { - rv.push(raw::Pair { - key: raw::Key { - type_value: PSBT_GLOBAL_XPUB, - key: xpub.encode().to_vec(), - }, - value: { - let mut ret = Vec::with_capacity(4 + derivation.len() * 4); - ret.extend(fingerprint.to_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: 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 PartiallySignedTransaction { - pub(crate) fn decode_global(r: &mut R) -> Result { - let mut r = r.take(MAX_VEC_SIZE as u64); - let mut tx: Option = None; - let mut version: Option = None; - let mut unknowns: BTreeMap> = Default::default(); - let mut xpub_map: BTreeMap = - Default::default(); - let mut proprietary: BTreeMap> = 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.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); - let version = Decodable::consensus_decode(&mut decoder)?; - let transaction_type: TransactionType = - Decodable::consensus_decode(&mut decoder)?; - let input = Decodable::consensus_decode(&mut decoder)?; - let output = Decodable::consensus_decode(&mut decoder)?; - let lock_time = Decodable::consensus_decode(&mut decoder)?; - - // Manually deserialized to ensure 0-input - // txs without witnesses are deserialized - // properly. - tx = Some(Transaction { - version, - lock_time, - input, - output, - special_transaction_payload: transaction_type - .consensus_decode(&mut decoder)?, - }); - - if decoder.position() != vlen as 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.is_empty() { - let xpub = ExtendedPubKey::decode(&pair.key.key) - .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::::with_capacity(child_count); - while let Ok(index) = u32::consensus_decode(&mut decoder) { - path.push(ChildNumber::from(index)) - } - let derivation = DerivationPath::from(path); - // Keys, according to BIP-174, must be unique - if 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.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-174 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(PartiallySignedTransaction { - unsigned_tx: tx, - version: version.unwrap_or(0), - xpub: xpub_map, - proprietary, - unknown: unknowns, - inputs: Vec::new(), - outputs: Vec::new(), - }) - } else { - Err(Error::MustHaveUnsignedTx) - } - } -} diff --git a/key-wallet/src/psbt/map/input.rs b/key-wallet/src/psbt/map/input.rs deleted file mode 100644 index 01da313e7..000000000 --- a/key-wallet/src/psbt/map/input.rs +++ /dev/null @@ -1,614 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -use core::convert::TryFrom; -use core::fmt; -use core::str::FromStr; - -use dashcore_hashes::{self as hashes, hash160, ripemd160, sha256, sha256d}; -use secp256k1::XOnlyPublicKey; - -use crate::psbt::map::Map; -use crate::psbt::serialize::Deserialize; -use crate::psbt::{error, raw, Error}; -use dashcore::blockdata::script::ScriptBuf; -use dashcore::blockdata::transaction::txout::TxOut; -use dashcore::blockdata::transaction::Transaction; -use dashcore::blockdata::witness::Witness; -use dashcore::crypto::key::PublicKey; -use dashcore::crypto::{ecdsa, taproot}; -use dashcore::sighash::{ - self, EcdsaSighashType, NonStandardSighashType, SighashTypeParseError, TapSighashType, -}; -use dashcore::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash}; -use std::collections::{btree_map, BTreeMap}; - -use crate::bip32::KeySource; -#[cfg(feature = "serde")] -use serde::{Deserialize as SerdeDeserialize, Serialize}; - -/// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00 -const PSBT_IN_NON_WITNESS_UTXO: u8 = 0x00; -/// Type: Witness UTXO PSBT_IN_WITNESS_UTXO = 0x01 -const PSBT_IN_WITNESS_UTXO: u8 = 0x01; -/// Type: Partial Signature PSBT_IN_PARTIAL_SIG = 0x02 -const PSBT_IN_PARTIAL_SIG: u8 = 0x02; -/// Type: Sighash Type PSBT_IN_SIGHASH_TYPE = 0x03 -const PSBT_IN_SIGHASH_TYPE: u8 = 0x03; -/// Type: Redeem Script PSBT_IN_REDEEM_SCRIPT = 0x04 -const PSBT_IN_REDEEM_SCRIPT: u8 = 0x04; -/// Type: Witness Script PSBT_IN_WITNESS_SCRIPT = 0x05 -const PSBT_IN_WITNESS_SCRIPT: u8 = 0x05; -/// Type: BIP 32 Derivation Path PSBT_IN_BIP32_DERIVATION = 0x06 -const PSBT_IN_BIP32_DERIVATION: u8 = 0x06; -/// Type: Finalized scriptSig PSBT_IN_FINAL_SCRIPTSIG = 0x07 -const PSBT_IN_FINAL_SCRIPTSIG: u8 = 0x07; -/// Type: Finalized scriptWitness PSBT_IN_FINAL_SCRIPTWITNESS = 0x08 -const PSBT_IN_FINAL_SCRIPTWITNESS: u8 = 0x08; -/// Type: RIPEMD160 preimage PSBT_IN_RIPEMD160 = 0x0a -const PSBT_IN_RIPEMD160: u8 = 0x0a; -/// Type: SHA256 preimage PSBT_IN_SHA256 = 0x0b -const PSBT_IN_SHA256: u8 = 0x0b; -/// Type: HASH160 preimage PSBT_IN_HASH160 = 0x0c -const PSBT_IN_HASH160: u8 = 0x0c; -/// Type: HASH256 preimage PSBT_IN_HASH256 = 0x0d -const PSBT_IN_HASH256: u8 = 0x0d; -/// Type: Taproot Signature in Key Spend PSBT_IN_TAP_KEY_SIG = 0x13 -const PSBT_IN_TAP_KEY_SIG: u8 = 0x13; -/// Type: Taproot Signature in Script Spend PSBT_IN_TAP_SCRIPT_SIG = 0x14 -const PSBT_IN_TAP_SCRIPT_SIG: u8 = 0x14; -/// Type: Taproot Leaf Script PSBT_IN_TAP_LEAF_SCRIPT = 0x14 -const PSBT_IN_TAP_LEAF_SCRIPT: u8 = 0x15; -/// Type: Taproot Key BIP 32 Derivation Path PSBT_IN_TAP_BIP32_DERIVATION = 0x16 -const PSBT_IN_TAP_BIP32_DERIVATION: u8 = 0x16; -/// Type: Taproot Internal Key PSBT_IN_TAP_INTERNAL_KEY = 0x17 -const PSBT_IN_TAP_INTERNAL_KEY: u8 = 0x17; -/// Type: Taproot Merkle Root PSBT_IN_TAP_MERKLE_ROOT = 0x18 -const PSBT_IN_TAP_MERKLE_ROOT: u8 = 0x18; -/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC -const PSBT_IN_PROPRIETARY: u8 = 0xFC; - -/// A key-value map for an input of the corresponding index in the unsigned -/// transaction. -#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, SerdeDeserialize))] -pub struct Input { - /// The non-witness transaction this input spends from. Should only be - /// [Some] for inputs which spend non-segwit outputs or - /// if it is unknown whether an input spends a segwit output. - pub non_witness_utxo: Option, - /// The transaction output this input spends from. Should only be - /// [Some] for inputs which spend segwit outputs, - /// including P2SH embedded ones. - pub witness_utxo: Option, - /// A map from public keys to their corresponding signature as would be - /// pushed to the stack from a scriptSig or witness for a non-taproot inputs. - pub partial_sigs: BTreeMap, - /// The sighash type to be used for this input. Signatures for this input - /// must use the sighash type. - pub sighash_type: Option, - /// The redeem script for this input. - pub redeem_script: Option, - /// The witness script for this input. - pub witness_script: Option, - /// A map from public keys needed to sign this input to their corresponding - /// master key fingerprints and derivation paths. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_as_seq"))] - pub bip32_derivation: BTreeMap, - /// The finalized, fully-constructed scriptSig with signatures and any other - /// scripts necessary for this input to pass validation. - pub final_script_sig: Option, - /// The finalized, fully-constructed scriptWitness with signatures and any - /// other scripts necessary for this input to pass validation. - pub final_script_witness: Option, - /// TODO: Proof of reserves commitment - /// RIPEMD160 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_byte_values"))] - pub ripemd160_preimages: BTreeMap>, - /// SHA256 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_byte_values"))] - pub sha256_preimages: BTreeMap>, - /// HASH160 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_byte_values"))] - pub hash160_preimages: BTreeMap>, - /// HAS256 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_byte_values"))] - pub hash256_preimages: BTreeMap>, - /// Serialized taproot signature with sighash type for key spend. - pub tap_key_sig: Option, - /// Map of `|` with signature. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_as_seq"))] - pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), taproot::Signature>, - /// Map of Control blocks to Script version pair. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_as_seq"))] - pub tap_scripts: BTreeMap, - /// Map of tap root x only keys to origin info and leaf hashes contained in it. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_as_seq"))] - pub tap_key_origins: BTreeMap, KeySource)>, - /// Taproot Internal key. - pub tap_internal_key: Option, - /// Taproot Merkle root. - pub tap_merkle_root: Option, - /// Proprietary key-value pairs for this input. - #[cfg_attr( - feature = "serde", - serde(with = "dashcore::serde_utils::btreemap_as_seq_byte_values") - )] - pub proprietary: BTreeMap>, - /// Unknown key-value pairs for this input. - #[cfg_attr( - feature = "serde", - serde(with = "dashcore::serde_utils::btreemap_as_seq_byte_values") - )] - pub unknown: BTreeMap>, -} - -/// 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. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, SerdeDeserialize))] -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 { - // 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). - if let Ok(ty) = TapSighashType::from_str(s) { - return Ok(ty.into()); - } - - // We accept non-standard sighash values. - if let Ok(inner) = u32::from_str_radix(s.trim_start_matches("0x"), 16) { - return Ok(PsbtSighashType { - inner, - }); - } - - Err(SighashTypeParseError { - unrecognized: s.to_owned(), - }) - } -} -impl From for PsbtSighashType { - fn from(ecdsa_hash_ty: EcdsaSighashType) -> Self { - PsbtSighashType { - inner: ecdsa_hash_ty as u32, - } - } -} - -impl From for PsbtSighashType { - fn from(taproot_hash_ty: TapSighashType) -> Self { - PsbtSighashType { - inner: taproot_hash_ty as u32, - } - } -} - -impl PsbtSighashType { - /// Returns the [`EcdsaSighashType`] if the [`PsbtSighashType`] can be - /// converted to one. - pub fn ecdsa_hash_ty(self) -> Result { - EcdsaSighashType::from_standard(self.inner) - } - - /// Returns the [`TapSighashType`] if the [`PsbtSighashType`] can be - /// converted to one. - pub fn taproot_hash_ty(self) -> Result { - if self.inner > 0xffu32 { - Err(sighash::Error::InvalidSighashType(self.inner)) - } else { - TapSighashType::from_consensus_u8(self.inner as u8) - } - } - - /// Creates a [`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) -> PsbtSighashType { - PsbtSighashType { - 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 { - 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 { - 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 <= | - } - } - PSBT_IN_WITNESS_UTXO => { - impl_psbt_insert_pair! { - self.witness_utxo <= | - } - } - PSBT_IN_PARTIAL_SIG => { - impl_psbt_insert_pair! { - self.partial_sigs <= | - } - } - PSBT_IN_SIGHASH_TYPE => { - impl_psbt_insert_pair! { - self.sighash_type <= | - } - } - PSBT_IN_REDEEM_SCRIPT => { - impl_psbt_insert_pair! { - self.redeem_script <= | - } - } - PSBT_IN_WITNESS_SCRIPT => { - impl_psbt_insert_pair! { - self.witness_script <= | - } - } - PSBT_IN_BIP32_DERIVATION => { - impl_psbt_insert_pair! { - self.bip32_derivation <= | - } - } - PSBT_IN_FINAL_SCRIPTSIG => { - impl_psbt_insert_pair! { - self.final_script_sig <= | - } - } - PSBT_IN_FINAL_SCRIPTWITNESS => { - impl_psbt_insert_pair! { - self.final_script_witness <= | - } - } - PSBT_IN_RIPEMD160 => { - psbt_insert_hash_pair( - &mut self.ripemd160_preimages, - raw_key, - raw_value, - error::PsbtHash::Ripemd, - )?; - } - PSBT_IN_SHA256 => { - psbt_insert_hash_pair( - &mut self.sha256_preimages, - raw_key, - raw_value, - error::PsbtHash::Sha256, - )?; - } - PSBT_IN_HASH160 => { - psbt_insert_hash_pair( - &mut self.hash160_preimages, - raw_key, - raw_value, - error::PsbtHash::Hash160, - )?; - } - PSBT_IN_HASH256 => { - psbt_insert_hash_pair( - &mut self.hash256_preimages, - raw_key, - raw_value, - error::PsbtHash::Hash256, - )?; - } - PSBT_IN_TAP_KEY_SIG => { - impl_psbt_insert_pair! { - self.tap_key_sig <= | - } - } - PSBT_IN_TAP_SCRIPT_SIG => { - impl_psbt_insert_pair! { - self.tap_script_sigs <= | - } - } - PSBT_IN_TAP_LEAF_SCRIPT => { - impl_psbt_insert_pair! { - self.tap_scripts <= |< raw_value: (ScriptBuf, LeafVersion)> - } - } - PSBT_IN_TAP_BIP32_DERIVATION => { - impl_psbt_insert_pair! { - self.tap_key_origins <= |< raw_value: (Vec, KeySource)> - } - } - PSBT_IN_TAP_INTERNAL_KEY => { - impl_psbt_insert_pair! { - self.tap_internal_key <= |< raw_value: XOnlyPublicKey> - } - } - PSBT_IN_TAP_MERKLE_ROOT => { - impl_psbt_insert_pair! { - self.tap_merkle_root <= |< raw_value: TapNodeHash> - } - } - PSBT_IN_PROPRIETARY => { - let key = raw::ProprietaryKey::try_from(raw_key.clone())?; - match self.proprietary.entry(key) { - btree_map::Entry::Vacant(empty_key) => { - empty_key.insert(raw_value); - } - btree_map::Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key)), - } - } - _ => match self.unknown.entry(raw_key) { - btree_map::Entry::Vacant(empty_key) => { - empty_key.insert(raw_value); - } - btree_map::Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone())), - }, - } - - Ok(()) - } - - /// Combines this [`Input`] with `other` `Input` (as described by BIP 174). - pub fn combine(&mut self, other: Self) { - combine!(non_witness_utxo, self, other); - - if let (&None, Some(witness_utxo)) = (&self.witness_utxo, other.witness_utxo) { - self.witness_utxo = Some(witness_utxo); - self.non_witness_utxo = None; // Clear out any non-witness UTXO when we set a witness one - } - - self.partial_sigs.extend(other.partial_sigs); - self.bip32_derivation.extend(other.bip32_derivation); - self.ripemd160_preimages.extend(other.ripemd160_preimages); - self.sha256_preimages.extend(other.sha256_preimages); - self.hash160_preimages.extend(other.hash160_preimages); - self.hash256_preimages.extend(other.hash256_preimages); - self.tap_script_sigs.extend(other.tap_script_sigs); - self.tap_scripts.extend(other.tap_scripts); - self.tap_key_origins.extend(other.tap_key_origins); - self.proprietary.extend(other.proprietary); - self.unknown.extend(other.unknown); - - combine!(redeem_script, self, other); - combine!(witness_script, self, other); - combine!(final_script_sig, self, other); - combine!(final_script_witness, self, other); - combine!(tap_key_sig, self, other); - combine!(tap_internal_key, self, other); - combine!(tap_merkle_root, self, other); - } -} - -impl Map for Input { - fn get_pairs(&self) -> Vec { - let mut rv: Vec = Default::default(); - - impl_psbt_get_pair! { - rv.push(self.non_witness_utxo, PSBT_IN_NON_WITNESS_UTXO) - } - - impl_psbt_get_pair! { - rv.push(self.witness_utxo, PSBT_IN_WITNESS_UTXO) - } - - impl_psbt_get_pair! { - rv.push_map(self.partial_sigs, PSBT_IN_PARTIAL_SIG) - } - - impl_psbt_get_pair! { - rv.push(self.sighash_type, PSBT_IN_SIGHASH_TYPE) - } - - impl_psbt_get_pair! { - rv.push(self.redeem_script, PSBT_IN_REDEEM_SCRIPT) - } - - impl_psbt_get_pair! { - rv.push(self.witness_script, PSBT_IN_WITNESS_SCRIPT) - } - - impl_psbt_get_pair! { - rv.push_map(self.bip32_derivation, PSBT_IN_BIP32_DERIVATION) - } - - impl_psbt_get_pair! { - rv.push(self.final_script_sig, PSBT_IN_FINAL_SCRIPTSIG) - } - - impl_psbt_get_pair! { - rv.push(self.final_script_witness, PSBT_IN_FINAL_SCRIPTWITNESS) - } - - impl_psbt_get_pair! { - rv.push_map(self.ripemd160_preimages, PSBT_IN_RIPEMD160) - } - - impl_psbt_get_pair! { - rv.push_map(self.sha256_preimages, PSBT_IN_SHA256) - } - - impl_psbt_get_pair! { - rv.push_map(self.hash160_preimages, PSBT_IN_HASH160) - } - - impl_psbt_get_pair! { - rv.push_map(self.hash256_preimages, PSBT_IN_HASH256) - } - - impl_psbt_get_pair! { - rv.push(self.tap_key_sig, PSBT_IN_TAP_KEY_SIG) - } - - impl_psbt_get_pair! { - rv.push_map(self.tap_script_sigs, PSBT_IN_TAP_SCRIPT_SIG) - } - - impl_psbt_get_pair! { - rv.push_map(self.tap_scripts, PSBT_IN_TAP_LEAF_SCRIPT) - } - - impl_psbt_get_pair! { - rv.push_map(self.tap_key_origins, PSBT_IN_TAP_BIP32_DERIVATION) - } - - impl_psbt_get_pair! { - rv.push(self.tap_internal_key, PSBT_IN_TAP_INTERNAL_KEY) - } - - impl_psbt_get_pair! { - rv.push(self.tap_merkle_root, PSBT_IN_TAP_MERKLE_ROOT) - } - for (key, value) in self.proprietary.iter() { - rv.push(raw::Pair { - key: key.to_key(), - value: value.clone(), - }); - } - - for (key, value) in self.unknown.iter() { - rv.push(raw::Pair { - key: key.clone(), - value: value.clone(), - }); - } - - rv - } -} - -impl_psbtmap_ser_de_serialize!(Input); - -fn psbt_insert_hash_pair( - map: &mut BTreeMap>, - raw_key: raw::Key, - raw_value: Vec, - hash_type: error::PsbtHash, -) -> Result<(), Error> -where - H: hashes::Hash + Deserialize, -{ - if raw_key.key.is_empty() { - return Err(Error::InvalidKey(raw_key)); - } - let key_val: H = Deserialize::deserialize(&raw_key.key)?; - match map.entry(key_val) { - btree_map::Entry::Vacant(empty_key) => { - let val: Vec = Deserialize::deserialize(&raw_value)?; - if ::hash(&val) != key_val { - return Err(Error::InvalidPreimageHashPair { - preimage: val.into_boxed_slice(), - hash: Box::from(key_val.borrow()), - hash_type, - }); - } - empty_key.insert(val); - Ok(()) - } - btree_map::Entry::Occupied(_) => Err(Error::DuplicateKey(raw_key)), - } -} - -#[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 = PsbtSighashType::from_str(&s).unwrap(); - assert_eq!(back, sighash); - assert_eq!(back.ecdsa_hash_ty().unwrap(), *ecdsa); - } - } - - #[test] - fn psbt_sighash_type_taproot() { - for tap in &[ - TapSighashType::Default, - TapSighashType::All, - TapSighashType::None, - TapSighashType::Single, - TapSighashType::AllPlusAnyoneCanPay, - TapSighashType::NonePlusAnyoneCanPay, - TapSighashType::SinglePlusAnyoneCanPay, - ] { - let sighash = PsbtSighashType::from(*tap); - let s = format!("{}", sighash); - let back = PsbtSighashType::from_str(&s).unwrap(); - assert_eq!(back, sighash); - assert_eq!(back.taproot_hash_ty().unwrap(), *tap); - } - } - - #[test] - fn psbt_sighash_type_not_std() { - let nonstd = 0xdddddddd; - let sighash = PsbtSighashType { - inner: nonstd, - }; - let s = format!("{}", sighash); - let back = PsbtSighashType::from_str(&s).unwrap(); - - assert_eq!(back, sighash); - assert_eq!(back.ecdsa_hash_ty(), Err(NonStandardSighashType(nonstd))); - assert_eq!(back.taproot_hash_ty(), Err(sighash::Error::InvalidSighashType(nonstd))); - } -} diff --git a/key-wallet/src/psbt/map/mod.rs b/key-wallet/src/psbt/map/mod.rs deleted file mode 100644 index b301973fc..000000000 --- a/key-wallet/src/psbt/map/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -use crate::psbt::raw; - -mod global; -mod input; -mod output; - -pub use self::input::{Input, PsbtSighashType}; -pub use self::output::Output; -use super::serialize::Serialize; - -/// 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; - - /// Serialize Psbt binary map data according to BIP-174 specification. - /// - /// := * 0x00 - /// - /// Why is the separator here 0x00 instead of 0xff? The separator here is used to distinguish between each chunk of data. - /// A separator of 0x00 would mean that the unserializer can read it as a key length of 0, which would never occur with - /// actual keys. It can thus be used as a separator and allow for easier unserializer implementation. - fn serialize_map(&self) -> Vec { - let mut buf = Vec::new(); - for pair in Map::get_pairs(self) { - buf.extend(&pair.serialize()); - } - buf.push(0x00_u8); - buf - } -} diff --git a/key-wallet/src/psbt/map/output.rs b/key-wallet/src/psbt/map/output.rs deleted file mode 100644 index f9dd45d4a..000000000 --- a/key-wallet/src/psbt/map/output.rs +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -use core::convert::TryFrom; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use secp256k1::XOnlyPublicKey; -use {core, secp256k1}; - -use crate::bip32::KeySource; -use crate::psbt::map::Map; -use crate::psbt::{raw, Error}; -use dashcore::blockdata::script::ScriptBuf; -use dashcore::taproot::{TapLeafHash, TapTree}; -use std::collections::{btree_map, BTreeMap}; - -/// Type: Redeem ScriptBuf PSBT_OUT_REDEEM_SCRIPT = 0x00 -const PSBT_OUT_REDEEM_SCRIPT: u8 = 0x00; -/// Type: Witness ScriptBuf PSBT_OUT_WITNESS_SCRIPT = 0x01 -const PSBT_OUT_WITNESS_SCRIPT: u8 = 0x01; -/// Type: BIP 32 Derivation Path PSBT_OUT_BIP32_DERIVATION = 0x02 -const PSBT_OUT_BIP32_DERIVATION: u8 = 0x02; -/// Type: Taproot Internal Key PSBT_OUT_TAP_INTERNAL_KEY = 0x05 -const PSBT_OUT_TAP_INTERNAL_KEY: u8 = 0x05; -/// Type: Taproot Tree PSBT_OUT_TAP_TREE = 0x06 -const PSBT_OUT_TAP_TREE: u8 = 0x06; -/// Type: Taproot Key BIP 32 Derivation Path PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 -const PSBT_OUT_TAP_BIP32_DERIVATION: u8 = 0x07; -/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC -const PSBT_OUT_PROPRIETARY: u8 = 0xFC; - -/// A key-value map for an output of the corresponding index in the unsigned -/// transaction. -#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] -pub struct Output { - /// The redeem script for this output. - pub redeem_script: Option, - /// The witness script for this output. - pub witness_script: Option, - /// A map from public keys needed to spend this output to their - /// corresponding master key fingerprints and derivation paths. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_as_seq"))] - pub bip32_derivation: BTreeMap, - /// The internal pubkey. - pub tap_internal_key: Option, - /// Taproot Output tree. - pub tap_tree: Option, - /// Map of tap root x only keys to origin info and leaf hashes contained in it. - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::btreemap_as_seq"))] - pub tap_key_origins: BTreeMap, KeySource)>, - /// Proprietary key-value pairs for this output. - #[cfg_attr( - feature = "serde", - serde(with = "dashcore::serde_utils::btreemap_as_seq_byte_values") - )] - pub proprietary: BTreeMap>, - /// Unknown key-value pairs for this output. - #[cfg_attr( - feature = "serde", - serde(with = "dashcore::serde_utils::btreemap_as_seq_byte_values") - )] - pub unknown: BTreeMap>, -} - -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 <= | - } - } - PSBT_OUT_WITNESS_SCRIPT => { - impl_psbt_insert_pair! { - self.witness_script <= | - } - } - PSBT_OUT_BIP32_DERIVATION => { - impl_psbt_insert_pair! { - self.bip32_derivation <= | - } - } - 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 <= | - } - } - PSBT_OUT_TAP_TREE => { - impl_psbt_insert_pair! { - self.tap_tree <= | - } - } - PSBT_OUT_TAP_BIP32_DERIVATION => { - impl_psbt_insert_pair! { - self.tap_key_origins <= |< raw_value: (Vec, KeySource)> - } - } - _ => match self.unknown.entry(raw_key) { - btree_map::Entry::Vacant(empty_key) => { - empty_key.insert(raw_value); - } - btree_map::Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone())), - }, - } - - Ok(()) - } - - /// Combines this [`Output`] with `other` `Output` (as described by BIP 174). - pub fn combine(&mut self, other: Self) { - self.bip32_derivation.extend(other.bip32_derivation); - self.proprietary.extend(other.proprietary); - self.unknown.extend(other.unknown); - self.tap_key_origins.extend(other.tap_key_origins); - - combine!(redeem_script, self, other); - combine!(witness_script, self, other); - combine!(tap_internal_key, self, other); - combine!(tap_tree, self, other); - } -} - -impl Map for Output { - fn get_pairs(&self) -> Vec { - let mut rv: Vec = Default::default(); - - impl_psbt_get_pair! { - rv.push(self.redeem_script, PSBT_OUT_REDEEM_SCRIPT) - } - - impl_psbt_get_pair! { - rv.push(self.witness_script, PSBT_OUT_WITNESS_SCRIPT) - } - - impl_psbt_get_pair! { - rv.push_map(self.bip32_derivation, PSBT_OUT_BIP32_DERIVATION) - } - - impl_psbt_get_pair! { - rv.push(self.tap_internal_key, PSBT_OUT_TAP_INTERNAL_KEY) - } - - impl_psbt_get_pair! { - rv.push(self.tap_tree, PSBT_OUT_TAP_TREE) - } - - impl_psbt_get_pair! { - rv.push_map(self.tap_key_origins, PSBT_OUT_TAP_BIP32_DERIVATION) - } - - for (key, value) in self.proprietary.iter() { - rv.push(raw::Pair { - key: key.to_key(), - value: value.clone(), - }); - } - - for (key, value) in self.unknown.iter() { - rv.push(raw::Pair { - key: key.clone(), - value: value.clone(), - }); - } - - rv - } -} - -impl_psbtmap_ser_de_serialize!(Output); diff --git a/key-wallet/src/psbt/mod.rs b/key-wallet/src/psbt/mod.rs deleted file mode 100644 index 649b8ed55..000000000 --- a/key-wallet/src/psbt/mod.rs +++ /dev/null @@ -1,1856 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Partially Signed Bitcoin Transactions. -//! -//! Implementation of BIP174 Partially Signed Bitcoin Transaction Format as -//! defined at -//! except we define PSBTs containing non-standard sighash types as invalid. -//! - -use core::{cmp, fmt}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap, HashSet}; - -use crate::bip32::KeySource; -use crate::bip32::{self, ExtendedPrivKey, ExtendedPubKey}; -use core::borrow::Borrow; -use dashcore::blockdata::script::ScriptBuf; -use dashcore::blockdata::transaction::txout::TxOut; -use dashcore::blockdata::transaction::Transaction; -use dashcore::crypto::ecdsa; -use dashcore::crypto::key::{PrivateKey, PublicKey}; -pub use dashcore::sighash::Prevouts; -use dashcore::sighash::{self, EcdsaSighashType, SighashCache}; -use dashcore::Amount; -use dashcore_hashes::Hash; -use internals::write_err; -use secp256k1::Message; -use std::collections::{btree_map, BTreeSet}; - -#[macro_use] -mod macros; -pub mod raw; -pub mod serialize; - -mod error; -pub use self::error::Error; - -mod map; -pub use self::map::{Input, Output, PsbtSighashType}; - -/// Partially signed transaction, commonly referred to as a PSBT. -pub type Psbt = PartiallySignedTransaction; - -/// A Partially Signed Transaction. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] -pub struct PartiallySignedTransaction { - /// 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, - /// Global proprietary key-value pairs. - #[cfg_attr( - feature = "serde", - serde(with = "dashcore::serde_utils::btreemap_as_seq_byte_values") - )] - pub proprietary: BTreeMap>, - /// Unknown global key-value pairs. - #[cfg_attr( - feature = "serde", - serde(with = "dashcore::serde_utils::btreemap_as_seq_byte_values") - )] - pub unknown: BTreeMap>, - - /// The corresponding key-value map for each input in the unsigned transaction. - pub inputs: Vec, - /// The corresponding key-value map for each output in the unsigned transaction. - pub outputs: Vec, -} - -impl PartiallySignedTransaction { - /// 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> { - assert_eq!(self.inputs.len(), self.unsigned_tx.input.len()); - self.unsigned_tx.input.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.output.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.input { - if !txin.script_sig.is_empty() { - return Err(Error::UnsignedTxHasScriptSigs); - } - - if !txin.witness.is_empty() { - return Err(Error::UnsignedTxHasScriptWitnesses); - } - } - - Ok(()) - } - - /// Creates a PSBT from an unsigned transaction. - /// - /// # Errors - /// - /// If transactions is not unsigned. - pub fn from_unsigned_tx(tx: Transaction) -> Result { - let psbt = PartiallySignedTransaction { - inputs: vec![Default::default(); tx.input.len()], - outputs: vec![Default::default(); tx.output.len()], - - unsigned_tx: tx, - xpub: Default::default(), - version: 0, - proprietary: Default::default(), - unknown: Default::default(), - }; - psbt.unsigned_tx_checks()?; - Ok(psbt) - } - - /// Extracts the `Transaction` from a PSBT by filling in the available signature information. - pub fn extract_tx(self) -> Transaction { - let mut tx: Transaction = self.unsigned_tx; - - for (vin, psbtin) in tx.input.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 - } - - /// Combines this [`PartiallySignedTransaction`] with `other` PSBT as described by BIP 174. - /// - /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` - pub fn combine(&mut self, other: Self) -> Result<(), Error> { - 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 174: The Combiner must remove any duplicate key-value pairs, in accordance with - // the specification. It can pick arbitrarily when conflicts occur. - - // Keeping the highest version - self.version = cmp::max(self.version, other.version); - - // Merging xpubs - for (xpub, (fingerprint1, derivation1)) in other.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 the longest derivation otherwise - - let (fingerprint2, derivation2) = entry.get().clone(); - - if (derivation1 == derivation2 && fingerprint1 == fingerprint2) - || (derivation1.len() < derivation2.len() - && derivation1[..] - == derivation2[derivation2.len() - derivation1.len()..]) - { - continue; - } else if derivation2[..] - == derivation1[derivation1.len() - derivation2.len()..] - { - entry.insert((fingerprint1, derivation1)); - continue; - } - return Err(Error::CombineInconsistentKeySources(Box::new(xpub))); - } - } - } - - self.proprietary.extend(other.proprietary); - self.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`. - /// - /// **NOTE**: Taproot inputs are, as yet, not supported by this function. We currently only - /// attempt to sign ECDSA inputs. - /// - /// If you just want to sign an input with one specific key consider using `sighash_ecdsa`. This - /// function does not support scripts that contain `OP_CODESEPARATOR`. - /// - /// # Returns - /// - /// Either Ok(SigningKeys) or Err((SigningKeys, SigningErrors)), where - /// - SigningKeys: A map of input index -> pubkey associated with secret key used to sign. - /// - SigningKeys: A map of input index -> the error encountered while attempting to sign. - /// - /// If an error is returned some signatures may already have been added to the PSBT. Since - /// `partial_sigs` is a [`BTreeMap`] it is safe to retry, previous sigs will be overwritten. - pub fn sign(&mut self, k: &K) -> Result - 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() { - if let Ok(SigningAlgorithm::Ecdsa) = self.signing_algorithm(i) { - match self.bip32_sign_ecdsa(k, i, &mut cache) { - Ok(v) => { - used.insert(i, v); - } - 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( - &mut self, - k: &K, - input_index: usize, - cache: &mut SighashCache, - ) -> Result, SignError> - where - T: Borrow, - K: GetKey, - { - let msg_sighash_ty_res = self.sighash_ecdsa(input_index, cache); - - let input = &mut self.inputs[input_index]; // Index checked in call to `sighash_ecdsa`. - - let mut used = Vec::new(); // 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(PublicKey::new(*pk))) { - sk - } else { - continue; - }; - - // Only return the error if we have a secret key to sign this input. - let (msg, sighash_ty) = match msg_sighash_ty_res { - Err(e) => return Err(e), - Ok((msg, sighash_ty)) => (msg, sighash_ty), - }; - - let sig = ecdsa::Signature { - sig: sk.inner.sign_ecdsa(msg), - hash_ty: sighash_ty, - }; - - let pk = sk.public_key(); - - input.partial_sigs.insert(pk, sig); - used.push(pk); - } - - Ok(used) - } - - /// Returns the sighash message to sign an ECDSA input along with the sighash type. - /// - /// Uses the [`EcdsaSighashType`] from this input if one is specified. If no sighash type is - /// specified uses [`EcdsaSighashType::All`]. This function does not support scripts that - /// contain `OP_CODESEPARATOR`. - pub fn sighash_ecdsa>( - &self, - input_index: usize, - cache: &mut SighashCache, - ) -> Result<(Message, EcdsaSighashType), SignError> { - use OutputType::*; - - if self.signing_algorithm(input_index)? != SigningAlgorithm::Ecdsa { - return Err(SignError::WrongSigningAlgorithm); - } - - let input = self.checked_input(input_index)?; - let utxo = self.spend_utxo(input_index)?; - let spk = &utxo.script_pubkey; // scriptPubkey for input spend utxo. - - let hash_ty = input.ecdsa_hash_ty().map_err(|_| SignError::InvalidSighashType)?; // Only support standard sighash types. - - match self.output_type(input_index)? { - Bare => { - let sighash = cache.legacy_signature_hash(input_index, spk, hash_ty.to_u32())?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) - } - Sh => { - let script_code = - input.redeem_script.as_ref().ok_or(SignError::MissingRedeemScript)?; - let sighash = - cache.legacy_signature_hash(input_index, script_code, hash_ty.to_u32())?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) - } - Wpkh => { - let script_code = ScriptBuf::p2wpkh_script_code(spk).ok_or(SignError::NotWpkh)?; - let sighash = - cache.segwit_signature_hash(input_index, &script_code, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) - } - ShWpkh => { - let script_code = ScriptBuf::p2wpkh_script_code( - input.redeem_script.as_ref().expect("checked above"), - ) - .ok_or(SignError::NotWpkh)?; - let sighash = - cache.segwit_signature_hash(input_index, &script_code, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) - } - Wsh | ShWsh => { - let script_code = - input.witness_script.as_ref().ok_or(SignError::MissingWitnessScript)?; - let sighash = - cache.segwit_signature_hash(input_index, script_code, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) - } - Tr => { - // This PSBT signing API is WIP, taproot to come shortly. - Err(SignError::Unsupported) - } - } - } - - /// Returns the spending utxo for this PSBT's input at `input_index`. - pub fn spend_utxo(&self, input_index: usize) -> Result<&TxOut, SignError> { - let input = self.checked_input(input_index)?; - let utxo = if let Some(witness_utxo) = &input.witness_utxo { - witness_utxo - } else if let Some(non_witness_utxo) = &input.non_witness_utxo { - let vout = self.unsigned_tx.input[input_index].previous_output.vout; - &non_witness_utxo.output[vout as usize] - } 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, SignError> { - self.check_index_is_within_bounds(input_index)?; - Ok(&self.inputs[input_index]) - } - - /// 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<(), SignError> { - if input_index >= self.inputs.len() { - return Err(SignError::IndexOutOfBounds(input_index, self.inputs.len())); - } - - if input_index >= self.unsigned_tx.input.len() { - return Err(SignError::IndexOutOfBounds(input_index, self.unsigned_tx.input.len())); - } - - Ok(()) - } - - /// Returns the algorithm used to sign this PSBT's input at `input_index`. - fn signing_algorithm(&self, input_index: usize) -> Result { - let output_type = self.output_type(input_index)?; - Ok(output_type.signing_algorithm()) - } - - /// Returns the [`OutputType`] of the spend utxo for this PBST's input at `input_index`. - fn output_type(&self, input_index: usize) -> Result { - let input = self.checked_input(input_index)?; - let utxo = self.spend_utxo(input_index)?; - let spk = utxo.script_pubkey.clone(); - - // Anything that is not segwit and is not p2sh is `Bare`. - if !(spk.is_witness_program() || spk.is_p2sh()) { - return Ok(OutputType::Bare); - } - - if spk.is_v0_p2wpkh() { - return Ok(OutputType::Wpkh); - } - - if spk.is_v0_p2wsh() { - return Ok(OutputType::Wsh); - } - - if spk.is_p2sh() { - if input.redeem_script.as_ref().map(|s| s.is_v0_p2wpkh()).unwrap_or(false) { - return Ok(OutputType::ShWpkh); - } - if input.redeem_script.as_ref().map(|x| x.is_v0_p2wsh()).unwrap_or(false) { - return Ok(OutputType::ShWsh); - } - return Ok(OutputType::Sh); - } - - if spk.is_v1_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 { - let mut inputs: u64 = 0; - for utxo in self.iter_funding_utxos() { - inputs = inputs.checked_add(utxo?.value).ok_or(Error::FeeOverflow)?; - } - let mut outputs: u64 = 0; - for out in &self.unsigned_tx.output { - outputs = outputs.checked_add(out.value).ok_or(Error::FeeOverflow)?; - } - inputs.checked_sub(outputs).map(Amount::from_sat).ok_or(Error::NegativeFee) - } -} - -/// Data required to call [`GetKey`] to get the private key to sign an input. -#[derive(Clone, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum KeyRequest { - /// Request a private key using the associated public key. - Pubkey(PublicKey), - /// Request a private key using BIP-32 fingerprint and derivation path. - Bip32(KeySource), -} - -/// Trait to get a private key from a key request, key is then used to sign an input. -pub trait GetKey { - /// An error occurred while getting the key. - type Error: 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, Self::Error>; -} - -impl GetKey for ExtendedPrivKey { - type Error = GetKeyError; - - fn get_key(&self, key_request: KeyRequest) -> Result, Self::Error> { - match key_request { - KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), - KeyRequest::Bip32((fingerprint, path)) => { - let key = if self.fingerprint() == fingerprint { - let k = self.derive_priv(&path)?; - Some(PrivateKey { - compressed: true, - network: k.network, - inner: k.private_key, - }) - } else { - None - }; - Ok(key) - } - } - } -} - -/// Map of input index -> pubkey associated with secret key used to create signature for that input. -pub type SigningKeys = BTreeMap>; - -/// Map of input index -> the error encountered while attempting to sign that input. -pub type SigningErrors = BTreeMap; - -#[rustfmt::skip] -macro_rules! impl_get_key_for_set { - ($set:ident) => { - -impl GetKey for $set { - type Error = GetKeyError; - - fn get_key( - &self, - key_request: KeyRequest, - ) -> Result, Self::Error> { - match key_request { - KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), - KeyRequest::Bip32((fingerprint, path)) => { - for xpriv in self.iter() { - if xpriv.parent_fingerprint == fingerprint { - let k = xpriv.derive_priv(&path)?; - return Ok(Some(PrivateKey { - compressed: true, - network: k.network.into(), - inner: k.private_key, - })); - } - } - Ok(None) - } - } - } -}}} -impl_get_key_for_set!(BTreeSet); -impl_get_key_for_set!(HashSet); - -#[rustfmt::skip] -macro_rules! impl_get_key_for_map { - ($map:ident) => { - -impl GetKey for $map { - type Error = GetKeyError; - - fn get_key(&self, key_request: KeyRequest) -> Result, Self::Error> { - match key_request { - KeyRequest::Pubkey(pk) => Ok(self.get(&pk).cloned()), - KeyRequest::Bip32(_) => Err(GetKeyError::NotSupported), - } - } -}}} -impl_get_key_for_map!(BTreeMap); -impl_get_key_for_map!(HashMap); - -/// Errors when getting a key. -#[derive(Clone, PartialEq, Eq, Debug)] -#[non_exhaustive] -pub enum GetKeyError { - /// A bip32 error. - Bip32(bip32::Error), - /// The GetKey operation is not supported for this key request. - NotSupported, -} - -impl fmt::Display for GetKeyError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use GetKeyError::*; - - match *self { - Bip32(ref e) => write_err!(f, "a bip23 error"; e), - NotSupported => { - f.write_str("the GetKey operation is not supported for this key request") - } - } - } -} - -impl std::error::Error for GetKeyError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use GetKeyError::*; - - match *self { - NotSupported => None, - Bip32(ref e) => Some(e), - } - } -} - -impl From for GetKeyError { - fn from(e: bip32::Error) -> Self { - GetKeyError::Bip32(e) - } -} - -/// The various output types supported by the Bitcoin network. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[non_exhaustive] -pub enum OutputType { - /// An output of type: pay-to-pubkey or pay-to-pubkey-hash. - Bare, - /// A pay-to-witness-pubkey-hash output (P2WPKH). - Wpkh, - /// A pay-to-witness-script-hash output (P2WSH). - Wsh, - /// A nested segwit output, pay-to-witness-pubkey-hash nested in a pay-to-script-hash. - ShWpkh, - /// A nested segwit output, pay-to-witness-script-hash nested in a pay-to-script-hash. - ShWsh, - /// A pay-to-script-hash output excluding wrapped segwit (P2SH). - Sh, - /// A taproot output (P2TR). - Tr, -} - -impl OutputType { - /// The signing algorithm used to sign this output type. - pub fn signing_algorithm(&self) -> SigningAlgorithm { - use OutputType::*; - - match self { - Bare | Wpkh | Wsh | ShWpkh | ShWsh | Sh => SigningAlgorithm::Ecdsa, - Tr => SigningAlgorithm::Schnorr, - } - } -} - -/// Signing algorithms supported by the Bitcoin network. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum SigningAlgorithm { - /// The Elliptic Curve Digital Signature Algorithm (see [wikipedia]). - /// - /// [wikipedia]: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm - Ecdsa, - /// The Schnorr signature algorithm (see [wikipedia]). - /// - /// [wikipedia]: https://en.wikipedia.org/wiki/Schnorr_signature - Schnorr, -} - -/// Errors encountered while calculating the sighash message. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -pub enum SignError { - /// Input index out of bounds (actual index, maximum index allowed). - IndexOutOfBounds(usize, usize), - /// Invalid Sighash type. - InvalidSighashType, - /// Missing input utxo. - MissingInputUtxo, - /// Missing Redeem script. - MissingRedeemScript, - /// Missing spending utxo. - MissingSpendUtxo, - /// Missing witness script. - MissingWitnessScript, - /// Signing algorithm and key type does not match. - MismatchedAlgoKey, - /// Attempted to ECDSA sign an non-ECDSA input. - NotEcdsa, - /// The `scriptPubkey` is not a P2WPKH script. - NotWpkh, - /// Sighash computation error. - SighashComputation(sighash::Error), - /// Unable to determine the output type. - UnknownOutputType, - /// Unable to find key. - KeyNotFound, - /// Attempt to sign an input with the wrong signing algorithm. - WrongSigningAlgorithm, - /// Signing request currently unsupported. - Unsupported, -} - -impl fmt::Display for SignError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::SignError::*; - - match *self { - IndexOutOfBounds(ind, len) => { - write!(f, "index {}, psbt input len: {}", ind, len) - } - InvalidSighashType => write!(f, "invalid sighash type"), - MissingInputUtxo => write!(f, "missing input utxo in PBST"), - MissingRedeemScript => write!(f, "missing redeem script"), - MissingSpendUtxo => write!(f, "missing spend utxo in PSBT"), - MissingWitnessScript => write!(f, "missing witness script"), - MismatchedAlgoKey => write!(f, "signing algorithm and key type does not match"), - NotEcdsa => write!(f, "attempted to ECDSA sign an non-ECDSA input"), - NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"), - SighashComputation(e) => write!(f, "sighash: {}", e), - UnknownOutputType => write!(f, "unable to determine the output type"), - KeyNotFound => write!(f, "unable to find key"), - WrongSigningAlgorithm => { - write!(f, "attempt to sign an input with the wrong signing algorithm") - } - Unsupported => write!(f, "signing request currently unsupported"), - } - } -} - -impl std::error::Error for SignError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use self::SignError::*; - - match *self { - IndexOutOfBounds(_, _) - | InvalidSighashType - | MissingInputUtxo - | MissingRedeemScript - | MissingSpendUtxo - | MissingWitnessScript - | MismatchedAlgoKey - | NotEcdsa - | NotWpkh - | UnknownOutputType - | KeyNotFound - | WrongSigningAlgorithm - | Unsupported => None, - SighashComputation(ref e) => Some(e), - } - } -} - -impl From for SignError { - fn from(e: sighash::Error) -> Self { - SignError::SighashComputation(e) - } -} - -#[cfg(feature = "base64")] -mod display_from_str { - use core::fmt::{self, Display, Formatter}; - use core::str::FromStr; - - use base64::{engine::general_purpose::STANDARD, Engine as _}; - use internals::write_err; - - use super::{Error, PartiallySignedTransaction}; - - /// 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 Display for PsbtParseError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - use self::PsbtParseError::*; - - match *self { - PsbtEncoding(ref e) => write_err!(f, "error in internal PSBT data structure"; e), - Base64Encoding(ref e) => write_err!(f, "error in PSBT base64 encoding"; e), - } - } - } - - impl std::error::Error for PsbtParseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use self::PsbtParseError::*; - - match self { - PsbtEncoding(e) => Some(e), - Base64Encoding(e) => Some(e), - } - } - } - - impl Display for PartiallySignedTransaction { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", STANDARD.encode(self.serialize())) - } - } - - impl FromStr for PartiallySignedTransaction { - type Err = PsbtParseError; - - fn from_str(s: &str) -> Result { - let data = STANDARD.decode(s).map_err(PsbtParseError::Base64Encoding)?; - PartiallySignedTransaction::deserialize(&data).map_err(PsbtParseError::PsbtEncoding) - } - } -} - -#[cfg(feature = "base64")] -pub use self::display_from_str::PsbtParseError; - -#[cfg(test)] -mod tests { - macro_rules! hex (($hex:expr) => ( as dashcore_hashes::hex::FromHex>::from_hex($hex).unwrap())); - - use std::collections::BTreeMap; - - use dashcore_hashes::{hash160, ripemd160, sha256, Hash}; - use secp256k1; - #[cfg(feature = "rand")] - use secp256k1::SecretKey; - - use super::*; - use crate::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, KeySource}; - use crate::psbt::map::{Input, Output}; - use crate::psbt::raw; - use crate::psbt::serialize::{Deserialize, Serialize}; - use dashcore::blockdata::script::ScriptBuf; - use dashcore::blockdata::transaction::outpoint::OutPoint; - use dashcore::blockdata::transaction::txin::TxIn; - use dashcore::blockdata::transaction::txout::TxOut; - use dashcore::blockdata::transaction::Transaction; - use dashcore::blockdata::witness::Witness; - - #[test] - fn trivial_psbt() { - let psbt = PartiallySignedTransaction { - unsigned_tx: Transaction { - version: 2, - lock_time: 0, - input: Vec::new(), - output: Vec::new(), - special_transaction_payload: None, - }, - xpub: Default::default(), - version: 0, - proprietary: BTreeMap::new(), - unknown: BTreeMap::new(), - - inputs: Vec::new(), - outputs: Vec::new(), - }; - assert_eq!(psbt.serialize_hex(), "70736274ff01000a0200000000000000000000"); - } - - #[test] - fn psbt_uncompressed_key() { - let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff01003302000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff000000000000420204bb0d5d0cca36e7b9c80f63bc04c1240babb83bcd2803ef7ac8b6e2af594291daec281e856c98d210c5ab14dfd5828761f8ee7d5f45ca21ad3e4c4b41b747a3a047304402204f67e2afb76142d44fae58a2495d33a3419daa26cd0db8d04f3452b63289ac0f022010762a9fb67e94cc5cad9026f6dc99ff7f070f4278d30fbc7d0c869dd38c7fe70100").unwrap(); - - assert_eq!(psbt.inputs[0].partial_sigs.len(), 1); - let pk = psbt.inputs[0].partial_sigs.iter().next().unwrap().0; - assert!(!pk.compressed); - } - - #[test] - fn serialize_then_deserialize_output() { - let seed = hex!("000102030405060708090a0b0c0d0e0f"); - - let mut hd_keypaths: BTreeMap = Default::default(); - - let mut sk: ExtendedPrivKey = - ExtendedPrivKey::new_master(key_wallet::Network::Mainnet, &seed).unwrap(); - - let fprint = sk.fingerprint(); - - let dpath: Vec = vec![ - ChildNumber::from_normal_idx(0).unwrap(), - ChildNumber::from_normal_idx(1).unwrap(), - 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_priv(&dpath).unwrap(); - - let pk = ExtendedPubKey::from_priv(&sk); - - hd_keypaths.insert(pk.public_key, (fprint, dpath.into())); - - let expected: Output = Output { - redeem_script: Some( - ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), - ), - witness_script: Some( - ScriptBuf::from_hex("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 = PartiallySignedTransaction { - unsigned_tx: Transaction { - version: 2, - lock_time: 1257139, - input: vec![TxIn { - previous_output: OutPoint { - txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126" - .parse() - .unwrap(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: u32::MAX, - witness: Witness::default(), - }], - output: vec![ - TxOut { - value: 99999699, - script_pubkey: ScriptBuf::from_hex( - "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac", - ) - .unwrap(), - }, - TxOut { - value: 100000000, - script_pubkey: ScriptBuf::from_hex( - "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", - ) - .unwrap(), - }, - ], - special_transaction_payload: None, - }, - 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: 0u8, - key: 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: PartiallySignedTransaction = hex_psbt!(hex).unwrap(); - assert_eq!(hex, psbt.serialize_hex()); - } - - #[cfg(feature = "serde")] - #[test] - fn test_serde_psbt() { - //! Create a full PSBT value with various fields filled and make sure it can be JSONized. - use dashcore_hashes::sha256d; - - use crate::psbt::map::Input; - - // create some values to use in the PSBT - let tx = Transaction { - version: 1, - lock_time: 0, - input: vec![TxIn { - previous_output: OutPoint { - txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389" - .parse() - .unwrap(), - vout: 1, - }, - script_sig: ScriptBuf::from_hex("160014be18d152a9b012039daf3da7de4f53349eecb985") - .unwrap(), - sequence: u32::MAX, - witness: Witness::from_slice(&[hex!( - "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105" - )]), - }], - output: vec![TxOut { - value: 190303501938, - script_pubkey: ScriptBuf::from_hex( - "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587", - ) - .unwrap(), - }], - special_transaction_payload: None, - }; - let unknown: BTreeMap> = vec![( - raw::Key { - type_value: 1, - key: vec![0, 1], - }, - vec![3, 4, 5], - )] - .into_iter() - .collect(); - let key_source = ("deadbeef".parse().unwrap(), "m/0'/1".parse().unwrap()); - let keypaths: BTreeMap = vec![( - "0339880dc92394b7355e3d0439fa283c31de7590812ea011c4245c0674a685e883".parse().unwrap(), - key_source.clone(), - )] - .into_iter() - .collect(); - - let proprietary: BTreeMap> = 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 = PartiallySignedTransaction { - version: 0, - xpub: { - let xpub: ExtendedPubKey = - "xpub661MyMwAqRbcGoRVtwfvzZsq2VBJR1LAHfQstHUoxqDorV89vRoMxUZ27kLrraAj6MPi\ - QfrDb27gigC1VS1dBXi5jGpxmMeBXEkKkcXUTg4".parse().unwrap(); - vec![(xpub, key_source)].into_iter().collect() - }, - unsigned_tx: { - let mut unsigned = tx.clone(); - unsigned.input[0].script_sig = ScriptBuf::new(); - unsigned.input[0].witness = Witness::default(); - unsigned - }, - proprietary: proprietary.clone(), - unknown: unknown.clone(), - - inputs: vec![ - Input { - non_witness_utxo: Some(tx), - witness_utxo: Some(TxOut { - value: 190303501938, - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), - }), - sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::().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(&[]), vec![1, 2])].into_iter().collect(), - sha256_preimages: vec![(sha256::Hash::hash(&[]), vec![1, 2])].into_iter().collect(), - hash160_preimages: vec![(hash160::Hash::hash(&[]), vec![1, 2])].into_iter().collect(), - hash256_preimages: vec![(sha256d::Hash::hash(&[]), 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: PartiallySignedTransaction = serde_json::from_str(&encoded).unwrap(); - assert_eq!(psbt, decoded); - } - - mod bip_vectors { - use std::collections::BTreeMap; - #[cfg(feature = "base64")] - use std::str::FromStr; - - use crate::psbt::map::Map; - - use super::*; - - #[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() { - PartiallySignedTransaction::from_str("AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==").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() { - use crate::psbt::PsbtParseError; - PartiallySignedTransaction::from_str("cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==") - // This weird thing is necessary since rustc 0.29 prints out I/O error in a different format than later versions - .map_err(|err| match err { - PsbtParseError::PsbtEncoding(err) => err, - PsbtParseError::Base64Encoding(_) => panic!("PSBT Base64 decoding failed") - }) - .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() { - PartiallySignedTransaction::from_str("cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA=").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() { - PartiallySignedTransaction::from_str("cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==").unwrap(); - } - - #[test] - #[should_panic(expected = "DuplicateKey(Key { type_value: 0, key: [] })")] - fn invalid_vector_5() { - hex_psbt!("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000000").unwrap(); - } - - #[cfg(feature = "base64")] - #[test] - #[should_panic(expected = "DuplicateKey(Key { type_value: 0, key: [] })")] - fn invalid_vector_5_base64() { - PartiallySignedTransaction::from_str("cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQA/AgAAAAH//////////////////////////////////////////wAAAAAA/////wEAAAAAAAAAAANqAQAAAAAAAAAA").unwrap(); - } - - #[test] - fn valid_vector_1() { - let unserialized = PartiallySignedTransaction { - unsigned_tx: Transaction { - version: 2, - lock_time: 1257139, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126".parse().unwrap(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: u32::MAX, - witness: Witness::default(), - } - ], - output: vec![ - TxOut { - value: 99999699, - script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), - }, - TxOut { - value: 100000000, - script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(), - }, - ], - special_transaction_payload: None, - }, - xpub: Default::default(), - version: 0, - proprietary: BTreeMap::new(), - unknown: BTreeMap::new(), - - inputs: vec![ - Input { - non_witness_utxo: Some(Transaction { - version: 1, - lock_time: 0, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse().unwrap(), - vout: 1, - }, - script_sig: ScriptBuf::from_hex("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(), - sequence: u32::MAX, - witness: Witness::from_slice(&[ - hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01"), - hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"), - ]), - }, - TxIn { - previous_output: OutPoint { - txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886".parse().unwrap(), - vout: 1, - }, - script_sig: ScriptBuf::from_hex("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(), - sequence: u32::MAX, - witness: Witness::from_slice(&[ - hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01"), - hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"), - ]), - }, - ], - output: vec![ - TxOut { - value: 200000000, - script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(), - }, - TxOut { - value: 190303501938, - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), - }, - ], - special_transaction_payload: None, - }), - ..Default::default() - }, - ], - outputs: vec![ - Output { - ..Default::default() - }, - Output { - ..Default::default() - }, - ], - }; - - let base16str = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000ffffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000"; - - 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!(PartiallySignedTransaction::from_str(base64str).unwrap(), unserialized); - assert_eq!(base64str, unserialized.to_string()); - assert_eq!( - PartiallySignedTransaction::from_str(base64str).unwrap(), - hex_psbt!(base16str).unwrap() - ); - } - } - - #[test] - fn valid_vector_2() { - let psbt: PartiallySignedTransaction = 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 = - ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(); - - assert!(redeem_script.is_v0_p2wpkh()); - assert_eq!( - redeem_script.to_p2sh(), - psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey - ); - assert_eq!(redeem_script.to_p2sh(), expected_out); - - for output in psbt.outputs { - assert_eq!(output.get_pairs().len(), 0) - } - } - - #[test] - fn valid_vector_3() { - let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000").unwrap(); - - assert_eq!(psbt.inputs.len(), 1); - assert_eq!(psbt.outputs.len(), 2); - - let tx_input = &psbt.unsigned_tx.input[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.txid()); - assert!(psbt_non_witness_utxo.output[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: PartiallySignedTransaction = 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 = - ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(); - - assert!(redeem_script.is_v0_p2wpkh()); - assert_eq!( - redeem_script.to_p2sh(), - psbt.inputs[1].witness_utxo.as_ref().unwrap().script_pubkey - ); - assert_eq!(redeem_script.to_p2sh(), expected_out); - - for output in psbt.outputs { - assert!(!output.get_pairs().is_empty()) - } - } - - #[test] - fn valid_vector_5() { - let psbt: PartiallySignedTransaction = 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 = - ScriptBuf::from_hex("a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87").unwrap(); - - assert!(redeem_script.is_v0_p2wsh()); - assert_eq!( - redeem_script.to_p2sh(), - psbt.inputs[0].witness_utxo.as_ref().unwrap().script_pubkey - ); - - assert_eq!(redeem_script.to_p2sh(), expected_out); - } - - #[test] - fn valid_vector_6() { - let psbt: PartiallySignedTransaction = hex_psbt!("70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000").unwrap(); - - assert_eq!(psbt.inputs.len(), 1); - assert_eq!(psbt.outputs.len(), 1); - - let tx = &psbt.unsigned_tx; - assert_eq!( - tx.txid(), - "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115".parse().unwrap(), - ); - - let mut unknown: BTreeMap> = BTreeMap::new(); - let key: raw::Key = raw::Key { - type_value: 0x0fu8, - key: hex!("010203040506070809"), - }; - let value: Vec = hex!("0102030405060708090a0b0c0d0e0f"); - - unknown.insert(key, value); - - assert_eq!(psbt.inputs[0].unknown, unknown) - } - } - - mod bip_371_vectors { - 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(); - // The error message varies depending on the feature flags - let err_str = err.to_string(); - assert!( - err_str == "invalid taproot signature" - || err_str == "invalid taproot signature: invalid taproot signature size: 66", - "Unexpected error message: {}", - err_str - ); - 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(); - // The error message varies depending on the feature flags - let err_str = err.to_string(); - assert!( - err_str == "hash parse error" - || err_str == "hash parse error: invalid slice length 33 (expected 32)", - "Unexpected error message: {}", - err_str - ); - let err = hex_psbt!("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b094289756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb01010000").unwrap_err(); - // The error message varies depending on the feature flags - let err_str = err.to_string(); - assert!( - err_str == "invalid taproot signature" - || err_str == "invalid taproot signature: invalid taproot signature size: 66", - "Unexpected error message: {}", - err_str - ); - let err = hex_psbt!("70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b093989756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb0000").unwrap_err(); - // The error message varies depending on the feature flags - let err_str = err.to_string(); - assert!( - err_str == "invalid taproot signature" - || err_str == "invalid taproot signature: invalid taproot signature size: 57", - "Unexpected error message: {}", - err_str - ); - 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: PartiallySignedTransaction) { - 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 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 = PartiallySignedTransaction { - unsigned_tx: Transaction { - version: 2, - lock_time: 1257139, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126".parse().unwrap(), - vout: 0, - }, - script_sig: ScriptBuf::new(), - sequence: 0, - witness: Witness::default(), - } - ], - output: vec![ - TxOut { - value: 99999699, - script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), - }, - TxOut { - value: 100000000, - script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(), - }, - ], - special_transaction_payload: None, - }, - version: 0, - xpub: Default::default(), - proprietary: Default::default(), - unknown: BTreeMap::new(), - - inputs: vec![ - Input { - non_witness_utxo: Some(Transaction { - version: 1, - lock_time: 0, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse().unwrap(), - vout: 1, - }, - script_sig: ScriptBuf::from_hex("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(), - sequence: u32::MAX, - witness: Witness::from_slice(&[ - hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01"), - hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"), - ]), - }, - TxIn { - previous_output: OutPoint { - txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886".parse().unwrap(), - vout: 1, - }, - script_sig: ScriptBuf::from_hex("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(), - sequence: u32::MAX, - witness: Witness::from_slice(&[ - hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01"), - hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"), - ]), - }, - ], - output: vec![ - TxOut { - value: 200000000, - script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(), - }, - TxOut { - value: 190303501938, - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), - }, - ], - special_transaction_payload: None, - }), - ..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: PartiallySignedTransaction = hex_psbt!(&unserialized.serialize_hex()).unwrap(); - assert_eq!(rtt, unserialized); - - // Now add an 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 = hex_psbt!(&unserialized.serialize_hex()); - assert!(rtt.is_err()); - } - - #[test] - fn serialize_and_deserialize_proprietary() { - let mut psbt: PartiallySignedTransaction = hex_psbt!("70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000").unwrap(); - psbt.proprietary.insert( - raw::ProprietaryKey { - prefix: b"test".to_vec(), - subtype: 0u8, - key: b"test".to_vec(), - }, - b"test".to_vec(), - ); - assert!(!psbt.proprietary.is_empty()); - let rtt: PartiallySignedTransaction = hex_psbt!(&psbt.serialize_hex()).unwrap(); - assert!(!rtt.proprietary.is_empty()); - } - - // PSBTs taken from BIP 174 test vectors. - #[test] - fn combine_psbts() { - let mut psbt1 = hex_psbt!(include_str!("../../tests/data/psbt1.hex")).unwrap(); - let psbt2 = hex_psbt!(include_str!("../../tests/data/psbt2.hex")).unwrap(); - let psbt_combined = hex_psbt!(include_str!("../../tests/data/psbt2.hex")).unwrap(); - - psbt1.combine(psbt2).expect("psbt combine to succeed"); - assert_eq!(psbt1, psbt_combined); - } - - #[test] - fn combine_psbts_commutative() { - let mut psbt1 = hex_psbt!(include_str!("../../tests/data/psbt1.hex")).unwrap(); - let mut psbt2 = hex_psbt!(include_str!("../../tests/data/psbt2.hex")).unwrap(); - - let psbt1_clone = psbt1.clone(); - let psbt2_clone = psbt2.clone(); - - psbt1.combine(psbt2_clone).expect("psbt1 combine to succeed"); - psbt2.combine(psbt1_clone).expect("psbt2 combine to succeed"); - - assert_eq!(psbt1, psbt2); - } - - #[cfg(feature = "rand")] - fn gen_keys() -> (PrivateKey, PublicKey) { - use rand::{rng, RngCore}; - - let mut rng = rng(); - let mut secret_key_bytes = [0u8; 32]; - rng.fill_bytes(&mut secret_key_bytes); - let sk = - SecretKey::from_secret_bytes(secret_key_bytes).expect("32 bytes, within curve order"); - let priv_key = PrivateKey::new(sk, crate::Network::Regtest); - let pk = PublicKey::from_private_key(&priv_key); - - (priv_key, pk) - } - - #[test] - #[cfg(feature = "rand")] - fn get_key_btree_map() { - let (priv_key, pk) = gen_keys(); - - let mut key_map = BTreeMap::new(); - key_map.insert(pk, priv_key); - - let got = key_map.get_key(KeyRequest::Pubkey(pk)).expect("failed to get key"); - assert_eq!(got.unwrap(), priv_key) - } - - #[test] - fn test_fee() { - let output_0_val = 99999699; - let output_1_val = 100000000; - let prev_output_val = 200000000; - - let mut t = PartiallySignedTransaction { - unsigned_tx: Transaction { - version: 2, - lock_time: 1257139, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126".parse().unwrap(), - vout: 0, - }, - sequence: 0, - ..Default::default() - } - ], - output: vec![ - TxOut { - value: output_0_val, - ..Default::default() - }, - TxOut { - value: output_1_val, - ..Default::default() - }, - ], - special_transaction_payload: None, - }, - xpub: Default::default(), - version: 0, - proprietary: BTreeMap::new(), - unknown: BTreeMap::new(), - - inputs: vec![ - Input { - non_witness_utxo: Some(Transaction { - version: 1, - lock_time: 0, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse().unwrap(), - vout: 1, - }, - sequence: u32::MAX, - ..Default::default() - }, - TxIn { - previous_output: OutPoint { - txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886".parse().unwrap(), - vout: 1, - }, - sequence: u32::MAX, - ..Default::default() - }, - ], - output: vec![ - TxOut { - value: prev_output_val, - ..Default::default() - }, - TxOut { - value: 190303501938, - ..Default::default() - }, - ], - special_transaction_payload: None, - }), - ..Default::default() - }, - ], - outputs: vec![ - Output { - ..Default::default() - }, - Output { - ..Default::default() - }, - ], - }; - assert_eq!( - t.fee().expect("fee calculation"), - Amount::from_sat(prev_output_val - (output_0_val + output_1_val)) - ); - // no previous output - let mut t2 = t.clone(); - t2.inputs[0].non_witness_utxo = None; - match t2.fee().unwrap_err() { - Error::MissingUtxo => {} - e => panic!("unexpected error: {:?}", e), - } - // negative fee - let mut t3 = t.clone(); - t3.unsigned_tx.output[0].value = prev_output_val; - match t3.fee().unwrap_err() { - Error::NegativeFee => {} - e => panic!("unexpected error: {:?}", e), - } - // overflow - t.unsigned_tx.output[0].value = u64::MAX; - t.unsigned_tx.output[1].value = u64::MAX; - match t.fee().unwrap_err() { - Error::FeeOverflow => {} - e => panic!("unexpected error: {:?}", e), - } - } - - #[test] - #[cfg(feature = "rand")] - fn sign_psbt() { - use crate::bip32::{DerivationPath, Fingerprint}; - use dashcore::address::{WitnessProgram, WitnessVersion}; - use dashcore::WPubkeyHash; - - let unsigned_tx = Transaction { - version: 2, - lock_time: 0, - input: vec![TxIn::default(), TxIn::default()], - output: vec![TxOut::default()], - special_transaction_payload: None, - }; - let mut psbt = PartiallySignedTransaction::from_unsigned_tx(unsigned_tx).unwrap(); - - let (priv_key, pk) = gen_keys(); - - // key_map implements `GetKey` using KeyRequest::Pubkey. A pubkey key request does not use - // keysource so we use default `KeySource` (fingreprint and derivation path) below. - let mut key_map = BTreeMap::new(); - key_map.insert(pk, priv_key); - - // First input we can spend. See comment above on key_map for why we use defaults here. - let txout_wpkh = TxOut { - value: 10, - script_pubkey: ScriptBuf::new_v0_p2wpkh(&WPubkeyHash::hash(&pk.to_bytes())), - }; - psbt.inputs[0].witness_utxo = Some(txout_wpkh); - - let mut map = BTreeMap::new(); - map.insert(pk.inner, (Fingerprint::default(), DerivationPath::default())); - psbt.inputs[0].bip32_derivation = map; - - // Second input is unspendable by us e.g., from another wallet that supports future upgrades. - let unknown_prog = WitnessProgram::new(WitnessVersion::V4, vec![0xaa; 34]).unwrap(); - let txout_unknown_future = TxOut { - value: 10, - script_pubkey: ScriptBuf::new_witness_program(&unknown_prog), - }; - psbt.inputs[1].witness_utxo = Some(txout_unknown_future); - - let sigs = psbt.sign(&key_map).unwrap(); - - assert!(sigs.len() == 1); - assert!(sigs[&0] == vec![pk]); - } -} diff --git a/key-wallet/src/psbt/raw.rs b/key-wallet/src/psbt/raw.rs deleted file mode 100644 index 20aae577e..000000000 --- a/key-wallet/src/psbt/raw.rs +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Raw PSBT key-value pairs. -//! -//! Raw PSBT key-value pairs as defined at -//! . -//! - -use core::convert::TryFrom; -use core::fmt; -#[cfg(feature = "serde")] -use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize}; - -use super::serialize::{self, Deserialize, Serialize}; -use crate::psbt::Error; -use dashcore::consensus::encode::{ - self, deserialize, serialize, Decodable, Encodable, ReadExt, VarInt, WriteExt, MAX_VEC_SIZE, -}; -use dashcore::io; -use dashcore::prelude::DisplayHex; - -/// A PSBT key in its raw byte form. -#[derive(Debug, PartialEq, Hash, Eq, Clone, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(SerdeSerialize, SerdeDeserialize))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] -pub struct Key { - /// The type of this PSBT key. - pub type_value: u8, - /// The key itself in raw byte form. - /// ` := ` - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::hex_bytes"))] - pub key: Vec, -} - -/// A PSBT key-value pair in its raw byte form. -/// ` := ` -#[derive(Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(SerdeSerialize, SerdeDeserialize))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] -pub struct Pair { - /// The key of this key-value pair. - pub key: Key, - /// The value data of this key-value pair in raw byte form. - /// ` := ` - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::hex_bytes"))] - pub value: Vec, -} - -/// Default implementation for proprietary key subtyping -pub type ProprietaryType = u8; - -/// Proprietary keys (i.e. keys starting with 0xFC byte) with their internal -/// structure according to BIP 174. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -#[cfg_attr(feature = "serde", derive(SerdeSerialize, SerdeDeserialize))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] -pub struct ProprietaryKey -where - Subtype: Copy + From + Into, -{ - /// Proprietary type prefix used for grouping together keys under some - /// application and avoid namespace collision - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::hex_bytes"))] - pub prefix: Vec, - /// Custom proprietary subtype - pub subtype: Subtype, - /// Additional key bytes (like serialized public key data etc.) - #[cfg_attr(feature = "serde", serde(with = "dashcore::serde_utils::hex_bytes"))] - pub key: Vec, -} - -impl fmt::Display for Key { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "type: {:#x}, key: {:x}", self.type_value, self.key.as_hex()) - } -} - -impl Key { - pub(crate) fn decode(r: &mut R) -> Result { - let VarInt(byte_size): VarInt = Decodable::consensus_decode(r)?; - - if byte_size == 0 { - return Err(Error::NoMorePairs); - } - - let key_byte_size: u64 = byte_size - 1; - - if key_byte_size > MAX_VEC_SIZE as u64 { - Err(encode::Error::OversizedVectorAllocation { - requested: key_byte_size as usize, - max: MAX_VEC_SIZE, - })?; - } - - let type_value: u8 = Decodable::consensus_decode(r)?; - - let mut key = Vec::with_capacity(key_byte_size as usize); - for _ in 0..key_byte_size { - key.push(Decodable::consensus_decode(r)?); - } - - Ok(Key { - type_value, - key, - }) - } -} - -impl Serialize for Key { - fn serialize(&self) -> Vec { - let mut buf = Vec::new(); - VarInt((self.key.len() + 1) as u64) - .consensus_encode(&mut buf) - .expect("in-memory writers don't error"); - - self.type_value.consensus_encode(&mut buf).expect("in-memory writers don't error"); - - for key in &self.key { - key.consensus_encode(&mut buf).expect("in-memory writers don't error"); - } - - buf - } -} - -impl Serialize for Pair { - fn serialize(&self) -> Vec { - let mut buf = Vec::new(); - buf.extend(serialize::Serialize::serialize(&self.key)); - // := - self.value.consensus_encode(&mut buf).unwrap(); - buf - } -} - -impl Deserialize for Pair { - fn deserialize(bytes: &[u8]) -> Result { - let mut decoder = bytes; - Pair::decode(&mut decoder) - } -} - -impl Pair { - pub(crate) fn decode(r: &mut R) -> Result { - Ok(Pair { - key: Key::decode(r)?, - value: Decodable::consensus_decode(r)?, - }) - } -} - -impl Encodable for ProprietaryKey -where - Subtype: Copy + From + Into, -{ - fn consensus_encode(&self, w: &mut W) -> Result { - let mut len = self.prefix.consensus_encode(w)? + 1; - w.emit_u8(self.subtype.into())?; - w.write_all(&self.key)?; - len += self.key.len(); - Ok(len) - } -} - -impl Decodable for ProprietaryKey -where - Subtype: Copy + From + Into, -{ - fn consensus_decode(r: &mut R) -> Result { - let prefix = Vec::::consensus_decode(r)?; - let subtype = Subtype::from(r.read_u8()?); - let key = read_to_end(r)?; - - Ok(ProprietaryKey { - prefix, - subtype, - key, - }) - } -} - -impl ProprietaryKey -where - Subtype: Copy + From + Into, -{ - /// Constructs full [Key] corresponding to this proprietary key type - pub fn to_key(&self) -> Key { - Key { - type_value: 0xFC, - key: serialize(self), - } - } -} - -impl TryFrom for ProprietaryKey -where - Subtype: Copy + From + Into, -{ - type Error = Error; - - /// Constructs a [`ProprietaryKey`] from a [`Key`]. - /// - /// # Errors - /// Returns [`Error::InvalidProprietaryKey`] if `key` does not start with `0xFC` byte. - fn try_from(key: Key) -> Result { - if key.type_value != 0xFC { - return Err(Error::InvalidProprietaryKey); - } - - Ok(deserialize(&key.key)?) - } -} - -// core2 doesn't have read_to_end -pub(crate) fn read_to_end(mut d: D) -> Result, io::Error> { - let mut result = Vec::new(); - let mut buf = [0u8; 64]; - loop { - match d.read(&mut buf) { - Ok(0) => break, - Ok(n) => result.extend_from_slice(&buf[0..n]), - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => return Err(e), - }; - } - Ok(result) -} diff --git a/key-wallet/src/psbt/serialize.rs b/key-wallet/src/psbt/serialize.rs deleted file mode 100644 index 7641a159f..000000000 --- a/key-wallet/src/psbt/serialize.rs +++ /dev/null @@ -1,499 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! PSBT serialization. -//! -//! Traits to serialize PSBT values to and from raw bytes -//! according to the BIP-174 specification. -//! - -use core::convert::{TryFrom, TryInto}; - -use dashcore_hashes::{hash160, ripemd160, sha256, sha256d, Hash}; -use secp256k1::{self, XOnlyPublicKey}; - -use super::map::{Input, Map, Output, PsbtSighashType}; -use super::Psbt; -use crate::bip32::KeySource; -use crate::bip32::{ChildNumber, Fingerprint}; -use crate::psbt::{Error, PartiallySignedTransaction}; -use dashcore::blockdata::script::ScriptBuf; -use dashcore::blockdata::transaction::txout::TxOut; -use dashcore::blockdata::transaction::Transaction; -use dashcore::blockdata::witness::Witness; -use dashcore::consensus::encode::{self, deserialize_partial, serialize, Decodable, Encodable}; -use dashcore::crypto::key::PublicKey; -use dashcore::crypto::{ecdsa, taproot}; -use dashcore::taproot::{ - ControlBlock, LeafVersion, TapLeafHash, TapNodeHash, TapTree, TaprootBuilder, -}; -use dashcore::{io, VarInt}; -/// A trait for serializing a value as raw data for insertion into PSBT -/// key-value maps. -pub trait Serialize { - /// Serialize a value as raw data. - fn serialize(&self) -> Vec; -} - -/// A trait for deserializing a value from raw data in PSBT key-value maps. -pub trait Deserialize: Sized { - /// Deserialize a value from raw data. - fn deserialize(bytes: &[u8]) -> Result; -} - -impl PartiallySignedTransaction { - /// Serialize a value as bytes in hex. - pub fn serialize_hex(&self) -> String { - use dashcore::prelude::DisplayHex; - format!("{:x}", self.serialize().as_hex()) - } - - /// Serialize as raw binary data - pub fn serialize(&self) -> Vec { - let mut buf: Vec = Vec::new(); - - // - buf.extend_from_slice(b"psbt"); - - buf.push(0xff_u8); - - buf.extend(self.serialize_map()); - - for i in &self.inputs { - buf.extend(i.serialize_map()); - } - - for i in &self.outputs { - buf.extend(i.serialize_map()); - } - - buf - } - - /// Deserialize a value from raw binary data. - pub fn deserialize(bytes: &[u8]) -> Result { - const MAGIC_BYTES: &[u8] = b"psbt"; - if bytes.get(0..MAGIC_BYTES.len()) != Some(MAGIC_BYTES) { - return Err(Error::InvalidMagic); - } - - const PSBT_SEPARATOR: u8 = 0xff_u8; - if bytes.get(MAGIC_BYTES.len()) != Some(&PSBT_SEPARATOR) { - return Err(Error::InvalidSeparator); - } - - let mut d = bytes.get(5..).ok_or(Error::NoMorePairs)?; - - let mut global = Psbt::decode_global(&mut d)?; - global.unsigned_tx_checks()?; - - let inputs: Vec = { - let inputs_len: usize = global.unsigned_tx.input.len(); - - let mut inputs: Vec = Vec::with_capacity(inputs_len); - - for _ in 0..inputs_len { - inputs.push(Input::decode(&mut d)?); - } - - inputs - }; - - let outputs: Vec = { - let outputs_len: usize = global.unsigned_tx.output.len(); - - let mut outputs: Vec = Vec::with_capacity(outputs_len); - - for _ in 0..outputs_len { - outputs.push(Output::decode(&mut d)?); - } - - outputs - }; - - global.inputs = inputs; - global.outputs = outputs; - Ok(global) - } -} -impl_psbt_de_serialize!(Transaction); -impl_psbt_de_serialize!(TxOut); -impl_psbt_de_serialize!(Witness); -impl_psbt_hash_de_serialize!(ripemd160::Hash); -impl_psbt_hash_de_serialize!(sha256::Hash); -impl_psbt_hash_de_serialize!(TapLeafHash); -impl_psbt_hash_de_serialize!(TapNodeHash); -impl_psbt_hash_de_serialize!(hash160::Hash); -impl_psbt_hash_de_serialize!(sha256d::Hash); - -// taproot -impl_psbt_de_serialize!(Vec); - -impl Serialize for ScriptBuf { - fn serialize(&self) -> Vec { - self.to_bytes() - } -} - -impl Deserialize for ScriptBuf { - fn deserialize(bytes: &[u8]) -> Result { - Ok(Self::from(bytes.to_vec())) - } -} - -impl Serialize for PublicKey { - fn serialize(&self) -> Vec { - let mut buf = Vec::new(); - self.write_into(&mut buf).expect("vecs don't error"); - buf - } -} - -impl Deserialize for PublicKey { - fn deserialize(bytes: &[u8]) -> Result { - PublicKey::from_slice(bytes).map_err(Error::InvalidPublicKey) - } -} - -impl Serialize for secp256k1::PublicKey { - fn serialize(&self) -> Vec { - self.serialize().to_vec() - } -} - -impl Deserialize for secp256k1::PublicKey { - fn deserialize(bytes: &[u8]) -> Result { - let invalid = || Error::InvalidSecp256k1PublicKey(secp256k1::Error::InvalidPublicKey); - match bytes.len() { - secp256k1::constants::PUBLIC_KEY_SIZE => { - secp256k1::PublicKey::from_byte_array_compressed( - bytes.try_into().map_err(|_| invalid())?, - ) - } - secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE => { - secp256k1::PublicKey::from_byte_array_uncompressed( - bytes.try_into().map_err(|_| invalid())?, - ) - } - _ => return Err(invalid()), - } - .map_err(Error::InvalidSecp256k1PublicKey) - } -} - -impl Serialize for ecdsa::Signature { - fn serialize(&self) -> Vec { - self.to_vec() - } -} - -impl Deserialize for ecdsa::Signature { - fn deserialize(bytes: &[u8]) -> Result { - // NB: Since BIP-174 says "the signature as would be pushed to the stack from - // a scriptSig or witness" we should ideally use a consensus deserialization and do - // not error on a non-standard values. However, - // - // 1) the current implementation of from_u32_consensus(`flag`) does not preserve - // the sighash byte `flag` mapping all unknown values to EcdsaSighashType::All or - // EcdsaSighashType::AllPlusAnyOneCanPay. Therefore, break the invariant - // EcdsaSig::from_slice(&sl[..]).to_vec = sl. - // - // 2) This would cause to have invalid signatures because the sighash message - // also has a field sighash_u32 (See BIP141). For example, when signing with non-standard - // 0x05, the sighash message would have the last field as 0x05u32 while, the verification - // would use check the signature assuming sighash_u32 as `0x01`. - ecdsa::Signature::from_slice(bytes).map_err(|e| match e { - ecdsa::Error::EmptySignature => Error::InvalidEcdsaSignature(e), - ecdsa::Error::NonStandardSighashType(flag) => Error::NonStandardSighashType(flag), - ecdsa::Error::Secp256k1(..) => Error::InvalidEcdsaSignature(e), - ecdsa::Error::HexEncoding(..) => { - unreachable!("Decoding from slice, not hex") - } - _ => Error::InvalidEcdsaSignature(e), - }) - } -} - -impl Serialize for KeySource { - fn serialize(&self) -> Vec { - let mut rv: Vec = Vec::with_capacity(key_source_len(self)); - - rv.append(&mut self.0.to_bytes().to_vec()); - - for cnum in self.1.into_iter() { - rv.append(&mut serialize(&u32::from(*cnum))) - } - - rv - } -} - -impl Deserialize for KeySource { - fn deserialize(bytes: &[u8]) -> Result { - if bytes.len() < 4 { - return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()); - } - - let fprint: Fingerprint = bytes[0..4].try_into().expect("4 is the fingerprint length"); - let mut dpath: Vec = Default::default(); - - let mut d = &bytes[4..]; - while !d.is_empty() { - match u32::consensus_decode(&mut d) { - Ok(index) => dpath.push(index.into()), - Err(e) => return Err(e)?, - } - } - - Ok((fprint, dpath.into())) - } -} - -// partial sigs -impl Serialize for Vec { - fn serialize(&self) -> Vec { - self.clone() - } -} - -impl Deserialize for Vec { - fn deserialize(bytes: &[u8]) -> Result { - Ok(bytes.to_vec()) - } -} - -impl Serialize for PsbtSighashType { - fn serialize(&self) -> Vec { - serialize(&self.to_u32()) - } -} - -impl Deserialize for PsbtSighashType { - fn deserialize(bytes: &[u8]) -> Result { - let raw: u32 = encode::deserialize(bytes)?; - Ok(PsbtSighashType { - inner: raw, - }) - } -} - -// Taproot related ser/deser -impl Serialize for XOnlyPublicKey { - fn serialize(&self) -> Vec { - XOnlyPublicKey::to_byte_array(self).to_vec() - } -} - -impl Deserialize for XOnlyPublicKey { - fn deserialize(bytes: &[u8]) -> Result { - let bytes = <[u8; 32]>::try_from(bytes).map_err(|_| Error::InvalidXOnlyPublicKey)?; - XOnlyPublicKey::from_byte_array(bytes).map_err(|_| Error::InvalidXOnlyPublicKey) - } -} - -impl Serialize for taproot::Signature { - fn serialize(&self) -> Vec { - self.to_vec() - } -} - -impl Deserialize for taproot::Signature { - fn deserialize(bytes: &[u8]) -> Result { - taproot::Signature::from_slice(bytes).map_err(|e| match e { - taproot::Error::InvalidSighashType(flag) => Error::NonStandardSighashType(flag as u32), - taproot::Error::InvalidSignatureSize(_) => Error::InvalidTaprootSignature(e), - taproot::Error::Secp256k1(..) => Error::InvalidTaprootSignature(e), - _ => Error::InvalidTaprootSignature(e), - }) - } -} - -impl Serialize for (XOnlyPublicKey, TapLeafHash) { - fn serialize(&self) -> Vec { - let ser_pk = self.0.to_byte_array(); - let mut buf = Vec::with_capacity(ser_pk.len() + self.1.as_byte_array().len()); - buf.extend(&ser_pk); - buf.extend(self.1.as_byte_array()); - buf - } -} - -impl Deserialize for (XOnlyPublicKey, TapLeafHash) { - fn deserialize(bytes: &[u8]) -> Result { - if bytes.len() < 32 { - return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()); - } - let a: XOnlyPublicKey = Deserialize::deserialize(&bytes[..32])?; - let b: TapLeafHash = Deserialize::deserialize(&bytes[32..])?; - Ok((a, b)) - } -} - -impl Serialize for ControlBlock { - fn serialize(&self) -> Vec { - ControlBlock::serialize(self) - } -} - -impl Deserialize for ControlBlock { - fn deserialize(bytes: &[u8]) -> Result { - Self::decode(bytes).map_err(|_| Error::InvalidControlBlock) - } -} - -// Versioned ScriptBuf -impl Serialize for (ScriptBuf, LeafVersion) { - fn serialize(&self) -> Vec { - let mut buf = Vec::with_capacity(self.0.len() + 1); - buf.extend(self.0.as_bytes()); - buf.push(self.1.to_consensus()); - buf - } -} - -impl Deserialize for (ScriptBuf, LeafVersion) { - fn deserialize(bytes: &[u8]) -> Result { - if bytes.is_empty() { - return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()); - } - // The last byte is LeafVersion. - let script = ScriptBuf::deserialize(&bytes[..bytes.len() - 1])?; - let leaf_ver = LeafVersion::from_consensus(bytes[bytes.len() - 1]) - .map_err(|_| Error::InvalidLeafVersion)?; - Ok((script, leaf_ver)) - } -} - -impl Serialize for (Vec, KeySource) { - fn serialize(&self) -> Vec { - let mut buf = Vec::with_capacity(32 * self.0.len() + key_source_len(&self.1)); - self.0.consensus_encode(&mut buf).expect("Vecs don't error allocation"); - // TODO: Add support for writing into a writer for key-source - buf.extend(self.1.serialize()); - buf - } -} - -impl Deserialize for (Vec, KeySource) { - fn deserialize(bytes: &[u8]) -> Result { - let (leafhash_vec, consumed) = deserialize_partial::>(bytes)?; - let key_source = KeySource::deserialize(&bytes[consumed..])?; - Ok((leafhash_vec, key_source)) - } -} - -impl Serialize for TapTree { - fn serialize(&self) -> Vec { - let capacity = self - .script_leaves() - .map(|l| { - l.script().len() + VarInt(l.script().len() as u64).len() // script version - + 1 // merkle branch - + 1 // leaf version - }) - .sum::(); - let mut buf = Vec::with_capacity(capacity); - for leaf_info in self.script_leaves() { - // # Cast Safety: - // - // TaprootMerkleBranch can only have len at most 128(TAPROOT_CONTROL_MAX_NODE_COUNT). - // safe to cast from usize to u8 - buf.push(leaf_info.merkle_branch().len() as u8); - buf.push(leaf_info.version().to_consensus()); - leaf_info.script().consensus_encode(&mut buf).expect("Vecs dont err"); - } - buf - } -} - -impl Deserialize for TapTree { - fn deserialize(bytes: &[u8]) -> Result { - let mut builder = TaprootBuilder::new(); - let mut bytes_iter = bytes.iter(); - while let Some(depth) = bytes_iter.next() { - let version = bytes_iter.next().ok_or(Error::Taproot("Invalid Taproot Builder"))?; - let (script, consumed) = deserialize_partial::(bytes_iter.as_slice())?; - if consumed > 0 { - bytes_iter.nth(consumed - 1); - } - let leaf_version = - LeafVersion::from_consensus(*version).map_err(|_| Error::InvalidLeafVersion)?; - builder = builder - .add_leaf_with_ver(*depth, script, leaf_version) - .map_err(|_| Error::Taproot("Tree not in DFS order"))?; - } - TapTree::try_from(builder).map_err(Error::TapTree) - } -} - -// Helper function to compute key source len -fn key_source_len(key_source: &KeySource) -> usize { - 4 + 4 * key_source.1.as_ref().len() -} - -#[cfg(test)] -mod tests { - use core::convert::TryFrom; - - use super::*; - - // Composes tree matching a given depth map, filled with dumb script leaves, - // each of which consists of a single push-int op code, with int value - // increased for each consecutive leaf. - pub fn compose_taproot_builder<'map>( - opcode: u8, - depth_map: impl IntoIterator, - ) -> TaprootBuilder { - let mut val = opcode; - let mut builder = TaprootBuilder::new(); - for depth in depth_map { - let script = ScriptBuf::from_hex(&format!("{:02x}", val)).unwrap(); - builder = builder.add_leaf(*depth, script).unwrap(); - let (new_val, _) = val.overflowing_add(1); - val = new_val; - } - builder - } - - #[test] - fn taptree_hidden() { - let mut builder = compose_taproot_builder(0x51, &[2, 2, 2]); - builder = builder - .add_leaf_with_ver( - 3, - ScriptBuf::from_hex("b9").unwrap(), - LeafVersion::from_consensus(0xC2).unwrap(), - ) - .unwrap(); - builder = builder.add_hidden_node(3, TapNodeHash::all_zeros()).unwrap(); - assert!(TapTree::try_from(builder).is_err()); - } - - #[test] - fn taptree_roundtrip() { - let mut builder = compose_taproot_builder(0x51, &[2, 2, 2, 3]); - builder = builder - .add_leaf_with_ver( - 3, - ScriptBuf::from_hex("b9").unwrap(), - LeafVersion::from_consensus(0xC2).unwrap(), - ) - .unwrap(); - let tree = TapTree::try_from(builder).unwrap(); - let tree_prime = TapTree::deserialize(&tree.serialize()).unwrap(); - assert_eq!(tree, tree_prime); - } - - #[test] - fn can_deserialize_non_standard_psbt_sighash_type() { - let non_standard_sighash = [222u8, 0u8, 0u8, 0u8]; // 32 byte value. - let sighash = PsbtSighashType::deserialize(&non_standard_sighash); - assert!(sighash.is_ok()) - } - - #[test] - #[should_panic(expected = "InvalidMagic")] - fn invalid_vector_1() { - let hex_psbt = b"0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300"; - PartiallySignedTransaction::deserialize(hex_psbt).unwrap(); - } -} diff --git a/key-wallet/tests/data/combine_psbt_hex b/key-wallet/tests/data/combine_psbt_hex deleted file mode 100644 index ed4300239..000000000 --- a/key-wallet/tests/data/combine_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f61876500000022020220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d915947304402206fb9df19d298b6709df692cffc0116252141c456972d06e3a276fd449bd12eb802200495df4145c6866d6f29714163d7f65b9821e05c1c9e330a7a5bb8293ebf92ec0122020353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e9447304402204205bdd760af17a41b44a79311671c889f5b570af18fa538deabc1e5569d1676022008a1130e1456140cb3761f246aa9dca66204ffab3c1ae9ffd131d9716dfefdc601010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae22060220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d915910fa7c43d400000080000000800100008022060353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e9410fa7c43d40000008000000080000000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220202e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee41918483045022100dc0b27baf232837b36918d043b291a4fa9710dd1cc1f0d20a08d18b5960148b202206c09dab81dc06056601f6951bb7b1303991177b4de8a6a1c00bbab835ecfd82c01220203a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df4483045022100ea1adaa2770707448f5309f6c7600d0eff4749a65ca8eec18e5424b909285ba30220533ad52540311d47c276325125f466415650e076923837cdb19e930284a904c4010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae220602e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee4191810fa7c43d4000000800000008003000080220603a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df410fa7c43d400000080000000800200008000220202a262d32e02856d69bb791042b3773b6fca08d62bcf87be9c5fa9e2b8d3adbb7510fa7c43d400000080000000800400008000220202c3c127892d7104e5f2f8c22612e0f81036ac64a7ac01d9ae0bc985337d4dd46e10fa7c43d400000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/create_psbt_hex b/key-wallet/tests/data/create_psbt_hex deleted file mode 100644 index b5ef8d5ed..000000000 --- a/key-wallet/tests/data/create_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000 \ No newline at end of file diff --git a/key-wallet/tests/data/extract_tx_hex b/key-wallet/tests/data/extract_tx_hex deleted file mode 100644 index 4b7ec925d..000000000 --- a/key-wallet/tests/data/extract_tx_hex +++ /dev/null @@ -1 +0,0 @@ -0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000d90047304402206fb9df19d298b6709df692cffc0116252141c456972d06e3a276fd449bd12eb802200495df4145c6866d6f29714163d7f65b9821e05c1c9e330a7a5bb8293ebf92ec0147304402204205bdd760af17a41b44a79311671c889f5b570af18fa538deabc1e5569d1676022008a1130e1456140cb3761f246aa9dca66204ffab3c1ae9ffd131d9716dfefdc601475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400483045022100ea1adaa2770707448f5309f6c7600d0eff4749a65ca8eec18e5424b909285ba30220533ad52540311d47c276325125f466415650e076923837cdb19e930284a904c401483045022100dc0b27baf232837b36918d043b291a4fa9710dd1cc1f0d20a08d18b5960148b202206c09dab81dc06056601f6951bb7b1303991177b4de8a6a1c00bbab835ecfd82c0147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000 \ No newline at end of file diff --git a/key-wallet/tests/data/finalize_psbt_hex b/key-wallet/tests/data/finalize_psbt_hex deleted file mode 100644 index 53e480b59..000000000 --- a/key-wallet/tests/data/finalize_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107d90047304402206fb9df19d298b6709df692cffc0116252141c456972d06e3a276fd449bd12eb802200495df4145c6866d6f29714163d7f65b9821e05c1c9e330a7a5bb8293ebf92ec0147304402204205bdd760af17a41b44a79311671c889f5b570af18fa538deabc1e5569d1676022008a1130e1456140cb3761f246aa9dca66204ffab3c1ae9ffd131d9716dfefdc601475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108dc0400483045022100ea1adaa2770707448f5309f6c7600d0eff4749a65ca8eec18e5424b909285ba30220533ad52540311d47c276325125f466415650e076923837cdb19e930284a904c401483045022100dc0b27baf232837b36918d043b291a4fa9710dd1cc1f0d20a08d18b5960148b202206c09dab81dc06056601f6951bb7b1303991177b4de8a6a1c00bbab835ecfd82c0147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220202a262d32e02856d69bb791042b3773b6fca08d62bcf87be9c5fa9e2b8d3adbb7510fa7c43d400000080000000800400008000220202c3c127892d7104e5f2f8c22612e0f81036ac64a7ac01d9ae0bc985337d4dd46e10fa7c43d400000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/lex_combine_psbt_hex b/key-wallet/tests/data/lex_combine_psbt_hex deleted file mode 100644 index 9ee710b6a..000000000 --- a/key-wallet/tests/data/lex_combine_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000af00102030405060708090f0102030405060708090a0b0c0d0e0f0af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f0af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f0af00102030405060708100f0102030405060708090a0b0c0d0e0f00 \ No newline at end of file diff --git a/key-wallet/tests/data/lex_psbt_1_hex b/key-wallet/tests/data/lex_psbt_1_hex deleted file mode 100644 index a125c5298..000000000 --- a/key-wallet/tests/data/lex_psbt_1_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000af00102030405060708090f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f000af00102030405060708090f0102030405060708090a0b0c0d0e0f00 \ No newline at end of file diff --git a/key-wallet/tests/data/lex_psbt_2_hex b/key-wallet/tests/data/lex_psbt_2_hex deleted file mode 100644 index 766c7eee1..000000000 --- a/key-wallet/tests/data/lex_psbt_2_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708100f0102030405060708090a0b0c0d0e0f000af00102030405060708100f0102030405060708090a0b0c0d0e0f00 \ No newline at end of file diff --git a/key-wallet/tests/data/previous_tx_0_hex b/key-wallet/tests/data/previous_tx_0_hex deleted file mode 100644 index ce2089453..000000000 --- a/key-wallet/tests/data/previous_tx_0_hex +++ /dev/null @@ -1 +0,0 @@ -0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000 \ No newline at end of file diff --git a/key-wallet/tests/data/previous_tx_1_hex b/key-wallet/tests/data/previous_tx_1_hex deleted file mode 100644 index 9297523e3..000000000 --- a/key-wallet/tests/data/previous_tx_1_hex +++ /dev/null @@ -1 +0,0 @@ -0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000 \ No newline at end of file diff --git a/key-wallet/tests/data/psbt1.hex b/key-wallet/tests/data/psbt1.hex deleted file mode 100644 index 3bcb9179a..000000000 --- a/key-wallet/tests/data/psbt1.hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/psbt2.hex b/key-wallet/tests/data/psbt2.hex deleted file mode 100644 index 47f387017..000000000 --- a/key-wallet/tests/data/psbt2.hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/psbt_combined.hex b/key-wallet/tests/data/psbt_combined.hex deleted file mode 100644 index 47f387017..000000000 --- a/key-wallet/tests/data/psbt_combined.hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/sign_1_psbt_hex b/key-wallet/tests/data/sign_1_psbt_hex deleted file mode 100644 index 7f388dee1..000000000 --- a/key-wallet/tests/data/sign_1_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f61876500000022020353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e9447304402204205bdd760af17a41b44a79311671c889f5b570af18fa538deabc1e5569d1676022008a1130e1456140cb3761f246aa9dca66204ffab3c1ae9ffd131d9716dfefdc601010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae22060220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d915910fa7c43d400000080000000800100008022060353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e9410fa7c43d40000008000000080000000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df4483045022100ea1adaa2770707448f5309f6c7600d0eff4749a65ca8eec18e5424b909285ba30220533ad52540311d47c276325125f466415650e076923837cdb19e930284a904c4010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae220602e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee4191810fa7c43d4000000800000008003000080220603a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df410fa7c43d400000080000000800200008000220202a262d32e02856d69bb791042b3773b6fca08d62bcf87be9c5fa9e2b8d3adbb7510fa7c43d400000080000000800400008000220202c3c127892d7104e5f2f8c22612e0f81036ac64a7ac01d9ae0bc985337d4dd46e10fa7c43d400000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/sign_2_psbt_hex b/key-wallet/tests/data/sign_2_psbt_hex deleted file mode 100644 index 537c3383b..000000000 --- a/key-wallet/tests/data/sign_2_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f61876500000022020220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d915947304402206fb9df19d298b6709df692cffc0116252141c456972d06e3a276fd449bd12eb802200495df4145c6866d6f29714163d7f65b9821e05c1c9e330a7a5bb8293ebf92ec01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae22060220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d915910fa7c43d400000080000000800100008022060353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e9410fa7c43d40000008000000080000000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220202e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee41918483045022100dc0b27baf232837b36918d043b291a4fa9710dd1cc1f0d20a08d18b5960148b202206c09dab81dc06056601f6951bb7b1303991177b4de8a6a1c00bbab835ecfd82c010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae220602e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee4191810fa7c43d4000000800000008003000080220603a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df410fa7c43d400000080000000800200008000220202a262d32e02856d69bb791042b3773b6fca08d62bcf87be9c5fa9e2b8d3adbb7510fa7c43d400000080000000800400008000220202c3c127892d7104e5f2f8c22612e0f81036ac64a7ac01d9ae0bc985337d4dd46e10fa7c43d400000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/update_1_psbt_hex b/key-wallet/tests/data/update_1_psbt_hex deleted file mode 100644 index 14d9c550c..000000000 --- a/key-wallet/tests/data/update_1_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae22060220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d915910fa7c43d400000080000000800100008022060353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e9410fa7c43d40000008000000080000000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae220602e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee4191810fa7c43d4000000800000008003000080220603a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df410fa7c43d400000080000000800200008000220202a262d32e02856d69bb791042b3773b6fca08d62bcf87be9c5fa9e2b8d3adbb7510fa7c43d400000080000000800400008000220202c3c127892d7104e5f2f8c22612e0f81036ac64a7ac01d9ae0bc985337d4dd46e10fa7c43d400000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/data/update_2_psbt_hex b/key-wallet/tests/data/update_2_psbt_hex deleted file mode 100644 index 2003d3583..000000000 --- a/key-wallet/tests/data/update_2_psbt_hex +++ /dev/null @@ -1 +0,0 @@ -70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae22060220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d915910fa7c43d400000080000000800100008022060353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e9410fa7c43d40000008000000080000000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae220602e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee4191810fa7c43d4000000800000008003000080220603a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df410fa7c43d400000080000000800200008000220202a262d32e02856d69bb791042b3773b6fca08d62bcf87be9c5fa9e2b8d3adbb7510fa7c43d400000080000000800400008000220202c3c127892d7104e5f2f8c22612e0f81036ac64a7ac01d9ae0bc985337d4dd46e10fa7c43d400000080000000800500008000 \ No newline at end of file diff --git a/key-wallet/tests/psbt.rs b/key-wallet/tests/psbt.rs deleted file mode 100644 index 6633fd2d8..000000000 --- a/key-wallet/tests/psbt.rs +++ /dev/null @@ -1,477 +0,0 @@ -//! Tests PSBT integration vectors from BIP 174 -//! defined at - -use core::convert::TryFrom; -use std::collections::BTreeMap; -use std::str::FromStr; - -use dashcore::blockdata::opcodes::OP_0; -use dashcore::blockdata::script; -use dashcore::consensus::encode::{deserialize, serialize_hex}; -use dashcore::hashes::hex::FromHex; -use dashcore::script::PushBytes; -use dashcore::secp256k1; -use dashcore::{ - Amount, Denomination, OutPoint, PrivateKey, PublicKey, ScriptBuf, Transaction, TxIn, TxOut, - Witness, -}; -use key_wallet::bip32::{ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource}; -use key_wallet::psbt::{PartiallySignedTransaction as Psbt, PsbtSighashType}; - -macro_rules! hex_script { - ($s:expr) => { - ::from_hex($s).unwrap() - }; -} - -macro_rules! hex_psbt { - ($s:expr) => { - key_wallet::psbt::PartiallySignedTransaction::deserialize( - & as FromHex>::from_hex($s).unwrap(), - ) - }; -} - -#[ignore] -#[test] -fn bip174_psbt_workflow() { - // - // Step 0: Create the extended private key from the test vector data. - // - - let ext_priv = build_extended_private_key(); - let ext_pub = ExtendedPubKey::from_priv(&ext_priv); - let parent_fingerprint = ext_pub.fingerprint(); - - // - // Step 1: The creator. - // - - let tx = create_transaction(); - let psbt = create_psbt(tx); - - // - // Step 2: The first updater. - // - - let psbt = update_psbt(psbt, parent_fingerprint); - - // - // Step 3: The second updater. - // - - let psbt = update_psbt_with_sighash_all(psbt); - - // - // Step 4: The first signer. - // - - // Strings from BIP 174 test vector. - let test_vector = vec![ - ("cQREycwKkUqJrgkYqcSLpv5Ab1ytgVkRk5e7dENJ5pQjUd83qwFd", "m/0h/0h/0h"), // from_priv, into_derivation_path? - ("cUQLHVPngMoreTZfKhb74quVxtiUuHtt5CnbEgnZTUFk8Vhid9HCh", "m/0h/0h/2h"), - ]; - - // We pass the keys to the signer after doing verification to make explicit - // that signer is only using these two keys. - let keys = parse_and_verify_keys(&ext_priv, &test_vector); - let psbt_1 = signer_one_sign(psbt.clone(), keys); - - // - // Step 5: The second signer. - // - - // Strings from BIP 174 test vector. - let test_vector = vec![ - ("cVKFqnCzcH5ZThgJ5YyfCYhh7Y8D6xkja7bdbwFT6FFAEtLTnqmy", "m/0h/0h/1h"), - ("cNUQGYrUibaUJ2eXuWohNiPJf4fuUHxnT4QYiMyT97QDhSuBZQBJ", "m/0h/0h/3h"), - ]; - - let keys = parse_and_verify_keys(&ext_priv, &test_vector); - let psbt_2 = signer_two_sign(psbt, keys); - - // - // Step 6: Combiner the two signed PSBTs. - // - - let combined = combine(psbt_1, psbt_2); - - // - // Step 7: Finalize the PSBT. - // - - let finalized = finalize(combined); - - // - // Step 8: Extract the transaction. - // - - let _tx = extract_transaction(finalized); - - // - // Step 9: Test lexicographical PSBT combiner. - // - // Combine would be done earlier, at Step 6, in typical workflow. - // We define it here to reflect the order of test vectors in BIP 174. - // - - combine_lexicographically(); -} - -/// Attempts to build an extended private key from seed and also directly from a string. -fn build_extended_private_key() -> ExtendedPrivKey { - // Strings from BIP 174 test vector. - let extended_private_key = "tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF"; - let seed = "cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T"; - - let xpriv = ExtendedPrivKey::from_str(extended_private_key).unwrap(); - - let sk = PrivateKey::from_wif(seed).unwrap(); - let seeded = - ExtendedPrivKey::new_master(key_wallet::Network::Testnet, &sk.inner.to_secret_bytes()) - .unwrap(); - assert_eq!(xpriv, seeded); - - xpriv -} - -/// Creates the initial transaction, called by the PSBT Creator. -fn create_transaction() -> Transaction { - // Strings from BIP 174 test vector. - let output_0 = TvOutput { - amount: "1.49990000", - script_pubkey: "0014d85c2b71d0060b09c9886aeb815e50991dda124d", - }; - let output_1 = TvOutput { - amount: "1.00000000", - script_pubkey: "001400aea9a2e5f0f876a588df5546e8742d1d87008f", - }; - let input_0 = TvInput { - txid: "75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858", - index: 0, - }; - let input_1 = TvInput { - txid: "1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83", - index: 1, - }; - struct TvOutput { - amount: &'static str, - script_pubkey: &'static str, - } - struct TvInput { - txid: &'static str, - index: u32, - } - - Transaction { - version: 2, - lock_time: 0, - input: vec![ - TxIn { - previous_output: OutPoint { - txid: input_0.txid.parse().expect("failed to parse txid"), - vout: input_0.index, - }, - script_sig: ScriptBuf::new(), - sequence: u32::MAX, // Disable nSequence. - witness: Witness::default(), - }, - TxIn { - previous_output: OutPoint { - txid: input_1.txid.parse().expect("failed to parse txid"), - vout: input_1.index, - }, - script_sig: ScriptBuf::new(), - sequence: u32::MAX, - witness: Witness::default(), - }, - ], - output: vec![ - TxOut { - value: Amount::from_str_in(output_0.amount, Denomination::Dash) - .expect("failed to parse amount") - .to_sat(), - script_pubkey: ScriptBuf::from_hex(output_0.script_pubkey) - .expect("failed to parse script"), - }, - TxOut { - value: Amount::from_str_in(output_1.amount, Denomination::Dash) - .expect("failed to parse amount") - .to_sat(), - script_pubkey: ScriptBuf::from_hex(output_1.script_pubkey) - .expect("failed to parse script"), - }, - ], - special_transaction_payload: None, - } -} - -/// Creates the initial PSBT, called by the Creator. Verifies against BIP 174 test vector. -fn create_psbt(tx: Transaction) -> Psbt { - // String from BIP 174 test vector. - let expected_psbt_hex = include_str!("data/create_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - - let psbt = Psbt::from_unsigned_tx(tx).unwrap(); - - assert_eq!(psbt, expected_psbt); - psbt -} - -/// Updates `psbt` according to the BIP, returns the newly updated PSBT. Verifies against BIP 174 test vector. -fn update_psbt(mut psbt: Psbt, fingerprint: Fingerprint) -> Psbt { - // Strings from BIP 174 test vector. - let previous_tx_0 = include_str!("data/previous_tx_0_hex"); - let previous_tx_1 = include_str!("data/previous_tx_1_hex"); - - let redeem_script_0 = "5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae"; - let redeem_script_1 = "00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903"; - let witness_script = "522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae"; - - // Public key and its derivation path (these are the child pubkeys for our `ExtendedPrivKey`, - // can be verified by deriving the key using this derivation path). - let pk_path = vec![ - ("0353f34734bf126e6be1a52dce8a79a6530d5299e1cc4027f5bb96207da3085e94", "m/0h/0h/0h"), - ("0220bb0ddea616ff71ea6a85945b830cc4359d4178c90420dc4d6d80d25a4d9159", "m/0h/0h/1h"), - ("03a696181a5adfab2af3267ea0617493d0f677d75404e879360fd0246de3423df4", "m/0h/0h/2h"), - ("02e2cc9898cd2a31f923dae903b1da8df9ea468c75358188fa510b0e017ee41918", "m/0h/0h/3h"), - ("02a262d32e02856d69bb791042b3773b6fca08d62bcf87be9c5fa9e2b8d3adbb75", "m/0h/0h/4h"), - ("02c3c127892d7104e5f2f8c22612e0f81036ac64a7ac01d9ae0bc985337d4dd46e", "m/0h/0h/5h"), - ]; - - let expected_psbt_hex = include_str!("data/update_1_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - - let mut input_0 = psbt.inputs[0].clone(); - - let v = Vec::from_hex(previous_tx_1).unwrap(); - let tx: Transaction = deserialize(&v).unwrap(); - input_0.non_witness_utxo = Some(tx); - input_0.redeem_script = Some(hex_script!(redeem_script_0)); - input_0.bip32_derivation = bip32_derivation(fingerprint, &pk_path, vec![0, 1]); - - let mut input_1 = psbt.inputs[1].clone(); - - let v = Vec::from_hex(previous_tx_0).unwrap(); - let tx: Transaction = deserialize(&v).unwrap(); - input_1.witness_utxo = Some(tx.output[1].clone()); - - input_1.redeem_script = Some(hex_script!(redeem_script_1)); - input_1.witness_script = Some(hex_script!(witness_script)); - input_1.bip32_derivation = bip32_derivation(fingerprint, &pk_path, vec![2, 3]); - - psbt.inputs = vec![input_0, input_1]; - - let mut output_0 = psbt.outputs[0].clone(); - output_0.bip32_derivation = bip32_derivation(fingerprint, &pk_path, vec![4]); - - let mut output_1 = psbt.outputs[1].clone(); - output_1.bip32_derivation = bip32_derivation(fingerprint, &pk_path, vec![5]); - - psbt.outputs = vec![output_0, output_1]; - - assert_eq!(psbt, expected_psbt); - psbt -} - -/// `pk_path` holds tuples of `(public_key, derivation_path)`. `indices` is used to access the -/// `pk_path` vector. `fingerprint` is from the parent extended public key. -fn bip32_derivation( - fingerprint: Fingerprint, - pk_path: &[(&str, &str)], - indices: Vec, -) -> BTreeMap { - let mut tree = BTreeMap::new(); - for i in indices { - let pk = pk_path[i].0; - let path = pk_path[i].1; - - let pk = PublicKey::from_str(pk).unwrap(); - use key_wallet::bip32::IntoDerivationPath; - let path = path.into_derivation_path().unwrap(); - - tree.insert(pk.inner, (fingerprint, path)); - } - tree -} - -/// Does the second update according to the BIP, returns the newly updated PSBT. Verifies against BIP 174 test vector. -fn update_psbt_with_sighash_all(mut psbt: Psbt) -> Psbt { - let expected_psbt_hex = include_str!("data/update_2_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - let ty = PsbtSighashType::from_str("SIGHASH_ALL").unwrap(); - - let mut input_0 = psbt.inputs[0].clone(); - input_0.sighash_type = Some(ty); - let mut input_1 = psbt.inputs[1].clone(); - input_1.sighash_type = Some(ty); - - psbt.inputs = vec![input_0, input_1]; - - assert_eq!(psbt, expected_psbt); - psbt -} - -/// Verifies the keys in the test vector are valid for the extended private key and derivation path. -fn parse_and_verify_keys( - ext_priv: &ExtendedPrivKey, - sk_path: &[(&str, &str)], -) -> BTreeMap { - let mut key_map = BTreeMap::new(); - for (secret_key, derivation_path) in sk_path.iter() { - let wif_priv = PrivateKey::from_wif(secret_key).expect("failed to parse key"); - - use key_wallet::bip32::IntoDerivationPath; - let path = - derivation_path.into_derivation_path().expect("failed to convert derivation path"); - let ext_derived = ext_priv.derive_priv(&path).expect("failed to derive ext priv key"); - let derived_priv = PrivateKey { - compressed: true, - network: ext_derived.network, - inner: ext_derived.private_key, - }; - assert_eq!(wif_priv, derived_priv); - let derived_pub = derived_priv.public_key(); - key_map.insert(derived_pub, derived_priv); - } - key_map -} - -/// Does the first signing according to the BIP, returns the signed PSBT. Verifies against BIP 174 test vector. -fn signer_one_sign(psbt: Psbt, key_map: BTreeMap) -> Psbt { - let expected_psbt_hex = include_str!("data/sign_1_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - - let psbt = sign(psbt, key_map); - - assert_eq!(psbt, expected_psbt); - psbt -} - -/// Does the second signing according to the BIP, returns the signed PSBT. Verifies against BIP 174 test vector. -fn signer_two_sign(psbt: Psbt, key_map: BTreeMap) -> Psbt { - let expected_psbt_hex = include_str!("data/sign_2_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - - let psbt = sign(psbt, key_map); - - assert_eq!(psbt, expected_psbt); - psbt -} - -/// Does the combine according to the BIP, returns the combined PSBT. Verifies against BIP 174 test vector. -fn combine(mut this: Psbt, that: Psbt) -> Psbt { - let expected_psbt_hex = include_str!("data/combine_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - - this.combine(that).expect("failed to combine PSBTs"); - - assert_eq!(this, expected_psbt); - this -} - -/// Does the finalize step according to the BIP, returns the combined PSBT. Verifies against BIP 174 -/// test vector. -fn finalize(psbt: Psbt) -> Psbt { - let expected_psbt_hex = include_str!("data/finalize_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - - let psbt = finalize_psbt(psbt); - - assert_eq!(psbt, expected_psbt); - psbt -} - -/// Does the transaction extractor step according to the BIP, returns the combined PSBT. Verifies -/// against BIP 174 test vector. -fn extract_transaction(psbt: Psbt) -> Transaction { - let expected_tx_hex = include_str!("data/extract_tx_hex"); - - let tx = psbt.extract_tx(); - - let got = serialize_hex(&tx); - assert_eq!(got, expected_tx_hex); - - tx -} - -/// Combines two PSBTs lexicographically according to the BIP. Verifies against BIP 174 test vector. -fn combine_lexicographically() { - let psbt_1_hex = include_str!("data/lex_psbt_1_hex"); - let psbt_2_hex = include_str!("data/lex_psbt_2_hex"); - - let expected_psbt_hex = include_str!("data/lex_combine_psbt_hex"); - let expected_psbt = hex_psbt!(expected_psbt_hex).unwrap(); - - let v = Vec::from_hex(psbt_1_hex).unwrap(); - let mut psbt_1 = Psbt::deserialize(&v).expect("failed to deserialize psbt 1"); - - let v = Vec::from_hex(psbt_2_hex).unwrap(); - let psbt_2 = Psbt::deserialize(&v).expect("failed to deserialize psbt 2"); - - psbt_1.combine(psbt_2).expect("failed to combine PSBTs"); - - assert_eq!(psbt_1, expected_psbt); -} - -/// Signs `psbt` with `keys` if required. -fn sign(mut psbt: Psbt, keys: BTreeMap) -> Psbt { - psbt.sign(&keys).unwrap(); - psbt -} - -/// Finalizes a PSBT accord to the Input Finalizer role described in BIP 174. -/// This is just a test. For a production-ready PSBT Finalizer, use [rust-miniscript](https://docs.rs/miniscript/latest/miniscript/psbt/trait.PsbtExt.html#tymethod.finalize) -fn finalize_psbt(mut psbt: Psbt) -> Psbt { - // Input 0: legacy UTXO - - let sigs: Vec<_> = psbt.inputs[0].partial_sigs.values().collect(); - let script_sig = script::Builder::new() - .push_opcode(OP_0) // OP_CHECKMULTISIG bug pops +1 value when evaluating so push OP_0. - .push_slice(sigs[0].serialize()) - .push_slice(sigs[1].serialize()) - .push_slice( - <&PushBytes>::try_from(psbt.inputs[0].redeem_script.as_ref().unwrap().as_bytes()) - .unwrap(), - ) - .into_script(); - - psbt.inputs[0].final_script_sig = Some(script_sig); - - psbt.inputs[0].partial_sigs = BTreeMap::new(); - psbt.inputs[0].sighash_type = None; - psbt.inputs[0].redeem_script = None; - psbt.inputs[0].bip32_derivation = BTreeMap::new(); - - // Input 1: SegWit UTXO - - let script_sig = script::Builder::new() - .push_slice( - <&PushBytes>::try_from(psbt.inputs[1].redeem_script.as_ref().unwrap().as_bytes()) - .unwrap(), - ) - .into_script(); - - psbt.inputs[1].final_script_sig = Some(script_sig); - - let script_witness = { - let sigs: Vec<_> = psbt.inputs[1].partial_sigs.values().collect(); - let mut script_witness = Witness::new(); - script_witness.push([]); // Push 0x00 to the stack. - script_witness.push(sigs[1].to_vec()); - script_witness.push(sigs[0].to_vec()); - script_witness.push(psbt.inputs[1].witness_script.clone().unwrap().as_bytes()); - - script_witness - }; - - psbt.inputs[1].final_script_witness = Some(script_witness); - - psbt.inputs[1].partial_sigs = BTreeMap::new(); - psbt.inputs[1].sighash_type = None; - psbt.inputs[1].redeem_script = None; - psbt.inputs[1].witness_script = None; - psbt.inputs[1].bip32_derivation = BTreeMap::new(); - - psbt -}