Support signing taproot in psbt
This commit is contained in:
parent
975ada3570
commit
41e8fb0863
|
@ -133,7 +133,7 @@ impl ColdStorage {
|
|||
fn master_fingerprint(&self) -> Fingerprint { self.master_xpub.fingerprint() }
|
||||
|
||||
/// Signs `psbt` with this signer.
|
||||
fn sign_psbt<C: Signing>(&self, secp: &Secp256k1<C>, mut psbt: Psbt) -> Result<Psbt> {
|
||||
fn sign_psbt<C: Signing + Verification>(&self, secp: &Secp256k1<C>, mut psbt: Psbt) -> Result<Psbt> {
|
||||
match psbt.sign(&self.master_xpriv, secp) {
|
||||
Ok(keys) => assert_eq!(keys.len(), 1),
|
||||
Err((_, e)) => {
|
||||
|
|
|
@ -20,15 +20,17 @@ use std::collections::{HashMap, HashSet};
|
|||
|
||||
use hashes::Hash;
|
||||
use internals::write_err;
|
||||
use secp256k1::{Message, Secp256k1, Signing};
|
||||
use secp256k1::{Keypair, Message, Secp256k1, Signing, Verification};
|
||||
|
||||
use crate::bip32::{self, KeySource, Xpriv, Xpub};
|
||||
use crate::blockdata::transaction::{self, Transaction, TxOut};
|
||||
use crate::crypto::ecdsa;
|
||||
use crate::crypto::{ecdsa, taproot};
|
||||
use crate::crypto::key::{PrivateKey, PublicKey};
|
||||
use crate::prelude::*;
|
||||
use crate::sighash::{self, EcdsaSighashType, SighashCache};
|
||||
use crate::{Amount, FeeRate};
|
||||
use crate::sighash::{self, EcdsaSighashType, Prevouts, SighashCache};
|
||||
use crate::{Amount, FeeRate, TapSighashType};
|
||||
use crate::key::TapTweak;
|
||||
use crate::TapLeafHash;
|
||||
|
||||
#[rustfmt::skip] // Keep public re-exports separate.
|
||||
#[doc(inline)]
|
||||
|
@ -303,7 +305,7 @@ impl Psbt {
|
|||
secp: &Secp256k1<C>,
|
||||
) -> Result<SigningKeys, (SigningKeys, SigningErrors)>
|
||||
where
|
||||
C: Signing,
|
||||
C: Signing + Verification,
|
||||
K: GetKey,
|
||||
{
|
||||
let tx = self.unsigned_tx.clone(); // clone because we need to mutably borrow when signing.
|
||||
|
@ -313,16 +315,31 @@ impl Psbt {
|
|||
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, secp) {
|
||||
Ok(v) => {
|
||||
used.insert(i, v);
|
||||
}
|
||||
Err(e) => {
|
||||
errors.insert(i, e);
|
||||
match self.signing_algorithm(i) {
|
||||
Ok(SigningAlgorithm::Ecdsa) => {
|
||||
match self.bip32_sign_ecdsa(k, i, &mut cache, secp) {
|
||||
Ok(v) => {
|
||||
used.insert(i, v);
|
||||
}
|
||||
Err(e) => {
|
||||
errors.insert(i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(SigningAlgorithm::Schnorr) => {
|
||||
match self.bip32_sign_schnorr(k, i, &mut cache, secp) {
|
||||
Ok(v) => {
|
||||
used.insert(i, v);
|
||||
}
|
||||
Err(e) => {
|
||||
errors.insert(i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
errors.insert(i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if errors.is_empty() {
|
||||
Ok(used)
|
||||
|
@ -385,6 +402,102 @@ impl Psbt {
|
|||
Ok(used)
|
||||
}
|
||||
|
||||
/// Attempts to create all signatures required by this PSBT's `tap_key_origins` field, adding
|
||||
/// them to `tap_key_sig` or `tap_script_sigs`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - Ok: A list of the public keys used in signing.
|
||||
/// - Err: Error encountered trying to calculate the sighash AND we had the signing key. Also panics
|
||||
/// if input_index is out of bounds.
|
||||
fn bip32_sign_schnorr<C, K, T>(
|
||||
&mut self,
|
||||
k: &K,
|
||||
input_index: usize,
|
||||
cache: &mut SighashCache<T>,
|
||||
secp: &Secp256k1<C>,
|
||||
) -> Result<Vec<PublicKey>, SignError>
|
||||
where
|
||||
C: Signing + Verification,
|
||||
T: Borrow<Transaction>,
|
||||
K: GetKey,
|
||||
{
|
||||
|
||||
let mut input = self.inputs[input_index].clone();
|
||||
|
||||
let mut used = vec![]; // List of pubkeys used to sign the input.
|
||||
|
||||
for (&xonly, (leaf_hashes, key_source)) in input.tap_key_origins.iter() {
|
||||
let sk = if let Ok(Some(secret_key)) = k.get_key(KeyRequest::Bip32(key_source.clone()), secp) {
|
||||
secret_key
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Considering the responsibility of the PSBT's finalizer to extract valid signatures,
|
||||
// the goal of this algorithm is to provide signatures to the best of our ability:
|
||||
// 1) If the conditions for key path spend are met, proceed to provide the signature for key path spend
|
||||
// 2) If the conditions for script path spend are met, proceed to provide the signature for script path spend
|
||||
|
||||
// key path spend
|
||||
if let Some(internal_key) = input.tap_internal_key {
|
||||
|
||||
// BIP 371: The internal key does not have leaf hashes, so can be indicated with a hashes len of 0.
|
||||
|
||||
// Based on input.tap_internal_key.is_some() alone, it is not sufficient to determine whether it is a key path spend.
|
||||
// According to BIP 371, we also need to consider the condition leaf_hashes.is_empty() for a more accurate determination.
|
||||
if internal_key == xonly && leaf_hashes.is_empty() && input.tap_key_sig.is_none() {
|
||||
let (msg, sighash_type) = self.sighash_taproot(input_index, cache, None)?;
|
||||
let key_pair = Keypair::from_secret_key(secp, &sk.inner)
|
||||
.tap_tweak(secp, input.tap_merkle_root)
|
||||
.to_inner();
|
||||
|
||||
#[cfg(feature = "rand-std")]
|
||||
let signature = secp.sign_schnorr(&msg, &key_pair);
|
||||
#[cfg(not(feature = "rand-std"))]
|
||||
let signature = secp.sign_schnorr_no_aux_rand(&msg, &key_pair);
|
||||
|
||||
let signature = taproot::Signature { signature, sighash_type };
|
||||
input.tap_key_sig = Some(signature);
|
||||
|
||||
used.push(sk.public_key(secp));
|
||||
}
|
||||
}
|
||||
|
||||
// script path spend
|
||||
if let Some((leaf_hashes, _)) = input.tap_key_origins.get(&xonly) {
|
||||
let leaf_hashes = leaf_hashes
|
||||
.iter()
|
||||
.filter(|lh| !input.tap_script_sigs.contains_key(&(xonly, **lh)))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !leaf_hashes.is_empty() {
|
||||
|
||||
let key_pair = Keypair::from_secret_key(secp, &sk.inner);
|
||||
|
||||
for lh in leaf_hashes {
|
||||
let (msg, sighash_type) = self.sighash_taproot(input_index, cache, Some(lh))?;
|
||||
|
||||
#[cfg(feature = "rand-std")]
|
||||
let signature = secp.sign_schnorr(&msg, &key_pair);
|
||||
#[cfg(not(feature = "rand-std"))]
|
||||
let signature = secp.sign_schnorr_no_aux_rand(&msg, &key_pair);
|
||||
|
||||
let signature = taproot::Signature { signature, sighash_type };
|
||||
input.tap_script_sigs.insert((xonly, lh), signature);
|
||||
}
|
||||
|
||||
used.push(sk.public_key(secp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.inputs[input_index] = input;
|
||||
|
||||
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
|
||||
|
@ -446,6 +559,64 @@ impl Psbt {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the sighash message to sign an SCHNORR input along with the sighash type.
|
||||
///
|
||||
/// Uses the [`TapSighashType`] from this input if one is specified. If no sighash type is
|
||||
/// specified uses [`TapSighashType::Default`].
|
||||
fn sighash_taproot<T: Borrow<Transaction>>(
|
||||
&self,
|
||||
input_index: usize,
|
||||
cache: &mut SighashCache<T>,
|
||||
leaf_hash: Option<TapLeafHash>
|
||||
) -> Result<(Message, TapSighashType), SignError> {
|
||||
use OutputType::*;
|
||||
|
||||
if self.signing_algorithm(input_index)? != SigningAlgorithm::Schnorr {
|
||||
return Err(SignError::WrongSigningAlgorithm);
|
||||
}
|
||||
|
||||
let input = self.checked_input(input_index)?;
|
||||
|
||||
match self.output_type(input_index)? {
|
||||
Tr => {
|
||||
let hash_ty = input
|
||||
.sighash_type
|
||||
.unwrap_or_else(|| TapSighashType::Default.into())
|
||||
.taproot_hash_ty()
|
||||
.map_err(|_| SignError::InvalidSighashType)?;
|
||||
|
||||
let spend_utxos = (0..self.inputs.len())
|
||||
.map(|i| self.spend_utxo(i).ok())
|
||||
.collect::<Vec<_>>();
|
||||
let all_spend_utxos;
|
||||
|
||||
let is_anyone_can_pay = PsbtSighashType::from(hash_ty).to_u32() & 0x80 != 0;
|
||||
|
||||
let prev_outs = if is_anyone_can_pay {
|
||||
Prevouts::One(
|
||||
input_index,
|
||||
spend_utxos[input_index].ok_or(SignError::MissingSpendUtxo)?,
|
||||
)
|
||||
} else if spend_utxos.iter().all(Option::is_some) {
|
||||
all_spend_utxos = spend_utxos.iter().filter_map(|x| *x).collect::<Vec<_>>();
|
||||
Prevouts::All(&all_spend_utxos)
|
||||
} else {
|
||||
return Err(SignError::MissingSpendUtxo);
|
||||
};
|
||||
|
||||
let sighash = if let Some(leaf_hash) = leaf_hash {
|
||||
cache.taproot_script_spend_signature_hash(input_index, &prev_outs, leaf_hash, hash_ty)?
|
||||
} else {
|
||||
cache.taproot_key_spend_signature_hash(input_index, &prev_outs, hash_ty)?
|
||||
};
|
||||
Ok((Message::from(sighash), hash_ty))
|
||||
}
|
||||
_ => {
|
||||
Err(SignError::Unsupported)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the spending utxo for this PSBT's input at `input_index`.
|
||||
pub fn spend_utxo(&self, input_index: usize) -> Result<&TxOut, SignError> {
|
||||
let input = self.checked_input(input_index)?;
|
||||
|
@ -774,6 +945,8 @@ pub enum SignError {
|
|||
SegwitV0Sighash(transaction::InputsIndexError),
|
||||
/// Sighash computation error (p2wpkh input).
|
||||
P2wpkhSighash(sighash::P2wpkhError),
|
||||
/// Sighash computation error (taproot input).
|
||||
TaprootError(sighash::TaprootError),
|
||||
/// Unable to determine the output type.
|
||||
UnknownOutputType,
|
||||
/// Unable to find key.
|
||||
|
@ -800,6 +973,7 @@ impl fmt::Display for SignError {
|
|||
NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"),
|
||||
SegwitV0Sighash(ref e) => write_err!(f, "segwit v0 sighash"; e),
|
||||
P2wpkhSighash(ref e) => write_err!(f, "p2wpkh sighash"; e),
|
||||
TaprootError(ref e) => write_err!(f, "taproot sighash"; e),
|
||||
UnknownOutputType => write!(f, "unable to determine the output type"),
|
||||
KeyNotFound => write!(f, "unable to find key"),
|
||||
WrongSigningAlgorithm =>
|
||||
|
@ -817,6 +991,7 @@ impl std::error::Error for SignError {
|
|||
match *self {
|
||||
SegwitV0Sighash(ref e) => Some(e),
|
||||
P2wpkhSighash(ref e) => Some(e),
|
||||
TaprootError(ref e) => Some(e),
|
||||
IndexOutOfBounds(ref e) => Some(e),
|
||||
InvalidSighashType
|
||||
| MissingInputUtxo
|
||||
|
@ -842,6 +1017,10 @@ impl From<IndexOutOfBoundsError> for SignError {
|
|||
fn from(e: IndexOutOfBoundsError) -> Self { SignError::IndexOutOfBounds(e) }
|
||||
}
|
||||
|
||||
impl From<sighash::TaprootError> for SignError {
|
||||
fn from(e: sighash::TaprootError) -> Self { SignError::TaprootError(e) }
|
||||
}
|
||||
|
||||
/// This error is returned when extracting a [`Transaction`] from a [`Psbt`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
|
@ -2091,9 +2270,9 @@ mod tests {
|
|||
};
|
||||
psbt.inputs[1].witness_utxo = Some(txout_unknown_future);
|
||||
|
||||
let sigs = psbt.sign(&key_map, &secp).unwrap();
|
||||
let (signing_keys, _) = psbt.sign(&key_map, &secp).unwrap_err();
|
||||
|
||||
assert!(sigs.len() == 1);
|
||||
assert!(sigs[&0] == vec![pk]);
|
||||
assert_eq!(signing_keys.len(), 1);
|
||||
assert_eq!(signing_keys[&0], vec![pk]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,316 @@
|
|||
#![cfg(not(feature = "rand-std"))]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
use secp256k1::{Keypair, Signing, Secp256k1, XOnlyPublicKey};
|
||||
use bitcoin::{absolute, Address, Network, OutPoint, PrivateKey, Psbt, script, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness};
|
||||
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 units::Amount;
|
||||
|
||||
|
||||
#[test]
|
||||
fn psbt_sign_taproot() {
|
||||
|
||||
struct Keystore {
|
||||
sk: PrivateKey,
|
||||
mfp: Fingerprint,
|
||||
}
|
||||
|
||||
impl GetKey for Keystore {
|
||||
type Error = SignError;
|
||||
fn get_key<C: Signing>(&self, key_request: KeyRequest, _secp: &Secp256k1<C>) -> Result<Option<PrivateKey>, 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", "86'/1'/0'/0/0"),
|
||||
("1ede31b0e7e47c2afc65ffd158b1b1b9d3b752bba8fd117dc8b9e944a390e8d9", "86'/1'/0'/0/1"),
|
||||
("1fb777f1a6fb9b76724551f8bc8ad91b77f33b8c456d65d746035391d724922a", "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::<secp256k1::All>, 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::<secp256k1::All>, 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 = "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
|
||||
}
|
Loading…
Reference in New Issue