Skip to content

Commit 06c2103

Browse files
committed
p2qrh: working on multi-leaf taptree example in rust for
pay-to-quantum-resistant-hash Currently only works when using a single leaf.
1 parent 8903528 commit 06c2103

File tree

11 files changed

+357
-87
lines changed

11 files changed

+357
-87
lines changed

bip-0360/ref-impl/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ rust-bitcoin
22
rust-miniscript
33
target
44
.btcdeb_history
5+
*.swp

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

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,16 @@ $ b-reg -named createwallet \
4545

4646
== Fund P2QRH UTXO
4747

48-
. View (hard-coded) tapscript used in single leaf taptree:
48+
. OPTIONAL: Define number of leaves in tap tree as well as the tap leaf to later use as the unlocking script:
4949
+
5050
-----
51-
$ b-reg decodescript 206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac | jq -r '.asm'
52-
53-
6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
51+
$ export TOTAL_LEAF_COUNT=5 \
52+
&& export LEAF_OF_INTEREST=4
5453
-----
5554
+
56-
NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
55+
NOTE: Defaults are 4 leaves with the first leaf (leaf 0 ) as the script to later use as the unlocking script.
5756

58-
. Generate a P2QRH scripPubKey and address given a taptree as defined in link:https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature[learnmeabitcoin tutorial] :
57+
. Generate a P2QRH scripPubKey with multi-leaf taptree:
5958
+
6059
-----
6160
$ export BITCOIN_NETWORK=regtest \
@@ -67,13 +66,25 @@ NOTE: In `regtest`, you can expect a P2QRH address that starts with: `bcrt1r` .
6766
+
6867
NOTE: In the context of P2QRH, the _tree_root_hex_ from the response is in reference to the _quantum_root_ used in this tutorial.
6968

70-
. Set some env vars (for use in later steps in this tutorial) based on previous result:
69+
. Set some env vars (for use in later steps in this tutorial) based on previous result:
70+
+
71+
-----
72+
$ export QUANTUM_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tree_root_hex' ) \
73+
&& export LEAF_SCRIPT_PRIV_KEY_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_priv_key_hex' ) \
74+
&& export LEAF_SCRIPT_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_hex' ) \
75+
&& export CONTROL_BLOCK_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.control_block_hex' ) \
76+
&& export FUNDING_SCRIPT_PUBKEY=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.script_pubkey_hex' ) \
77+
&& export P2QRH_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.bech32m_address' )
78+
-----
79+
80+
. View tapscript used in target leaf of taptree:
7181
+
7282
-----
73-
$ export QUANTUM_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.tree_root_hex' ) \
74-
&& export FUNDING_SCRIPT_PUBKEY=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.script_pubkey_hex' ) \
75-
&& export P2QRH_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.bech32m_address' )
83+
$ b-reg decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
7684
-----
85+
+
86+
NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
87+
7788

