Skip to content

Commit 5ef52b6

Browse files
committed
p2qrh: multi-leaf p2qrh example now works
1 parent a1ef895 commit 5ef52b6

File tree

4 files changed

+57
-16
lines changed

4 files changed

+57
-16
lines changed

bip-0360/ref-impl/rust/docs/p2qrh-end-to-end.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
8989
. fund this P2QRH address with the coinbase reward of a newly generated block:
9090
+
9191
-----
92-
$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2QRH_ADDR 1 | jq -r '.[]' ) \
92+
$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2QRH_ADDR 5 | jq -r '.[]' ) \
9393
&& echo $COINBASE_REWARD_TX_ID
9494
-----
9595
+
96-
NOTE: Sometimes Bitcoin Core does not immediately respond. If so, wait a few seconds and try the above command again.
96+
NOTE: Sometimes Bitcoin Core may not hit a block (even on regtest). If so, just try the above command again.
9797

9898
. view summary of all txs that have funded P2QRH address
9999
+

bip-0360/ref-impl/rust/docs/p2tr-end-to-end.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
8989
. fund this P2TR address with the coinbase reward of a newly generated block:
9090
+
9191
-----
92-
$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2TR_ADDR 1 | jq -r '.[]' ) \
92+
$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2TR_ADDR 5 | jq -r '.[]' ) \
9393
&& echo $COINBASE_REWARD_TX_ID
9494
-----
9595
+
96-
NOTE: Sometimes Bitcoin Core does not immediately respond. If so, wait a few seconds and try the above command again.
96+
NOTE: Sometimes Bitcoin Core may not hit a block (even on regtest). If so, just try the above command again.
9797

9898
. view summary of all txs that have funded P2TR address
9999
+

bip-0360/ref-impl/rust/src/lib.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use bitcoin::{ Amount, TxOut, WPubkeyHash,
1313
blockdata::witness::Witness,
1414
Script, ScriptBuf, XOnlyPublicKey, PublicKey,
1515
sighash::{SighashCache, TapSighashType, Prevouts, TapSighash},
16-
taproot::{LeafVersion, TapLeafHash, TapNodeHash, TapTree, ScriptLeaves, TaprootMerkleBranch, TaprootBuilder, TaprootSpendInfo, ControlBlock},
16+
taproot::{LeafVersion, NodeInfo, TapLeafHash, TapNodeHash, TapTree, ScriptLeaves, TaprootMerkleBranch, TaprootBuilder, TaprootSpendInfo, ControlBlock},
1717
transaction::{Transaction, Sequence}
1818
};
1919

20-
use bitcoin::p2qrh::{P2qrhScriptBuf, P2qrhBuilder, P2qrhSpendInfo, P2qrhControlBlock};
20+
use bitcoin::p2qrh::{P2qrhScriptBuf, P2qrhBuilder, P2qrhSpendInfo, P2qrhControlBlock, QuantumRootHash, P2QRH_LEAF_VERSION};
2121

2222
use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn};
2323

