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/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/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..54f9ae6293
--- /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": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f247f",
+ "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUBSTR",
+ "priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
+ "leafVersion": 192
+ }
+ },
+ "intermediary": {
+ "leafHashes": [
+ "3bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc"
+ ],
+ "merkleRoot": "3bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc"
+ },
+ "expected": {
+ "scriptPubKey": "52203bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc",
+ "bip350Address": "bc1z8wcdhrr2mnv8xv9y4ry3hc87rv3a50q4rdhjld8jd9pfcsacmz7qg5szm8",
+ "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": "201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f247f",
+ "asm": "1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUBSTR",
+ "priv_key": "3f0213a51771f25c906cd82c656175e4b2a10e85958039d6e358352a2eb62b791f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24",
+ "leafVersion": 192
+ },
+ {
+ "id": 1,
+ "script": "06424950333431",
+ "asm": "424950333431",
+ "description": "just pushes the string 'BIP341' onto the stack",
+ "leafVersion": 250
+ }
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "3bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc",
+ "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
+ ],
+ "merkleRoot": "1619ce6d22a46dea045c4adf7f5f33d6810d00d0e9c8a4c7ba35db37b915c604"
+ },
+ "expected": {
+ "scriptPubKey": "52201619ce6d22a46dea045c4adf7f5f33d6810d00d0e9c8a4c7ba35db37b915c604",
+ "bip350Address": "bc1zzcvuumfz53k75pzuft0h7hen66qs6qxsa8y2f3a6xhdn0wg4cczq0h84sj",
+ "scriptPathControlBlocks": [
+ "c13bb0db8c6adcd87330a4a8c91be0fe1b23da3c151b6f2fb4f269429c43b8d8bc",
+ "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": "029000b275201f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f247f",
+ "asm": "144 OP_CHECKSEQUENCEVERIFY OP_DROP 1f4b1febdc7dfa77c1efacc875b43317b46155e5a564decf475fc369124c5f24 OP_SUBSTR",
+ "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_SUBSTR",
+ "priv_key": "49a4214f386240d97ea68efb4268043fd5a55208dcdac18ce5bd3332b8e488944c7f98ab73cc36abeb76d6eace6a9d8b0ff69dfe0f4a17e4f94f0898ec52fadd",
+ "description": "Bob takes the money whenever he wishes to by revealing the preimage_hash",
+ "leafVersion": 192
+ }
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "cfd5fc07ac39947cba799e14f933f20e7c233dea72dc2792f5547c58cdce743e",
+ "a9745ac96d4f3702b78751f1e08f3040fbe6347e7b4f520d22d3f907730cbb7e"
+ ],
+ "merkleRoot": "2794771cd51f215ba3a19fbcdf08c771edb7de782a0c34457e0e9be5d0e4008f"
+ },
+ "expected": {
+ "scriptPubKey": "52202794771cd51f215ba3a19fbcdf08c771edb7de782a0c34457e0e9be5d0e4008f",
+ "bip350Address": "bc1zy728w8x4rus4hgapn77d7zx8w8km0hnc9gxrg3t7p6d7t58yqz8sg0nccq",
+ "scriptPathControlBlocks": [
+ "c1cfd5fc07ac39947cba799e14f933f20e7c233dea72dc2792f5547c58cdce743e",
+ "c1a9745ac96d4f3702b78751f1e08f3040fbe6347e7b4f520d22d3f907730cbb7e"
+ ]
+ }
+ },
+ {
+ "id": "p2tsh_two_leaf_same_version",
+ "objective": "Tests P2TSH with two script leaves of same version",
+ "given": {
+ "scriptTree": [
+ {
+ "id": 0,
+ "script": "20e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f53447f",
+ "asm": "e6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f5344 OP_SUBSTR",
+ "priv_key": "a695d8a1351774d59ed1b980462c2ab8d58b3b7a48f55b114c6a39980dc1c13ee6b3dad32fc04095a021f5356163cfc14e15f703831e332c56f6b499801f5344",
+ "leafVersion": 192
+ },
+ {
+ "id": 1,
+ "script": "07546170726f6f74",
+ "asm": "546170726f6f74",
+ "description": "pushes the string 'Taproot' onto the stack",
+ "leafVersion": 192
+ }
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "9de7eeded7832c28c6f80de76904dd79f98fd302747823b5bc5be440186b0c6d",
+ "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
+ ],
+ "merkleRoot": "5112b3edfd2c0b717491e9d4888ed2d5dfeaa25115143540e0a08516b68c008c"
+ },
+ "expected": {
+ "scriptPubKey": "52205112b3edfd2c0b717491e9d4888ed2d5dfeaa25115143540e0a08516b68c008c",
+ "bip350Address": "bc1z2yft8m0a9s9hzay3a82g3rkj6h074gj3z52r2s8q5zz3dd5vqzxqngpk2w",
+ "scriptPathControlBlocks": [
+ "c19de7eeded7832c28c6f80de76904dd79f98fd302747823b5bc5be440186b0c6d",
+ "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": "201d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc7f",
+ "asm": "1d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc OP_SUBSTR",
+ "priv_key": "2b339b5055fad695f0595d5581fa087455854f64c5443c03d5b4ca53549f12a41d58016252d5a941f84574c4821b5f87e56778b2bbc3b610dfd801fc0250f6cc",
+ "leafVersion": 192
+ },
+ [
+ {
+ "id": 1,
+ "script": "20a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad7f",
+ "asm": "a3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad OP_SUBSTR",
+ "priv_key": "ec29d60c1be9263602906499b5e3c1de9e36cc88cec31154210191a578b92e9da3b0dcac08f86306ba04f5fe2a3243ef3a0347c0e2a4529720a18d3cfdce90ad",
+ "leafVersion": 192
+ },
+ {
+ "id": 2,
+ "script": "20e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f367f",
+ "asm": "e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f36 OP_SUBSTR",
+ "priv_key": "da04d13f706a6e0d22ac0db7c361f1aaa706a27043efca4edfecfed3125238e1e6ccf03593dbd07eba10403e33586c130889de6e8219da0a9ccbdd46a56a5f36",
+ "leafVersion": 192
+ }
+ ]
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "0840c39e59eda6c9deee687a480cb169130c2f053ed2eb3134511ec1cfd8a2c7",
+ "837ef6677aeb0df2b0de47f45024684cc6ca03bda10fa30bb5bc05a94beb8dd1",
+ "b2a5304f678cc5a2ed51feb377dd0a609bd22ec979cc608bfcf884d0f8e6f93a"
+ ],
+ "merkleRoot": "eaf8f557fdb9673de7bb9bad7e7452da9f44a3e65133fdadf2849c55cfb3cf5b"
+ },
+ "expected": {
+ "scriptPubKey": "5220eaf8f557fdb9673de7bb9bad7e7452da9f44a3e65133fdadf2849c55cfb3cf5b",
+ "bip350Address": "bc1zatu024lah9nnmeamnwkhuazjm205fglx2yelmt0jsjw9tnaneadszq7wg7",
+ "scriptPathControlBlocks": [
+ "c1837ef6677aeb0df2b0de47f45024684cc6ca03bda10fa30bb5bc05a94beb8dd1b2a5304f678cc5a2ed51feb377dd0a609bd22ec979cc608bfcf884d0f8e6f93a",
+ "c10840c39e59eda6c9deee687a480cb169130c2f053ed2eb3134511ec1cfd8a2c7b2a5304f678cc5a2ed51feb377dd0a609bd22ec979cc608bfcf884d0f8e6f93a",
+ "c118781f42f664d67acaf0ce7c6826437e5440eb1789f232af05e9a09fdf547903"
+ ]
+ }
+ },
+ {
+ "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": "20409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c7f",
+ "asm": "409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c OP_SUBSTR",
+ "priv_key": "c6ec3801b04afa40be6f77f3f7a7b3b7cb8cfe233b0263fb70e2f087b21397c1409f78ce0978519ff5d960c4ab595174c7efca11b1f89410a4e98b70cd423e1c",
+ "leafVersion": 192
+ },
+ [
+ {
+ "id": 1,
+ "script": "209f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a7f",
+ "asm": "9f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a OP_SUBSTR",
+ "priv_key": "7524bca170d54231f1246f30fb00f9da2208b1363ba5a7bbaf11fc3b0309e9519f0955dc884a5c88d1732e017d30e27e8a445889bce5aa42611b7635c5c1073a",
+ "leafVersion": 192
+ },
+ {
+ "id": 2,
+ "script": "20c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f7f",
+ "asm": "c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f OP_SUBSTR",
+ "priv_key": "d9d0f17d630b6a538e6a8a036373e7b9e5023a4c08f72dd8c3ef59288b98c079c60b10d57c0cdf27e5c751e131ea4301b37abe7384948059256c7f5f1f285a7f",
+ "leafVersion": 192
+ }
+ ]
+ ]
+ },
+ "intermediary": {
+ "leafHashes": [
+ "52e9326c2bf04d926b7e9f6c7645dd853f3f007b870201de9b814952750c9310",
+ "dcef3ce86cc8cea78c9e00f3d9ef58360cb6ed3cb90ec62efe00b9703854ba5c",
+ "ddb521a44e33ff4974e618d8b8b7794275b7dc754d847c537404f84330454361"
+ ],
+ "merkleRoot": "51e3c1151ba73d9efce801837773331bf9030977242f62dfeb6756795f482409"
+ },
+ "expected": {
+ "scriptPubKey": "522051e3c1151ba73d9efce801837773331bf9030977242f62dfeb6756795f482409",
+ "bip350Address": "bc1z283uz9gm5u7eal8gqxphwuenr0usxzthyshk9hltvat8jh6gysys28twnc",
+ "scriptPathControlBlocks": [
+ "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
new file mode 100755
index 0000000000..b5882cb8e8
--- /dev/null
+++ b/bip-0360/ref-impl/common/utils/signet_miner_loop.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# 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/anduro-360/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
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..4c03bc149b
--- /dev/null
+++ b/bip-0360/ref-impl/common/utils/workshop/Dockerfile.bcli
@@ -0,0 +1,88 @@
+# podman build -f Dockerfile.bcli -t quay.io/jbride2000/p2tsh_bcli:0.1 .
+# podman run -it --entrypoint /bin/bash quay.io/jbride200/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 \
+ telnet \
+ && 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..2e047f85c0
--- /dev/null
+++ b/bip-0360/ref-impl/common/utils/workshop/Dockerfile.full
@@ -0,0 +1,115 @@
+# podman build -f Dockerfile.full -t quay.io/jbride2000/p2tsh_demo:0.1 .
+# podman run -it --entrypoint /bin/bash quay.io/jbride2000/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
+
+# Runtime stage with Debian Slim
+FROM rust:1-slim-bookworm
+
+# 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
+
+# Install additional tools needed for building Rust code (before switching to non-root user)
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ libclang-dev \
+ clang \
+ git \
+ curl \
+ cmake \
+ build-essential \
+ jq \
+ gawk \
+ telnet \
+ && rm -rf /var/lib/apt/lists/*
+
+# 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
+
+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
+
+WORKDIR /home/bip360/bips/bip-0360/ref-impl/rust
+
+ENV BITCOIN_NETWORK=signet \
+ USE_PQC=true
+
+ENTRYPOINT ["/usr/local/bin/bitcoin-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 0000000000..4c80145f8d
Binary files /dev/null and b/bip-0360/ref-impl/common/utils/workshop/signet_360.drawio.png differ
diff --git a/bip-0360/ref-impl/js/.gitignore b/bip-0360/ref-impl/js/.gitignore
new file mode 100644
index 0000000000..1d1baf2e55
--- /dev/null
+++ b/bip-0360/ref-impl/js/.gitignore
@@ -0,0 +1,76 @@
+# Dependencies
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# Build outputs
+dist/
+build/
+*.tsbuildinfo
+
+# TypeScript
+*.js.map
+*.d.ts.map
+
+# Environment variables
+.env
+.env.local
+.env.*.local
+
+# IDE / Editor files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+.DS_Store
+
+# OS files
+Thumbs.db
+desktop.ini
+
+# Testing
+coverage/
+.nyc_output/
+*.lcov
+
+# Logs
+logs/
+*.log
+
+# Temporary files
+*.tmp
+*.temp
+.cache/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# parcel-bundler cache
+.parcel-cache
+
+# Next.js (if ever used)
+.next
+out
+
+# Vercel (if ever used)
+.vercel
+
+# Turbo (if ever used)
+.turbo
+
diff --git a/bip-0360/ref-impl/js/README.adoc b/bip-0360/ref-impl/js/README.adoc
new file mode 100644
index 0000000000..8d18999318
--- /dev/null
+++ b/bip-0360/ref-impl/js/README.adoc
@@ -0,0 +1,18 @@
+
+= BIP 360 Javascript Reference Implementation
+
+:numbered:
+
+== procedure
+
+-----
+$ npm install
+
+# compile Typecript
+$ npx tsc
+
+
+# run tests
+$ node src/p2tsh-example.ts
+$ node src/test-npm-pqc-package.js
+-----
diff --git a/bip-0360/ref-impl/js/package-lock.json b/bip-0360/ref-impl/js/package-lock.json
new file mode 100644
index 0000000000..2fd47283f6
--- /dev/null
+++ b/bip-0360/ref-impl/js/package-lock.json
@@ -0,0 +1,245 @@
+{
+ "name": "js",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "js",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@jbride/bitcoinjs-lib": "^7.0.0-rc.0-p2tsh-0.0",
+ "@jbride/bitcoinpqc-wasm": "^0.1.1",
+ "ecpair": "^3.0.0",
+ "tiny-secp256k1": "^2.2.4"
+ },
+ "devDependencies": {
+ "@types/node": "^24.10.0",
+ "typescript": "^5.9.3"
+ }
+ },
+ "node_modules/@jbride/bitcoinjs-lib": {
+ "version": "7.0.0-rc.0-p2tsh-0.0",
+ "resolved": "https://registry.npmjs.org/@jbride/bitcoinjs-lib/-/bitcoinjs-lib-7.0.0-rc.0-p2tsh-0.0.tgz",
+ "integrity": "sha512-JRL0LTBVFBhfHhEciJi8I7C5jGoL24b07rwQthe+3UAGbv2LLOqKRYAD3dc5XD4Maanh1awV+MUELgc7OFgjYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "^1.2.0",
+ "bech32": "^2.0.0",
+ "bip174": "^3.0.0-rc.0",
+ "bs58check": "^4.0.0",
+ "uint8array-tools": "^0.0.9",
+ "valibot": "^0.38.0",
+ "varuint-bitcoin": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@jbride/bitcoinpqc-wasm": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@jbride/bitcoinpqc-wasm/-/bitcoinpqc-wasm-0.1.1.tgz",
+ "integrity": "sha512-aftRuCBYXPEYzvWBVG84Vgum/ba4zkpUQrNA3jsWK2ir/DzxY7WmReskFEuPprc8X/LQ0/2lxJZyzqGRxDuO8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
+ "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/base-x": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz",
+ "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==",
+ "license": "MIT"
+ },
+ "node_modules/bech32": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
+ "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==",
+ "license": "MIT"
+ },
+ "node_modules/bip174": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bip174/-/bip174-3.0.0.tgz",
+ "integrity": "sha512-N3vz3rqikLEu0d6yQL8GTrSkpYb35NQKWMR7Hlza0lOj6ZOlvQ3Xr7N9Y+JPebaCVoEUHdBeBSuLxcHr71r+Lw==",
+ "license": "MIT",
+ "dependencies": {
+ "uint8array-tools": "^0.0.9",
+ "varuint-bitcoin": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/bs58": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz",
+ "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==",
+ "license": "MIT",
+ "dependencies": {
+ "base-x": "^5.0.0"
+ }
+ },
+ "node_modules/bs58check": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz",
+ "integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "^1.2.0",
+ "bs58": "^6.0.0"
+ }
+ },
+ "node_modules/ecpair": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-3.0.0.tgz",
+ "integrity": "sha512-kf4JxjsRQoD4EBzpYjGAcR0t9i/4oAeRPtyCpKvSwyotgkc6oA4E4M0/e+kep7cXe+mgxAvoeh/jdgH9h5+Wxw==",
+ "license": "MIT",
+ "dependencies": {
+ "uint8array-tools": "^0.0.8",
+ "valibot": "^0.37.0",
+ "wif": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/ecpair/node_modules/uint8array-tools": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz",
+ "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/ecpair/node_modules/valibot": {
+ "version": "0.37.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.37.0.tgz",
+ "integrity": "sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tiny-secp256k1": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.4.tgz",
+ "integrity": "sha512-FoDTcToPqZE454Q04hH9o2EhxWsm7pOSpicyHkgTwKhdKWdsTUuqfP5MLq3g+VjAtl2vSx6JpXGdwA2qpYkI0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "uint8array-tools": "0.0.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tiny-secp256k1/node_modules/uint8array-tools": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz",
+ "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uint8array-tools": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz",
+ "integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/valibot": {
+ "version": "0.38.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.38.0.tgz",
+ "integrity": "sha512-RCJa0fetnzp+h+KN9BdgYOgtsMAG9bfoJ9JSjIhFHobKWVWyzM3jjaeNTdpFK9tQtf3q1sguXeERJ/LcmdFE7w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/varuint-bitcoin": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz",
+ "integrity": "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==",
+ "license": "MIT",
+ "dependencies": {
+ "uint8array-tools": "^0.0.8"
+ }
+ },
+ "node_modules/varuint-bitcoin/node_modules/uint8array-tools": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz",
+ "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/wif": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/wif/-/wif-5.0.0.tgz",
+ "integrity": "sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==",
+ "license": "MIT",
+ "dependencies": {
+ "bs58check": "^4.0.0"
+ }
+ }
+ }
+}
diff --git a/bip-0360/ref-impl/js/package.json b/bip-0360/ref-impl/js/package.json
new file mode 100644
index 0000000000..f01277be61
--- /dev/null
+++ b/bip-0360/ref-impl/js/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "js",
+ "version": "1.0.0",
+ "type": "module",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "@types/node": "^24.10.0",
+ "typescript": "^5.9.3"
+ },
+ "dependencies": {
+ "@jbride/bitcoinjs-lib": "^7.0.0-rc.0-p2tsh-0.0",
+ "@jbride/bitcoinpqc-wasm": "^0.1.1",
+ "ecpair": "^3.0.0",
+ "tiny-secp256k1": "^2.2.4"
+ }
+}
diff --git a/bip-0360/ref-impl/js/src/p2tsh-example.ts b/bip-0360/ref-impl/js/src/p2tsh-example.ts
new file mode 100644
index 0000000000..af009e9857
--- /dev/null
+++ b/bip-0360/ref-impl/js/src/p2tsh-example.ts
@@ -0,0 +1,200 @@
+// src/p2tsh-example.ts
+// Example demonstrating P2TSH (Pay-to-Taproot-Script-Hash) address construction
+
+import { payments } from '@jbride/bitcoinjs-lib';
+import * as bitcoinCrypto from '@jbride/bitcoinjs-lib/src/crypto';
+import * as bscript from '@jbride/bitcoinjs-lib/src/script';
+import type { Taptree } from '@jbride/bitcoinjs-lib/src/types';
+import ECPairFactory, { type ECPairInterface } from 'ecpair';
+import * as ecc from 'tiny-secp256k1';
+import { randomBytes } from 'crypto';
+
+const { p2tsh } = payments;
+
+// Initialize ECPair with the ECC library
+const ECPair = ECPairFactory(ecc);
+
+// Create a secure RNG function
+const rng = (size: number) => randomBytes(size);
+
+function signAndVerify(
+ keyPair: ECPairInterface,
+ xOnlyPubkey: Uint8Array,
+ message: Buffer,
+) {
+ const hash = Buffer.from(bitcoinCrypto.hash256(message));
+ const schnorrSignature = Buffer.from(keyPair.signSchnorr(hash));
+ const signatureWithSighashDefault = Buffer.concat([schnorrSignature, Buffer.from([0x00])]);
+ const verified = keyPair.verifySchnorr(hash, schnorrSignature);
+
+ return {
+ message,
+ hash,
+ signature: schnorrSignature,
+ signatureWithSighashDefault,
+ verified,
+ };
+}
+
+/**
+ * Example 1: Construct a P2TSH address from a script tree with a single leaf
+ * This is the simplest case - a script tree containing one script.
+ */
+function example1_simpleScriptTree() {
+ console.log('=== Example 1: P2TSH from simple script tree ===');
+
+ // Generate a key pair
+ const keyPair = ECPair.makeRandom({ rng });
+ const pubkey = keyPair.publicKey;
+ const xOnlyPubkey = ecc.xOnlyPointFromPoint(pubkey);
+
+ // Compile the script: x-only pubkey OP_CHECKSIG (BIP342 Schnorr signature)
+ const script = bscript.compile([Buffer.from(xOnlyPubkey), bscript.OPS.OP_CHECKSIG]);
+
+ // Create a script tree with one leaf
+ const scriptTree = {
+ output: script,
+ };
+
+ // Construct the P2TSH payment
+ const payment = p2tsh({
+ scriptTree: scriptTree,
+ });
+
+ console.log('Generated compressed pubkey:', pubkey.toString('hex'));
+ console.log('X-only pubkey:', Buffer.from(xOnlyPubkey).toString('hex'));
+ console.log('Script tree:', { output: bscript.toASM(script) });
+ console.log('P2TSH Address:', payment.address);
+ console.log('Output script:', bscript.toASM(payment.output!));
+ console.log('Merkle root hash:', payment.hash ? Buffer.from(payment.hash).toString('hex') : undefined);
+ const message = Buffer.from('P2TSH demo - example 1', 'utf8');
+ const result = signAndVerify(keyPair, xOnlyPubkey, message);
+
+ console.log('Message:', result.message.toString('utf8'));
+ console.log('Hash256(message):', result.hash.toString('hex'));
+ console.log('Schnorr signature (64-byte):', result.signature.toString('hex'));
+ console.log('Signature + default sighash (65-byte witness element):', result.signatureWithSighashDefault.toString('hex'));
+ console.log('Signature valid:', result.verified);
+ console.log('Witness stack for spend:', [result.signatureWithSighashDefault.toString('hex'), bscript.toASM(script)]);
+ console.log();
+}
+
+/**
+ * Example 2: Construct a P2TSH address from a script tree with multiple leaves
+ * This demonstrates a more complex script tree structure.
+ */
+function example2_multiLeafScriptTree() {
+ console.log('=== Example 2: P2TSH from multi-leaf script tree ===');
+
+ // Generate two different key pairs for the leaves
+ const keyPair1 = ECPair.makeRandom({ rng });
+ const keyPair2 = ECPair.makeRandom({ rng });
+ const pubkey1 = keyPair1.publicKey;
+ const pubkey2 = keyPair2.publicKey;
+ const xOnlyPubkey1 = ecc.xOnlyPointFromPoint(pubkey1);
+ const xOnlyPubkey2 = ecc.xOnlyPointFromPoint(pubkey2);
+
+ const script1 = bscript.compile([Buffer.from(xOnlyPubkey1), bscript.OPS.OP_CHECKSIG]);
+ const script2 = bscript.compile([Buffer.from(xOnlyPubkey2), bscript.OPS.OP_CHECKSIG]);
+
+ // Create a script tree with two leaves (array of two leaf objects)
+ const scriptTree: Taptree = [
+ { output: script1 },
+ { output: script2 },
+ ];
+
+ // Construct the P2TSH payment
+ const payment = p2tsh({
+ scriptTree: scriptTree,
+ });
+
+ console.log('Generated compressed public keys:');
+ console.log(' Pubkey 1:', pubkey1.toString('hex'));
+ console.log(' Pubkey 2:', pubkey2.toString('hex'));
+ console.log('X-only pubkeys:');
+ console.log(' X-only 1:', Buffer.from(xOnlyPubkey1).toString('hex'));
+ console.log(' X-only 2:', Buffer.from(xOnlyPubkey2).toString('hex'));
+ console.log('Script tree leaves:');
+ console.log(' Leaf 1:', bscript.toASM(script1));
+ console.log(' Leaf 2:', bscript.toASM(script2));
+ console.log('P2TSH Address:', payment.address);
+ console.log('Output script:', bscript.toASM(payment.output!));
+ console.log('Merkle root hash:', payment.hash ? Buffer.from(payment.hash).toString('hex') : undefined);
+ const message1 = Buffer.from('P2TSH demo - example 2 leaf 1', 'utf8');
+ const message2 = Buffer.from('P2TSH demo - example 2 leaf 2', 'utf8');
+ const result1 = signAndVerify(keyPair1, xOnlyPubkey1, message1);
+ const result2 = signAndVerify(keyPair2, xOnlyPubkey2, message2);
+
+ console.log('Leaf 1 signature info:');
+ console.log(' Message:', result1.message.toString('utf8'));
+ console.log(' Hash256(message):', result1.hash.toString('hex'));
+ console.log(' Schnorr signature (64-byte):', result1.signature.toString('hex'));
+ console.log(' Signature + default sighash (65-byte):', result1.signatureWithSighashDefault.toString('hex'));
+ console.log(' Signature valid:', result1.verified);
+ console.log(' Witness stack:', [result1.signatureWithSighashDefault.toString('hex'), bscript.toASM(script1)]);
+
+ console.log('Leaf 2 signature info:');
+ console.log(' Message:', result2.message.toString('utf8'));
+ console.log(' Hash256(message):', result2.hash.toString('hex'));
+ console.log(' Schnorr signature (64-byte):', result2.signature.toString('hex'));
+ console.log(' Signature + default sighash (65-byte):', result2.signatureWithSighashDefault.toString('hex'));
+ console.log(' Signature valid:', result2.verified);
+ console.log(' Witness stack:', [result2.signatureWithSighashDefault.toString('hex'), bscript.toASM(script2)]);
+ console.log();
+}
+
+/**
+ * Example 4: Construct a P2TSH address from a hash and redeem script
+ * This demonstrates creating a P2TSH when you have the hash directly.
+ */
+function example3_fromHashAndRedeem() {
+ console.log('=== Example 3: P2TSH from hash and redeem script ===');
+
+ // Generate a key pair
+ const keyPair = ECPair.makeRandom({ rng });
+ const pubkey = keyPair.publicKey;
+ const xOnlyPubkey = ecc.xOnlyPointFromPoint(pubkey);
+ const redeemScript = bscript.compile([Buffer.from(xOnlyPubkey), bscript.OPS.OP_CHECKSIG]);
+
+ // Use a known hash (from test fixtures)
+ const hash = Buffer.from(
+ 'b424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26',
+ 'hex',
+ );
+
+ // Construct the P2TSH payment
+ const payment = p2tsh({
+ hash: hash,
+ redeem: {
+ output: redeemScript,
+ },
+ });
+
+ console.log('Generated compressed pubkey:', pubkey.toString('hex'));
+ console.log('X-only pubkey:', Buffer.from(xOnlyPubkey).toString('hex'));
+ console.log('Redeem script:', bscript.toASM(redeemScript));
+ console.log('Hash:', hash.toString('hex'));
+ console.log('P2TSH Address:', payment.address);
+ console.log('Output script:', bscript.toASM(payment.output!));
+ const message = Buffer.from('P2TSH demo - example 3', 'utf8');
+ const result = signAndVerify(keyPair, xOnlyPubkey, message);
+
+ console.log('Message:', result.message.toString('utf8'));
+ console.log('Hash256(message):', result.hash.toString('hex'));
+ console.log('Schnorr signature (64-byte):', result.signature.toString('hex'));
+ console.log('Signature + default sighash (65-byte):', result.signatureWithSighashDefault.toString('hex'));
+ console.log('Signature valid:', result.verified);
+ console.log('Witness stack:', [result.signatureWithSighashDefault.toString('hex'), bscript.toASM(redeemScript)]);
+ console.log();
+}
+
+// Run all examples
+console.log('P2TSH Address Construction Examples\n');
+console.log('=====================================\n');
+
+example1_simpleScriptTree();
+example2_multiLeafScriptTree();
+example3_fromHashAndRedeem();
+
+console.log('=====================================');
+console.log('All examples completed!');
diff --git a/bip-0360/ref-impl/js/src/test-npm-pqc-package.js b/bip-0360/ref-impl/js/src/test-npm-pqc-package.js
new file mode 100644
index 0000000000..a16e50dff3
--- /dev/null
+++ b/bip-0360/ref-impl/js/src/test-npm-pqc-package.js
@@ -0,0 +1,197 @@
+#!/usr/bin/env node
+
+/**
+ * Node.js test script for Bitcoin PQC WASM module (High-Level API)
+ *
+ * Usage: node test-npm-package.js
+ *
+ * This script tests the high-level TypeScript wrapper API (index.js) from the
+ * command line, which provides a cleaner interface than the low-level API.
+ */
+
+import { randomBytes } from 'node:crypto';
+
+// Load the high-level WASM module
+let bitcoinpqc;
+let Algorithm;
+
+try {
+ const module = await import('@jbride/bitcoinpqc-wasm');
+ bitcoinpqc = module.bitcoinpqc || module.default;
+ Algorithm = module.Algorithm;
+
+ if (!bitcoinpqc || !Algorithm) {
+ throw new Error('Failed to import bitcoinpqc or Algorithm from @jbride/bitcoinpqc-wasm');
+ }
+} catch (error) {
+ console.error('Failed to load WASM module:', error);
+ console.error('Make sure you have installed the @jbride/bitcoinpqc-wasm package before running this test.');
+ process.exit(1);
+}
+
+// Helper function to generate random bytes
+function generateRandomBytes(length) {
+ const array = new Uint8Array(length);
+ const bytes = randomBytes(length);
+ array.set(bytes);
+ return array;
+}
+
+// Test function
+async function testAlgorithm(algorithm, name) {
+ console.log(`\nTesting ${name} algorithm:`);
+ console.log('------------------------');
+
+ try {
+ // Get key and signature sizes
+ const pkSize = bitcoinpqc.publicKeySize(algorithm);
+ const skSize = bitcoinpqc.secretKeySize(algorithm);
+ const sigSize = bitcoinpqc.signatureSize(algorithm);
+
+ console.log(`Public key size: ${pkSize} bytes`);
+ console.log(`Secret key size: ${skSize} bytes`);
+ console.log(`Signature size: ${sigSize} bytes`);
+
+ // Generate random data for key generation
+ const randomData = generateRandomBytes(128);
+
+ // Generate a key pair
+ const keygenStart = Date.now();
+ const keypair = bitcoinpqc.generateKeypair(algorithm, randomData);
+ const keygenDuration = Date.now() - keygenStart;
+ console.log(`Key generation time: ${keygenDuration} ms`);
+
+ // Create a message to sign
+ const messageText = 'This is a test message for PQC signature verification';
+ const message = Buffer.from(messageText, 'utf8');
+ const messageUint8 = new Uint8Array(message);
+ console.log(`Message to sign: "${messageText}"`);
+ console.log(`Message length: ${message.length} bytes`);
+
+ // Sign the message
+ const signStart = Date.now();
+ let signature;
+ try {
+ signature = bitcoinpqc.sign(keypair.secretKey, messageUint8, algorithm);
+ const signDuration = Date.now() - signStart;
+ console.log(`Signing time: ${signDuration} ms`);
+ console.log(`Actual signature size: ${signature.size} bytes`);
+ } catch (error) {
+ const signDuration = Date.now() - signStart;
+ console.log(`Signing failed after ${signDuration} ms`);
+ console.log(`Error: ${error.message}`);
+ if (algorithm === Algorithm.SLH_DSA_SHAKE_128S) {
+ console.log('');
+ console.log('⚠️ NOTE: SLH-DSA-SHAKE-128s signing is currently experiencing');
+ console.log(' issues when compiled to WebAssembly. This appears to be a');
+ console.log(' bug in the SPHINCS+ reference implementation when compiled');
+ console.log(' to WASM. ML-DSA-44 (Dilithium) works correctly.');
+ console.log('');
+ console.log(' Key generation succeeded, but signing failed.');
+ console.log(' This is a known limitation of the browser/WASM build.');
+ }
+ throw error;
+ }
+
+ // Verify the signature
+ const verifyStart = Date.now();
+ const verifyResult = bitcoinpqc.verify(
+ keypair.publicKey,
+ messageUint8,
+ signature,
+ algorithm
+ );
+ const verifyDuration = Date.now() - verifyStart;
+
+ if (verifyResult) {
+ console.log('Signature verified successfully!');
+ } else {
+ console.log('ERROR: Signature verification failed!');
+ }
+ console.log(`Verification time: ${verifyDuration} ms`);
+
+ // Try to verify with a modified message
+ const modifiedMessageText = 'This is a MODIFIED message for PQC signature verification';
+ const modifiedMessage = Buffer.from(modifiedMessageText, 'utf8');
+ const modifiedMessageUint8 = new Uint8Array(modifiedMessage);
+ console.log(`Modified message: "${modifiedMessageText}"`);
+ const modifiedVerifyResult = bitcoinpqc.verify(
+ keypair.publicKey,
+ modifiedMessageUint8,
+ signature,
+ algorithm
+ );
+
+ if (modifiedVerifyResult) {
+ console.log('ERROR: Signature verified for modified message!');
+ } else {
+ console.log('Correctly rejected signature for modified message');
+ }
+
+ console.log('✓ Test passed!\n');
+ return true;
+ } catch (error) {
+ console.error(`❌ Error: ${error.message}`);
+ if (error.stack) {
+ console.error(error.stack);
+ }
+ return false;
+ }
+}
+
+async function runTests() {
+ console.log('Bitcoin PQC Library Example (Node.js - High-Level API)');
+ console.log('======================================================\n');
+ console.log('This example tests the post-quantum signature algorithms designed for BIP-360 and the Bitcoin QuBit soft fork.');
+ console.log('Using the high-level TypeScript wrapper API (index.js).\n');
+
+ // Initialize the module
+ try {
+ console.log('Initializing WASM module...');
+ await bitcoinpqc.init({
+ onRuntimeInitialized: () => {
+ console.log('✓ WASM module initialized successfully!\n');
+ },
+ print: (text) => {
+ // Enable WASM print output for debugging
+ console.log('WASM:', text);
+ },
+ printErr: (text) => {
+ console.error('WASM Error:', text);
+ },
+ // Node.js-specific: provide crypto.getRandomValues
+ getRandomValues: (arr) => {
+ const bytes = randomBytes(arr.length);
+ arr.set(bytes);
+ return arr;
+ }
+ });
+ } catch (error) {
+ console.error('Failed to initialize module:', error);
+ if (error.stack) {
+ console.error(error.stack);
+ }
+ process.exit(1);
+ }
+
+ const results = [];
+
+ // Test ML-DSA-44
+ results.push(await testAlgorithm(Algorithm.ML_DSA_44, 'ML-DSA-44'));
+
+ // Test SLH-DSA-Shake-128s
+ results.push(await testAlgorithm(Algorithm.SLH_DSA_SHAKE_128S, 'SLH-DSA-Shake-128s'));
+
+ // Summary
+ console.log('\n======================================================');
+ console.log('Test Summary:');
+ console.log(` ML-DSA-44: ${results[0] ? '✓ PASSED' : '✗ FAILED'}`);
+ console.log(` SLH-DSA-Shake-128s: ${results[1] ? '✓ PASSED' : '✗ FAILED'}`);
+ console.log('======================================================\n');
+
+ const exitCode = results.every(r => r) ? 0 : 1;
+ process.exit(exitCode);
+}
+
+// Start
+runTests();
diff --git a/bip-0360/ref-impl/js/tsconfig.json b/bip-0360/ref-impl/js/tsconfig.json
new file mode 100644
index 0000000000..b181afe130
--- /dev/null
+++ b/bip-0360/ref-impl/js/tsconfig.json
@@ -0,0 +1,45 @@
+{
+ // Visit https://aka.ms/tsconfig to read more about this file
+ "include": [
+ "src/**/*"
+ ],
+ "compilerOptions": {
+ // File Layout
+ "rootDir": "./src",
+ "outDir": "./dist",
+
+ // Environment Settings
+ // See also https://aka.ms/tsconfig/module
+ "module": "nodenext",
+ "target": "esnext",
+ "types": ["node"],
+ // For nodejs:
+ // "lib": ["esnext"],
+
+ // Other Outputs
+ "sourceMap": true,
+ "declaration": true,
+ "declarationMap": true,
+
+ // Stricter Typechecking Options
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+
+ // Style Options
+ // "noImplicitReturns": true,
+ // "noImplicitOverride": true,
+ // "noUnusedLocals": true,
+ // "noUnusedParameters": true,
+ // "noFallthroughCasesInSwitch": true,
+ // "noPropertyAccessFromIndexSignature": true,
+
+ // Recommended Options
+ "strict": true,
+ "jsx": "react-jsx",
+ "verbatimModuleSyntax": true,
+ "isolatedModules": true,
+ "noUncheckedSideEffectImports": true,
+ "moduleDetection": "force",
+ "skipLibCheck": true,
+ }
+}
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 b24393d0e9..ebdf473ddc 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"
@@ -13,9 +25,9 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.6.19"
+version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -28,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"
@@ -43,18 +55,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 +75,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.98"
+version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arrayvec"
@@ -83,17 +95,84 @@ 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-pqc-0.1"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "6ea956ce13678893de26ffce4ead2f28ac1748ccb8058341bf839616cd357ccf"
+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-pqc-0.1"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "e7687f76ee256115a2778e4b115bad9b439b87a8fccd52c4358c0f532482cf4c"
+dependencies = [
+ "bdk_chain",
+ "bitcoin",
+ "bitcoinpqc",
+ "miniscript",
+ "rand_core 0.6.4",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "bech32"
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"
+source = "sparse+https://crates.denver.space/api/v1/crates/"
+checksum = "b1831b23596c0d9d0d3cb01b059027718add9748f4c1167455c748d632124379"
dependencies = [
"base58ck",
+ "base64",
"bech32",
"bitcoin-internals",
"bitcoin-io",
@@ -101,7 +180,7 @@ dependencies = [
"bitcoin_hashes",
"hex-conservative",
"hex_lit",
- "secp256k1",
+ "secp256k1 0.29.1",
"serde",
]
@@ -141,20 +220,81 @@ dependencies = [
"serde",
]
+[[package]]
+name = "bitcoinpqc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf74aafaea8106c29daed19657c3952a4f297d44fbd437d5dd697772bb463fc2"
+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"
+version = "1.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
+checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [
+ "find-msvc-tools",
"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"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[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 = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+dependencies = [
+ "cc",
+]
[[package]]
name = "colorchoice"
@@ -162,6 +302,12 @@ 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"
@@ -185,6 +331,12 @@ dependencies = [
"log",
]
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
+
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -193,7 +345,35 @@ 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.7+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 = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "serde",
]
[[package]]
@@ -223,6 +403,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"
@@ -231,9 +420,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",
@@ -244,9 +433,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",
@@ -255,28 +444,58 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.174"
+version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
[[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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+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 = "4cf5cd2ed677044868fdcfbbc28175d535f5086524674f85938c474258bc60aa"
dependencies = [
"bech32",
"bitcoin",
+ "bitcoinpqc",
+ "serde",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
]
[[package]]
@@ -292,16 +511,19 @@ 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",
+ "bdk_wallet",
"bitcoin",
+ "bitcoinpqc",
"env_logger",
"hex",
"log",
"miniscript",
"once_cell",
+ "rand 0.9.2",
"serde",
"serde_json",
"thiserror",
@@ -309,9 +531,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"
@@ -331,24 +553,40 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
[[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",
]
[[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",
]
+[[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 +594,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 +615,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,14 +634,23 @@ 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]]
name = "regex"
-version = "1.11.1"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433"
dependencies = [
"aho-corasick",
"memchr",
@@ -393,9 +660,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.9"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6"
dependencies = [
"aho-corasick",
"memchr",
@@ -404,9 +671,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.5"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "ryu"
@@ -421,8 +694,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,20 +720,39 @@ 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"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.219"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@@ -457,14 +761,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]]
@@ -475,9 +780,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",
@@ -486,18 +791,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.12"
+version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.12"
+version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
@@ -506,9 +811,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"
@@ -516,27 +821,58 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+[[package]]
+name = "wasi"
+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 = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
[[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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
+ "windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
@@ -549,66 +885,72 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.52.6"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.52.6"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
-version = "0.52.6"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
-version = "0.52.6"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
-version = "0.52.6"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.52.6"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.52.6"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.52.6"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+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 80ba13e004..c3ddb0c961 100644
--- a/bip-0360/ref-impl/rust/Cargo.toml
+++ b/bip-0360/ref-impl/rust/Cargo.toml
@@ -1,15 +1,20 @@
[package]
-name = "p2qrh-ref"
+name = "p2tsh-ref"
version = "0.1.0"
edition = "2024"
[dependencies]
# Dev version of miniscript crate re-exports bitcoin 0.32.6
-miniscript = "13.0.0"
+# view configuration for "kellnr-denver-space":
+# cat .cargo/config.toml
+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" }
-# 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"] }
+
+# BDK Wallet with P2TSH support
+bdk_wallet = { version = "=3.0.0-alpha.0-pqc-0.1", registry = "kellnr-denver-space" }
env_logger = "0.11.5"
log = "0.4.22"
@@ -19,15 +24,18 @@ once_cell = "1.19"
hex = "0.4.3"
anyhow = "1.0.98"
thiserror = "2.0.12"
+rand = "0.9"
[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
# 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" }
diff --git a/bip-0360/ref-impl/rust/README.md b/bip-0360/ref-impl/rust/README.md
index 4b391dcadb..7a0de948d0 100644
--- a/bip-0360/ref-impl/rust/README.md
+++ b/bip-0360/ref-impl/rust/README.md
@@ -1,23 +1,13 @@
-# 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.
-As such, these test vectors assume the presence of these customized forks cloned to your local environment.
+## Run Test Vectors
-1. create soft-link to your `rust-bitcoin` clone:
- ```
- $ ln -s /path/to/git/clone/of/rust-bitcoin
- ```
+These test vectors are being developed in conjunction with forks of [rust-bitcoin](https://github.com/jbride/rust-bitcoin/tree/p2tsh) and [rust-miniscript](https://github.com/jbride/rust-miniscript/tree/p2tsh-pqc) customized with p2tsh functionality.
-1. create soft-link to your `rust-miniscript` clone:
- ```
- $ ln -s /path/to/git/clone/of/rust-miniscript
- ```
1. environment variables
```
@@ -29,6 +19,27 @@ 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
```
+## 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/rust_bitcoin_p2pqrh_changes.md b/bip-0360/ref-impl/rust/docs/development_notes.adoc
similarity index 55%
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..a1e8799969 100644
--- a/bip-0360/ref-impl/rust/docs/rust_bitcoin_p2pqrh_changes.md
+++ b/bip-0360/ref-impl/rust/docs/development_notes.adoc
@@ -1,73 +1,81 @@
-*P2QRH specific changes to rust-bitcoin*
-# 1. p2qrh module
+== bitcoin core
-The p2qrh branch of rust-bitcoin includes a new module: `p2qrh`.
+=== Two Different Size Limits:
-Source code for this new module can be found [here](https://github.com/jbride/rust-bitcoin/blob/p2qrh/bitcoin/src/p2qrh/mod.rs).
+* *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.
-Highlights of this _p2qrh_ module as follows:
+== P2TSH changes to rust-bitcoin
-## 1.1. P2qrhBuilder
+# 1. p2tsh module
+
+The p2tsh branch of rust-bitcoin includes a new module: `p2tsh`.
+
+Source code for this new module can be found [here](https://github.com/jbride/rust-bitcoin/blob/p2tsh/bitcoin/src/p2tsh/mod.rs).
+
+Highlights of this _p2tsh_ module as follows:
+
+## 1.1. p2tshBuilder
This is struct inherits from the rust-bitcoin _TaprootBuilder_.
It has an important modification in that it disables keypath spend.
-Similar to its Taproot parent, P2qrhBuilder provides functionality to add leaves to a TapTree.
-One its TapTree has been fully populated with all leaves, an instance of _P2qrhSpendInfo_ can be retrieved from P2qrhBuilder.
+Similar to its Taproot parent, p2tshBuilder provides functionality to add leaves to a TapTree.
+One its TapTree has been fully populated with all leaves, an instance of _p2tshSpendInfo_ can be retrieved from p2tshBuilder.
```
-pub struct P2qrhBuilder {
+pub struct p2tshBuilder {
inner: TaprootBuilder
}
-impl P2qrhBuilder {
+impl p2tshBuilder {
- /// Creates a new P2QRH builder.
+ /// Creates a new p2tsh builder.
pub fn new() -> Self {
Self {
inner: TaprootBuilder::new()
}
}
- /// Adds a leaf to the P2QRH builder.
+ /// Adds a leaf to the p2tsh builder.
pub fn add_leaf_with_ver(
self,
depth: u8,
script: ScriptBuf,
leaf_version: LeafVersion,
- ) -> Result {
+ ) -> Result {
match self.inner.add_leaf_with_ver(depth, script, leaf_version) {
Ok(builder) => Ok(Self { inner: builder }),
- Err(_) => Err(P2qrhError::LeafAdditionError)
+ Err(_) => Err(p2tshError::LeafAdditionError)
}
}
- /// Finalizes the P2QRH builder.
- pub fn finalize(self) -> Result {
+ /// Finalizes the p2tsh builder.
+ pub fn finalize(self) -> Result {
let node_info: NodeInfo = self.inner.try_into_node_info().unwrap();
- Ok(P2qrhSpendInfo {
+ Ok(p2tshSpendInfo {
merkle_root: Some(node_info.node_hash()),
//script_map: self.inner.script_map().clone(),
})
}
- /// Converts the P2QRH builder into a Taproot builder.
+ /// Converts the p2tsh builder into a Taproot builder.
pub fn into_inner(self) -> TaprootBuilder {
self.inner
}
}
```
-## 1.2. P2qrhSpendInfo
+## 1.2. p2tshSpendInfo
-Provides merkle_root of a completed P2qrh TapTree
+Provides merkle_root of a completed p2tsh TapTree
```
-/// A struct for P2QRH spend information.
+/// A struct for p2tsh spend information.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct P2qrhSpendInfo {
+pub struct p2tshSpendInfo {
/// The merkle root of the script path.
pub merkle_root: Option
@@ -75,27 +83,27 @@ pub struct P2qrhSpendInfo {
}
```
-## 1.3. P2qrhScriptBuf
+## 1.3. p2tshScriptBuf
-Allows for creation of a P2QRH scriptPubKey UTXO using only the merkle root of a script tree only.
+Allows for creation of a p2tsh scriptPubKey UTXO using only the merkle root of a script tree only.
```
-/// A wrapper around ScriptBuf for P2QRH (Pay to Quantum Resistant Hash) scripts.
-pub struct P2qrhScriptBuf {
+/// A wrapper around ScriptBuf for p2tsh (Pay to Quantum Resistant Hash) scripts.
+pub struct p2tshScriptBuf {
inner: ScriptBuf
}
-impl P2qrhScriptBuf {
- /// Creates a new P2QRH script from a ScriptBuf.
+impl p2tshScriptBuf {
+ /// Creates a new p2tsh script from a ScriptBuf.
pub fn new(inner: ScriptBuf) -> Self {
Self { inner }
}
- /// Generates P2QRH scriptPubKey output
+ /// Generates p2tsh scriptPubKey output
/// Only accepts the merkle_root (of type TapNodeHash)
- /// since keypath spend is disabled in p2qrh
- pub fn new_p2qrh(merkle_root: TapNodeHash) -> Self {
- // https://github.com/cryptoquick/bips/blob/p2qrh/bip-0360.mediawiki#scriptpubkey
+ /// since keypath spend is disabled in p2tsh
+ pub fn new_p2tsh(merkle_root: TapNodeHash) -> Self {
+ // https://github.com/cryptoquick/bips/blob/p2tsh/bip-0360.mediawiki#scriptpubkey
let merkle_root_hash_bytes: [u8; 32] = merkle_root.to_byte_array();
let script = Builder::new()
.push_opcode(OP_PUSHNUM_3)
@@ -104,7 +112,7 @@ impl P2qrhScriptBuf {
.push_slice(&merkle_root_hash_bytes)
.into_script();
- P2qrhScriptBuf::new(script)
+ p2tshScriptBuf::new(script)
}
/// Returns the script as a reference.
@@ -114,17 +122,17 @@ impl P2qrhScriptBuf {
}
```
-## 1.4. P2QRH Control Block
+## 1.4. p2tsh Control Block
Closely related to P2TR control block.
Difference being that _internal public key_ is not included.
```
-/// A control block for P2QRH (Pay to Quantum Resistant Hash) script path spending.
+/// A control block for p2tsh (Pay to Quantum Resistant Hash) script path spending.
/// This is a simplified version of Taproot's control block that excludes key-related fields.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct P2qrhControlBlock {
+pub struct p2tshControlBlock {
/// The version of the leaf.
pub leaf_version: LeafVersion,
/// The merkle branch of the leaf.
@@ -134,33 +142,33 @@ pub struct P2qrhControlBlock {
# 2. Witness Program
-New p2qrh related functions that allow for creation of a new V3 _witness program_ given a merkle_root only.
+New p2tsh related functions that allow for creation of a new V3 _witness program_ given a merkle_root only.
Found in bitcoin/src/blockdata/script/witness_program.rs
```
/// Creates a [`WitnessProgram`] from a 32 byte merkle root.
-fn new_p2qrh(program: [u8; 32]) -> Self {
+fn new_p2tsh(program: [u8; 32]) -> Self {
WitnessProgram { version: WitnessVersion::V3, program: ArrayVec::from_slice(&program) }
}
/// Creates a pay to quantum resistant hash address from a merkle root.
-pub fn p2qrh(merkle_root: Option) -> Self {
+pub fn p2tsh(merkle_root: Option) -> Self {
let merkle_root = merkle_root.unwrap();
- WitnessProgram::new_p2qrh(merkle_root.to_byte_array())
+ WitnessProgram::new_p2tsh(merkle_root.to_byte_array())
}
```
# 3. Address
-New _p2qrh_ function that allows for creation of a new _p2qrh_ Address given a merkle_root only.
+New _p2tsh_ function that allows for creation of a new _p2tsh_ Address given a merkle_root only.
Found in bitcoin/src/address/mod.rs
```
/// Creates a pay to quantum resistant hash address from a merkle root.
-pub fn p2qrh(merkle_root: Option, hrp: impl Into) -> Address {
- let program = WitnessProgram::p2qrh(merkle_root);
+pub fn p2tsh(merkle_root: Option, hrp: impl Into) -> Address {
+ let program = WitnessProgram::p2tsh(merkle_root);
Address::from_witness_program(program, hrp)
}
```
diff --git a/bip-0360/ref-impl/rust/docs/images/crypto_key_characteristics.png b/bip-0360/ref-impl/rust/docs/images/crypto_key_characteristics.png
new file mode 100644
index 0000000000..901d57a859
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/crypto_key_characteristics.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/faucet_1.png b/bip-0360/ref-impl/rust/docs/images/faucet_1.png
new file mode 100644
index 0000000000..826057475b
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/faucet_1.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/faucet_2.png b/bip-0360/ref-impl/rust/docs/images/faucet_2.png
new file mode 100644
index 0000000000..4ac08e24c4
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/faucet_2.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/funding_utxo_id.png b/bip-0360/ref-impl/rust/docs/images/funding_utxo_id.png
new file mode 100644
index 0000000000..e409d695bb
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/funding_utxo_id.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/mempool_next_block.png b/bip-0360/ref-impl/rust/docs/images/mempool_next_block.png
new file mode 100644
index 0000000000..89754f1411
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/mempool_next_block.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/mempool_spending_tx_1.png b/bip-0360/ref-impl/rust/docs/images/mempool_spending_tx_1.png
new file mode 100644
index 0000000000..14aa99bc94
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/mempool_spending_tx_1.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/mempool_spending_tx_2.png b/bip-0360/ref-impl/rust/docs/images/mempool_spending_tx_2.png
new file mode 100644
index 0000000000..e92fada18c
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/mempool_spending_tx_2.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/merkletree.png b/bip-0360/ref-impl/rust/docs/images/merkletree.png
new file mode 100644
index 0000000000..a4ed868819
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/merkletree.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/p2tsh_construction.png b/bip-0360/ref-impl/rust/docs/images/p2tsh_construction.png
new file mode 100644
index 0000000000..9ac23a5f93
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/p2tsh_construction.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/p2tsh_witness.png b/bip-0360/ref-impl/rust/docs/images/p2tsh_witness.png
new file mode 100644
index 0000000000..b1e7c72c85
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/p2tsh_witness.png differ
diff --git a/bip-0360/ref-impl/rust/docs/images/workshop_deployment_arch.png b/bip-0360/ref-impl/rust/docs/images/workshop_deployment_arch.png
new file mode 100644
index 0000000000..8435e2181a
Binary files /dev/null and b/bip-0360/ref-impl/rust/docs/images/workshop_deployment_arch.png differ
diff --git a/bip-0360/ref-impl/rust/docs/p2qrh-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2qrh-end-to-end.adoc
deleted file mode 100644
index 7a5d06ebb6..0000000000
--- a/bip-0360/ref-impl/rust/docs/p2qrh-end-to-end.adoc
+++ /dev/null
@@ -1,260 +0,0 @@
-:scrollbar:
-:data-uri:
-:toc2:
-:linkattrs:
-
-= P2QRH 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.
-
-Execute in Bitcoin Core `regtest` mode.
-
-== Pre-reqs
-
-=== Bitcoin Core
-
-The link:https://github.com/jbride/bitcoin/tree/p2qrh[p2qrh 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].
-
-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:
-
-. build
-+
------
-$ 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 `regtest` mode
-+
------
-$ ./build/bin/bitcoind -daemon=0 -regtest=1 -txindex
------
-
-=== Shell Environment
-
-. *b-reg* command line alias:
-+
-Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node running in `regtest` mode.
-. *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.
-For example, the following would be sufficient:
-
------
-$ export W_NAME=regtest \
- && export WPASS=regtest
-
-$ b-reg -named createwallet \
- wallet_name=$W_NAME \
- descriptors=true \
- passphrase="$WPASS" \
- load_on_startup=true
------
-
-== Fund P2QRH UTXO
-
-. OPTIONAL: Define number of leaves in tap tree as well as the tap leaf to later use as the unlocking script:
-+
------
-$ export TOTAL_LEAF_COUNT=5 \
- && export LEAF_OF_INTEREST=4
------
-+
-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:
-+
------
-$ export BITCOIN_NETWORK=regtest \
- && export BITCOIN_ADDRESS_INFO=$( cargo run --example p2qrh_construction ) \
- && echo $BITCOIN_ADDRESS_INFO | jq -r .
------
-+
-NOTE: In `regtest`, you can expect a P2QRH address that starts with: `bcrt1r` .
-+
-NOTE: In the context of P2QRH, 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:
-+
------
-$ 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 P2QRH_ADDR=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.utxo_return.bech32m_address' )
------
-
-. View tapscript used in target leaf of taptree:
-+
------
-$ 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:
-+
------
-$ export COINBASE_REWARD_TX_ID=$( b-reg -named generatetoaddress 1 $P2QRH_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
-+
------
-$ export P2QRH_DESC=$( b-reg getdescriptorinfo "addr($P2QRH_ADDR)" | jq -r '.descriptor' ) \
- && echo $P2QRH_DESC \
- && b-reg scantxoutset start '[{"desc": "'''$P2QRH_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' ) \
- && echo $FUNDING_TX_ID
------
-
-. Set FUNDING_UTXO_INDEX env var (used later to correctly identify funding UTXO when generating the spending tx)
-+
------
-$ export FUNDING_UTXO_INDEX=0
------
-
-. view details of funding UTXO to the P2QRH address:
-+
------
-$ export FUNDING_UTXO=$( b-reg getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout['''$FUNDING_UTXO_INDEX''']' ) \
- && echo $FUNDING_UTXO | jq -r .
------
-+
-NOTE: the above only works when Bitcoin Core is started with the following arg: -txindex
-
-== Spend P2QRH UTXO
-
-
-. Determine value (in sats) of funding utxo:
-+
------
-$ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{printf "%.0f", $1 * 100000000}') \
- && echo $FUNDING_UTXO_AMOUNT_SATS
------
-
-. Generate additional blocks.
-+
-This is necessary if you have only previously generated less than 100 blocks.
-+
------
-$ b-reg -generate 110
------
-+
-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_
-
-
-. 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 RAW_P2QRH_SPEND_TX=$( echo $SPEND_DETAILS | jq -r '.tx_hex' ) \
- && echo "RAW_P2QRH_SPEND_TX = $RAW_P2QRH_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' ) \
- && echo "SIG_BYTES = $SIG_BYTES"
------
-
-. Inspect the spending tx:
-+
------
-$ b-reg decoderawtransaction $RAW_P2QRH_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:
-+
------
-$ b-reg testmempoolaccept '["'''$RAW_P2QRH_SPEND_TX'''"]'
------
-
-. Submit tx:
-+
------
-$ export P2QRH_SPENDING_TX_ID=$( b-reg sendrawtransaction $RAW_P2QRH_SPEND_TX ) \
- && echo $P2QRH_SPENDING_TX_ID
------
-+
-NOTE: Should return same tx id as was included in $RAW_P2QRH_SPEND_TX
-
-== Mine P2QRH Spend TX
-
-. View tx in mempool:
-+
------
-$ b-reg getrawtransaction $P2QRH_SPENDING_TX_ID 1
------
-+
-NOTE: There will not yet be a field `blockhash` in the response.
-
-. Mine 1 block:
-+
------
-$ b-reg -generate 1
------
-
-. Obtain `blockhash` field of mined tx:
-+
------
-$ export BLOCK_HASH=$( b-reg getrawtransaction $P2QRH_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
- && echo $BLOCK_HASH
------
-
-. View tx in block:
-+
------
-$ b-reg getblock $BLOCK_HASH | jq -r .tx
------
-
-== TO-DO
-
-=== Import P2QRH 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
-
-# Set as non-active address (because can't generate subsequent p2qrh addresses yet)
-$ b-reg importdescriptors '[{
- "desc": "'''$P2QRH_DESC'''",
- "timestamp": "now",
- "active": false,
- "label": "p2qrh"
-}]'
------
diff --git a/bip-0360/ref-impl/rust/docs/p2tr-end-to-end.adoc b/bip-0360/ref-impl/rust/docs/p2tr-end-to-end.adoc
index 8c2243ce9d..df73a038f3 100644
--- a/bip-0360/ref-impl/rust/docs/p2tr-end-to-end.adoc
+++ b/bip-0360/ref-impl/rust/docs/p2tr-end-to-end.adoc
@@ -17,11 +17,11 @@ 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-pqc[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
+
@@ -93,7 +93,7 @@ NOTE: In the context of P2TR, the _tree_root_hex_ from the response is in refere
+
-----
$ 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_PRIV_KEYS_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_priv_keys_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' ) \
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
new file mode 100644
index 0000000000..42036e4613
--- /dev/null
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-end-to-end.adoc
@@ -0,0 +1,468 @@
+:scrollbar:
+:data-uri:
+:toc2:
+:linkattrs:
+
+= P2TSH End-to-End Tutorial
+
+:numbered:
+
+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
+
+If participating in a workshop, your instructor will provide a bitcoin environment.
+Related: your instructor should also provide you with a wallet.
+
+Otherwise, if running this tutorial on your own, follow the instructions in the appendix of this doc: <>.
+
+
+=== Shell Environment
+
+. *docker / podman*
++
+NOTE: If you have built the custom `p2tsh` enabled Bitcoin Core, you do not need docker (nor podman) installed. Skip this section.
++
+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.
+
+. *bitcoin-cli* command line utility:
++
+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.
+
+.. You will need a `bitcoin-cli` binary that is `p2tsh` enabled.
+For this purpose, a docker container with this `bitcoin-cli` utility is provided:
++
+-----
+$ docker pull quay.io/jbride2000/bitcoin-cli:p2tsh-pqc-0.0.1
+-----
+
+.. Configure an alias to the `bitcoin-cli` command that connects to your customized bitcoin-core node.
++
+-----
+$ 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'
+-----
+
+. *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.
+
+== Create & Fund P2TSH UTXO
+
+. Set an environment variable specific to your Bitcoin network environment (regtest, signet, etc)
++
+[source,bash]
+-----
+$ export BITCOIN_NETWORK=regtest
+-----
++
+Doing so influences the P2TSH address that you'll create later in this tutorial.
+
+
+. OPTIONAL: Indicate what type of cryptography to use in the locking scripts of your TapTree leaves.
+Valid options are: `SLH_DSA_ONLY`, `SCHNORR_ONLY`, `SCHNORR_AND_SLH_DSA`.
+Default is `SCHNORR_ONLY`.
++
+[source,bash]
+-----
+$ export LEAF_SCRIPT_TYPE=SLH_DSA_ONLY
+-----
+
+. OPTIONAL: Define number of leaves in tap tree as well as the tap leaf to later use as the unlocking script:
++
+[source,bash]
+-----
+$ export TOTAL_LEAF_COUNT=5 \
+ && export LEAF_OF_INTEREST=4
+-----
++
+NOTE: Defaults are 4 leaves with the first leaf (leaf 0 ) as the script to later use as the unlocking script.
+
+. Generate a P2TSH scripPubKey with multi-leaf taptree:
++
+[source,bash]
+-----
+$ export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tsh_construction ) \
+ && echo $BITCOIN_ADDRESS_INFO | jq -r .
+-----
++
+NOTE: In `regtest`, you can expect a P2TSH address that starts with: `bcrt1z` .
++
+[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:
++
+[source,bash]
+-----
+$ export MERKLE_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tree_root_hex' ) \
+ && export LEAF_SCRIPT_PRIV_KEYS_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_priv_keys_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' )
+-----
+
+. View tapscript used in target leaf of taptree:
++
+[source,bash]
+-----
+$ 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`) opcode.
+
+. 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, execute the following:
++
+[source,bash]
+-----
+$ export COINBASE_REWARD_TX_ID=$( b-cli -regtest -named generatetoaddress nblocks=1 address="$P2TSH_ADDR" maxtries=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:
++
+[source,bash]
+-----
+$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"
+-----
+
+. view summary of all txs that have funded P2TSH address
++
+[source,bash]
+-----
+$ export P2TSH_DESC=$( b-cli getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
+ && echo $P2TSH_DESC \
+ && b-cli scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]'
+-----
+
+. grab txid of first tx with unspent funds:
++
+[source,bash]
+-----
+$ export FUNDING_TX_ID=$( b-cli scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]' | jq -r '.unspents[0].txid' ) \
+ && echo $FUNDING_TX_ID
+-----
+
+. Set FUNDING_UTXO_INDEX env var (used later to correctly identify funding UTXO when generating the spending tx)
++
+[source,bash]
+-----
+$ export FUNDING_UTXO_INDEX=0
+-----
+
+. view details of funding UTXO to the P2TSH address:
++
+[source,bash]
+-----
+$ export FUNDING_UTXO=$( b-cli getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout['''$FUNDING_UTXO_INDEX''']' ) \
+ && echo $FUNDING_UTXO | jq -r .
+-----
++
+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 the funding P2TSH utxo:
++
+[source,bash]
+-----
+$ export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{printf "%.0f", $1 * 100000000}') \
+ && echo $FUNDING_UTXO_AMOUNT_SATS
+-----
+
+. Generate additional blocks.
++
+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
++
+[source,bash]
+-----
+$ b-cli -generate 110
+-----
+
+.. signet
++
+This will involve having the signet miner generate about 110 blocks .... which can take about 10 minutes.
++
+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:
++
+[source,bash]
+-----
+$ export SPEND_DETAILS=$( cargo run --example p2tsh_spend )
+
+$ 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' ) \
+ && 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:
++
+[source,bash]
+-----
+$ b-cli 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 p2tsh enabled Bitcoin Core:
++
+[source,bash]
+-----
+$ b-cli testmempoolaccept '["'''$RAW_P2TSH_SPEND_TX'''"]'
+-----
+
+. Submit tx:
++
+[source,bash]
+-----
+$ export P2TSH_SPENDING_TX_ID=$( b-cli sendrawtransaction $RAW_P2TSH_SPEND_TX ) \
+ && echo $P2TSH_SPENDING_TX_ID
+-----
++
+NOTE: Should return same tx id as was included in $RAW_P2TSH_SPEND_TX
+
+== Mine P2TSH Spend TX
+
+. View tx in mempool:
++
+[source,bash]
+-----
+$ b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1
+-----
++
+NOTE: There will not yet be a field `blockhash` in the response.
+
+. Mine 1 block:
+
+.. regtest:
++
+[source,bash]
+-----
+$ b-cli -generate 1
+-----
+
+.. signet:
++
+If on `signet` network, then execute the following:
++
+[source,bash]
+-----
+$ $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:
++
+[source,bash]
+-----
+$ export BLOCK_HASH=$( b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
+ && echo $BLOCK_HASH
+-----
+
+. View tx in block:
++
+[source,bash]
+-----
+$ b-cli getblock $BLOCK_HASH | jq -r .tx
+-----
+
+== 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
+
+$ cmake --build build -j$(nproc)
+-----
+
+. run in either `regtest` or `signet` mode:
+
+.. regtest:
++
+-----
+$ ./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.
+
+. 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
+-----
+
+=== Schnorr + SLH-DSA
+
+-----
+ OP_CHECKSIG OP_SUBSTR OP_BOOLAND OP_VERIFY
+-----
+
+
+The logic flow is:
+
+. OP_CHECKSIG: Verify Schnorr signature against Schnorr pubkey
+. OP_SUBSTR: Verify SLH-DSA signature against SLH-DSA pubkey (using OP_SUBSTR for the SLH-DSA verification)
+. OP_BOOLAND: Ensure both signature verifications succeeded
+. OP_VERIFY: Final verification that the script execution succeeded
+. This creates a "both signatures required" locking condition, which is exactly what you want for SCHNORR_AND_SLH_DSA scripts.
+
+
+===== Sighash bytes
+
+Sighash bytes are appended to each signature, instead of being separate witness elements:
+
+. SlhDsaOnly: SLH-DSA signature + sighash byte appended
+. SchnorrOnly: Schnorr signature + sighash byte appended
+. SchnorrAndSlhDsa: Schnorr signature (no sighash) + SLH-DSA signature + sighash byte appended to the last signature
diff --git a/bip-0360/ref-impl/rust/docs/p2tsh-workshop.adoc b/bip-0360/ref-impl/rust/docs/p2tsh-workshop.adoc
new file mode 100644
index 0000000000..7ff10b867e
--- /dev/null
+++ b/bip-0360/ref-impl/rust/docs/p2tsh-workshop.adoc
@@ -0,0 +1,510 @@
+:scrollbar:
+:data-uri:
+:toc2:
+:linkattrs:
+
+= P2TSH End-to-End workshop
+
+:numbered:
+
+Welcome to the BIP-360 / _Pay-To-Tap-Script-Hash_ (P2TSH) workshop !
+
+In this workshop, you will interact with a custom Signet environment to create, fund and spend from a _P2TSH_ address.
+
+_P2TSH_ is a new Bitcoin address type defined in link:https://bip360.org/bip360.html[bip-360].
+
+
+In addition, this workshop allows for the (un)locking mechanism of the leaf scripts of your P2TSH address to optionally use _Post Quantum Cryptography_ (PQC).
+The use of PQC is alluded to in BIP-360 and will be further defined in future BIPs.
+
+The steps outlined in this workshop are executed using a P2TSH/PQC enabled Bitcoin Core instance running on a signet environment.
+*The target audience of the workshop is Bitcoin developers and ops personnel.
+As such, the workshop makes heavy use of the _bitcoin-cli_ at the command line.*
+
+== Pre-reqs
+
+=== *docker / podman*
+
+This workshop environment is provided as a _docker_ container.
+Subsequently, ensure your host machine has either link:https://docs.docker.com/desktop/[docker] or link:https://podman.io/docs/installation[podman] installed.
+
+=== *p2tsh_demo* docker container
+
+==== Obtain container
+
+Once docker or podman is installed on your machine, you can obtain the workshop docker container via any of the following:
+
+. Pull from _quay.io_ :
++
+[source,bash]
+-----
+sudo docker pull quay.io/jbride2000/p2tsh_demo:0.1
+-----
++
+NOTE: The container image is 1.76GB in size. This approach may be slow depending on network bandwidth.
+
+. Download container image archive file from local HTTP server:
++
+*TO_DO*
+
+. Obtain container image archive file from instructor:
+
+.. Workshop instructors have the container image available via USB thumb drives.
+If you feel comfortable with this approach, ask an instructor for a thumb drive.
+
+... Mount the USB thumb drive and copy for the file called: _p2tsh_demo-image.tar_.
+... Load the container image into your docker environment as per the following:
++
+[source,bash]
+-----
+docker load -i /path/to/p2tsh_demo-image.tar
+-----
+
+==== Start container
+
+You will need to start the workshop container using the docker infrastructure on your machine.
+
+. If working at the command line, the following is an example to run the container and obtain a command prompt:
++
+[source,bash]
+-----
+
+sudo docker run -it --rm --entrypoint /bin/bash --network host \
+ -e RPC_CONNECT=10.21.3.194 \
+ quay.io/jbride2000/p2tsh_demo:0.1
+-----
+
+. You should see a _bash_ shell command prompt similar to the following:
++
+-----
+bip360@0aa9edf3d201:~/bips/bip-0360/ref-impl/rust$ ls
+
+
+Cargo.lock Cargo.toml README.md docs examples src tests
+-----
++
+As per the `ls` command seen above, your command prompt path defaults to the link:https://github.com/jbride/bips/tree/p2tsh/bip-0360/ref-impl/rust[Rust based reference implementation] for BIP-360.
+
+==== Container contents
+Your docker environment already includes a P2TSH/PQC enabled `bitcoin-cli` utility.
+In addition, an alias to this custom bitcoin-cli utility configured for the signet workshop environment has also been provided.
+
+. You can view this alias as follows (execute all commands within workshop container image):
++
+[source,bash]
+-----
+declare -f b-cli
+-----
++
+You should see a response similar to the following:
++
+-----
+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} "$@"
+}
+-----
+
+. Test interaction between your _b-cli_ utility and the workshop's signet node via the following:
++
+[source,bash]
+-----
+b-cli getnetworkinfo
+-----
++
+[source,bash]
+-----
+b-cli getblockcount
+-----
+
+. In addition, your docker environment also comes pre-installed with the following utilities needed for this workshop:
+
+. *jq*: json parsing utility
+. *awk*
+. *Rust* development environment with _cargo_ utility
+
+== Bitcoin Environment
+
+Your workshop instructors have provided you with a _P2TSH/PQC_ enabled Bitcoin environment running in _signet_.
+
+You will send RPC commands to this custom Bitcoin node via the _b-cli_of your docker container.
+
+image::images/workshop_deployment_arch.png[]
+
+Via your browser, you will interact with the P2TSH enabled _mempool.space_ for the workshop at: link:http://signet.bip360.org[signet.bip360.org].
+
+
+== Create & Fund P2TSH Address
+
+The purpose of this workshop is to demonstrate construction and spending of a link:https://github.com/cryptoquick/bips/blob/p2qrh/bip-0360.mediawiki[bip-360] _P2TSH_ address (optionally using _Post-Quantum Cryptography_).
+
+In this section of the workshop, you create and fund a P2TSH address.
+
+The following depicts the construction of a P2TSH _TapTree_ and computation its _scriptPubKey_.
+
+image::images/p2tsh_construction.png[]
+
+A P2TSH address is created by adding locking scripts to leaves of a _TapTree_.
+The locking scripts can use either _Schnorr_ (as per BIP-360) or _SLH-DSA_ (defined in a future BIP) cryptography.
+
+. OPTIONAL: In your container image, indicate what type of cryptography to use in the locking scripts of your TapTree leaves.
+Valid options are: `SLH_DSA_ONLY`, `SCHNORR_ONLY`, `SCHNORR_AND_SLH_DSA`.
+Default is `SCHNORR_ONLY`.
++
+[source,bash]
+-----
+$ export LEAF_SCRIPT_TYPE=SLH_DSA_ONLY
+-----
+
+.. If you set _LEAF_SCRIPT_TYPE=SCHNORR_ONLY_, then the locking script of your TapTree leaves will utilize _Schnorr_ cryptography.
++
+Schnorr is not quantum-resistant. However, its signature size is relatively small: 64 bytes.
+
+.. If you set _LEAF_SCRIPT_TYPE=SLH_DSA_ONLY_, then the locking script of your TapTree leaves will utilize _SLH-DSA_ cryptography.
++
+SLH_DSA is quantum-resistant. However, the trade-off is the much larger signature size 7,856 bytes when spending.
++
+image::images/crypto_key_characteristics.png[]
++
+NOTE: PQC cryptography is made available to this BIP-360 reference implementation via the link:https://crates.io/crates/bitcoinpqc[libbitcoinpqc Rust bindings].
+
+.. If you set _LEAF_SCRIPT_TYPE=SCHNORR_AND_SLH_DSA_, then the locking script of your TapTree leaves will be secured using both SCHNORR and SLH-DSA cryptography.
+
+. Define number of total leaves in tap tree :
++
+[source,bash]
+-----
+export TOTAL_LEAF_COUNT=5
+-----
+
+. Set the tap leaf index to later use as the unlocking script (when spending)
++
+[source,bash]
+-----
+export LEAF_OF_INTEREST=4
+-----
++
+NOTE: Defaults is 4 leaves with the first leaf (leaf 0 ) as the script to later use to unlock during spending.
+
+. Generate a P2TSH scripPubKey with multi-leaf taptree:
++
+[source,bash]
+-----
+export BITCOIN_ADDRESS_INFO=$( cargo run --example p2tsh_construction ) \
+ && echo $BITCOIN_ADDRESS_INFO | jq -r .
+-----
++
+NOTE: In signet, you can expect a P2TSH address that starts with the following prefix: `tb1z` .
++
+[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's source file: link:../src/lib.rs[src/lib.rs]
+
+. Fund this P2TSH address using workshop's signet faucet
+
+.. In a browser tab, navigate to: link:http://faucet.bip360.org/[faucet.bip360.org].
+.. Copy-n-paste the value of `bech32m_address` (found in the json response from the previous step)
++
+image::images/faucet_1.png[]
++
+Press the `Request` button.
+.. The faucet should allocate bitcoin to your address.
++
+image::images/faucet_2.png[]
+
+.. Click on the link of your transaction id.
+This will take you a detailed view of the transaction.
+Scroll down to the the _Inputs & Outputs_ section of the transaction and identify the _vout_ index of funds sent to your _P2TSH_ address.
++
+image::images/funding_utxo_id.png[]
+
+.. Return back to your terminal and set a _FUNDING_UTXO_INDEX_ environment variable (used later to correctly identify funding UTXO when generating the spending tx)
++
+[source,bash]
+-----
+export FUNDING_UTXO_INDEX=
+-----
+
+.. Return back to your browser tab at navigate to: link:https://signet.bip360.org[signet.bip360.org] and wait until a new block mines the transaction from the faucet that funded your P2TSH address.
++
+image::images/mempool_next_block.png[]
+
+. Set some env vars (for use in later steps in this workshop) based on previous json result:
++
+[source,bash]
+-----
+export MERKLE_ROOT=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.tree_root_hex' ) \
+ && export LEAF_SCRIPT_PRIV_KEYS_HEX=$( echo $BITCOIN_ADDRESS_INFO | jq -r '.taptree_return.leaf_script_priv_keys_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' )
+-----
+
+. View tapscript used in target leaf of taptree:
++
+[source,bash]
+-----
+b-cli decodescript $LEAF_SCRIPT_HEX | jq -r '.asm'
+-----
++
+NOTE: If not using Schnorr crypto, this script commits to a Schnorr 32-byte x-only public key.
+If using SLH_DSA, this script commits to a 32-byte SLH-DSA pub key and a OP_SUCCESS127 (represented as `OP_SUBSTR`) opcode.,
+If using SCHNORR + SLH_DSA, then you should see a locking script in the leaf similar to the following:
++
+-----
+886fc1edb7a8a364da65aef57343de451c1449d8a6c5b766fe150667d50d3e80 OP_CHECKSIG 479f93fbd251863c3e3e72da6e26ea82f87313da13090de10e57eca1f8b5e0f3 OP_SUBSTR OP_BOOLAND OP_VERIFY
+-----
+
+. view summary of all txs that have funded P2TSH address
++
+[source,bash]
+-----
+export P2TSH_DESC=$( b-cli getdescriptorinfo "addr($P2TSH_ADDR)" | jq -r '.descriptor' ) \
+ && echo $P2TSH_DESC \
+ && b-cli scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]'
+-----
++
+NOTE: You will likely have to wait a few minutes until a new block (containing the tx that funds your P2TSH address) is mined.
+
+. grab txid of first tx with unspent funds:
++
+[source,bash]
+-----
+export FUNDING_TX_ID=$( b-cli scantxoutset start '[{"desc": "'''$P2TSH_DESC'''"}]' | jq -r '.unspents[0].txid' ) \
+ && echo $FUNDING_TX_ID
+-----
+
+. view details of funding UTXO to the P2TSH address:
++
+[source,bash]
+-----
+export FUNDING_UTXO=$( b-cli getrawtransaction $FUNDING_TX_ID 1 | jq -r '.vout['''$FUNDING_UTXO_INDEX''']' ) \
+ && echo $FUNDING_UTXO | jq -r .
+-----
+
+
+== 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.
+
+The target address type that you send funds to is: P2WPKH.
+
+
+. Determine value (in sats) of the funding P2TSH utxo:
++
+[source,bash]
+-----
+export FUNDING_UTXO_AMOUNT_SATS=$(echo $FUNDING_UTXO | jq -r '.value' | awk '{printf "%.0f", $1 * 100000000}') \
+ && echo $FUNDING_UTXO_AMOUNT_SATS
+-----
+
+. Referencing the funding tx (via $FUNDING_TX_ID and $FUNDING_UTXO_INDEX), create the spending tx:
++
+[source,bash]
+-----
+export SPEND_DETAILS=$( cargo run --example p2tsh_spend )
+-----
++
+[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's source file: link:../src/lib.rs[src/lib.rs]
+
+. Set environment variables passed to _bitcoin-cli_ when spending:
++
+[source,bash]
+-----
+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' ) \
+ && echo "SIG_BYTES = $SIG_BYTES"
+-----
+
+. Inspect the spending tx:
++
+[source,bash]
+-----
+b-cli decoderawtransaction $RAW_P2TSH_SPEND_TX
+-----
++
+Pay particular attention to the `vin.txinwitness` field.
+The following depicts the elements of a P2TSH witness stack.
++
+image::images/p2tsh_witness.png[]
++
+Do the three elements (script input, script and control block) of the witness stack for this _script path spend_ correspond ?
+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:
++
+[source,bash]
+-----
+b-cli testmempoolaccept '["'''$RAW_P2TSH_SPEND_TX'''"]'
+-----
+
+. Submit tx:
++
+[source,bash]
+-----
+export P2TSH_SPENDING_TX_ID=$( b-cli sendrawtransaction $RAW_P2TSH_SPEND_TX ) \
+ && echo $P2TSH_SPENDING_TX_ID
+-----
++
+NOTE: Should return same tx id as was included in $RAW_P2TSH_SPEND_TX
+
+== Mine P2TSH Spend TX
+
+. View tx in mempool:
++
+[source,bash]
+-----
+b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1
+-----
++
+NOTE: There will not yet be a field `blockhash` in the response.
+
+. Monitor the mempool.space instance at link:http://signet.bip360.org[signet.bip360.org] until a new block is mined.
+
+. While still in the mempool.space instance at link:http://signet.bip360.org[signet.bip360.org], lookup your tx (denoted by: $P2TSH_SPENDING_TX_ID ) in the top-right search bar:
++
+image::images/mempool_spending_tx_1.png[]
++
+Click on the `Details` button at the top-right of the `Inputs & Outputs` section.
+
+.. Study the elements of the `Witness. Approximately how large is each element of the witness stack?
+
+.. View the values of the `Previous output script` and `Previous output type` fields:
++
+image::images/mempool_spending_tx_2.png[]
+
+. Obtain `blockhash` field of mined tx:
++
+[source,bash]
+-----
+export BLOCK_HASH=$( b-cli getrawtransaction $P2TSH_SPENDING_TX_ID 1 | jq -r '.blockhash' ) \
+ && echo $BLOCK_HASH
+-----
+
+. View txs in block:
++
+[source,bash]
+-----
+b-cli getblock $BLOCK_HASH | jq -r .tx
+-----
++
+You should see your tx (as per $P2TSH_SPENDING_TX_ID) in the list.
++
+Congratulations!! You have created, funded and spent from a P2TSH address.
+
+== Appendix
+
+[[build_p2tsh]]
+=== Build P2TSH / PQC Enabled Bitcoin Core
+
+*FOR THE PURPOSE OF THE WORKSHOP, YOU CAN IGNORE THIS SECTION*
+
+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
+
+$ cmake --build build -j$(nproc)
+-----
+
+. run in either `regtest` or `signet` mode:
+
+.. regtest:
++
+-----
+$ ./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 workshop] provides a nice overview of the topic.
+
+=== libbitcoinpqc Rust bindings
+
+*FOR THE PURPOSE OF THE WORKSHOP, YOU CAN IGNORE THIS SECTION*
+
+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
+-----
diff --git a/bip-0360/ref-impl/rust/docs/stack_element_size_performance_tests.adoc b/bip-0360/ref-impl/rust/docs/stack_element_size_performance_tests.adoc
index de315b2279..468760626a 100644
--- a/bip-0360/ref-impl/rust/docs/stack_element_size_performance_tests.adoc
+++ b/bip-0360/ref-impl/rust/docs/stack_element_size_performance_tests.adoc
@@ -243,7 +243,7 @@ This analysis shows that the OP_DUP operation has a measurable but manageable pe
=== Procedure
-* Testing is done using functionality found in the link:https://github.com/jbride/bitcoin/tree/p2qrh[p2qrh branch] of Bitcoin Core.
+* Testing is done using functionality found in the link:https://github.com/jbride/bitcoin/tree/p2tsh-pqc[p2tsh branch] of Bitcoin Core.
* Compilation of Bitcoin Core is done using the following `cmake` flags:
+
diff --git a/bip-0360/ref-impl/rust/examples/p2qrh_construction.rs b/bip-0360/ref-impl/rust/examples/p2qrh_construction.rs
deleted file mode 100644
index 267f78703c..0000000000
--- a/bip-0360/ref-impl/rust/examples/p2qrh_construction.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use p2qrh_ref::{create_p2qrh_utxo, create_p2qrh_multi_leaf_taptree};
-use p2qrh_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);
-
- return ConstructionReturn {
- taptree_return: taptree_return,
- utxo_return: p2qrh_utxo_return,
- };
-}
diff --git a/bip-0360/ref-impl/rust/examples/p2qrh_spend.rs b/bip-0360/ref-impl/rust/examples/p2qrh_spend.rs
deleted file mode 100644
index 1a207f1389..0000000000
--- a/bip-0360/ref-impl/rust/examples/p2qrh_spend.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-use p2qrh_ref::{ pay_to_p2wpkh_tx, verify_schnorr_signature_via_bytes };
-
-use p2qrh_ref::data_structures::SpendDetails;
-use std::env;
-use log::{info, error};
-
-// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
-fn main() -> SpendDetails {
-
- let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
-
- // FUNDING_TX_ID environment variable is required
- let funding_tx_id: String = env::var("FUNDING_TX_ID")
- .unwrap_or_else(|_| {
- error!("FUNDING_TX_ID environment variable is required but not set");
- std::process::exit(1);
- });
- let funding_tx_id_bytes: Vec = hex::decode(funding_tx_id.clone()).unwrap();
-
- // FUNDING_UTXO_AMOUNT_SATS environment variable is required
- let funding_utxo_amount_sats: u64 = env::var("FUNDING_UTXO_AMOUNT_SATS")
- .unwrap_or_else(|_| {
- error!("FUNDING_UTXO_AMOUNT_SATS environment variable is required but not set");
- std::process::exit(1);
- })
- .parse::()
- .unwrap_or_else(|_| {
- error!("FUNDING_UTXO_AMOUNT_SATS must be a valid u64 integer");
- std::process::exit(1);
- });
-
- // The input index of the funding tx
- // Allow override via FUNDING_UTXO_INDEX environment variable
- let funding_utxo_index: u32 = env::var("FUNDING_UTXO_INDEX")
- .ok()
- .and_then(|s| s.parse::().ok())
- .unwrap_or(0);
-
- info!("Funding tx id: {}, utxo index: {}", funding_tx_id, funding_utxo_index);
-
- // FUNDING_SCRIPT_PUBKEY environment variable is required
- let funding_script_pubkey_bytes: Vec = env::var("FUNDING_SCRIPT_PUBKEY")
- .map(|s| hex::decode(s).unwrap())
- .unwrap_or_else(|_| {
- error!("FUNDING_SCRIPT_PUBKEY environment variable is required but not set");
- std::process::exit(1);
- });
-
- let control_block_bytes: Vec = env::var("CONTROL_BLOCK_HEX")
- .map(|s| hex::decode(s).unwrap())
- .unwrap_or_else(|_| {
- error!("CONTROL_BLOCK_HEX environment variable is required but not set");
- std::process::exit(1);
- });
- info!("P2QRH 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())
- .unwrap_or_else(|_| {
- error!("LEAF_SCRIPT_PRIV_KEY_HEX environment variable is required but not set");
- 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())
- .unwrap_or_else(|_| {
- error!("LEAF_SCRIPT_HEX environment variable is required but not set");
- std::process::exit(1);
- });
-
- // https://learnmeabitcoin.com/explorer/tx/797505b104b5fb840931c115ea35d445eb1f64c9279bf23aa5bb4c3d779da0c2#outputs
- let spend_output_pubkey_hash_bytes: Vec = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
-
- // OUTPUT_AMOUNT_SATS env var is optional. Default is FUNDING_UTXO_AMOUNT_SATS - 5000 sats
- let spend_output_amount_sats: u64 = env::var("OUTPUT_AMOUNT_SATS")
- .ok()
- .and_then(|s| s.parse::().ok())
- .unwrap_or(funding_utxo_amount_sats.saturating_sub(5000));
-
-
- let result: SpendDetails = pay_to_p2wpkh_tx(
- funding_tx_id_bytes,
- funding_utxo_index,
- funding_utxo_amount_sats,
- funding_script_pubkey_bytes,
- control_block_bytes,
- leaf_script_bytes.clone(),
- leaf_script_priv_key_bytes,
- spend_output_pubkey_hash_bytes,
- spend_output_amount_sats
- );
-
- // 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);
-
- return result;
-}
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..cf60ffd744 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, LeafScriptType};
use std::env;
use log::{info, error};
@@ -54,12 +54,36 @@ fn main() -> SpendDetails {
});
info!("P2TR 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())
- .unwrap_or_else(|_| {
- error!("LEAF_SCRIPT_PRIV_KEY_HEX environment variable is required but not set");
- std::process::exit(1);
- });
+ // P2TR only supports Schnorr signatures, so we only need one private key
+ let leaf_script_priv_key_bytes: Vec = {
+ let priv_keys_hex_array = env::var("LEAF_SCRIPT_PRIV_KEYS_HEX")
+ .unwrap_or_else(|_| {
+ error!("LEAF_SCRIPT_PRIV_KEYS_HEX environment variable is required but not set");
+ std::process::exit(1);
+ });
+ // Parse JSON array and extract the first (and only) hex string
+ let priv_keys_hex: String = serde_json::from_str::>(&priv_keys_hex_array)
+ .unwrap_or_else(|_| {
+ error!("Failed to parse LEAF_SCRIPT_PRIV_KEYS_HEX as JSON array");
+ std::process::exit(1);
+ })
+ .into_iter()
+ .next()
+ .unwrap_or_else(|| {
+ error!("LEAF_SCRIPT_PRIV_KEYS_HEX array is empty");
+ std::process::exit(1);
+ });
+ hex::decode(priv_keys_hex).unwrap()
+ };
+
+ // Validate that the private key is 32 bytes (Schnorr key size)
+ if leaf_script_priv_key_bytes.len() != 32 {
+ error!("P2TR private key must be 32 bytes (Schnorr), got {}", leaf_script_priv_key_bytes.len());
+ std::process::exit(1);
+ }
+
+ // Convert to Vec> format expected by the function
+ let leaf_script_priv_keys_bytes: Vec> = vec![leaf_script_priv_key_bytes];
// ie: OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
let leaf_script_bytes: Vec = env::var("LEAF_SCRIPT_HEX")
@@ -86,9 +110,10 @@ fn main() -> SpendDetails {
funding_script_pubkey_bytes,
control_block_bytes,
leaf_script_bytes.clone(),
- leaf_script_priv_key_bytes,
+ leaf_script_priv_keys_bytes, // Now passing Vec> format
spend_output_pubkey_hash_bytes.clone(),
- spend_output_amount_sats
+ spend_output_amount_sats,
+ LeafScriptType::SchnorrOnly
);
// Remove first and last byte from leaf_script_bytes to get tapleaf_pubkey_bytes
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'''"}]'
diff --git a/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs b/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
new file mode 100644
index 0000000000..1130708903
--- /dev/null
+++ b/bip-0360/ref-impl/rust/examples/p2tsh_construction.rs
@@ -0,0 +1,21 @@
+use p2tsh_ref::{create_p2tsh_utxo, create_p2tsh_multi_leaf_taptree, parse_leaf_script_type};
+use p2tsh_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn, LeafScriptType};
+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 leaf_script_type = parse_leaf_script_type();
+ info!("leaf_script_type: {:?}", leaf_script_type);
+
+ 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: p2tsh_utxo_return,
+ };
+}
diff --git a/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs b/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
new file mode 100644
index 0000000000..fc326f5c45
--- /dev/null
+++ b/bip-0360/ref-impl/rust/examples/p2tsh_spend.rs
@@ -0,0 +1,248 @@
+use p2tsh_ref::{ pay_to_p2wpkh_tx, verify_schnorr_signature_via_bytes, verify_slh_dsa_via_bytes, parse_leaf_script_type };
+
+use p2tsh_ref::data_structures::{SpendDetails, LeafScriptType};
+use std::env;
+use log::{info, error};
+
+// Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
+fn main() -> SpendDetails {
+
+ let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
+
+ // FUNDING_TX_ID environment variable is required
+ let funding_tx_id: String = env::var("FUNDING_TX_ID")
+ .unwrap_or_else(|_| {
+ error!("FUNDING_TX_ID environment variable is required but not set");
+ std::process::exit(1);
+ });
+ let funding_tx_id_bytes: Vec = hex::decode(funding_tx_id.clone()).unwrap();
+
+ // FUNDING_UTXO_AMOUNT_SATS environment variable is required
+ let funding_utxo_amount_sats: u64 = env::var("FUNDING_UTXO_AMOUNT_SATS")
+ .unwrap_or_else(|_| {
+ error!("FUNDING_UTXO_AMOUNT_SATS environment variable is required but not set");
+ std::process::exit(1);
+ })
+ .parse::()
+ .unwrap_or_else(|_| {
+ error!("FUNDING_UTXO_AMOUNT_SATS must be a valid u64 integer");
+ std::process::exit(1);
+ });
+
+ // The input index of the funding tx
+ // Allow override via FUNDING_UTXO_INDEX environment variable
+ let funding_utxo_index: u32 = env::var("FUNDING_UTXO_INDEX")
+ .ok()
+ .and_then(|s| s.parse::().ok())
+ .unwrap_or(0);
+
+ info!("Funding tx id: {}, utxo index: {}", funding_tx_id, funding_utxo_index);
+
+ // FUNDING_SCRIPT_PUBKEY environment variable is required
+ let funding_script_pubkey_bytes: Vec = env::var("FUNDING_SCRIPT_PUBKEY")
+ .map(|s| hex::decode(s).unwrap())
+ .unwrap_or_else(|_| {
+ error!("FUNDING_SCRIPT_PUBKEY environment variable is required but not set");
+ std::process::exit(1);
+ });
+
+ let control_block_bytes: Vec = env::var("CONTROL_BLOCK_HEX")
+ .map(|s| hex::decode(s).unwrap())
+ .unwrap_or_else(|_| {
+ error!("CONTROL_BLOCK_HEX environment variable is required but not set");
+ std::process::exit(1);
+ });
+ info!("P2TSH control block size: {}", control_block_bytes.len());
+
+ // LEAF_SCRIPT_TYPE environment variable is required to determine key structure
+ let leaf_script_type: LeafScriptType = parse_leaf_script_type();
+ info!("leaf_script_type: {:?}", leaf_script_type);
+
+ // Parse private keys based on script type
+ let leaf_script_priv_keys_bytes: Vec> = match leaf_script_type {
+ LeafScriptType::SlhDsaOnly => {
+ let priv_keys_hex_array = env::var("LEAF_SCRIPT_PRIV_KEYS_HEX")
+ .unwrap_or_else(|_| {
+ error!("LEAF_SCRIPT_PRIV_KEYS_HEX environment variable is required for SLH_DSA_ONLY");
+ std::process::exit(1);
+ });
+ // Parse JSON array and extract the first (and only) hex string
+ let priv_keys_hex: String = serde_json::from_str::>(&priv_keys_hex_array)
+ .unwrap_or_else(|_| {
+ error!("Failed to parse LEAF_SCRIPT_PRIV_KEYS_HEX as JSON array");
+ std::process::exit(1);
+ })
+ .into_iter()
+ .next()
+ .unwrap_or_else(|| {
+ error!("LEAF_SCRIPT_PRIV_KEYS_HEX array is empty");
+ std::process::exit(1);
+ });
+ let priv_keys_bytes = hex::decode(priv_keys_hex).unwrap();
+ if priv_keys_bytes.len() != 64 {
+ error!("SLH-DSA private key must be 64 bytes, got {}", priv_keys_bytes.len());
+ std::process::exit(1);
+ }
+ vec![priv_keys_bytes]
+ },
+ LeafScriptType::SchnorrOnly => {
+ let priv_keys_hex_array = env::var("LEAF_SCRIPT_PRIV_KEYS_HEX")
+ .unwrap_or_else(|_| {
+ error!("LEAF_SCRIPT_PRIV_KEYS_HEX environment variable is required for SCHNORR_ONLY");
+ std::process::exit(1);
+ });
+ // Parse JSON array and extract the first (and only) hex string
+ let priv_keys_hex: String = serde_json::from_str::>(&priv_keys_hex_array)
+ .unwrap_or_else(|_| {
+ error!("Failed to parse LEAF_SCRIPT_PRIV_KEYS_HEX as JSON array");
+ std::process::exit(1);
+ })
+ .into_iter()
+ .next()
+ .unwrap_or_else(|| {
+ error!("LEAF_SCRIPT_PRIV_KEYS_HEX array is empty");
+ std::process::exit(1);
+ });
+ let priv_keys_bytes = hex::decode(priv_keys_hex).unwrap();
+ if priv_keys_bytes.len() != 32 {
+ error!("Schnorr private key must be 32 bytes, got {}", priv_keys_bytes.len());
+ std::process::exit(1);
+ }
+ vec![priv_keys_bytes]
+ },
+ LeafScriptType::SchnorrAndSlhDsa => {
+ let priv_keys_hex_array = env::var("LEAF_SCRIPT_PRIV_KEYS_HEX")
+ .unwrap_or_else(|_| {
+ error!("LEAF_SCRIPT_PRIV_KEYS_HEX environment variable is required for SCHNORR_AND_SLH_DSA");
+ std::process::exit(1);
+ });
+ // Parse JSON array and extract the hex strings
+ let priv_keys_hex_vec: Vec = serde_json::from_str(&priv_keys_hex_array)
+ .unwrap_or_else(|_| {
+ error!("Failed to parse LEAF_SCRIPT_PRIV_KEYS_HEX as JSON array");
+ std::process::exit(1);
+ });
+
+ if priv_keys_hex_vec.len() != 2 {
+ error!("For SCHNORR_AND_SLH_DSA, LEAF_SCRIPT_PRIV_KEYS_HEX must contain exactly 2 hex strings, got {}", priv_keys_hex_vec.len());
+ std::process::exit(1);
+ }
+
+ let schnorr_priv_key_hex = &priv_keys_hex_vec[0];
+ let slh_dsa_priv_key_hex = &priv_keys_hex_vec[1];
+
+ let schnorr_priv_key_bytes = hex::decode(schnorr_priv_key_hex).unwrap();
+ let slh_dsa_priv_key_bytes = hex::decode(slh_dsa_priv_key_hex).unwrap();
+
+ if schnorr_priv_key_bytes.len() != 32 {
+ error!("Schnorr private key must be 32 bytes, got {}", schnorr_priv_key_bytes.len());
+ std::process::exit(1);
+ }
+ if slh_dsa_priv_key_bytes.len() != 64 {
+ error!("SLH-DSA private key must be 64 bytes, got {}", slh_dsa_priv_key_bytes.len());
+ std::process::exit(1);
+ }
+
+ vec![schnorr_priv_key_bytes, slh_dsa_priv_key_bytes]
+ },
+ LeafScriptType::NotApplicable => {
+ panic!("LeafScriptType::NotApplicable is not applicable");
+ }
+ };
+
+
+ // ie: OP_PUSHBYTES_32 6d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0 OP_CHECKSIG
+ let leaf_script_bytes: Vec = env::var("LEAF_SCRIPT_HEX")
+ .map(|s| hex::decode(s).unwrap())
+ .unwrap_or_else(|_| {
+ error!("LEAF_SCRIPT_HEX environment variable is required but not set");
+ std::process::exit(1);
+ });
+
+ // https://learnmeabitcoin.com/explorer/tx/797505b104b5fb840931c115ea35d445eb1f64c9279bf23aa5bb4c3d779da0c2#outputs
+ let spend_output_pubkey_hash_bytes: Vec = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
+
+ // OUTPUT_AMOUNT_SATS env var is optional. Default is FUNDING_UTXO_AMOUNT_SATS - 5000 sats
+ let spend_output_amount_sats: u64 = env::var("OUTPUT_AMOUNT_SATS")
+ .ok()
+ .and_then(|s| s.parse::().ok())
+ .unwrap_or(funding_utxo_amount_sats.saturating_sub(5000));
+
+
+ let result: SpendDetails = pay_to_p2wpkh_tx(
+ funding_tx_id_bytes,
+ funding_utxo_index,
+ funding_utxo_amount_sats,
+ funding_script_pubkey_bytes,
+ control_block_bytes,
+ leaf_script_bytes.clone(),
+ leaf_script_priv_keys_bytes, // Now passing Vec> instead of Vec
+ spend_output_pubkey_hash_bytes,
+ spend_output_amount_sats,
+ leaf_script_type
+ );
+
+ // 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();
+
+ match leaf_script_type {
+ LeafScriptType::SlhDsaOnly => {
+ let is_valid: bool = verify_slh_dsa_via_bytes(&result.sig_bytes, &result.sighash, &tapleaf_pubkey_bytes);
+ info!("is_valid: {}", is_valid);
+ },
+ LeafScriptType::SchnorrOnly => {
+ let is_valid: bool = verify_schnorr_signature_via_bytes(
+ &result.sig_bytes,
+ &result.sighash,
+ &tapleaf_pubkey_bytes);
+ info!("is_valid: {}", is_valid);
+ },
+ LeafScriptType::SchnorrAndSlhDsa => {
+ // For combined scripts, we need to separate the signatures
+ // The sig_bytes contains: [schnorr_sig (64 bytes), slh_dsa_sig (7856 bytes)] (raw signatures without sighash)
+ let schnorr_sig_len = 64; // Schnorr signature is 64 bytes
+ let slh_dsa_sig_len = 7856; // SLH-DSA signature is 7856 bytes
+
+ let expected_min_len = schnorr_sig_len + slh_dsa_sig_len;
+
+ if result.sig_bytes.len() < expected_min_len {
+ error!("Combined signature length is too short: expected at least {}, got {}",
+ expected_min_len, result.sig_bytes.len());
+ return result;
+ }
+
+ // Extract Schnorr signature (first 64 bytes)
+ let schnorr_sig = &result.sig_bytes[..schnorr_sig_len];
+ // Extract SLH-DSA signature (next 7856 bytes)
+ let slh_dsa_sig = &result.sig_bytes[schnorr_sig_len..schnorr_sig_len + slh_dsa_sig_len];
+
+ // For SCHNORR_AND_SLH_DSA scripts, we need to extract the individual public keys
+ // The script structure is: OP_PUSHBYTES_32 OP_CHECKSIG OP_PUSHBYTES_32 OP_SUBSTR OP_BOOLAND OP_VERIFY
+ // So we need to extract the Schnorr pubkey (first 32 bytes after OP_PUSHBYTES_32)
+ let schnorr_pubkey_bytes = &leaf_script_bytes[1..33]; // Skip OP_PUSHBYTES_32 (0x20), get next 32 bytes
+ let slh_dsa_pubkey_bytes = &leaf_script_bytes[35..67]; // Skip OP_CHECKSIG (0xac), OP_PUSHBYTES_32 (0x20), get next 32 bytes
+
+ // Verify Schnorr signature
+ let schnorr_is_valid: bool = verify_schnorr_signature_via_bytes(
+ schnorr_sig,
+ &result.sighash,
+ schnorr_pubkey_bytes);
+ info!("Schnorr signature is_valid: {}", schnorr_is_valid);
+
+ // Verify SLH-DSA signature
+ let slh_dsa_is_valid: bool = verify_slh_dsa_via_bytes(
+ slh_dsa_sig,
+ &result.sighash,
+ slh_dsa_pubkey_bytes);
+ info!("SLH-DSA signature is_valid: {}", slh_dsa_is_valid);
+
+ let both_valid = schnorr_is_valid && slh_dsa_is_valid;
+ info!("Both signatures valid: {}", both_valid);
+ }
+ LeafScriptType::NotApplicable => {
+ panic!("LeafScriptType::NotApplicable is not applicable");
+ }
+ }
+
+ 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 3277543a13..84b5aa3907 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:
@@ -17,25 +17,53 @@ 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 (secret_key, public_key) = keypair.as_schnorr().unwrap();
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, &secret_key.keypair(&SECP));
+ info!("Signature created successfully, size: {}", signature.serialize().len());
+
+ //let pubkey = public_key;
- let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr(&message, &keypair.0.keypair(&SECP));
+ /*
+ * 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, *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),
- &[0u8; 32] // 32 zero bytes of auxiliary random data
+ &secret_key.keypair(&SECP),
+ &aux_rand
);
+ info!("aux_rand signature created successfully, size: {}", signature_aux_rand.serialize().len());
- let schnorr_valid = verify_schnorr_signature(signature, message, pubkey);
- info!("schnorr_valid: {}", schnorr_valid);
-
- 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);
-}
\ No newline at end of file
+}
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..c6c965e7a0
--- /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, KeyPair,
+};
+
+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: 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..999d487d3d
--- /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, KeyPair,
+};
+
+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: 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/data_structures.rs b/bip-0360/ref-impl/rust/src/data_structures.rs
index 64cf0f1685..d5e6ad06ee 100644
--- a/bip-0360/ref-impl/rust/src/data_structures.rs
+++ b/bip-0360/ref-impl/rust/src/data_structures.rs
@@ -2,6 +2,45 @@ 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};
+
+/// Enum representing the type of leaf script to create
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum LeafScriptType {
+ /// Script requires only SLH-DSA signature
+ SlhDsaOnly,
+ /// Script requires only Schnorr signature
+ SchnorrOnly,
+ /// Script requires both Schnorr and SLH-DSA signatures (in that order)
+ SchnorrAndSlhDsa,
+ /// Script type is not applicable
+ NotApplicable,
+}
+
+impl LeafScriptType {
+ /// Check if this script type uses SLH-DSA
+ pub fn uses_slh_dsa(&self) -> bool {
+ matches!(self, LeafScriptType::SlhDsaOnly | LeafScriptType::SchnorrAndSlhDsa)
+ }
+
+ /// Check if this script type uses Schnorr
+ pub fn uses_schnorr(&self) -> bool {
+ matches!(self, LeafScriptType::SchnorrOnly | LeafScriptType::SchnorrAndSlhDsa)
+ }
+
+ /// Check if this script type requires both signature types
+ pub fn requires_both(&self) -> bool {
+ matches!(self, LeafScriptType::SchnorrAndSlhDsa)
+ }
+
+ /// Check if this script type is not applicable
+ pub fn is_not_applicable(&self) -> bool {
+ matches!(self, LeafScriptType::NotApplicable)
+ }
+}
+
#[derive(Debug, Serialize)]
pub struct TestVectors {
pub version: u32,
@@ -67,8 +106,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
}
@@ -285,7 +324,7 @@ impl std::process::Termination for UtxoReturn {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaptreeReturn {
- pub leaf_script_priv_key_hex: String,
+ pub leaf_script_priv_keys_hex: Vec, // Changed to support multiple private keys
pub leaf_script_hex: String,
pub tree_root_hex: String,
pub control_block_hex: String,
@@ -318,3 +357,149 @@ 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),
+}
+
+/// A container for multiple keypairs that can be used in a single leaf script
+#[derive(Debug, Clone)]
+pub struct MultiKeypair {
+ pub schnorr_keypair: Option,
+ pub slh_dsa_keypair: Option,
+}
+
+impl MultiKeypair {
+ /// Create a new MultiKeypair with only a Schnorr keypair
+ pub fn new_schnorr_only(schnorr_keypair: UnifiedKeypair) -> Self {
+ Self {
+ schnorr_keypair: Some(schnorr_keypair),
+ slh_dsa_keypair: None,
+ }
+ }
+
+ /// Create a new MultiKeypair with only an SLH-DSA keypair
+ pub fn new_slh_dsa_only(slh_dsa_keypair: UnifiedKeypair) -> Self {
+ Self {
+ schnorr_keypair: None,
+ slh_dsa_keypair: Some(slh_dsa_keypair),
+ }
+ }
+
+ /// Create a new MultiKeypair with both keypairs
+ pub fn new_combined(schnorr_keypair: UnifiedKeypair, slh_dsa_keypair: UnifiedKeypair) -> Self {
+ Self {
+ schnorr_keypair: Some(schnorr_keypair),
+ slh_dsa_keypair: Some(slh_dsa_keypair),
+ }
+ }
+
+ /// Get all secret key bytes for serialization (in order: schnorr, then slh_dsa if present)
+ pub fn secret_key_bytes(&self) -> Vec> {
+ let mut result = Vec::new();
+ if let Some(ref schnorr) = self.schnorr_keypair {
+ result.push(schnorr.secret_key_bytes());
+ }
+ if let Some(ref slh_dsa) = self.slh_dsa_keypair {
+ result.push(slh_dsa.secret_key_bytes());
+ }
+ result
+ }
+
+ /// Get all public key bytes for script construction (in order: schnorr, then slh_dsa if present)
+ pub fn public_key_bytes(&self) -> Vec> {
+ let mut result = Vec::new();
+ if let Some(ref schnorr) = self.schnorr_keypair {
+ result.push(schnorr.public_key_bytes());
+ }
+ if let Some(ref slh_dsa) = self.slh_dsa_keypair {
+ result.push(slh_dsa.public_key_bytes());
+ }
+ result
+ }
+
+ /// Check if this contains a Schnorr keypair
+ pub fn has_schnorr(&self) -> bool {
+ self.schnorr_keypair.is_some()
+ }
+
+ /// Check if this contains an SLH-DSA keypair
+ pub fn has_slh_dsa(&self) -> bool {
+ self.slh_dsa_keypair.is_some()
+ }
+
+ /// Get the Schnorr keypair if present
+ pub fn schnorr_keypair(&self) -> Option<&UnifiedKeypair> {
+ self.schnorr_keypair.as_ref()
+ }
+
+ /// Get the SLH-DSA keypair if present
+ pub fn slh_dsa_keypair(&self) -> Option<&UnifiedKeypair> {
+ self.slh_dsa_keypair.as_ref()
+ }
+}
+
+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/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..3a7b82c0d2 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};
@@ -17,9 +18,13 @@ 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};
+use bitcoinpqc::{
+ generate_keypair, public_key_size, secret_key_size, Algorithm, KeyPair, sign, verify,
+};
+
+use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn, UnifiedKeypair, MultiKeypair, LeafScriptType};
/* 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(leaf_script_type: LeafScriptType) -> (Vec<(u32, ScriptBuf)>, MultiKeypair, ScriptBuf) {
let mut total_leaf_count: u32 = 1;
if let Ok(env_value) = env::var("TOTAL_LEAF_COUNT") {
@@ -54,90 +59,150 @@ 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 keypairs_of_interest: Option = None;
let mut script_buf_of_interest: Option = None;
for leaf_index in 0..total_leaf_count {
+ let keypairs: MultiKeypair;
+ let script_buf: ScriptBuf;
- let keypair: (SecretKey, XOnlyPublicKey) = acquire_schnorr_keypair();
- let pubkey_bytes = keypair.1.serialize();
-
- // 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);
+ match leaf_script_type {
+ LeafScriptType::SchnorrOnly => {
+ let schnorr_keypair = acquire_schnorr_keypair();
+ keypairs = MultiKeypair::new_schnorr_only(schnorr_keypair);
+ let pubkey_bytes = keypairs.schnorr_keypair().unwrap().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); // OP_CHECKSIG
+ script_buf = ScriptBuf::from_bytes(script_buf_bytes);
+ },
+ LeafScriptType::SlhDsaOnly => {
+ let slh_dsa_keypair = acquire_slh_dsa_keypair();
+ keypairs = MultiKeypair::new_slh_dsa_only(slh_dsa_keypair);
+ let pubkey_bytes = keypairs.slh_dsa_keypair().unwrap().public_key_bytes();
+ // OP_PUSHBYTES_32 <32-byte pubkey> OP_SUBSTR
+ let mut script_buf_bytes = vec![0x20];
+ script_buf_bytes.extend_from_slice(&pubkey_bytes);
+ script_buf_bytes.push(0x7f); // OP_SUBSTR
+ script_buf = ScriptBuf::from_bytes(script_buf_bytes);
+ },
+ LeafScriptType::SchnorrAndSlhDsa => {
+ // For combined scripts, we need both keypairs
+ let schnorr_keypair = acquire_schnorr_keypair();
+ let slh_dsa_keypair = acquire_slh_dsa_keypair();
+ keypairs = MultiKeypair::new_combined(schnorr_keypair, slh_dsa_keypair);
+
+ let schnorr_pubkey = keypairs.schnorr_keypair().unwrap().public_key_bytes();
+ let slh_dsa_pubkey = keypairs.slh_dsa_keypair().unwrap().public_key_bytes();
+
+ // Debug: Print the private key used for script construction
+ info!("SLH-DSA DEBUG: Script construction using private key: {}", hex::encode(keypairs.slh_dsa_keypair().unwrap().secret_key_bytes()));
+ info!("SLH-DSA DEBUG: Script construction using public key: {}", hex::encode(&slh_dsa_pubkey));
+
+ // Combined script: OP_CHECKSIG OP_SUBSTR OP_BOOLAND OP_VERIFY
+ let mut script_buf_bytes = vec![0x20]; // OP_PUSHBYTES_32
+ script_buf_bytes.extend_from_slice(&schnorr_pubkey);
+ script_buf_bytes.push(0xac); // OP_CHECKSIG
+ script_buf_bytes.push(0x20); // OP_PUSHBYTES_32
+ script_buf_bytes.extend_from_slice(&slh_dsa_pubkey);
+ script_buf_bytes.push(0x7f); // OP_SUBSTR
+ script_buf_bytes.push(0x9a); // OP_BOOLAND
+ script_buf_bytes.push(0x69); // OP_VERIFY
+ script_buf = ScriptBuf::from_bytes(script_buf_bytes);
+ }
+ LeafScriptType::NotApplicable => {
+ panic!("LeafScriptType::NotApplicable is not applicable");
+ }
}
- };
- return (huffman_entries, keypair_of_interest, script_buf_of_interest);
+
+ 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 {
+ keypairs_of_interest = Some(keypairs);
+ script_buf_of_interest = Some(script_buf.clone());
+ debug!("Selected leaf: weight: {}, script: {:?}", random_weight, script_buf);
+ }
+ }
+ return (huffman_entries, keypairs_of_interest.unwrap(), script_buf_of_interest.unwrap());
}
-pub fn create_p2qrh_multi_leaf_taptree() -> TaptreeReturn {
+/// Parses the LEAF_SCRIPT_TYPE environment variable and returns the corresponding LeafScriptType.
+/// Defaults to LeafScriptType::SchnorrOnly if the environment variable is not set or has an invalid value.
+pub fn parse_leaf_script_type() -> LeafScriptType {
+ match env::var("LEAF_SCRIPT_TYPE")
+ .unwrap_or_else(|_| "SCHNORR_ONLY".to_string())
+ .as_str() {
+ "SLH_DSA_ONLY" => LeafScriptType::SlhDsaOnly,
+ "SCHNORR_ONLY" => LeafScriptType::SchnorrOnly,
+ "SCHNORR_AND_SLH_DSA" => LeafScriptType::SchnorrAndSlhDsa,
+ _ => {
+ error!("Invalid LEAF_SCRIPT_TYPE. Must be one of: SLH_DSA_ONLY, SCHNORR_ONLY, SCHNORR_AND_SLH_DSA");
+ LeafScriptType::SchnorrOnly
+ }
+ }
+}
+
+pub fn create_p2tsh_multi_leaf_taptree() -> TaptreeReturn {
+ let leaf_script_type = parse_leaf_script_type();
- 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 p2qrh_spend_info: P2qrhSpendInfo = p2qrh_builder.clone().finalize().unwrap();
- let quantum_root: QuantumRootHash = p2qrh_spend_info.quantum_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 (huffman_entries, keypairs_of_interest, script_buf_of_interest) = create_huffman_tree(leaf_script_type);
+ let p2tsh_builder: P2tshBuilder = P2tshBuilder::with_huffman_tree(huffman_entries).unwrap();
+
+
+ let p2tsh_spend_info: P2tshSpendInfo = p2tsh_builder.clone().finalize().unwrap();
+ let merkle_root:TapNodeHash = p2tsh_spend_info.merkle_root.unwrap();
+
- 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())
+ .find(|leaf| leaf.script() == script_buf_of_interest.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();
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();
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 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());
return TaptreeReturn {
- leaf_script_priv_key_hex: hex::encode(keypair_of_interest.unwrap().0.secret_bytes()),
+ leaf_script_priv_keys_hex: keypairs_of_interest.secret_key_bytes()
+ .into_iter()
+ .map(|bytes| hex::encode(bytes))
+ .collect(),
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,
};
}
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, keypairs_of_interest, script_buf_of_interest) = create_huffman_tree(LeafScriptType::SchnorrOnly);
let pub_key_string = format!("02{}", p2tr_internal_pubkey_hex);
let internal_pubkey: PublicKey = pub_key_string.parse::().unwrap();
@@ -154,15 +219,15 @@ pub fn create_p2tr_multi_leaf_taptree(p2tr_internal_pubkey_hex: String) -> Taptr
let output_key_parity: Parity = p2tr_spend_info.output_key_parity();
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
+ info!("keypairs_of_interest: \n\tsecret_bytes: {:?} \n\tpubkeys: {:?} \n\tmerkle_root: {}",
+ keypairs_of_interest.secret_key_bytes().iter().map(|bytes| hex::encode(bytes)).collect::>(), // secret_bytes returns big endian
+ keypairs_of_interest.public_key_bytes().iter().map(|bytes| hex::encode(bytes)).collect::>(), // 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,26 +246,20 @@ 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_keys_hex: keypairs_of_interest.secret_key_bytes()
+ .into_iter()
+ .map(|bytes| hex::encode(bytes))
+ .collect(),
leaf_script_hex: leaf_script,
tree_root_hex: hex::encode(merkle_root.to_byte_array()),
control_block_hex: control_block_hex,
};
}
-pub fn create_p2qrh_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)
- */
- let script_buf: P2qrhScriptBuf = P2qrhScriptBuf::new_p2qrh(quantum_root);
- let script: &Script = script_buf.as_script();
- let script_pubkey = script.to_hex_string();
-
- let mut bitcoin_network: Network = Network::Bitcoin;
+/// Parses the BITCOIN_NETWORK environment variable and returns the corresponding Network.
+/// Defaults to Network::Regtest if the environment variable is not set or has an invalid value.
+pub fn get_bitcoin_network() -> Network {
+ let mut bitcoin_network: Network = Network::Regtest;
// Check for BITCOIN_NETWORK environment variable and override if set
if let Ok(network_str) = std::env::var("BITCOIN_NETWORK") {
@@ -209,15 +268,32 @@ pub fn create_p2qrh_utxo(quantum_root_hex: String) -> UtxoReturn {
"testnet" => Network::Testnet,
"signet" => Network::Signet,
_ => {
- debug!("Invalid BITCOIN_NETWORK value '{}', using default Bitcoin network", network_str);
- Network::Bitcoin
+ debug!("Invalid BITCOIN_NETWORK value '{}', using default Regtest network", network_str);
+ Network::Regtest
}
};
}
- // 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);
+ bitcoin_network
+}
+
+pub fn create_p2tsh_utxo(merkle_root_hex: String) -> UtxoReturn {
+
+ 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(merkle_root);
+ let script: &Script = script_buf.as_script();
+ let script_pubkey = script.to_hex_string();
+
+ let bitcoin_network = get_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,
@@ -227,7 +303,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,
@@ -235,9 +311,10 @@ pub fn pay_to_p2wpkh_tx(
funding_script_pubkey_bytes: Vec,
control_block_bytes: Vec,
leaf_script_bytes: Vec,
- leaf_script_priv_key_bytes: Vec,
+ leaf_script_priv_keys_bytes: Vec>, // Changed to support multiple private keys
spend_output_pubkey_hash_bytes: Vec,
spend_output_amount_sats: u64,
+ leaf_script_type: LeafScriptType
) -> SpendDetails {
let mut txid_little_endian = funding_tx_id_bytes.clone(); // initially in big endian format
@@ -301,21 +378,85 @@ 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();
-
- // 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();
- derived_witness.push(&sig_bytes);
+ let mut sig_bytes = Vec::new();
+ match leaf_script_type {
+ LeafScriptType::SlhDsaOnly => {
+ if leaf_script_priv_keys_bytes.len() != 1 {
+ panic!("SlhDsaOnly requires exactly one private key");
+ }
+ let secret_key: bitcoinpqc::SecretKey = bitcoinpqc::SecretKey::try_from_slice(
+ Algorithm::SLH_DSA_128S, &leaf_script_priv_keys_bytes[0]).unwrap();
+ let signature = sign(&secret_key, spend_msg.as_ref()).expect("Failed to sign with SLH-DSA-128S");
+ debug!("SlhDsaOnly signature.bytes: {:?}", signature.bytes.len());
+ let mut sig_bytes_with_sighash = signature.bytes.clone();
+ sig_bytes_with_sighash.push(TapSighashType::All as u8);
+ derived_witness.push(&sig_bytes_with_sighash);
+ sig_bytes = signature.bytes;
+ },
+ LeafScriptType::SchnorrOnly => {
+ if leaf_script_priv_keys_bytes.len() != 1 {
+ panic!("SchnorrOnly requires exactly one private key");
+ }
+ // assumes bytes are in big endian format
+ let secret_key = SecretKey::from_slice(&leaf_script_priv_keys_bytes[0]).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();
+ let mut sig_bytes_with_sighash = sig_bytes.clone();
+ sig_bytes_with_sighash.push(TapSighashType::All as u8);
+ derived_witness.push(&sig_bytes_with_sighash);
+ debug!("SchnorrOnly signature bytes: {:?}", sig_bytes.len());
+ },
+ LeafScriptType::SchnorrAndSlhDsa => {
+ if leaf_script_priv_keys_bytes.len() != 2 {
+ panic!("SchnorrAndSlhDsa requires exactly two private keys (Schnorr first, then SLH-DSA)");
+ }
+
+ // Generate Schnorr signature (first key)
+ let schnorr_secret_key = SecretKey::from_slice(&leaf_script_priv_keys_bytes[0]).unwrap();
+ let schnorr_signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand(
+ &spend_msg,
+ &schnorr_secret_key.keypair(&SECP),
+ &[0u8; 32] // 32 zero bytes of auxiliary random data
+ );
+ // Build combined signature for return value (without sighash bytes)
+ let mut combined_sig_bytes = schnorr_signature.serialize().to_vec();
+ debug!("SchnorrAndSlhDsa schnorr_sig_bytes: {:?}", combined_sig_bytes.len());
+
+ // Generate SLH-DSA signature (second key)
+ let slh_dsa_secret_key: bitcoinpqc::SecretKey = bitcoinpqc::SecretKey::try_from_slice(
+ Algorithm::SLH_DSA_128S, &leaf_script_priv_keys_bytes[1]).unwrap();
+
+ // Debug: Print the private key being used for signature creation
+ info!("SLH-DSA DEBUG: Using private key for signature creation: {}", hex::encode(&leaf_script_priv_keys_bytes[1]));
+
+ let slh_dsa_signature = sign(&slh_dsa_secret_key, spend_msg.as_ref()).expect("Failed to sign with SLH-DSA-128S");
+ debug!("SchnorrAndSlhDsa slh_dsa_signature.bytes: {:?}", slh_dsa_signature.bytes.len());
+
+ // Add SLH-DSA signature to combined signature for return value
+ combined_sig_bytes.extend_from_slice(&slh_dsa_signature.bytes);
+ sig_bytes = combined_sig_bytes;
+
+ // Build witness with sighash bytes
+ let mut witness_sig_bytes = schnorr_signature.serialize().to_vec();
+ witness_sig_bytes.push(TapSighashType::All as u8);
+ witness_sig_bytes.extend_from_slice(&slh_dsa_signature.bytes);
+ witness_sig_bytes.push(TapSighashType::All as u8);
+ derived_witness.push(&witness_sig_bytes);
+ }
+ LeafScriptType::NotApplicable => {
+ panic!("LeafScriptType::NotApplicable is not applicable");
+ }
+ }
+ // Note: sighash byte is now appended to signatures, not as separate witness element
derived_witness.push(&leaf_script_bytes);
derived_witness.push(&control_block_bytes);
@@ -332,7 +473,7 @@ pub fn pay_to_p2wpkh_tx(
return SpendDetails {
tx_hex,
sighash: tapscript_sighash.as_byte_array().to_vec(),
- sig_bytes,
+ sig_bytes: sig_bytes,
derived_witness_vec: derived_witness_vec,
};
}
@@ -352,23 +493,10 @@ pub fn create_p2tr_utxo(merkle_root_hex: String, internal_pubkey_hex: String) ->
let script: &Script = script_buf.as_script();
let script_pubkey = script.to_hex_string();
- let mut bitcoin_network: Network = Network::Bitcoin;
-
- // Check for BITCOIN_NETWORK environment variable and override if set
- if let Ok(network_str) = std::env::var("BITCOIN_NETWORK") {
- bitcoin_network = match network_str.to_lowercase().as_str() {
- "regtest" => Network::Regtest,
- "testnet" => Network::Testnet,
- "signet" => Network::Signet,
- _ => {
- debug!("Invalid BITCOIN_NETWORK value '{}', using default Bitcoin network", network_str);
- Network::Bitcoin
- }
- };
- }
+ let bitcoin_network = get_bitcoin_network();
// 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,
@@ -431,12 +559,24 @@ 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
+ * 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)
+ UnifiedKeypair::new_schnorr(privkey, pubkey.0)
}
pub fn verify_schnorr_signature_via_bytes(signature: &[u8], message: &[u8], pubkey_bytes: &[u8]) -> bool {
@@ -452,6 +592,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
@@ -486,3 +644,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/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/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(())
+}
diff --git a/bip-0360/ref-impl/rust/tests/p2qrh_spend.rs b/bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
similarity index 86%
rename from bip-0360/ref-impl/rust/tests/p2qrh_spend.rs
rename to bip-0360/ref-impl/rust/tests/p2tsh_spend.rs
index 31c02e4b35..fd6b2bd42d 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, LeafScriptType};
/* 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();
@@ -65,6 +65,9 @@ fn test_script_path_spend_signatures() {
hex::decode("5120f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80")
.unwrap();
let input_script_priv_key_bytes: Vec = hex::decode("9b8de5d7f20a8ebb026a82babac3aa47a008debbfde5348962b2c46520bd5189").unwrap();
+
+ // Convert to Vec> format expected by the function
+ let input_script_priv_keys_bytes: Vec> = vec![input_script_priv_key_bytes];
// https://learnmeabitcoin.com/explorer/tx/797505b104b5fb840931c115ea35d445eb1f64c9279bf23aa5bb4c3d779da0c2#outputs
@@ -76,7 +79,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,
@@ -85,9 +88,10 @@ fn test_script_path_spend_signatures() {
input_script_pubkey_bytes,
input_control_block_bytes,
input_leaf_script_bytes,
- input_script_priv_key_bytes,
+ input_script_priv_keys_bytes, // Now passing Vec> format
spend_output_pubkey_bytes,
- spend_output_amount_sats
+ spend_output_amount_sats,
+ LeafScriptType::SchnorrOnly // This test uses a Schnorr signature
);
assert_eq!(result.sighash.as_slice(), test_sighash_bytes.as_slice(), "sighash mismatch");