7889
. fund this P2QRH address with the coinbase reward of a newly generated block:
7990
+
@@ -122,34 +133,43 @@ $ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{
122133
&& echo $FUNDING_UTXO_AMOUNT_SATS
123134
-----
124135

125-
. Referencing the funding tx (via $FUNDING_TX_ID and $FUNDING_UTXO_INDEX), create the spending tx:
136+
. Generate additional blocks.
137+
+
138+
This is necessary if you have only previously generated less than 100 blocks.
126139
+
127140
-----
128-
$ export RAW_P2QRH_SPEND_TX=$( cargo run --example p2qrh_spend | jq -r '.tx_hex' ) \
129-
&& echo $RAW_P2QRH_SPEND_TX
141+
$ b-reg -generate 110
130142
-----
143+
+
144+
Otherwise, you may see an error from bitcoin core such as the following when attempting to spend:
145+
+
146+
_bad-txns-premature-spend-of-coinbase, tried to spend coinbase at depth 1_
131147

132-
. Inspect the spending tx:
148+
149+
. Referencing the funding tx (via $FUNDING_TX_ID and $FUNDING_UTXO_INDEX), create the spending tx:
133150
+
134151
-----
135-
$ b-reg decoderawtransaction $RAW_P2QRH_SPEND_TX
136-
-----
152+
$ export SPEND_DETAILS=$( cargo run --example p2qrh_spend )
137153
138-
. Test standardness of the spending tx by sending to local mempool of p2qrh enabled Bitcoin Core:
154+
$ export RAW_P2QRH_SPEND_TX=$( echo $SPEND_DETAILS | jq -r '.tx_hex' ) \
155+
&& echo "RAW_P2QRH_SPEND_TX = $RAW_P2QRH_SPEND_TX" \
156+
&& export SIG_HASH=$( echo $SPEND_DETAILS | jq -r '.sighash' ) \
157+
&& echo "SIG_HASH = $SIG_HASH" \
158+
&& export SIG_BYTES=$( echo $SPEND_DETAILS | jq -r '.sig_bytes' ) \
159+
&& echo "SIG_BYTES = $SIG_BYTES"
160+
-----
139161

140-
.. Generate additional blocks.
141-
+
142-
This is necessary if you have only previously generated 1 block.
162+
. Inspect the spending tx:
143163
+
144164
-----
145-
$ b-reg -generate 110
165+
$ b-reg decoderawtransaction $RAW_P2QRH_SPEND_TX
146166
-----
147167
+
148-
Otherwise, you may see an error from bitcoin core such as the following:
149-
+
150-
_bad-txns-premature-spend-of-coinbase, tried to spend coinbase at depth 1_
168+
Pay particular attention to the `vin.txinwitness` field.
169+
Do the three elements (script input, script and control block) of the witness stack for this script path spend make sense ?
170+
What do you observe as the first byte of the `control block` element ?
151171

152-
.. Execute:
172+
. Test standardness of the spending tx by sending to local mempool of p2qrh enabled Bitcoin Core:
153173
+
154174
-----
155175
$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
┌───────────────────────┐
2+
│ tapleaf Merkle root │
3+
│ │
4+
└───────────────────────┘
5+
|
6+
┌───────────────────────┐
7+
│ 5 tagged_hash │
8+
│ QuantumRoot │
9+
└───────────|───────────┘
10+
┌───────────|───────────┐
11+
┌───────────────────────────────►│ 4 tagged_hash ◄─────────────────────┐
12+
│ │ TapBranch │ │
13+
│ └───────────────────────┘ │
14+
│ │
15+
│ │
16+
│ │
17+
│ │
18+
│ │
19+
│ ┌─────────────┼───────────┐
20+
│ │ 3 tagged_hash │
21+
│ ┌──►│ TapBranch ◄───────┐
22+
│ │ └─────────────────────────┘ │
23+
│ │ │
24+
│ │ │
25+
┌───────────┼────────────┐ ┌───────┼────────┐ │
26+
┌─────►│ 2 tagged_hash ◄────┐ ┌─────►│ 2 tagged_hash ◄────┐ │
27+
│ │ TapBranch │ │ │ │ TapBranch │ │ │
28+
│ └────────────────────────┘ │ │ └────────────────┘ │ │
29+
│ │ │ │ │
30+
│ │ ┌─────┼────────┐ ┌───────|───-─┐ ┌──────┴──────┐
31+
┌───┼──────────┐ ┌──────────┼──┐ │ 1 tagged_hash│ │1 tagged_hash│ │1 tagged_hash│
32+
│1 tagged_hash │ │1 tagged_hash│ │ Tapleaf │ │ Tapleaf │ │ Tapleaf │
33+
│ Tapleaf │ │ Tapleaf │ └──────────────┘ └─────────────┘ └─────────────┘
34+
└──▲───────────┘ └──────▲──────┘ ▲ ▲ ▲
35+
│ │ │ │ │
36+
version | A script version | B script version | C script version | D script version|E script
Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
1-
use p2qrh_ref::{create_p2qrh_utxo, tagged_hash};
2-
use p2qrh_ref::data_structures::UtxoReturn;
1+
use p2qrh_ref::{create_p2qrh_utxo, create_multi_leaf_taptree};
2+
use p2qrh_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn};
3+
use std::env;
34

