Skip to content

Instantly share code, notes, and snippets.

@RCasatta
Created October 27, 2021 12:52
Show Gist options
  • Save RCasatta/874f2a95ad502ad7c1fc3a60e9777947 to your computer and use it in GitHub Desktop.
Save RCasatta/874f2a95ad502ad7c1fc3a60e9777947 to your computer and use it in GitHub Desktop.
Some test code trying out taproot
#[cfg(test)]
mod tests {
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::schnorr::{KeyPair, PublicKey};
use bitcoin::{Script, Address, Network, Transaction, TxIn, OutPoint, TxOut};
use bitcoin::blockdata::{script, opcodes};
use bitcoin::util::taproot::{TaprootSpendInfo, LeafVersion};
use bitcoin::util::address::WitnessVersion;
use bitcoin::util::sighash::{SigHashCache, ScriptPath, SigHashType};
use bitcoin::util::sighash::Prevouts;
use bitcoin::secp256k1::{Secp256k1, Message, All};
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::consensus::{encode, deserialize, serialize};
use bitcoin::secp256k1::rand::thread_rng;
use bitcoin::util::taproot::TapTweakHash;
use bitcoin::util::psbt::serialize::Serialize;
use bitcoin::util::taproot::TapLeafHash;
struct Init {
secp: Secp256k1<All>,
alice: KeyPair,
alice_public: PublicKey,
bob: KeyPair,
bob_public: PublicKey,
charlie: KeyPair,
charlie_public: PublicKey,
}
impl Default for Init {
fn default() -> Self {
let secp = Secp256k1::new();
let alice_secret = Vec::from_hex("6c068c2094c3f0986741cddbaff96399e338b4120671c6640216d3e474487a19").unwrap();
let alice = KeyPair::from_seckey_slice(&secp, &alice_secret).unwrap();
let alice_public = PublicKey::from_keypair(&secp, &alice);
assert_eq!(format!("{:x}", alice_public), "72ad3fb702bf1a2111b09c2bf1e72f4f52d4ceb2acdcfcd0c63abf70a59c36d6");
let bob_secret = Vec::from_hex("5c068c2094c3f0986741cddbaff96399e338b4120671c6640216d3e474487a19").unwrap();
let bob = KeyPair::from_seckey_slice(&secp, &bob_secret).unwrap();
let bob_public = PublicKey::from_keypair(&secp, &bob);
assert_eq!(format!("{:x}", bob_public), "97142dd0eca06bdb03a65dfc7f4681fd07efb8a2becf9bfb1c930aaef0d6c53c");
let charlie_secret = Vec::from_hex("e108a1eab0a9966a0740cda37684bd67f8ca746e0a8e8ac5c52f4989b24926a7").unwrap();
let charlie = KeyPair::from_seckey_slice(&secp, &charlie_secret).unwrap();
let charlie_public = PublicKey::from_keypair(&secp, &charlie);
assert_eq!(format!("{:x}", charlie_public), "2dff7bbd4757a0511f4f8a30bf8b86717d0e45cd56b951b542bf0bf993ce302e");
Init {
secp,
alice,
alice_public,
bob,
bob_public,
charlie,
charlie_public,
}
}
}
fn taproot_construct_mine() -> TaprootConstructTest<'static> {
TaprootConstructTest {
secret_hex: "6c068c2094c3f0986741cddbaff96399e338b4120671c6640216d3e474487a19",
public_hex: "72ad3fb702bf1a2111b09c2bf1e72f4f52d4ceb2acdcfcd0c63abf70a59c36d6",
secret_hex_for_script: "5c068c2094c3f0986741cddbaff96399e338b4120671c6640216d3e474487a19",
public_hex_for_script: "97142dd0eca06bdb03a65dfc7f4681fd07efb8a2becf9bfb1c930aaef0d6c53c",
script_hex_with_version: Some("c0222097142dd0eca06bdb03a65dfc7f4681fd07efb8a2becf9bfb1c930aaef0d6c53cac"),
script_hash: "c703e70a9fb4115e3eb385d0ac80eeb8bee4ac2f711fa962655d91cf8e3ecfed",
tweak: "31bff2db66cc90df93b40e44b288a21909e4f27baf00e6f785e61dc8a1ee94b8",
public_tweaked: "6899428e9fa30ff2105263ba3f59c61c40d280e1e9699196328830c21d3f80d2",
script_pubkey: "51206899428e9fa30ff2105263ba3f59c61c40d280e1e9699196328830c21d3f80d2"
}
}
#[test]
fn taproot_address_keypath_only() {
// If the spending conditions do not require a script path, the output key should commit to
// an unspendable script path instead of having no script path. This can be achieved by
// computing the output key point as Q = P + int(hashTapTweak(bytes(P)))G.
// $ bitcoin-cli -signet deriveaddresses "tr(72ad3fb702bf1a2111b09c2bf1e72f4f52d4ceb2acdcfcd0c63abf70a59c36d6)#xn9s745x"
// [
// "tb1paykwrvp768pjj8nrcrkxcgh7y6cvm5xhse53yd6v6zumv2f6gkkqft0lpc"
// ]
let init = Init::default();
let hash = TapTweakHash::hash(&init.alice_public.serialize());
let mut output_key = init.alice_public.clone();
let _ = output_key.tweak_add_assign(&init.secp, &hash.into_inner()).unwrap();
let script = Script::new_witness_program(WitnessVersion::V1, &output_key.serialize());
let address = Address::from_script(&script, Network::Signet).unwrap();
assert_eq!(address.to_string(), "tb1paykwrvp768pjj8nrcrkxcgh7y6cvm5xhse53yd6v6zumv2f6gkkqft0lpc");
}
#[test]
fn taproot_address_with_script_path() {
let init = Init::default();
let merkle_script = script::Builder::new()
.push_slice(&init.bob_public.serialize()[..])
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script();
let script_odds = vec![
(1, merkle_script),
];
let tree_info = TaprootSpendInfo::new_huffman_tree(&init.secp, init.alice_public, script_odds.clone()).unwrap();
let script = Script::new_witness_program(WitnessVersion::V1, &tree_info.output_key().serialize());
assert_eq!(script.to_hex(), "51206899428e9fa30ff2105263ba3f59c61c40d280e1e9699196328830c21d3f80d2");
let address = Address::from_script(&script, Network::Signet).unwrap();
assert_eq!(address.to_string(),"tb1pdzv59r5l5v8lyyzjvwar7kwxr3qd9q8pa95er93j3qcvy8flsrfqmjqmwl");
let address = Address::from_script(&script, Network::Bitcoin).unwrap();
assert_eq!(address.to_string(),"bc1pdzv59r5l5v8lyyzjvwar7kwxr3qd9q8pa95er93j3qcvy8flsrfqv6k55s");
/*
$ bitcoin-cli -signet deriveaddresses "tr(72ad3fb702bf1a2111b09c2bf1e72f4f52d4ceb2acdcfcd0c63abf70a59c36d6,pk(97142dd0eca06bdb03a65dfc7f4681fd07efb8a2becf9bfb1c930aaef0d6c53c))#hrl4mupj"
[
"tb1pdzv59r5l5v8lyyzjvwar7kwxr3qd9q8pa95er93j3qcvy8flsrfqmjqmwl"
]
*/
}
#[test]
fn taproot_script_spend() {
let init = Init::default();
/*
inputs is received on tb1pdzv59r5l5v8lyyzjvwar7kwxr3qd9q8pa95er93j3qcvy8flsrfqmjqmwl on txid fd92b607c0784394541c5946f8e91bac58eabd92a92f875d361e842faaed7621
*/
let source_tx = "0200000000010160112538ec5f11907176ac8e94a4ec047fa8b21884d2c1814a9ea4458c60875e0000000000feffffff02683c0100000000002251206899428e9fa30ff2105263ba3f59c61c40d280e1e9699196328830c21d3f80d2a4208f000000000016001413c53720b09463a7b06e4cbbb8a29073d71964a30247304402205e34ec1b91dd90f029b054569916c822d99d540c56483529c4f398ce7a1d7a760220023f1a5825c40063444bd7ce6b6969682815e6a7349cf3c18d219f6948bdd212012103b4a87562945b0153843e9aefe53ce3ab2ee50a705260be9d99d088f46f8525065cec0000";
let source_tx : Transaction = deserialize(&Vec::from_hex(source_tx).unwrap()).unwrap();
assert_eq!(source_tx.txid().to_hex(), "fd92b607c0784394541c5946f8e91bac58eabd92a92f875d361e842faaed7621");
let mut spending_tx = Transaction {
version: source_tx.version,
lock_time: source_tx.lock_time,
input: vec![TxIn {
previous_output: OutPoint::new(source_tx.txid(), 0),
script_sig: Default::default(),
sequence: 0,
witness: vec![]
}],
output: vec![TxOut {
value: 1011,
script_pubkey: Script::from_hex("0014e57e98a796cfd38c4a1a49ba37213a0aa77a69f0").unwrap(),
}]
};
let merkle_script = script::Builder::new()
.push_slice(&init.bob_public.serialize()[..])
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script();
assert_eq!(34, merkle_script.serialize().len());
let script_odds = vec![(1, merkle_script.clone()), ];
let tree_info = TaprootSpendInfo::new_huffman_tree(&init.secp, init.alice_public, script_odds.clone()).unwrap();
let cb = tree_info.control_block(&(merkle_script.clone(), LeafVersion::default())).unwrap();
let prevouts = [source_tx.output[0].clone()];
let prevouts = Prevouts::All(&prevouts);
let mut cache = SigHashCache::new(&spending_tx);
let script_path = ScriptPath::with_defaults(&merkle_script);
let hash = cache.taproot_signature_hash(0, &prevouts, None, Some(script_path), SigHashType::Default).unwrap();
let mut rng = thread_rng();
let message = Message::from_slice(&hash.into_inner()[..]).unwrap();
let signature = init.secp.schnorrsig_sign_with_rng(&message, &init.bob, &mut rng);
assert!(init.secp.schnorrsig_verify(&signature, &message, &init.bob_public).is_ok());
// signature, script, control_block
let mut cb_serialized = cb.serialize();
assert_eq!(cb_serialized.len(), 33);
assert_eq!(cb_serialized.to_hex(), "c172ad3fb702bf1a2111b09c2bf1e72f4f52d4ceb2acdcfcd0c63abf70a59c36d6");
let signature_serialized = signature.as_ref().to_vec();
assert_eq!(signature_serialized.len(), 64);
let witness = vec![signature_serialized, merkle_script.serialize(), cb_serialized];
spending_tx.input[0].witness = witness;
assert_eq!(spending_tx.txid().to_hex(), "ed4f734eddd53970bd4bb143e8be11ae30a9af8b85e23444ee852aece0f21f42");
}
#[test]
fn taproot_script_spend_merkle_root() {
let init = Init::default();
/*
$ bitcoin-cli -signet deriveaddresses "tr(72ad3fb702bf1a2111b09c2bf1e72f4f52d4ceb2acdcfcd0c63abf70a59c36d6,{pk(97142dd0eca06bdb03a65dfc7f4681fd07efb8a2becf9bfb1c930aaef0d6c53c),pk(2dff7bbd4757a0511f4f8a30bf8b86717d0e45cd56b951b542bf0bf993ce302e)})#n9d9305r"
[
"tb1p8wuydhkdl79p323rfk7f2uhdfr8drtf6yteln6sqcf7yt6svzfgss7z60r"
]
*/
let script1 = script::Builder::new()
.push_slice(&init.bob_public.serialize()[..])
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script();
let script2 = script::Builder::new()
.push_slice(&init.charlie_public.serialize()[..])
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script();
let script_odds = vec![
(1, script1.clone()),
(1, script2.clone()),
];
let tree_info = TaprootSpendInfo::new_huffman_tree(&init.secp, init.alice_public, script_odds.clone()).unwrap();
let script = Script::new_witness_program(WitnessVersion::V1, &tree_info.output_key().serialize());
let address = Address::from_script(&script, Network::Signet).unwrap();
assert_eq!(address.to_string(),"tb1p8wuydhkdl79p323rfk7f2uhdfr8drtf6yteln6sqcf7yt6svzfgss7z60r");
/*
inputs is received on tb1p8wuydhkdl79p323rfk7f2uhdfr8drtf6yteln6sqcf7yt6svzfgss7z60r on txid ea5d1b146a4d25c2063e54a9a190fb7f9d905692ca0481e71f3939a024980144
*/
let source_tx = "020000000001012176edaa2f841e365d872fa992bdea58ac1be9f846591c54944378c007b692fd0100000000feffffff0238440100000000002251203bb846decdff8a18aa234dbc9572ed48ced1ad3a22f3f9ea00c27c45ea0c1251a5db8d00000000001600144069a2d6e858c86c23f065e4bdc1d9b849ebf603024730440220792ed741a46654264e395484de4e87dadd5706faf45d7aa233798c31373c514b02207ee518fb1b72d1d5d8d2b522f26f78d0d83232639099c0bf53c2d063dc5334a5012103ad3c33f17fc2e66096a7bca75db70af78cd89991ba4b353c46a9c3464370c90fe9ed0000";
let source_tx : Transaction = deserialize(&Vec::from_hex(source_tx).unwrap()).unwrap();
assert_eq!(source_tx.txid().to_hex(), "ea5d1b146a4d25c2063e54a9a190fb7f9d905692ca0481e71f3939a024980144");
let mut spending_tx = Transaction {
version: source_tx.version,
lock_time: source_tx.lock_time,
input: vec![TxIn {
previous_output: OutPoint::new(source_tx.txid(), 0),
script_sig: Default::default(),
sequence: 0,
witness: vec![]
}],
output: vec![TxOut {
value: 1011,
script_pubkey: Script::from_hex("0014e57e98a796cfd38c4a1a49ba37213a0aa77a69f0").unwrap(),
}]
};
let cb = tree_info.control_block(&(script2.clone(), LeafVersion::default())).unwrap(); // control block for charlie
let prevouts = [source_tx.output[0].clone()];
let prevouts = Prevouts::All(&prevouts);
let mut cache = SigHashCache::new(&spending_tx);
let script_path = ScriptPath::with_defaults(&script2);
let hash = cache.taproot_signature_hash(0, &prevouts, None, Some(script_path), SigHashType::Default).unwrap();
let mut rng = thread_rng();
let message = Message::from_slice(&hash.into_inner()[..]).unwrap();
let signature = init.secp.schnorrsig_sign_with_rng(&message, &init.charlie, &mut rng);
assert!(init.secp.schnorrsig_verify(&signature, &message, &init.charlie_public).is_ok());
// signature, script, control_block
let mut cb_serialized = cb.serialize();
assert_eq!(cb_serialized.len(), 65);
assert_eq!(cb_serialized.to_hex(), "c072ad3fb702bf1a2111b09c2bf1e72f4f52d4ceb2acdcfcd0c63abf70a59c36d6edcf3e8ecf915d6562a91f712face4beb8ee80acd085b33e5e11b49f0ae703c7");
let signature_serialized = signature.as_ref().to_vec();
assert_eq!(signature_serialized.len(), 64);
let witness = vec![signature_serialized, script2.serialize(), cb_serialized];
spending_tx.input[0].witness = witness;
assert_eq!(spending_tx.txid().to_hex(), "618803696055ce03d15602ca611e2b8089f8d1824bb310bf13aa2945a492cbef");
}
struct TaprootConstructTest<'s> {
secret_hex: &'s str,
public_hex: &'s str,
secret_hex_for_script: &'s str,
public_hex_for_script: &'s str,
script_hex_with_version: Option<&'s str>,
script_hash: &'s str,
tweak: &'s str,
public_tweaked: &'s str,
script_pubkey: &'s str,
}
fn taproot_construct_example() -> TaprootConstructTest<'static> {
// # hacked core taproot_construct() test to print out values
//
// secs[0]: e108a1eab0a9966a0740cda37684bd67f8ca746e0a8e8ac5c52f4989b24926a7
// pubs[0]: 2dff7bbd4757a0511f4f8a30bf8b86717d0e45cd56b951b542bf0bf993ce302e
// secs[1]: d4ab553b4bc4975af7cfdd5c9b0871387d72ff9961b0f26cbf4ef859c66a7eb2
// pubs[1]: 47cfb867b57faca6dd8ea005d25f59917810e0813a8a0c8a53e2b4cda3ffe932
// code_with_version: c0222047cfb867b57faca6dd8ea005d25f59917810e0813a8a0c8a53e2b4cda3ffe932ac
// h: 1a7c356074ec60691eccf65dc0a5837a83be5223a04cef449b4d81cb99f39e19
// tweak: b0b31c347918ab541b4d6748081abdc1ac87dcbfb1dfce03fcf35bfa04c23b3a
// tweaked: 9c5869b824ee1abed24e9477e21a7dc63c112b6525a9aab82bf100941e1eca03
// scripts: [('s0', CScript([x('47cfb867b57faca6dd8ea005d25f59917810e0813a8a0c8a53e2b4cda3ffe932'), OP_CHECKSIG]))]
// scriptPubKey: 51209c5869b824ee1abed24e9477e21a7dc63c112b6525a9aab82bf100941e1eca03
// negflag: 0
// leaves: {'s0': TaprootLeafInfo(script=CScript([x('47cfb867b57faca6dd8ea005d25f59917810e0813a8a0c8a53e2b4cda3ffe932'), OP_CHECKSIG]), version=192, merklebranch=b'')}
TaprootConstructTest {
secret_hex: "e108a1eab0a9966a0740cda37684bd67f8ca746e0a8e8ac5c52f4989b24926a7",
public_hex: "2dff7bbd4757a0511f4f8a30bf8b86717d0e45cd56b951b542bf0bf993ce302e",
secret_hex_for_script: "d4ab553b4bc4975af7cfdd5c9b0871387d72ff9961b0f26cbf4ef859c66a7eb2",
public_hex_for_script: "47cfb867b57faca6dd8ea005d25f59917810e0813a8a0c8a53e2b4cda3ffe932",
script_hex_with_version: Some("c0222047cfb867b57faca6dd8ea005d25f59917810e0813a8a0c8a53e2b4cda3ffe932ac"),
script_hash: "1a7c356074ec60691eccf65dc0a5837a83be5223a04cef449b4d81cb99f39e19",
tweak: "b0b31c347918ab541b4d6748081abdc1ac87dcbfb1dfce03fcf35bfa04c23b3a",
public_tweaked: "9c5869b824ee1abed24e9477e21a7dc63c112b6525a9aab82bf100941e1eca03",
script_pubkey: "51209c5869b824ee1abed24e9477e21a7dc63c112b6525a9aab82bf100941e1eca03"
}
}
#[test]
fn test_taproot_construct_address() {
let test = taproot_construct_example();
let tap = test_taproot_construct(&test);
let address = Address::from_script(&tap.script_pubkey, Network::Signet).unwrap();
assert_eq!(address.to_string(), "tb1pn3vxnwpyacdta5jwj3m7yxnacc7pz2m9yk564wpt7yqfg8s7egps6zs3jg");
// $ bitcoin-cli -signet sendtoaddress tb1pn3vxnwpyacdta5jwj3m7yxnacc7pz2m9yk564wpt7yqfg8s7egps6zs3jg 0.00001352
// 74145e40130c911956d3d6952d4cd224a21a9d7428355abdc4ab01728de4c584
let test = taproot_construct_mine();
let _tap = test_taproot_construct(&test);
}
struct TaprootConstruct {
pair: KeyPair,
pair_script: KeyPair,
inner_script: Script,
pubkey_in_script: PublicKey,
script_pubkey: Script,
public: PublicKey,
}
fn test_taproot_construct(test: &TaprootConstructTest) -> TaprootConstruct {
let secp = Secp256k1::new();
let secret = Vec::from_hex(&test.secret_hex).unwrap();
let pair = KeyPair::from_seckey_slice(&secp, &secret).unwrap();
let public = PublicKey::from_keypair(&secp, &pair);
assert_eq!(format!("{:x}", public), test.public_hex);
let secret_script = Vec::from_hex(&test.secret_hex_for_script).unwrap();
let pair_script = KeyPair::from_seckey_slice(&secp, &secret_script).unwrap();
let pubkey_in_script = PublicKey::from_keypair(&secp, &pair_script);
assert_eq!(format!("{:x}", pubkey_in_script), test.public_hex_for_script);
let inner_script_hex = format!("20{}ac", pubkey_in_script); // ac = OP_CHECKSIG
let inner_script = Script::from_hex(&inner_script_hex).unwrap();
let inner_script_hex_with_version = format!("{:x}22{}", 0xc0, inner_script_hex);
if let Some(script_hex_with_version_expected) = test.script_hex_with_version.as_ref() {
assert_eq!(inner_script_hex_with_version, *script_hex_with_version_expected);
}
let hash = TapLeafHash::hash(&Vec::from_hex(&inner_script_hex_with_version).unwrap());
// h = TaggedHash("TapLeaf", bytes([version]) + ser_string(code))
assert_eq!(format!("{:x}", hash), test.script_hash);
// TaggedHash("TapTweak", pubkey + h)
let mut engine = TapTweakHash::engine();
engine.input(&public.serialize());
engine.input(&hash);
let tweak = TapTweakHash::from_engine(engine);
assert_eq!(format!("{:x}", tweak), test.tweak);
let mut public_tweaked = public.clone();
let _parity = public_tweaked.tweak_add_assign(&secp, &tweak).unwrap();
assert_eq!(format!("{:x}", public_tweaked), test.public_tweaked);
let script_pubkey = Script::new_witness_program(WitnessVersion::V1, &public_tweaked.serialize());
assert_eq!(format!("{:x}", script_pubkey), test.script_pubkey);
TaprootConstruct {
inner_script, script_pubkey, public, pair, pair_script, pubkey_in_script
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment