mirror of
https://github.com/bitcoin/bips.git
synced 2026-04-06 16:16:45 +00:00
BIP360: Pay to Merkle Root (P2MR) (#1670)
Review comments and assistance by: Armin Sabouri <armins88@gmail.com> D++ <82842780+dplusplus1024@users.noreply.github.com> Jameson Lopp <jameson.lopp@gmail.com> jbride <jbride2001@yahoo.com> Joey Yandle <xoloki@gmail.com> Jon Atack <jon@atack.com> Jonas Nick <jonasd.nick@gmail.com> Kyle Crews <kylecrews@Kyles-Mac-Studio.local> Mark "Murch" Erhardt <murch@murch.one> notmike-5 <notmike-5@users.noreply.github.com> Vojtěch Strnad <43024885+vostrnad@users.noreply.github.com> Co-authored-by: Ethan Heilman <ethan.r.heilman@gmail.com> Co-authored-by: Isabel Foxen Duke <110147802+Isabelfoxenduke@users.noreply.github.com>
This commit is contained in:
129
bip-0360/ref-impl/rust/examples/p2tr_spend.rs
Normal file
129
bip-0360/ref-impl/rust/examples/p2tr_spend.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use p2mr_ref::{ pay_to_p2wpkh_tx , verify_schnorr_signature_via_bytes};
|
||||
|
||||
use p2mr_ref::data_structures::{SpendDetails, LeafScriptType};
|
||||
use std::env;
|
||||
use log::{info, error};
|
||||
|
||||
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
|
||||
fn main() -> SpendDetails {
|
||||
|
||||
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
|
||||
|
||||
// FUNDING_TX_ID environment variable is required
|
||||
let funding_tx_id: String = env::var("FUNDING_TX_ID")
|
||||
.unwrap_or_else(|_| {
|
||||
error!("FUNDING_TX_ID environment variable is required but not set");
|
||||
std::process::exit(1);
|
||||
});
|
||||
let funding_tx_id_bytes: Vec<u8> = hex::decode(funding_tx_id.clone()).unwrap();
|
||||
|
||||
// FUNDING_UTXO_AMOUNT_SATS environment variable is required
|
||||
let funding_utxo_amount_sats: u64 = env::var("FUNDING_UTXO_AMOUNT_SATS")
|
||||
.unwrap_or_else(|_| {
|
||||
error!("FUNDING_UTXO_AMOUNT_SATS environment variable is required but not set");
|
||||
std::process::exit(1);
|
||||
})
|
||||
.parse::<u64>()
|
||||
.unwrap_or_else(|_| {
|
||||
error!("FUNDING_UTXO_AMOUNT_SATS must be a valid u64 integer");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
// The input index of the funding tx
|
||||
// Allow override via FUNDING_UTXO_INDEX environment variable
|
||||
let funding_utxo_index: u32 = env::var("FUNDING_UTXO_INDEX")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u32>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
info!("Funding tx id: {}, utxo index: {}", funding_tx_id, funding_utxo_index);
|
||||
|
||||
// FUNDING_SCRIPT_PUBKEY environment variable is required
|
||||
let funding_script_pubkey_bytes: Vec<u8> = env::var("FUNDING_SCRIPT_PUBKEY")
|
||||
.map(|s| hex::decode(s).unwrap())
|
||||
.unwrap_or_else(|_| {
|
||||
error!("FUNDING_SCRIPT_PUBKEY environment variable is required but not set");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let control_block_bytes: Vec<u8> = env::var("CONTROL_BLOCK_HEX")
|
||||
.map(|s| hex::decode(s).unwrap())
|
||||
.unwrap_or_else(|_| {
|
||||
error!("CONTROL_BLOCK_HEX environment variable is required but not set");
|
||||
std::process::exit(1);
|
||||
});
|
||||
info!("P2TR control block size: {}", control_block_bytes.len());
|
||||
|
||||
// P2TR only supports Schnorr signatures, so we only need one private key
|
||||
let leaf_script_priv_key_bytes: Vec<u8> = {
|
||||
let priv_keys_hex_array = env::var("LEAF_SCRIPT_PRIV_KEYS_HEX")
|
||||
.unwrap_or_else(|_| {
|
||||
error!("LEAF_SCRIPT_PRIV_KEYS_HEX environment variable is required but not set");
|
||||
std::process::exit(1);
|
||||
});
|
||||
// Parse JSON array and extract the first (and only) hex string
|
||||
let priv_keys_hex: String = serde_json::from_str::<Vec<String>>(&priv_keys_hex_array)
|
||||
.unwrap_or_else(|_| {
|
||||
error!("Failed to parse LEAF_SCRIPT_PRIV_KEYS_HEX as JSON array");
|
||||
std::process::exit(1);
|
||||
})
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| {
|
||||
error!("LEAF_SCRIPT_PRIV_KEYS_HEX array is empty");
|
||||
std::process::exit(1);
|
||||
});
|
||||
hex::decode(priv_keys_hex).unwrap()
|
||||
};
|
||||
|
||||
// Validate that the private key is 32 bytes (Schnorr key size)
|
||||
if leaf_script_priv_key_bytes.len() != 32 {
|
||||
error!("P2TR private key must be 32 bytes (Schnorr), got {}", leaf_script_priv_key_bytes.len());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Convert to Vec<Vec<u8>> format expected by the function
|
||||
let leaf_script_priv_keys_bytes: Vec<Vec<u8>> = vec![leaf_script_priv_key_bytes];
|
||||
|
||||
// ie: OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
|
||||
let leaf_script_bytes: Vec<u8> = env::var("LEAF_SCRIPT_HEX")
|
||||
.map(|s| hex::decode(s).unwrap())
|
||||
.unwrap_or_else(|_| {
|
||||
error!("LEAF_SCRIPT_HEX environment variable is required but not set");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
// https://learnmeabitcoin.com/explorer/tx/797505b104b5fb840931c115ea35d445eb1f64c9279bf23aa5bb4c3d779da0c2#outputs
|
||||
let spend_output_pubkey_hash_bytes: Vec<u8> = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
|
||||
|
||||
// OUTPUT_AMOUNT_SATS env var is optional. Default is FUNDING_UTXO_AMOUNT_SATS - 5000 sats
|
||||
let spend_output_amount_sats: u64 = env::var("OUTPUT_AMOUNT_SATS")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(funding_utxo_amount_sats.saturating_sub(5000));
|
||||
|
||||
|
||||
let result: SpendDetails = pay_to_p2wpkh_tx(
|
||||
funding_tx_id_bytes,
|
||||
funding_utxo_index,
|
||||
funding_utxo_amount_sats,
|
||||
funding_script_pubkey_bytes,
|
||||
control_block_bytes,
|
||||
leaf_script_bytes.clone(),
|
||||
leaf_script_priv_keys_bytes, // Now passing Vec<Vec<u8>> format
|
||||
spend_output_pubkey_hash_bytes.clone(),
|
||||
spend_output_amount_sats,
|
||||
LeafScriptType::SchnorrOnly
|
||||
);
|
||||
|
||||
// Remove first and last byte from leaf_script_bytes to get tapleaf_pubkey_bytes
|
||||
let tapleaf_pubkey_bytes: Vec<u8> = leaf_script_bytes[1..leaf_script_bytes.len()-1].to_vec();
|
||||
|
||||
let is_valid: bool = verify_schnorr_signature_via_bytes(
|
||||
&result.sig_bytes,
|
||||
&result.sighash,
|
||||
&tapleaf_pubkey_bytes);
|
||||
info!("is_valid: {}", is_valid);
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user