@@ -86,24 +86,44 @@ pub fn create_p2qrh_multi_leaf_taptree() -> TaptreeReturn {
8686
let p2qrh_builder: P2qrhBuilder = P2qrhBuilder::with_huffman_tree(huffman_entries).unwrap();
8787

8888
let p2qrh_spend_info: P2qrhSpendInfo = p2qrh_builder.clone().finalize().unwrap();
89-
let quantum_root: TapNodeHash = p2qrh_spend_info.quantum_root.unwrap();
90-
info!("keypair_of_interest: \n\tsecret_bytes: {} \n\tpubkey: {} \n\tquantum_root: {}",
89+
let quantum_root: QuantumRootHash = p2qrh_spend_info.quantum_root.unwrap();
90+
info!("keypair_of_interest: \n\tsecret_bytes: {} \n\tpubkey: {}",
9191
hex::encode(keypair_of_interest.unwrap().0.secret_bytes()), // secret_bytes returns big endian
9292
hex::encode(keypair_of_interest.unwrap().1.serialize()), // serialize returns little endian
93-
quantum_root);
94-
93+
);
94+
9595
let tap_tree: TapTree = p2qrh_builder.clone().into_inner().try_into_taptree().unwrap();
9696
let mut script_leaves: ScriptLeaves = tap_tree.script_leaves();
9797
let script_leaf = script_leaves
9898
.find(|leaf| leaf.script() == script_buf_of_interest.as_ref().unwrap().as_script())
9999
.expect("Script leaf not found");
100+
101+
let merkle_root_node_info: NodeInfo = p2qrh_builder.clone().into_inner().try_into_node_info().unwrap();
102+
let merkle_root: TapNodeHash = merkle_root_node_info.node_hash();
103+
104+
let leaf_hash: TapLeafHash = TapLeafHash::from_script(script_leaf.script(), LeafVersion::from_consensus(P2QRH_LEAF_VERSION).unwrap());
105+
106+
// Convert leaf hash to big-endian for display (like Bitcoin Core)
107+
let mut leaf_hash_bytes = leaf_hash.as_raw_hash().to_byte_array().to_vec();
108+
leaf_hash_bytes.reverse();
109+
110+
info!("leaf_hash: {}, merkle_root: {}, quantum_root: {}",
111+
hex::encode(leaf_hash_bytes),
112+
merkle_root,
113+
quantum_root);
114+
100115
let leaf_script = script_leaf.script();
101116
let merkle_branch: &TaprootMerkleBranch = script_leaf.merkle_branch();
117+
102118
info!("Leaf script: {}, merkle branch: {:?}", leaf_script, merkle_branch);
103119

104120
let control_block: P2qrhControlBlock = P2qrhControlBlock{
105121
merkle_branch: merkle_branch.clone(),
106122
};
123+
124+
// Not a requirement but useful to demonstrate what Bitcoin Core does as the verifier when spending from a p2qrh UTXO
125+
control_block.verify_script_in_quantum_root_path(leaf_script, quantum_root);
126+
107127
let control_block_hex: String = hex::encode(control_block.serialize());
108128

109129
return TaptreeReturn {
@@ -132,6 +152,7 @@ pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> Taptr
132152
// When spending via script path, the verifier needs to know whether the output key has an even or odd Y-coordinate to properly reconstruct & verify the internal key.
133153
// The internal key can be recovered from the output key using the parity bit and the merkle root.
134154
let output_key_parity: Parity = p2tr_spend_info.output_key_parity();
155+
let output_key: XOnlyPublicKey = p2tr_spend_info.output_key().into();
135156

136157
info!("keypair_of_interest: \n\tsecret_bytes: {} \n\tpubkey: {} \n\tmerkle_root: {}",
137158
hex::encode(keypair_of_interest.unwrap().0.secret_bytes()), // secret_bytes returns big endian
@@ -145,14 +166,19 @@ pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> Taptr
145166
.expect("Script leaf not found");
146167
let leaf_script = script_leaf.script().to_hex_string();
147168
let merkle_branch: &TaprootMerkleBranch = script_leaf.merkle_branch();
169+
debug!("Leaf script: {}, merkle branch: {:?}", leaf_script, merkle_branch);
170+
148171
let control_block: ControlBlock = ControlBlock{
149172
leaf_version: LeafVersion::TapScript,
150173
output_key_parity: output_key_parity,
151174
internal_key: internal_xonly_pubkey,
152175
merkle_branch: merkle_branch.clone(),
153176
};
154177
let control_block_hex: String = hex::encode(control_block.serialize());
155-
debug!("Leaf script: {}, merkle branch: {:?}", leaf_script, merkle_branch);
178+
179+
// Not a requirement but useful to demonstrate what Bitcoin Core does as the verifier when spending from a p2tr UTXO
180+
let verify: bool = verify_taproot_commitment(control_block_hex.clone(), output_key, script_leaf.script());
181+
info!("verify_taproot_commitment: {}", verify);
156182

157183
return TaptreeReturn {
158184
leaf_script_priv_key_hex: hex::encode(keypair_of_interest.unwrap().0.secret_bytes()),
@@ -443,3 +469,20 @@ pub fn verify_schnorr_signature(mut signature: Signature, message: Message, pubk
443469
}
444470
is_valid
445471
}
472+
473+
/* 1. Re-constructs merkle_root from merkle_path (found in control_block) and provided script.
474+
2. Determines the parity of the output key via the control byte (found in the control block).
475+
- the parity bit indicates whether the output key has an even or odd Y-coordinate
476+
3. Computes the tap tweak hash using the internal key and reconstructed merkle root.
477+
- tap_tweak_hash = tagged_hash("TapTweak", internal_key || merkle_root)
478+
4. Verifies that the provided output key can be derived from the internal key using the tweak.
479+
- tap_tweak_hash = tagged_hash("TapTweak", internal_key || merkle_root)
480+
5. This proves the script is committed to in the taptree described by the output key.
481+
*/
482+
pub fn verify_taproot_commitment(control_block_hex: String, output_key: XOnlyPublicKey, script: &Script) -> bool {
483+
484+
let control_block_bytes = hex::decode(control_block_hex).unwrap();
485+
let control_block: ControlBlock = ControlBlock::decode(&control_block_bytes).unwrap();
486+
487+
return control_block.verify_taproot_commitment(&SECP, output_key, script);
488+
}

bip-0360/ref-impl/rust/tests/p2qrh_construction.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::HashSet;
22
use bitcoin::{Network, ScriptBuf};
3-
use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch, TapNodeHash};
4-
use bitcoin::p2qrh::{P2qrhBuilder, P2qrhControlBlock, P2qrhSpendInfo };
3+
use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch};
4+
use bitcoin::p2qrh::{P2qrhBuilder, P2qrhControlBlock, P2qrhSpendInfo, QuantumRootHash};
55
use bitcoin::hashes::Hash;
66

77
use hex;
@@ -140,8 +140,6 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
140140
let tv_leaf_script_buf = ScriptBuf::from_bytes(tv_leaf_script_bytes.clone());
141141
let tv_leaf_version = LeafVersion::from_consensus(tv_leaf.leaf_version).unwrap();
142142
control_block_data.push((tv_leaf_script_buf.clone(), tv_leaf_version));
143-
144-
LeafVersion::TapScript;
145143

146144
let mut modified_depth = depth + 1;
147145
if direction == Direction::Root {
@@ -174,7 +172,7 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
174172
panic!("finalize failed: {:?}", e);
175173
});
176174

177-
let derived_quantum_root: TapNodeHash= spend_info.quantum_root.unwrap();
175+
let derived_quantum_root: QuantumRootHash = spend_info.quantum_root.unwrap();
178176

179177
// 2) verify derived quantum root against test vector
180178
let test_vector_quantum_root = test_vector.intermediary.quantum_root.as_ref().unwrap();

0 commit comments

Comments
 (0)