Skip to content

Commit 2f5cf47

Browse files
committed
P2QRH:
1. No need to prefix every element of witness stack with 1 byte compact size length 2. Includes P2TR end-to-end example in Rust (for purpose of comparison with P2QRH)
1 parent 3ab1e3e commit 2f5cf47

File tree

10 files changed

+418
-71
lines changed

10 files changed

+418
-71
lines changed

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ $ b-reg decodescript 206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6
5555
+
5656
NOTE: This script includes a 32-byte x-only public key.
5757
Subsequently, it is not of standard type P2PK.
58-
As a standalone script, this script would be considered _non-standard_.
59-
However, as the script of a tapleaf, the script is considered _standard_.
58+
As a standalone script, this script would be considered _non-standard_.
59+
However, as a tapleaf script, it is considered _standard_.
6060
Tapscript relaxes many script standardness rules.
6161

6262
. 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] :
@@ -67,9 +67,11 @@ $ export BITCOIN_ADDRESS_INFO=$( cargo run --example p2qrh_construction ) \
6767
6868
$ export BITCOIN_NETWORK=regtest \
6969
&& export P2QRH_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.bech32m_address' ) \
70-
&& export FUNDING_SCRIPT_PUBKEY=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.script_pubkey' ) \
70+
&& export FUNDING_SCRIPT_PUBKEY=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.script_pubkey_hex' ) \
7171
&& echo -en "$P2QRH_ADDR\n$FUNDING_SCRIPT_PUBKEY\n"
7272
-----
73+
+
74+
NOTE: In `regtest`, you can expect a P2QRH address that starts with: `bcrt1r` .
7375