45
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
5-
fn main() -> UtxoReturn {
6+
fn main() -> ConstructionReturn {
7+
8+
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
9+
10+
let mut total_leaf_count: u32 = 1;
11+
if let Ok(env_value) = env::var("TOTAL_LEAF_COUNT") {
12+
if let Ok(parsed_value) = env_value.parse::<u32>() {
13+
total_leaf_count = parsed_value;
14+
}
15+
}
616

7-
let merkle_root_hex = hex::decode("858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef16").unwrap();
8-
let quantum_root_hex = tagged_hash("QuantumRoot", &merkle_root_hex);
9-
let p2qrh_utxo_return: UtxoReturn = create_p2qrh_utxo(quantum_root_hex);
10-
p2qrh_utxo_return
17+
let mut leaf_of_interest: u32 = 0;
18+
if let Ok(env_value) = env::var("LEAF_OF_INTEREST") {
19+
if let Ok(parsed_value) = env_value.parse::<u32>() {
20+
leaf_of_interest = parsed_value;
21+
}
22+
}
23+
24+
let taptree_return: TaptreeReturn = create_multi_leaf_taptree(total_leaf_count, leaf_of_interest);
25+
let p2qrh_utxo_return: UtxoReturn = create_p2qrh_utxo(taptree_return.clone().tree_root_hex);
26+
27+
return ConstructionReturn {
28+
taptree_return: taptree_return,
29+
utxo_return: p2qrh_utxo_return,
30+
};
1131
}

bip-0360/ref-impl/rust/examples/p2qrh_spend.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use p2qrh_ref::{ pay_to_p2wpkh_tx };
1+
use p2qrh_ref::{ pay_to_p2wpkh_tx, verify_schnorr_signature_via_bytes };
22

33
use p2qrh_ref::data_structures::SpendDetails;
44
use std::env;
@@ -15,6 +15,7 @@ fn main() -> SpendDetails {
1515
error!("FUNDING_TX_ID environment variable is required but not set");
1616
std::process::exit(1);
1717
});
18+
let funding_tx_id_bytes: Vec<u8> = hex::decode(funding_tx_id.clone()).unwrap();
1819

1920
// FUNDING_UTXO_AMOUNT_SATS environment variable is required
2021
let funding_utxo_amount_sats: u64 = env::var("FUNDING_UTXO_AMOUNT_SATS")
@@ -28,7 +29,6 @@ fn main() -> SpendDetails {
2829
std::process::exit(1);
2930
});
3031

31-
let funding_tx_id_bytes: Vec<u8> = hex::decode(funding_tx_id.clone()).unwrap();
3232

3333
// The input index of the funding tx
3434
// Allow override via FUNDING_UTXO_INDEX environment variable
@@ -47,22 +47,31 @@ fn main() -> SpendDetails {
4747
std::process::exit(1);
4848
});
4949

50-
// P2QRH control block does not include internal pubkey.
51-
// (P2TR internal pubkey is used to verify that the pub key and merkle root can be combined to match the tweaked pub key in ScriptPubKey).
52-
// P2QRH leaf version will always be c1: The parity bit of the control byte is always 1 since P2QRH does not have a key-spend path.
53-
// In this tutorial, there is only a single leaf in the taptree; thus no script path.
54-
let p2qrh_control_block_bytes: Vec<u8> =
55-
hex::decode("c1").unwrap();
50+
let p2qrh_control_block_bytes: Vec<u8> = env::var("CONTROL_BLOCK_HEX")
51+
.map(|s| hex::decode(s).unwrap())
52+
.unwrap_or_else(|_| {
53+
error!("CONTROL_BLOCK_HEX environment variable is required but not set");
54+
std::process::exit(1);
55+
});
5656
info!("P2QRH control block size: {}", p2qrh_control_block_bytes.len());
5757

58-
let leaf_script_priv_key_bytes: Vec<u8> = hex::decode("9b8de5d7f20a8ebb026a82babac3aa47a008debbfde5348962b2c46520bd5189").unwrap();
58+
let leaf_script_priv_key_bytes: Vec<u8> = env::var("LEAF_SCRIPT_PRIV_KEY_HEX")
59+
.map(|s| hex::decode(s).unwrap())
60+
.unwrap_or_else(|_| {
61+
error!("LEAF_SCRIPT_PRIV_KEY_HEX environment variable is required but not set");
62+
std::process::exit(1);
63+
});
5964

60-
// OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
61-
let leaf_script_bytes: Vec<u8> =
62-
hex::decode("206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac").unwrap();
65+
// ie: OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
66+
let leaf_script_bytes: Vec<u8> = env::var("LEAF_SCRIPT_HEX")
67+
.map(|s| hex::decode(s).unwrap())
68+
.unwrap_or_else(|_| {
69+
error!("LEAF_SCRIPT_HEX environment variable is required but not set");
70+
std::process::exit(1);
71+
});
6372

6473
// https://learnmeabitcoin.com/explorer/tx/797505b104b5fb840931c115ea35d445eb1f64c9279bf23aa5bb4c3d779da0c2#outputs
65-
let spend_output_pubkey_bytes: Vec<u8> = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
74+
let spend_output_pubkey_hash_bytes: Vec<u8> = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
6675

