#![cfg(not(feature = "rand-std"))] use std::collections::BTreeMap; use std::str::FromStr; use bitcoin::bip32::{DerivationPath, Fingerprint}; use bitcoin::consensus::encode::serialize_hex; use bitcoin::opcodes::all::OP_CHECKSIG; use bitcoin::psbt::{GetKey, Input, KeyRequest, PsbtSighashType, SignError}; use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo}; use bitcoin::transaction::Version; use bitcoin::{ absolute, script, Address, Network, OutPoint, PrivateKey, Psbt, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, }; use secp256k1::{Keypair, Secp256k1, Signing, XOnlyPublicKey}; use units::Amount; #[test] fn psbt_sign_taproot() { struct Keystore { sk: PrivateKey, mfp: Fingerprint, } impl GetKey for Keystore { type Error = SignError; fn get_key( &self, key_request: KeyRequest, _secp: &Secp256k1, ) -> Result, Self::Error> { match key_request { KeyRequest::Bip32((mfp, _)) => if mfp == self.mfp { Ok(Some(self.sk)) } else { Err(SignError::KeyNotFound) }, _ => Err(SignError::KeyNotFound), } } } let secp = &Secp256k1::new(); let sk_path = [ ("dff1c8c2c016a572914b4c5adb8791d62b4768ae9d0a61be8ab94cf5038d7d90", "m/86'/1'/0'/0/0"), ("1ede31b0e7e47c2afc65ffd158b1b1b9d3b752bba8fd117dc8b9e944a390e8d9", "m/86'/1'/0'/0/1"), ("1fb777f1a6fb9b76724551f8bc8ad91b77f33b8c456d65d746035391d724922a", "m/86'/1'/0'/0/2"), ]; let mfp = "73c5da0a"; // // Step 0: Create P2TR address. // // Create three basic scripts to test script path spend. let script1 = create_basic_single_sig_script(secp, sk_path[0].0); // m/86'/1'/0'/0/0 let script2 = create_basic_single_sig_script(secp, sk_path[1].0); // m/86'/1'/0'/0/1 let script3 = create_basic_single_sig_script(secp, sk_path[2].0); // m/86'/1'/0'/0/2 // Just use one of the secret keys for the key path spend. let kp = Keypair::from_seckey_str(secp, sk_path[2].0).expect("failed to create keypair"); let internal_key = kp.x_only_public_key().0; // Ignore the parity. let tree = create_taproot_tree(secp, script1.clone(), script2.clone(), script3.clone(), internal_key); let address = create_p2tr_address(tree.clone()); assert_eq!( "tb1pytee2mxz0f4fkrsqqws2lsgnkp8nrw2atjkjy2n9gahggsphr0gszaxxmv", address.to_string() ); // m/86'/1'/0'/0/7 let to_address = "tb1pyfv094rr0vk28lf8v9yx3veaacdzg26ztqk4ga84zucqqhafnn5q9my9rz"; let to_address = Address::from_str(to_address).unwrap().assume_checked(); // key path spend { // // Step 1: create psbt for key path spend. // let mut psbt_key_path_spend = create_psbt_for_taproot_key_path_spend( address.clone(), to_address.clone(), tree.clone(), ); // // Step 2: sign psbt. // let keystore = Keystore { mfp: Fingerprint::from_str(mfp).unwrap(), sk: PrivateKey::new(kp.secret_key(), Network::Testnet), }; let _ = psbt_key_path_spend.sign(&keystore, secp); let sig = "92864dc9e56b6260ecbd54ec16b94bb597a2e6be7cca0de89d75e17921e0e1528cba32dd04217175c237e1835b5db1c8b384401718514f9443dce933c6ba9c87"; assert_eq!(sig, psbt_key_path_spend.inputs[0].tap_key_sig.unwrap().signature.to_string()); // // Step 3: finalize psbt. // let final_psbt = finalize_psbt_for_key_path_spend(psbt_key_path_spend); let tx = final_psbt.extract_tx().unwrap(); let tx_id = "5306516f2032d9f34c9f2f6d2b1b8ad2486ef1ba196d8d8d780e59773e48ad6d"; assert_eq!(tx_id, tx.compute_txid().to_string()); let tx_bytes = "020000000001013aee4d6b51da574900e56d173041115bd1e1d01d4697a845784cf716a10c98060000000000ffffffff0100190000000000002251202258f2d4637b2ca3fd27614868b33dee1a242b42582d5474f51730005fa99ce8014092864dc9e56b6260ecbd54ec16b94bb597a2e6be7cca0de89d75e17921e0e1528cba32dd04217175c237e1835b5db1c8b384401718514f9443dce933c6ba9c8700000000"; let tx_hex = serialize_hex(&tx); assert_eq!(tx_bytes, tx_hex); } // script path spend { // use private key of path "m/86'/1'/0'/0/1" as signing key let kp = Keypair::from_seckey_str(secp, sk_path[1].0).expect("failed to create keypair"); let x_only_pubkey = kp.x_only_public_key().0; let signing_key_path = sk_path[1].1; let keystore = Keystore { mfp: Fingerprint::from_str(mfp).unwrap(), sk: PrivateKey::new(kp.secret_key(), Network::Testnet), }; // // Step 1: create psbt for script path spend. // let mut psbt_script_path_spend = create_psbt_for_taproot_script_path_spend( address.clone(), to_address.clone(), tree.clone(), x_only_pubkey, signing_key_path, script2.clone(), ); // // Step 2: sign psbt. // let _ = psbt_script_path_spend.sign(&keystore, secp); let sig = "9c1466e1631a58c55fcb8642ce5f7896314f4b565d92c5c80b17aa9abf56d22e0b5e5dcbcfe836bbd7d409491f58aa9e1f68a491ef8f05eef62fb50ffac85727"; assert_eq!( sig, psbt_script_path_spend.inputs[0] .tap_script_sigs .get(&(x_only_pubkey, script2.clone().tapscript_leaf_hash())) .unwrap() .signature .to_string() ); // // Step 3: finalize psbt. // let final_psbt = finalize_psbt_for_script_path_spend(psbt_script_path_spend); let tx = final_psbt.extract_tx().unwrap(); let tx_id = "a51f723beffc810248809355ba9c9e4b39c6e55c08429f0aeaa79b73f18bc2a6"; assert_eq!(tx_id, tx.compute_txid().to_string()); let tx_hex = serialize_hex(&tx); let tx_bytes = "0200000000010176a3c94a6b21d742e8ca192130ad10fdfc4c83510cb6baba8572a5fc70677c9d0000000000ffffffff0170170000000000002251202258f2d4637b2ca3fd27614868b33dee1a242b42582d5474f51730005fa99ce803419c1466e1631a58c55fcb8642ce5f7896314f4b565d92c5c80b17aa9abf56d22e0b5e5dcbcfe836bbd7d409491f58aa9e1f68a491ef8f05eef62fb50ffac857270122203058679f6d60b87ef921d98a2a9a1f1e0779dae27bedbd1cdb2f147a07835ac9ac61c1b68df382cad577d8304d5a8e640c3cb42d77c10016ab754caa4d6e68b6cb296d9b9d92a717ebeba858f75182936f0da5a7aecc434b0eebb2dc8a6af5409422ccf87f124e735a592a8ff390a68f6f05469ba8422e246dc78b0b57cd1576ffa98c00000000"; assert_eq!(tx_bytes, tx_hex); } } fn create_basic_single_sig_script(secp: &Secp256k1, sk: &str) -> ScriptBuf { let kp = Keypair::from_seckey_str(secp, sk).expect("failed to create keypair"); let x_only_pubkey = kp.x_only_public_key().0; script::Builder::new() .push_slice(x_only_pubkey.serialize()) .push_opcode(OP_CHECKSIG) .into_script() } fn create_taproot_tree( secp: &Secp256k1, script1: ScriptBuf, script2: ScriptBuf, script3: ScriptBuf, internal_key: XOnlyPublicKey, ) -> TaprootSpendInfo { let builder = TaprootBuilder::new(); let builder = builder.add_leaf(2, script1).unwrap(); let builder = builder.add_leaf(2, script2).unwrap(); let builder = builder.add_leaf(1, script3).unwrap(); builder.finalize(secp, internal_key).unwrap() } fn create_p2tr_address(tree: TaprootSpendInfo) -> Address { let output_key = tree.output_key(); Address::p2tr_tweaked(output_key, Network::Testnet) } fn create_psbt_for_taproot_key_path_spend( from_address: Address, to_address: Address, tree: TaprootSpendInfo, ) -> Psbt { let send_value = 6400; let out_puts = vec![TxOut { value: Amount::from_sat(send_value), script_pubkey: to_address.script_pubkey(), }]; let prev_tx_id = "06980ca116f74c7845a897461dd0e1d15b114130176de5004957da516b4dee3a"; let transaction = Transaction { version: Version(2), lock_time: absolute::LockTime::ZERO, input: vec![TxIn { previous_output: OutPoint { txid: prev_tx_id.parse().unwrap(), vout: 0 }, script_sig: ScriptBuf::new(), sequence: Sequence(0xFFFFFFFF), // Ignore nSequence. witness: Witness::default(), }], output: out_puts, }; let mut psbt = Psbt::from_unsigned_tx(transaction).unwrap(); let mfp = "73c5da0a"; let internal_key_path = "m/86'/1'/0'/0/2"; let mut origins = BTreeMap::new(); origins.insert( tree.internal_key(), ( vec![], ( Fingerprint::from_str(mfp).unwrap(), DerivationPath::from_str(internal_key_path).unwrap(), ), ), ); let utxo_value = 6588; let mut input = Input { witness_utxo: { let script_pubkey = from_address.script_pubkey(); Some(TxOut { value: Amount::from_sat(utxo_value), script_pubkey }) }, tap_key_origins: origins, ..Default::default() }; let ty = PsbtSighashType::from_str("SIGHASH_DEFAULT").unwrap(); input.sighash_type = Some(ty); input.tap_internal_key = Some(tree.internal_key()); input.tap_merkle_root = tree.merkle_root(); psbt.inputs = vec![input]; psbt } fn finalize_psbt_for_key_path_spend(mut psbt: Psbt) -> Psbt { 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); input.partial_sigs = BTreeMap::new(); input.sighash_type = None; input.redeem_script = None; input.witness_script = None; input.bip32_derivation = BTreeMap::new(); }); psbt } fn create_psbt_for_taproot_script_path_spend( from_address: Address, to_address: Address, tree: TaprootSpendInfo, x_only_pubkey_of_signing_key: XOnlyPublicKey, signing_key_path: &str, use_script: ScriptBuf, ) -> Psbt { let utxo_value = 6280; let send_value = 6000; let mfp = "73c5da0a"; let out_puts = vec![TxOut { value: Amount::from_sat(send_value), script_pubkey: to_address.script_pubkey(), }]; let prev_tx_id = "9d7c6770fca57285babab60c51834cfcfd10ad302119cae842d7216b4ac9a376"; let transaction = Transaction { version: Version(2), lock_time: absolute::LockTime::ZERO, input: vec![TxIn { previous_output: OutPoint { txid: prev_tx_id.parse().unwrap(), vout: 0 }, script_sig: ScriptBuf::new(), sequence: Sequence(0xFFFFFFFF), // Ignore nSequence. witness: Witness::default(), }], output: out_puts, }; let mut psbt = Psbt::from_unsigned_tx(transaction).unwrap(); let mut origins = BTreeMap::new(); origins.insert( x_only_pubkey_of_signing_key, ( vec![use_script.tapscript_leaf_hash()], ( Fingerprint::from_str(mfp).unwrap(), DerivationPath::from_str(signing_key_path).unwrap(), ), ), ); let mut tap_scripts = BTreeMap::new(); tap_scripts.insert( tree.control_block(&(use_script.clone(), LeafVersion::TapScript)).unwrap(), (use_script.clone(), LeafVersion::TapScript), ); let mut input = Input { witness_utxo: { let script_pubkey = from_address.script_pubkey(); Some(TxOut { value: Amount::from_sat(utxo_value), script_pubkey }) }, tap_key_origins: origins, tap_scripts, ..Default::default() }; let ty = PsbtSighashType::from_str("SIGHASH_ALL").unwrap(); input.sighash_type = Some(ty); input.tap_internal_key = Some(tree.internal_key()); input.tap_merkle_root = tree.merkle_root(); psbt.inputs = vec![input]; psbt } fn finalize_psbt_for_script_path_spend(mut psbt: Psbt) -> Psbt { psbt.inputs.iter_mut().for_each(|input| { let mut script_witness: Witness = Witness::new(); for (_, signature) in input.tap_script_sigs.iter() { 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); 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; }); psbt }