From 61709055843086ce25c77c60f466c089447e4cf8 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Sun, 24 Dec 2023 13:05:40 +0100 Subject: [PATCH 1/7] txscript: add TxFieldSelector This commit adds the TxFieldSelector type which is used to generate TxHash values for the OP_TXHASH and OP_CHECKTXHASHVERIFY opcodes. --- txscript/data/txhash_vectors.json | 1591 +++++++++++++++++++++++++++++ txscript/standard.go | 12 + txscript/txhash.go | 913 +++++++++++++++++ txscript/txhash_test.go | 136 +++ 4 files changed, 2652 insertions(+) create mode 100644 txscript/data/txhash_vectors.json create mode 100644 txscript/txhash.go create mode 100644 txscript/txhash_test.go diff --git a/txscript/data/txhash_vectors.json b/txscript/data/txhash_vectors.json new file mode 100644 index 0000000000..2bfd1a8002 --- /dev/null +++ b/txscript/data/txhash_vectors.json @@ -0,0 +1,1591 @@ + [ + { + "prevs": [ + "68010000000000000160", + "69010000000000002200200100000000000000000000000000000000000000000000000000000000000000", + "69010000000000002251200100000000000000000000000000000000000000000000000000000000000000", + "6a010000000000002251200200000000000000000000000000000000000000000000000000000000000000" + ], + "tx": "02000000000104111111111111111111111111111111111111111111111111111111111111111101000000012301000000222222222222222222222222222222222222222222222222222222222222222202000000000300000033333333333333333333333333333333333333333333333333333333333333330300000000020000004444444444444444444444444444444444444444444444444444444444444444040000000003000000035e0100000000000001565f0100000000000001576101000000000000015800030113011402504201011203011301140250422a000000", + "vectors": [ + { + "codeseparator": null, + "input": 0, + "txfs": "01", + "txhash": "26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6ece" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "01", + "txhash": "26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6ece" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "01", + "txhash": "26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6ece" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "01", + "txhash": "26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6ece" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "02", + "txhash": "e8a4b2ee7ede79a3afb332b5b6cc3d952a65fd8cffb897f5d18016577c33d7cc" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "02", + "txhash": "e8a4b2ee7ede79a3afb332b5b6cc3d952a65fd8cffb897f5d18016577c33d7cc" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "02", + "txhash": "e8a4b2ee7ede79a3afb332b5b6cc3d952a65fd8cffb897f5d18016577c33d7cc" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "02", + "txhash": "e8a4b2ee7ede79a3afb332b5b6cc3d952a65fd8cffb897f5d18016577c33d7cc" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "04", + "txhash": "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "04", + "txhash": "67abdd721024f0ff4e0b3f4c2fc13bc5bad42d0b7851d456d88d203d15aaa450" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "04", + "txhash": "26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6ece" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "04", + "txhash": "9d9f290527a6be626a8f5985b26e19b237b44872b03631811df4416fc1713178" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "08", + "txhash": "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "08", + "txhash": "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "08", + "txhash": "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "08", + "txhash": "d703d3da6a87bd8e0b453f3b6c41edcc9bf331b2b88ef26eb39dc7abee4e00a3" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "10", + "txhash": "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "10", + "txhash": "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "10", + "txhash": "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "10", + "txhash": "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "9f", + "txhash": "4f248b4664d9b7eb78506ee3ae3d40f45b65b2c34f5472bb5e9c367770e7f1ee" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "9f", + "txhash": "634565d0889156efa4c3a932c6b832aaeac34af7e77245b1c1db8ed260ad4e1d" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "9f", + "txhash": "55d54935fbc0dbc572da8ad1eb47c9e3fc28449a070d59a4a2c180f6dc33b285" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "9f", + "txhash": "51f86e7bb438301882176aa6dff4ba755fed8cf4d2948bf54bcd143bce135376" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df4040", + "txhash": "295da1be3ab5d30bbdbc803ae25066922d0ad964dc08654fc14966d4636271c3" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df4040", + "txhash": "76c6722f456306260a5c91037dfec2d5bd586ab1f21039216f74455150b221d4" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df4040", + "txhash": "78cc0e2841fc0c261d98a577ec46af84c0a19ff2d39a9a9e31dec0a5bfab0f09" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df8040", + "txhash": "563f4ebd7bb6f7f4313eaadab32a1e8b9f949227d0cde6b15712b0f26f25dd03" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df8040", + "txhash": "1b0b39caf5579978f8ad3542e58a5a5a02e328be0ab357153a7673108a7edf21" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df8040", + "txhash": "a145587dce2a85b50bdd085568062612ca3b1ebd4a4ad61f0c765799fd804195" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "dfc040", + "txhash": "7a4f203d00749f677f7cb83043ac4eeb803099063cd01127aeb984bdc4632d7d" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "dfc040", + "txhash": "200caf79e16000d8d83cb0d954904ae0780bcae952ac7fa283517e6b8eae69f2" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "dfc040", + "txhash": "2cc49b1f27c090bbc7bf1e4e4916b3225a75fdae44be3dadb37b0ee32b1f4499" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df403f", + "txhash": "5a141494a4ac6a3206fbf0290c094b1aff30638f762f8d15ebea9a1eb98ee309" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df403f", + "txhash": "0aaee2fb5fcb20b261346efadbb2165823e24d1e4613ee14396815381172db64" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df403f", + "txhash": "c51cf8d872616d08f467eade57662b3944b872929c454e71936d315b76683cc6" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "df403f", + "txhash": "36a1135d6bd5585ce45c07813aca090f26acd04501789c0a3c59d5de613f5082" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df803f", + "txhash": "4c4364e3e8642aede7454bc332ce5c9973ef6ad6a9ec8817f706f73c9fbfdc24" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df803f", + "txhash": "d619772a10b0fd8f390744a47aba9761deb00798235499253c67e54ce0408b0b" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df803f", + "txhash": "4f2f21a2e853e09daff7540d095f214360ba1172a31ed137ad248229ef9f2df8" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "df803f", + "txhash": "03eb47fa98296ec7ac24c0e91c7fc8f79080af737c7b0c9508aae2a58cb9968e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "dfc03f", + "txhash": "49ec992a00d9ddfba9bfe933cf0325a1aa0f97e58382924c6291aca0cb0cc36b" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "dfc03f", + "txhash": "31cb19d1a5d718ad03459183a66e5ea630603ee9efea2219b11e95861561dbb6" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "dfc03f", + "txhash": "33862645c5a38f881c35a1b86b56532600be24e3fa6568ec8b97f756192f4a56" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "dfc03f", + "txhash": "8aaa0dd9ed60315a5b55d20e4ca7aa180c5c5f7becaddf93fc43211aa69cdf9e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df4080", + "txhash": "df534a79f3487d041c71ac83e98bd4a8d62b46311ad8521aeab5dce996212697" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df4080", + "txhash": "160c9bb49915d462f30cd8d73f1ad91790f426781a671add821838a6e29df7df" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df4080", + "txhash": "5883c4220597e5651c139913c4f4f172ddf03ecb3a4324323e325bb4df7a830d" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "df4080", + "txhash": "0623a9a97b6265f00e498e0e4f9173f6436221f5b7321877f3bf89f7deb1c65a" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df8080", + "txhash": "b3c900a1547c86bbec94d19ed18b6324f79c6945d200fa333ade3ad934efd606" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df8080", + "txhash": "7f9ec8329c6ed349f7f39ce1a3527aaa9cb0a1cfe95201a267db74416dbca620" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df8080", + "txhash": "abe5b10c15a23ddb09d86fe13108564f08c7cdaf4044eea17697b6c50889af79" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "df8080", + "txhash": "55102c3467fc5b299def1e1103ae4a59a784200904e755a284335a92d31513df" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "dfc080", + "txhash": "086c9be876b432efcc4a82d8b5aa1eea25cdde324552a0c6acce090b45a7f2eb" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "dfc080", + "txhash": "9e1c87642dfe7451423094f60009c689a88efd9c9a2453f8355e0df9d3e2c821" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "dfc080", + "txhash": "d14524a4b2de734214a14eed77505dea60ddf90e92d2dee2b4c561f83de70d6c" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "dfc080", + "txhash": "ddc9da80b68d18e7f810668a9e185a4066c0a96c016c510947aabaf2348b5305" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df40c0", + "txhash": "7cabeeea3d2abdfec93ee33c1e7197b8255b342bdfeff200b01f1fa4fdadbe91" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df40c0", + "txhash": "83c9657bb3ccf6b5bfd1ee3a38c2fafdeb1d843526cad53aef57a9b6cd13965d" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df40c0", + "txhash": "1343074535405fd18da5e7a78a9ee76dcb0a20c53eb4894c8f577499a2b4ead5" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df80c0", + "txhash": "3a983b896e2a5a0686fe65cb481d2cb367c80cdb1ab3e3be8baec53237eb88eb" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df80c0", + "txhash": "f7a552eb23e2a4f36fe4b72371e1e2808637f6bc4ea66af789646f404f61ba24" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df80c0", + "txhash": "c118fc8b23e2414bfaab1503515529dd5f916c5202dc662e02dcd0f0b6fdaf8a" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "dfc0c0", + "txhash": "2bd80d3ac416e2bcc2db3aacdf42e8bfa37f0e66d4151e73ce1030560b457eb7" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "dfc0c0", + "txhash": "23f3048aaa59b549b4fe9160f0a0ba3d015cb453256b09b588715913dce24d19" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "dfc0c0", + "txhash": "dd964c815a875dc3abef1c32f790597d23e82cf1c583ab2a1d48f0c0143aab78" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df40bf", + "txhash": "da59a82b3d3485c4830add922bf465a942c3c9a16c6d3ddde84e79e0975d15ed" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df40bf", + "txhash": "b92124b11d31d9d8e2fe0e0f82ef582b1cd7a59c16ae61a641b8a2ee947314d5" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df40bf", + "txhash": "79b25aea531bd1138fa6afa1d1f6d4eace1924be8a7a9222503c7613ce5ba0e9" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "df40bf", + "txhash": "22eeb3eb26b54f20aa20b94eb9c5eaf47e3989b2681327f81fee9f6a12479dfc" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "df80bf", + "txhash": "acde5987109fbd603ba4fe7f3e539493409daa5c8a80f5b14e071f2729afcdcc" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "df80bf", + "txhash": "95aa52f89dd3672e2db811a5da70b23d0620e2d7d78ac55746438db53420628e" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "df80bf", + "txhash": "34704e9968c0670f47e97b1fa77e6e441d2cbd97f23aae216654820a97a3119f" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "df80bf", + "txhash": "904338c16b423808e8d3bf491c9ab46308cff6ec09025b20ee288681206133ff" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "dfc0bf", + "txhash": "d46d78ffaaa4264075c102a2792f35cbd7cb0d2bba46f84a74d3bcc0e2fd4a07" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "dfc0bf", + "txhash": "209191f354e31d55cef29adee64c5c70502a6a13d6bf3e93e2cce3cd13d86bde" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "dfc0bf", + "txhash": "a50ecaa5a999547054a44367cdcceb537c75a4b16a2bf43da3f08ef1909725ff" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "dfc0bf", + "txhash": "0c3e7985a168c152f450d39279f9d0a8d8ec0142af05018b8b88e04d3516e73e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0140", + "txhash": "91d38e0ca0e5095249ddd5c6ea15c98eb09ad84935ee15b078ce16e80062c87b" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0140", + "txhash": "99e5d133679c749fe3f7a990205aa8b86808bde5d98a563e049eb887def6961d" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0140", + "txhash": "fe8a42c47ff81de1664346ba78ac501a7aaf57d56ab5d7edac26220e07420efc" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0140", + "txhash": "e5ec50028b9a3953769c22d5042baf2c0264710467b802d85c8a0967aa80d690" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0240", + "txhash": "ffb8f7ca010f0a1bed083290a7428115ab9a526a6986d169045f987826c3e5a2" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0240", + "txhash": "56092aa1f0a8b76bd1aa16e8db211d9627f007b9f58a605c16cbd39d3b7d2542" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0240", + "txhash": "060bce50807d4bb8662bc9535372d33f649bda3512f4c351b60304e1e3d82ad7" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0240", + "txhash": "4223ea00d094655082bf73cd3fcab91de3b8f8939c56228deee6be429c6c54a5" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0440", + "txhash": "9ee281b8f2085859d08cf70696fe023761ac32191767f09ce34179e0a5e21366" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0440", + "txhash": "c0dc425b65f85631add220410540c860733ec1d0d0e8099e8fdcf8d3b437fd39" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0440", + "txhash": "d6d195953a0bd49ccca47f604f04135330420528bf9a542fab16fa2e7b582568" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0440", + "txhash": "a34f814e1c1ac0a9f429cd48675c26d89bc23d3e358a3343734423445a76a890" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0840", + "txhash": "112c0da280dd4c137c7aee304d3171b0a28b9fa218cc869570b2916a579a15a3" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0840", + "txhash": "54244057189beb0ee25325b4f2d2814a471b04de0a5a0906c5b0b56d97444f43" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0840", + "txhash": "0c136aab4e6a317299df526a51714951741b32ff8d4caf5cb3d16984b5c9d7e0" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0840", + "txhash": "4e520f2941db8d101b9d96a7899796954df87f0ec09944a77c1b4bfb332809c2" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf1040", + "txhash": "dbd2f02312c7442ab580a8bb45711688f5c1142555fd8c94dac76f72925b1aff" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf1040", + "txhash": "5073580ef20d94a230bfdfe3f4d850a0d4a405957e46262ec2c02d3c53ce6d00" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf1040", + "txhash": "e2aae3653e626a6f56e5901d44de6c8a2f65fcdaaa21552ad4a9b3263cfbf393" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf1040", + "txhash": "cfdc04bd6c444eb9bb0e2db32db7bb66542c7ae5f99d831ed899d50a7f3ccb11" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf2040", + "txhash": "5144500234219dac2bfd9f1bb8a92886e5f8b143e009341936a12a6fb26c750a" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf2040", + "txhash": "1adbc6c62633fd18c1d0570d3c8a8f9bb0ea073aa8a79995a9cfed622a4608a4" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf2040", + "txhash": "7c2c214489c1496236c71cf5a47c77979b81604dd0b1a6aa4eabcb867dfd3bae" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf2040", + "txhash": "9095453a420b8f59a62368eee24769741241c15aa99e74cbf8ce029b28a5b8bc" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf3f40", + "txhash": "4b1e78f5da41122f5155ba68146cf5100baa90a3e92e44009eb18ec8c2c11e55" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf3f40", + "txhash": "58db27da765fca79e76f2d5b45b8a215e8e2dfd57f3e624d5ba700150ff72a10" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf3f40", + "txhash": "32f66cc966a04e69dd72b9ffb1dbf58edc7b1d51fde4084cee0dc8000374fda5" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf3f40", + "txhash": "1dd603250c852348e98057583c81fb31bcdf4918a7023256b047f5acd46d81ee" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf013f", + "txhash": "c133c0a4d74c5736ef471afc33d8cc7aa91ecfea07509458f127e6fde2400676" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf013f", + "txhash": "704cdcaffd4c954061ec58b872fbee9c2b94b7385de9e11b5b5d1e43e888e510" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf013f", + "txhash": "cc02ac60a04206b4f7c0faf787bdef47c441c5464a0d24f95ea3f9e1c81d5760" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf013f", + "txhash": "a7189cdabf51e9a8930d086450a1f9965f5a2f1bbfcdfb47ba14ec96c5e2ecd0" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf023f", + "txhash": "10f9c1076dc6d34f7177b9366b686a3520af52c8fb5ccb7e3724709ca0210572" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf023f", + "txhash": "08a5646e6638a493aa2814104210bd5f2a543b8b88b379279e5d5a0b0e7c4f3b" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf023f", + "txhash": "113b7e60da52bbd344cd8b18d2fcfdb3f520123e13c8f966a71c161a97c95ba6" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf023f", + "txhash": "387c49d806cb88a1436983b91e2a63653813c24ad4d9974ba8d44c1b63a9557e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf043f", + "txhash": "b11bc217da71d67ed976f0908471006b0cec02891616c455fcb98d75128bd328" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf043f", + "txhash": "7b3b4b969557627aa398dc10a4e6e9125e7b757abad6dcc06caa3680dea07f70" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf043f", + "txhash": "ac8f492346e55c11add88155404f956536ae1d3ead8df107c253ba50ac0e11b4" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf043f", + "txhash": "407549d8b10ab18bf129363da6c5cc7a3ad21a3bae5068fede793eef4b148d4b" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf083f", + "txhash": "b44795d262703ffab6d38510c419b2a1e42314e1b9cb500ab16db10000f0ca56" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf083f", + "txhash": "d54c373b31de8c13946c34ea4ae27b3af2448dd98b5b901a9816d0454ad615b2" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf083f", + "txhash": "dc37b517a33e8ff426d40178113ffe95cadd5bfeabfe65f449488c7206508b48" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf083f", + "txhash": "546d4464cc1c901ebe5caac760465ac44c6232299417694d5153fa96d0753da6" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf103f", + "txhash": "8ecc06837f33cc467fec862858cb6d7e9c35e1d62a408daf8f6efbab5d812c66" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf103f", + "txhash": "d52c830612571ec9b2dbb6aec9f2aadffd48c76126f34c40fc786ea09c71b0f1" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf103f", + "txhash": "5a42af4f19625d8ec806e8d8e1b0246cdb3059804db50cdf356f52e40b8c86e0" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf103f", + "txhash": "ca2e63bc58e793bcb56fa32fecf3a1ec58df97d53648ebdcdf7f01cd8aa4e63a" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf203f", + "txhash": "c40a940307664a7bba1bf4873d0991f3f0cd4574b04bc89b86dacb12da4b6a21" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf203f", + "txhash": "c1ef990ce8e9d0284d566b35370f01f1c7fff7ab53c7b2a48f5e6e5d78b1c5c7" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf203f", + "txhash": "628072f4eea49cf2c29af02238ca82a91234485ac79307e07d643135409bcfc3" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf203f", + "txhash": "ef1f7b3dc46a2bd6a3d0d6b14568059d246b7337764be277087fdc89424b6b75" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf3f3f", + "txhash": "787817cb8c2ea16d12e2fe66b38f765ba8e170eaddb0c511e9b37f895f5c666f" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf3f3f", + "txhash": "6973cb4041cc8ee9d37b4cdd8e99db9fdb2a131e4ac325924e947315f06701e7" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf3f3f", + "txhash": "cd2bec43e2c4e22b868d0396f0acc9dbe68ce12093e7470b493849d12e174461" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf3f3f", + "txhash": "7c745caed0eec865cc8c8672f4efc45aceb681ee9112508521474848dff12830" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0180", + "txhash": "ac959dc4847d5fb094be2c6bf9e3ac96d3c1cbc16526876fa69c105dadb3e59d" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0180", + "txhash": "7519ca17a0305144d817dafc6d9e15c46da39abdd411a6788364fb635e1d7dc4" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0180", + "txhash": "fb1625c14a7bf1e9bfc26019999992b1512163afd5ac51737f33b643dc6c7ee9" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0180", + "txhash": "9caa154c3c430a4cda5f693ac58738ab7fd275b0817e87d0096806cc1d5c2aeb" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0280", + "txhash": "9391fee41bc4e011d6248e350e9810fee89e930425291142675b24712ca65a21" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0280", + "txhash": "3a615ca685575f58870150c8e3d5fc3d4c869aef55f3a75f8612f1c514fe74a5" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0280", + "txhash": "1e8aa43768e2f2ac592a42e0a620c012f138cb1f7f889e519edbb64bc683f4fb" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0280", + "txhash": "876d911b6a58dba83a4b42ad11aaa8bdfc147f6bdccfcb247616762a7ae2ab67" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0480", + "txhash": "aeed6fe11242904a6b856153c3c5333b37ef93c96d6e301c43325a093a00b174" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0480", + "txhash": "70fddcb15b6f8b98cb401ca8fb41b5c829980b500b320717b7ab2efb06e99368" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0480", + "txhash": "f6b7b3bfb91710b07028e282c2d482c2692bf7c4633798aed56bfba5c7cb8a63" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0480", + "txhash": "9e8ea9ce296035ab4d2602506538976ef157256ff0803a0d388550a6ba16e6c6" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf0880", + "txhash": "f49451b4e7376a60c5b3549e2859ea4ac93399dbcc88ca8fc9e4fbbf0a660d73" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf0880", + "txhash": "d134fa8249145db8226a0aac9b0ae4a63822120cdbfbd9272b675a80cba7ad91" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf0880", + "txhash": "39a0f8a7c74c226c1b14513a9ec2b15446a1ce34b0ab89df56c0852d1bfecffd" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf0880", + "txhash": "beefd026dfcd60543a36a6f7bbffd84f3a78742ea169edca8d03fe9bc849ab6e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf1080", + "txhash": "0c34aa04360c298392ad1aa7e3ba37c08a714f4941a257bc32fbf60a7429ba10" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf1080", + "txhash": "d0c875772e789e889eac356c7b8f0f353370298d94b03733c10108c5a94544f4" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf1080", + "txhash": "ac0f4560cb2a8ff78d7d3cf32826726c4042ed4c0f5608a57b5a9462d6945fbc" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf1080", + "txhash": "d537146edf56396d3d9102a3d54fbe4345f4ace3f9381c1bbe265e398206ecde" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf2080", + "txhash": "20cb75f623ed737d65149a7524c0dd4b442ccc099f5e9e4baa66d8423519c048" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf2080", + "txhash": "180276f49f05dea354769340c9641bba5d4553b44e394e7178b01606b313960d" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf2080", + "txhash": "6bd0116358cf22d8f2dcc2a318ebb2f74454168df14aee6d468ce5a8a4fb3cd6" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf2080", + "txhash": "23d89f7808eec62ab7135ed2c7103f2660fe64d1e4d917c01a6dc42e314523a8" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf3f80", + "txhash": "b906691aac414c3a31ded419b295c921f6f7bd09e474919fe4341ee26317e08c" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf3f80", + "txhash": "e0daa6996d8a8a808d95787222ffdba42f135fa0b72d64549d040d2fd0ecf9dc" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf3f80", + "txhash": "678472c9fd9a8ffa20d9bff2a3688bb87d509394640b3a5a0e947308f57333fe" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf3f80", + "txhash": "37f06b72cf1f2f577fff579b918bbdd801146fa15d5ba9cf66ac64659369e74e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf01c0", + "txhash": "703d1a46549f50c8a083d717075f52e7b115b2ee979bf31915fe9a5312c01935" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf01c0", + "txhash": "168074638cc553173d362226045868ba709e4407373dfa51a3db849b45a31b6f" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf01c0", + "txhash": "6efc9bbffa2f4baf83a72c0dad6f927f3e6723d7c66ad6bbd294f783eaf0dbce" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf01c0", + "txhash": "c114c2de48a8390adeaaad5a63e677de18026b934eb0fc4f588e17ef281e1515" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf02c0", + "txhash": "a0aea094bf1fc0266d191eddb13396a02156a97bd9ed73ddd25d71ed18d41e18" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf02c0", + "txhash": "a9a5c7342201f9dc9792825c722336f56ee9fb3b679dbbee2aa603792a4e5a69" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf02c0", + "txhash": "167efe8eedab55a8e1ccc6b2bcc79aa21b3486622b063fc2dea9f32815e182e4" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf02c0", + "txhash": "d4259f2141f0a93411b78d32cc2716bb4a78d5f587df604f90e0cf88a4a0f641" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf04c0", + "txhash": "97495126380c45d14c147ca957fdafd281f43fa5ff9133f8db97f561bba9f276" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf04c0", + "txhash": "5170aa6429978cd526991509af600d3a023f19c91a705ba783f6c815f69195a1" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf04c0", + "txhash": "ec308363d3235813fe12f6ac97d11e806cc9cb4b0922bc09ed394147ec11b3d0" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf04c0", + "txhash": "24328fc176716844102ace005a9fea93177a475d2115d4f9480c3c50ace7ff74" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf08c0", + "txhash": "71a685b55b2fded5618052009c5906ab49b7ddd1e7f4da40d2c44666ea0f7fa1" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf08c0", + "txhash": "71b9f005621870f63372ea8595f58aa9a8cb52fae899df69aa047d18a447ba21" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf08c0", + "txhash": "0d65552788391f605a10e5b21b5f226e2240566a4446a8ab5d52c985aa50ac31" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf08c0", + "txhash": "6440e25776c53da20c47ebfb5beb31956041ea1b1eeb6cf5d88d83a42b5ec550" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf10c0", + "txhash": "d7e6ccec8c6a15c3c7216bbdfa98229413e9e53d006bba5382dd27788ec21c0f" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf10c0", + "txhash": "70be929709d9c3c4c9ef26c4a09ad87ab16199a9273152accd8239dd122a9da4" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf10c0", + "txhash": "8045026a37838ba8caf889ebf4a7d08c350b646a0bca4f76616e5afdc92dd71a" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf10c0", + "txhash": "f56a4095a26dd3578791b2f9bf2e88801c89500f7dc2871137e33317729e8313" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf20c0", + "txhash": "7b5b8cddfb740ab8eee2861ef1a356a2611e23d3d3a4e74c54c8728534cfa393" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf20c0", + "txhash": "96cc12bc1bc76f73e2cf4510e1126bca528db060372e212d4b9b4acecc1993b9" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf20c0", + "txhash": "aca85f5ca541f96e58d286959819e99ab0ba78023e202e40b8fdc8c09d8ec75f" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf20c0", + "txhash": "44b1b71bbda59f87662843921acaf333d303b6e44dee61d47635f86224fbd311" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf3fc0", + "txhash": "e4ad1c14efa4241f07d6e1856130b7be4684a56cb0d5b294e867f14577fafba8" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf3fc0", + "txhash": "b70ab3e74903611697ee4acbd83dd38309668e4a55e4b896fd613980d9dcd862" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf3fc0", + "txhash": "e3d82c6c6de142e2cb4da0af6cd4fd0f33ce9a3189ede385f0512b322561cc70" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf3fc0", + "txhash": "f326187d19bdf2fa2d518170180fec7ceca5972798f6dc315a00a7625e1af332" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf01bf", + "txhash": "2904ae7e8a1a10c7fbd145fe619252e01d7bd21f1787e3afbf70282effe54133" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf01bf", + "txhash": "ab85f02daf2348c5f0128eda15448c82d3f6830dd1266c3659a5b298a1eac7e4" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf01bf", + "txhash": "0ba449340acf50bf5c94a73a902607d93f72b808fc30a3a222173dfa4e22aa25" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf01bf", + "txhash": "cb67d45e1386c92fbc940139093694fca0c3e087465a8e62ddd8a8c0f6269dce" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf02bf", + "txhash": "2a48b62be39251aa84ce4449cd6105aadbdf4e98f37f248439b7365ce4618e56" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf02bf", + "txhash": "6918e003a7098e0d3736da752e520c8cb0798ca0e20a991b987963053308e709" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf02bf", + "txhash": "707fa269766ec96221f2a364648483397d076f6cc1945cc555050f9768fb0d53" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf02bf", + "txhash": "33001865949e90fbb9bd3fb5765aecb4ced05da24c19f995f4edf66173b0d815" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf04bf", + "txhash": "3a4be77c748d9b59ac4a83d8d5f548e446863798bb60fb5876a53dbe2dc81454" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf04bf", + "txhash": "e22d699c81521211137a23aab1d94389822c4342494c89b27942e2991ec4c526" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf04bf", + "txhash": "b2a3644a82ba4021f9ad5d7507c51dcdd2cbac9ba1d7b6ee80739daed2966b44" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf04bf", + "txhash": "96fa865973293dae2846a4b4d4a412cea6fa5adb615d97f1ecf7f77f5c6dcd06" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf08bf", + "txhash": "d09224751853e6ec7cef1cc261bab08c5f697cd430e2b7c6435065ce60e15b3d" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf08bf", + "txhash": "1a71ef9972fac9ed042d6fa9769084601e62a7db270e9af46aa995ed5488aea0" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf08bf", + "txhash": "58610c8a0a6a0392669489c8769b1c0fc65fa5a09bd10f46f7df71f3fc64a58f" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf08bf", + "txhash": "997795369fa0afddce28fd275c0a94c96ddb3047b11d22d3a436cbe85fb0d89b" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf10bf", + "txhash": "8bec5eff66f7de96ccc037c31885f7dae13796a65b692597d64c5754f7c8cf83" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf10bf", + "txhash": "b7b0090b401715af3291333e09884a97624737b5903f774341acfc68ecc253bf" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf10bf", + "txhash": "5b60a5acdbfe9d41cbe7113b23da7ab05eeb9f1483d4bfb3acea5d68fde27a2e" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf10bf", + "txhash": "7b7260a0b97bf6cf51345b436e454038eb2042fb4a821a0606a0f1fc8f639e20" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf20bf", + "txhash": "2334e8398c5b47a357cc223bf8887bbecff1b5d51c3b2b65a5fb8fbfefa5eba2" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf20bf", + "txhash": "f172ad19a8463e0184013bd8f1f0cb3f53676c92018821851bff9a3553ef7a9d" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf20bf", + "txhash": "fcf2e4d9809f85c18d9bd7431b1f28c9a720a2445c1f452e4e4b22802a37e1ec" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf20bf", + "txhash": "e39b0dd76c8f6de2587fc9f37ff5e20a8b61a643826a26c54b0e11ded5eebc54" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "bf3fbf", + "txhash": "704f3966892269f367215ceba260a38f2697b118d43b83bdeb5786e300294397" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "bf3fbf", + "txhash": "f785c544d541edc20c0196e99055f992399e4576d4b3905e401761cb8585bb7c" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "bf3fbf", + "txhash": "bcf596c4f29c79cfdfc347c9a800629c15205b341e2eeae59abf9e18f9785e9c" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "bf3fbf", + "txhash": "e2e1be3fe1ae57fd217b7b1451f2d2ec203b7a01009fb538bd05240c4bb0f792" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffff3f3f", + "txhash": "bd9b6f5c22c7da4c380c239a7ea8b132ada659f5a5334a597e9bd41b78ce1c44" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffff3f3f", + "txhash": "7bcb1faec3b3675f667274924f3f8357fe8af7e9dab1c7ab6662e82f249b30c0" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffff3f3f", + "txhash": "426c94659c2cea2f7761103a0780c78c1935ef6eaec5657080b1326c16849600" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "ffff3f3f", + "txhash": "183229534a28400cb72121911896ed76c3fe5b91fdba12d5b8e36ecd2aff2712" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffff4040", + "txhash": "ecb487752546e951516705c240b5c7d421158b3b1a83ae959d109afd0ce3a208" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffff4040", + "txhash": "78f559915492aff22f62bb24a578825653c473f4ca3ff6d588f2e35de83c676a" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffff4040", + "txhash": "ecab4704755a57b9fc58ad47b6a841e45b9c835721a627b6333d14666b4fe809" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffff8080", + "txhash": "0c1059668b37be3cb601854a16a856642cbc22f41f8e4ec3dad84b6540bcf7d6" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffff8080", + "txhash": "23d5cd006d71efea16c0051035a87c90714568c9d780a0477fe9db3e4331652d" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffff8080", + "txhash": "e129745f8a5c962a3989e62fc471c58c734346f09a41e985dcf4417a974b96a6" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "ffff8080", + "txhash": "40dd74dc56c6bb8436611325d13e15fa7c0d913bd426252e634463b7e993741f" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffffbfbf", + "txhash": "fe0ec98b3d640ec3bec81e0d9b58a7a563a93beb476c61bf648f6c8b2cb3cc4b" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffffbfbf", + "txhash": "44004bc5bf81b4bef13f3b1a88019dd7e8557e8dc7384a4cdb4089714b243025" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffffbfbf", + "txhash": "5b873279c3719485395261e3161b422252e612cd3a083d0c83136be4da4c1c1b" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "ffffbfbf", + "txhash": "244a94e0e8c391b6f105ff2c8da24d26efa67e9b69e1917a17d6ae0c2872b1eb" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffffc0c0", + "txhash": "67ab05d39c6b98b0d8b0ffeb892d6435dcbedb3284dcd7ed4912436a3e7ecee0" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffffc0c0", + "txhash": "667cf47f3d2af2e589135e1fc657bcb2c3008d99c66ae5696f689cdd05987460" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffffc0c0", + "txhash": "0034079e8ee835e01539a90ed12960df0c20d00ce768e5f8cff81f4b1de56718" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffff403f", + "txhash": "de613bc1538f52435e2fcb520d98f5cbfb9922ba6b241c46964ac5a6058bf26d" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffff403f", + "txhash": "ff2e38e426425ac5913674afde5e8113dcacdb32984e93aa31ced26390f2f45c" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffff403f", + "txhash": "cefb9122e27b6faf022b8d5ce8e3ae592b320edd176c4c5394b86ad894553dc4" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "ffff403f", + "txhash": "34535aab9e71af1fe7db821e7e8c5895b2a30b06cf5a03b5974a5d0bebabaea4" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffff3f40", + "txhash": "c593165638d0d7d157c3bdc5295464d6f201c3de660971668c24acb4ff694e1a" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffff3f40", + "txhash": "a91f5fe5a174ce47d5d86c54432b10dc1110db85bc4d6a98243306cb997deb21" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffff3f40", + "txhash": "6e305601b18038736da36ec3b2ac4c8547e5e31c2666ac6e52665435b0cb90a2" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffff0102", + "txhash": "1126186623a5da2b205ce726176fff52cd8ea13d1700fbec2430c6b21fb1947b" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffff0102", + "txhash": "61ae007ce70ce68b324a9f87091e1b9fa3108e93aebb44cef92206ee1610f3d9" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffff0102", + "txhash": "88453084e176f7ea39815b1d971149d1e9eedee019d7073143ee7581f83773aa" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "ffff0102", + "txhash": "30a1e19ef5b0787f1d73fe5f20b849e20861fd950833f300f09018e53d9ded0b" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffff4101420002", + "txhash": "f716067fe48fafd0dc2b8eb7c66bc1e6c5728267679f54c8c6701e512865dc7e" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffff4101420002", + "txhash": "b33949998e8593e81a062494955b43b0efc7d6536f11e3128fb68a1c26e432d5" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffff4101420002", + "txhash": "91f2e9acae60c7550361da5aebd121794f379684541ca3fe0e14b682f2aa9e3d" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "ffff4101420002", + "txhash": "5e60916b38ecc08902ff6edb07a0ca59c788a5b94f2b6cb45dd3473c0c5c6676" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "ffffc101c20002", + "txhash": "3985e4a2cb45548f4f65c1bc19ce4a39b1f377b9846bf9d0b638756511e46519" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "ffffc101c20002", + "txhash": "15b181822828e7dfa1cae09e9e461655f87fc03de4b6c4b338966a397665acad" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "ffffc101c20002", + "txhash": "56e33218cda9d407a8e05939100c90ff32f692b4ab5902dda2852a6be96576da" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "ffffc101c20002", + "txhash": "39e4475f09fbe045174ad7f1bd0db8da8158a542f53380e1e26c06cd098068ea" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "", + "txhash": "723c1c2f5947763d0a3f0d545e5b1d041eb7195037161b65a568ee5977ac486f" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "", + "txhash": "dbb5b4c37596ccfc3ea19931bed2d7f67ccd2362f95cd34e34504b50df724a71" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "", + "txhash": "4cc217e7a101d94c5b54a0442a591933239fb112059ed229d594b8fcee76f3c3" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "", + "txhash": "c64c106fb50f3ba80cf6887cc7161933fc33c658baba57156a7d66b6cd10d35e" + }, + { + "codeseparator": null, + "input": 0, + "txfs": "00", + "txhash": "fe0ec98b3d640ec3bec81e0d9b58a7a563a93beb476c61bf648f6c8b2cb3cc4b" + }, + { + "codeseparator": null, + "input": 1, + "txfs": "00", + "txhash": "44004bc5bf81b4bef13f3b1a88019dd7e8557e8dc7384a4cdb4089714b243025" + }, + { + "codeseparator": null, + "input": 2, + "txfs": "00", + "txhash": "5b873279c3719485395261e3161b422252e612cd3a083d0c83136be4da4c1c1b" + }, + { + "codeseparator": null, + "input": 3, + "txfs": "00", + "txhash": "244a94e0e8c391b6f105ff2c8da24d26efa67e9b69e1917a17d6ae0c2872b1eb" + } + ] + } + ] \ No newline at end of file diff --git a/txscript/standard.go b/txscript/standard.go index 5ef2ad167f..e24af49ac1 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -489,6 +489,18 @@ func extractAnnex(witness [][]byte) ([]byte, error) { return lastElement, nil } +// extractControlBlock attempts to extract the control block from the passed +// witness. If the witness doesn't contain a control block, then nil is +// returned. +func extractControlBlock(witness [][]byte) []byte { + if !isAnnexedWitness(witness) { + return nil + } + + // return the second to last element, which is the control block. + return witness[len(witness)-2] +} + // isNullDataScript returns whether or not the passed script is a standard // null data script. // diff --git a/txscript/txhash.go b/txscript/txhash.go new file mode 100644 index 0000000000..18005e55d0 --- /dev/null +++ b/txscript/txhash.go @@ -0,0 +1,913 @@ +package txscript + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +const ( + // TXFSVersion is the bit that indicates that the version should be + // hashed into the TxHash. + TXFSVersion uint8 = 1 << 0 + + // TXFSLockTime is the bit that indicates that the locktime should be + // hashed into the TxHash. + TXFSLockTime uint8 = 1 << 1 + + // TXFSCurrentInputIdx is the bit that indicates that the current input + // index should be hashed into the TxHash. + TXFSCurrentInputIdx uint8 = 1 << 2 + + // TXFSCurrentInputControlBlock is the bit that indicates that the + // control block of the current input should be hashed into the TxHash. + TXFSCurrentInputControlBlock uint8 = 1 << 3 + + // TXFSCurrentInputLastCodeseparatorPos is the bit that indicates that + // the last codeseparator position of the current input should be hashed + // into the TxHash. + TXFSCurrentInputLastCodeseparatorPos uint8 = 1 << 4 + + // TXFSInputs is the bit that indicates that the inputs should be hashed + // into the TxHash. + TXFSInputs uint8 = 1 << 5 + + // TXFSOutputs is the bit that indicates that the outputs should be + // hashed into the TxHash. + TXFSOutputs uint8 = 1 << 6 + + // TXFSControl is the bit that indicates that the TxFieldSelector + // should be hashed into the TxHash. + TXFSControl uint8 = 1 << 7 + + // TXFSAll is the bit that indicates that all fields should be hashed + // into the TxHash. + TXFSAll uint8 = TXFSVersion | + TXFSLockTime | + TXFSCurrentInputIdx | + TXFSCurrentInputControlBlock | + TXFSCurrentInputLastCodeseparatorPos | + TXFSInputs | + TXFSOutputs | + TXFSControl +) +const ( + // TXFSInputsPrevouts is the bit that indicates that the previous + // outpoints should be hashed into the TxHash. + TXFSInputsPrevouts uint8 = 1 << 0 + + // TXFSInputsSequences is the bit that indicates that the sequences + // should be hashed into the TxHash. + TXFSInputsSequences uint8 = 1 << 1 + + // TXFSInputsScriptsigs is the bit that indicates that the scriptSigs + // should be hashed into the TxHash. + TXFSInputsScriptsigs uint8 = 1 << 2 + + // TXFSInputsPrevScriptpubkeys is the bit that indicates that the + // previous scriptPubkeys should be hashed into the TxHash. + TXFSInputsPrevScriptpubkeys uint8 = 1 << 3 + + // TXFSInputsPrevValues is the bit that indicates that the previous + // values should be hashed into the TxHash. + TXFSInputsPrevValues uint8 = 1 << 4 + + // TXFSInputsTaprootAnnexes is the bit that indicates that the annexes + // of the taproot inputs should be hashed into the TxHash. + TXFSInputsTaprootAnnexes uint8 = 1 << 5 + + // TXFSOutputsScriptpubkeys is the bit that indicates that the + // scriptPubkeys should be hashed into the TxHash. + TXFSOutputsScriptpubkeys uint8 = 1 << 6 + + // TXFSOutputsValues is the bit that indicates that the values should + // be hashed into the TxHash. + TXFSOutputsValues uint8 = 1 << 7 + + // TXFSInputsAll is the bit that indicates that all input fields should + // be hashed into the TxHash. + TXFSInputsAll uint8 = TXFSInputsPrevouts | + TXFSInputsSequences | + TXFSInputsScriptsigs | + TXFSInputsPrevScriptpubkeys | + TXFSInputsPrevValues | + TXFSInputsTaprootAnnexes + + // TXFSInputsTemplate is the bit that indicates that the input fields + // that are part of the template should be hashed into the TxHash. + TXFSInputsTemplate uint8 = TXFSInputsSequences | + TXFSInputsScriptsigs | + TXFSInputsPrevValues | + TXFSInputsTaprootAnnexes + + // TXFSOutputsAll is the bit that indicates that all output fields + // should be hashed into the TxHash. + TXFSOutputsAll uint8 = TXFSOutputsScriptpubkeys | TXFSOutputsValues + + // TXFSInoutNumber is the bit that indicates that the number of inputs + // or outputs should be hashed into the TxHash. + TXFSInoutNumber uint8 = 1 << 7 + + // TXFSInoutSelectionNone is the bit that indicates that no inputs or + // outputs should be selected. + TXFSInoutSelectionNone uint8 = 0x00 + + // TXFSInoutSelectionCurrent is the bit that indicates that the current + // input or output should be selected. + TXFSInoutSelectionCurrent uint8 = 0x40 + + // TXFSInoutSelectionAll is the bit that indicates that all inputs or + // outputs should be selected. + TXFSInoutSelectionAll uint8 = 0x3f + + // TXFSInoutSelectionMode is the bit that indicates that the selection + // mode is leading or individual. + TXFSInoutSelectionMode uint8 = 1 << 6 + + // TXFSInoutSelectionSize is the bit that indicates the size of the + // selection. + TXFSInoutSelectionSize uint8 = 1 << 5 + + // TXFSInoutSelectionMask is the mask that selects the number of inputs + // or outputs to be selected. + TXFSInoutSelectionMask uint8 = 0xff ^ TXFSInoutNumber ^ + TXFSInoutSelectionMode ^ TXFSInoutSelectionSize +) + +// TXFSSpecialAll is a template that means everything should be hashed into the +// TxHash. This is useful to emulate SIGHASH_ALL when OP_TXHASH is used in +// combination with OP_CHECKSIGFROMSTACK. +var TXFSSpecialAll = [4]uint8{ + TXFSAll, + TXFSInputsAll | TXFSOutputsAll, + TXFSInoutNumber | TXFSInoutSelectionAll, + TXFSInoutNumber | TXFSInoutSelectionAll, +} + +// TXFSSpecialAll is a template that means everything except the prevouts and +// the prevout scriptPubkeys should be hashed into the TxHash. +var TXFSSpecialTemplate = [4]uint8{ + TXFSAll, + TXFSInputsTemplate | TXFSOutputsAll, + TXFSInoutNumber | TXFSInoutSelectionAll, + TXFSInoutNumber | TXFSInoutSelectionAll, +} + +// InOutSelector is an interface for selecting inputs or outputs from a +// transaction. It is used to select the inputs or outputs that are hashed into +// the TxHash. +type InOutSelector interface { + SelectInputs(tx *wire.MsgTx, currentInput uint32, + inputs bool) ([]int, error) +} + +// InOutSelectorNone selects no inputs or outputs. +type InOutSelectorNone struct{} + +// SelectInputs returns an empty slice. +func (s *InOutSelectorNone) SelectInputs(tx *wire.MsgTx, currentInput uint32, + inputs bool) ([]int, error) { + + return nil, nil +} + +// InOutSelectorAll selects all inputs or outputs. +type InOutSelectorAll struct{} + +// SelectInputs returns a slice of all inputs or outputs. +func (s *InOutSelectorAll) SelectInputs(tx *wire.MsgTx, currentInput uint32, + inputs bool) ([]int, error) { + + var selected []int + if inputs { + selected = make([]int, len(tx.TxIn)) + for i := 0; i < len(tx.TxIn); i++ { + selected[i] = i + } + } else { + selected = make([]int, len(tx.TxOut)) + for i := 0; i < len(tx.TxOut); i++ { + selected[i] = i + } + } + return selected, nil +} + +// InOutSelectorCurrent selects the current input or output. +type InOutSelectorCurrent struct{} + +// SelectInputs returns a slice containing the current input or output. +func (s *InOutSelectorCurrent) SelectInputs(tx *wire.MsgTx, currentInput uint32, + inputs bool) ([]int, error) { + + if int(currentInput) >= len(tx.TxIn) && inputs || + (int(currentInput) > len(tx.TxOut)) && !inputs { + + return nil, fmt.Errorf("current input index exceeds number" + + " of inputs") + } + return []int{int(currentInput)}, nil +} + +// InOutSelectorLeading selects a leading number of inputs or outputs. +type InOutSelectorLeading struct { + Count int +} + +// SelectInputs returns a slice containing the leading number of inputs or +// outputs. +func (s *InOutSelectorLeading) SelectInputs(tx *wire.MsgTx, currentInput uint32, + inputs bool) ([]int, error) { + + if (s.Count > len(tx.TxIn)) && inputs || + (s.Count > len(tx.TxOut)) && !inputs { + + return nil, fmt.Errorf("selected number of inputs exceeds " + + "number of inputs") + } + selected := make([]int, s.Count) + for i := 0; i < s.Count; i++ { + selected[i] = i + } + return selected, nil +} + +// InOutSelectorIndividual selects individual inputs or outputs. +type InOutSelectorIndividual struct { + Indices []int +} + +// SelectInputs returns a slice containing the selected inputs or outputs. +func (s *InOutSelectorIndividual) SelectInputs(tx *wire.MsgTx, + currentInput uint32, inputs bool) ([]int, error) { + + for _, idx := range s.Indices { + if idx >= len(tx.TxIn) && inputs || + idx >= len(tx.TxOut) && !inputs { + + return nil, fmt.Errorf( + "selected input index exceeds number of inputs", + ) + } + } + return s.Indices, nil +} + +// newInOutSelector creates a new InOutSelector from a byte slice. +func newInOutSelector(txfs []byte) (InOutSelector, bool, []byte, error) { + first := (txfs)[0] + txfs = txfs[1:] + + commitNumber := first&TXFSInoutNumber != 0 + + selection := first & (0xff ^ TXFSInoutNumber) + + var selector InOutSelector + switch { + case selection == TXFSInoutSelectionNone: + selector = &InOutSelectorNone{} + + case selection == TXFSInoutSelectionAll: + selector = &InOutSelectorAll{} + + case selection == TXFSInoutSelectionCurrent: + selector = &InOutSelectorCurrent{} + + // Leading mode + case selection&TXFSInoutSelectionMode == 0: + var count int + if selection&TXFSInoutSelectionSize == 0 { + count = int(selection & TXFSInoutSelectionMask) + } else { + txfs = txfs[1:] + if selection&TXFSInoutSelectionMask == 0 { + return nil, false, nil, fmt.Errorf( + "leading mode with size selection" + + "but no size given") + } + count = (int(selection&TXFSInoutSelectionMask) << 8) + + int(txfs[0]) + } + + if count == 0 { + return nil, false, nil, fmt.Errorf("leading mode with" + + " size selection but no size given") + } + selector = &InOutSelectorLeading{Count: count} + + // Individual mode + default: + count := int(selection & TXFSInoutSelectionMask) + if count == 0 { + return nil, false, nil, fmt.Errorf("can't select 0 " + + "in/outputs in individual mode") + } + if len(txfs) < count { + return nil, false, nil, fmt.Errorf("not enough " + + "single-byte indices") + } + indices := make([]int, count) + for i := 0; i < count; i++ { + if selection&TXFSInoutSelectionSize == 0 { + indices[i] = int(txfs[0]) + txfs = txfs[1:] + } else { + first := txfs[0] + second := txfs[1] + indices[i] = (int(first) << 8) + int(second) + txfs = txfs[2:] + } + } + selector = &InOutSelectorIndividual{Indices: indices} + } + + return selector, commitNumber, txfs, nil +} + +// inOutSelectorToBytes serializes an InOutSelector to a byte slice. +func inOutSelectorToBytes(selector InOutSelector, + commitNumber bool) ([]byte, error) { + + var first byte + var txfs []byte + + switch s := selector.(type) { + case *InOutSelectorNone: + first = TXFSInoutSelectionNone + + case *InOutSelectorAll: + first = TXFSInoutSelectionAll + + case *InOutSelectorCurrent: + first = TXFSInoutSelectionCurrent + + case *InOutSelectorLeading: + count := s.Count + if count <= int(TXFSInoutSelectionMask) { + first = byte(count) & TXFSInoutSelectionMask + } else { + first = byte(uint8(count>>8)&TXFSInoutSelectionMask) | TXFSInoutSelectionSize + txfs = append(txfs, byte(count&0xFF)) + } + + case *InOutSelectorIndividual: + count := len(s.Indices) + first = TXFSInoutSelectionMode | byte(uint8(count)&TXFSInoutSelectionMask) + if count > int(TXFSInoutSelectionMask) { + first |= TXFSInoutSelectionSize + } + for _, idx := range s.Indices { + if count > int(TXFSInoutSelectionMask) { + txfs = append(txfs, byte((idx>>8)&0xFF), byte(idx&0xFF)) + } else { + txfs = append(txfs, byte(idx&0xFF)) + } + } + + default: + return nil, fmt.Errorf("unknown InOutSelector type") + } + + if commitNumber { + first |= TXFSInoutNumber + } + + // Prepend the first byte + txfs = append([]byte{first}, txfs...) + + return txfs, nil +} + +// InputSelector is used to get the hash of the inputs of a transaction, as +// specified by the TxFieldSelector. +type InputSelector struct { + // CommitNumber is true if the number of inputs should be committed to + // the TxHash. + CommitNumber bool + + // PrevOuts is true if the previous outpoints should be hashed into the + // TxHash. + PrevOuts bool + + // Sequences is true if the Sequences should be hashed into the + // TxHash. + Sequences bool + + // ScriptSigs is true if the ScriptSigs should be hashed into the + // TxHash. + ScriptSigs bool + + // PrevScriptPubkeys is true if the previous scriptPubkeys should be + // hashed into the TxHash. + PrevScriptPubkeys bool + + // PrevValues is true if the previous values should be hashed into the + // TxHash. + PrevValues bool + + // TaprootAnnexes is true if the annexes of the taproot inputs should + // be hashed into the TxHash. + TaprootAnnexes bool + + // InOutSelector is the InOutSelector that selects the inputs that + // should be used to calculate the TxHash. + InOutSelector InOutSelector +} + +// NewInputSelectorFromBytes creates a new inputSelector from a byte slice. +func NewInputSelectorFromBytes(inoutFields byte, txfs []byte) (*InputSelector, + []byte, error) { + + inputSelector := &InputSelector{ + PrevOuts: inoutFields&TXFSInputsPrevouts != 0, + Sequences: inoutFields&TXFSInputsSequences != 0, + ScriptSigs: inoutFields&TXFSInputsScriptsigs != 0, + PrevScriptPubkeys: inoutFields&TXFSInputsPrevScriptpubkeys != 0, + PrevValues: inoutFields&TXFSInputsPrevValues != 0, + TaprootAnnexes: inoutFields&TXFSInputsTaprootAnnexes != 0, + } + + var err error + + inputSelector.InOutSelector, inputSelector.CommitNumber, + txfs, err = newInOutSelector(txfs) + + if err != nil { + return nil, nil, err + } + + return inputSelector, txfs, nil +} + +// writeInputsHash writes the hash of the inputs of a transaction to the given +// buffer. +func (s *InputSelector) writeInputsHash(txHashBuffer *bytes.Buffer, + tx *wire.MsgTx, prevoutFetcher PrevOutputFetcher, + currentInputIdx uint32) error { + + // Select the inputs that we should hash into the TxHash. + inputIndices, err := s.InOutSelector.SelectInputs( + tx, currentInputIdx, true, + ) + if err != nil { + return err + } + + // Write the number of inputs to the buffer if the CommitNumber bit is + // set. + if s.CommitNumber { + binary.Write(txHashBuffer, binary.LittleEndian, uint32(len(tx.TxIn))) + } + + if len(inputIndices) > 0 && s.PrevOuts { + var buffer bytes.Buffer + for _, idx := range inputIndices { + wire.WriteOutPoint( + &buffer, 0, tx.Version, + &tx.TxIn[idx].PreviousOutPoint, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) + } + + if len(inputIndices) > 0 && s.Sequences { + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write( + &buffer, binary.LittleEndian, + tx.TxIn[idx].Sequence, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) + } + + if len(inputIndices) > 0 && s.ScriptSigs { + var buffer bytes.Buffer + for _, idx := range inputIndices { + buffer.Write( + chainhash.HashB(tx.TxIn[idx].SignatureScript), + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) + } + + if len(inputIndices) > 0 && s.PrevScriptPubkeys { + var buffer bytes.Buffer + for _, idx := range inputIndices { + prevout := prevoutFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint, + ) + buffer.Write(chainhash.HashB(prevout.PkScript)) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) + } + + if len(inputIndices) > 0 && s.PrevValues { + var buffer bytes.Buffer + for _, idx := range inputIndices { + prevout := prevoutFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint, + ) + binary.Write( + &buffer, binary.LittleEndian, prevout.Value, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) + } + + if len(inputIndices) > 0 && s.TaprootAnnexes { + var buffer bytes.Buffer + for _, idx := range inputIndices { + if IsPayToTaproot(prevoutFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint).PkScript, + ) { + + if isAnnexedWitness(tx.TxIn[idx].Witness) { + annex, err := extractAnnex( + tx.TxIn[idx].Witness, + ) + if err != nil { + return err + } + buffer.Write(chainhash.HashB(annex)) + } else { + buffer.Write(chainhash.HashB([]byte{})) + } + } else { + buffer.Write(chainhash.HashB([]byte{})) + } + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) + } + + return nil +} + +// OutputSelector is used to get the hash of the outputs of a transaction, as +// specified by the TxFieldSelector. +type OutputSelector struct { + // CommitNumber is true if the number of outputs should be committed to + // the TxHash. + CommitNumber bool + + // ScriptPubkeys is true if the ScriptPubkeys should be hashed into the + // TxHash. + ScriptPubkeys bool + + // Values is true if the Values should be hashed into the TxHash. + Values bool + + // InOutSelector is the InOutSelector that selects the outputs that + // should be used to calculate the TxHash. + InOutSelector InOutSelector +} + +// newOutputSelectorFromBytes creates a new outputSelector from a byte slice. +func newOutputSelectorFromBytes(inoutFields byte, txfs []byte) (*OutputSelector, + []byte, error) { + + outputSelector := &OutputSelector{ + ScriptPubkeys: inoutFields&TXFSOutputsScriptpubkeys != 0, + Values: inoutFields&TXFSOutputsValues != 0, + } + + var err error + + outputSelector.InOutSelector, outputSelector.CommitNumber, + txfs, err = newInOutSelector(txfs) + + if err != nil { + return nil, nil, err + } + + return outputSelector, txfs, nil +} + +// writeOutputsHash writes the hash of the outputs of a transaction to the given +// buffer. +func (s *OutputSelector) writeOutputsHash(sigMsg *bytes.Buffer, tx *wire.MsgTx, + currentInputIdx uint32) error { + + // Select the outputs that we should hash into the TxHash. + outputIndices, err := s.InOutSelector.SelectInputs( + tx, currentInputIdx, false, + ) + if err != nil { + return err + } + + // Write the number of outputs to the buffer if the CommitNumber bit is + // set. + if s.CommitNumber { + binary.Write(sigMsg, binary.LittleEndian, uint32(len(tx.TxOut))) + } + + if len(outputIndices) > 0 && s.ScriptPubkeys { + var buffer bytes.Buffer + for _, idx := range outputIndices { + buffer.Write(chainhash.HashB(tx.TxOut[idx].PkScript)) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + sigMsg.Write(bufferHash) + } + + if len(outputIndices) > 0 && s.Values { + var buffer bytes.Buffer + for _, idx := range outputIndices { + binary.Write( + &buffer, binary.LittleEndian, + tx.TxOut[idx].Value, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + sigMsg.Write(bufferHash) + } + + return nil +} + +// TxFieldSelector is used to select the fields of a transaction that should be +// hashed into the TxHash. +type TxFieldSelector struct { + // Control is true if the TxFieldSelector itself should be hashed into + // the TxHash. + Control bool + + // Version is true if the version should be hashed into the TxHash. + Version bool + + // LockTime is true if the locktime should be hashed into the TxHash. + LockTime bool + + // CurrentInputIdx is true if the current input index should be hashed + // into the TxHash. + CurrentInputIdx bool + + // CurrentInputControlBlock is true if the control block of the current + // input should be hashed into the TxHash. + CurrentInputControlBlock bool + + // CurrentInputLastCodeseparatorPos is true if the last codeseparator + // position of the current input should be hashed into the TxHash. + CurrentInputLastCodeseparatorPos bool + + // Inputs is the InputSelector that selects the inputs that should be + // used to calculate the TxHash. + Inputs *InputSelector + + // Outputs is the OutputSelector that selects the outputs that should + // be used to calculate the TxHash. + Outputs *OutputSelector +} + +// NewTxFieldSelectorFromBytes creates a new TxFieldSelector from a byte slice. +func NewTxFieldSelectorFromBytes(txfs []byte) (*TxFieldSelector, error) { + if len(txfs) == 0 { + txfs = TXFSSpecialTemplate[:] + } else if len(txfs) == 1 && txfs[0] == 0x00 { + txfs = TXFSSpecialAll[:] + } + + // The first byte is the global byte. + global := txfs[0] + + // Create the TxFieldSelector from the global byte. + fields := &TxFieldSelector{ + Control: global&TXFSControl != 0, + Version: global&TXFSVersion != 0, + LockTime: global&TXFSLockTime != 0, + CurrentInputIdx: global&TXFSCurrentInputIdx != 0, + CurrentInputControlBlock: global&TXFSCurrentInputControlBlock != 0, + CurrentInputLastCodeseparatorPos: global&TXFSCurrentInputLastCodeseparatorPos != 0, + } + + // Stop early if no inputs or outputs are selected. + if global&TXFSInputs == 0 && global&TXFSOutputs == 0 { + return fields, nil + } + + if len(txfs) < 2 { + return nil, fmt.Errorf("in- or output bit set but only one byte") + } + + txfs = txfs[1:] + + // The second byte is the in- and output byte. + inoutFields := txfs[0] + + txfs = txfs[1:] + + var err error + // If the TXFSInputs bit is set at the global byte, create the + // InputSelector from the in- and output byte. + if global&TXFSInputs != 0 { + fields.Inputs, txfs, err = NewInputSelectorFromBytes( + inoutFields, txfs, + ) + if err != nil { + return nil, err + } + } + + // If the TXFSOutputs bit is set at the global byte, create the + // OutputSelector from the in- and output byte. + if global&TXFSOutputs != 0 { + fields.Outputs, _, err = newOutputSelectorFromBytes( + inoutFields, txfs, + ) + if err != nil { + return nil, err + } + } + + return fields, nil +} + +// GetTxHash returns the TxHash of a transaction, as specified by the +// TxFieldSelector. +func (f *TxFieldSelector) GetTxHash(tx *wire.MsgTx, currentInputIdx uint32, + prevOutputFetcher PrevOutputFetcher, + currentInputLastCodeseparatorPos uint32) ([]byte, error) { + + var sigMsg bytes.Buffer + + // If the control bit is set, write the full TxFieldSelector to the + // buffer. + if f.Control { + fullTxfs, err := f.ToBytes() + if err != nil { + return nil, err + } + sigMsg.Write(fullTxfs) + } + + // Now we'll write the version, locktime, current input index, + // current input control block and current input last codeseparator + // position to the buffer. + + if f.Version { + binary.Write(&sigMsg, binary.LittleEndian, tx.Version) + } + + if f.LockTime { + binary.Write(&sigMsg, binary.LittleEndian, tx.LockTime) + } + + if f.CurrentInputIdx { + binary.Write(&sigMsg, binary.LittleEndian, currentInputIdx) + } + + if f.CurrentInputControlBlock { + controlBytes := []byte{} + + prevout := prevOutputFetcher.FetchPrevOutput( + tx.TxIn[currentInputIdx].PreviousOutPoint, + ) + + // If the prevout is p2tr, unwrap the control block. + if IsPayToTaproot(prevout.PkScript) { + controlBytes = extractControlBlock( + tx.TxIn[currentInputIdx].Witness, + ) + } + + sigMsg.Write(chainhash.HashB(controlBytes)) + } + + if f.CurrentInputLastCodeseparatorPos { + if currentInputLastCodeseparatorPos == 0 { + // Set the current input last codeseparator position to + // the max value if it is not set. + currentInputLastCodeseparatorPos = 0xffffffff + } + binary.Write( + &sigMsg, binary.LittleEndian, + currentInputLastCodeseparatorPos, + ) + } + + // If we have an InputSelector, write the hash of the inputs to the + // buffer. + if f.Inputs != nil { + err := f.Inputs.writeInputsHash( + &sigMsg, tx, prevOutputFetcher, currentInputIdx, + ) + if err != nil { + return nil, err + } + } + + // If we have an OutputSelector, write the hash of the outputs to the + // buffer. + if f.Outputs != nil { + err := f.Outputs.writeOutputsHash(&sigMsg, tx, currentInputIdx) + if err != nil { + return nil, err + } + } + + // Return the hash of the buffer. + return chainhash.HashB(sigMsg.Bytes()), nil +} + +// ToBytes serializes a TxFieldSelector to a byte slice. +func (f *TxFieldSelector) ToBytes() ([]byte, error) { + var txfs []byte + var global byte + if f.Control { + global |= TXFSControl + } + if f.Version { + global |= TXFSVersion + } + if f.LockTime { + global |= TXFSLockTime + } + if f.CurrentInputIdx { + global |= TXFSCurrentInputIdx + } + if f.CurrentInputControlBlock { + global |= TXFSCurrentInputControlBlock + } + if f.CurrentInputLastCodeseparatorPos { + global |= TXFSCurrentInputLastCodeseparatorPos + } + var err error + var inout byte + + var inputsBytes []byte + if f.Inputs != nil { + global |= TXFSInputs + + if f.Inputs.PrevOuts { + inout |= TXFSInputsPrevouts + } + if f.Inputs.Sequences { + inout |= TXFSInputsSequences + } + if f.Inputs.ScriptSigs { + inout |= TXFSInputsScriptsigs + } + if f.Inputs.PrevScriptPubkeys { + inout |= TXFSInputsPrevScriptpubkeys + } + if f.Inputs.PrevValues { + inout |= TXFSInputsPrevValues + } + if f.Inputs.TaprootAnnexes { + inout |= TXFSInputsTaprootAnnexes + } + + // Serialize the InOutSelector part + inputsBytes, err = inOutSelectorToBytes( + f.Inputs.InOutSelector, f.Inputs.CommitNumber, + ) + if err != nil { + return nil, err + } + } + + var outputsBytes []byte + if f.Outputs != nil { + global |= TXFSOutputs + + if f.Outputs.ScriptPubkeys { + inout |= TXFSOutputsScriptpubkeys + } + if f.Outputs.Values { + inout |= TXFSOutputsValues + } + + // Serialize the InOutSelector part + outputsBytes, err = inOutSelectorToBytes( + f.Outputs.InOutSelector, f.Outputs.CommitNumber, + ) + if err != nil { + return nil, err + } + + } + + txfs = append(txfs, global) + if inout != 0 { + txfs = append(txfs, inout) + } + + if len(inputsBytes) > 0 { + txfs = append(txfs, inputsBytes...) + } + if len(outputsBytes) > 0 { + txfs = append(txfs, outputsBytes...) + } + + return txfs, nil +} diff --git a/txscript/txhash_test.go b/txscript/txhash_test.go new file mode 100644 index 0000000000..ff08960396 --- /dev/null +++ b/txscript/txhash_test.go @@ -0,0 +1,136 @@ +package txscript + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +// TestCalculateTxHash tests the calculation of the tx hash for a given +// transaction, prevouts and tx field selector. +func TestCalculateTxHash(t *testing.T) { + // First read the test vectors from the file. + testVectors := parseTestVectorsFromFile(t, "./data/txhash_vectors.json") + + // Create a new transaction from the hex string. + tx := wire.NewMsgTx(wire.TxVersion) + err := tx.Deserialize(bytes.NewReader( + decodeHex(t, testVectors.Tx), + )) + require.NoError(t, err) + + // Create the prevout map for the prevout fetcher. + prevOuts := make([]*wire.TxOut, len(testVectors.Prevs)) + prevOutMap := make(map[wire.OutPoint]*wire.TxOut) + for idx, prev := range testVectors.Prevs { + txOut := wire.NewTxOut(0, nil) + + r := bytes.NewReader(decodeHex(t, prev)) + + err = wire.ReadTxOut(r, 0, wire.TxVersion, txOut) + require.NoError(t, err) + + prevOuts[idx] = txOut + + prevOutMap[tx.TxIn[idx].PreviousOutPoint] = txOut + } + + prevOutFetcher := NewMultiPrevOutFetcher(prevOutMap) + + // Run through the test vectors and make sure the tx hash matches. + for testIdx, testVector := range testVectors.Vectors { + txfs := decodeHex(t, testVector.Txfs) + + // Create the TxFieldSelector from the bytes. + TxFields, err := NewTxFieldSelectorFromBytes(txfs) + require.NoError(t, err) + + // Calculate the tx hash. + txHash, err := TxFields.GetTxHash( + tx, testVector.Input, prevOutFetcher, 0, + ) + require.NoError( + t, err, fmt.Sprintf("failed at test vector %d"+ + " expected hash %s", testIdx, testVector.TxHash), + ) + + // Make sure the tx hash matches the expected. + require.Equal( + t, testVector.TxHash, fmt.Sprintf("%x", txHash), + fmt.Sprintf("failed at test vector %d", testIdx), + ) + + // Now we'll serialize the TxFieldSelector and make sure it + // matches the original. + txfs2, err := TxFields.ToBytes() + require.NoError(t, err, "failed to serialize txfs") + + // We'll need to handle the special cases where the expected is + // either empty or the special template selector. + compareTxfs(t, txfs, txfs2) + } + t.Logf("passed %d test vectors", len(testVectors.Vectors)) +} + +// compareTxfs compares two txfs byte slices. It handles the special cases +// where the expected is either empty or the special template selector. +func compareTxfs(t *testing.T, expected, actual []byte) { + t.Helper() + + // Check if the expected is either empty or the special template + // selector. + if len(expected) == 0 { + if bytes.Equal(actual, TXFSSpecialTemplate[:]) { + actual = []byte{} + } + } else if len(expected) == 1 && expected[0] == 0x00 { + if bytes.Equal(actual, TXFSSpecialAll[:]) { + actual = []byte{0x00} + } + } + + require.Equal(t, expected, actual) +} + +type TestVectorFile []TestVectors + +// TestVectors is a struct that represents the test vectors for the +// CalculateTxHash tests. +type TestVectors struct { + Prevs []string `json:"prevs"` + Tx string `json:"tx"` + Vectors []struct { + Input uint32 `json:"input"` + Txfs string `json:"txfs"` + TxHash string `json:"txhash"` + } `json:"vectors"` +} + +// parseTestVectorsFromFile parses the test vectors from a file. +func parseTestVectorsFromFile(t *testing.T, filename string) TestVectors { + t.Helper() + + fileContents, err := os.ReadFile(filename) + require.NoError(t, err) + + var testVectors TestVectorFile + err = json.Unmarshal(fileContents, &testVectors) + + require.NoError(t, err) + + return testVectors[0] +} + +// decodeHex decodes a hex string into a byte slice. +func decodeHex(t *testing.T, hexStr string) []byte { + decoded, err := hex.DecodeString(hexStr) + require.NoError(t, err) + + return decoded +} From 181c2514acacf95994d9cebb96f446b14ffe9a26 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Sun, 24 Dec 2023 13:05:50 +0100 Subject: [PATCH 2/7] txscript: add OP_TXHASH and OP_CHECKTXHASHVERIFY This commit adds two new opcodes, OP_TXHASH and OP_CHECKTXHASHVERIFY. These opcodes replace the OP_NOP4 and OP_UNKNOWN189 opcodes that were previously reserved for future use. --- txscript/engine.go | 4 ++ txscript/error.go | 6 +++ txscript/error_test.go | 1 + txscript/opcode.go | 117 +++++++++++++++++++++++++++++++++++++--- txscript/opcode_test.go | 18 ++++++- 5 files changed, 139 insertions(+), 7 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 30206152b8..ae256d32dd 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -114,6 +114,10 @@ const ( // ScriptVerifyDiscourageUpgradeablePubkeyType defines if unknown // public key versions (during tapscript execution) is non-standard. ScriptVerifyDiscourageUpgradeablePubkeyType + + // ScriptVerifyTxHash defines whether or not to support the OP_TxHash + // and OP_TxHashVerify op codes. + ScriptVerifyTxHash ) const ( diff --git a/txscript/error.go b/txscript/error.go index 1f046b9612..95287932d6 100644 --- a/txscript/error.go +++ b/txscript/error.go @@ -141,6 +141,11 @@ const ( // evaluate to true. ErrCheckMultiSigVerify + // ErrCheckTxHashVerify is returned when OP_CHECKTXHASHVERIFY is + // encountered in a script and the TxHash at the top of the stack does + // not match the calculated TxHash of the transaction being verified. + ErrCheckTxHashVerify + // -------------------------------------------- // Failures related to improper use of opcodes. // -------------------------------------------- @@ -440,6 +445,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrNumEqualVerify: "ErrNumEqualVerify", ErrCheckSigVerify: "ErrCheckSigVerify", ErrCheckMultiSigVerify: "ErrCheckMultiSigVerify", + ErrCheckTxHashVerify: "ErrCheckTxHashVerify", ErrDisabledOpcode: "ErrDisabledOpcode", ErrReservedOpcode: "ErrReservedOpcode", ErrMalformedPush: "ErrMalformedPush", diff --git a/txscript/error_test.go b/txscript/error_test.go index accdf11a8c..3f6b1aed0e 100644 --- a/txscript/error_test.go +++ b/txscript/error_test.go @@ -96,6 +96,7 @@ func TestErrorCodeStringer(t *testing.T) { {ErrInvalidTaprootSigLen, "ErrInvalidTaprootSigLen"}, {ErrTaprootPubkeyIsEmpty, "ErrTaprootPubkeyIsEmpty"}, {ErrTaprootMaxSigOps, "ErrTaprootMaxSigOps"}, + {ErrCheckTxHashVerify, "ErrCheckTxHashVerify"}, {0xffff, "Unknown ErrorCode (65535)"}, } diff --git a/txscript/opcode.go b/txscript/opcode.go index 4918b991c5..42a9441843 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -220,7 +220,7 @@ const ( OP_CHECKLOCKTIMEVERIFY = 0xb1 // 177 - AKA OP_NOP2 OP_NOP3 = 0xb2 // 178 OP_CHECKSEQUENCEVERIFY = 0xb2 // 178 - AKA OP_NOP3 - OP_NOP4 = 0xb3 // 179 + OP_CHECKTXHASHVERIFY = 0xb3 // 179 - AKA OP_NOP4 OP_NOP5 = 0xb4 // 180 OP_NOP6 = 0xb5 // 181 OP_NOP7 = 0xb6 // 182 @@ -230,7 +230,7 @@ const ( OP_CHECKSIGADD = 0xba // 186 OP_UNKNOWN187 = 0xbb // 187 OP_UNKNOWN188 = 0xbc // 188 - OP_UNKNOWN189 = 0xbd // 189 + OP_TXHASH = 0xbd // 189 OP_UNKNOWN190 = 0xbe // 190 OP_UNKNOWN191 = 0xbf // 191 OP_UNKNOWN192 = 0xc0 // 192 @@ -503,9 +503,12 @@ var opcodeArray = [256]opcode{ OP_CHECKMULTISIGVERIFY: {OP_CHECKMULTISIGVERIFY, "OP_CHECKMULTISIGVERIFY", 1, opcodeCheckMultiSigVerify}, OP_CHECKSIGADD: {OP_CHECKSIGADD, "OP_CHECKSIGADD", 1, opcodeCheckSigAdd}, + // TxHash opcodes. + OP_CHECKTXHASHVERIFY: {OP_CHECKTXHASHVERIFY, "OP_CHECKTXHASHVERIFY", 1, opcodeCheckTxHashVerify}, + OP_TXHASH: {OP_TXHASH, "OP_TXHASH", 1, opcodeTxHash}, + // Reserved opcodes. OP_NOP1: {OP_NOP1, "OP_NOP1", 1, opcodeNop}, - OP_NOP4: {OP_NOP4, "OP_NOP4", 1, opcodeNop}, OP_NOP5: {OP_NOP5, "OP_NOP5", 1, opcodeNop}, OP_NOP6: {OP_NOP6, "OP_NOP6", 1, opcodeNop}, OP_NOP7: {OP_NOP7, "OP_NOP7", 1, opcodeNop}, @@ -516,7 +519,6 @@ var opcodeArray = [256]opcode{ // Undefined opcodes. OP_UNKNOWN187: {OP_UNKNOWN187, "OP_UNKNOWN187", 1, opcodeInvalid}, OP_UNKNOWN188: {OP_UNKNOWN188, "OP_UNKNOWN188", 1, opcodeInvalid}, - OP_UNKNOWN189: {OP_UNKNOWN189, "OP_UNKNOWN189", 1, opcodeInvalid}, OP_UNKNOWN190: {OP_UNKNOWN190, "OP_UNKNOWN190", 1, opcodeInvalid}, OP_UNKNOWN191: {OP_UNKNOWN191, "OP_UNKNOWN191", 1, opcodeInvalid}, OP_UNKNOWN192: {OP_UNKNOWN192, "OP_UNKNOWN192", 1, opcodeInvalid}, @@ -638,7 +640,6 @@ var successOpcodes = map[byte]struct{}{ OP_RSHIFT: {}, // 153 OP_UNKNOWN187: {}, // 187 OP_UNKNOWN188: {}, // 188 - OP_UNKNOWN189: {}, // 189 OP_UNKNOWN190: {}, // 190 OP_UNKNOWN191: {}, // 191 OP_UNKNOWN192: {}, // 192 @@ -819,7 +820,7 @@ func opcodeN(op *opcode, data []byte, vm *Engine) error { // the flag to discourage use of NOPs is set for select opcodes. func opcodeNop(op *opcode, data []byte, vm *Engine) error { switch op.value { - case OP_NOP1, OP_NOP4, OP_NOP5, + case OP_NOP1, OP_NOP5, OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10: if vm.hasFlag(ScriptDiscourageUpgradableNops) { @@ -2452,6 +2453,110 @@ func opcodeCheckMultiSigVerify(op *opcode, data []byte, vm *Engine) error { return err } +// opcodeCheckTxHashVerify treats the top item of the data stack as a TxHash +// and a TxFieldSelector. It will create TxHash of the transaction at the +// current index using the TxFieldSelector and compare it with the +// provided TxHash. +// +// Stack transformation: [... TxHash+TxFieldSelector] -> [... TxHash+TxFieldSelector] +func opcodeCheckTxHashVerify(op *opcode, data []byte, vm *Engine) error { + // If the flag is not enabled treat it as a NOP. + if !vm.hasFlag(ScriptVerifyTxHash) { + return nil + } + + if vm.dstack.Depth() < 1 { + str := fmt.Sprintf("stack has %d items, not enough to "+ + "execute OP_CHECKTXHASHVERIFY", vm.dstack.Depth()) + return scriptError(ErrInvalidStackOperation, str) + } + + so, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + + // Check if the stack top element is at least 32 bytes. + if len(so) < 32 { + str := fmt.Sprintf("stack top element has %d bytes, "+ + "must be at least 32", len(so)) + return scriptError(ErrInvalidStackOperation, str) + } + + // The first 32 bytes of the stack are interpreted as a tx hash. + // The remaining bytes specify the TxFieldSelector. + txHash := so[:32] + txfs, err := NewTxFieldSelectorFromBytes(so[32:]) + if err != nil { + return scriptError(ErrInternal, err.Error()) + } + + // Calculate the actual TxHash of the transaction at the current index. + txHashActual, err := txfs.GetTxHash( + &vm.tx, uint32(vm.txIdx), vm.prevOutFetcher, + uint32(vm.lastCodeSep), + ) + if err != nil { + return scriptError(ErrInternal, err.Error()) + } + + // Check if the TxHash matches the expected TxHash. + if !bytes.Equal(txHash, txHashActual) { + str := fmt.Sprintf("TxHash mismatch: expected %x, got %x", + txHash, txHashActual) + + return scriptError(ErrCheckTxHashVerify, str) + } + + // Put the item back on the stack. + vm.dstack.PushByteArray(so) + + return nil +} + +// opcodeTxHash treats the top item of the data stack as a TxFieldSelector. +// It will create TxHash of the transaction at the current index using the +// TxFieldSelector and push it to the stack. +// +// Stack transformation: [... TxFieldSelector] -> [... TxHash] +func opcodeTxHash(op *opcode, data []byte, vm *Engine) error { + // If the flag is not enabled treat it as a NOP. + if !vm.hasFlag(ScriptVerifyTxHash) { + return nil + } + + if vm.dstack.Depth() < 1 { + str := fmt.Sprintf("stack has %d items, not enough to "+ + "execute OP_CHECKTXHASHVERIFY", vm.dstack.Depth()) + return scriptError(ErrInvalidStackOperation, str) + } + + so, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + + // Create a TxFieldSelector from the stack top element. + txfs, err := NewTxFieldSelectorFromBytes(so) + if err != nil { + return scriptError(ErrInternal, err.Error()) + } + + // Calculate the TxHash of the transaction at the current index. + txHash, err := txfs.GetTxHash( + &vm.tx, uint32(vm.txIdx), vm.prevOutFetcher, + uint32(vm.lastCodeSep), + ) + if err != nil { + return scriptError(ErrInternal, err.Error()) + } + + // Push the TxHash to the stack. + vm.dstack.PushByteArray(txHash) + + return nil +} + // OpcodeByName is a map that can be used to lookup an opcode by its // human-readable name (OP_CHECKMULTISIG, OP_CHECKSIG, etc). var OpcodeByName = make(map[string]byte) diff --git a/txscript/opcode_test.go b/txscript/opcode_test.go index 15c62907aa..df06ee80b0 100644 --- a/txscript/opcode_test.go +++ b/txscript/opcode_test.go @@ -117,6 +117,9 @@ func TestOpcodeDisasm(t *testing.T) { case 0xb2: // OP_NOP3 is an alias of OP_CHECKSEQUENCEVERIFY expectedStr = "OP_CHECKSEQUENCEVERIFY" + case 0xb3: + // OP_NOP4 is an alias for OP_OP_CHECKTXHASHVERIFY + expectedStr = "OP_CHECKTXHASHVERIFY" default: val := byte(opcodeVal - (0xb0 - 1)) expectedStr = "OP_NOP" + strconv.Itoa(int(val)) @@ -124,7 +127,14 @@ func TestOpcodeDisasm(t *testing.T) { // OP_UNKNOWN#. case opcodeVal >= 0xbb && opcodeVal <= 0xf9 || opcodeVal == 0xfc: - expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) + switch opcodeVal { + // OP_UNKOWN189 a.k.a 0xbd is now OP_TXHASH. + case 0xbd: + expectedStr = "OP_TXHASH" + + default: + expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) + } } var buf strings.Builder @@ -184,6 +194,9 @@ func TestOpcodeDisasm(t *testing.T) { case 0xb2: // OP_NOP3 is an alias of OP_CHECKSEQUENCEVERIFY expectedStr = "OP_CHECKSEQUENCEVERIFY" + case 0xb3: + // OP_NOP4 is an alias for OP_OP_CHECKTXHASHVERIFY + expectedStr = "OP_CHECKTXHASHVERIFY" default: val := byte(opcodeVal - (0xb0 - 1)) expectedStr = "OP_NOP" + strconv.Itoa(int(val)) @@ -195,6 +208,9 @@ func TestOpcodeDisasm(t *testing.T) { // OP_UNKNOWN186 a.k.a 0xba is now OP_CHECKSIGADD. case 0xba: expectedStr = "OP_CHECKSIGADD" + // OP_UNKNOWN189 a.k.a 0xbd is now OP_TXHASH. + case 0xbd: + expectedStr = "OP_TXHASH" default: expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) } From d8351fb5ea187be41637a104e94620b1a1767033 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Sun, 24 Dec 2023 13:05:59 +0100 Subject: [PATCH 3/7] txscript: add OP_CHECKTXHASHVERIFY example This commit adds a simple example of how to use the new OP_CHECKTXHASHVERIFY opcode added in the previous commit. The example shows how to create a transaction that is restricted to spend to two specific outputs. --- txscript/txhash_test.go | 177 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/txscript/txhash_test.go b/txscript/txhash_test.go index ff08960396..73981f3623 100644 --- a/txscript/txhash_test.go +++ b/txscript/txhash_test.go @@ -2,12 +2,16 @@ package txscript import ( "bytes" + "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "os" "testing" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) @@ -98,6 +102,179 @@ func compareTxfs(t *testing.T, expected, actual []byte) { require.Equal(t, expected, actual) } +// This example demonstrates creating a transaction that spends to a +// OP_TXHASHVERIFY output that constraints the transaction to a template +// commiting to 2 outputs along with their values. +func TestExampleTx(t *testing.T) { + var ( + txValue = int64(5000) + feeValue = int64(1000) + ) + + // Get 2 addresses we will restrict the output to be spent to. + addr1 := getAddress(t) + pkscript1, err := PayToAddrScript(addr1) + require.NoError(t, err) + + addr2 := getAddress(t) + pkscript2, err := PayToAddrScript(addr2) + require.NoError(t, err) + + // Create the tx template we're commiting to. + txTemplate := wire.NewMsgTx(wire.TxVersion) + txTemplate.AddTxOut(&wire.TxOut{ + Value: txValue, + PkScript: pkscript1, + }) + txTemplate.AddTxOut(&wire.TxOut{ + Value: txValue, + PkScript: pkscript2, + }) + + // We'll create the TxFieldSelector that will be used to generate and + // verify the OP_TXHASHVERIFY output. We'll fully commit to the + // output number, scriptPubkey and value. + outputSelector := &OutputSelector{ + CommitNumber: true, + ScriptPubkeys: true, + Values: true, + + InOutSelector: &InOutSelectorAll{}, + } + + txFieldSelector := TxFieldSelector{ + Control: true, + Version: true, + Outputs: outputSelector, + } + txfs, err := txFieldSelector.ToBytes() + require.NoError(t, err) + + // We'll now create the transaction that will spend to the + // OP_TXHASHVERIFY output. + tx := wire.NewMsgTx(wire.TxVersion) + + // Add a fake input to the transaction. + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: sha256.Sum256([]byte{1, 2, 3}), + Index: 0, + }}) + + // Now we'll create the TxHash that the OP_TXHASHVERIFY output will + // require. + txHash, err := txFieldSelector.GetTxHash(txTemplate, 0, nil, 0) + require.NoError(t, err) + + txHashScript, err := genTxHashScript(txHash, txfs) + require.NoError(t, err) + + // This prevout fetcher will ensure that the engine checks against + // the txHashScript we created above. + prevoutFetcher := NewCannedPrevOutputFetcher( + txHashScript, txValue*2+feeValue, + ) + + // Add the outputs as defined in the template + for _, txOut := range txTemplate.TxOut { + tx.AddTxOut(txOut) + } + + sigHashes := NewTxSigHashes(tx, prevoutFetcher) + + // Now we can define some test cases that we will test. + testCases := []struct { + // The flags we want the engine to run with. + verifyFlags ScriptFlags + // The expected error we should get. + expectedErrCode ErrorCode + // The value of the first output. + modifiedValueTxOut1 int64 + }{ + + { + // This tests the case where we have the + // ScriptVerifyTxHash flag set. + verifyFlags: StandardVerifyFlags | ScriptVerifyTxHash, + }, + { + // We'll modify the value of the first output to + // generate a different tx hash and thus fail the + // opcode verification. + verifyFlags: StandardVerifyFlags | ScriptVerifyTxHash, + expectedErrCode: ErrCheckTxHashVerify, + modifiedValueTxOut1: txValue - 1, + }, + { + // This tests the case where we don't have the + // ScriptVerifyTxHash flag set, the opcode should + // succeed. + verifyFlags: StandardVerifyFlags, + modifiedValueTxOut1: txValue - 1, + }, + } + + for _, testCase := range testCases { + // We'll modify the value of the first output if needed. + if testCase.modifiedValueTxOut1 != 0 { + tx.TxOut[0].Value = testCase.modifiedValueTxOut1 + } + + // Create a new engine with the ScriptVerifyTxHash flag set. + engine, err := NewEngine( + txHashScript, tx, 0, testCase.verifyFlags, nil, + sigHashes, txValue+feeValue, prevoutFetcher, + ) + require.NoError(t, err) + + err = engine.Execute() + if testCase.expectedErrCode != 0 { + // We expect an error, so make sure it's the one we + // expect. + require.Error(t, err) + require.Equal( + t, testCase.expectedErrCode, + err.(Error).ErrorCode, + ) + } else { + require.NoError(t, err) + } + } +} + +// genTxHashScript generates a script that will require the tx hash to be +// committed to in the transaction. +func genTxHashScript(txhash, txfs []byte) ([]byte, error) { + builder := NewScriptBuilder() + + builder.AddData(append(txhash, txfs...)) + builder.AddOp(OP_CHECKTXHASHVERIFY) + + return builder.Script() +} + +// getAddress returns a new address. +func getAddress(t *testing.T) btcutil.Address { + // Ordinarily the private key would come from whatever storage mechanism + // is being used, but for this example just hard code it. + privKeyBytes, err := hex.DecodeString("22a47fa09a223f2aa079edf85a7c2" + + "d4f8720ee63e502ee2869afab7de234b80c") + if err != nil { + fmt.Println(err) + return nil + } + _, pubKey := btcec.PrivKeyFromBytes(privKeyBytes) + pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed()) + addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, + &chaincfg.MainNetParams) + if err != nil { + fmt.Println(err) + return nil + } + + return addr +} + type TestVectorFile []TestVectors // TestVectors is a struct that represents the test vectors for the From 931afa7e959f3adb677413a5a0398a1adcb87e86 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Sun, 24 Dec 2023 13:23:30 +0100 Subject: [PATCH 4/7] (tmp) tsxcript: remove NOP4 script_tests This commit removes the NOP4 stuff from the script_tests.json file in order for the test to succeed. This commit will be changed to reflect expected OP_TXHASH changes. --- txscript/data/script_tests.json | 12 ++++-------- .../2b229e9febea1749971fa08007b52b574b3046fb | 1 - .../40198d56b45470dd3064ec201e05ca4e3cdca930 | 1 - .../63e781018cd701d6d445dc7c4ea28f9a53de825f | 1 - .../b085cb29b4ab162f71d870e1b8f890fd0cf23cd6 | 1 - .../bb0848b612e67f32638209d33374606f9f878516 | 1 - .../e0588caa737bfbd960ff3fd219ca062193aa6bf0 | 1 - .../f9fc9e2a4f2af623fc883937402f30bb39d7194c | 1 - .../fb3feed4650211ed4fd84c29e022288219979e4a | 1 - 9 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 txscript/data/taproot-ref/2b229e9febea1749971fa08007b52b574b3046fb delete mode 100644 txscript/data/taproot-ref/40198d56b45470dd3064ec201e05ca4e3cdca930 delete mode 100644 txscript/data/taproot-ref/63e781018cd701d6d445dc7c4ea28f9a53de825f delete mode 100644 txscript/data/taproot-ref/b085cb29b4ab162f71d870e1b8f890fd0cf23cd6 delete mode 100644 txscript/data/taproot-ref/bb0848b612e67f32638209d33374606f9f878516 delete mode 100644 txscript/data/taproot-ref/e0588caa737bfbd960ff3fd219ca062193aa6bf0 delete mode 100644 txscript/data/taproot-ref/f9fc9e2a4f2af623fc883937402f30bb39d7194c delete mode 100644 txscript/data/taproot-ref/fb3feed4650211ed4fd84c29e022288219979e4a diff --git a/txscript/data/script_tests.json b/txscript/data/script_tests.json index 5c054ed3e8..7fd500eac9 100644 --- a/txscript/data/script_tests.json +++ b/txscript/data/script_tests.json @@ -232,8 +232,8 @@ ["'abcdefghijklmnopqrstuvwxyz'", "HASH256 0x4c 0x20 0xca139bc10c2f660da42666f72e89a225936fc60f193c161124a672050c434671 EQUAL", "P2SH,STRICTENC", "OK"], -["1","NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL", "P2SH,STRICTENC", "OK"], -["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_10' EQUAL", "P2SH,STRICTENC", "OK"], +["1","NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL", "P2SH,STRICTENC", "OK"], +["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_10' EQUAL", "P2SH,STRICTENC", "OK"], ["1", "NOP", "P2SH,STRICTENC,DISCOURAGE_UPGRADABLE_NOPS", "OK", "Discourage NOPx flag allows OP_NOP"], @@ -243,7 +243,6 @@ ["0", "IF 0xba ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes above NOP10 invalid if executed"], ["0", "IF 0xbb ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xbc ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], -["0", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xbf ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xc0 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], @@ -444,7 +443,6 @@ ["NOP", "NOP1 1", "P2SH,STRICTENC", "OK"], ["NOP", "CHECKLOCKTIMEVERIFY 1", "P2SH,STRICTENC", "OK"], ["NOP", "CHECKSEQUENCEVERIFY 1", "P2SH,STRICTENC", "OK"], -["NOP", "NOP4 1", "P2SH,STRICTENC", "OK"], ["NOP", "NOP5 1", "P2SH,STRICTENC", "OK"], ["NOP", "NOP6 1", "P2SH,STRICTENC", "OK"], ["NOP", "NOP7 1", "P2SH,STRICTENC", "OK"], @@ -857,14 +855,13 @@ ["2 2 LSHIFT", "8 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], ["2 1 RSHIFT", "1 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], -["1", "NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 2 EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"], -["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP4 NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_11' EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"], +["1", "NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 2 EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"], +["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_11' EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"], ["Ensure 100% coverage of discouraged NOPS"], ["1", "NOP1", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "CHECKLOCKTIMEVERIFY", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "CHECKSEQUENCEVERIFY", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], -["1", "NOP4", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP5", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP6", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], ["1", "NOP7", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"], @@ -881,7 +878,6 @@ ["1", "IF 0xba ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "opcodes above NOP10 invalid if executed"], ["1", "IF 0xbb ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xbc ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], -["1", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xbf ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xc0 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], diff --git a/txscript/data/taproot-ref/2b229e9febea1749971fa08007b52b574b3046fb b/txscript/data/taproot-ref/2b229e9febea1749971fa08007b52b574b3046fb deleted file mode 100644 index d1840f1a19..0000000000 --- a/txscript/data/taproot-ref/2b229e9febea1749971fa08007b52b574b3046fb +++ /dev/null @@ -1 +0,0 @@ -{"tx": "b354418b02dceb5f5568f8ada45d428630f512fb8efacd46682b4367b4edaf1985c5e4af4b2b01000000785719ab60f8b8616e71e7ed05613145ce7cda782ac9861e64f9ce24e333ca1e91d91270d801000000a030ebbf032d37300000000000160014619b982e9f6832d2edb1a1ee4e7656a8d72c65e758020000000000001600149d38710eb90e420b159c7a9263994c88e6810bc758020000000000001976a914f9cfef42654b8e1307276f4274b9e35435f17e8d88ac35050000", "prevouts": ["1c06210000000000225120c08dce064ec071eea1f5304817c49a2bfdceb360f072491bf2d2a32ed65843b9", "72e9100000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e"], "index": 1, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/1001inputs", "success": {"scriptSig": "", "witness": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "bd", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9366d6822c3ab459532077d5f4bfcf7544c522d220251729d5888eecbf9f185531198751320860179e53b82a877a47edb7ce4c17ae8ab38dd25c39273bf19ccb7d50e634e19498d3396bfa452af2ece499faa564dc4b58fae514f4ede8dd179fb909e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}, "failure": {"scriptSig": "", "witness": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "61", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9361ef6944ca9eae19b43c4423072484bbc3f643e0e770f53f8e7474c81f5d900450cffa7efd13876b56a4fb6d16fe87f2b3bb25d39f5e6fb1dfb5ce04c0283c8690e634e19498d3396bfa452af2ece499faa564dc4b58fae514f4ede8dd179fb909e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}}, diff --git a/txscript/data/taproot-ref/40198d56b45470dd3064ec201e05ca4e3cdca930 b/txscript/data/taproot-ref/40198d56b45470dd3064ec201e05ca4e3cdca930 deleted file mode 100644 index fc7ceb79bd..0000000000 --- a/txscript/data/taproot-ref/40198d56b45470dd3064ec201e05ca4e3cdca930 +++ /dev/null @@ -1 +0,0 @@ -{"tx": "db1a6b1b02bcb2054607a921b3c6df992a9486776863b28485e731a805931b6feb14221acfc300000000730c61b260f8b8616e71e7ed05613145ce7cda782ac9861e64f9ce24e333ca1e91d9127017000000003c7d6ccc044728720000000000160014deb4696df95e4685eae8f9ff2e77fc7edabbe2fc580200000000000017a914472b5d2e0c04ba5495728dd81d0885af2587df4787580200000000000016001428425a8aab0a57cd9398c2c78c3d097fe1a397a658020000000000001976a9145dabd582fbdb106f3f7460c03ce83bc27d461d0f88ac09020000", "prevouts": ["5f59630000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e", "986311000000000022512001f97817fc806a0f47072a55dae4866d18cdd8ca9234fe6851c34258ebf487c5"], "index": 0, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/1001push", "success": {"scriptSig": "", "witness": ["0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f93620e1233246ac9a23fcb5b3ba301349c9efee22ef31ffea4e16f8e5d228bda7e148d44b3b8b580a9f727722e46acefe6d67c1e6b80ead7c236fb066c3dd6b0bfbdede6356752267b6a4958657c43b99b93cfd40f762fcdaad4937ef48d6413f31b5843f54915b2c97abdf26ed2d562b36c2375ce95d63af6aa508e6368a687449"]}, "failure": {"scriptSig": "", "witness": ["000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f936a576d03eca93f143feb7e152136a467cbd42f46620dff7de4663782cb270610348d44b3b8b580a9f727722e46acefe6d67c1e6b80ead7c236fb066c3dd6b0bfbdede6356752267b6a4958657c43b99b93cfd40f762fcdaad4937ef48d6413f31b5843f54915b2c97abdf26ed2d562b36c2375ce95d63af6aa508e6368a687449"]}}, diff --git a/txscript/data/taproot-ref/63e781018cd701d6d445dc7c4ea28f9a53de825f b/txscript/data/taproot-ref/63e781018cd701d6d445dc7c4ea28f9a53de825f deleted file mode 100644 index 6c4791f121..0000000000 --- a/txscript/data/taproot-ref/63e781018cd701d6d445dc7c4ea28f9a53de825f +++ /dev/null @@ -1 +0,0 @@ -{"tx": "02000000028bd9b9012d1e9d0bc9c34df9d487a1d5663f1b37dbd4a857a2bddcbe25f0d0c4050100000060d5b89860f8b8616e71e7ed05613145ce7cda782ac9861e64f9ce24e333ca1e91d91270c5000000004169b1a9047cec4d000000000017a914f017945d4d088c7d42ab3bcbc1adce51d74fbd9f87580200000000000017a9141d5a2c690c3e2dacb3cead240f0ce4a273b9d0e487580200000000000017a914472b5d2e0c04ba5495728dd81d0885af2587df478758020000000000001976a914f9cfef42654b8e1307276f4274b9e35435f17e8d88acecddd42f", "prevouts": ["b7923d0000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e", "523413000000000022512097f3f32bbea7bd397ebd6824dc6e34758f0b169a6c237662287beed33756fea6"], "index": 0, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/undecodable_bypass", "success": {"scriptSig": "", "witness": ["bd4c", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f936f8a7004a68fdc05e100340c712f74a3d62667ca0b5d0eafc5e716949571fe18725432b67bf7a212872373c5ea5ac6512ad650fe3d5c26e1d584bcbdba0083b9a9e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}, "failure": {"scriptSig": "", "witness": ["4c52bd", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9363dcaea11c13b9b36db1e711f26b17d850c4278eabda72c0bd6c73a20768b7a5e98751320860179e53b82a877a47edb7ce4c17ae8ab38dd25c39273bf19ccb7d50e634e19498d3396bfa452af2ece499faa564dc4b58fae514f4ede8dd179fb909e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}}, diff --git a/txscript/data/taproot-ref/b085cb29b4ab162f71d870e1b8f890fd0cf23cd6 b/txscript/data/taproot-ref/b085cb29b4ab162f71d870e1b8f890fd0cf23cd6 deleted file mode 100644 index a2a88ca800..0000000000 --- a/txscript/data/taproot-ref/b085cb29b4ab162f71d870e1b8f890fd0cf23cd6 +++ /dev/null @@ -1 +0,0 @@ -{"tx": "02000000028bd9b9012d1e9d0bc9c34df9d487a1d5663f1b37dbd4a857a2bddcbe25f0d0c4c0000000003bd730a360f8b8616e71e7ed05613145ce7cda782ac9861e64f9ce24e333ca1e91d91270b701000000ad8a8be30263f44c000000000017a9141d5a2c690c3e2dacb3cead240f0ce4a273b9d0e487580200000000000017a9148f07d0f98cfe0d6aff29ca20bcda3fa93083937487deff4536", "prevouts": ["9cd63d00000000002251200fcaedfb972c31a562a88e2127675cb61d773b6b9ce4a4a9159012ab236e47b8", "09b9110000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e"], "index": 1, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/bigpush", "success": {"scriptSig": "", "witness": ["4d0902addd150bdc5faa9073d3026a89ee3a83638e006b85fce8df78ff3a5342cac9f0f8ffd4979aefbafd524a3800293ad76a76b34f84dd4a3b78e536c86aa056d764787fb984bc77e060196cb9ac369affdf5eb3c2d1fab5307950ec3877075c0dfe6d877d3c1ddf55f09d924619eccf87c0dae4f4f995d57266fa5445a49e10e2cf39b3f548235c4378cc21fcc3c356341e0910c9b811c55dccef17788d7471e063b0cacf18d805d597ded2fe81e4114d6ffb90f29f13e369154e376bd9b7a050c4f41f9765666a8188490caac683cfc4d9a20bc8fbbd34be1826c4d8f5550275e4aece9ba372f149145022c8fc3ea9b71074e287776cb455b841bd4059b4231ff30d93e94d36f311ec69cd967f062a51ccbb7fcac7a533b4518151b54a9fb619e17f4a25d8d4356c43e5f485bf335d710a825b5ba74f47eee53d14faffa2ebff59f31c99068735ed14c9a22781016f4992415590a7e0472a30d30b117d57ae33a3b0c40ca1cd16c1b5aa1541444136bfecd46add624d95abb5dcde73b39ade4b2db82b5793a14da1cca41e2918deb5833e4f256d7c6f200b1688bfa2ed4fb8a9d45c2468ec32f5b069a56f266923242b2a98c93f847b97053a295a325d4799002013e88d0dfd378efcc9539fdd077c66998e0a443916cf50d71b24c9a54e43a6d6cef56c9b47b5344682466b0d8ae2f7b7b0c92255abc5db2f8e49a86fce194ce78b8170728670c23c5e75bd", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9361ef20d31d73af45144650ecda4bfa65e941320968360f95ac17d612a18851d4fdede6356752267b6a4958657c43b99b93cfd40f762fcdaad4937ef48d6413f31b5843f54915b2c97abdf26ed2d562b36c2375ce95d63af6aa508e6368a687449"]}, "failure": {"scriptSig": "", "witness": ["4d09027739f2ea80564eb4ff7bb1ceb524c01579510d94e3a290a69b3399a812039b89574afbdb86bcba9fa0f516082b61e41e4668a2cb9715a2a7052421104434edd8b75d398c3bb2a00a41e25a55349cdf6b1a354082d4fff8a1472ca2fdc6b302672807a635887d1e562a935edbe0de04a1aa37830170c07e3cf04f9b607545289aab146d87e481f5c67456f75e8c59dbe39cfbc19b4290b296a077e7db0a542e9c3a5aed74a8b306d183b491e8dd4d696e7c4683a95b619406f5522705671d6da9f8c92646e84fdc5b3dfe20aa68bf45da12ed9c418e5825d64726ee64e4c6bef2d3b03d92ffb5d5cccce2ed73d91b6198bd6149fbf490fc668f1627c43420d12af28257b1f78e4a1ca224bea36c0b684821c253ef37eff25ad911b600a94ff4330aa62ac6ce23b4b569d639b2e9654d651b1f1392a6d049b39682feeeb401bc4c9ad33ee702d507f36d54ff846d5885ba81d4fd599776ceb049ad3d43c0f1aef9e99059b965db1181c794eecd57101a2b1f5fb741da4e1253d7b60ffe29d7b8dced3a7940f9a9070688654322f70df2bf54af903858172d05571d590f31fe7e785512b997c1f444d5acceec7734c6ad8a1ffeae1bb4c956252fb86bb8f92446f7d8e80ff9ac0a5b76e425a75cc1a20071d66485428ff11d9e07fe7a9c93f73fb22c023e512704b75713bcdee57e6b38ecd6ee32e66a4878e331560a157e4a5d2d76057a33ef15d592c27561", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f936d300bab14a59e521ba24782c2f2145d4a7429c0cdd6ce75f9b04f76a9e5b4d51631743d48971d1733c6ca7857843602fffe2e4122fe98dc3fa85acbd6da797d181cd61fd18311004a5536d1440b72b537197adb3a0d17581cb4a1679e89097edb5843f54915b2c97abdf26ed2d562b36c2375ce95d63af6aa508e6368a687449"]}}, diff --git a/txscript/data/taproot-ref/bb0848b612e67f32638209d33374606f9f878516 b/txscript/data/taproot-ref/bb0848b612e67f32638209d33374606f9f878516 deleted file mode 100644 index b415999820..0000000000 --- a/txscript/data/taproot-ref/bb0848b612e67f32638209d33374606f9f878516 +++ /dev/null @@ -1 +0,0 @@ -{"tx": "0100000002dff9d694a434b13abfbbd618e2ece4460f24b4821cf47d5afc481a386c59565cd801000000872a088c8bd9b9012d1e9d0bc9c34df9d487a1d5663f1b37dbd4a857a2bddcbe25f0d0c418010000008af1d13301eb745300000000001976a91401f109af244d8c7f2563284ac2d2ba7d6323a75e88ac3d95c32e", "prevouts": ["a1705a0000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e", "2fb636000000000022512099a26739d97cb47a5f7edeeb47465139706da2fc4352eb812a3e381cc2e19a92"], "index": 0, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/bare", "success": {"scriptSig": "", "witness": ["bd", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9366d6822c3ab459532077d5f4bfcf7544c522d220251729d5888eecbf9f185531198751320860179e53b82a877a47edb7ce4c17ae8ab38dd25c39273bf19ccb7d50e634e19498d3396bfa452af2ece499faa564dc4b58fae514f4ede8dd179fb909e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}, "failure": {"scriptSig": "", "witness": ["61", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9361ef6944ca9eae19b43c4423072484bbc3f643e0e770f53f8e7474c81f5d900450cffa7efd13876b56a4fb6d16fe87f2b3bb25d39f5e6fb1dfb5ce04c0283c8690e634e19498d3396bfa452af2ece499faa564dc4b58fae514f4ede8dd179fb909e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}}, diff --git a/txscript/data/taproot-ref/e0588caa737bfbd960ff3fd219ca062193aa6bf0 b/txscript/data/taproot-ref/e0588caa737bfbd960ff3fd219ca062193aa6bf0 deleted file mode 100644 index a4128d3133..0000000000 --- a/txscript/data/taproot-ref/e0588caa737bfbd960ff3fd219ca062193aa6bf0 +++ /dev/null @@ -1 +0,0 @@ -{"tx": "67281e5e02bcb2054607a921b3c6df992a9486776863b28485e731a805931b6feb14221acfea000000004aacbe908bd9b9012d1e9d0bc9c34df9d487a1d5663f1b37dbd4a857a2bddcbe25f0d0c42200000000a23368a302d754a3000000000017a9148f07d0f98cfe0d6aff29ca20bcda3fa93083937487580200000000000017a914719f78084af863e000acd618ba76df97972236898740461b2f", "prevouts": ["a9f1670000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e", "032d3e0000000000225120860597d3b29a47949c68e53703a7c358236fede9036ee1439f49b54ea72cb70b"], "index": 0, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/return", "success": {"scriptSig": "", "witness": ["6abd", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f93699aaf103cceb41d9bc37ec231aca89b984b5fd3c65977ce764d51033ac65adb45bc79ec207f4553f17b4d8afbf0e47b02e8cf3ab2b0172732171fcb0f92ff87125432b67bf7a212872373c5ea5ac6512ad650fe3d5c26e1d584bcbdba0083b9a9e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}, "failure": {"scriptSig": "", "witness": ["6a61", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f936effe4d48aab6fcce879b221d7012ab600ee1e9c541d61044892151855353df93631743d48971d1733c6ca7857843602fffe2e4122fe98dc3fa85acbd6da797d181cd61fd18311004a5536d1440b72b537197adb3a0d17581cb4a1679e89097edb5843f54915b2c97abdf26ed2d562b36c2375ce95d63af6aa508e6368a687449"]}}, diff --git a/txscript/data/taproot-ref/f9fc9e2a4f2af623fc883937402f30bb39d7194c b/txscript/data/taproot-ref/f9fc9e2a4f2af623fc883937402f30bb39d7194c deleted file mode 100644 index 9b941588da..0000000000 --- a/txscript/data/taproot-ref/f9fc9e2a4f2af623fc883937402f30bb39d7194c +++ /dev/null @@ -1 +0,0 @@ -{"tx": "020000000260f8b8616e71e7ed05613145ce7cda782ac9861e64f9ce24e333ca1e91d91270240100000016d9ce96dceb5f5568f8ada45d428630f512fb8efacd46682b4367b4edaf1985c5e4af4b740100000085d2d2f90174352f00000000001976a91490770ceff2b1c32e9dbf952fbe65b04a54d1949388acc1040000", "prevouts": ["3fde11000000000017a9149d4bcb1ed806c9beed692a78614f8b90a68c708187", "7903270000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e"], "index": 1, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/unexecif", "success": {"scriptSig": "", "witness": ["0063bd68", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9364beac8453c08a82879ff5e72e60d02b43bb4030aabb448d6315e82d153ff340281cd61fd18311004a5536d1440b72b537197adb3a0d17581cb4a1679e89097edb5843f54915b2c97abdf26ed2d562b36c2375ce95d63af6aa508e6368a687449"]}, "failure": {"scriptSig": "", "witness": ["00636168", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f9363f7be6f8848b5bddf332c4d7bd83077f73701e2479f70e02b5730e841234d0820cffa7efd13876b56a4fb6d16fe87f2b3bb25d39f5e6fb1dfb5ce04c0283c8690e634e19498d3396bfa452af2ece499faa564dc4b58fae514f4ede8dd179fb909e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}}, diff --git a/txscript/data/taproot-ref/fb3feed4650211ed4fd84c29e022288219979e4a b/txscript/data/taproot-ref/fb3feed4650211ed4fd84c29e022288219979e4a deleted file mode 100644 index c6808f089b..0000000000 --- a/txscript/data/taproot-ref/fb3feed4650211ed4fd84c29e022288219979e4a +++ /dev/null @@ -1 +0,0 @@ -{"tx": "020000000160f8b8616e71e7ed05613145ce7cda782ac9861e64f9ce24e333ca1e91d91270b401000000391043c902e2fa0d00000000001976a914c629d61df58baceae110d15eb5b55e144268615388ac580200000000000017a914472b5d2e0c04ba5495728dd81d0885af2587df478776010000", "prevouts": ["1591100000000000225120703a27ee37b547411791bd0e189100b9b1aab12509c8c95d384d172c3abbca5e"], "index": 0, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "opsuccess/undecodable", "success": {"scriptSig": "", "witness": ["bd4c", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f936f8a7004a68fdc05e100340c712f74a3d62667ca0b5d0eafc5e716949571fe18725432b67bf7a212872373c5ea5ac6512ad650fe3d5c26e1d584bcbdba0083b9a9e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}, "failure": {"scriptSig": "", "witness": ["614c", "c07d732801de7e0c866f2462f29c14b63e555159b62ba93a5d5963d1c04795f93617cd6ee4f8b94dcd19f57a756bcb8a90fe2911c96cfd4cc653cd06b0f9d9556a5bc79ec207f4553f17b4d8afbf0e47b02e8cf3ab2b0172732171fcb0f92ff87125432b67bf7a212872373c5ea5ac6512ad650fe3d5c26e1d584bcbdba0083b9a9e9ba325ae7de51b47d98058ae5f9889bb6f52223c96865cd06dfd05531cc8a0"]}}, From ebdbd3d88efe2fab2dec4431588e75b85a86827a Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Tue, 26 Dec 2023 14:17:17 +0100 Subject: [PATCH 5/7] txscript: implement cache for inputs and outputs --- txscript/opcode.go | 4 +- txscript/txhash.go | 522 ++++++++++++++++++++++++++++++++++------ txscript/txhash_test.go | 77 +++++- 3 files changed, 528 insertions(+), 75 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 42a9441843..62624a6fb5 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -2486,7 +2486,7 @@ func opcodeCheckTxHashVerify(op *opcode, data []byte, vm *Engine) error { // The first 32 bytes of the stack are interpreted as a tx hash. // The remaining bytes specify the TxFieldSelector. txHash := so[:32] - txfs, err := NewTxFieldSelectorFromBytes(so[32:]) + txfs, err := NewTxFieldSelectorFromBytes(so[32:], nil, nil) if err != nil { return scriptError(ErrInternal, err.Error()) } @@ -2537,7 +2537,7 @@ func opcodeTxHash(op *opcode, data []byte, vm *Engine) error { } // Create a TxFieldSelector from the stack top element. - txfs, err := NewTxFieldSelectorFromBytes(so) + txfs, err := NewTxFieldSelectorFromBytes(so, nil, nil) if err != nil { return scriptError(ErrInternal, err.Error()) } diff --git a/txscript/txhash.go b/txscript/txhash.go index 18005e55d0..1d70364a0c 100644 --- a/txscript/txhash.go +++ b/txscript/txhash.go @@ -3,6 +3,7 @@ package txscript import ( "bytes" "encoding/binary" + "encoding/hex" "fmt" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -384,6 +385,10 @@ func inOutSelectorToBytes(selector InOutSelector, // InputSelector is used to get the hash of the inputs of a transaction, as // specified by the TxFieldSelector. type InputSelector struct { + // InputsCache is a cache that can be used to speed up the hashing of + // inputs. + InputsCache *TxfsInputsCache + // CommitNumber is true if the number of inputs should be committed to // the TxHash. CommitNumber bool @@ -418,8 +423,8 @@ type InputSelector struct { } // NewInputSelectorFromBytes creates a new inputSelector from a byte slice. -func NewInputSelectorFromBytes(inoutFields byte, txfs []byte) (*InputSelector, - []byte, error) { +func NewInputSelectorFromBytes(inoutFields byte, txfs []byte, + inputsCache *TxfsInputsCache) (*InputSelector, []byte, error) { inputSelector := &InputSelector{ PrevOuts: inoutFields&TXFSInputsPrevouts != 0, @@ -428,6 +433,7 @@ func NewInputSelectorFromBytes(inoutFields byte, txfs []byte) (*InputSelector, PrevScriptPubkeys: inoutFields&TXFSInputsPrevScriptpubkeys != 0, PrevValues: inoutFields&TXFSInputsPrevValues != 0, TaprootAnnexes: inoutFields&TXFSInputsTaprootAnnexes != 0, + InputsCache: inputsCache, } var err error @@ -463,90 +469,138 @@ func (s *InputSelector) writeInputsHash(txHashBuffer *bytes.Buffer, } if len(inputIndices) > 0 && s.PrevOuts { - var buffer bytes.Buffer - for _, idx := range inputIndices { - wire.WriteOutPoint( - &buffer, 0, tx.Version, - &tx.TxIn[idx].PreviousOutPoint, + // If we have a cache, use it to speed up the hashing. + if s.InputsCache != nil { + bufferHash := s.InputsCache.GetPrevoutHash( + tx, inputIndices, ) + txHashBuffer.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range inputIndices { + wire.WriteOutPoint( + &buffer, 0, tx.Version, + &tx.TxIn[idx].PreviousOutPoint, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - txHashBuffer.Write(bufferHash) } if len(inputIndices) > 0 && s.Sequences { - var buffer bytes.Buffer - for _, idx := range inputIndices { - binary.Write( - &buffer, binary.LittleEndian, - tx.TxIn[idx].Sequence, + // If we have a cache, use it to speed up the hashing. + if s.InputsCache != nil { + bufferHash := s.InputsCache.GetSequenceHash( + tx, inputIndices, ) + txHashBuffer.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write( + &buffer, binary.LittleEndian, + tx.TxIn[idx].Sequence, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - txHashBuffer.Write(bufferHash) } if len(inputIndices) > 0 && s.ScriptSigs { - var buffer bytes.Buffer - for _, idx := range inputIndices { - buffer.Write( - chainhash.HashB(tx.TxIn[idx].SignatureScript), + // If we have a cache, use it to speed up the hashing. + if s.InputsCache != nil { + bufferHash := s.InputsCache.GetScriptSigHash( + tx, inputIndices, ) + txHashBuffer.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range inputIndices { + buffer.Write( + chainhash.HashB(tx.TxIn[idx].SignatureScript), + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - txHashBuffer.Write(bufferHash) } if len(inputIndices) > 0 && s.PrevScriptPubkeys { - var buffer bytes.Buffer - for _, idx := range inputIndices { - prevout := prevoutFetcher.FetchPrevOutput( - tx.TxIn[idx].PreviousOutPoint, + // If we have a cache, use it to speed up the hashing. + if s.InputsCache != nil { + bufferHash := s.InputsCache.GetPrevScriptPubkeyHash( + tx, inputIndices, prevoutFetcher, ) - buffer.Write(chainhash.HashB(prevout.PkScript)) + txHashBuffer.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range inputIndices { + prevout := prevoutFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint, + ) + buffer.Write(chainhash.HashB(prevout.PkScript)) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - txHashBuffer.Write(bufferHash) } if len(inputIndices) > 0 && s.PrevValues { - var buffer bytes.Buffer - for _, idx := range inputIndices { - prevout := prevoutFetcher.FetchPrevOutput( - tx.TxIn[idx].PreviousOutPoint, - ) - binary.Write( - &buffer, binary.LittleEndian, prevout.Value, + // If we have a cache, use it to speed up the hashing. + if s.InputsCache != nil { + bufferHash := s.InputsCache.GetPrevValueHash( + tx, inputIndices, prevoutFetcher, ) + txHashBuffer.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range inputIndices { + prevout := prevoutFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint, + ) + binary.Write( + &buffer, binary.LittleEndian, prevout.Value, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - txHashBuffer.Write(bufferHash) } if len(inputIndices) > 0 && s.TaprootAnnexes { - var buffer bytes.Buffer - for _, idx := range inputIndices { - if IsPayToTaproot(prevoutFetcher.FetchPrevOutput( - tx.TxIn[idx].PreviousOutPoint).PkScript, - ) { - - if isAnnexedWitness(tx.TxIn[idx].Witness) { - annex, err := extractAnnex( - tx.TxIn[idx].Witness, - ) - if err != nil { - return err + // If we have a cache, use it to speed up the hashing. + if s.InputsCache != nil { + bufferHash := s.InputsCache.GetTaprootAnnexHash( + tx, inputIndices, prevoutFetcher, + ) + txHashBuffer.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range inputIndices { + if IsPayToTaproot(prevoutFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint).PkScript, + ) { + + if isAnnexedWitness(tx.TxIn[idx].Witness) { + annex, err := extractAnnex( + tx.TxIn[idx].Witness, + ) + if err != nil { + return err + } + buffer.Write(chainhash.HashB(annex)) + } else { + buffer.Write(chainhash.HashB([]byte{})) } - buffer.Write(chainhash.HashB(annex)) } else { buffer.Write(chainhash.HashB([]byte{})) } - } else { - buffer.Write(chainhash.HashB([]byte{})) } + bufferHash := chainhash.HashB(buffer.Bytes()) + txHashBuffer.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - txHashBuffer.Write(bufferHash) } return nil @@ -555,6 +609,10 @@ func (s *InputSelector) writeInputsHash(txHashBuffer *bytes.Buffer, // OutputSelector is used to get the hash of the outputs of a transaction, as // specified by the TxFieldSelector. type OutputSelector struct { + // OutputsCache is a cache that can be used to speed up the hashing of + // outputs. + OutputsCache *TxfsOutputsCache + // CommitNumber is true if the number of outputs should be committed to // the TxHash. CommitNumber bool @@ -572,12 +630,13 @@ type OutputSelector struct { } // newOutputSelectorFromBytes creates a new outputSelector from a byte slice. -func newOutputSelectorFromBytes(inoutFields byte, txfs []byte) (*OutputSelector, - []byte, error) { +func newOutputSelectorFromBytes(inoutFields byte, txfs []byte, + outputsCache *TxfsOutputsCache) (*OutputSelector, []byte, error) { outputSelector := &OutputSelector{ ScriptPubkeys: inoutFields&TXFSOutputsScriptpubkeys != 0, Values: inoutFields&TXFSOutputsValues != 0, + OutputsCache: outputsCache, } var err error @@ -612,24 +671,38 @@ func (s *OutputSelector) writeOutputsHash(sigMsg *bytes.Buffer, tx *wire.MsgTx, } if len(outputIndices) > 0 && s.ScriptPubkeys { - var buffer bytes.Buffer - for _, idx := range outputIndices { - buffer.Write(chainhash.HashB(tx.TxOut[idx].PkScript)) + if s.OutputsCache != nil { + bufferHash := s.OutputsCache.GetScriptPubkeyHash( + tx, outputIndices, + ) + sigMsg.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range outputIndices { + buffer.Write(chainhash.HashB(tx.TxOut[idx].PkScript)) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + sigMsg.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - sigMsg.Write(bufferHash) } if len(outputIndices) > 0 && s.Values { - var buffer bytes.Buffer - for _, idx := range outputIndices { - binary.Write( - &buffer, binary.LittleEndian, - tx.TxOut[idx].Value, + if s.OutputsCache != nil { + bufferHash := s.OutputsCache.GetValueHash( + tx, outputIndices, ) + sigMsg.Write(bufferHash) + } else { + var buffer bytes.Buffer + for _, idx := range outputIndices { + binary.Write( + &buffer, binary.LittleEndian, + tx.TxOut[idx].Value, + ) + } + bufferHash := chainhash.HashB(buffer.Bytes()) + sigMsg.Write(bufferHash) } - bufferHash := chainhash.HashB(buffer.Bytes()) - sigMsg.Write(bufferHash) } return nil @@ -670,7 +743,9 @@ type TxFieldSelector struct { } // NewTxFieldSelectorFromBytes creates a new TxFieldSelector from a byte slice. -func NewTxFieldSelectorFromBytes(txfs []byte) (*TxFieldSelector, error) { +func NewTxFieldSelectorFromBytes(txfs []byte, inputsCache *TxfsInputsCache, + outputsCache *TxfsOutputsCache) (*TxFieldSelector, error) { + if len(txfs) == 0 { txfs = TXFSSpecialTemplate[:] } else if len(txfs) == 1 && txfs[0] == 0x00 { @@ -711,7 +786,7 @@ func NewTxFieldSelectorFromBytes(txfs []byte) (*TxFieldSelector, error) { // InputSelector from the in- and output byte. if global&TXFSInputs != 0 { fields.Inputs, txfs, err = NewInputSelectorFromBytes( - inoutFields, txfs, + inoutFields, txfs, inputsCache, ) if err != nil { return nil, err @@ -722,7 +797,7 @@ func NewTxFieldSelectorFromBytes(txfs []byte) (*TxFieldSelector, error) { // OutputSelector from the in- and output byte. if global&TXFSOutputs != 0 { fields.Outputs, _, err = newOutputSelectorFromBytes( - inoutFields, txfs, + inoutFields, txfs, outputsCache, ) if err != nil { return nil, err @@ -911,3 +986,306 @@ func (f *TxFieldSelector) ToBytes() ([]byte, error) { return txfs, nil } + +// TxfsInputsCache is a cache that can be used to speed up the hashing of +// inputs. +type TxfsInputsCache struct { + // PrevoutHashes is a map from the previous outpoint to the hash of the + // previous outpoint. + PrevoutHashes map[string][]byte + + // SequenceHashes is a map from the previous outpoint to the hash of + // the sequence. + SequenceHashes map[string][]byte + + // ScriptSigHashes is a map from the previous outpoint to the hash of + // the scriptSig. + ScriptSigHashes map[string][]byte + + // PrevScriptPubkeyHashes is a map from the previous outpoint to the + // hash of the previous scriptPubkey. + PrevScriptPubkeyHashes map[string][]byte + + // PrevValueHashes is a map from the previous outpoint to the hash of + // the previous value. + PrevValueHashes map[string][]byte + + // TaprootAnnexHashes is a map from the previous outpoint to the hash + // of the annex of the taproot input. + TaprootAnnexHashes map[string][]byte +} + +// NewTxfsInputsCache creates a new TxfsInputsCache. +func NewTxfsInputsCache() *TxfsInputsCache { + return &TxfsInputsCache{ + PrevoutHashes: make(map[string][]byte), + SequenceHashes: make(map[string][]byte), + ScriptSigHashes: make(map[string][]byte), + PrevScriptPubkeyHashes: make(map[string][]byte), + PrevValueHashes: make(map[string][]byte), + TaprootAnnexHashes: make(map[string][]byte), + } +} + +// GetPrevoutHash returns the hash of the previous outpoint. If the hash is not +// in the cache, it is calculated and added to the cache. +func (c *TxfsInputsCache) GetPrevoutHash(tx *wire.MsgTx, + inputIndices []int) []byte { + + // Create a string from the tx hash and the input indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.PrevoutHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range inputIndices { + wire.WriteOutPoint( + &buffer, 0, tx.Version, + &tx.TxIn[idx].PreviousOutPoint, + ) + } + hash = chainhash.HashB(buffer.Bytes()) + c.PrevoutHashes[key] = hash + } + + return hash +} + +// GetSequenceHash returns the hash of the sequence. If the hash is not in the +// cache, it is calculated and added to the cache. +func (c *TxfsInputsCache) GetSequenceHash(tx *wire.MsgTx, + inputIndices []int) []byte { + + // Create a string from the tx hash and the input indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.SequenceHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write( + &buffer, binary.LittleEndian, + tx.TxIn[idx].Sequence, + ) + } + hash = chainhash.HashB(buffer.Bytes()) + c.SequenceHashes[key] = hash + } + + return hash +} + +// GetScriptSigHash returns the hash of the scriptSig. If the hash is not in the +// cache, it is calculated and added to the cache. +func (c *TxfsInputsCache) GetScriptSigHash(tx *wire.MsgTx, + inputIndices []int) []byte { + + // Create a string from the tx hash and the input indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.ScriptSigHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range inputIndices { + buffer.Write( + chainhash.HashB(tx.TxIn[idx].SignatureScript), + ) + } + hash = chainhash.HashB(buffer.Bytes()) + c.ScriptSigHashes[key] = hash + } + + return hash +} + +// GetPrevScriptPubkeyHash returns the hash of the previous scriptPubkey. If the +// hash is not in the cache, it is calculated and added to the cache. +func (c *TxfsInputsCache) GetPrevScriptPubkeyHash(tx *wire.MsgTx, + inputIndices []int, prevOutputFetcher PrevOutputFetcher) []byte { + + // Create a string from the tx hash and the input indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.PrevScriptPubkeyHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range inputIndices { + prevout := prevOutputFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint, + ) + buffer.Write(chainhash.HashB(prevout.PkScript)) + } + hash = chainhash.HashB(buffer.Bytes()) + c.PrevScriptPubkeyHashes[key] = hash + } + + return hash +} + +// GetPrevValueHash returns the hash of the previous value. If the hash is not +// in the cache, it is calculated and added to the cache. +func (c *TxfsInputsCache) GetPrevValueHash(tx *wire.MsgTx, + inputIndices []int, prevOutputFetcher PrevOutputFetcher) []byte { + + // Create a string from the tx hash and the input indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.PrevValueHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range inputIndices { + prevout := prevOutputFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint, + ) + binary.Write( + &buffer, binary.LittleEndian, prevout.Value, + ) + } + hash = chainhash.HashB(buffer.Bytes()) + c.PrevValueHashes[key] = hash + } + + return hash +} + +// GetTaprootAnnexHash returns the hash of the annex of the taproot input. If +// the hash is not in the cache, it is calculated and added to the cache. +func (c *TxfsInputsCache) GetTaprootAnnexHash(tx *wire.MsgTx, + inputIndices []int, prevOutputFetcher PrevOutputFetcher) []byte { + + // Create a string from the tx hash and the input indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range inputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.TaprootAnnexHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range inputIndices { + if IsPayToTaproot(prevOutputFetcher.FetchPrevOutput( + tx.TxIn[idx].PreviousOutPoint).PkScript, + ) { + + if isAnnexedWitness(tx.TxIn[idx].Witness) { + annex, err := extractAnnex( + tx.TxIn[idx].Witness, + ) + if err != nil { + return nil + } + buffer.Write(chainhash.HashB(annex)) + } else { + buffer.Write(chainhash.HashB([]byte{})) + } + } else { + buffer.Write(chainhash.HashB([]byte{})) + } + } + hash = chainhash.HashB(buffer.Bytes()) + c.TaprootAnnexHashes[key] = hash + } + + return hash +} + +// TxfsOutputsCache is a cache that can be used to speed up the hashing of +// outputs. +type TxfsOutputsCache struct { + // ScriptPubkeyHashes is a map from the output index to the hash of the + // scriptPubkey. + ScriptPubkeyHashes map[string][]byte + + // ValueHashes is a map from the output index to the hash of the value. + ValueHashes map[string][]byte +} + +// NewTxfsOutputsCache creates a new TxfsOutputsCache. +func NewTxfsOutputsCache() *TxfsOutputsCache { + return &TxfsOutputsCache{ + ScriptPubkeyHashes: make(map[string][]byte), + ValueHashes: make(map[string][]byte), + } +} + +// GetScriptPubkeyHash returns the hash of the scriptPubkey. If the hash is not +// in the cache, it is calculated and added to the cache. +func (c *TxfsOutputsCache) GetScriptPubkeyHash(tx *wire.MsgTx, + outputIndices []int) []byte { + + // Create a string from the tx hash and the output indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range outputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.ScriptPubkeyHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range outputIndices { + buffer.Write(chainhash.HashB(tx.TxOut[idx].PkScript)) + } + hash = chainhash.HashB(buffer.Bytes()) + c.ScriptPubkeyHashes[key] = hash + } + + return hash +} + +// GetValueHash returns the hash of the value. If the hash is not in the cache, +// it is calculated and added to the cache. +func (c *TxfsOutputsCache) GetValueHash(tx *wire.MsgTx, + outputIndices []int) []byte { + + // Create a string from the tx hash and the output indices. This is + // used as a key in the cache. + var buffer bytes.Buffer + for _, idx := range outputIndices { + binary.Write(&buffer, binary.LittleEndian, uint32(idx)) + } + key := hex.EncodeToString(buffer.Bytes()) + + hash, ok := c.ValueHashes[key] + if !ok { + var buffer bytes.Buffer + for _, idx := range outputIndices { + binary.Write( + &buffer, binary.LittleEndian, + tx.TxOut[idx].Value, + ) + } + hash = chainhash.HashB(buffer.Bytes()) + c.ValueHashes[key] = hash + } + + return hash +} diff --git a/txscript/txhash_test.go b/txscript/txhash_test.go index 73981f3623..9fc919e444 100644 --- a/txscript/txhash_test.go +++ b/txscript/txhash_test.go @@ -52,7 +52,76 @@ func TestCalculateTxHash(t *testing.T) { txfs := decodeHex(t, testVector.Txfs) // Create the TxFieldSelector from the bytes. - TxFields, err := NewTxFieldSelectorFromBytes(txfs) + TxFields, err := NewTxFieldSelectorFromBytes(txfs, nil, nil) + require.NoError(t, err) + + // Calculate the tx hash. + txHash, err := TxFields.GetTxHash( + tx, testVector.Input, prevOutFetcher, 0, + ) + require.NoError( + t, err, fmt.Sprintf("failed at test vector %d"+ + " expected hash %s", testIdx, testVector.TxHash), + ) + + // Make sure the tx hash matches the expected. + require.Equal( + t, testVector.TxHash, fmt.Sprintf("%x", txHash), + fmt.Sprintf("failed at test vector %d", testIdx), + ) + + // Now we'll serialize the TxFieldSelector and make sure it + // matches the original. + txfs2, err := TxFields.ToBytes() + require.NoError(t, err, "failed to serialize txfs") + + // We'll need to handle the special cases where the expected is + // either empty or the special template selector. + compareTxfs(t, txfs, txfs2) + } + t.Logf("passed %d test vectors", len(testVectors.Vectors)) +} + +func TestCalculateTxHashWithCache(t *testing.T) { + // First read the test vectors from the file. + testVectors := parseTestVectorsFromFile(t, "./data/txhash_vectors.json") + + // Create a new transaction from the hex string. + tx := wire.NewMsgTx(wire.TxVersion) + err := tx.Deserialize(bytes.NewReader( + decodeHex(t, testVectors.Tx), + )) + require.NoError(t, err) + + // Create the prevout map for the prevout fetcher. + prevOuts := make([]*wire.TxOut, len(testVectors.Prevs)) + prevOutMap := make(map[wire.OutPoint]*wire.TxOut) + for idx, prev := range testVectors.Prevs { + txOut := wire.NewTxOut(0, nil) + + r := bytes.NewReader(decodeHex(t, prev)) + + err = wire.ReadTxOut(r, 0, wire.TxVersion, txOut) + require.NoError(t, err) + + prevOuts[idx] = txOut + + prevOutMap[tx.TxIn[idx].PreviousOutPoint] = txOut + } + + prevOutFetcher := NewMultiPrevOutFetcher(prevOutMap) + + inputsCache := NewTxfsInputsCache() + outputsCache := NewTxfsOutputsCache() + + // Run through the test vectors and make sure the tx hash matches. + for testIdx, testVector := range testVectors.Vectors { + txfs := decodeHex(t, testVector.Txfs) + + // Create the TxFieldSelector from the bytes. + TxFields, err := NewTxFieldSelectorFromBytes( + txfs, inputsCache, outputsCache, + ) require.NoError(t, err) // Calculate the tx hash. @@ -154,6 +223,12 @@ func TestExampleTx(t *testing.T) { // OP_TXHASHVERIFY output. tx := wire.NewMsgTx(wire.TxVersion) + // Add a fake input to the transaction. + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: sha256.Sum256([]byte{1, 2, 3}), + Index: 0, + }}) // Add a fake input to the transaction. tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ From cad6f7ff95e8cb5e5d68c2f3847ee0b42d32de47 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Tue, 26 Dec 2023 14:23:06 +0100 Subject: [PATCH 6/7] txscript: add txfs caches to vm --- txscript/engine.go | 28 ++++++++++++++++++++-------- txscript/opcode.go | 4 +++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index ae256d32dd..1dc03af4e5 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -232,14 +232,19 @@ type Engine struct { // prevOutFetcher is used to look up all the previous output of // taproot transactions, as that information is hashed into the // sighash digest for such inputs. - flags ScriptFlags - tx wire.MsgTx - txIdx int - version uint16 - bip16 bool - sigCache *SigCache - hashCache *TxSigHashes - prevOutFetcher PrevOutputFetcher + // + // todo(sputn1ck): comment for inputs and outputs cache. + + flags ScriptFlags + tx wire.MsgTx + txIdx int + version uint16 + bip16 bool + sigCache *SigCache + hashCache *TxSigHashes + prevOutFetcher PrevOutputFetcher + txfsInputsCache *TxfsInputsCache + txfsOutputsCache *TxfsOutputsCache // The following fields handle keeping track of the current execution state // of the engine. @@ -1623,6 +1628,13 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags } + // If the vm has the ScriptVerifyTxHash flag set, then we'll also + // create the txfs input and outputs cache. + if vm.hasFlag(ScriptVerifyTxHash) { + vm.txfsInputsCache = NewTxfsInputsCache() + vm.txfsOutputsCache = NewTxfsOutputsCache() + } + // Setup the current tokenizer used to parse through the script one opcode // at a time with the script associated with the program counter. vm.tokenizer = MakeScriptTokenizer(scriptVersion, scripts[vm.scriptIdx]) diff --git a/txscript/opcode.go b/txscript/opcode.go index 62624a6fb5..61bfa2a3ff 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -2486,7 +2486,9 @@ func opcodeCheckTxHashVerify(op *opcode, data []byte, vm *Engine) error { // The first 32 bytes of the stack are interpreted as a tx hash. // The remaining bytes specify the TxFieldSelector. txHash := so[:32] - txfs, err := NewTxFieldSelectorFromBytes(so[32:], nil, nil) + txfs, err := NewTxFieldSelectorFromBytes( + so[32:], vm.txfsInputsCache, vm.txfsOutputsCache, + ) if err != nil { return scriptError(ErrInternal, err.Error()) } From 608063ebbb606d471817267af9fff30523cf41a8 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Tue, 26 Dec 2023 15:08:39 +0100 Subject: [PATCH 7/7] txscript: add txfs cache benchmark --- txscript/txhash_test.go | 138 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/txscript/txhash_test.go b/txscript/txhash_test.go index 9fc919e444..eb9745e475 100644 --- a/txscript/txhash_test.go +++ b/txscript/txhash_test.go @@ -317,6 +317,144 @@ func TestExampleTx(t *testing.T) { } } +// This test benchmarks the execution of the engine with and without the +// txfs cache. +func BenchmarkTxfsCache(t *testing.B) { + var ( + inputCount = 100 + outputCount = 100 + outputValue = int64(5000) + chekTxHashVerifyCount = 200 + ) + + txTemplate := wire.NewMsgTx(wire.TxVersion) + + // Fill the template with inputs and outputs. + inputTxHash := sha256.Sum256([]byte{1, 2, 3}) + for i := 0; i < inputCount; i++ { + txTemplate.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: inputTxHash, + Index: uint32(i), + }, + }) + } + + for i := 0; i < outputCount; i++ { + txTemplate.AddTxOut(&wire.TxOut{ + Value: outputValue, + PkScript: []byte{0x00}, + }) + } + + // Create the TxFieldSelector that will be used to generate and + // verify the OP_TXHASHVERIFY output. + inputSelector := &InputSelector{ + CommitNumber: true, + PrevOuts: true, + Sequences: true, + InOutSelector: &InOutSelectorAll{}, + } + + outputSelector := &OutputSelector{ + CommitNumber: true, + ScriptPubkeys: true, + Values: true, + InOutSelector: &InOutSelectorAll{}, + } + + txFieldSelector := TxFieldSelector{ + Control: true, + Version: true, + Inputs: inputSelector, + Outputs: outputSelector, + } + + txfs, err := txFieldSelector.ToBytes() + require.NoError(t, err) + + // Now we'll create the TxHash that the OP_TXHASHVERIFY output will + // require. + txHash, err := txFieldSelector.GetTxHash(txTemplate, 0, nil, 0) + require.NoError(t, err) + + // We'll now create the transaction that will be verified. + tx := wire.NewMsgTx(wire.TxVersion) + + for i := 0; i < inputCount; i++ { + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: inputTxHash, + Index: uint32(i), + }}) + } + + // Add the outputs as defined in the template + for _, txOut := range txTemplate.TxOut { + tx.AddTxOut(txOut) + } + + // Create a really large tx hash script. + builder := NewScriptBuilder() + builder.AddData(append(txHash[:], txfs...)) + for i := 0; i < chekTxHashVerifyCount; i++ { + builder.AddOp(OP_CHECKTXHASHVERIFY) + } + txHashScript, err := builder.Script() + require.NoError(t, err) + + // This prevout fetcher will ensure that the engine checks against + // the txHashScript we created above. + prevoutFetcher := NewCannedPrevOutputFetcher( + txHashScript, outputValue*int64(outputCount), + ) + + sigHashes := NewTxSigHashes(tx, prevoutFetcher) + + testCases := []struct { + // If we should use a cache or not. + useCache bool + // Name of the test case. + name string + }{ + { + useCache: false, + name: "no_cache", + }, + { + useCache: true, + name: "cache", + }, + } + + for _, testCase := range testCases { + // Now run the test benchmarked. + t.Run(testCase.name, func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + // Create a new engine with the ScriptVerifyTxHash flag set. + engine, err := NewEngine( + txHashScript, tx, 0, StandardVerifyFlags|ScriptVerifyTxHash, nil, + sigHashes, outputValue*int64(outputCount), + prevoutFetcher, + ) + require.NoError(t, err) + + if !testCase.useCache { + engine.txfsInputsCache = nil + engine.txfsOutputsCache = nil + } + + err = engine.Execute() + require.NoError(t, err) + } + }) + } + +} + // genTxHashScript generates a script that will require the tx hash to be // committed to in the transaction. func genTxHashScript(txhash, txfs []byte) ([]byte, error) {