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");