Created
October 27, 2021 12:52
-
-
Save RCasatta/874f2a95ad502ad7c1fc3a60e9777947 to your computer and use it in GitHub Desktop.
Some test code trying out taproot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[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