6776
// OUTPUT_AMOUNT_SATS env var is optional. Default is FUNDING_UTXO_AMOUNT_SATS - 5000 sats
6877
let spend_output_amount_sats: u64 = env::var("OUTPUT_AMOUNT_SATS")
@@ -77,11 +86,20 @@ fn main() -> SpendDetails {
7786
funding_utxo_amount_sats,
7887
funding_script_pubkey_bytes,
7988
p2qrh_control_block_bytes,
80-
leaf_script_bytes,
89+
leaf_script_bytes.clone(),
8190
leaf_script_priv_key_bytes,
82-
spend_output_pubkey_bytes,
91+
spend_output_pubkey_hash_bytes,
8392
spend_output_amount_sats
8493
);
8594

95+
// Remove first and last byte from leaf_script_bytes to get tapleaf_pubkey_bytes
96+
let tapleaf_pubkey_bytes: Vec<u8> = leaf_script_bytes[1..leaf_script_bytes.len()-1].to_vec();
97+
98+
let is_valid: bool = verify_schnorr_signature_via_bytes(
99+
&result.sig_bytes,
100+
&result.sighash,
101+
&tapleaf_pubkey_bytes);
102+
info!("is_valid: {}", is_valid);
103+
86104
return result;
87105
}

bip-0360/ref-impl/rust/examples/p2tr_spend.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use p2qrh_ref::{ pay_to_p2wpkh_tx };
1+
use p2qrh_ref::{ pay_to_p2wpkh_tx , verify_schnorr_signature_via_bytes};
22

33
use p2qrh_ref::data_structures::SpendDetails;
44
use std::env;
@@ -76,9 +76,15 @@ fn main() -> SpendDetails {
7676
control_block_bytes,
7777
leaf_script_bytes,
7878
leaf_script_priv_key_bytes,
79-
spend_output_pubkey_bytes,
79+
spend_output_pubkey_bytes.clone(),
8080
spend_output_amount_sats
8181
);
8282

83+
let is_valid: bool = verify_schnorr_signature_via_bytes(
84+
&result.sig_bytes,
85+
&result.sighash,
86+
&spend_output_pubkey_bytes);
87+
info!("is_valid: {}", is_valid);
88+
8389
return result;
8490
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use std::env;
2+
use log::info;
3+
use once_cell::sync::Lazy;
4+
use bitcoin::key::{Secp256k1};
5+
use bitcoin::hashes::{sha256::Hash, Hash as HashTrait};
6+
use bitcoin::secp256k1::{Message};
7+
8+
use p2qrh_ref::{ acquire_schnorr_keypair, verify_schnorr_signature };
9+
10+
/* Secp256k1 implements the Signing trait when it's initialized in signing mode.
11+
It's important to note that Secp256k1 has different capabilities depending on how it's constructed:
12+
* Secp256k1::new() creates a context capable of both signing and verification
13+
* Secp256k1::signing_only() creates a context that can only sign
14+
* Secp256k1::verification_only() creates a context that can only verify
15+
*/
16+
static SECP: Lazy<Secp256k1<bitcoin::secp256k1::All>> = Lazy::new(Secp256k1::new);
17+
18+
fn main() {
19+
let _ = env_logger::try_init();
20+
let keypair = acquire_schnorr_keypair();
21+
let message_bytes = b"hello";
22+
23+
// Hash the message first to get a 32-byte digest
24+
let message_hash = Hash::hash(message_bytes);
25+
let message = Message::from_digest_slice(&message_hash.to_byte_array()).unwrap();
26+
let pubkey = keypair.1;
27+
28+
let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr(&message, &keypair.0.keypair(&SECP));
29+
30+
let signature_aux_rand: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand(
31+
&message,
32+
&keypair.0.keypair(&SECP),
33+
&[0u8; 32] // 32 zero bytes of auxiliary random data
34+
);
35+
36+
let schnorr_valid = verify_schnorr_signature(signature, message, pubkey);
37+
info!("schnorr_valid: {}", schnorr_valid);
38+
39+
let schnorr_valid_aux_rand = verify_schnorr_signature(signature_aux_rand, message, pubkey);
40+
info!("schnorr_valid_aux_rand: {}", schnorr_valid_aux_rand);
41+
}

0 commit comments

Comments
 (0)