7476
. fund this P2QRH address with the coinbase reward of a newly generated block:
7577
+
@@ -95,7 +97,7 @@ $ export FUNDING_TX_ID=$( b-reg scantxoutset start '[{"desc": "'''$P2QRH_DESC'''
9597
&& echo $FUNDING_TX_ID
9698
-----
9799

98-
. Set FUNDIN_UTXO_INDEX env var (used later to correctly identify funding UTXO when generating the spending tx)
100+
. Set FUNDING_UTXO_INDEX env var (used later to correctly identify funding UTXO when generating the spending tx)
99101
+
100102
-----
101103
$ export FUNDING_UTXO_INDEX=0
@@ -111,7 +113,7 @@ $ export FUNDING_UTXO=$( b-reg getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout
111113
== Spend P2QRH UTXO
112114

113115

114-
. Determine value (in BTC) of funding utxo:
116+
. Determine value (in sats) of funding utxo:
115117
+
116118
-----
117119
$ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{printf "%.0f", $1 * 100000000}') \
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
:scrollbar:
2+
:data-uri:
3+
:toc2:
4+
:linkattrs:
5+
6+
= P2TR End-to-End Tutorial
7+
8+
:numbered:
9+
10+
This tutorial is inspired from the link:https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature[script-path-spend-signature] example of the _learnmeabitcoin_ tutorial.
11+
12+
Execute in Bitcoin Core `regtest` mode.
13+
14+
== Pre-reqs
15+
16+
=== Bitcoin Core
17+
18+
TO-DO
19+
20+
=== Shell Environment
21+
22+
. *b-reg* command line alias:
23+
+
24+
Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node running in `regtest` mode.
25+
. *jq*: ensure json parsing utility is installed and available via your $PATH.
26+
. *awk* : standard utility for all Linux distros (often packaged as `gawk`).
27+
28+
=== Bitcoin Core Wallet
29+
30+
This tutorial assumes that a bitcoin core wallet is available.
31+
For example, the following would be sufficient:
32+
33+
-----
34+
$ export W_NAME=regtest \
35+
&& export WPASS=regtest
36+
37+
$ b-reg -named createwallet \
38+
wallet_name=$W_NAME \
39+
descriptors=true \
40+
passphrase="$WPASS" \
41+
load_on_startup=true
42+
-----
43+
44+
== Fund P2TR UTXO
45+
46+
. View (hard-coded) tapscript used in single leaf taptree:
47+
+
48+
-----
49+
$ b-reg decodescript 206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac | jq -r '.asm'
50+
51+
6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
52+
-----
53+
+
54+
NOTE: This script includes a 32-byte x-only public key.
55+
Subsequently, it is not of standard type P2PK.
56+
As a standalone script, this script would be considered _non-standard_.
57+
However, as a tapleaf script, it is considered _standard_.
58+
Tapscript relaxes many script standardness rules.
59+
60+
. Generate a P2TR scripPubKey and address given a taptree as defined in link:https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature[learnmeabitcoin tutorial] :
61+
+
62+
-----
63+
$ export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tr_construction ) \
64+
&& echo $BITCOIN_ADDRESS_INFO | jq -r .
65+
66+
$ export BITCOIN_NETWORK=regtest \
67+
&& export P2TR_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.bech32m_address' ) \
68+
&& export FUNDING_SCRIPT_PUBKEY=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.script_pubkey_hex' ) \
69+
&& echo -en "$P2TR_ADDR\n$FUNDING_SCRIPT_PUBKEY\n"
70+
-----
71+
+
72+
NOTE: In `regtest`, you can expect a P2TR address that starts with: `bcrt1q` .
73+
74+
. fund this P2TR address with the coinbase reward of a newly generated block:
75+
+
76+
-----
77+
$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2TR_ADDR 1 | jq -r '.[]' ) \
78+
&& echo $COINBASE_REWARD_TX_ID
79+
-----
80+
+
81+
NOTE: Sometimes Bitcoin Core does not immediately respond. If so, wait a few seconds and try the above command again.
82+
83+
. view summary of all txs that have funded P2TR address
84+
+
85+
-----
86+
$ export P2TR_DESC=$( b-reg getdescriptorinfo "addr($P2TR_ADDR)" | jq -r '.descriptor' ) \
87+
&& echo $P2TR_DESC
88+
$ b-reg scantxoutset start '[{"desc": "'''$P2TR_DESC'''"}]'
89+
-----
90+
91+
. grab txid of first tx with unspent funds:
92+
+
93+
-----
94+
$ export FUNDING_TX_ID=$( b-reg scantxoutset start '[{"desc": "'''$P2TR_DESC'''"}]' | jq -r '.unspents[0].txid' ) \
95+
&& echo $FUNDING_TX_ID
96+
-----
97+
98+
. Set FUNDING_UTXO_INDEX env var (used later to correctly identify funding UTXO when generating the spending tx)
99+
+
100+
-----
101+
$ export FUNDING_UTXO_INDEX=0
102+
-----
103+
104+
. view details of funding UTXO to the P2TR address:
105+
+
106+
-----
107+
$ export FUNDING_UTXO=$( b-reg getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout['''$FUNDING_UTXO_INDEX''']' ) \
108+
&& echo $FUNDING_UTXO | jq -r .
109+
-----
110+
111+
== Spend P2TR UTXO
112+
113+
114+
. Determine value (in sats) of funding utxo:
115+
+
116+
-----
117+
$ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{printf "%.0f", $1 * 100000000}') \
118+
&& echo $FUNDING_UTXO_AMOUNT_SATS
119+
-----
120+
121+
. Referencing the funding tx (via $FUNDING_TX_ID and $FUNDING_UTXO_INDEX), create the spending tx:
122+
+
123+
-----
124+
$ export RAW_P2TR_SPEND_TX=$( cargo run --example p2tr_spend | jq -r '.tx_hex' ) \
125+
&& echo $RAW_P2TR_SPEND_TX
126+
-----
127+
128+
. Inspect the spending tx:
129+
+
130+
-----
131+
$ b-reg decoderawtransaction $RAW_P2TR_SPEND_TX
132+
-----
133+
134+
. Test standardness of the spending tx by sending to local mempool of p2tr enabled Bitcoin Core:
135+
136+
.. Generate additional blocks.
137+
+
138+
This is necessary if you have only previously generated 1 block in your bitcoin core.
139+
+
140+
-----
141+
$ b-reg -generate 110
142+
-----
143+
+
144+
Otherwise, you may see an error from bitcoin core such as the following:
145+
+
146+
_bad-txns-premature-spend-of-coinbase, tried to spend coinbase at depth 1_
147+
148+
.. Execute:
149+
+
150+
-----
151+
$ b-reg testmempoolaccept '["'''$RAW_P2TR_SPEND_TX'''"]'
152+
-----
153+
154+
. Submit tx:
155+
+
156+
-----
157+
$ export P2TR_SPENDING_TX_ID=$( b-reg sendrawtransaction $RAW_P2TR_SPEND_TX ) \
158+
&& echo $P2TR_SPENDING_TX_ID
159+
-----
160+
+
161+
NOTE: Should return same tx id as was included in $RAW_P2TR_SPEND_TX
162+
163+
== Mine P2TR Spend TX
164+
165+
. View tx in mempool:
166+
+
167+
-----
168+
$ b-reg getrawtransaction $P2TR_SPENDING_TX_ID 1
169+
-----
170+
+
171+
NOTE: There will not yet be a field `blockhash` in the response.
172+
173+
. Mine 1 block:
174+
+
175+
-----
176+
$ b-reg -generate 1
177+
-----
178+
179+
. Obtain `blockhash` field of mined tx:
180+
+
181+
-----
182+
$ export BLOCK_HASH=$( b-reg getrawtransaction $P2TR_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
183+
&& echo $BLOCK_HASH
184+
-----
185+
186+
. View tx in block:
187+
+
188+
-----
189+
$ b-reg getblock $BLOCK_HASH | jq -r .tx
190+
-----
191+
192+
== TO-DO
Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
1-
use bitcoin::taproot::TapNodeHash;
21
use p2qrh_ref::create_p2qrh_utxo;
3-
use p2qrh_ref::data_structures::P2qrhUtxoReturn;
4-
use bitcoin::hashes::Hash;
5-
use hex;
6-
7-
fn main() -> P2qrhUtxoReturn {
8-
let p2qrh_utxo_return: P2qrhUtxoReturn = p2qrh_script_path_utxo_construction();
9-
p2qrh_utxo_return
10-
}
2+
use p2qrh_ref::data_structures::UtxoReturn;
113

124
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
13-
fn p2qrh_script_path_utxo_construction() -> P2qrhUtxoReturn {
14-
15-
let merkle_root_bytes= hex::decode("858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef16").unwrap();
16-
let merkle_root: TapNodeHash = TapNodeHash::from_byte_array(merkle_root_bytes.try_into().unwrap());
17-
18-
let p2qrh_utxo_return: P2qrhUtxoReturn = create_p2qrh_utxo(merkle_root);
19-
20-
return p2qrh_utxo_return;
5+
fn main() -> UtxoReturn {
6+
7+
let merkle_root_hex = "858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef16".to_string();
8+
let p2qrh_utxo_return: UtxoReturn = create_p2qrh_utxo(merkle_root_hex);
9+
p2qrh_utxo_return
2110
}

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use p2qrh_ref::{ p2qrh_to_p2wpkh_tx };
1+
use p2qrh_ref::{ pay_to_p2wpkh_tx };
22

3-
use p2qrh_ref::data_structures::P2qrhSpendDetails;
3+
use p2qrh_ref::data_structures::SpendDetails;
44
use std::env;
55
use log::{info, error};
66

77
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
8-
fn main() -> P2qrhSpendDetails {
8+
fn main() -> SpendDetails {
99

1010
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
1111

@@ -47,10 +47,12 @@ fn main() -> P2qrhSpendDetails {
4747
std::process::exit(1);
4848
});
4949

50-
// Modified from learnmeabitcoin example
51-
// Changed from c0 to c1 control byte to reflect p2qrh specification: The parity bit of the control byte is always 1 since P2QRH does not have a key-spend path.
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.
5254
let p2qrh_control_block_bytes: Vec<u8> =
53-
hex::decode("c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329").unwrap();
55+
hex::decode("c1").unwrap();
5456
info!("P2QRH control block size: {}", p2qrh_control_block_bytes.len());
5557

5658
let leaf_script_priv_key_bytes: Vec<u8> = hex::decode("9b8de5d7f20a8ebb026a82babac3aa47a008debbfde5348962b2c46520bd5189").unwrap();
@@ -59,25 +61,26 @@ fn main() -> P2qrhSpendDetails {
5961
let leaf_script_bytes: Vec<u8> =
6062
hex::decode("206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac").unwrap();
6163

62-
let leaf_script_pubkey_hash_bytes: Vec<u8> = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
64+
// https://learnmeabitcoin.com/explorer/tx/797505b104b5fb840931c115ea35d445eb1f64c9279bf23aa5bb4c3d779da0c2#outputs
65+
let spend_output_pubkey_bytes: Vec<u8> = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
6366

6467
// OUTPUT_AMOUNT_SATS env var is optional. Default is FUNDING_UTXO_AMOUNT_SATS - 5000 sats
65-
let output_amount_sats: u64 = env::var("OUTPUT_AMOUNT_SATS")
68+
let spend_output_amount_sats: u64 = env::var("OUTPUT_AMOUNT_SATS")
6669
.ok()
6770
.and_then(|s| s.parse::<u64>().ok())
6871
.unwrap_or(funding_utxo_amount_sats.saturating_sub(5000));
6972

7073

71-
let result: P2qrhSpendDetails = p2qrh_to_p2wpkh_tx(
74+
let result: SpendDetails = pay_to_p2wpkh_tx(
7275
funding_tx_id_bytes,
7376
funding_utxo_index,
7477
funding_utxo_amount_sats,
7578
funding_script_pubkey_bytes,
7679
p2qrh_control_block_bytes,
77-
leaf_script_pubkey_hash_bytes,
7880
leaf_script_bytes,
7981
leaf_script_priv_key_bytes,
80-
output_amount_sats
82+
spend_output_pubkey_bytes,
83+
spend_output_amount_sats
8184
);
8285

8386
return result;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
use p2qrh_ref::create_p2tr_utxo;
3+
use p2qrh_ref::data_structures::UtxoReturn;
4+
5+
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
6+
fn main() -> UtxoReturn {
7+
8+
let merkle_root_hex = "858dfe26a3dd48a2c1fcee1d631f0aadf6a61135fc51f75758e945bca534ef16".to_string();
9+
let internal_pubkey_hex = "924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329".to_string();
10+
11+
let p2tr_utxo_return: UtxoReturn = create_p2tr_utxo(merkle_root_hex, internal_pubkey_hex);
12+
p2tr_utxo_return
13+
}

0 commit comments

Comments
 (0)