From f3baa42a254254b2e52cb4522cfd46bf366c78b5 Mon Sep 17 00:00:00 2001
From: jbride
Date: Wed, 13 Aug 2025 12:44:00 -0600
Subject: [PATCH 01/16] bip360: p2qrh -> p2tsh
---
...struction.json => p2tsh_construction.json} | 66 ++++-----
bip-0360/ref-impl/rust/Cargo.lock | 2 +-
bip-0360/ref-impl/rust/Cargo.toml | 4 +-
bip-0360/ref-impl/rust/README.md | 6 +-
...-end-to-end.adoc => p2tsh-end-to-end.adoc} | 82 ++++++------
.../rust/examples/p2tr_construction.rs | 4 +-
bip-0360/ref-impl/rust/examples/p2tr_spend.rs | 4 +-
..._construction.rs => p2tsh_construction.rs} | 10 +-
.../{p2qrh_spend.rs => p2tsh_spend.rs} | 6 +-
.../ref-impl/rust/examples/schnorr_example.rs | 4 +-
bip-0360/ref-impl/rust/src/data_structures.rs | 4 +-
bip-0360/ref-impl/rust/src/error.rs | 4 +-
bip-0360/ref-impl/rust/src/lib.rs | 36 ++---
..._construction.rs => p2tsh_construction.rs} | 125 ++++++++----------
.../tests/{p2qrh_spend.rs => p2tsh_spend.rs} | 10 +-
15 files changed, 178 insertions(+), 189 deletions(-)
rename bip-0360/ref-impl/common/tests/data/{p2qrh_construction.json => p2tsh_construction.json} (79%)
rename bip-0360/ref-impl/rust/docs/{p2qrh-end-to-end.adoc => p2tsh-end-to-end.adoc} (74%)
rename bip-0360/ref-impl/rust/examples/{p2qrh_construction.rs => p2tsh_construction.rs} (56%)
rename bip-0360/ref-impl/rust/examples/{p2qrh_spend.rs => p2tsh_spend.rs} (95%)
rename bip-0360/ref-impl/rust/tests/{p2qrh_construction.rs => p2tsh_construction.rs} (69%)
rename bip-0360/ref-impl/rust/tests/{p2qrh_spend.rs => p2tsh_spend.rs} (92%)
diff --git a/bip-0360/ref-impl/common/tests/data/p2qrh_construction.json b/bip-0360/ref-impl/common/tests/data/p2tsh_construction.json
similarity index 79%
rename from bip-0360/ref-impl/common/tests/data/p2qrh_construction.json
rename to bip-0360/ref-impl/common/tests/data/p2tsh_construction.json
index a6df7ebe44..7729217bcc 100644
--- a/bip-0360/ref-impl/common/tests/data/p2qrh_construction.json
+++ b/bip-0360/ref-impl/common/tests/data/p2tsh_construction.json
@@ -2,20 +2,20 @@
"version": 1,
"test_vectors": [
{
- "id": "p2qrh_missing_leaf_script_tree_error",
- "objective": "Tests P2QRH with missing leaf script tree",
+ "id": "p2tsh_missing_leaf_script_tree_error",
+ "objective": "Tests P2TSH with missing leaf script tree",
"given": {
"script_tree": ""
},
"intermediary": {
},
"expected": {
- "error": "P2QRH requires a script tree with at least one leaf"
+ "error": "P2TSH requires a script tree with at least one leaf"
}
},
{
- "id": "p2qrh_single_leaf_script_tree",
- "objective": "Tests P2QRH with single leaf script tree",
+ "id": "p2tsh_single_leaf_script_tree",
+ "objective": "Tests P2TSH with single leaf script tree",
"given": {
"scriptTree": {
"id": 0,
@@ -28,19 +28,19 @@
"leafHashes": [
"c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b"
],
- "quantumRoot": "27e8a6af1f05d3dbfebfc4073a8391cf8db28746767c2b34d606900ad721127b"
+ "merkleRoot": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b"
},
"expected": {
- "scriptPubKey": "532027e8a6af1f05d3dbfebfc4073a8391cf8db28746767c2b34d606900ad721127b",
- "bip350Address": "bc1ryl52dtclqhfahl4lcsrn4qu3e7xm9p6xwe7zkdxkq6gq44epzfasklalkw",
+ "scriptPubKey": "5220c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b",
+ "bip350Address": "bc1zc5jhzjnlf8pg4mdmhfuvqpvnr2quyd9j7mye5uly6psg9twghu4ssr0v9k",
"scriptPathControlBlocks": [
"c1"
]
}
},
{
- "id": "p2qrh_different_version_leaves",
- "objective": "Tests P2QRH with two script leaves of different versions. TO-DO: currently ignores given leaf version and over-rides. Probably better to throw error",
+ "id": "p2tsh_different_version_leaves",
+ "objective": "Tests P2TSH with two script leaves of different versions. TO-DO: currently ignores given leaf version and over-rides. Probably better to throw error",
"given": {
"scriptTree": [
{
@@ -63,11 +63,11 @@
"8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7",
"f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
],
- "quantumRoot": "af8e7912e4794d9234f22ae3c03905d50d1015572617efc74f482aa6c8ba0376"
+ "merkleRoot": "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef"
},
"expected": {
- "scriptPubKey": "5320af8e7912e4794d9234f22ae3c03905d50d1015572617efc74f482aa6c8ba0376",
- "bip350Address": "bc1r4788jyhy09xeyd8j9t3uqwg965x3q92hyct7l360fq42dj96qdmqtpftcg",
+ "scriptPubKey": "52206c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef",
+ "bip350Address": "bc1zdskuzp4ts94h87ws0c7drmev3sf9dagewj8qsylyahfyqhf800hsam4d6e",
"scriptPathControlBlocks": [
"c1f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a",
"c18ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7"
@@ -75,8 +75,8 @@
}
},
{
- "id": "p2qrh_simple_lightning_contract",
- "objective": "Tests P2QRH with two script leaves that simulate a simple lightning network contract. Reference: https://github.com/bitcoin-core/btcdeb/blob/master/doc/tapscript-example-with-tap.md",
+ "id": "p2tsh_simple_lightning_contract",
+ "objective": "Tests P2TSH with two script leaves that simulate a simple lightning network contract. Reference: https://github.com/bitcoin-core/btcdeb/blob/master/doc/tapscript-example-with-tap.md",
"given": {
"scriptTree": [
{
@@ -100,11 +100,11 @@
"c81451874bd9ebd4b6fd4bba1f84cdfb533c532365d22a0a702205ff658b17c9",
"632c8632b4f29c6291416e23135cf78ecb82e525788ea5ed6483e3c6ce943b42"
],
- "quantumRoot": "04b74fb166133f6700927ce4f83ad08cbef5209bdc253b6e727b0206a78fbfd9"
+ "merkleRoot": "41646f8c1fe2a96ddad7f5471bc4fee7da98794ef8c45a4f4fc6a559d60c9f6b"
},
"expected": {
- "scriptPubKey": "532004b74fb166133f6700927ce4f83ad08cbef5209bdc253b6e727b0206a78fbfd9",
- "bip350Address": "bc1rqjm5lvtxzvlkwqyj0nj0swks3jl02gymmsjnkmnj0vpqdfu0hlvsn5j95l",
+ "scriptPubKey": "522041646f8c1fe2a96ddad7f5471bc4fee7da98794ef8c45a4f4fc6a559d60c9f6b",
+ "bip350Address": "bc1zg9jxlrqlu25kmkkh74r3h387uldfs72wlrz95n60c6j4n4svna4s4lhfhe",
"scriptPathControlBlocks": [
"c1c81451874bd9ebd4b6fd4bba1f84cdfb533c532365d22a0a702205ff658b17c9",
"c1632c8632b4f29c6291416e23135cf78ecb82e525788ea5ed6483e3c6ce943b42"
@@ -112,8 +112,8 @@
}
},
{
- "id": "p2qrh_two_leaf_same_version",
- "objective": "Tests P2QRH with two script leaves of same version",
+ "id": "p2tsh_two_leaf_same_version",
+ "objective": "Tests P2TSH with two script leaves of same version",
"given": {
"scriptTree": [
{
@@ -136,11 +136,11 @@
"64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89",
"2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
],
- "quantumRoot": "f2916fd14cd711482f5262fef02d61647602493e94a3b5ec48d2cc1beee78fa5"
+ "merkleRoot": "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc"
},
"expected": {
- "scriptPubKey": "5320f2916fd14cd711482f5262fef02d61647602493e94a3b5ec48d2cc1beee78fa5",
- "bip350Address": "bc1r72gkl52v6ug5st6jvtl0qttpv3mqyjf7jj3mtmzg6txphmh837jsv2ydgs",
+ "scriptPubKey": "5220ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc",
+ "bip350Address": "bc1z4vtegvwz35ak37me39tl4a2f045u3q7xlv0pek0czjpas7avjrxqz20g2y",
"scriptPathControlBlocks": [
"c12cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb",
"c164512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89"
@@ -148,8 +148,8 @@
}
},
{
- "id": "p2qrh_three_leaf_complex",
- "objective": "Tests P2QRH with a complex three-leaf script tree structure, demonstrating nested script paths and multiple verification options",
+ "id": "p2tsh_three_leaf_complex",
+ "objective": "Tests P2TSH with a complex three-leaf script tree structure, demonstrating nested script paths and multiple verification options",
"given": {
"internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
"scriptTree": [
@@ -181,11 +181,11 @@
"ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c",
"9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6"
],
- "quantumRoot": "6035c881af058a8135592c019cab755a0b41692e408eebe3ca9f0ee32d1b020c"
+ "merkleRoot": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2"
},
"expected": {
- "scriptPubKey": "53206035c881af058a8135592c019cab755a0b41692e408eebe3ca9f0ee32d1b020c",
- "bip350Address": "bc1rvq6u3qd0qk9gzd2e9sqee2m4tg95z6fwgz8whc72nu8wxtgmqgxqu3r6wq",
+ "scriptPubKey": "5220ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
+ "bip350Address": "bc1zej7kd3hhar76k3an5jr0t8fgyc47s4lnp4rh8uk4afrlwasuur3qzgewqq",
"scriptPathControlBlocks": [
"c1ffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553",
"c1ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817",
@@ -194,8 +194,8 @@
}
},
{
- "id": "p2qrh_three_leaf_alternative",
- "objective": "Tests another variant of P2QRH with three leaves arranged in a different tree structure, showing alternative script path spending options",
+ "id": "p2tsh_three_leaf_alternative",
+ "objective": "Tests another variant of P2TSH with three leaves arranged in a different tree structure, showing alternative script path spending options",
"given": {
"internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
"scriptTree": [
@@ -227,11 +227,11 @@
"737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711",
"d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7"
],
- "quantumRoot": "4de1ac7ff4cef472d80e44e1873ff7e4c61cc109f8cf87b49aa5309d3f2b8994"
+ "merkleRoot": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def"
},
"expected": {
- "scriptPubKey": "53204de1ac7ff4cef472d80e44e1873ff7e4c61cc109f8cf87b49aa5309d3f2b8994",
- "bip350Address": "bc1rfhs6cll5em689kqwgnscw0lhunrpesgflr8c0dy655cf60et3x2q2vmn2f",
+ "scriptPubKey": "52202f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
+ "bip350Address": "bc1z9a4jc5uhkmtgegvwpx3lq5tpv68layaf3pvz64wx7paatvejnhhsv52lcv",
"scriptPathControlBlocks": [
"c13cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91",
"c1737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d",
diff --git a/bip-0360/ref-impl/rust/Cargo.lock b/bip-0360/ref-impl/rust/Cargo.lock
index b24393d0e9..b7f2dfd90b 100644
--- a/bip-0360/ref-impl/rust/Cargo.lock
+++ b/bip-0360/ref-impl/rust/Cargo.lock
@@ -292,7 +292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
-name = "p2qrh-ref"
+name = "p2tsh-ref"
version = "0.1.0"
dependencies = [
"anyhow",
diff --git a/bip-0360/ref-impl/rust/Cargo.toml b/bip-0360/ref-impl/rust/Cargo.toml
index 80ba13e004..9f995d9cc5 100644
--- a/bip-0360/ref-impl/rust/Cargo.toml
+++ b/bip-0360/ref-impl/rust/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "p2qrh-ref"
+name = "p2tsh-ref"
version = "0.1.0"
edition = "2024"
@@ -22,7 +22,7 @@ thiserror = "2.0.12"
[patch.crates-io]
-#bitcoin = { git = "https://github.com/jbride/rust-bitcoin.git", branch = "p2qrh" }
+#bitcoin = { git = "https://github.com/jbride/rust-bitcoin.git", branch = "p2tsh" }
# Verify:
# cargo update
diff --git a/bip-0360/ref-impl/rust/README.md b/bip-0360/ref-impl/rust/README.md
index 4b391dcadb..99cbe625b5 100644
--- a/bip-0360/ref-impl/rust/README.md
+++ b/bip-0360/ref-impl/rust/README.md
@@ -1,12 +1,12 @@
-# p2qrh test vectors
+# p2tsh test vectors
This rust project contains the test vectors for BIP-360
## Local Development
-These test vectors are being developed in conjunction with forks of [rust-bitcoin](https://github.com/jbride/rust-bitcoin/tree/p2qrh) and [rust-miniscript](https://github.com/jbride/rust-miniscript/tree/p2qrh) customized with p2qrh functionality.
+These test vectors are being developed in conjunction with forks of [rust-bitcoin](https://github.com/jbride/rust-bitcoin/tree/p2qrh) and [rust-miniscript](https://github.com/jbride/rust-miniscript/tree/p2qrh) customized with p2tsh functionality.
As such, these test vectors assume the presence of these customized forks cloned to your local environment.
1. create soft-link to your `rust-bitcoin` clone:
@@ -29,6 +29,6 @@ As such, these test vectors assume the presence of these customized forks cloned
1. run a specific test:
```
- $ cargo test test_p2qrh_single_leaf_script_tree -- --nocapture
+ $ cargo test test_p2tsh_single_leaf_script_tree -- --nocapture
```
diff --git a/bip-0360/ref-impl/rust/docs/p2qrh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
similarity index 74%
rename from bip-0360/ref-impl/rust/docs/p2qrh-end-to-end.adoc
rename to bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
index 7a5d06ebb6..d92a470963 100644
--- a/bip-0360/ref-impl/rust/docs/p2qrh-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -3,13 +3,13 @@
:toc2:
:linkattrs:
-= P2QRH End-to-End Tutorial
+= P2TSH End-to-End Tutorial
:numbered:
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.
-It is customized to create, fund and spend from a P2QRH UTXO to a P2WPKH address.
+It is customized to create, fund and spend from a P2TSH UTXO to a P2WPKH address.
Execute in Bitcoin Core `regtest` mode.
@@ -17,19 +17,19 @@ Execute in Bitcoin Core `regtest` mode.
=== Bitcoin Core
-The link:https://github.com/jbride/bitcoin/tree/p2qrh[p2qrh branch] of bitcoin core is needed.
+The link:https://github.com/jbride/bitcoin/tree/p2tsh[p2tsh branch] of bitcoin core is needed.
-Build instructions for the `p2qrh` branch are the same as `master` and is documented link:https://github.com/bitcoin/bitcoin/blob/master/doc/build-unix.md[here].
+Build instructions for the `p2tsh` branch are the same as `master` and is documented link:https://github.com/bitcoin/bitcoin/blob/master/doc/build-unix.md[here].
-As such, the following is an example series of steps (on a Fedora 42 host) to compile and run the `p2qrh` branch of bitcoin core:
+As such, the following is an example series of steps (on a Fedora 42 host) to compile and run the `p2tsh` branch of bitcoin core:
. build
+
-----
$ cmake -B build \
-DWITH_ZMQ=ON \
- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
- -DBUILD_BENCH=ON \
+ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
+ -DBUILD_BENCH=ON \
-DBUILD_DAEMON=ON \
-DSANITIZERS=address,undefined
@@ -66,7 +66,7 @@ $ b-reg -named createwallet \
load_on_startup=true
-----
-== Fund P2QRH UTXO
+== Fund P2TSH UTXO
. OPTIONAL: Define number of leaves in tap tree as well as the tap leaf to later use as the unlocking script:
+
@@ -77,17 +77,17 @@ $ export TOTAL_LEAF_COUNT=5 \
+
NOTE: Defaults are 4 leaves with the first leaf (leaf 0 ) as the script to later use as the unlocking script.
-. Generate a P2QRH scripPubKey with multi-leaf taptree:
+. Generate a P2TSH scripPubKey with multi-leaf taptree:
+
-----
$ export BITCOIN_NETWORK=regtest \
- && export BITCOIN_ADDRESS_INFO=$( cargo run --example p2qrh_construction ) \
+ && export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tsh_construction ) \
&& echo $BITCOIN_ADDRESS_INFO | jq -r .
-----
+
-NOTE: In `regtest`, you can expect a P2QRH address that starts with: `bcrt1r` .
+NOTE: In `regtest`, you can expect a P2TSH address that starts with: `bcrt1z` .
+
-NOTE: In the context of P2QRH, the _tree_root_hex_ from the response is in reference to the _quantum_root_ used in this tutorial.
+NOTE: In the context of P2TSH, the _tree_root_hex_ from the response is in reference to the _quantum_root_ used in this tutorial.
. Set some env vars (for use in later steps in this tutorial) based on previous result:
+
@@ -97,7 +97,7 @@ $ export QUANTUM_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tre
&& export LEAF_SCRIPT_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_hex' ) \
&& export CONTROL_BLOCK_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.control_block_hex' ) \
&& export FUNDING_SCRIPT_PUBKEY=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.script_pubkey_hex' ) \
- && export P2QRH_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.bech32m_address' )
+ && export P2TSH_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.bech32m_address' )
-----
. View tapscript used in target leaf of taptree:
@@ -109,27 +109,27 @@ $ b-reg decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
-. fund this P2QRH address with the coinbase reward of a newly generated block:
+. fund this P2TSH address with the coinbase reward of a newly generated block:
+
-----
-$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2QRH_ADDR 5 | jq -r '.[]' ) \
+$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2TSH_ADDR 5 | jq -r '.[]' ) \
&& echo $COINBASE_REWARD_TX_ID
-----
+
NOTE: Sometimes Bitcoin Core may not hit a block (even on regtest). If so, just try the above command again.
-. view summary of all txs that have funded P2QRH address
+. view summary of all txs that have funded P2TSH address
+
-----
-$ export P2QRH_DESC=$( b-reg getdescriptorinfo "addr($P2QRH_ADDR)" | jq -r '.descriptor' ) \
- && echo $P2QRH_DESC \
- && b-reg scantxoutset start '[{"desc": "'''$P2QRH_DESC'''"}]'
+$ export P2TSH_DESC=$( b-reg getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
+ && echo $P2TSH_DESC \
+ && b-reg scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]'
-----
. grab txid of first tx with unspent funds:
+
-----
-$ export FUNDING_TX_ID=$( b-reg scantxoutset start '[{"desc": "'''$P2QRH_DESC'''"}]' | jq -r '.unspents[0].txid' ) \
+$ export FUNDING_TX_ID=$( b-reg scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]' | jq -r '.unspents[0].txid' ) \
&& echo $FUNDING_TX_ID
-----
@@ -139,7 +139,7 @@ $ export FUNDING_TX_ID=$( b-reg scantxoutset start '[{"desc": "'''$P2QRH_DESC'''
$ export FUNDING_UTXO_INDEX=0
-----
-. view details of funding UTXO to the P2QRH address:
+. view details of funding UTXO to the P2TSH address:
+
-----
$ export FUNDING_UTXO=$( b-reg getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout['''$FUNDING_UTXO_INDEX''']' ) \
@@ -148,7 +148,7 @@ $ export FUNDING_UTXO=$( b-reg getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout
+
NOTE: the above only works when Bitcoin Core is started with the following arg: -txindex
-== Spend P2QRH UTXO
+== Spend P2TSH UTXO
. Determine value (in sats) of funding utxo:
@@ -174,10 +174,10 @@ _bad-txns-premature-spend-of-coinbase, tried to spend coinbase at depth 1_
. Referencing the funding tx (via $FUNDING_TX_ID and $FUNDING_UTXO_INDEX), create the spending tx:
+
-----
-$ export SPEND_DETAILS=$( cargo run --example p2qrh_spend )
+$ export SPEND_DETAILS=$( cargo run --example p2tsh_spend )
-$ export RAW_P2QRH_SPEND_TX=$( echo $SPEND_DETAILS | jq -r '.tx_hex' ) \
- && echo "RAW_P2QRH_SPEND_TX = $RAW_P2QRH_SPEND_TX" \
+$ export RAW_P2TSH_SPEND_TX=$( echo $SPEND_DETAILS | jq -r '.tx_hex' ) \
+ && echo "RAW_P2TSH_SPEND_TX = $RAW_P2TSH_SPEND_TX" \
&& export SIG_HASH=$( echo $SPEND_DETAILS | jq -r '.sighash' ) \
&& echo "SIG_HASH = $SIG_HASH" \
&& export SIG_BYTES=$( echo $SPEND_DETAILS | jq -r '.sig_bytes' ) \
@@ -187,34 +187,34 @@ $ export RAW_P2QRH_SPEND_TX=$( echo $SPEND_DETAILS | jq -r '.tx_hex' ) \
. Inspect the spending tx:
+
-----
-$ b-reg decoderawtransaction $RAW_P2QRH_SPEND_TX
+$ b-reg decoderawtransaction $RAW_P2TSH_SPEND_TX
-----
+
Pay particular attention to the `vin.txinwitness` field.
Do the three elements (script input, script and control block) of the witness stack for this script path spend make sense ?
What do you observe as the first byte of the `control block` element ?
-. Test standardness of the spending tx by sending to local mempool of p2qrh enabled Bitcoin Core:
+. Test standardness of the spending tx by sending to local mempool of p2tsh enabled Bitcoin Core:
+
-----
-$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
+$ b-reg testmempoolaccept '["'''$RAW_P2TSH_SPEND_TX'''"]'
-----
. Submit tx:
+
-----
-$ export P2QRH_SPENDING_TX_ID=$( b-reg sendrawtransaction $RAW_P2QRH_SPEND_TX ) \
- && echo $P2QRH_SPENDING_TX_ID
+$ export P2TSH_SPENDING_TX_ID=$( b-reg sendrawtransaction $RAW_P2TSH_SPEND_TX ) \
+ && echo $P2TSH_SPENDING_TX_ID
-----
+
-NOTE: Should return same tx id as was included in $RAW_P2QRH_SPEND_TX
+NOTE: Should return same tx id as was included in $RAW_P2TSH_SPEND_TX
-== Mine P2QRH Spend TX
+== Mine P2TSH Spend TX
. View tx in mempool:
+
-----
-$ b-reg getrawtransaction $P2QRH_SPENDING_TX_ID 1
+$ b-reg getrawtransaction $P2TSH_SPENDING_TX_ID 1
-----
+
NOTE: There will not yet be a field `blockhash` in the response.
@@ -228,7 +228,7 @@ $ b-reg -generate 1
. Obtain `blockhash` field of mined tx:
+
-----
-$ export BLOCK_HASH=$( b-reg getrawtransaction $P2QRH_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
+$ export BLOCK_HASH=$( b-reg getrawtransaction $P2TSH_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
&& echo $BLOCK_HASH
-----
@@ -240,21 +240,21 @@ $ b-reg getblock $BLOCK_HASH | jq -r .tx
== TO-DO
-=== Import P2QRH address into wallet
+=== Import P2TSH address into wallet
NOTE: currently fails with: "message": "Cannot import descriptor without private keys to a wallet with private keys enabled"
-----
$ b-reg -rpcwallet=$W_NAME walletpassphrase $WPASS 120
-$ echo $P2QRH_ADDR
-$ export P2QRH_DESC=$( b-reg getdescriptorinfo "addr($P2QRH_ADDR)" | jq -r '.descriptor' ) \
- && echo $P2QRH_DESC
+$ echo $P2TSH_ADDR
+$ export P2TSH_DESC=$( b-reg getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
+ && echo $P2TSH_DESC
-# Set as non-active address (because can't generate subsequent p2qrh addresses yet)
+# Set as non-active address (because can't generate subsequent p2tsh addresses yet)
$ b-reg importdescriptors '[{
- "desc": "'''$P2QRH_DESC'''",
+ "desc": "'''$P2TSH_DESC'''",
"timestamp": "now",
"active": false,
- "label": "p2qrh"
+ "label": "p2tsh"
}]'
-----
diff --git a/bip-0360/ref-impl/rust/examples/p2tr_construction.rs b/bip-0360/ref-impl/rust/examples/p2tr_construction.rs
index 48118683eb..506fc5591a 100644
--- a/bip-0360/ref-impl/rust/examples/p2tr_construction.rs
+++ b/bip-0360/ref-impl/rust/examples/p2tr_construction.rs
@@ -1,5 +1,5 @@
-use p2qrh_ref::{create_p2tr_utxo, create_p2tr_multi_leaf_taptree};
-use p2qrh_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn};
+use p2tsh_ref::{create_p2tr_utxo, create_p2tr_multi_leaf_taptree};
+use p2tsh_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn};
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
fn main() -> ConstructionReturn {
diff --git a/bip-0360/ref-impl/rust/examples/p2tr_spend.rs b/bip-0360/ref-impl/rust/examples/p2tr_spend.rs
index c23c077993..98d00d2b19 100644
--- a/bip-0360/ref-impl/rust/examples/p2tr_spend.rs
+++ b/bip-0360/ref-impl/rust/examples/p2tr_spend.rs
@@ -1,6 +1,6 @@
-use p2qrh_ref::{ pay_to_p2wpkh_tx , verify_schnorr_signature_via_bytes};
+use p2tsh_ref::{ pay_to_p2wpkh_tx , verify_schnorr_signature_via_bytes};
-use p2qrh_ref::data_structures::SpendDetails;
+use p2tsh_ref::data_structures::SpendDetails;
use std::env;
use log::{info, error};
diff --git a/bip-0360/ref-impl/rust/examples/p2qrh_construction.rs b/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
similarity index 56%
rename from bip-0360/ref-impl/rust/examples/p2qrh_construction.rs
rename to bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
index 267f78703c..b314289767 100644
--- a/bip-0360/ref-impl/rust/examples/p2qrh_construction.rs
+++ b/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
@@ -1,16 +1,16 @@
-use p2qrh_ref::{create_p2qrh_utxo, create_p2qrh_multi_leaf_taptree};
-use p2qrh_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn};
+use p2tsh_ref::{create_p2tsh_utxo, create_p2tsh_multi_leaf_taptree};
+use p2tsh_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn};
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
fn main() -> ConstructionReturn {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
- let taptree_return: TaptreeReturn = create_p2qrh_multi_leaf_taptree();
- let p2qrh_utxo_return: UtxoReturn = create_p2qrh_utxo(taptree_return.clone().tree_root_hex);
+ let taptree_return: TaptreeReturn = create_p2tsh_multi_leaf_taptree();
+ let p2tsh_utxo_return: UtxoReturn = create_p2tsh_utxo(taptree_return.clone().tree_root_hex);
return ConstructionReturn {
taptree_return: taptree_return,
- utxo_return: p2qrh_utxo_return,
+ utxo_return: p2tsh_utxo_return,
};
}
diff --git a/bip-0360/ref-impl/rust/examples/p2qrh_spend.rs b/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
similarity index 95%
rename from bip-0360/ref-impl/rust/examples/p2qrh_spend.rs
rename to bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
index 1a207f1389..20b9510f48 100644
--- a/bip-0360/ref-impl/rust/examples/p2qrh_spend.rs
+++ b/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
@@ -1,6 +1,6 @@
-use p2qrh_ref::{ pay_to_p2wpkh_tx, verify_schnorr_signature_via_bytes };
+use p2tsh_ref::{ pay_to_p2wpkh_tx, verify_schnorr_signature_via_bytes };
-use p2qrh_ref::data_structures::SpendDetails;
+use p2tsh_ref::data_structures::SpendDetails;
use std::env;
use log::{info, error};
@@ -52,7 +52,7 @@ fn main() -> SpendDetails {
error!("CONTROL_BLOCK_HEX environment variable is required but not set");
std::process::exit(1);
});
- info!("P2QRH control block size: {}", control_block_bytes.len());
+ info!("P2TSH control block size: {}", control_block_bytes.len());
let leaf_script_priv_key_bytes: Vec = env::var("LEAF_SCRIPT_PRIV_KEY_HEX")
.map(|s| hex::decode(s).unwrap())
diff --git a/bip-0360/ref-impl/rust/examples/schnorr_example.rs b/bip-0360/ref-impl/rust/examples/schnorr_example.rs
index 3277543a13..5a4c04a3ba 100644
--- a/bip-0360/ref-impl/rust/examples/schnorr_example.rs
+++ b/bip-0360/ref-impl/rust/examples/schnorr_example.rs
@@ -5,7 +5,7 @@ use bitcoin::key::{Secp256k1};
use bitcoin::hashes::{sha256::Hash, Hash as HashTrait};
use bitcoin::secp256k1::{Message};
-use p2qrh_ref::{ acquire_schnorr_keypair, verify_schnorr_signature };
+use p2tsh_ref::{ acquire_schnorr_keypair, verify_schnorr_signature };
/* Secp256k1 implements the Signing trait when it's initialized in signing mode.
It's important to note that Secp256k1 has different capabilities depending on how it's constructed:
@@ -38,4 +38,4 @@ fn main() {
let schnorr_valid_aux_rand = verify_schnorr_signature(signature_aux_rand, message, pubkey);
info!("schnorr_valid_aux_rand: {}", schnorr_valid_aux_rand);
-}
\ No newline at end of file
+}
diff --git a/bip-0360/ref-impl/rust/src/data_structures.rs b/bip-0360/ref-impl/rust/src/data_structures.rs
index 64cf0f1685..fbfa83e7d2 100644
--- a/bip-0360/ref-impl/rust/src/data_structures.rs
+++ b/bip-0360/ref-impl/rust/src/data_structures.rs
@@ -67,8 +67,8 @@ pub struct TestVectorIntermediary {
#[serde(default)]
#[serde(rename = "leafHashes")]
pub leaf_hashes: Vec,
- #[serde(rename = "quantumRoot")]
- pub quantum_root: Option
+ #[serde(rename = "merkleRoot")]
+ pub merkle_root: Option
}
diff --git a/bip-0360/ref-impl/rust/src/error.rs b/bip-0360/ref-impl/rust/src/error.rs
index c8dd8d71c4..100a48b7da 100644
--- a/bip-0360/ref-impl/rust/src/error.rs
+++ b/bip-0360/ref-impl/rust/src/error.rs
@@ -1,8 +1,8 @@
use thiserror::Error;
#[derive(Error, Debug)]
-pub enum P2QRHError {
- #[error("P2QRH requires a script tree with at least one leaf")]
+pub enum P2TSHError {
+ #[error("P2TSH requires a script tree with at least one leaf")]
MissingScriptTreeLeaf,
// We can add more specific error variants here as needed
diff --git a/bip-0360/ref-impl/rust/src/lib.rs b/bip-0360/ref-impl/rust/src/lib.rs
index c53c10a21e..2a3fcf8e6c 100644
--- a/bip-0360/ref-impl/rust/src/lib.rs
+++ b/bip-0360/ref-impl/rust/src/lib.rs
@@ -17,7 +17,7 @@ use bitcoin::{ Amount, TxOut, WPubkeyHash,
transaction::{Transaction, Sequence}
};
-use bitcoin::p2qrh::{P2qrhScriptBuf, P2qrhBuilder, P2qrhSpendInfo, P2qrhControlBlock, QuantumRootHash, P2QRH_LEAF_VERSION};
+use bitcoin::p2tsh::{P2tshScriptBuf, P2tshBuilder, P2tshSpendInfo, P2tshControlBlock, P2TSH_LEAF_VERSION};
use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn};
@@ -80,28 +80,28 @@ fn create_huffman_tree() -> (Vec<(u32, ScriptBuf)>, Option<(SecretKey, XOnlyPubl
return (huffman_entries, keypair_of_interest, script_buf_of_interest);
}
-pub fn create_p2qrh_multi_leaf_taptree() -> TaptreeReturn {
+pub fn create_p2tsh_multi_leaf_taptree() -> TaptreeReturn {
let (huffman_entries, keypair_of_interest, script_buf_of_interest) = create_huffman_tree();
- let p2qrh_builder: P2qrhBuilder = P2qrhBuilder::with_huffman_tree(huffman_entries).unwrap();
+ let p2tsh_builder: P2tshBuilder = P2tshBuilder::with_huffman_tree(huffman_entries).unwrap();
- let p2qrh_spend_info: P2qrhSpendInfo = p2qrh_builder.clone().finalize().unwrap();
- let quantum_root: QuantumRootHash = p2qrh_spend_info.quantum_root.unwrap();
+ let p2tsh_spend_info: P2tshSpendInfo = p2tsh_builder.clone().finalize().unwrap();
+ let quantum_root:TapNodeHash = p2tsh_spend_info.merkle_root.unwrap();
info!("keypair_of_interest: \n\tsecret_bytes: {} \n\tpubkey: {}",
hex::encode(keypair_of_interest.unwrap().0.secret_bytes()), // secret_bytes returns big endian
hex::encode(keypair_of_interest.unwrap().1.serialize()), // serialize returns little endian
);
- let tap_tree: TapTree = p2qrh_builder.clone().into_inner().try_into_taptree().unwrap();
+ let tap_tree: TapTree = p2tsh_builder.clone().into_inner().try_into_taptree().unwrap();
let mut script_leaves: ScriptLeaves = tap_tree.script_leaves();
let script_leaf = script_leaves
.find(|leaf| leaf.script() == script_buf_of_interest.as_ref().unwrap().as_script())
.expect("Script leaf not found");
- let merkle_root_node_info: NodeInfo = p2qrh_builder.clone().into_inner().try_into_node_info().unwrap();
+ let merkle_root_node_info: NodeInfo = p2tsh_builder.clone().into_inner().try_into_node_info().unwrap();
let merkle_root: TapNodeHash = merkle_root_node_info.node_hash();
- let leaf_hash: TapLeafHash = TapLeafHash::from_script(script_leaf.script(), LeafVersion::from_consensus(P2QRH_LEAF_VERSION).unwrap());
+ let leaf_hash: TapLeafHash = TapLeafHash::from_script(script_leaf.script(), LeafVersion::from_consensus(P2TSH_LEAF_VERSION).unwrap());
// Convert leaf hash to big-endian for display (like Bitcoin Core)
let mut leaf_hash_bytes = leaf_hash.as_raw_hash().to_byte_array().to_vec();
@@ -117,12 +117,12 @@ pub fn create_p2qrh_multi_leaf_taptree() -> TaptreeReturn {
info!("Leaf script: {}, merkle branch: {:?}", leaf_script, merkle_branch);
- let control_block: P2qrhControlBlock = P2qrhControlBlock{
+ let control_block: P2tshControlBlock = P2tshControlBlock{
merkle_branch: merkle_branch.clone(),
};
- // Not a requirement but useful to demonstrate what Bitcoin Core does as the verifier when spending from a p2qrh UTXO
- control_block.verify_script_in_quantum_root_path(leaf_script, quantum_root);
+ // Not a requirement but useful to demonstrate what Bitcoin Core does as the verifier when spending from a p2tsh UTXO
+ control_block.verify_script_in_merkle_root_path(leaf_script, merkle_root);
let control_block_hex: String = hex::encode(control_block.serialize());
@@ -188,15 +188,15 @@ pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> Taptr
};
}
-pub fn create_p2qrh_utxo(quantum_root_hex: String) -> UtxoReturn {
+pub fn create_p2tsh_utxo(quantum_root_hex: String) -> UtxoReturn {
let quantum_root_bytes= hex::decode(quantum_root_hex.clone()).unwrap();
let quantum_root: TapNodeHash = TapNodeHash::from_byte_array(quantum_root_bytes.try_into().unwrap());
/* commit (in scriptPubKey) to the merkle root of all the script path leaves. ie:
- This output key is what gets committed to in the final P2QRH address (ie: scriptPubKey)
+ This output key is what gets committed to in the final P2TSH address (ie: scriptPubKey)
*/
- let script_buf: P2qrhScriptBuf = P2qrhScriptBuf::new_p2qrh(quantum_root);
+ let script_buf: P2tshScriptBuf = P2tshScriptBuf::new_p2tsh(quantum_root);
let script: &Script = script_buf.as_script();
let script_pubkey = script.to_hex_string();
@@ -216,8 +216,8 @@ pub fn create_p2qrh_utxo(quantum_root_hex: String) -> UtxoReturn {
}
// 4) derive bech32m address and verify against test vector
- // p2qrh address is comprised of network HRP + WitnessProgram (version + program)
- let bech32m_address = Address::p2qrh(Some(quantum_root), bitcoin_network);
+ // p2tsh address is comprised of network HRP + WitnessProgram (version + program)
+ let bech32m_address = Address::p2tsh(Some(quantum_root), bitcoin_network);
return UtxoReturn {
script_pubkey_hex: script_pubkey,
@@ -227,7 +227,7 @@ pub fn create_p2qrh_utxo(quantum_root_hex: String) -> UtxoReturn {
}
-// Given script path p2tr or p2qrh UTXO details, spend to p2wpkh
+// Given script path p2tr or p2tsh UTXO details, spend to p2wpkh
pub fn pay_to_p2wpkh_tx(
funding_tx_id_bytes: Vec,
funding_utxo_index: u32,
@@ -368,7 +368,7 @@ pub fn create_p2tr_utxo(merkle_root_hex: String, internal_pubkey_hex: String) ->
}
// 4) derive bech32m address and verify against test vector
- // p2qrh address is comprised of network HRP + WitnessProgram (version + program)
+ // p2tsh address is comprised of network HRP + WitnessProgram (version + program)
let bech32m_address = Address::p2tr(
&SECP,
internal_xonly_pubkey,
diff --git a/bip-0360/ref-impl/rust/tests/p2qrh_construction.rs b/bip-0360/ref-impl/rust/tests/p2tsh_construction.rs
similarity index 69%
rename from bip-0360/ref-impl/rust/tests/p2qrh_construction.rs
rename to bip-0360/ref-impl/rust/tests/p2tsh_construction.rs
index 2a93196015..66431d9a36 100644
--- a/bip-0360/ref-impl/rust/tests/p2qrh_construction.rs
+++ b/bip-0360/ref-impl/rust/tests/p2tsh_construction.rs
@@ -1,119 +1,108 @@
use std::collections::HashSet;
use bitcoin::{Network, ScriptBuf};
-use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch};
-use bitcoin::p2qrh::{P2qrhBuilder, P2qrhControlBlock, P2qrhSpendInfo, QuantumRootHash};
+use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch, TapNodeHash};
+use bitcoin::p2tsh::{P2tshBuilder, P2tshControlBlock, P2tshSpendInfo};
use bitcoin::hashes::Hash;
use hex;
use log::debug;
use once_cell::sync::Lazy;
-use p2qrh_ref::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn};
-use p2qrh_ref::error::P2QRHError;
-use p2qrh_ref::{create_p2qrh_utxo, tagged_hash};
+use p2tsh_ref::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn};
+use p2tsh_ref::error::P2TSHError;
+use p2tsh_ref::{create_p2tsh_utxo, tagged_hash};
// This file contains tests that execute against the BIP360 script-path-only test vectors.
static TEST_VECTORS: Lazy = Lazy::new(|| {
- let bip360_test_vectors = include_str!("../../common/tests/data/p2qrh_construction.json");
+ let bip360_test_vectors = include_str!("../../common/tests/data/p2tsh_construction.json");
let test_vectors: TestVectors = serde_json::from_str(bip360_test_vectors).unwrap();
assert_eq!(test_vectors.version, 1);
test_vectors
});
-static P2QRH_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST: &str = "p2qrh_missing_leaf_script_tree_error";
-static P2QRH_SINGLE_LEAF_SCRIPT_TREE_TEST: &str = "p2qrh_single_leaf_script_tree";
-static P2QRH_DIFFERENT_VERSION_LEAVES_TEST: &str = "p2qrh_different_version_leaves";
-static P2QRH_TWO_LEAF_SAME_VERSION_TEST: &str = "p2qrh_two_leaf_same_version";
-static P2QRH_THREE_LEAF_COMPLEX_TEST: &str = "p2qrh_three_leaf_complex";
-static P2QRH_THREE_LEAF_ALTERNATIVE_TEST: &str = "p2qrh_three_leaf_alternative";
-static P2QRH_SIMPLE_LIGHTNING_CONTRACT_TEST: &str = "p2qrh_simple_lightning_contract";
-
-#[test]
-fn test_p2qrh_quantum_root() {
-
- let _ = env_logger::try_init();
-
- let taproot_merkle_root: String = "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b".to_string();
- let expected_quantum_root: String = "27e8a6af1f05d3dbfebfc4073a8391cf8db28746767c2b34d606900ad721127b".to_string();
- let quantum_root = tagged_hash("QuantumRoot", &hex::decode(&taproot_merkle_root).unwrap());
- assert_eq!(quantum_root.to_string(), expected_quantum_root);
-}
+static P2TSH_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST: &str = "p2tsh_missing_leaf_script_tree_error";
+static P2TSH_SINGLE_LEAF_SCRIPT_TREE_TEST: &str = "p2tsh_single_leaf_script_tree";
+static P2TSH_DIFFERENT_VERSION_LEAVES_TEST: &str = "p2tsh_different_version_leaves";
+static P2TSH_TWO_LEAF_SAME_VERSION_TEST: &str = "p2tsh_two_leaf_same_version";
+static P2TSH_THREE_LEAF_COMPLEX_TEST: &str = "p2tsh_three_leaf_complex";
+static P2TSH_THREE_LEAF_ALTERNATIVE_TEST: &str = "p2tsh_three_leaf_alternative";
+static P2TSH_SIMPLE_LIGHTNING_CONTRACT_TEST: &str = "p2tsh_simple_lightning_contract";
// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple
#[test]
-fn test_p2qrh_missing_leaf_script_tree_error() {
+fn test_p2tsh_missing_leaf_script_tree_error() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
let test_vectors = &*TEST_VECTORS;
- let test_vector = test_vectors.test_vector_map.get(P2QRH_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST).unwrap();
- let test_result: anyhow::Result<()> = process_test_vector_p2qrh(test_vector);
- assert!(matches!(test_result.unwrap_err().downcast_ref::(),
- Some(P2QRHError::MissingScriptTreeLeaf)));
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST).unwrap();
+ let test_result: anyhow::Result<()> = process_test_vector_p2tsh(test_vector);
+ assert!(matches!(test_result.unwrap_err().downcast_ref::(),
+ Some(P2TSHError::MissingScriptTreeLeaf)));
}
// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple
#[test]
-fn test_p2qrh_single_leaf_script_tree() {
+fn test_p2tsh_single_leaf_script_tree() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
let test_vectors = &*TEST_VECTORS;
- let test_vector = test_vectors.test_vector_map.get(P2QRH_SINGLE_LEAF_SCRIPT_TREE_TEST).unwrap();
- process_test_vector_p2qrh(test_vector).unwrap();
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_SINGLE_LEAF_SCRIPT_TREE_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
}
#[test]
-fn test_p2qrh_different_version_leaves() {
+fn test_p2tsh_different_version_leaves() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
let test_vectors = &*TEST_VECTORS;
- let test_vector = test_vectors.test_vector_map.get(P2QRH_DIFFERENT_VERSION_LEAVES_TEST).unwrap();
- process_test_vector_p2qrh(test_vector).unwrap();
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_DIFFERENT_VERSION_LEAVES_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
}
#[test]
-fn test_p2qrh_simple_lightning_contract() {
+fn test_p2tsh_simple_lightning_contract() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
let test_vectors = &*TEST_VECTORS;
- let test_vector = test_vectors.test_vector_map.get(P2QRH_SIMPLE_LIGHTNING_CONTRACT_TEST).unwrap();
- process_test_vector_p2qrh(test_vector).unwrap();
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_SIMPLE_LIGHTNING_CONTRACT_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
}
#[test]
-fn test_p2qrh_two_leaf_same_version() {
+fn test_p2tsh_two_leaf_same_version() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
let test_vectors = &*TEST_VECTORS;
- let test_vector = test_vectors.test_vector_map.get(P2QRH_TWO_LEAF_SAME_VERSION_TEST).unwrap();
- process_test_vector_p2qrh(test_vector).unwrap();
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_TWO_LEAF_SAME_VERSION_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
}
#[test]
-fn test_p2qrh_three_leaf_complex() {
+fn test_p2tsh_three_leaf_complex() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
let test_vectors = &*TEST_VECTORS;
- let test_vector = test_vectors.test_vector_map.get(P2QRH_THREE_LEAF_COMPLEX_TEST).unwrap();
- process_test_vector_p2qrh(test_vector).unwrap();
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_THREE_LEAF_COMPLEX_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
}
#[test]
-fn test_p2qrh_three_leaf_alternative() {
+fn test_p2tsh_three_leaf_alternative() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
let test_vectors = &*TEST_VECTORS;
- let test_vector = test_vectors.test_vector_map.get(P2QRH_THREE_LEAF_ALTERNATIVE_TEST).unwrap();
- process_test_vector_p2qrh(test_vector).unwrap();
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_THREE_LEAF_ALTERNATIVE_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
}
-fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
+fn process_test_vector_p2tsh(test_vector: &TestVector) -> anyhow::Result<()> {
let tv_script_tree: Option<&TVScriptTree> = test_vector.given.script_tree.as_ref();
@@ -123,11 +112,11 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
// TaprootBuilder expects the addition of each leaf script with its associated depth
// It then constructs the binary tree in DFS order, sorting siblings lexicographically & combining them via BIP341's tapbranch_hash
// Use of TaprootBuilder avoids user error in constructing branches manually and ensures Merkle tree correctness and determinism
- let mut p2qrh_builder: P2qrhBuilder = P2qrhBuilder::new();
+ let mut p2tsh_builder: P2tshBuilder = P2tshBuilder::new();
let mut control_block_data: Vec<(ScriptBuf, LeafVersion)> = Vec::new();
- // 1) traverse test vector script tree and add leaves to P2QRH builder
+ // 1) traverse test vector script tree and add leaves to P2TSH builder
if let Some(script_tree) = tv_script_tree {
script_tree.traverse_with_right_subtree_first(0, Direction::Root,&mut |node, depth, direction| {
@@ -149,7 +138,7 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
tv_leaf_count, depth, modified_depth, direction, tv_leaf.script);
// NOTE: Some of the the test vectors in this project specify leaves with non-standard versions (ie: 250 / 0xfa)
- p2qrh_builder = p2qrh_builder.clone().add_leaf_with_ver(depth, tv_leaf_script_buf.clone(), tv_leaf_version)
+ p2tsh_builder = p2tsh_builder.clone().add_leaf_with_ver(depth, tv_leaf_script_buf.clone(), tv_leaf_version)
.unwrap_or_else(|e| {
panic!("Failed to add leaf: {:?}", e);
});
@@ -163,31 +152,31 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
}
});
}else {
- return Err(P2QRHError::MissingScriptTreeLeaf.into());
+ return Err(P2TSHError::MissingScriptTreeLeaf.into());
}
- let spend_info: P2qrhSpendInfo = p2qrh_builder.clone()
+ let spend_info: P2tshSpendInfo = p2tsh_builder.clone()
.finalize()
.unwrap_or_else(|e| {
panic!("finalize failed: {:?}", e);
});
- let derived_quantum_root: QuantumRootHash = spend_info.quantum_root.unwrap();
+ let derived_merkle_root: TapNodeHash = spend_info.merkle_root.unwrap();
- // 2) verify derived quantum root against test vector
- let test_vector_quantum_root = test_vector.intermediary.quantum_root.as_ref().unwrap();
+ // 2) verify derived merkle root against test vector
+ let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap();
assert_eq!(
- derived_quantum_root.to_string(),
- *test_vector_quantum_root,
- "Quantum root mismatch"
+ derived_merkle_root.to_string(),
+ *test_vector_merkle_root,
+ "Merkle root mismatch"
);
- debug!("just passed quantum root validation: {}", test_vector_quantum_root);
+ debug!("just passed merkle root validation: {}", test_vector_merkle_root);
let test_vector_leaf_hashes_vec: Vec = test_vector.intermediary.leaf_hashes.clone();
let test_vector_leaf_hash_set: HashSet = test_vector_leaf_hashes_vec.iter().cloned().collect();
let test_vector_control_blocks_vec = &test_vector.expected.script_path_control_blocks;
let test_vector_control_blocks_set: HashSet = test_vector_control_blocks_vec.as_ref().unwrap().iter().cloned().collect();
- let tap_tree: TapTree = p2qrh_builder.clone().into_inner().try_into_taptree().unwrap();
+ let tap_tree: TapTree = p2tsh_builder.clone().into_inner().try_into_taptree().unwrap();
let script_leaves: ScriptLeaves = tap_tree.script_leaves();
// TO-DO: Investigate why the ordering of script leaves seems to be reverse of test vectors.
@@ -213,7 +202,7 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
// There is no consensus limit on n, but large Merkle trees increase the witness size, impacting block weight.
// NOTE: Control blocks could have also been obtained from spend_info.control_block(..) using the data in control_block_data
debug!("merkle_branch nodes: {:?}", merkle_branch);
- let derived_control_block: P2qrhControlBlock = P2qrhControlBlock{
+ let derived_control_block: P2tshControlBlock = P2tshControlBlock{
merkle_branch: merkle_branch.clone(),
};
let serialized_control_block = derived_control_block.serialize();
@@ -231,19 +220,19 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
}
- let p2qrh_utxo_return: UtxoReturn = create_p2qrh_utxo(derived_quantum_root.to_string());
+ let p2tsh_utxo_return: UtxoReturn = create_p2tsh_utxo(derived_merkle_root.to_string());
assert_eq!(
- p2qrh_utxo_return.script_pubkey_hex,
+ p2tsh_utxo_return.script_pubkey_hex,
*test_vector.expected.script_pubkey.as_ref().unwrap(),
"Script pubkey mismatch"
);
- debug!("just passed script_pubkey validation. script_pubkey = {}", p2qrh_utxo_return.script_pubkey_hex);
+ debug!("just passed script_pubkey validation. script_pubkey = {}", p2tsh_utxo_return.script_pubkey_hex);
- let bech32m_address: String = p2qrh_utxo_return.bech32m_address;
- debug!("derived bech32m address for bitcoin_network: {} : {}", p2qrh_utxo_return.bitcoin_network, bech32m_address);
+ let bech32m_address: String = p2tsh_utxo_return.bech32m_address;
+ debug!("derived bech32m address for bitcoin_network: {} : {}", p2tsh_utxo_return.bitcoin_network, bech32m_address);
- if p2qrh_utxo_return.bitcoin_network == Network::Bitcoin {
+ if p2tsh_utxo_return.bitcoin_network == Network::Bitcoin {
assert_eq!(bech32m_address, *test_vector.expected.bip350_address.as_ref().unwrap(), "Bech32m address mismatch.");
}
diff --git a/bip-0360/ref-impl/rust/tests/p2qrh_spend.rs b/bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
similarity index 92%
rename from bip-0360/ref-impl/rust/tests/p2qrh_spend.rs
rename to bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
index 31c02e4b35..972c0ddbf3 100644
--- a/bip-0360/ref-impl/rust/tests/p2qrh_spend.rs
+++ b/bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
@@ -1,9 +1,9 @@
use log::info;
use bitcoin::blockdata::witness::Witness;
-use p2qrh_ref::{ pay_to_p2wpkh_tx, serialize_script };
+use p2tsh_ref::{ pay_to_p2wpkh_tx, serialize_script };
-use p2qrh_ref::data_structures::SpendDetails;
+use p2tsh_ref::data_structures::SpendDetails;
/* The rust-bitcoin crate does not provide a single high-level API that builds the full Taproot script-path witness stack for you.
It does expose all the necessary types and primitives to build it manually and correctly.
@@ -39,7 +39,7 @@ fn test_script_path_spend_simple() {
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
-// Spends from a p2qrh UTXO to a p2wpk UTXO
+// Spends from a p2tsh UTXO to a p2wpk UTXO
#[test]
fn test_script_path_spend_signatures() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
@@ -57,7 +57,7 @@ fn test_script_path_spend_signatures() {
hex::decode("206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac").unwrap();
// Modified from learnmeabitcoin example
- // 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.
+ // Changed from c0 to c1 control byte to reflect p2tsh specification: The parity bit of the control byte is always 1 since P2TSH does not have a key-spend path.
let input_control_block_bytes: Vec =
hex::decode("c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329").unwrap();
@@ -76,7 +76,7 @@ fn test_script_path_spend_signatures() {
let test_signature_bytes: Vec = hex::decode("01769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f01").unwrap();
// Modified from learnmeabitcoin example
- // 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.
+ // Changed from c0 to c1 control byte to reflect p2tsh specification: The parity bit of the control byte is always 1 since P2TSH does not have a key-spend path.
let test_witness_bytes: Vec = hex::decode("01769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f01206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0acc1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329").unwrap();
let result: SpendDetails = pay_to_p2wpkh_tx(funding_tx_id_bytes,
From 55b065240b20d8c3efcc5198be125ec7ce5c93ce Mon Sep 17 00:00:00 2001
From: jbride
Date: Thu, 28 Aug 2025 21:53:16 -0600
Subject: [PATCH 02/16] bip360: updating p2tsh end-to-end doc for signet
---
.../ref-impl/rust/docs/p2tsh-end-to-end.adoc | 84 +++++++++++++------
1 file changed, 60 insertions(+), 24 deletions(-)
diff --git a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
index d92a470963..40998cf2c0 100644
--- a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -23,6 +23,12 @@ Build instructions for the `p2tsh` branch are the same as `master` and is docume
As such, the following is an example series of steps (on a Fedora 42 host) to compile and run the `p2tsh` branch of bitcoin core:
+. Set BITCOIN_SOURCE_DIR
++
+-----
+$ export BITCOIN_SOURCE_DIR=/path/to/root/dir/of/cloned/bitcoin/source
+-----
+
. build
+
-----
@@ -44,22 +50,35 @@ $ ./build/bin/bitcoind -daemon=0 -regtest=1 -txindex
=== Shell Environment
-. *b-reg* command line alias:
+. *b-cli* command line alias:
++
+Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node. ie:
+
-Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node running in `regtest` mode.
+-----
+$ alias b-cli=bitcoin-cli -conf=$HOME/configs/bitcoin.conf.signet
+-----
+
. *jq*: ensure json parsing utility is installed and available via your $PATH.
. *awk* : standard utility for all Linux distros (often packaged as `gawk`).
=== Bitcoin Core Wallet
This tutorial assumes that a bitcoin core wallet is available.
+
+. Set an environment variable specific to your Bitcoin network environment (regtest, signet, etc)
++
+-----
+$ export BITCOIN_NETWORK=regtest
+-----
+
For example, the following would be sufficient:
-----
-$ export W_NAME=regtest \
- && export WPASS=regtest
-$ b-reg -named createwallet \
+$ export W_NAME=$BITCOIN_NETWORK \
+ && export WPASS=$BITCOIN_NETWORK
+
+$ b-cli -named createwallet \
wallet_name=$W_NAME \
descriptors=true \
passphrase="$WPASS" \
@@ -80,8 +99,7 @@ NOTE: Defaults are 4 leaves with the first leaf (leaf 0 ) as the script to later
. Generate a P2TSH scripPubKey with multi-leaf taptree:
+
-----
-$ export BITCOIN_NETWORK=regtest \
- && export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tsh_construction ) \
+$ export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tsh_construction ) \
&& echo $BITCOIN_ADDRESS_INFO | jq -r .
-----
+
@@ -103,7 +121,7 @@ $ export QUANTUM_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tre
. View tapscript used in target leaf of taptree:
+
-----
-$ b-reg decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
+$ b-cli decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
-----
+
NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
@@ -111,25 +129,43 @@ NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
. fund this P2TSH address with the coinbase reward of a newly generated block:
+
+Choose from one of the following networks:
+
+
+.. Regtest
++
+If on `regtest` network, then execute the following:
++
-----
-$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2TSH_ADDR 5 | jq -r '.[]' ) \
+$ export COINBASE_REWARD_TX_ID=$( b-cli -named generatetoaddress 1 $P2TSH_ADDR 5 | jq -r '.[]' ) \
&& echo $COINBASE_REWARD_TX_ID
-----
+
NOTE: Sometimes Bitcoin Core may not hit a block (even on regtest). If so, just try the above command again.
+.. Signet
++
+If on `signet` network, then execute the following:
++
+-----
+$BITCOIN_SOURCE_DIR/contrib/signet/miner --cli "bitcoin-cli -conf=$HOME/configs/bitcoin.conf.signet" generate \
+ --address $P2TSH_ADDR \
+ --grind-cmd "$BITCOIN_SOURCE_DIR/build/bin/bitcoin-util grind" \
+ --min-nbits --set-block-time $(date +%s)
+-----
+
. view summary of all txs that have funded P2TSH address
+
-----
-$ export P2TSH_DESC=$( b-reg getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
+$ export P2TSH_DESC=$( b-cli getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
&& echo $P2TSH_DESC \
- && b-reg scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]'
+ && b-cli scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]'
-----
. grab txid of first tx with unspent funds:
+
-----
-$ export FUNDING_TX_ID=$( b-reg scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]' | jq -r '.unspents[0].txid' ) \
+$ export FUNDING_TX_ID=$( b-cli scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]' | jq -r '.unspents[0].txid' ) \
&& echo $FUNDING_TX_ID
-----
@@ -142,7 +178,7 @@ $ export FUNDING_UTXO_INDEX=0
. view details of funding UTXO to the P2TSH address:
+
-----
-$ export FUNDING_UTXO=$( b-reg getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout['''$FUNDING_UTXO_INDEX''']' ) \
+$ export FUNDING_UTXO=$( b-cli getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout['''$FUNDING_UTXO_INDEX''']' ) \
&& echo $FUNDING_UTXO | jq -r .
-----
+
@@ -163,7 +199,7 @@ $ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{
This is necessary if you have only previously generated less than 100 blocks.
+
-----
-$ b-reg -generate 110
+$ b-cli -generate 110
-----
+
Otherwise, you may see an error from bitcoin core such as the following when attempting to spend:
@@ -187,7 +223,7 @@ $ export RAW_P2TSH_SPEND_TX=$( echo $SPEND_DETAILS | jq -r '.tx_hex' ) \
. Inspect the spending tx:
+
-----
-$ b-reg decoderawtransaction $RAW_P2TSH_SPEND_TX
+$ b-cli decoderawtransaction $RAW_P2TSH_SPEND_TX
-----
+
Pay particular attention to the `vin.txinwitness` field.
@@ -197,13 +233,13 @@ What do you observe as the first byte of the `control block` element ?
. Test standardness of the spending tx by sending to local mempool of p2tsh enabled Bitcoin Core:
+
-----
-$ b-reg testmempoolaccept '["'''$RAW_P2TSH_SPEND_TX'''"]'
+$ b-cli testmempoolaccept '["'''$RAW_P2TSH_SPEND_TX'''"]'
-----
. Submit tx:
+
-----
-$ export P2TSH_SPENDING_TX_ID=$( b-reg sendrawtransaction $RAW_P2TSH_SPEND_TX ) \
+$ export P2TSH_SPENDING_TX_ID=$( b-cli sendrawtransaction $RAW_P2TSH_SPEND_TX ) \
&& echo $P2TSH_SPENDING_TX_ID
-----
+
@@ -214,7 +250,7 @@ NOTE: Should return same tx id as was included in $RAW_P2TSH_SPEND_TX
. View tx in mempool:
+
-----
-$ b-reg getrawtransaction $P2TSH_SPENDING_TX_ID 1
+$ b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1
-----
+
NOTE: There will not yet be a field `blockhash` in the response.
@@ -222,20 +258,20 @@ NOTE: There will not yet be a field `blockhash` in the response.
. Mine 1 block:
+
-----
-$ b-reg -generate 1
+$ b-cli -generate 1
-----
. Obtain `blockhash` field of mined tx:
+
-----
-$ export BLOCK_HASH=$( b-reg getrawtransaction $P2TSH_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
+$ export BLOCK_HASH=$( b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
&& echo $BLOCK_HASH
-----
. View tx in block:
+
-----
-$ b-reg getblock $BLOCK_HASH | jq -r .tx
+$ b-cli getblock $BLOCK_HASH | jq -r .tx
-----
== TO-DO
@@ -245,13 +281,13 @@ $ b-reg getblock $BLOCK_HASH | jq -r .tx
NOTE: currently fails with: "message": "Cannot import descriptor without private keys to a wallet with private keys enabled"
-----
-$ b-reg -rpcwallet=$W_NAME walletpassphrase $WPASS 120
+$ b-cli -rpcwallet=$W_NAME walletpassphrase $WPASS 120
$ echo $P2TSH_ADDR
-$ export P2TSH_DESC=$( b-reg getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
+$ export P2TSH_DESC=$( b-cli getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
&& echo $P2TSH_DESC
# Set as non-active address (because can't generate subsequent p2tsh addresses yet)
-$ b-reg importdescriptors '[{
+$ b-cli importdescriptors '[{
"desc": "'''$P2TSH_DESC'''",
"timestamp": "now",
"active": false,
From 308e3ccea029e7e2fceb2bb7da0401269481824c Mon Sep 17 00:00:00 2001
From: jbride
Date: Wed, 3 Sep 2025 07:50:58 -0600
Subject: [PATCH 03/16] bip360: more updates to p2tsh end-to-end doc for
signet
---
.../ref-impl/rust/docs/p2tsh-end-to-end.adoc | 64 +++++++++++++++----
1 file changed, 51 insertions(+), 13 deletions(-)
diff --git a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
index 40998cf2c0..1f7f6482c4 100644
--- a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -11,7 +11,6 @@ This tutorial is inspired from the link:https://learnmeabitcoin.com/technical/up
It is customized to create, fund and spend from a P2TSH UTXO to a P2WPKH address.
-Execute in Bitcoin Core `regtest` mode.
== Pre-reqs
@@ -42,17 +41,30 @@ $ cmake -B build \
$ cmake --build build -j$(nproc)
-----
-. run in `regtest` mode
+. run in either `regtest` or `signet` mode:
+
+.. regtest:
+
-----
-$ ./build/bin/bitcoind -daemon=0 -regtest=1 -txindex
+$ ./build/bin/bitcoind -daemon=0 -regtest=1 -txindex -prune=0
-----
+.. signet:
++
+-----
+$ ./build/bin/bitcoind -daemon=0 -signet=1 -txindex -prune=0
+-----
++
+NOTE: If running in `signet`, your bitcoin core will need to be configured with the `signetchallenge` property.
+link:https://edil.com.br/blog/creating-a-custom-bitcoin-signet[This tutorial] provides a nice overview of the topic.
+
=== Shell Environment
. *b-cli* command line alias:
+
-Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node. ie:
+Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node.
++
+ie: for signet
+
-----
$ alias b-cli=bitcoin-cli -conf=$HOME/configs/bitcoin.conf.signet
@@ -75,14 +87,14 @@ For example, the following would be sufficient:
-----
-$ export W_NAME=$BITCOIN_NETWORK \
- && export WPASS=$BITCOIN_NETWORK
+$ export W_NAME=anduro
$ b-cli -named createwallet \
- wallet_name=$W_NAME \
+ wallet_name=$W_NAME \
descriptors=true \
- passphrase="$WPASS" \
- load_on_startup=true
+ load_on_startup=true \
+ blank=true
+
-----
== Fund P2TSH UTXO
@@ -148,10 +160,11 @@ NOTE: Sometimes Bitcoin Core may not hit a block (even on regtest). If so, jus
If on `signet` network, then execute the following:
+
-----
-$BITCOIN_SOURCE_DIR/contrib/signet/miner --cli "bitcoin-cli -conf=$HOME/configs/bitcoin.conf.signet" generate \
+$ $BITCOIN_SOURCE_DIR/contrib/signet/miner --cli "bitcoin-cli -conf=$HOME/anduro-360/configs/bitcoin.conf.signet" generate \
--address $P2TSH_ADDR \
--grind-cmd "$BITCOIN_SOURCE_DIR/build/bin/bitcoin-util grind" \
- --min-nbits --set-block-time $(date +%s)
+ --min-nbits --set-block-time $(date +%s) \
+ --poolid "MARA Pool"
-----
. view summary of all txs that have funded P2TSH address
@@ -198,13 +211,21 @@ $ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{
+
This is necessary if you have only previously generated less than 100 blocks.
+
+Otherwise, you may see an error from bitcoin core such as the following when attempting to spend:
++
+_bad-txns-premature-spend-of-coinbase, tried to spend coinbase at depth 1_
+
+.. regtest
++
-----
$ b-cli -generate 110
-----
+
+.. signet
+
-Otherwise, you may see an error from bitcoin core such as the following when attempting to spend:
+This will involve having the signet miner generate about 110 blocks .... which can take about 10 minutes.
+
-_bad-txns-premature-spend-of-coinbase, tried to spend coinbase at depth 1_
+The `common/utils` directory of this project provides a script called: link:../../common/utils/signet_miner_loop.sh[signet_miner_loop.sh].
. Referencing the funding tx (via $FUNDING_TX_ID and $FUNDING_UTXO_INDEX), create the spending tx:
@@ -256,11 +277,28 @@ $ b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1
NOTE: There will not yet be a field `blockhash` in the response.
. Mine 1 block:
+
+.. regtest:
+
-----
$ b-cli -generate 1
-----
+.. signet:
++
+-----
+.. Signet
++
+If on `signet` network, then execute the following:
++
+-----
+$ $BITCOIN_SOURCE_DIR/contrib/signet/miner --cli "bitcoin-cli -conf=$HOME/anduro-360/configs/bitcoin.conf.signet" generate \
+ --address $P2TSH_ADDR \
+ --grind-cmd "$BITCOIN_SOURCE_DIR/build/bin/bitcoin-util grind" \
+ --min-nbits --set-block-time $(date +%s) \
+ --poolid "MARA Pool"
+-----
+
. Obtain `blockhash` field of mined tx:
+
-----
From 0c2bf8e8ba496ac1cf000df18c9b99762d4a8929 Mon Sep 17 00:00:00 2001
From: jbride
Date: Mon, 8 Sep 2025 08:09:13 -0600
Subject: [PATCH 04/16] bip360: signet miner utility
---
.../common/utils/signet_miner_loop.sh | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100755 bip-0360/ref-impl/common/utils/signet_miner_loop.sh
diff --git a/bip-0360/ref-impl/common/utils/signet_miner_loop.sh b/bip-0360/ref-impl/common/utils/signet_miner_loop.sh
new file mode 100755
index 0000000000..93c687d7b9
--- /dev/null
+++ b/bip-0360/ref-impl/common/utils/signet_miner_loop.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# Invokes mining simulator a configurable number of times
+
+
+
+
+BITCOIN_SOURCE_DIR=${BITCOIN_SOURCE_DIR:-$HOME/bitcoin}
+
+# path to bitcoin.conf for signet
+BITCOIN_CONF_FILE_PATH=${BITCOIN_CONF_FILE_PATH:-$HOME/configs/bitcoin.conf.signet}
+
+# Set default LOOP_COUNT to 110 if not set
+LOOP_COUNT=${LOOP_COUNT:-110}
+
+# Validate LOOP_COUNT is a positive integer
+if ! [[ "$LOOP_COUNT" =~ ^[0-9]+$ ]] || [ "$LOOP_COUNT" -le 0 ]; then
+ echo "Error: LOOP_COUNT must be a positive integer"
+ exit 1
+fi
+
+# Determine name of pool by querying mempool.space backend
+# curl -X GET "http://localhost:8999/api/v1/mining/pool/marapool" | jq -r .pool.regexes
+export POOL_ID=${POOL_ID:-"MARA Pool"}
+
+echo -en "\nLoop_COUNT = $LOOP_COUNT\nBITCOIN_CONF_FILE_PATH=$BITCOIN_CONF_FILE_PATH\nBITCOIN_SOURCE_DIR=$BITCOIN_SOURCE_DIR\nPOOL_ID=$POOL_ID\n\n";
+
+
+for ((i=1; i<=LOOP_COUNT; i++))
+do
+ echo "Iteration $i of $LOOP_COUNT"
+ $BITCOIN_SOURCE_DIR/contrib/signet/miner --cli "bitcoin-cli -conf=$BITCOIN_CONF_FILE_PATH" generate \
+ --address $P2TSH_ADDR \
+ --grind-cmd "$BITCOIN_SOURCE_DIR/build/bin/bitcoin-util grind" \
+ --poolid $POOL_ID \
+ --min-nbits --set-block-time $(date +%s)
+done
From 32172df3a70b0f0bcf02dc13c0adedf6142778a6 Mon Sep 17 00:00:00 2001
From: jbride
Date: Mon, 8 Sep 2025 08:06:58 -0600
Subject: [PATCH 05/16] bip360: introducing libbitcoinpqc
---
bip-0360/ref-impl/.gitignore | 1 +
.../tests/data/p2tsh_pqc_construction.json | 252 ++++++++++++++++++
bip-0360/ref-impl/rust/Cargo.lock | 242 ++++++++++++++++-
bip-0360/ref-impl/rust/Cargo.toml | 5 +
.../ref-impl/rust/examples/schnorr_example.rs | 43 ++-
.../examples/slh_dsa_verification_example.rs | 77 ++++++
.../ref-impl/rust/src/bin/slh_dsa_key_gen.rs | 33 +++
bip-0360/ref-impl/rust/src/lib.rs | 12 +
.../rust/tests/p2tsh_pqc_construction.rs | 240 +++++++++++++++++
9 files changed, 889 insertions(+), 16 deletions(-)
create mode 100644 bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json
create mode 100644 bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs
create mode 100644 bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs
create mode 100644 bip-0360/ref-impl/rust/tests/p2tsh_pqc_construction.rs
diff --git a/bip-0360/ref-impl/.gitignore b/bip-0360/ref-impl/.gitignore
index 1fd386e9a2..db5bd6b642 100644
--- a/bip-0360/ref-impl/.gitignore
+++ b/bip-0360/ref-impl/.gitignore
@@ -1,5 +1,6 @@
rust-bitcoin
rust-miniscript
+libbitcoinpqc
target
.btcdeb_history
*.swp
diff --git a/bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json b/bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json
new file mode 100644
index 0000000000..90184cdaf2
--- /dev/null
+++ b/bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json
@@ -0,0 +1,252 @@
+{
+ "version": 1,
+ "test_vectors": [
+ {
+ "id": "p2tsh_missing_leaf_script_tree_error",
+ "objective": "Tests P2TSH with missing leaf script tree",
+ "given": {
+ "script_tree": ""
+ },
+ "intermediary": {
+ },
+ "expected": {
+ "error": "P2TSH requires a script tree with at least one leaf"
+ }
+ },
+ {
+ "id": "p2tsh_single_leaf_script_tree",
+ "objective": "Tests P2TSH with single leaf script tree",
+ "given": {
+ "scriptTree": {
+ "id": 0,
+ "script": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f2450",
+ "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUCCESS80",
+ "priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
+ "leafVersion": 192
+ }
+ },
+ "intermediary": {
+ "leafHashes": [
+ "3766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869"
+ ],
+ "merkleRoot": "3766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869"
+ },
+ "expected": {
+ "scriptPubKey": "52203766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869",
+ "bip350Address": "bc1zxan04fs7spxasm7ph4rklr95ghmkewxmfcva8en5wje8tukg8p5sfm2vnw",
+ "scriptPathControlBlocks": [
+ "c1"
+ ]
+ }
+ },
+ {
+ "id": "p2tsh_different_version_leaves",
+ "objective": "Tests P2TSH with two script leaves of different versions. TO-DO: currently ignores given leaf version and over-rides. Probably better to throw error",
+ "given": {
+ "scriptTree": [
+ {
+ "id": 0,
+ "script": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f2450",
+ "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUCCESS80",
+ "priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
+ "leafVersion": 192
+ },
+ {
+ "id": 1,
+ "script": "06424950333431",
+ "asm": "424950333431",
+ "description": "just pushes the string 'BIP341' onto the stack",
+ "leafVersion": 250
+ }
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "3766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869",
+ "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
+ ],
+ "merkleRoot": "1f8b34e43d12df49a89ec41b70f58bd57a020702ab4895bc350c88e4ab599c28"
+ },
+ "expected": {
+ "scriptPubKey": "52201f8b34e43d12df49a89ec41b70f58bd57a020702ab4895bc350c88e4ab599c28",
+ "bip350Address": "bc1zr79nfepazt05n2y7csdhpavt64aqypcz4dyft0p4pjywf26ens5qjlm4a6",
+ "scriptPathControlBlocks": [
+ "c13766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869",
+ "c1f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
+ ]
+ }
+ },
+ {
+ "id": "p2tsh_simple_lightning_contract",
+ "objective": "Tests P2TSH with two script leaves that simulate a simple lightning network contract. Reference: https://github.com/bitcoin-core/btcdeb/blob/master/doc/tapscript-example-with-tap.md",
+ "given": {
+ "scriptTree": [
+ {
+ "id": 0,
+ "script": "029000b275201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f2450",
+ "asm": "144 OP_CHECKSEQUENCEVERIFY OP_DROP 1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUCCESS80",
+ "priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
+ "description": "Alice takes the money after waiting 1 day",
+ "leafVersion": 192
+ },
+ {
+ "id": 1,
+ "script": "a8206c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd533388204c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd",
+ "asm": "OP_SHA256 6c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd5333 OP_EQUALVERIFY 4c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd OP_SUCCESS80",
+ "priv_key": "49a4214f386240d97ea68efb4268043fd5a55208dcdac18ce5bd3332b8e488944c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd",
+ "description": "Bob takes the money whenever he wishes to by revealing the preimage_hash",
+ "leafVersion": 192
+ }
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "a9745ac96d4f3702b78751f1e08f3040fbe6347e7b4f520d22d3f907730cbb7e",
+ "bf117a8095de4ae4727e8741061b40d29b8e0fe03e225cb603aa5edb54dcd441"
+ ],
+ "merkleRoot": "8488b7529e5590b49d2ea381e2949870670e59f2229ec4ae8c12f28ba1af26c2"
+ },
+ "expected": {
+ "scriptPubKey": "52208488b7529e5590b49d2ea381e2949870670e59f2229ec4ae8c12f28ba1af26c2",
+ "bip350Address": "bc1zsjytw5572kgtf8fw5wq799ycwpnsuk0jy20vft5vzteghgd0ympqkyk6vx",
+ "scriptPathControlBlocks": [
+ "c1bf117a8095de4ae4727e8741061b40d29b8e0fe03e225cb603aa5edb54dcd441",
+ "c1a9745ac96d4f3702b78751f1e08f3040fbe6347e7b4f520d22d3f907730cbb7e"
+ ]
+ }
+ },
+ {
+ "id": "p2tsh_two_leaf_same_version",
+ "objective": "Tests P2TSH with two script leaves of same version",
+ "given": {
+ "scriptTree": [
+ {
+ "id": 0,
+ "script": "20e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f534450",
+ "asm": "e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f5344 OP_SUCCESS80",
+ "priv_key": "a695d8a1351774d59ed1b980462c2ab8d58b3b7a48f55b114c6a39980dc1c13ee6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f5344",
+ "leafVersion": 192
+ },
+ {
+ "id": 1,
+ "script": "07546170726f6f74",
+ "asm": "546170726f6f74",
+ "description": "pushes the string 'Taproot' onto the stack",
+ "leafVersion": 192
+ }
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "2d13d635cf05ee7de0b6ea933efff5dce7f3aff9cc12a7f743fd561f6147c561",
+ "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
+ ],
+ "merkleRoot": "8d14ecbad89fbe37cf010ea1cf016b015fdf53aaeb298d809b9a689d50984c02"
+ },
+ "expected": {
+ "scriptPubKey": "52208d14ecbad89fbe37cf010ea1cf016b015fdf53aaeb298d809b9a689d50984c02",
+ "bip350Address": "bc1z352wewkcn7lr0ncpp6su7qttq90a75a2av5cmqymnf5f65ycfspqdmwdrz",
+ "scriptPathControlBlocks": [
+ "c12d13d635cf05ee7de0b6ea933efff5dce7f3aff9cc12a7f743fd561f6147c561",
+ "c12cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
+ ]
+ }
+ },
+ {
+ "id": "p2tsh_three_leaf_complex",
+ "objective": "Tests P2TSH with a complex three-leaf script tree structure, demonstrating nested script paths and multiple verification options",
+ "given": {
+ "scriptTree": [
+ {
+ "id": 0,
+ "script": "201d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc50",
+ "asm": "1d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc OP_SUCCESS80",
+ "priv_key": "2b339b5055fad695f0595d5581fa087455854f64c5443c03d5b4ca53549f12a41d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc",
+ "leafVersion": 192
+ },
+ [
+ {
+ "id": 1,
+ "script": "20a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad50",
+ "asm": "a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad OP_SUCCESS80",
+ "priv_key": "ec29d60c1be9263602906499b5e3c1de9e36cc88cec31154210191a578b92e9da3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad",
+ "leafVersion": 192
+ },
+ {
+ "id": 2,
+ "script": "20e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f3650",
+ "asm": "e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f36 OP_SUCCESS80",
+ "priv_key": "da04d13f706a6e0d22ac0db7c361f1aaa706a27043efca4edfecfed3125238e1e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f36",
+ "leafVersion": 192
+ }
+ ]
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "35d1f75d65fbe192b8c5cbe44097ba5d9975e61789af49214e4a0709e4b9a8e5",
+ "4350873c0939e615520d9d1bf9e42c7522d0b9cafe30fc575fa91ced72eb5d78",
+ "6a198cc871eb9cb184b78c23b0b660541f8bc6e7854e69d21d1bcafb2da0ef17"
+ ],
+ "merkleRoot": "6e5b1eae3ea3cab9af523a878e823d471ca86c12ddfe253eb10d412bb8b084cd"
+ },
+ "expected": {
+ "scriptPubKey": "52206e5b1eae3ea3cab9af523a878e823d471ca86c12ddfe253eb10d412bb8b084cd",
+ "bip350Address": "bc1zded3at37509tnt6j82rcaq3aguw2smqjmhlz2043p4qjhw9ssnxs48f2e5",
+ "scriptPathControlBlocks": [
+ "c1e1e25e1e72b47fe03f70dc934b798caec4aaad6861e6e698cee786a1d6248527",
+ "c16a198cc871eb9cb184b78c23b0b660541f8bc6e7854e69d21d1bcafb2da0ef1735d1f75d65fbe192b8c5cbe44097ba5d9975e61789af49214e4a0709e4b9a8e5",
+ "c14350873c0939e615520d9d1bf9e42c7522d0b9cafe30fc575fa91ced72eb5d7835d1f75d65fbe192b8c5cbe44097ba5d9975e61789af49214e4a0709e4b9a8e5"
+ ]
+ }
+ },
+ {
+ "id": "p2tsh_three_leaf_alternative",
+ "objective": "Tests another variant of P2TSH with three leaves arranged in a different tree structure, showing alternative script path spending options",
+ "given": {
+ "scriptTree": [
+ {
+ "id": 0,
+ "script": "20409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c50",
+ "asm": "409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c OP_SUCCESS80",
+ "priv_key": "c6ec3801b04afa40be6f77f3f7a7b3b7cb8cfe233b0263fb70e2f087b21397c1409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c",
+ "leafVersion": 192
+ },
+ [
+ {
+ "id": 1,
+ "script": "209f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a50",
+ "asm": "9f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a OP_SUCCESS80",
+ "priv_key": "7524bca170d54231f1246f30fb00f9da2208b1363ba5a7bbaf11fc3b0309e9519f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a",
+ "leafVersion": 192
+ },
+ {
+ "id": 2,
+ "script": "20c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f50",
+ "asm": "c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f OP_SUCCESS80",
+ "priv_key": "d9d0f17d630b6a538e6a8a036373e7b9e5023a4c08f72dd8c3ef59288b98c079c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f",
+ "leafVersion": 192
+ }
+ ]
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "0102b0ba8fe443b500865e4e87e806d0d2fab23040b617b80bba31d0ea8b2164",
+ "52fd4722ad69cb1d0c5e1b4830507c533d33389b39d86e20f2aff4991ecf7e27",
+ "61cddcc41e5075a45f69bcc9486d7babdeb1bf9edcb0f292957f7f158026ae3a"
+ ],
+ "merkleRoot": "c82b934c5fd94054ab11204ae754ae3e6395abb23a6629bd6f93ede4cd5c1756"
+ },
+ "expected": {
+ "scriptPubKey": "5220c82b934c5fd94054ab11204ae754ae3e6395abb23a6629bd6f93ede4cd5c1756",
+ "bip350Address": "bc1zeq4exnzlm9q9f2c3yp9ww49w8e3et2aj8fnzn0t0j0k7fn2uzatq6vc68m",
+ "scriptPathControlBlocks": [
+ "c11bf3e4588edf111915ed79885a5e56a2dd2f13ae375846c56b8e0723a5077966",
+ "c161cddcc41e5075a45f69bcc9486d7babdeb1bf9edcb0f292957f7f158026ae3a0102b0ba8fe443b500865e4e87e806d0d2fab23040b617b80bba31d0ea8b2164",
+ "c152fd4722ad69cb1d0c5e1b4830507c533d33389b39d86e20f2aff4991ecf7e270102b0ba8fe443b500865e4e87e806d0d2fab23040b617b80bba31d0ea8b2164"
+ ]
+ }
+ }
+ ]
+}
diff --git a/bip-0360/ref-impl/rust/Cargo.lock b/bip-0360/ref-impl/rust/Cargo.lock
index b7f2dfd90b..ef5f88d5bd 100644
--- a/bip-0360/ref-impl/rust/Cargo.lock
+++ b/bip-0360/ref-impl/rust/Cargo.lock
@@ -89,6 +89,26 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
+[[package]]
+name = "bindgen"
+version = "0.71.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+]
+
[[package]]
name = "bitcoin"
version = "0.32.6"
@@ -101,7 +121,7 @@ dependencies = [
"bitcoin_hashes",
"hex-conservative",
"hex_lit",
- "secp256k1",
+ "secp256k1 0.29.1",
"serde",
]
@@ -141,6 +161,35 @@ dependencies = [
"serde",
]
+[[package]]
+name = "bitcoinpqc"
+version = "0.2.0"
+dependencies = [
+ "bindgen",
+ "bitmask-enum",
+ "cmake",
+ "hex",
+ "libc",
+ "secp256k1 0.31.1",
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+
+[[package]]
+name = "bitmask-enum"
+version = "2.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6cbbb8f56245b5a479b30a62cdc86d26e2f35c2b9f594bc4671654b03851380"
+dependencies = [
+ "quote",
+ "syn",
+]
+
[[package]]
name = "cc"
version = "1.2.25"
@@ -150,18 +199,53 @@ dependencies = [
"shlex",
]
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
[[package]]
name = "env_filter"
version = "0.1.3"
@@ -193,9 +277,27 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.3+wasi-0.2.4",
]
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
[[package]]
name = "hex"
version = "0.4.3"
@@ -223,6 +325,15 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itoa"
version = "1.0.15"
@@ -259,6 +370,16 @@ version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+[[package]]
+name = "libloading"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
+dependencies = [
+ "cfg-if",
+ "windows-targets",
+]
+
[[package]]
name = "log"
version = "0.4.27"
@@ -271,6 +392,12 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
[[package]]
name = "miniscript"
version = "13.0.0"
@@ -279,6 +406,16 @@ dependencies = [
"bitcoin",
]
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -297,11 +434,13 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bitcoin",
+ "bitcoinpqc",
"env_logger",
"hex",
"log",
"miniscript",
"once_cell",
+ "rand 0.9.2",
"serde",
"serde_json",
"thiserror",
@@ -331,6 +470,16 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "prettyplease"
+version = "0.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.95"
@@ -349,6 +498,12 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
[[package]]
name = "rand"
version = "0.8.5"
@@ -356,8 +511,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
- "rand_chacha",
- "rand_core",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.3",
]
[[package]]
@@ -367,7 +532,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
]
[[package]]
@@ -376,7 +551,16 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom",
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
]
[[package]]
@@ -408,6 +592,12 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
[[package]]
name = "ryu"
version = "1.0.20"
@@ -421,8 +611,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
"bitcoin_hashes",
- "rand",
- "secp256k1-sys",
+ "rand 0.8.5",
+ "secp256k1-sys 0.10.1",
+ "serde",
+]
+
+[[package]]
+name = "secp256k1"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2"
+dependencies = [
+ "bitcoin_hashes",
+ "rand 0.9.2",
+ "secp256k1-sys 0.11.0",
"serde",
]
@@ -435,6 +637,15 @@ dependencies = [
"cc",
]
+[[package]]
+name = "secp256k1-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "serde"
version = "1.0.219"
@@ -522,6 +733,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+[[package]]
+name = "wasi"
+version = "0.14.3+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
+dependencies = [
+ "wit-bindgen",
+]
+
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -595,6 +815,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+[[package]]
+name = "wit-bindgen"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
+
[[package]]
name = "zerocopy"
version = "0.8.26"
diff --git a/bip-0360/ref-impl/rust/Cargo.toml b/bip-0360/ref-impl/rust/Cargo.toml
index 9f995d9cc5..95909c0cd8 100644
--- a/bip-0360/ref-impl/rust/Cargo.toml
+++ b/bip-0360/ref-impl/rust/Cargo.toml
@@ -11,6 +11,8 @@ miniscript = "13.0.0"
# During local dev, ensure version used here matches the version of the forked local library (referenced in: patch.crates-io)
bitcoin = { version="0.32.6", features = ["rand-std", "serde"] }
+bitcoinpqc = { version="0.2.0", features = ["serde"] }
+
env_logger = "0.11.5"
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
@@ -19,6 +21,7 @@ once_cell = "1.19"
hex = "0.4.3"
anyhow = "1.0.98"
thiserror = "2.0.12"
+rand = "0.9"
[patch.crates-io]
@@ -31,3 +34,5 @@ bitcoin = { path = "./rust-bitcoin/bitcoin" }
# cargo tree -p miniscript | more
miniscript = { path = "./rust-miniscript" }
+
+bitcoinpqc = { path = "./libbitcoinpqc" }
diff --git a/bip-0360/ref-impl/rust/examples/schnorr_example.rs b/bip-0360/ref-impl/rust/examples/schnorr_example.rs
index 5a4c04a3ba..9b12d9f526 100644
--- a/bip-0360/ref-impl/rust/examples/schnorr_example.rs
+++ b/bip-0360/ref-impl/rust/examples/schnorr_example.rs
@@ -17,24 +17,51 @@ static SECP: Lazy> = Lazy::new(Secp256k1::new
fn main() {
let _ = env_logger::try_init();
+
+ // acquire a schnorr keypair (leveraging OS provided random number generator)
let keypair = acquire_schnorr_keypair();
let message_bytes = b"hello";
- // Hash the message first to get a 32-byte digest
- let message_hash = Hash::hash(message_bytes);
- let message = Message::from_digest_slice(&message_hash.to_byte_array()).unwrap();
- let pubkey = keypair.1;
+ // secp256k1 operates on a 256-bit (32-byte) field, so inputs must be exactly this size
+ // subsequently, Schnorr signatures on secp256k1 require exactly a 32-byte input (the curve's scalar field size)
+ let message_hash: Hash = Hash::hash(message_bytes);
+ let message: Message = Message::from_digest_slice(&message_hash.to_byte_array()).unwrap();
+
+
+ /* The secp256k1 library internally generates a random scalar value (aka: nonce or k-value) for each signature
+ * Every signature is unique - even if you sign the same message with the same private key multiple times
+ * The randomness is handled automatically by the secp256k1 implementation
+ * You get different signatures each time for the same inputs
+ * The nonce is only needed during signing, not during verification
+
+ Schnorr signatures require randomness for security reasons:
+ * Prevents private key recovery - If the same nonce is used twice, an attacker could potentially derive your private key
+ * Ensures signature uniqueness - Each signature should be cryptographically distinct
+ * Protects against replay attacks - Different signatures for the same data
+ */
let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr(&message, &keypair.0.keypair(&SECP));
+ info!("Signature created successfully, size: {}", signature.serialize().len());
+
+ let pubkey = keypair.1;
+
+ /*
+ * The nonce provides security during signing (prevents private key recovery)
+ * The nonce is mathematically eliminated during verification
+ * The verifier only needs public information (signature, message, public key)
+ */
+ let schnorr_valid = verify_schnorr_signature(signature, message, pubkey);
+ info!("schnorr_valid: {}", schnorr_valid);
+
+
+ let aux_rand = [0u8; 32]; // 32 zero bytes; fine for testing
let signature_aux_rand: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand(
&message,
&keypair.0.keypair(&SECP),
- &[0u8; 32] // 32 zero bytes of auxiliary random data
+ &aux_rand
);
-
- let schnorr_valid = verify_schnorr_signature(signature, message, pubkey);
- info!("schnorr_valid: {}", schnorr_valid);
+ info!("aux_rand signature created successfully, size: {}", signature_aux_rand.serialize().len());
let schnorr_valid_aux_rand = verify_schnorr_signature(signature_aux_rand, message, pubkey);
info!("schnorr_valid_aux_rand: {}", schnorr_valid_aux_rand);
diff --git a/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs b/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs
new file mode 100644
index 0000000000..151df2ba65
--- /dev/null
+++ b/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs
@@ -0,0 +1,77 @@
+use std::env;
+use log::info;
+use once_cell::sync::Lazy;
+use bitcoin::hashes::{sha256::Hash, Hash as HashTrait};
+use rand::{rng, RngCore};
+
+use bitcoinpqc::{
+ generate_keypair, public_key_size, secret_key_size, sign, signature_size, verify, Algorithm,
+};
+
+fn main() {
+ let _ = env_logger::try_init();
+
+ /*
+ In SPHINCS+ (underlying algorithm of SLH-DSA), the random data is used to:
+ * Initialize hash function parameters within the key generation
+ * Seed the Merkle tree construction that forms the public key
+ * Generate the secret key components that enable signing
+ */
+ let random_data = get_random_bytes(128);
+ println!("Generated random data of size {}", random_data.len());
+
+ let keypair = generate_keypair(Algorithm::SLH_DSA_128S, &random_data)
+ .expect("Failed to generate SLH-DSA-128S keypair");
+
+ let message_bytes = b"SLH-DSA-128S Test Message";
+
+ println!("Message to sign: {message_bytes:?}");
+
+ /* No need to hash the message
+ 1. Variable Input Size: SPHINCS+ can handle messages of arbitrary length directly
+ 2. Internal Hashing: The SPHINCS+ algorithm internally handles message processing and hashing as part of its design
+ 3. Hash-Based Design: SPHINCS+ is built on hash functions and Merkle trees, so it's designed to work with variable-length inputs
+ 4. No Curve Constraints: Unlike elliptic curve schemes, SPHINCS+ doesn't have fixed field size requirements
+
+ SLH-DSA doesn't use nonces like Schnorr does.
+ With SLH-DSA, randomness is built into the key generation process only ( and not the signing process; ie: SECP256K1)
+ Thus, no need for aux_rand data fed to the signature function.
+ The signing algorithm is deterministic and doesn't require random input during signing.
+ */
+
+ let signature = sign(&keypair.secret_key, message_bytes).expect("Failed to sign with SLH-DSA-128S");
+
+ println!(
+ "Signature created successfully, size: {}",
+ signature.bytes.len()
+ );
+ println!(
+ "Signature prefix: {:02x?}",
+ &signature.bytes[..8.min(signature.bytes.len())]
+ );
+
+ // Verify the signature
+ println!("Verifying signature...");
+ let result = verify(&keypair.public_key, message_bytes, &signature);
+ println!("Verification result: {result:?}");
+
+ assert!(result.is_ok(), "SLH-DSA-128S signature verification failed");
+
+ // Try to verify with a modified message - should fail
+ let modified_message = b"SLH-DSA-128S Modified Message";
+ println!("Modified message: {modified_message:?}");
+
+ let result = verify(&keypair.public_key, modified_message, &signature);
+ println!("Verification with modified message result: {result:?}");
+
+ assert!(
+ result.is_err(),
+ "SLH-DSA-128S verification should fail with modified message"
+ );
+}
+
+fn get_random_bytes(size: usize) -> Vec {
+ let mut bytes = vec![0u8; size];
+ rng().fill_bytes(&mut bytes);
+ bytes
+}
diff --git a/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs b/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs
new file mode 100644
index 0000000000..4b7c90896e
--- /dev/null
+++ b/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs
@@ -0,0 +1,33 @@
+use std::env;
+use log::info;
+use rand::{rng, RngCore};
+
+use bitcoinpqc::{
+ generate_keypair, public_key_size, secret_key_size, Algorithm,
+};
+
+fn main() {
+ let _ = env_logger::try_init();
+
+ /*
+ In SPHINCS+ (underlying algorithm of SLH-DSA), the random data is used to:
+ * Initialize hash function parameters within the key generation
+ * Seed the Merkle tree construction that forms the public key
+ * Generate the secret key components that enable signing
+ */
+ let random_data = get_random_bytes(128);
+ println!("Generated random data of size {}", random_data.len());
+
+ let keypair = generate_keypair(Algorithm::SLH_DSA_128S, &random_data)
+ .expect("Failed to generate SLH-DSA-128S keypair");
+
+ info!("public key size / value = {}, {}", public_key_size(Algorithm::SLH_DSA_128S), hex::encode(&keypair.public_key.bytes));
+ info!("private key size / value = {}, {}", secret_key_size(Algorithm::SLH_DSA_128S), hex::encode(&keypair.secret_key.bytes));
+
+}
+
+fn get_random_bytes(size: usize) -> Vec {
+ let mut bytes = vec![0u8; size];
+ rng().fill_bytes(&mut bytes);
+ bytes
+}
diff --git a/bip-0360/ref-impl/rust/src/lib.rs b/bip-0360/ref-impl/rust/src/lib.rs
index 2a3fcf8e6c..629ec4e688 100644
--- a/bip-0360/ref-impl/rust/src/lib.rs
+++ b/bip-0360/ref-impl/rust/src/lib.rs
@@ -433,7 +433,19 @@ fn compact_size(n: u64) -> Vec {
pub fn acquire_schnorr_keypair() -> (SecretKey, XOnlyPublicKey) {
+ /* OsRng typically draws from the OS's entropy pool (hardware random num generators, system events, etc), ie:
+ * 1. $ cat /proc/sys/kernel/random/entropy_avail
+ * 2. $ sudo dmesg | grep -i "random\|rng\|entropy"
+
+ The Linux kernel's RNG (/dev/random and /dev/urandom) typically combines multiple entropy sources: ie:
+ * Hardware RNG (if available)
+ * CPU RNG instructions (RDRAND/RDSEED)
+ * Hardware events (disk I/O, network packets, keyboard/mouse input)
+ * Timer jitter
+ * Interrupt timing
+ */
let keypair = Keypair::new(&SECP, &mut OsRng);
+
let privkey: SecretKey = keypair.secret_key();
let pubkey: (XOnlyPublicKey, Parity) = XOnlyPublicKey::from_keypair(&keypair);
(privkey, pubkey.0)
diff --git a/bip-0360/ref-impl/rust/tests/p2tsh_pqc_construction.rs b/bip-0360/ref-impl/rust/tests/p2tsh_pqc_construction.rs
new file mode 100644
index 0000000000..dcbf104f20
--- /dev/null
+++ b/bip-0360/ref-impl/rust/tests/p2tsh_pqc_construction.rs
@@ -0,0 +1,240 @@
+use std::collections::HashSet;
+use bitcoin::{Network, ScriptBuf};
+use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch, TapNodeHash};
+use bitcoin::p2tsh::{P2tshBuilder, P2tshControlBlock, P2tshSpendInfo};
+use bitcoin::hashes::Hash;
+
+use hex;
+use log::debug;
+use once_cell::sync::Lazy;
+
+use p2tsh_ref::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn};
+use p2tsh_ref::error::P2TSHError;
+use p2tsh_ref::{create_p2tsh_utxo, tagged_hash};
+
+// This file contains tests that execute against the BIP360 script-path-only test vectors.
+
+static TEST_VECTORS: Lazy = Lazy::new(|| {
+ let bip360_test_vectors = include_str!("../../common/tests/data/p2tsh_pqc_construction.json");
+ let test_vectors: TestVectors = serde_json::from_str(bip360_test_vectors).unwrap();
+ assert_eq!(test_vectors.version, 1);
+ test_vectors
+});
+
+static P2TSH_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST: &str = "p2tsh_missing_leaf_script_tree_error";
+static P2TSH_SINGLE_LEAF_SCRIPT_TREE_TEST: &str = "p2tsh_single_leaf_script_tree";
+static P2TSH_DIFFERENT_VERSION_LEAVES_TEST: &str = "p2tsh_different_version_leaves";
+static P2TSH_TWO_LEAF_SAME_VERSION_TEST: &str = "p2tsh_two_leaf_same_version";
+static P2TSH_THREE_LEAF_COMPLEX_TEST: &str = "p2tsh_three_leaf_complex";
+static P2TSH_THREE_LEAF_ALTERNATIVE_TEST: &str = "p2tsh_three_leaf_alternative";
+static P2TSH_SIMPLE_LIGHTNING_CONTRACT_TEST: &str = "p2tsh_simple_lightning_contract";
+
+// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple
+#[test]
+fn test_p2tsh_pqc_missing_leaf_script_tree_error() {
+
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ let test_vectors = &*TEST_VECTORS;
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST).unwrap();
+ let test_result: anyhow::Result<()> = process_test_vector_p2tsh(test_vector);
+ assert!(matches!(test_result.unwrap_err().downcast_ref::(),
+ Some(P2TSHError::MissingScriptTreeLeaf)));
+}
+
+// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple
+#[test]
+fn test_p2tsh_pqc_single_leaf_script_tree() {
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ let test_vectors = &*TEST_VECTORS;
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_SINGLE_LEAF_SCRIPT_TREE_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
+}
+
+#[test]
+fn test_p2tsh_pqc_different_version_leaves() {
+
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ let test_vectors = &*TEST_VECTORS;
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_DIFFERENT_VERSION_LEAVES_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
+}
+
+#[test]
+fn test_p2tsh_pqc_simple_lightning_contract() {
+
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ let test_vectors = &*TEST_VECTORS;
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_SIMPLE_LIGHTNING_CONTRACT_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
+}
+
+#[test]
+fn test_p2tsh_pqc_two_leaf_same_version() {
+
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ let test_vectors = &*TEST_VECTORS;
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_TWO_LEAF_SAME_VERSION_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
+}
+
+#[test]
+fn test_p2tsh_pqc_three_leaf_complex() {
+
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ let test_vectors = &*TEST_VECTORS;
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_THREE_LEAF_COMPLEX_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
+}
+
+#[test]
+fn test_p2tsh_pqc_three_leaf_alternative() {
+
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ let test_vectors = &*TEST_VECTORS;
+ let test_vector = test_vectors.test_vector_map.get(P2TSH_THREE_LEAF_ALTERNATIVE_TEST).unwrap();
+ process_test_vector_p2tsh(test_vector).unwrap();
+}
+
+fn process_test_vector_p2tsh(test_vector: &TestVector) -> anyhow::Result<()> {
+
+ let tv_script_tree: Option<&TVScriptTree> = test_vector.given.script_tree.as_ref();
+
+ let mut tv_leaf_count: u8 = 0;
+ let mut current_branch_id: u8 = 0;
+
+ // TaprootBuilder expects the addition of each leaf script with its associated depth
+ // It then constructs the binary tree in DFS order, sorting siblings lexicographically & combining them via BIP341's tapbranch_hash
+ // Use of TaprootBuilder avoids user error in constructing branches manually and ensures Merkle tree correctness and determinism
+ let mut p2tsh_builder: P2tshBuilder = P2tshBuilder::new();
+
+ let mut control_block_data: Vec<(ScriptBuf, LeafVersion)> = Vec::new();
+
+ // 1) traverse test vector script tree and add leaves to P2TSH builder
+ if let Some(script_tree) = tv_script_tree {
+
+ script_tree.traverse_with_right_subtree_first(0, Direction::Root,&mut |node, depth, direction| {
+
+ if let TVScriptTree::Leaf(tv_leaf) = node {
+
+ let tv_leaf_script_bytes = hex::decode(&tv_leaf.script).unwrap();
+
+ // NOTE: IOT to execute script_info.control_block(..), will add these to a vector
+ let tv_leaf_script_buf = ScriptBuf::from_bytes(tv_leaf_script_bytes.clone());
+ let tv_leaf_version = LeafVersion::from_consensus(tv_leaf.leaf_version).unwrap();
+ control_block_data.push((tv_leaf_script_buf.clone(), tv_leaf_version));
+
+ let mut modified_depth = depth + 1;
+ if direction == Direction::Root {
+ modified_depth = depth;
+ }
+ debug!("traverse_with_depth: leaf_count: {}, depth: {}, modified_depth: {}, direction: {}, tv_leaf_script: {}",
+ tv_leaf_count, depth, modified_depth, direction, tv_leaf.script);
+
+ // NOTE: Some of the the test vectors in this project specify leaves with non-standard versions (ie: 250 / 0xfa)
+ p2tsh_builder = p2tsh_builder.clone().add_leaf_with_ver(depth, tv_leaf_script_buf.clone(), tv_leaf_version)
+ .unwrap_or_else(|e| {
+ panic!("Failed to add leaf: {:?}", e);
+ });
+
+ tv_leaf_count += 1;
+ } else if let TVScriptTree::Branch { left, right } = node {
+ // No need to calculate branch hash.
+ // TaprootBuilder does this for us.
+ debug!("branch_count: {}, depth: {}, direction: {}", current_branch_id, depth, direction);
+ current_branch_id += 1;
+ }
+ });
+ }else {
+ return Err(P2TSHError::MissingScriptTreeLeaf.into());
+ }
+
+ let spend_info: P2tshSpendInfo = p2tsh_builder.clone()
+ .finalize()
+ .unwrap_or_else(|e| {
+ panic!("finalize failed: {:?}", e);
+ });
+
+ let derived_merkle_root: TapNodeHash = spend_info.merkle_root.unwrap();
+
+ // 2) verify derived merkle root against test vector
+ let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap();
+ assert_eq!(
+ derived_merkle_root.to_string(),
+ *test_vector_merkle_root,
+ "Merkle root mismatch"
+ );
+ debug!("just passed merkle root validation: {}", test_vector_merkle_root);
+
+ let test_vector_leaf_hashes_vec: Vec = test_vector.intermediary.leaf_hashes.clone();
+ let test_vector_leaf_hash_set: HashSet = test_vector_leaf_hashes_vec.iter().cloned().collect();
+ let test_vector_control_blocks_vec = &test_vector.expected.script_path_control_blocks;
+ let test_vector_control_blocks_set: HashSet = test_vector_control_blocks_vec.as_ref().unwrap().iter().cloned().collect();
+ let tap_tree: TapTree = p2tsh_builder.clone().into_inner().try_into_taptree().unwrap();
+ let script_leaves: ScriptLeaves = tap_tree.script_leaves();
+
+ // TO-DO: Investigate why the ordering of script leaves seems to be reverse of test vectors.
+ // 3) Iterate through leaves of derived script tree and verify both script leaf hashes and control blocks
+ for derived_leaf in script_leaves {
+
+ let version = derived_leaf.version();
+ let script = derived_leaf.script();
+ let merkle_branch: &TaprootMerkleBranch = derived_leaf.merkle_branch();
+
+ let derived_leaf_hash: TapLeafHash = TapLeafHash::from_script(script, version);
+ let leaf_hash = hex::encode(derived_leaf_hash.as_raw_hash().to_byte_array());
+ assert!(
+ test_vector_leaf_hash_set.contains(&leaf_hash),
+ "Leaf hash not found in expected set for {}", leaf_hash
+ );
+ debug!("just passed leaf_hash validation: {}", leaf_hash);
+
+ // Each leaf in the script tree has a corresponding control block.
+ // Specific to P2TR, the 3 sections of the control block (control byte, public key & merkle path) are highlighted here:
+ // https://learnmeabitcoin.com/technical/upgrades/taproot/#script-path-spend-control-block
+ // The control block, which includes the Merkle path, must be 33 + 32 * n bytes, where n is the number of Merkle path hashes (n ≥ 0).
+ // There is no consensus limit on n, but large Merkle trees increase the witness size, impacting block weight.
+ // NOTE: Control blocks could have also been obtained from spend_info.control_block(..) using the data in control_block_data
+ debug!("merkle_branch nodes: {:?}", merkle_branch);
+ let derived_control_block: P2tshControlBlock = P2tshControlBlock{
+ merkle_branch: merkle_branch.clone(),
+ };
+ let serialized_control_block = derived_control_block.serialize();
+ debug!("derived_control_block: {:?}, merkle_branch size: {}, control_block size: {}, serialized size: {}",
+ derived_control_block,
+ merkle_branch.len(),
+ derived_control_block.size(),
+ serialized_control_block.len());
+ let derived_serialized_control_block = hex::encode(serialized_control_block);
+ assert!(
+ test_vector_control_blocks_set.contains(&derived_serialized_control_block),
+ "Control block mismatch: {}, expected: {:?}", derived_serialized_control_block, test_vector_control_blocks_set
+ );
+ debug!("leaf_hash: {}, derived_serialized_control_block: {}", leaf_hash, derived_serialized_control_block);
+
+ }
+
+ let p2tsh_utxo_return: UtxoReturn = create_p2tsh_utxo(derived_merkle_root.to_string());
+
+ assert_eq!(
+ p2tsh_utxo_return.script_pubkey_hex,
+ *test_vector.expected.script_pubkey.as_ref().unwrap(),
+ "Script pubkey mismatch"
+ );
+ debug!("just passed script_pubkey validation. script_pubkey = {}", p2tsh_utxo_return.script_pubkey_hex);
+
+ let bech32m_address: String = p2tsh_utxo_return.bech32m_address;
+ debug!("derived bech32m address for bitcoin_network: {} : {}", p2tsh_utxo_return.bitcoin_network, bech32m_address);
+
+ if p2tsh_utxo_return.bitcoin_network == Network::Bitcoin {
+ assert_eq!(bech32m_address, *test_vector.expected.bip350_address.as_ref().unwrap(), "Bech32m address mismatch.");
+ }
+
+ Ok(())
+}
From 02c18e12930071f13c303d2fb64f820a66ec7e88 Mon Sep 17 00:00:00 2001
From: jbride
Date: Tue, 9 Sep 2025 08:57:43 -0600
Subject: [PATCH 06/16] bip360: extending p2tsh construction example to use
SLH-DSA keypair
---
.../ref-impl/rust/docs/p2tsh-end-to-end.adoc | 9 +-
bip-0360/ref-impl/rust/examples/p2tr_spend.rs | 3 +-
.../rust/examples/p2tsh_construction.rs | 11 +-
.../ref-impl/rust/examples/p2tsh_spend.rs | 31 +++-
.../ref-impl/rust/examples/schnorr_example.rs | 11 +-
.../examples/slh_dsa_verification_example.rs | 4 +-
.../ref-impl/rust/src/bin/slh_dsa_key_gen.rs | 4 +-
bip-0360/ref-impl/rust/src/data_structures.rs | 73 +++++++++
bip-0360/ref-impl/rust/src/lib.rs | 152 ++++++++++++------
bip-0360/ref-impl/rust/tests/p2tsh_spend.rs | 3 +-
10 files changed, 234 insertions(+), 67 deletions(-)
diff --git a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
index 1f7f6482c4..dc49263818 100644
--- a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -99,6 +99,12 @@ $ b-cli -named createwallet \
== Fund P2TSH UTXO
+. OPTIONAL: Indicate whether you prefer to use _Post Quantum Cryptography_ (in the form of _SLH-DSA_. Default is the use of `Schnorr` signatures ).
++
+-----
+$ export USE_PQC=false
+-----
+
. OPTIONAL: Define number of leaves in tap tree as well as the tap leaf to later use as the unlocking script:
+
-----
@@ -136,7 +142,8 @@ $ export QUANTUM_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tre
$ b-cli decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
-----
+
-NOTE: Notice that this script commits to a Schnorr 32-byte x-only public key.
+NOTE: If not using PQC, notice that this script commits to a Schnorr 32-byte x-only public key.
+If using PQC, this script commits to a Schnorr 32-byte SLH-DSA pub key and a OP_SUCCESS80 (represented as `OP_RESERVED`)
. fund this P2TSH address with the coinbase reward of a newly generated block:
diff --git a/bip-0360/ref-impl/rust/examples/p2tr_spend.rs b/bip-0360/ref-impl/rust/examples/p2tr_spend.rs
index 98d00d2b19..2956117c63 100644
--- a/bip-0360/ref-impl/rust/examples/p2tr_spend.rs
+++ b/bip-0360/ref-impl/rust/examples/p2tr_spend.rs
@@ -88,7 +88,8 @@ fn main() -> SpendDetails {
leaf_script_bytes.clone(),
leaf_script_priv_key_bytes,
spend_output_pubkey_hash_bytes.clone(),
- spend_output_amount_sats
+ spend_output_amount_sats,
+ false
);
// Remove first and last byte from leaf_script_bytes to get tapleaf_pubkey_bytes
diff --git a/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs b/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
index b314289767..94150d4bdb 100644
--- a/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
+++ b/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
@@ -1,12 +1,21 @@
use p2tsh_ref::{create_p2tsh_utxo, create_p2tsh_multi_leaf_taptree};
use p2tsh_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn};
+use std::env;
+use log::{info, error};
// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
fn main() -> ConstructionReturn {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
- let taptree_return: TaptreeReturn = create_p2tsh_multi_leaf_taptree();
+ // USE_PQC environment variable defaults to false if not set
+ let use_pqc: bool = env::var("USE_PQC")
+ .unwrap_or_else(|_| "false".to_string())
+ .parse()
+ .unwrap_or(false);
+ info!("use_pqc: {}", use_pqc);
+
+ let taptree_return: TaptreeReturn = create_p2tsh_multi_leaf_taptree(use_pqc);
let p2tsh_utxo_return: UtxoReturn = create_p2tsh_utxo(taptree_return.clone().tree_root_hex);
return ConstructionReturn {
diff --git a/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs b/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
index 20b9510f48..563d291788 100644
--- a/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
+++ b/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
@@ -1,4 +1,4 @@
-use p2tsh_ref::{ pay_to_p2wpkh_tx, verify_schnorr_signature_via_bytes };
+use p2tsh_ref::{ pay_to_p2wpkh_tx, verify_schnorr_signature_via_bytes, verify_slh_dsa_via_bytes };
use p2tsh_ref::data_structures::SpendDetails;
use std::env;
@@ -61,6 +61,17 @@ fn main() -> SpendDetails {
std::process::exit(1);
});
+ let priv_key_size: usize = leaf_script_priv_key_bytes.len();
+ let use_pqc: bool = match priv_key_size {
+ 32 => false,
+ 64 => true,
+ _ => {
+ error!("Invalid private key size: {}. Expected 32 or 64 bytes", priv_key_size);
+ std::process::exit(1);
+ }
+ };
+
+
// ie: OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
let leaf_script_bytes: Vec = env::var("LEAF_SCRIPT_HEX")
.map(|s| hex::decode(s).unwrap())
@@ -88,17 +99,23 @@ fn main() -> SpendDetails {
leaf_script_bytes.clone(),
leaf_script_priv_key_bytes,
spend_output_pubkey_hash_bytes,
- spend_output_amount_sats
+ spend_output_amount_sats,
+ use_pqc
);
// Remove first and last byte from leaf_script_bytes to get tapleaf_pubkey_bytes
let tapleaf_pubkey_bytes: Vec = leaf_script_bytes[1..leaf_script_bytes.len()-1].to_vec();
- let is_valid: bool = verify_schnorr_signature_via_bytes(
- &result.sig_bytes,
- &result.sighash,
- &tapleaf_pubkey_bytes);
- info!("is_valid: {}", is_valid);
+ if use_pqc {
+ let is_valid: bool = verify_slh_dsa_via_bytes(&result.sig_bytes, &result.sighash, &tapleaf_pubkey_bytes);
+ info!("is_valid: {}", is_valid);
+ } else {
+ let is_valid: bool = verify_schnorr_signature_via_bytes(
+ &result.sig_bytes,
+ &result.sighash,
+ &tapleaf_pubkey_bytes);
+ info!("is_valid: {}", is_valid);
+ }
return result;
}
diff --git a/bip-0360/ref-impl/rust/examples/schnorr_example.rs b/bip-0360/ref-impl/rust/examples/schnorr_example.rs
index 9b12d9f526..84b5aa3907 100644
--- a/bip-0360/ref-impl/rust/examples/schnorr_example.rs
+++ b/bip-0360/ref-impl/rust/examples/schnorr_example.rs
@@ -20,6 +20,7 @@ fn main() {
// acquire a schnorr keypair (leveraging OS provided random number generator)
let keypair = acquire_schnorr_keypair();
+ let (secret_key, public_key) = keypair.as_schnorr().unwrap();
let message_bytes = b"hello";
// secp256k1 operates on a 256-bit (32-byte) field, so inputs must be exactly this size
@@ -40,10 +41,10 @@ fn main() {
* Ensures signature uniqueness - Each signature should be cryptographically distinct
* Protects against replay attacks - Different signatures for the same data
*/
- let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr(&message, &keypair.0.keypair(&SECP));
+ let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr(&message, &secret_key.keypair(&SECP));
info!("Signature created successfully, size: {}", signature.serialize().len());
- let pubkey = keypair.1;
+ //let pubkey = public_key;
/*
@@ -51,18 +52,18 @@ fn main() {
* The nonce is mathematically eliminated during verification
* The verifier only needs public information (signature, message, public key)
*/
- let schnorr_valid = verify_schnorr_signature(signature, message, pubkey);
+ let schnorr_valid = verify_schnorr_signature(signature, message, *public_key);
info!("schnorr_valid: {}", schnorr_valid);
let aux_rand = [0u8; 32]; // 32 zero bytes; fine for testing
let signature_aux_rand: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand(
&message,
- &keypair.0.keypair(&SECP),
+ &secret_key.keypair(&SECP),
&aux_rand
);
info!("aux_rand signature created successfully, size: {}", signature_aux_rand.serialize().len());
- let schnorr_valid_aux_rand = verify_schnorr_signature(signature_aux_rand, message, pubkey);
+ let schnorr_valid_aux_rand = verify_schnorr_signature(signature_aux_rand, message, *public_key);
info!("schnorr_valid_aux_rand: {}", schnorr_valid_aux_rand);
}
diff --git a/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs b/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs
index 151df2ba65..c6c965e7a0 100644
--- a/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs
+++ b/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs
@@ -5,7 +5,7 @@ use bitcoin::hashes::{sha256::Hash, Hash as HashTrait};
use rand::{rng, RngCore};
use bitcoinpqc::{
- generate_keypair, public_key_size, secret_key_size, sign, signature_size, verify, Algorithm,
+ generate_keypair, public_key_size, secret_key_size, sign, signature_size, verify, Algorithm, KeyPair,
};
fn main() {
@@ -20,7 +20,7 @@ fn main() {
let random_data = get_random_bytes(128);
println!("Generated random data of size {}", random_data.len());
- let keypair = generate_keypair(Algorithm::SLH_DSA_128S, &random_data)
+ let keypair: KeyPair = generate_keypair(Algorithm::SLH_DSA_128S, &random_data)
.expect("Failed to generate SLH-DSA-128S keypair");
let message_bytes = b"SLH-DSA-128S Test Message";
diff --git a/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs b/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs
index 4b7c90896e..999d487d3d 100644
--- a/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs
+++ b/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs
@@ -3,7 +3,7 @@ use log::info;
use rand::{rng, RngCore};
use bitcoinpqc::{
- generate_keypair, public_key_size, secret_key_size, Algorithm,
+ generate_keypair, public_key_size, secret_key_size, Algorithm, KeyPair,
};
fn main() {
@@ -18,7 +18,7 @@ fn main() {
let random_data = get_random_bytes(128);
println!("Generated random data of size {}", random_data.len());
- let keypair = generate_keypair(Algorithm::SLH_DSA_128S, &random_data)
+ let keypair: KeyPair = generate_keypair(Algorithm::SLH_DSA_128S, &random_data)
.expect("Failed to generate SLH-DSA-128S keypair");
info!("public key size / value = {}, {}", public_key_size(Algorithm::SLH_DSA_128S), hex::encode(&keypair.public_key.bytes));
diff --git a/bip-0360/ref-impl/rust/src/data_structures.rs b/bip-0360/ref-impl/rust/src/data_structures.rs
index fbfa83e7d2..fd642f12e5 100644
--- a/bip-0360/ref-impl/rust/src/data_structures.rs
+++ b/bip-0360/ref-impl/rust/src/data_structures.rs
@@ -2,6 +2,10 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use log::debug;
+// Add imports for the unified keypair
+use bitcoin::secp256k1::{SecretKey, XOnlyPublicKey};
+use bitcoinpqc::{KeyPair, Algorithm};
+
#[derive(Debug, Serialize)]
pub struct TestVectors {
pub version: u32,
@@ -318,3 +322,72 @@ impl std::process::Termination for ConstructionReturn {
std::process::ExitCode::SUCCESS
}
}
+
+/// A unified keypair that can contain either a Schnorr keypair or an SLH-DSA keypair
+#[derive(Debug, Clone)]
+pub enum UnifiedKeypair {
+ Schnorr(SecretKey, XOnlyPublicKey),
+ SlhDsa(KeyPair),
+}
+
+impl UnifiedKeypair {
+ /// Create a new Schnorr keypair
+ pub fn new_schnorr(secret_key: SecretKey, public_key: XOnlyPublicKey) -> Self {
+ UnifiedKeypair::Schnorr(secret_key, public_key)
+ }
+
+ /// Create a new SLH-DSA keypair
+ pub fn new_slh_dsa(keypair: KeyPair) -> Self {
+ UnifiedKeypair::SlhDsa(keypair)
+ }
+
+ /// Get the secret key bytes for serialization
+ pub fn secret_key_bytes(&self) -> Vec {
+ match self {
+ UnifiedKeypair::Schnorr(secret_key, _) => secret_key.secret_bytes().to_vec(),
+ UnifiedKeypair::SlhDsa(keypair) => keypair.secret_key.bytes.clone(),
+ }
+ }
+
+ /// Get the public key bytes for script construction
+ pub fn public_key_bytes(&self) -> Vec {
+ match self {
+ UnifiedKeypair::Schnorr(_, public_key) => public_key.serialize().to_vec(),
+ UnifiedKeypair::SlhDsa(keypair) => keypair.public_key.bytes.clone(),
+ }
+ }
+
+ /// Get the algorithm type
+ pub fn algorithm(&self) -> &'static str {
+ match self {
+ UnifiedKeypair::Schnorr(_, _) => "Schnorr",
+ UnifiedKeypair::SlhDsa(_) => "SLH-DSA",
+ }
+ }
+
+ /// Check if this is a Schnorr keypair
+ pub fn is_schnorr(&self) -> bool {
+ matches!(self, UnifiedKeypair::Schnorr(_, _))
+ }
+
+ /// Check if this is an SLH-DSA keypair
+ pub fn is_slh_dsa(&self) -> bool {
+ matches!(self, UnifiedKeypair::SlhDsa(_))
+ }
+
+ /// Get the underlying Schnorr keypair if this is a Schnorr keypair
+ pub fn as_schnorr(&self) -> Option<(&SecretKey, &XOnlyPublicKey)> {
+ match self {
+ UnifiedKeypair::Schnorr(secret_key, public_key) => Some((secret_key, public_key)),
+ _ => None,
+ }
+ }
+
+ /// Get the underlying SLH-DSA keypair if this is an SLH-DSA keypair
+ pub fn as_slh_dsa(&self) -> Option<&KeyPair> {
+ match self {
+ UnifiedKeypair::SlhDsa(keypair) => Some(keypair),
+ _ => None,
+ }
+ }
+}
diff --git a/bip-0360/ref-impl/rust/src/lib.rs b/bip-0360/ref-impl/rust/src/lib.rs
index 629ec4e688..54a5138cd2 100644
--- a/bip-0360/ref-impl/rust/src/lib.rs
+++ b/bip-0360/ref-impl/rust/src/lib.rs
@@ -4,6 +4,7 @@ pub mod error;
use log::{debug, info, error};
use std::env;
use std::io::Write;
+use rand::{rng, RngCore};
use once_cell::sync::Lazy;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::key::{Secp256k1, Parity};
@@ -19,7 +20,11 @@ use bitcoin::{ Amount, TxOut, WPubkeyHash,
use bitcoin::p2tsh::{P2tshScriptBuf, P2tshBuilder, P2tshSpendInfo, P2tshControlBlock, P2TSH_LEAF_VERSION};
-use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn};
+use bitcoinpqc::{
+ generate_keypair, public_key_size, secret_key_size, Algorithm, KeyPair, sign, verify,
+};
+
+use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn, UnifiedKeypair};
/* Secp256k1 implements the Signing trait when it's initialized in signing mode.
It's important to note that Secp256k1 has different capabilities depending on how it's constructed:
@@ -29,7 +34,7 @@ use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn};
*/
static SECP: Lazy> = Lazy::new(Secp256k1::new);
-fn create_huffman_tree() -> (Vec<(u32, ScriptBuf)>, Option<(SecretKey, XOnlyPublicKey)>, Option) {
+fn create_huffman_tree(use_pqc: bool) -> (Vec<(u32, ScriptBuf)>, UnifiedKeypair, ScriptBuf) {
let mut total_leaf_count: u32 = 1;
if let Ok(env_value) = env::var("TOTAL_LEAF_COUNT") {
@@ -54,48 +59,53 @@ fn create_huffman_tree() -> (Vec<(u32, ScriptBuf)>, Option<(SecretKey, XOnlyPubl
debug!("Creating multi-leaf taptree with total_leaf_count: {}, leaf_of_interest: {}", total_leaf_count, leaf_of_interest);
let mut huffman_entries: Vec<(u32, ScriptBuf)> = vec![];
- let mut keypair_of_interest: Option<(SecretKey, XOnlyPublicKey)> = None;
+ let mut keypair_of_interest: Option = None;
let mut script_buf_of_interest: Option = None;
for leaf_index in 0..total_leaf_count {
-
- let keypair: (SecretKey, XOnlyPublicKey) = acquire_schnorr_keypair();
- let pubkey_bytes = keypair.1.serialize();
-
+ let keypair: UnifiedKeypair;
+ let op_code: u8;
+ if !use_pqc {
+ keypair = acquire_schnorr_keypair();
+ op_code = 0xac;
+ } else {
+ keypair = acquire_slh_dsa_keypair();
+ op_code = 0x50;
+ }
+ let pubkey_bytes = keypair.public_key_bytes();
+
// OP_PUSHBYTES_32 <32-byte xonly pubkey> OP_CHECKSIG
let mut script_buf_bytes = vec![0x20];
script_buf_bytes.extend_from_slice(&pubkey_bytes);
- script_buf_bytes.push(0xac);
- let script_buf: ScriptBuf = ScriptBuf::from_bytes(script_buf_bytes.clone());
-
- let random_weight = thread_rng().gen_range(0..total_leaf_count);
-
- let huffman_entry = (random_weight, script_buf.clone());
- huffman_entries.push(huffman_entry);
- if leaf_index == leaf_of_interest {
- keypair_of_interest = Some(keypair);
- script_buf_of_interest = Some(script_buf.clone());
- debug!("Selected leaf: weight: {}, script: {:?}", random_weight, script_buf);
- }
- };
- return (huffman_entries, keypair_of_interest, script_buf_of_interest);
+ script_buf_bytes.push(op_code);
+ let script_buf: ScriptBuf = ScriptBuf::from_bytes(script_buf_bytes.clone());
+
+ let random_weight = thread_rng().gen_range(0..total_leaf_count);
+
+ let huffman_entry = (random_weight, script_buf.clone());
+ huffman_entries.push(huffman_entry);
+ if leaf_index == leaf_of_interest {
+ keypair_of_interest = Some(keypair);
+ script_buf_of_interest = Some(script_buf.clone());
+ debug!("Selected leaf: weight: {}, script: {:?}", random_weight, script_buf);
+ }
+ }
+ return (huffman_entries, keypair_of_interest.unwrap(), script_buf_of_interest.unwrap());
}
-pub fn create_p2tsh_multi_leaf_taptree() -> TaptreeReturn {
+pub fn create_p2tsh_multi_leaf_taptree(use_pqc: bool) -> TaptreeReturn {
- let (huffman_entries, keypair_of_interest, script_buf_of_interest) = create_huffman_tree();
+ let (huffman_entries, keypair_of_interest, script_buf_of_interest) = create_huffman_tree(use_pqc);
let p2tsh_builder: P2tshBuilder = P2tshBuilder::with_huffman_tree(huffman_entries).unwrap();
+
let p2tsh_spend_info: P2tshSpendInfo = p2tsh_builder.clone().finalize().unwrap();
let quantum_root:TapNodeHash = p2tsh_spend_info.merkle_root.unwrap();
- info!("keypair_of_interest: \n\tsecret_bytes: {} \n\tpubkey: {}",
- hex::encode(keypair_of_interest.unwrap().0.secret_bytes()), // secret_bytes returns big endian
- hex::encode(keypair_of_interest.unwrap().1.serialize()), // serialize returns little endian
- );
+
let tap_tree: TapTree = p2tsh_builder.clone().into_inner().try_into_taptree().unwrap();
let mut script_leaves: ScriptLeaves = tap_tree.script_leaves();
let script_leaf = script_leaves
- .find(|leaf| leaf.script() == script_buf_of_interest.as_ref().unwrap().as_script())
+ .find(|leaf| leaf.script() == script_buf_of_interest.as_script())
.expect("Script leaf not found");
let merkle_root_node_info: NodeInfo = p2tsh_builder.clone().into_inner().try_into_node_info().unwrap();
@@ -127,7 +137,7 @@ pub fn create_p2tsh_multi_leaf_taptree() -> TaptreeReturn {
let control_block_hex: String = hex::encode(control_block.serialize());
return TaptreeReturn {
- leaf_script_priv_key_hex: hex::encode(keypair_of_interest.unwrap().0.secret_bytes()),
+ leaf_script_priv_key_hex: hex::encode(keypair_of_interest.secret_key_bytes()),
leaf_script_hex: leaf_script.to_hex_string(),
tree_root_hex: hex::encode(quantum_root.to_byte_array()),
@@ -137,7 +147,7 @@ pub fn create_p2tsh_multi_leaf_taptree() -> TaptreeReturn {
pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> TaptreeReturn {
- let (huffman_entries, keypair_of_interest, script_buf_of_interest) = create_huffman_tree();
+ let (huffman_entries, keypair_of_interest, script_buf_of_interest) = create_huffman_tree(false);
let pub_key_string = format!("02{}", p2tr_internal_pubkey_hex);
let internal_pubkey: PublicKey = pub_key_string.parse::().unwrap();
@@ -155,14 +165,14 @@ pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> Taptr
let output_key: XOnlyPublicKey = p2tr_spend_info.output_key().into();
info!("keypair_of_interest: \n\tsecret_bytes: {} \n\tpubkey: {} \n\tmerkle_root: {}",
- hex::encode(keypair_of_interest.unwrap().0.secret_bytes()), // secret_bytes returns big endian
- hex::encode(keypair_of_interest.unwrap().1.serialize()), // serialize returns little endian
+ hex::encode(keypair_of_interest.secret_key_bytes()), // secret_bytes returns big endian
+ hex::encode(keypair_of_interest.public_key_bytes()), // serialize returns little endian
merkle_root);
let tap_tree: TapTree = p2tr_builder.clone().try_into_taptree().unwrap();
let mut script_leaves: ScriptLeaves = tap_tree.script_leaves();
let script_leaf = script_leaves
- .find(|leaf| leaf.script() == script_buf_of_interest.as_ref().unwrap().as_script())
+ .find(|leaf| leaf.script() == script_buf_of_interest.as_script())
.expect("Script leaf not found");
let leaf_script = script_leaf.script().to_hex_string();
let merkle_branch: &TaprootMerkleBranch = script_leaf.merkle_branch();
@@ -181,7 +191,7 @@ pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> Taptr
info!("verify_taproot_commitment: {}", verify);
return TaptreeReturn {
- leaf_script_priv_key_hex: hex::encode(keypair_of_interest.unwrap().0.secret_bytes()),
+ leaf_script_priv_key_hex: hex::encode(keypair_of_interest.secret_key_bytes()),
leaf_script_hex: leaf_script,
tree_root_hex: hex::encode(merkle_root.to_byte_array()),
control_block_hex: control_block_hex,
@@ -238,6 +248,7 @@ pub fn pay_to_p2wpkh_tx(
leaf_script_priv_key_bytes: Vec,
spend_output_pubkey_hash_bytes: Vec,
spend_output_amount_sats: u64,
+ use_pqc: bool
) -> SpendDetails {
let mut txid_little_endian = funding_tx_id_bytes.clone(); // initially in big endian format
@@ -301,17 +312,27 @@ pub fn pay_to_p2wpkh_tx(
let spend_msg = Message::from(tapscript_sighash);
- // assumes bytes are in big endian format
- let secret_key = SecretKey::from_slice(&leaf_script_priv_key_bytes).unwrap();
+ let mut sig_bytes: Vec;
+ if use_pqc {
+ let secret_key: bitcoinpqc::SecretKey = bitcoinpqc::SecretKey::try_from_slice(
+ Algorithm::SLH_DSA_128S, &leaf_script_priv_key_bytes).unwrap();
+ let signature = sign(&secret_key, spend_msg.as_ref()).expect("Failed to sign with SLH-DSA-128S");
+ sig_bytes = signature.bytes;
+ } else {
+ // assumes bytes are in big endian format
+ let secret_key = SecretKey::from_slice(&leaf_script_priv_key_bytes).unwrap();
+
+ // Spending a p2tr UTXO thus using Schnorr signature
+ // The aux_rand parameter ensures that signing the same message with the same key produces the same signature
+ // Otherwise (without providing aux_rand), the secp256k1 library internally generates a random nonce for each signature
+ let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand(
+ &spend_msg,
+ &secret_key.keypair(&SECP),
+ &[0u8; 32] // 32 zero bytes of auxiliary random data
+ );
+ sig_bytes = signature.serialize().to_vec();
+ }
- // Spending a p2tr UTXO thus using Schnorr signature
- // The aux_rand parameter ensures that signing the same message with the same key produces the same signature
- let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand(
- &spend_msg,
- &secret_key.keypair(&SECP),
- &[0u8; 32] // 32 zero bytes of auxiliary random data
- );
- let mut sig_bytes: Vec = signature.serialize().to_vec();
sig_bytes.push(TapSighashType::All as u8);
let mut derived_witness: Witness = Witness::new();
@@ -431,7 +452,7 @@ fn compact_size(n: u64) -> Vec {
}
}
-pub fn acquire_schnorr_keypair() -> (SecretKey, XOnlyPublicKey) {
+pub fn acquire_schnorr_keypair() -> UnifiedKeypair {
/* OsRng typically draws from the OS's entropy pool (hardware random num generators, system events, etc), ie:
* 1. $ cat /proc/sys/kernel/random/entropy_avail
@@ -445,10 +466,10 @@ pub fn acquire_schnorr_keypair() -> (SecretKey, XOnlyPublicKey) {
* Interrupt timing
*/
let keypair = Keypair::new(&SECP, &mut OsRng);
-
+
let privkey: SecretKey = keypair.secret_key();
let pubkey: (XOnlyPublicKey, Parity) = XOnlyPublicKey::from_keypair(&keypair);
- (privkey, pubkey.0)
+ UnifiedKeypair::new_schnorr(privkey, pubkey.0)
}
pub fn verify_schnorr_signature_via_bytes(signature: &[u8], message: &[u8], pubkey_bytes: &[u8]) -> bool {
@@ -464,6 +485,24 @@ pub fn verify_schnorr_signature_via_bytes(signature: &[u8], message: &[u8], pubk
verify_schnorr_signature(signature, message, pubkey)
}
+pub fn verify_slh_dsa_via_bytes(signature: &[u8], message: &[u8], pubkey_bytes: &[u8]) -> bool {
+
+ // Remove possible trailing Sighash Type byte if present (SLH-DSA-128S is 7856 bytes, so 7857 would indicate SIGHASH byte)
+ let mut sig_bytes = signature.to_vec();
+ if sig_bytes.len() == 7857 {
+ sig_bytes.pop(); // Remove the last byte
+ }
+
+ info!("verify_slh_dsa_via_bytes: signature length: {:?}, message: {:?}, pubkey_bytes: {:?}",
+ sig_bytes.len(),
+ hex::encode(message),
+ hex::encode(pubkey_bytes));
+
+ let signature = bitcoinpqc::Signature::try_from_slice(Algorithm::SLH_DSA_128S, &sig_bytes).unwrap();
+ let public_key: bitcoinpqc::PublicKey = bitcoinpqc::PublicKey::try_from_slice(Algorithm::SLH_DSA_128S, pubkey_bytes).unwrap();
+ verify(&public_key, message, &signature).is_ok()
+}
+
pub fn verify_schnorr_signature(mut signature: Signature, message: Message, pubkey: XOnlyPublicKey) -> bool {
// schnorr is 64 bytes so remove possible trailing Sighash Type byte if present
@@ -498,3 +537,22 @@ pub fn verify_taproot_commitment(control_block_hex: String, output_key: XOnlyPub
return control_block.verify_taproot_commitment(&SECP, output_key, script);
}
+
+fn acquire_slh_dsa_keypair() -> UnifiedKeypair {
+ /*
+ In SPHINCS+ (underlying algorithm of SLH-DSA), the random data is used to:
+ * Initialize hash function parameters within the key generation
+ * Seed the Merkle tree construction that forms the public key
+ * Generate the secret key components that enable signing
+ */
+ let random_data = get_random_bytes(128);
+ let keypair: KeyPair = generate_keypair(Algorithm::SLH_DSA_128S, &random_data)
+ .expect("Failed to generate SLH-DSA-128S keypair");
+ UnifiedKeypair::new_slh_dsa(keypair)
+}
+
+fn get_random_bytes(size: usize) -> Vec {
+ let mut bytes = vec![0u8; size];
+ rng().fill_bytes(&mut bytes);
+ bytes
+}
diff --git a/bip-0360/ref-impl/rust/tests/p2tsh_spend.rs b/bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
index 972c0ddbf3..58d37aac1c 100644
--- a/bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
+++ b/bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
@@ -87,7 +87,8 @@ fn test_script_path_spend_signatures() {
input_leaf_script_bytes,
input_script_priv_key_bytes,
spend_output_pubkey_bytes,
- spend_output_amount_sats
+ spend_output_amount_sats,
+ false
);
assert_eq!(result.sighash.as_slice(), test_sighash_bytes.as_slice(), "sighash mismatch");
From 00e8b9b52f9331f76e2265ca3dfe7b792a5b0e47 Mon Sep 17 00:00:00 2001
From: jbride
Date: Thu, 11 Sep 2025 16:28:14 -0600
Subject: [PATCH 07/16] bip360:
1. change use of OP_SUCCESSx code from 80 -> 127
2. now able to spend from a P2TSH utxo locked with a PQC enabled
tapleaf script
---
.../tests/data/p2tsh_pqc_construction.json | 118 +++++++++---------
.../common/utils/signet_miner_loop.sh | 9 +-
...pqrh_changes.md => development_notes.adoc} | 10 +-
.../ref-impl/rust/docs/p2tsh-end-to-end.adoc | 5 +-
bip-0360/ref-impl/rust/src/lib.rs | 2 +-
5 files changed, 76 insertions(+), 68 deletions(-)
rename bip-0360/ref-impl/rust/docs/{rust_bitcoin_p2pqrh_changes.md => development_notes.adoc} (91%)
diff --git a/bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json b/bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json
index 90184cdaf2..54f9ae6293 100644
--- a/bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json
+++ b/bip-0360/ref-impl/common/tests/data/p2tsh_pqc_construction.json
@@ -19,21 +19,21 @@
"given": {
"scriptTree": {
"id": 0,
- "script": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f2450",
- "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUCCESS80",
+ "script": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f247f",
+ "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUBSTR",
"priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
"leafVersion": 192
}
},
"intermediary": {
"leafHashes": [
- "3766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869"
+ "3bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc"
],
- "merkleRoot": "3766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869"
+ "merkleRoot": "3bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc"
},
"expected": {
- "scriptPubKey": "52203766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869",
- "bip350Address": "bc1zxan04fs7spxasm7ph4rklr95ghmkewxmfcva8en5wje8tukg8p5sfm2vnw",
+ "scriptPubKey": "52203bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc",
+ "bip350Address": "bc1z8wcdhrr2mnv8xv9y4ry3hc87rv3a50q4rdhjld8jd9pfcsacmz7qg5szm8",
"scriptPathControlBlocks": [
"c1"
]
@@ -46,8 +46,8 @@
"scriptTree": [
{
"id": 0,
- "script": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f2450",
- "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUCCESS80",
+ "script": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f247f",
+ "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUBSTR",
"priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
"leafVersion": 192
},
@@ -62,16 +62,16 @@
},
"intermediary": {
"leafHashes": [
- "3766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869",
+ "3bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc",
"f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
],
- "merkleRoot": "1f8b34e43d12df49a89ec41b70f58bd57a020702ab4895bc350c88e4ab599c28"
+ "merkleRoot": "1619ce6d22a46dea045c4adf7f5f33d6810d00d0e9c8a4c7ba35db37b915c604"
},
"expected": {
- "scriptPubKey": "52201f8b34e43d12df49a89ec41b70f58bd57a020702ab4895bc350c88e4ab599c28",
- "bip350Address": "bc1zr79nfepazt05n2y7csdhpavt64aqypcz4dyft0p4pjywf26ens5qjlm4a6",
+ "scriptPubKey": "52201619ce6d22a46dea045c4adf7f5f33d6810d00d0e9c8a4c7ba35db37b915c604",
+ "bip350Address": "bc1zzcvuumfz53k75pzuft0h7hen66qs6qxsa8y2f3a6xhdn0wg4cczq0h84sj",
"scriptPathControlBlocks": [
- "c13766faa61e804dd86fc1bd476f8cb445f76cb8db4e19d3e67474b275f2c83869",
+ "c13bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc",
"c1f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
]
}
@@ -83,8 +83,8 @@
"scriptTree": [
{
"id": 0,
- "script": "029000b275201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f2450",
- "asm": "144 OP_CHECKSEQUENCEVERIFY OP_DROP 1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUCCESS80",
+ "script": "029000b275201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f247f",
+ "asm": "144 OP_CHECKSEQUENCEVERIFY OP_DROP 1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUBSTR",
"priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
"description": "Alice takes the money after waiting 1 day",
"leafVersion": 192
@@ -92,7 +92,7 @@
{
"id": 1,
"script": "a8206c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd533388204c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd",
- "asm": "OP_SHA256 6c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd5333 OP_EQUALVERIFY 4c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd OP_SUCCESS80",
+ "asm": "OP_SHA256 6c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd5333 OP_EQUALVERIFY 4c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd OP_SUBSTR",
"priv_key": "49a4214f386240d97ea68efb4268043fd5a55208dcdac18ce5bd3332b8e488944c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd",
"description": "Bob takes the money whenever he wishes to by revealing the preimage_hash",
"leafVersion": 192
@@ -101,16 +101,16 @@
},
"intermediary": {
"leafHashes": [
- "a9745ac96d4f3702b78751f1e08f3040fbe6347e7b4f520d22d3f907730cbb7e",
- "bf117a8095de4ae4727e8741061b40d29b8e0fe03e225cb603aa5edb54dcd441"
+ "cfd5fc07ac39947cba799e14f933f20e7c233dea72dc2792f5547c58cdce743e",
+ "a9745ac96d4f3702b78751f1e08f3040fbe6347e7b4f520d22d3f907730cbb7e"
],
- "merkleRoot": "8488b7529e5590b49d2ea381e2949870670e59f2229ec4ae8c12f28ba1af26c2"
+ "merkleRoot": "2794771cd51f215ba3a19fbcdf08c771edb7de782a0c34457e0e9be5d0e4008f"
},
"expected": {
- "scriptPubKey": "52208488b7529e5590b49d2ea381e2949870670e59f2229ec4ae8c12f28ba1af26c2",
- "bip350Address": "bc1zsjytw5572kgtf8fw5wq799ycwpnsuk0jy20vft5vzteghgd0ympqkyk6vx",
+ "scriptPubKey": "52202794771cd51f215ba3a19fbcdf08c771edb7de782a0c34457e0e9be5d0e4008f",
+ "bip350Address": "bc1zy728w8x4rus4hgapn77d7zx8w8km0hnc9gxrg3t7p6d7t58yqz8sg0nccq",
"scriptPathControlBlocks": [
- "c1bf117a8095de4ae4727e8741061b40d29b8e0fe03e225cb603aa5edb54dcd441",
+ "c1cfd5fc07ac39947cba799e14f933f20e7c233dea72dc2792f5547c58cdce743e",
"c1a9745ac96d4f3702b78751f1e08f3040fbe6347e7b4f520d22d3f907730cbb7e"
]
}
@@ -122,8 +122,8 @@
"scriptTree": [
{
"id": 0,
- "script": "20e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f534450",
- "asm": "e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f5344 OP_SUCCESS80",
+ "script": "20e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f53447f",
+ "asm": "e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f5344 OP_SUBSTR",
"priv_key": "a695d8a1351774d59ed1b980462c2ab8d58b3b7a48f55b114c6a39980dc1c13ee6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f5344",
"leafVersion": 192
},
@@ -138,16 +138,16 @@
},
"intermediary": {
"leafHashes": [
- "2d13d635cf05ee7de0b6ea933efff5dce7f3aff9cc12a7f743fd561f6147c561",
+ "9de7eeded7832c28c6f80de76904dd79f98fd302747823b5bc5be440186b0c6d",
"2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
],
- "merkleRoot": "8d14ecbad89fbe37cf010ea1cf016b015fdf53aaeb298d809b9a689d50984c02"
+ "merkleRoot": "5112b3edfd2c0b717491e9d4888ed2d5dfeaa25115143540e0a08516b68c008c"
},
"expected": {
- "scriptPubKey": "52208d14ecbad89fbe37cf010ea1cf016b015fdf53aaeb298d809b9a689d50984c02",
- "bip350Address": "bc1z352wewkcn7lr0ncpp6su7qttq90a75a2av5cmqymnf5f65ycfspqdmwdrz",
+ "scriptPubKey": "52205112b3edfd2c0b717491e9d4888ed2d5dfeaa25115143540e0a08516b68c008c",
+ "bip350Address": "bc1z2yft8m0a9s9hzay3a82g3rkj6h074gj3z52r2s8q5zz3dd5vqzxqngpk2w",
"scriptPathControlBlocks": [
- "c12d13d635cf05ee7de0b6ea933efff5dce7f3aff9cc12a7f743fd561f6147c561",
+ "c19de7eeded7832c28c6f80de76904dd79f98fd302747823b5bc5be440186b0c6d",
"c12cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
]
}
@@ -159,23 +159,23 @@
"scriptTree": [
{
"id": 0,
- "script": "201d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc50",
- "asm": "1d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc OP_SUCCESS80",
+ "script": "201d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc7f",
+ "asm": "1d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc OP_SUBSTR",
"priv_key": "2b339b5055fad695f0595d5581fa087455854f64c5443c03d5b4ca53549f12a41d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc",
"leafVersion": 192
},
[
{
"id": 1,
- "script": "20a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad50",
- "asm": "a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad OP_SUCCESS80",
+ "script": "20a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad7f",
+ "asm": "a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad OP_SUBSTR",
"priv_key": "ec29d60c1be9263602906499b5e3c1de9e36cc88cec31154210191a578b92e9da3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad",
"leafVersion": 192
},
{
"id": 2,
- "script": "20e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f3650",
- "asm": "e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f36 OP_SUCCESS80",
+ "script": "20e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f367f",
+ "asm": "e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f36 OP_SUBSTR",
"priv_key": "da04d13f706a6e0d22ac0db7c361f1aaa706a27043efca4edfecfed3125238e1e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f36",
"leafVersion": 192
}
@@ -184,19 +184,19 @@
},
"intermediary": {
"leafHashes": [
- "35d1f75d65fbe192b8c5cbe44097ba5d9975e61789af49214e4a0709e4b9a8e5",
- "4350873c0939e615520d9d1bf9e42c7522d0b9cafe30fc575fa91ced72eb5d78",
- "6a198cc871eb9cb184b78c23b0b660541f8bc6e7854e69d21d1bcafb2da0ef17"
+ "0840c39e59eda6c9deee687a480cb169130c2f053ed2eb3134511ec1cfd8a2c7",
+ "837ef6677aeb0df2b0de47f45024684cc6ca03bda10fa30bb5bc05a94beb8dd1",
+ "b2a5304f678cc5a2ed51feb377dd0a609bd22ec979cc608bfcf884d0f8e6f93a"
],
- "merkleRoot": "6e5b1eae3ea3cab9af523a878e823d471ca86c12ddfe253eb10d412bb8b084cd"
+ "merkleRoot": "eaf8f557fdb9673de7bb9bad7e7452da9f44a3e65133fdadf2849c55cfb3cf5b"
},
"expected": {
- "scriptPubKey": "52206e5b1eae3ea3cab9af523a878e823d471ca86c12ddfe253eb10d412bb8b084cd",
- "bip350Address": "bc1zded3at37509tnt6j82rcaq3aguw2smqjmhlz2043p4qjhw9ssnxs48f2e5",
+ "scriptPubKey": "5220eaf8f557fdb9673de7bb9bad7e7452da9f44a3e65133fdadf2849c55cfb3cf5b",
+ "bip350Address": "bc1zatu024lah9nnmeamnwkhuazjm205fglx2yelmt0jsjw9tnaneadszq7wg7",
"scriptPathControlBlocks": [
- "c1e1e25e1e72b47fe03f70dc934b798caec4aaad6861e6e698cee786a1d6248527",
- "c16a198cc871eb9cb184b78c23b0b660541f8bc6e7854e69d21d1bcafb2da0ef1735d1f75d65fbe192b8c5cbe44097ba5d9975e61789af49214e4a0709e4b9a8e5",
- "c14350873c0939e615520d9d1bf9e42c7522d0b9cafe30fc575fa91ced72eb5d7835d1f75d65fbe192b8c5cbe44097ba5d9975e61789af49214e4a0709e4b9a8e5"
+ "c1837ef6677aeb0df2b0de47f45024684cc6ca03bda10fa30bb5bc05a94beb8dd1b2a5304f678cc5a2ed51feb377dd0a609bd22ec979cc608bfcf884d0f8e6f93a",
+ "c10840c39e59eda6c9deee687a480cb169130c2f053ed2eb3134511ec1cfd8a2c7b2a5304f678cc5a2ed51feb377dd0a609bd22ec979cc608bfcf884d0f8e6f93a",
+ "c118781f42f664d67acaf0ce7c6826437e5440eb1789f232af05e9a09fdf547903"
]
}
},
@@ -207,23 +207,23 @@
"scriptTree": [
{
"id": 0,
- "script": "20409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c50",
- "asm": "409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c OP_SUCCESS80",
+ "script": "20409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c7f",
+ "asm": "409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c OP_SUBSTR",
"priv_key": "c6ec3801b04afa40be6f77f3f7a7b3b7cb8cfe233b0263fb70e2f087b21397c1409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c",
"leafVersion": 192
},
[
{
"id": 1,
- "script": "209f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a50",
- "asm": "9f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a OP_SUCCESS80",
+ "script": "209f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a7f",
+ "asm": "9f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a OP_SUBSTR",
"priv_key": "7524bca170d54231f1246f30fb00f9da2208b1363ba5a7bbaf11fc3b0309e9519f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a",
"leafVersion": 192
},
{
"id": 2,
- "script": "20c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f50",
- "asm": "c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f OP_SUCCESS80",
+ "script": "20c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f7f",
+ "asm": "c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f OP_SUBSTR",
"priv_key": "d9d0f17d630b6a538e6a8a036373e7b9e5023a4c08f72dd8c3ef59288b98c079c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f",
"leafVersion": 192
}
@@ -232,19 +232,19 @@
},
"intermediary": {
"leafHashes": [
- "0102b0ba8fe443b500865e4e87e806d0d2fab23040b617b80bba31d0ea8b2164",
- "52fd4722ad69cb1d0c5e1b4830507c533d33389b39d86e20f2aff4991ecf7e27",
- "61cddcc41e5075a45f69bcc9486d7babdeb1bf9edcb0f292957f7f158026ae3a"
+ "52e9326c2bf04d926b7e9f6c7645dd853f3f007b870201de9b814952750c9310",
+ "dcef3ce86cc8cea78c9e00f3d9ef58360cb6ed3cb90ec62efe00b9703854ba5c",
+ "ddb521a44e33ff4974e618d8b8b7794275b7dc754d847c537404f84330454361"
],
- "merkleRoot": "c82b934c5fd94054ab11204ae754ae3e6395abb23a6629bd6f93ede4cd5c1756"
+ "merkleRoot": "51e3c1151ba73d9efce801837773331bf9030977242f62dfeb6756795f482409"
},
"expected": {
- "scriptPubKey": "5220c82b934c5fd94054ab11204ae754ae3e6395abb23a6629bd6f93ede4cd5c1756",
- "bip350Address": "bc1zeq4exnzlm9q9f2c3yp9ww49w8e3et2aj8fnzn0t0j0k7fn2uzatq6vc68m",
+ "scriptPubKey": "522051e3c1151ba73d9efce801837773331bf9030977242f62dfeb6756795f482409",
+ "bip350Address": "bc1z283uz9gm5u7eal8gqxphwuenr0usxzthyshk9hltvat8jh6gysys28twnc",
"scriptPathControlBlocks": [
- "c11bf3e4588edf111915ed79885a5e56a2dd2f13ae375846c56b8e0723a5077966",
- "c161cddcc41e5075a45f69bcc9486d7babdeb1bf9edcb0f292957f7f158026ae3a0102b0ba8fe443b500865e4e87e806d0d2fab23040b617b80bba31d0ea8b2164",
- "c152fd4722ad69cb1d0c5e1b4830507c533d33389b39d86e20f2aff4991ecf7e270102b0ba8fe443b500865e4e87e806d0d2fab23040b617b80bba31d0ea8b2164"
+ "c1dcef3ce86cc8cea78c9e00f3d9ef58360cb6ed3cb90ec62efe00b9703854ba5cddb521a44e33ff4974e618d8b8b7794275b7dc754d847c537404f84330454361",
+ "c152e9326c2bf04d926b7e9f6c7645dd853f3f007b870201de9b814952750c9310ddb521a44e33ff4974e618d8b8b7794275b7dc754d847c537404f84330454361",
+ "c1b45680a7821e4b9450096ab38adbc3c99225af8f6c7ec121a0a5f1ae02893ba3"
]
}
}
diff --git a/bip-0360/ref-impl/common/utils/signet_miner_loop.sh b/bip-0360/ref-impl/common/utils/signet_miner_loop.sh
index 93c687d7b9..b5882cb8e8 100755
--- a/bip-0360/ref-impl/common/utils/signet_miner_loop.sh
+++ b/bip-0360/ref-impl/common/utils/signet_miner_loop.sh
@@ -2,13 +2,16 @@
# Invokes mining simulator a configurable number of times
-
+if [ -z "${P2TSH_ADDR}" ]; then
+ echo "Error: Environment variable P2TSH_ADDR needs to be set"
+ exit 1
+fi
BITCOIN_SOURCE_DIR=${BITCOIN_SOURCE_DIR:-$HOME/bitcoin}
# path to bitcoin.conf for signet
-BITCOIN_CONF_FILE_PATH=${BITCOIN_CONF_FILE_PATH:-$HOME/configs/bitcoin.conf.signet}
+BITCOIN_CONF_FILE_PATH=${BITCOIN_CONF_FILE_PATH:-$HOME/anduro-360/configs/bitcoin.conf.signet}
# Set default LOOP_COUNT to 110 if not set
LOOP_COUNT=${LOOP_COUNT:-110}
@@ -32,6 +35,6 @@ do
$BITCOIN_SOURCE_DIR/contrib/signet/miner --cli "bitcoin-cli -conf=$BITCOIN_CONF_FILE_PATH" generate \
--address $P2TSH_ADDR \
--grind-cmd "$BITCOIN_SOURCE_DIR/build/bin/bitcoin-util grind" \
- --poolid $POOL_ID \
+ --poolid "$POOL_ID" \
--min-nbits --set-block-time $(date +%s)
done
diff --git a/bip-0360/ref-impl/rust/docs/rust_bitcoin_p2pqrh_changes.md b/bip-0360/ref-impl/rust/docs/development_notes.adoc
similarity index 91%
rename from bip-0360/ref-impl/rust/docs/rust_bitcoin_p2pqrh_changes.md
rename to bip-0360/ref-impl/rust/docs/development_notes.adoc
index 5ff2255e2b..e7debcd239 100644
--- a/bip-0360/ref-impl/rust/docs/rust_bitcoin_p2pqrh_changes.md
+++ b/bip-0360/ref-impl/rust/docs/development_notes.adoc
@@ -1,4 +1,12 @@
-*P2QRH specific changes to rust-bitcoin*
+
+== bitcoin core
+
+=== Two Different Size Limits:
+
+* *MAX_SCRIPT_ELEMENT_SIZE* (in interpreter.cpp line 1882) - This is a consensus rule that limits individual stack elements to 520 bytes. This is what's currently blocking your SLH-DSA signature.
+* *MAX_STANDARD_P2TSH_STACK_ITEM_SIZE* (in policy.h) - This is a policy rule that limits P2TSH stack items to 80 bytes (or 8000 bytes with your change) for standardness.
+
+== P2TSH changes to rust-bitcoin
# 1. p2qrh module
diff --git a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
index dc49263818..7be19e4e40 100644
--- a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -143,7 +143,7 @@ $ b-cli decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
-----
+
NOTE: If not using PQC, notice that this script commits to a Schnorr 32-byte x-only public key.
-If using PQC, this script commits to a Schnorr 32-byte SLH-DSA pub key and a OP_SUCCESS80 (represented as `OP_RESERVED`)
+If using PQC, this script commits to a Schnorr 32-byte SLH-DSA pub key and a OP_SUCCESS127 (represented as `OP_SUBSTR`)
. fund this P2TSH address with the coinbase reward of a newly generated block:
@@ -293,9 +293,6 @@ $ b-cli -generate 1
.. signet:
+
------
-.. Signet
-+
If on `signet` network, then execute the following:
+
-----
diff --git a/bip-0360/ref-impl/rust/src/lib.rs b/bip-0360/ref-impl/rust/src/lib.rs
index 54a5138cd2..23e8bd2bee 100644
--- a/bip-0360/ref-impl/rust/src/lib.rs
+++ b/bip-0360/ref-impl/rust/src/lib.rs
@@ -69,7 +69,7 @@ fn create_huffman_tree(use_pqc: bool) -> (Vec<(u32, ScriptBuf)>, UnifiedKeypair,
op_code = 0xac;
} else {
keypair = acquire_slh_dsa_keypair();
- op_code = 0x50;
+ op_code = 0x7f; // OP_SUBSTR
}
let pubkey_bytes = keypair.public_key_bytes();
From 396d3ca25679323a734f3a6d69380a0a80b7e621 Mon Sep 17 00:00:00 2001
From: jbride
Date: Sat, 20 Sep 2025 06:39:30 -0600
Subject: [PATCH 08/16] Using crates from self-hosted registry
---
bip-0360/ref-impl/rust/.cargo/config.toml | 2 +
bip-0360/ref-impl/rust/Cargo.lock | 194 +++++++++++-------
bip-0360/ref-impl/rust/Cargo.toml | 10 +-
.../ref-impl/rust/docs/p2tsh-end-to-end.adoc | 4 +-
.../rust/examples/p2tsh-end-to-end.sh | 30 +++
5 files changed, 155 insertions(+), 85 deletions(-)
create mode 100644 bip-0360/ref-impl/rust/.cargo/config.toml
create mode 100644 bip-0360/ref-impl/rust/examples/p2tsh-end-to-end.sh
diff --git a/bip-0360/ref-impl/rust/.cargo/config.toml b/bip-0360/ref-impl/rust/.cargo/config.toml
new file mode 100644
index 0000000000..04bd2901e0
--- /dev/null
+++ b/bip-0360/ref-impl/rust/.cargo/config.toml
@@ -0,0 +1,2 @@
+[registries.kellnr-denver-space]
+index = "sparse+https://crates.denver.space/api/v1/crates/"
diff --git a/bip-0360/ref-impl/rust/Cargo.lock b/bip-0360/ref-impl/rust/Cargo.lock
index ef5f88d5bd..0b3ff17591 100644
--- a/bip-0360/ref-impl/rust/Cargo.lock
+++ b/bip-0360/ref-impl/rust/Cargo.lock
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.6.19"
+version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
+checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -43,18 +43,18 @@ dependencies = [
[[package]]
name = "anstyle-query"
-version = "1.1.3"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
+checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
-version = "3.0.9"
+version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
+checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
@@ -63,9 +63,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.98"
+version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]]
name = "arrayvec"
@@ -112,6 +112,8 @@ dependencies = [
[[package]]
name = "bitcoin"
version = "0.32.6"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "b1831b23596c0d9d0d3cb01b059027718add9748f4c1167455c748d632124379"
dependencies = [
"base58ck",
"bech32",
@@ -164,6 +166,8 @@ dependencies = [
[[package]]
name = "bitcoinpqc"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf74aafaea8106c29daed19657c3952a4f297d44fbd437d5dd697772bb463fc2"
dependencies = [
"bindgen",
"bitmask-enum",
@@ -192,10 +196,11 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.25"
+version = "1.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
+checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44"
dependencies = [
+ "find-msvc-tools",
"shlex",
]
@@ -210,9 +215,9 @@ dependencies = [
[[package]]
name = "cfg-if"
-version = "1.0.1"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "clang-sys"
@@ -269,6 +274,12 @@ dependencies = [
"log",
]
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
+
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -289,7 +300,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi",
- "wasi 0.14.3+wasi-0.2.4",
+ "wasi 0.14.7+wasi-0.2.4",
]
[[package]]
@@ -342,9 +353,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
-version = "0.2.14"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
+checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"log",
@@ -355,9 +366,9 @@ dependencies = [
[[package]]
name = "jiff-static"
-version = "0.2.14"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
+checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
@@ -366,9 +377,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.174"
+version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libloading"
@@ -382,15 +393,15 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.27"
+version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
-version = "2.7.4"
+version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "minimal-lexical"
@@ -401,6 +412,8 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniscript"
version = "13.0.0"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "babfe2517c5b56123c43a10843ddc8ea85773d4fc04611d81f6a634c270b386e"
dependencies = [
"bech32",
"bitcoin",
@@ -448,9 +461,9 @@ dependencies = [
[[package]]
name = "portable-atomic"
-version = "1.11.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
+checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
@@ -472,9 +485,9 @@ dependencies = [
[[package]]
name = "prettyplease"
-version = "0.2.34"
+version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
@@ -482,9 +495,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.95"
+version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
@@ -565,9 +578,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.11.1"
+version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
@@ -577,9 +590,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.9"
+version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
@@ -588,9 +601,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "rustc-hash"
@@ -648,18 +661,28 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.219"
+version = "1.0.225"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.219"
+version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
dependencies = [
"proc-macro2",
"quote",
@@ -668,14 +691,15 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.140"
+version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
+ "serde_core",
]
[[package]]
@@ -686,9 +710,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
-version = "2.0.101"
+version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
@@ -697,18 +721,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.12"
+version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.12"
+version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
@@ -717,9 +741,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
-version = "1.0.18"
+version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "utf8parse"
@@ -735,28 +759,44 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
-version = "0.14.3+wasi-0.2.4"
+version = "0.14.7+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
+dependencies = [
+ "wasip2",
+]
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
[[package]]
name = "windows-sys"
-version = "0.59.0"
+version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
-version = "0.52.6"
+version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
+ "windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
@@ -769,72 +809,72 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.52.6"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen"
-version = "0.45.0"
+version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
-version = "0.8.26"
+version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
+checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.26"
+version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
+checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
diff --git a/bip-0360/ref-impl/rust/Cargo.toml b/bip-0360/ref-impl/rust/Cargo.toml
index 95909c0cd8..97975fcd7f 100644
--- a/bip-0360/ref-impl/rust/Cargo.toml
+++ b/bip-0360/ref-impl/rust/Cargo.toml
@@ -6,10 +6,10 @@ edition = "2024"
[dependencies]
# Dev version of miniscript crate re-exports bitcoin 0.32.6
-miniscript = "13.0.0"
+miniscript = { version="13.0.0", registry="kellnr-denver-space" }
# During local dev, ensure version used here matches the version of the forked local library (referenced in: patch.crates-io)
-bitcoin = { version="0.32.6", features = ["rand-std", "serde"] }
+bitcoin = { version="0.32.6", features = ["rand-std", "serde"], registry = "kellnr-denver-space" }
bitcoinpqc = { version="0.2.0", features = ["serde"] }
@@ -30,9 +30,9 @@ rand = "0.9"
# Verify:
# cargo update
# cargo tree -p bitcoin | more
-bitcoin = { path = "./rust-bitcoin/bitcoin" }
+# bitcoin = { path = "./rust-bitcoin/bitcoin" }
# cargo tree -p miniscript | more
-miniscript = { path = "./rust-miniscript" }
+#miniscript = { path = "./rust-miniscript" }
-bitcoinpqc = { path = "./libbitcoinpqc" }
+# bitcoinpqc = { path = "./libbitcoinpqc" }
diff --git a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
index 7be19e4e40..d3d12729e0 100644
--- a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -92,9 +92,7 @@ $ export W_NAME=anduro
$ b-cli -named createwallet \
wallet_name=$W_NAME \
descriptors=true \
- load_on_startup=true \
- blank=true
-
+ load_on_startup=true
-----
== Fund P2TSH UTXO
diff --git a/bip-0360/ref-impl/rust/examples/p2tsh-end-to-end.sh b/bip-0360/ref-impl/rust/examples/p2tsh-end-to-end.sh
new file mode 100644
index 0000000000..26273f40d2
--- /dev/null
+++ b/bip-0360/ref-impl/rust/examples/p2tsh-end-to-end.sh
@@ -0,0 +1,30 @@
+
+export BITCOIN_SOURCE_DIR=$HOME/bitcoin
+export W_NAME=anduro
+export USE_PQC=false
+export TOTAL_LEAF_COUNT=5
+export LEAF_OF_INTEREST=4
+
+b-cli -named createwallet \
+ wallet_name=$W_NAME \
+ descriptors=true \
+ load_on_startup=true
+
+export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tsh_construction ) \
+ && echo $BITCOIN_ADDRESS_INFO | jq -r .
+
+export QUANTUM_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tree_root_hex' ) \
+ && export LEAF_SCRIPT_PRIV_KEY_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_priv_key_hex' ) \
+ && export LEAF_SCRIPT_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_hex' ) \
+ && export CONTROL_BLOCK_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.control_block_hex' ) \
+ && export FUNDING_SCRIPT_PUBKEY=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.script_pubkey_hex' ) \
+ && export P2TSH_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.bech32m_address' )
+
+b-cli decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
+
+export COINBASE_REWARD_TX_ID=$( b-cli -named generatetoaddress 1 $P2TSH_ADDR 5 | jq -r '.[]' ) \
+ && echo $COINBASE_REWARD_TX_ID
+
+export P2TSH_DESC=$( b-cli getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
+ && echo $P2TSH_DESC \
+ && b-cli scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]'
From 69caea5a81d017bb69f15bc526b7560296eb3ce5 Mon Sep 17 00:00:00 2001
From: jbride
Date: Mon, 22 Sep 2025 12:57:37 -0600
Subject: [PATCH 09/16] bip360: clarifications to p2tsh-end-to-end
documentation
---
bip-0360/ref-impl/rust/Cargo.lock | 75 ++++++
bip-0360/ref-impl/rust/Cargo.toml | 9 +-
bip-0360/ref-impl/rust/README.md | 33 ++-
.../ref-impl/rust/docs/p2tsh-end-to-end.adoc | 252 ++++++++++++------
bip-0360/ref-impl/rust/src/lib.rs | 24 +-
5 files changed, 281 insertions(+), 112 deletions(-)
diff --git a/bip-0360/ref-impl/rust/Cargo.lock b/bip-0360/ref-impl/rust/Cargo.lock
index 0b3ff17591..d10d7c1450 100644
--- a/bip-0360/ref-impl/rust/Cargo.lock
+++ b/bip-0360/ref-impl/rust/Cargo.lock
@@ -2,6 +2,18 @@
# It is not intended for manual editing.
version = 4
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -83,6 +95,50 @@ dependencies = [
"bitcoin_hashes",
]
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "bdk_chain"
+version = "0.23.2"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "0c3b6dc7f800ba667d58729f0fbccf55cd6df9b031a39685fc42ebb00afe0c55"
+dependencies = [
+ "bdk_core",
+ "bitcoin",
+ "miniscript",
+ "serde",
+]
+
+[[package]]
+name = "bdk_core"
+version = "0.6.2"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "a31fd2c38b90b16d97da383ff281f3f5ae636a7767cb067af6c0d90364c30fb6"
+dependencies = [
+ "bitcoin",
+ "hashbrown",
+ "serde",
+]
+
+[[package]]
+name = "bdk_wallet"
+version = "3.0.0-alpha.0-p2tsh-0"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "791cded5cd02d230ea775e1e2c6eb62baf79e335863f9c25332d18e956fbc998"
+dependencies = [
+ "bdk_chain",
+ "bitcoin",
+ "bitcoinpqc",
+ "miniscript",
+ "rand_core 0.6.4",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "bech32"
version = "0.11.0"
@@ -116,6 +172,7 @@ source = "sparse+https://crates.denver.space/api/v1/crates/"
checksum = "b1831b23596c0d9d0d3cb01b059027718add9748f4c1167455c748d632124379"
dependencies = [
"base58ck",
+ "base64",
"bech32",
"bitcoin-internals",
"bitcoin-io",
@@ -309,6 +366,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "serde",
+]
+
[[package]]
name = "hex"
version = "0.4.3"
@@ -417,6 +484,7 @@ checksum = "babfe2517c5b56123c43a10843ddc8ea85773d4fc04611d81f6a634c270b386e"
dependencies = [
"bech32",
"bitcoin",
+ "serde",
]
[[package]]
@@ -446,6 +514,7 @@ name = "p2tsh-ref"
version = "0.1.0"
dependencies = [
"anyhow",
+ "bdk_wallet",
"bitcoin",
"bitcoinpqc",
"env_logger",
@@ -751,6 +820,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
diff --git a/bip-0360/ref-impl/rust/Cargo.toml b/bip-0360/ref-impl/rust/Cargo.toml
index 97975fcd7f..d66dbca5ef 100644
--- a/bip-0360/ref-impl/rust/Cargo.toml
+++ b/bip-0360/ref-impl/rust/Cargo.toml
@@ -6,13 +6,16 @@ edition = "2024"
[dependencies]
# Dev version of miniscript crate re-exports bitcoin 0.32.6
+# view configuration for "kellnr-denver-space":
+# cat .cargo/config.toml
miniscript = { version="13.0.0", registry="kellnr-denver-space" }
-
-# During local dev, ensure version used here matches the version of the forked local library (referenced in: patch.crates-io)
-bitcoin = { version="0.32.6", features = ["rand-std", "serde"], registry = "kellnr-denver-space" }
+bitcoin = { version="0.32.6", features = ["rand-std", "serde", "base64"], registry = "kellnr-denver-space" }
bitcoinpqc = { version="0.2.0", features = ["serde"] }
+# BDK Wallet with P2TSH support
+bdk_wallet = { version = "3.0.0-alpha.0-p2tsh-0", registry = "kellnr-denver-space" }
+
env_logger = "0.11.5"
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
diff --git a/bip-0360/ref-impl/rust/README.md b/bip-0360/ref-impl/rust/README.md
index 99cbe625b5..4d67378eb7 100644
--- a/bip-0360/ref-impl/rust/README.md
+++ b/bip-0360/ref-impl/rust/README.md
@@ -4,20 +4,10 @@
This rust project contains the test vectors for BIP-360
-## Local Development
+## Run Test Vectors
These test vectors are being developed in conjunction with forks of [rust-bitcoin](https://github.com/jbride/rust-bitcoin/tree/p2qrh) and [rust-miniscript](https://github.com/jbride/rust-miniscript/tree/p2qrh) customized with p2tsh functionality.
-As such, these test vectors assume the presence of these customized forks cloned to your local environment.
-
-1. create soft-link to your `rust-bitcoin` clone:
- ```
- $ ln -s /path/to/git/clone/of/rust-bitcoin
- ```
-1. create soft-link to your `rust-miniscript` clone:
- ```
- $ ln -s /path/to/git/clone/of/rust-miniscript
- ```
1. environment variables
```
@@ -32,3 +22,24 @@ As such, these test vectors assume the presence of these customized forks cloned
$ cargo test test_p2tsh_single_leaf_script_tree -- --nocapture
```
+## Local Development
+
+
+All P2TSH/PQC enabled bitcoin crates are temporarily available in a custom crate registry at: `https://crates.denver.space`.
+These crates will be made available in `crates.io` in the near future.
+
+Subsequently, you will need to execute the following at the root of your rust workspace:
+
+```bash
+mkdir .cargo \
+ && echo '[registries.kellnr-denver-space]
+index = "sparse+https://crates.denver.space/api/v1/crates/"' > .cargo/config
+```
+
+Afterwards, for all P2TSH/PQC enabled dependencies used in your project, include a "registry" similar to the following:
+
+```bash
+bitcoin = { version="0.32.6", registry = "kellnr-denver-space" }
+```
+
+
diff --git a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
index d3d12729e0..63c3668026 100644
--- a/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -7,100 +7,63 @@
:numbered:
-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.
+This tutorial is inspired by the link:https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature[script-path-spend-signature] example of the _learnmeabitcoin_ tutorial.
It is customized to create, fund and spend from a P2TSH UTXO to a P2WPKH address.
+In addition, this tutorial allows for the (un)locking mechanism of the script to optionally use _Post Quantum Cryptography_ (PQC).
+
+The purpose of this tutorial is to demonstrate construction and spending of a link:https://github.com/cryptoquick/bips/blob/p2qrh/bip-0360.mediawiki[bip-360] `p2tsh` UTXO (optionally using _Post-Quantum Cryptography_).
+
+The steps outlined in this tutorial are executed using a custom Bitcoin Core instance running either in `regtest` or `signet`.
== Pre-reqs
=== Bitcoin Core
-The link:https://github.com/jbride/bitcoin/tree/p2tsh[p2tsh branch] of bitcoin core is needed.
+If participating in a workshop, your instructor will provide a bitcoin environment.
+Related: your instructor should also provide you with a wallet.
-Build instructions for the `p2tsh` branch are the same as `master` and is documented link:https://github.com/bitcoin/bitcoin/blob/master/doc/build-unix.md[here].
+Otherwise, if running this tutorial on your own, follow the instructions in the appendix of this doc: <>.
-As such, the following is an example series of steps (on a Fedora 42 host) to compile and run the `p2tsh` branch of bitcoin core:
-. Set BITCOIN_SOURCE_DIR
-+
------
-$ export BITCOIN_SOURCE_DIR=/path/to/root/dir/of/cloned/bitcoin/source
------
+=== Shell Environment
-. build
+. *docker / podman*
+
------
-$ cmake -B build \
- -DWITH_ZMQ=ON \
- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
- -DBUILD_BENCH=ON \
- -DBUILD_DAEMON=ON \
- -DSANITIZERS=address,undefined
-
-$ cmake --build build -j$(nproc)
------
-
-. run in either `regtest` or `signet` mode:
-
-.. regtest:
+NOTE: If you have built the custom `p2tsh` enabled Bitcoin Core, you do not need docker (nor podman) installed. Skip this section.
+
------
-$ ./build/bin/bitcoind -daemon=0 -regtest=1 -txindex -prune=0
------
+This tutorial makes use of a `p2tsh` enabled _bitcoin-cli_ utility.
+This utility is made available as a docker (or podman) container.
+Ensure your host machine has either docker or podman installed.
-.. signet:
+. *bitcoin-cli* command line utility:
+
------
-$ ./build/bin/bitcoind -daemon=0 -signet=1 -txindex -prune=0
------
-+
-NOTE: If running in `signet`, your bitcoin core will need to be configured with the `signetchallenge` property.
-link:https://edil.com.br/blog/creating-a-custom-bitcoin-signet[This tutorial] provides a nice overview of the topic.
+NOTE: If you have built the custom `p2tsh` enabled Bitcoin Core, you can simply use the `bitcoin-cli` utility found in the `build/bin/` directory. No need to use the _dockerized_ utility described below.
-=== Shell Environment
-
-. *b-cli* command line alias:
-+
-Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node.
-+
-ie: for signet
+.. You will need a `bitcoin-cli` binary that is `p2tsh` enabled.
+For this purpose, a docker container with this `bitcoin-cli` utility is provided:
+
-----
-$ alias b-cli=bitcoin-cli -conf=$HOME/configs/bitcoin.conf.signet
+$ docker pull quay.io/jbride2000/bitcoin-cli:p2tsh-pqc-0.0.1
-----
-. *jq*: ensure json parsing utility is installed and available via your $PATH.
-. *awk* : standard utility for all Linux distros (often packaged as `gawk`).
-
-=== Bitcoin Core Wallet
-
-This tutorial assumes that a bitcoin core wallet is available.
-
-. Set an environment variable specific to your Bitcoin network environment (regtest, signet, etc)
+.. Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node.
+
-----
-$ export BITCOIN_NETWORK=regtest
+$ alias b-cli='docker run --rm --network host bitcoin-cli:p2tsh-pqc-0.0.1 -rpcconnect=192.168.122.1 -rpcport=18443 -rpcuser=regtest -rpcpassword=regtest'
-----
-For example, the following would be sufficient:
-
------
-
-$ export W_NAME=anduro
-
-$ b-cli -named createwallet \
- wallet_name=$W_NAME \
- descriptors=true \
- load_on_startup=true
------
+. *jq*: ensure json parsing utility is link:https://jqlang.org/download/[installed] and available via your $PATH.
+. *awk* : standard utility for all Linux distros (often packaged as `gawk`).
+. *Rust* development environment with _cargo_ utility. Use link:https://rustup[Rustup] to install.
-== Fund P2TSH UTXO
+== Create & Fund P2TSH UTXO
. OPTIONAL: Indicate whether you prefer to use _Post Quantum Cryptography_ (in the form of _SLH-DSA_. Default is the use of `Schnorr` signatures ).
+
-----
-$ export USE_PQC=false
+$ export USE_PQC=true
-----
. OPTIONAL: Define number of leaves in tap tree as well as the tap leaf to later use as the unlocking script:
@@ -121,12 +84,27 @@ $ export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tsh_construction ) \
+
NOTE: In `regtest`, you can expect a P2TSH address that starts with: `bcrt1z` .
+
-NOTE: In the context of P2TSH, the _tree_root_hex_ from the response is in reference to the _quantum_root_ used in this tutorial.
+[subs=+quotes]
+++++
+
+What just happened?
+The Rust based reference implementation for BIP-0360 is leveraged to construct a transaction with a `p2tsh` UTXO as follows:
+
+
+ - A configurable number of leaves are generated each with their own locking script.
+ - Each of these leaves are added to a Huffman tree that sorts the leaves by weight.
+ - The merkle root of the tree is calculated and subsequently used to generate the p2tsh witness program and BIP0350 address.
+
+
+The source code for the above logic is found in this project: src/lib.rs
+
+
+++++
. Set some env vars (for use in later steps in this tutorial) based on previous result:
+
-----
-$ export QUANTUM_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tree_root_hex' ) \
+$ export MERKLE_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tree_root_hex' ) \
&& export LEAF_SCRIPT_PRIV_KEY_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_priv_key_hex' ) \
&& export LEAF_SCRIPT_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_hex' ) \
&& export CONTROL_BLOCK_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.control_block_hex' ) \
@@ -141,14 +119,12 @@ $ b-cli decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
-----
+
NOTE: If not using PQC, notice that this script commits to a Schnorr 32-byte x-only public key.
-If using PQC, this script commits to a Schnorr 32-byte SLH-DSA pub key and a OP_SUCCESS127 (represented as `OP_SUBSTR`)
+If using PQC, this script commits to a Schnorr 32-byte SLH-DSA pub key and a OP_SUCCESS127 (represented as `OP_SUBSTR`) opcode.
-
-. fund this P2TSH address with the coinbase reward of a newly generated block:
+. Fund this P2TSH address with the coinbase reward of a newly generated block:
+
Choose from one of the following networks:
-
.. Regtest
+
If on `regtest` network, then execute the following:
@@ -202,10 +178,19 @@ $ export FUNDING_UTXO=$( b-cli getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout
+
NOTE: the above only works when Bitcoin Core is started with the following arg: -txindex
+
== Spend P2TSH UTXO
+In the previous section, you created and funded a P2TSH UTXO.
+That UTXO includes a leaf script locked with a key-pair (optionally based on PQC) known to you.
+
+In this section, you spend from that P2TSH UTXO.
+Specifically, you will generate an appropriate _SigHash_ and sign it (to create a signature) using the known private key that unlocks the known leaf script of the P2TSH UTXO.
+
+For the purpose of this tutorial, you will spend funds to a new P2WPKH utxo. (there is nothing novel about this P2WPKH utxo).
-. Determine value (in sats) of funding utxo:
+
+. Determine value (in sats) of the funding P2TSH utxo:
+
-----
$ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{printf "%.0f", $1 * 100000000}') \
@@ -245,6 +230,23 @@ $ export RAW_P2TSH_SPEND_TX=$( echo $SPEND_DETAILS | jq -r '.tx_hex' ) \
&& export SIG_BYTES=$( echo $SPEND_DETAILS | jq -r '.sig_bytes' ) \
&& echo "SIG_BYTES = $SIG_BYTES"
-----
++
+[subs=+quotes]
+++++
+
+What just happened?
+The Rust based reference implementation for BIP-0360 is leveraged to construct a transaction that spends from the `p2tsh` UTXO as follows:
+
+
+ - Create a transaction template (aka: SigHash) that serves as the message to be signed.
+ - Using the known private key and the SigHash, create a signature that is capable of unlocking one of the leaf scripts of the P2TSH tree.
+ - Add this signature to the witness section of the transaction.
+
+
+The source code for the above logic is found in this project: src/lib.rs
+
+
+++++
. Inspect the spending tx:
+
@@ -314,23 +316,101 @@ $ export BLOCK_HASH=$( b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1 | jq -r '
$ b-cli getblock $BLOCK_HASH | jq -r .tx
-----
-== TO-DO
+== Appendix
+
+[[build_p2tsh]]
+=== Build P2TSH / PQC Enabled Bitcoin Core
+
+The link:https://github.com/jbride/bitcoin/tree/p2tsh[p2tsh branch] of bitcoin core is needed.
+
+Build instructions for the `p2tsh` branch are the same as `master` and is documented link:https://github.com/bitcoin/bitcoin/blob/master/doc/build-unix.md[here].
+
+As such, the following is an example series of steps (on a Fedora 42 host) to compile and run the `p2tsh` branch of bitcoin core:
+
+. Set BITCOIN_SOURCE_DIR
++
+-----
+$ export BITCOIN_SOURCE_DIR=/path/to/root/dir/of/cloned/bitcoin/source
+-----
+
+. build
++
+-----
+$ cmake -B build \
+ -DWITH_ZMQ=ON \
+ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
+ -DBUILD_BENCH=ON \
+ -DBUILD_DAEMON=ON \
+ -DSANITIZERS=address,undefined
-=== Import P2TSH address into wallet
+$ cmake --build build -j$(nproc)
+-----
-NOTE: currently fails with: "message": "Cannot import descriptor without private keys to a wallet with private keys enabled"
+. run in either `regtest` or `signet` mode:
+.. regtest:
++
-----
-$ b-cli -rpcwallet=$W_NAME walletpassphrase $WPASS 120
-$ echo $P2TSH_ADDR
-$ export P2TSH_DESC=$( b-cli getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
- && echo $P2TSH_DESC
-
-# Set as non-active address (because can't generate subsequent p2tsh addresses yet)
-$ b-cli importdescriptors '[{
- "desc": "'''$P2TSH_DESC'''",
- "timestamp": "now",
- "active": false,
- "label": "p2tsh"
-}]'
+$ ./build/bin/bitcoind -daemon=0 -regtest=1 -txindex -prune=0
+-----
+
+.. signet:
++
+-----
+$ ./build/bin/bitcoind -daemon=0 -signet=1 -txindex -prune=0
+-----
++
+NOTE: If running in `signet`, your bitcoin core will need to be configured with the `signetchallenge` property.
+link:https://edil.com.br/blog/creating-a-custom-bitcoin-signet[This tutorial] provides a nice overview of the topic.
+
+=== libbitcoinpqc build
+
+The `p2tsh-pqc` branch of this project includes a dependency on the link:https://crates.io/crates/libbitcoinpqc[libbitcoinpqc crate].
+libbitcoinpqc contains native code (C/C++/ASM) and is made available to Rust projects via Rust bindings.
+This C/C++/ASM code is provided in the libbitcoinpqc crate as source code (not prebuilt binaries).
+
+Subsequently, the `Cargo` utility needs to build this libbitcoinpqc C native code on your local machine.
+You will need to have C development related libraries installed on your local machine.
+
+Every developer or CI machine building `p2tsh-ref` must have cmake and a C toolchain installed locally.
+
+==== Linux
+
+. Debian / Ubuntu
++
+-----
+$ sudo apt update
+$ sudo apt install cmake build-essential clang libclang-dev
+-----
+
+. Fedora / RHEL
++
+-----
+$ sudo dnf5 update
+$ sudo dnf5 install cmake make gcc gcc-c++ clang clang-libs llvm-devel
+-----
+
+==== OSX
+
+[[bitcoin_core_wallet]]
+=== Bitcoin Core Wallet
+
+This tutorial assumes that a bitcoin core wallet is available.
+
+. Set an environment variable specific to your Bitcoin network environment (regtest, signet, etc)
++
+-----
+$ export BITCOIN_NETWORK=regtest
+-----
+
+For example, the following would be sufficient:
+
+-----
+
+$ export W_NAME=anduro
+
+$ b-cli -named createwallet \
+ wallet_name=$W_NAME \
+ descriptors=true \
+ load_on_startup=true
-----
diff --git a/bip-0360/ref-impl/rust/src/lib.rs b/bip-0360/ref-impl/rust/src/lib.rs
index 23e8bd2bee..1667370f40 100644
--- a/bip-0360/ref-impl/rust/src/lib.rs
+++ b/bip-0360/ref-impl/rust/src/lib.rs
@@ -99,7 +99,7 @@ pub fn create_p2tsh_multi_leaf_taptree(use_pqc: bool) -> TaptreeReturn {
let p2tsh_spend_info: P2tshSpendInfo = p2tsh_builder.clone().finalize().unwrap();
- let quantum_root:TapNodeHash = p2tsh_spend_info.merkle_root.unwrap();
+ let merkle_root:TapNodeHash = p2tsh_spend_info.merkle_root.unwrap();
let tap_tree: TapTree = p2tsh_builder.clone().into_inner().try_into_taptree().unwrap();
@@ -117,10 +117,10 @@ pub fn create_p2tsh_multi_leaf_taptree(use_pqc: bool) -> TaptreeReturn {
let mut leaf_hash_bytes = leaf_hash.as_raw_hash().to_byte_array().to_vec();
leaf_hash_bytes.reverse();
- info!("leaf_hash: {}, merkle_root: {}, quantum_root: {}",
+ info!("leaf_hash: {}, merkle_root: {}, merkle_root: {}",
hex::encode(leaf_hash_bytes),
merkle_root,
- quantum_root);
+ merkle_root);
let leaf_script = script_leaf.script();
let merkle_branch: &TaprootMerkleBranch = script_leaf.merkle_branch();
@@ -131,7 +131,7 @@ pub fn create_p2tsh_multi_leaf_taptree(use_pqc: bool) -> TaptreeReturn {
merkle_branch: merkle_branch.clone(),
};
- // Not a requirement but useful to demonstrate what Bitcoin Core does as the verifier when spending from a p2tsh UTXO
+ // Not a requirement here but useful to demonstrate what Bitcoin Core does as the verifier when spending from a p2tsh UTXO
control_block.verify_script_in_merkle_root_path(leaf_script, merkle_root);
let control_block_hex: String = hex::encode(control_block.serialize());
@@ -140,7 +140,7 @@ pub fn create_p2tsh_multi_leaf_taptree(use_pqc: bool) -> TaptreeReturn {
leaf_script_priv_key_hex: hex::encode(keypair_of_interest.secret_key_bytes()),
leaf_script_hex: leaf_script.to_hex_string(),
- tree_root_hex: hex::encode(quantum_root.to_byte_array()),
+ tree_root_hex: hex::encode(merkle_root.to_byte_array()),
control_block_hex: control_block_hex,
};
}
@@ -198,15 +198,15 @@ pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> Taptr
};
}
-pub fn create_p2tsh_utxo(quantum_root_hex: String) -> UtxoReturn {
+pub fn create_p2tsh_utxo(merkle_root_hex: String) -> UtxoReturn {
- let quantum_root_bytes= hex::decode(quantum_root_hex.clone()).unwrap();
- let quantum_root: TapNodeHash = TapNodeHash::from_byte_array(quantum_root_bytes.try_into().unwrap());
+ let merkle_root_bytes= hex::decode(merkle_root_hex.clone()).unwrap();
+ let merkle_root: TapNodeHash = TapNodeHash::from_byte_array(merkle_root_bytes.try_into().unwrap());
/* commit (in scriptPubKey) to the merkle root of all the script path leaves. ie:
This output key is what gets committed to in the final P2TSH address (ie: scriptPubKey)
*/
- let script_buf: P2tshScriptBuf = P2tshScriptBuf::new_p2tsh(quantum_root);
+ let script_buf: P2tshScriptBuf = P2tshScriptBuf::new_p2tsh(merkle_root);
let script: &Script = script_buf.as_script();
let script_pubkey = script.to_hex_string();
@@ -225,9 +225,9 @@ pub fn create_p2tsh_utxo(quantum_root_hex: String) -> UtxoReturn {
};
}
- // 4) derive bech32m address and verify against test vector
- // p2tsh address is comprised of network HRP + WitnessProgram (version + program)
- let bech32m_address = Address::p2tsh(Some(quantum_root), bitcoin_network);
+ // derive bech32m address and verify against test vector
+ // p2tsh address is comprised of network HRP + WitnessProgram (version + program)
+ let bech32m_address = Address::p2tsh(Some(merkle_root), bitcoin_network);
return UtxoReturn {
script_pubkey_hex: script_pubkey,
From 33195771af5a7bc410a3f9f480f33e86aa033384 Mon Sep 17 00:00:00 2001
From: jbride
Date: Fri, 10 Oct 2025 19:39:59 -0600
Subject: [PATCH 10/16] bip360: introducing pqc enabled bdk_wallet dependencies
---
bip-0360/ref-impl/rust/Cargo.lock | 123 +++++++++++++++---------------
bip-0360/ref-impl/rust/Cargo.toml | 4 +-
2 files changed, 64 insertions(+), 63 deletions(-)
diff --git a/bip-0360/ref-impl/rust/Cargo.lock b/bip-0360/ref-impl/rust/Cargo.lock
index d10d7c1450..ebdf473ddc 100644
--- a/bip-0360/ref-impl/rust/Cargo.lock
+++ b/bip-0360/ref-impl/rust/Cargo.lock
@@ -25,9 +25,9 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.6.20"
+version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -40,9 +40,9 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.11"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
@@ -75,9 +75,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.99"
+version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arrayvec"
@@ -103,9 +103,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bdk_chain"
-version = "0.23.2"
+version = "0.23.2-pqc-0.1"
source = "sparse+https://crates.denver.space/api/v1/crates/"
-checksum = "0c3b6dc7f800ba667d58729f0fbccf55cd6df9b031a39685fc42ebb00afe0c55"
+checksum = "6ea956ce13678893de26ffce4ead2f28ac1748ccb8058341bf839616cd357ccf"
dependencies = [
"bdk_core",
"bitcoin",
@@ -126,9 +126,9 @@ dependencies = [
[[package]]
name = "bdk_wallet"
-version = "3.0.0-alpha.0-p2tsh-0"
+version = "3.0.0-alpha.0-pqc-0.1"
source = "sparse+https://crates.denver.space/api/v1/crates/"
-checksum = "791cded5cd02d230ea775e1e2c6eb62baf79e335863f9c25332d18e956fbc998"
+checksum = "e7687f76ee256115a2778e4b115bad9b439b87a8fccd52c4358c0f532482cf4c"
dependencies = [
"bdk_chain",
"bitcoin",
@@ -253,9 +253,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.37"
+version = "1.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44"
+checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -333,9 +333,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
-version = "0.1.1"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
+checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "getrandom"
@@ -444,18 +444,18 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.175"
+version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libloading"
-version = "0.8.8"
+version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
- "windows-targets",
+ "windows-link",
]
[[package]]
@@ -466,9 +466,9 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
-version = "2.7.5"
+version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
@@ -478,12 +478,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniscript"
-version = "13.0.0"
+version = "13.0.0-pqc-0.2"
source = "sparse+https://crates.denver.space/api/v1/crates/"
-checksum = "babfe2517c5b56123c43a10843ddc8ea85773d4fc04611d81f6a634c270b386e"
+checksum = "4cf5cd2ed677044868fdcfbbc28175d535f5086524674f85938c474258bc60aa"
dependencies = [
"bech32",
"bitcoin",
+ "bitcoinpqc",
"serde",
]
@@ -573,9 +574,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.40"
+version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
@@ -647,9 +648,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.11.2"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433"
dependencies = [
"aho-corasick",
"memchr",
@@ -659,9 +660,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.10"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6"
dependencies = [
"aho-corasick",
"memchr",
@@ -670,9 +671,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.6"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298"
[[package]]
name = "rustc-hash"
@@ -730,9 +731,9 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.225"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
@@ -740,18 +741,18 @@ dependencies = [
[[package]]
name = "serde_core"
-version = "1.0.225"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.225"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@@ -790,18 +791,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.16"
+version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.16"
+version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
@@ -852,9 +853,9 @@ dependencies = [
[[package]]
name = "windows-link"
-version = "0.1.3"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
@@ -867,9 +868,9 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.53.3"
+version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
@@ -884,51 +885,51 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "wit-bindgen"
diff --git a/bip-0360/ref-impl/rust/Cargo.toml b/bip-0360/ref-impl/rust/Cargo.toml
index d66dbca5ef..c3ddb0c961 100644
--- a/bip-0360/ref-impl/rust/Cargo.toml
+++ b/bip-0360/ref-impl/rust/Cargo.toml
@@ -8,13 +8,13 @@ edition = "2024"
# Dev version of miniscript crate re-exports bitcoin 0.32.6
# view configuration for "kellnr-denver-space":
# cat .cargo/config.toml
-miniscript = { version="13.0.0", registry="kellnr-denver-space" }
+miniscript = { version="=13.0.0-pqc-0.2", registry="kellnr-denver-space" }
bitcoin = { version="0.32.6", features = ["rand-std", "serde", "base64"], registry = "kellnr-denver-space" }
bitcoinpqc = { version="0.2.0", features = ["serde"] }
# BDK Wallet with P2TSH support
-bdk_wallet = { version = "3.0.0-alpha.0-p2tsh-0", registry = "kellnr-denver-space" }
+bdk_wallet = { version = "=3.0.0-alpha.0-pqc-0.1", registry = "kellnr-denver-space" }
env_logger = "0.11.5"
log = "0.4.22"
From 8dd3531e7c5e5358e8d3c2af9e51e3d3dba3b42a Mon Sep 17 00:00:00 2001
From: jbride
Date: Sat, 11 Oct 2025 11:59:50 -0600
Subject: [PATCH 11/16] bip360: workshop related utils
---
.../common/utils/workshop/Dockerfile.bcli | 87 ++++++++++++++++++
.../common/utils/workshop/Dockerfile.full | 82 +++++++++++++++++
.../utils/workshop/bip360-workshop.drawio | 10 ++
.../utils/workshop/signet_360.drawio.png | Bin 0 -> 82122 bytes
4 files changed, 179 insertions(+)
create mode 100644 bip-0360/ref-impl/common/utils/workshop/Dockerfile.bcli
create mode 100644 bip-0360/ref-impl/common/utils/workshop/Dockerfile.full
create mode 100644 bip-0360/ref-impl/common/utils/workshop/bip360-workshop.drawio
create mode 100644 bip-0360/ref-impl/common/utils/workshop/signet_360.drawio.png
diff --git a/bip-0360/ref-impl/common/utils/workshop/Dockerfile.bcli b/bip-0360/ref-impl/common/utils/workshop/Dockerfile.bcli
new file mode 100644
index 0000000000..a28cf94f31
--- /dev/null
+++ b/bip-0360/ref-impl/common/utils/workshop/Dockerfile.bcli
@@ -0,0 +1,87 @@
+# podman build -f Dockerfile.bcli -t quay.io/jbride/p2tsh_bcli:0.1 .
+# podman run -it --entrypoint /bin/bash quay.io/jbride/p2tsh_bcli:0.1
+
+FROM rust:1-slim-bookworm AS builder
+
+# Install build dependencies
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ cmake \
+ git \
+ autoconf \
+ automake \
+ libtool \
+ pkg-config \
+ zlib1g-dev \
+ libevent-dev \
+ libboost-dev \
+ libzmq3-dev \
+ bash \
+ python3 \
+ python3-pip \
+ libclang-dev \
+ clang \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set working directory
+WORKDIR /bitcoin
+
+# Copy Bitcoin Core source (or clone)
+# COPY . /bitcoin
+RUN git clone --branch p2tsh-pqc --single-branch https://github.com/jbride/bitcoin.git
+
+# Environment variables for musl
+ENV CC=gcc
+ENV CXX=g++
+
+# Build Bitcoin Core
+WORKDIR bitcoin
+
+RUN apt-get update && apt-get install -y libsqlite3-dev && rm -rf /var/lib/apt/lists/*
+
+RUN mkdir build && cd build && \
+ cmake .. \
+ -DWITH_ZMQ=ON \
+ -DBUILD_BENCH=ON \
+ -DBUILD_DAEMON=ON && \
+ make -j$(nproc) bitcoin-cli
+
+# Runtime stage with Debian Slim
+FROM debian:bookworm-slim
+
+# Install minimal runtime dependencies
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ bash \
+ ca-certificates \
+ libevent-core-2.1-7 \
+ libevent-pthreads-2.1-7 \
+ libevent-extra-2.1-7 \
+ libboost-filesystem1.74.0 \
+ libboost-thread1.74.0 \
+ libzmq5 \
+ libsqlite3-0 \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy bitcoin-cli from builder
+COPY --from=builder /bitcoin/bitcoin/build/bin/bitcoin-cli /usr/local/bin/bitcoin-cli
+
+# Create non-root user and set permissions
+RUN groupadd --system bip360 && \
+ useradd --system --gid bip360 --shell /bin/bash --create-home bip360 && \
+ chmod +x /usr/local/bin/bitcoin-cli && \
+ ln -s /usr/local/bin/bitcoin-cli /usr/local/bin/b-cli && \
+ echo 'b-cli() { /usr/local/bin/bitcoin-cli -rpcconnect=${RPC_CONNECT:-192.168.122.1} -rpcport=${RPC_PORT:-18443} -rpcuser=${RPC_USER:-signet} -rpcpassword=${RPC_PASSWORD:-signet} "$@"; }' >> /home/bip360/.bashrc
+
+# Set default environment variables (can be overridden at runtime)
+ENV RPC_CONNECT=192.168.122.1 \
+ RPC_PORT=38332 \
+ RPC_USER=signet \
+ RPC_PASSWORD=signet
+
+# Switch to non-root user
+USER bip360
+
+WORKDIR /home/bip360
+
+ENTRYPOINT ["/usr/local/bin/bitcoin-cli"]
+
diff --git a/bip-0360/ref-impl/common/utils/workshop/Dockerfile.full b/bip-0360/ref-impl/common/utils/workshop/Dockerfile.full
new file mode 100644
index 0000000000..b41a8a71e0
--- /dev/null
+++ b/bip-0360/ref-impl/common/utils/workshop/Dockerfile.full
@@ -0,0 +1,82 @@
+# podman build -f Dockerfile.full -t quay.io/jbride/p2tsh_demo:0.1 .
+
+FROM rust:1-slim-bookworm AS builder
+
+# Install build dependencies
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ cmake \
+ git \
+ autoconf \
+ automake \
+ libtool \
+ pkg-config \
+ zlib1g-dev \
+ libevent-dev \
+ libboost-dev \
+ libzmq3-dev \
+ bash \
+ python3 \
+ python3-pip \
+ libclang-dev \
+ clang \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set working directory
+WORKDIR /bitcoin
+
+# Copy Bitcoin Core source (or clone)
+# COPY . /bitcoin
+RUN git clone --branch p2tsh-pqc --single-branch https://github.com/jbride/bitcoin.git
+
+# Environment variables for musl
+ENV CC=gcc
+ENV CXX=g++
+
+# Build Bitcoin Core
+WORKDIR bitcoin
+
+RUN apt-get update && apt-get install -y libsqlite3-dev && rm -rf /var/lib/apt/lists/*
+
+RUN mkdir build && cd build && \
+ cmake .. \
+ -DWITH_ZMQ=ON \
+ -DBUILD_BENCH=ON \
+ -DBUILD_DAEMON=ON && \
+ make -j$(nproc) bitcoin-cli
+
+# Clone the BIPs repository (p2tsh-pqc branch, bip-360/ref-impl/rust directory)
+WORKDIR /
+RUN git clone --no-checkout --depth 1 --branch p2tsh-pqc \
+ --single-branch https://github.com/jbride/bips.git bips && \
+ cd bips && \
+ git sparse-checkout init --cone && \
+ git sparse-checkout set bip-0360 && \
+ git checkout && \
+ cd bip-0360/ref-impl/rust && \
+ cargo build && \
+ rm -rf target
+
+
+# Create non-root user and set permissions
+RUN groupadd bip360 && \
+ useradd -g bip360 -s /bin/bash -m bip360 && \
+ chown -R bip360:bip360 /bips && \
+ chmod +x /bitcoin/bitcoin/build/bin/bitcoin-cli && \
+ ln -s /bitcoin/bitcoin/build/bin/bitcoin-cli /usr/local/bin/b-cli && \
+ echo 'b-cli() { /usr/local/bin/b-cli -rpcconnect=${RPC_CONNECT:-192.168.122.1} -rpcport=${RPC_PORT:-38332} -rpcuser=${RPC_USER:-signet} -rpcpassword=${RPC_PASSWORD:-signet} "$@"; }' >> /home/bip360/.bashrc
+
+# Set default environment variables (can be overridden at runtime)
+ENV RPC_CONNECT=192.168.122.1 \
+ RPC_PORT=18443 \
+ RPC_USER=regtest \
+ RPC_PASSWORD=regtest \
+ SIGNET_CHALLENGE=1
+
+# Switch to non-root user
+USER bip360
+
+WORKDIR /bips/bip-0360/ref-impl/rust
+
+ENTRYPOINT ["/usr/local/bin/b-cli"]
+
diff --git a/bip-0360/ref-impl/common/utils/workshop/bip360-workshop.drawio b/bip-0360/ref-impl/common/utils/workshop/bip360-workshop.drawio
new file mode 100644
index 0000000000..4f587663c1
--- /dev/null
+++ b/bip-0360/ref-impl/common/utils/workshop/bip360-workshop.drawio
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bip-0360/ref-impl/common/utils/workshop/signet_360.drawio.png b/bip-0360/ref-impl/common/utils/workshop/signet_360.drawio.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c80145f8dbfad52602ede8a8288a98615b21040
GIT binary patch
literal 82122
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%0DSjQ&X-&SxlcN~w2!|aWhX?%*Q
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