From 843d7607ef8d984ed53fcf8432792b3f852b7594 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:00 -0500 Subject: [PATCH 001/108] txscript: Add benchmark for CalcSignatureHash --- txscript/bench_test.go | 52 ++++++++++++++++++++++++++++++++ txscript/data/many_inputs_tx.hex | 1 + 2 files changed, 53 insertions(+) create mode 100644 txscript/bench_test.go create mode 100644 txscript/data/many_inputs_tx.hex diff --git a/txscript/bench_test.go b/txscript/bench_test.go new file mode 100644 index 0000000000..b129b80386 --- /dev/null +++ b/txscript/bench_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2018-2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript + +import ( + "bytes" + "fmt" + "io/ioutil" + "testing" + + "github.com/btcsuite/btcd/wire" +) + +var ( + // manyInputsBenchTx is a transaction that contains a lot of inputs which is + // useful for benchmarking signature hash calculation. + manyInputsBenchTx wire.MsgTx + + // A mock previous output script to use in the signing benchmark. + prevOutScript = hexToBytes("a914f5916158e3e2c4551c1796708db8367207ed13bb87") +) + +func init() { + // tx 620f57c92cf05a7f7e7f7d28255d5f7089437bc48e34dcfebf7751d08b7fb8f5 + txHex, err := ioutil.ReadFile("data/many_inputs_tx.hex") + if err != nil { + panic(fmt.Sprintf("unable to read benchmark tx file: %v", err)) + } + + txBytes := hexToBytes(string(txHex)) + err = manyInputsBenchTx.Deserialize(bytes.NewReader(txBytes)) + if err != nil { + panic(err) + } +} + +// BenchmarkCalcSigHash benchmarks how long it takes to calculate the signature +// hashes for all inputs of a transaction with many inputs. +func BenchmarkCalcSigHash(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < len(manyInputsBenchTx.TxIn); j++ { + _, err := CalcSignatureHash(prevOutScript, SigHashAll, + &manyInputsBenchTx, j) + if err != nil { + b.Fatalf("failed to calc signature hash: %v", err) + } + } + } +} diff --git a/txscript/data/many_inputs_tx.hex b/txscript/data/many_inputs_tx.hex new file mode 100644 index 0000000000..2f9833de02 --- /dev/null +++ b/txscript/data/many_inputs_tx.hex @@ -0,0 +1 @@ +010000005901fa054079e69a46e5aacd8b57f94c3c99744d68c6097957eaa9021c344141a90000000000ffffffff020706fa6444e4c9d110274488a4641161165370cd11a208ad3a3a00178e1d9c0000000000ffffffff0210805cd4cceae0873daaf8b81d2eb7122cdcdf2dfae5a90569378f847e293c0000000000ffffffff021d93a6970f13f10f6b8b1b5763f772f1f37350f893cb305005319a76069f5a0000000000ffffffff021fa71c8d5f8c455820e6d83c6fca218ed2e70391c7dfba4119168c7ebc68c30000000000ffffffff0227b4b9041f1c8ac86ea16f48909c94adac7081b26a36ed5863e4c53175262b0000000000ffffffff0228a49d6e91275b9d0d8aa5f6e81d4e1e9cd957ecfdabd6df68fe6f4970a0b60000000000ffffffff022db9dd2128b6d1f1eba30a681f062f6eaad362e58b23e6cadcd2cb8d65d6880000000000ffffffff0243411baa5b4b3357849e505310dbd29a8a50e6324c4655e403043ac07ce9d10000000000ffffffff024b630ce631441c527d555c237bb68b694d50010b7723957ef4537185eef6c40000000000ffffffff025312b6a2f95a25a109b4f0b46fb35abd74b15a39241291dd3673f1dd58fd3b0000000000ffffffff02554f0d7fe6df9454c8152c6f46429596abc08b57d7cfbc7f5f22db8a0787e00000000000ffffffff025793409101621f48c0f9ebdef0867088c015c5e412ccbdd1d32a5de62ae1b80000000000ffffffff025b53aa8359a7a3fd92d69ef96be30dfed1ea99fa03db0050f3f6d3b9e2b7320000000000ffffffff025cfc35711a57b373b807bbfaadf0950b81a74d0215a0508f2e62989bdbb4aa0000000000ffffffff025ddd8921db66b54f1ad8ef2eae70913d22e5579e2781a1e485fc6a555f6e410000000000ffffffff026466da9b3ebf93b07e7de704789d5ae04f584fa39043716a9830edf5bf99140000000000ffffffff027904d29d4cb521d7c884325b1d42434bc99b6d712681e5e254597821a944550000000000ffffffff027c662f67150bca47a7b23f0fb7c50956b21a4f46b9f4c4a304d51ce9c5900f0000000000ffffffff027ef5afdeab2ec6515a739dc00c2ef8994303c19a74fd0512f8306fb601d10f0000000000ffffffff0284cdef7bdd0c45efdecb2a3a68079d7e01ccc8e229a3e72160ec93218b1ad90000000000ffffffff028885c672656acd03755167361be6ccd94ad72c5e3890773f11a5c627f046890000000000ffffffff028b372c55109471551db0aa772aa66321862fb3a11e4cd7a31cdc4ed989436d0000000000ffffffff0290198f3a73a8febae55d559676303ae495acbad11dde304c21483a951078270000000000ffffffff0298dc5698fb1823ee1ec7469936e0b9b71e69e843d993b2c0096ac3c1d86bb20000000000ffffffff029c5d6da641328e356368383951c5dad56398f29f58a922eb54afaf888e41430000000000ffffffff02d702320af97f4f498aedf0886cd6123692f6d7b6f0b1da3910703648bc80c40000000000ffffffff02d90670fc2d34b7fcfce03ecb3a3650a4cd384eca645c2b05263721e9a5c19f0000000000ffffffff02de1f9e03b532c0bd6a28a7066c81b7abf0f9dc7e40003f638b9a872fa9a74c0000000000ffffffff02ded955aaf74a935a479fb08d4e8ded2a99664cb2235bc617627616e0e313630000000000ffffffff02e3f8ded8a0d2716817259e4144faa83facd27a5c9550c7c65835cc2c93738a0000000000ffffffff03012cf75972966f2c7abb609b43c7e85dd65cada0ee20118c80d92d766a32400000000000ffffffff0307efdd04c2f3e033a0294aaa894b4245695876fc76c12b8523d448ef05e6620000000000ffffffff030db2a3d8afa67b68f0ae663cc88bee0af0a8900e88f099d2ee648603e7b0e30000000000ffffffff03133fb852424925f55c8a660fdec8a10dce9bb9b500bf07bc73e2641f1a55310000000000ffffffff0313954647c73d9c63e4e83d36e337b17797be10eb0d641deaff0c768aa6db060000000000ffffffff031bfd6a0bfc310e204fc0eb5e1c83b9d21dab82847b909db111a371ffdcb5a20000000000ffffffff032588268e35fbd648c621638633ef8188e8b946ebd3d5833a18d9bae556ee5c0000000000ffffffff03378677fc365f67102c2559a2a4300344d41a63920e7450951fc6d8d63fb0110000000000ffffffff033c9955351d351b4f5ac398adce94b2a7ed3ab14a1c790c8e4742b1558c6b200000000000ffffffff034061cfcca9fb77d28d2411f4502c3696e0aa28b040f6780a4bd00c3fa8a02b0000000000ffffffff034b063b587eb75409e56b5ff2f9cc24a994e3ecbf9de56bd46983443067e81b0000000000ffffffff034cb2f8a145b3a55bb07dcdb904859808b29a5f53a01ba3a3cd707211afb7190000000000ffffffff034f96fa5e134b5310731b23fa9235aad58008c7c61f1b8128542b51622a52250000000000ffffffff0357236d19aef1428f117c3067a89adbecfe234ef32d744acdd6b6a5107a95e90000000000ffffffff0362504fcc65272e50ba6cc4583b4b1486592b59d943ab375ea7d6f44ba8038c0000000000ffffffff0369b02dad4259759408f8a3b1fad3c24df0873b767a9d0f1d50bd68f18c789d0000000000ffffffff036e080ba3f983e5bd7d8700dfd4b510a502231d0112c38e804b59d8a84026230000000000ffffffff038298149c290f73c535c3a184d181316ef695146a58d3bb3d2e61ed993cc11c0000000000ffffffff038538c9d6b8200609e4ca2443910daa6d9bbe772b2793e11fcf1763202c998b0000000000ffffffff038aad027dade93191c546648f7125bda82c895f1d80bf174d9637e533e12a1a0000000000ffffffff03960708a37a5654abd75755d1b8f49a84cdbde4676e14fd4863b125a94e9fc40000000000ffffffff03b0e09c55ecabf221c30a086c812e35bc9b586547cde8a83961a7ffa9067cb20000000000ffffffff03b28e682c13e7ada60cb905f8070109ec5a7800a0424c5f7afb6bd3039d48cd0000000000ffffffff03b7e8adc6174ca369ec31b4f944a6b74231d09254fde6b2bfe3416f3c6096ad0000000000ffffffff03c4f1eef6176ce6a2114dce78dd292f6db20fdae7c534d22cbada58befad6cf0000000000ffffffff03c7b5bf7fef69ae11c467f68fb551d58424e8723d05ce3ccf88385187898a710000000000ffffffff03d5150f5295b1e817f44fa34cade950c13789c4129e98eb32634aaad2abe13e0000000000ffffffff03d8250c9557188c7672009c4b14f37f96528e3f9c6889607b2e25299efe08720000000000ffffffff03de1dc49d0146dcc0cdbeb84c68a14ecb7075aba67d6494a5c8d098147843640000000000ffffffff03df8c8392bc17eddab688530697c2c221420464eb643ca8edfd30d7371ed5460000000000ffffffff03ea1dbbe72e20229648695a9b01e09b6080fb1c4632931fb810c5bfbe4b4eed0000000000ffffffff03ebf599f34a7d2cfe7bda1722f76e3df5c7fe299fd5c6e4099cfec4f551f46b0000000000ffffffff03edf95bb2c93c07d734ee8b3b1de4d4085c6b65cdbc3ca23eceec0c4d2b29a00000000000ffffffff03fe259ab7c4c03447011200d7d5236f4b1eeffd075926b78650cf1a59d570590000000000ffffffff040f71baef9c5273ac67fcba44ab29456a820274654624255c27e53ff62fdf8e0000000000ffffffff042938cae95e712592060f872cca771dfcd0218562928311c81127c3170a57bc0000000000ffffffff043a09d788547db3d1bfd8512000f9ad8bb3caab6fc24fd186663542c9f06d750000000000ffffffff043fe8a5586d0309c8dad9277bfba04fb27e7a39d279c0c9a2232e2927874d470000000000ffffffff04426f1fb4981529c32ba5756f59a2211ee9d2b9cab2c5de06a07680e833c33c0000000000ffffffff04454fa62f2c03d785307982ae406224025343f19f183e0fdff83d8e1d4cc1500000000000ffffffff04496141484d641e3b0a661210b225df50221b13bcbcb3cb8c86786890c745db0000000000ffffffff045731a8be6bfab1f9d62d2b2158591029cae655fbde8c8bdee80f183e72ca4e0000000000ffffffff04610a21533b98d3360a6f2c65be5685a1bb3a43f67da08dc1674a0f517618990000000000ffffffff0471b7883558587879bd53e13694069d95acc0d809fd0a65125ba3acdcfd66900000000000ffffffff04729b78d9b706fcb20c9cbb49ec1baf5fa782ff44d45d9a23868bcbdcbca1330000000000ffffffff04747b056b7cdd34c9891845220c2b7227a7c7c4f90e1ced6f36fd2197b26af90000000000ffffffff047a37d63a2a17986896072c77fa2e068f6e593dc64bf2ad8e180fde729fe3250000000000ffffffff04875cf5c94486e6596beaffbd7d08cf5e3111df06a89f59d65989d1485949190000000000ffffffff048944d4aa4d42ae9e864bea755a4c934713c63ce1c61d177a1ef03fd679ee330000000000ffffffff0489d5c9638a3f689c232fc1b9b59e5f82bd2e720744c4405d6537d7931edb030000000000ffffffff04a206bfdb054b041ddca180111aad2abf7b31813e4551da3a29bfb16c03534c0000000000ffffffff04a5feab7629e0cd9569aa62ea70f7ab3fdd58d3aa9bf73e8f76ac32a949d31a0000000000ffffffff04a70237d798a0b48b5abf682b806f31ff425406d93e74cde1ab4b9939ce9bc20000000000ffffffff04acd550baa26049b3dc52fb9efef1cca0b8dfa228c64482440d7217a81e587a0000000000ffffffff04b1caf936f5746da6d683eb00ae36b885fd031a8928df91e3172b1ca17f4e520000000000ffffffff04c45bd25f17cc765d8a67ca80a801c1f1c2131eb51b7b8ceb71eb898582b0380000000000ffffffff04c99a8ab69a640f9891aff371876a9d0c9043cda0430cbd436145c5574c124b0000000000ffffffff04cc0c4723cf9952c0a51db275c9d11370a95f478de98dce603d09de8e3d8e680000000000ffffffff02c6afbe970400000000001976a914c055f905fe8d14a2766801307a418f1e03ba566e88ac5ee85e0d00000000000017a914f5916158e3e2c4551c1796708db8367207ed13bb8700000000000000005923aaa80d0000000018f6020000000000fd4301473044022044e60dc7ff9d720bdc8329f97c5b321a84e0846fed248b23d399d222a074bff402204aaab1ed5df1e4b94d627d8b06171df51140e65eb056536e91103b75d032cacf0147304402200fcc38b7bff890fbd390f4bd1057c72df74c2487a3cd20edee80a20d6b2ce02902200b7893fa29f24e09358b6777a946d40166bc71c25f8e5f850059a1914e25959401473044022006430580bc312023db40b6928ba0c39141a8a59bb0613c5f6feca1c1f9b2bce90220055ffd441357bc34c246024281a7b394c7ee6a2a3c013689d95a3b9dbbb98d9e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000009503030000000000fd4501473044022059a8f1680e80b50cb8a9180c1fabe8a66e84b34fb6c10912715fa71a5bc0f9770220531c0836d4de61d8e291c517cda25919a8b822b80b5737068120411976e5577e01483045022100a48b720930c142235cc5585a97a11d20e73a81049791526cdcee34d36f92fcef02204d2523393dfc4ba21451fa563b08f53df7c7115b65a9f2f01fd578474d308eaa01483045022100a73d3ebefadefc6dd9c7041b32bf7a3a25e49afd41bb3251c502ae6fef89316102201652f9ee39ef09335f5c8e83b3fc7591b592ff0651c5e8c4786875710cb9d1bb014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006113030000000000fd4501483045022100a5af3c72e2e15539caf81189fa020cd4b3ac5432c512dc48772d825dfca7c60802203ad5f3caaf54593b8068ce50db67111ac166c2efb73b52b30dfb7884682faadc0147304402207796a265903387a8a612cf47a8df0aae7c251a339590b24b6d813fb4cfb2dacc022076df8e67ec560d8fb74ec7ed0cd368a4da09c98846635c662e5484f5ac5b862101483045022100eb3318b340dd14f8fb797af81d8c1784668f082e54e58cd59c1224890d7f6c19022064ce22ff6bb93806930dd34dcbc846d38028db4d4d4b9c5527b6d493dba01cbd014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000099f2020000000000fd460148304502210081e68619898e8b0f9eadf2b5a324fdd7a684f935bf2cb72eef221e4310631ebc02206c7d5f1067d2980a95bfdf3b8530898ee94138dd99715c44e30b0894a5a6031b01483045022100f675ced0b828f2bae7a8046b41869ff865c081a79e32cbd400baa8ad99a975cb022075f347b57272df25c5d49c810af71fd1ba515b27166e61ad0c2bcbbd746a818101483045022100f271a0a0a8c5cfd149bf99411c4504d9905e6f9ce9f9787744c9593cab7f1c1b0220527057676ffcdd60caf0812b20b08b94ee1f935b44614b29bb9ab59e5bc0080c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001b02030000000000fd4401483045022100d068f2da5ba187b9d16b06dc87f1ff241063efaa786e07668abd06f8c2bea77e02206c541479ba49347104b386c36df7e14bc087bff7a17bd9c4096356a8c780fb2701473044022027336ad739d3ae08d1d136a9b7002a97873199d82abdd4bfd0531d62294116a9022001925305ccab8a328b873126baf7977b51154b4f0436685fc21f483ad347fb8b01473044022031a20fbdd66cf2a0d59ed37eda8ac50772c25adc62035b53ea675200fc85cc1c02204c1ab7d35c53ab9a5086fbcb669452367d68c8a3f07e47843c8fc2e2dbbbccc2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000c7f9020000000000fd4301473044022022bfa36e676571479c3dff1d1a5a5c0cee3677b5ac45c14314d242bee32664270220736875bcd0807c93c3a542dbf98c753c7164ad6830acec833cc43e157eb665180147304402203c2d6ce11bff536cd9bd4f58a930649f708a5735a26971dfd8d4efa7748c48b302201e4c69309d99c41d1c19ee200fe23547f1972173f0954e8833b8ba8ecedb6aca0147304402200e9ac49f352071840f13104f247d1a506a637d898495ffcc4aec7743ca56a2e702203e279aea8208e3128dc27173a6cfbab2bd8c2bc8f273794028ac77a129d5cab7014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000005210030000000000fd4401483045022100d8e2743e7051807e428574f9c881781b40daf0ec572dcc30357350c48683fd84022032a131a11df9571b53284a2cdceb7668e7b6442897fbfd54dfc7e26f8a3a19130147304402207798db2d9031f37eca9788b63869a91b100abfff8ba9966c91c42636e43c56bc0220499af71936bfae1579f6ae8d88dd6ff980ae1dc4fa8d1c5585d08a6461ce1e1301473044022062d193f3c5f74685fbccdd8795515f67b88c8a9aac089591fdc07a8238cbf5390220200f0d61cf48c33f7800150c8af578107c20875846b4a0f2af2a4aff9d2b172e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000dcfb020000000000fd4501483045022100e4711a4de4d6cb64e8cdaae2a4a5033c1ae87549c8d419b42b8170ee5217e04602202784ec57a6591083dbabaabd49dea7cddd3f8a32d9497e23d4b4219a56a9395f01483045022100fd776ca1d482c1d39e3a414bd0e6d353637ef626929ca5ee37db62e3dbe3501802202abe2e1279e5a354745fc5c8f8dcf367c616d82f91052925b1e2880f3906e7670147304402201976bceee0c3bb19da53e80a10a92dd6d135c86e581f349268f58b7d6b387bb5022042616ed8eee7f19ba7bee1fd5ae5415b521615aae99c45d56f9bde39d2577b9c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008f00030000000000fd4501483045022100fda77866c51a7ded28a6e12d3459e32b33401b1c0426f3c727ba4542ded40d1302205981be0eafc9b2c702038844769765df04bef8e17c80d1fef65c2bdba0a8e6ca01473044022018343c7eb59ef103fe102361f9ab8c789dadcfb3e60dc9b25b429165a9625af10220367f3f95f988ae6bc29c8a400687ffd70d009fe0b650994f48c1875620440fe501483045022100ac63791400e4ab5fee7f8c8ac602017409764dd7ef7ffc07d793298277661a2202200c3de5c22cad718ae83c101e987b66f31f62cc3364e8c8505407ebf11b392b66014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006211030000000000fd4501473044022029a09ec85ea2c7741cdab03fb1185d94029a5511bbe830d11ac22dfa7f21f452022060d4039356c9cf06be106dc81390025735cde670de5c0a5a38e3946cca95211e01483045022100ca82755fa0c2a23e207c665099d102d13a393ace8cfabee79b1a6f97ffb001a2022014e2ef964e3fb5e90743c54e620d40d79ef111be1299d917f762c442d40c2f770148304502210094e9d5926748124f8c215fb006ec2bec55e4cf469a7f8579a339c08a267ed2540220323b0224d9964cd55b9ccc3f1dd919073d83edb293d022b00497b8681624bec3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b009030000000000fd4501483045022100a308d49610b603e9ff378fb40620f255216f3325aedc8d9a909f9c5e8983cd4e02205c93d14a43aa5d4cf7a3d4d483c6d47ccb8c2c4b78647e6cbe490ca6e03cfad601473044022004b8e60724ecba532e2677411e2617cbc49ba42cd4843423d724797aed8ba60f0220198d033cf732b5451d5ef00666ecd19bf4ea8a08f6c5fc16f4e153f1759021ba01483045022100e6217183d6869b431befccd5f4b12372028e7975640cebaeefd5f5172b871ad10220152db2015f3a8c16129f34e2488a4cf19a85f26956e3818f2cf76fdc9c43fd35014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000005805030000000000fd4401473044022011d6116b37d985ed52d59aa4fee1c67a44668f55d2db37fcecc3cf89245994a002202ecb47c384e42351995cfac7ee0fa8f55053dbaa11784a1890be84c164a5cdfd01483045022100922746c881c2becbadee3859902d93c9f623d1ccf3cdc0231d31e3ee806a1db90220715d1689faa159f4abe4959063e4dd8f108b4dae79167367843dc31e0fd46e97014730440220280556147f23f8cad1907869154a98b025baae29367cc80c6d23a3a0d3b6443e02207a62c31f1331459885ee236245d3e122013b77b304ed512914b05acd1886ce79014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000004bff020000000000fd45014730440220053f5cb6b633930fc7ccd1abd012292cee5b64d6d5c25220f9d9f22ab16d1df40220430e464f5823ee503616dfbfca55fdd01949cca336c8586f4e1de72ec3f3063201483045022100e16f4708245ca99bb01ab257f495d6a33b2db4ee9ee5089a6a2074ea9070d8f602201d3302d14c85f11b2f42af10e2ac31739f085036f26aa1723c3cfd2d4b35b2b501483045022100a0be245e53ce42f783481e3ff658c13ac850eda7ad21b8206bb48fa20fa2bc5d022072963db0ace1449652a083be585dc75574490248d1069c0a48f7f7a6902fddc2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001703030000000000fd4401473044022029960cfa720d3818d4d2d7a55fb1a68323e34678a2a8fa3e68505cd78184cf9d0220723861ca8cee7eaed1f503a9668d9866ccae44f15a3e9da77e4dd23679715e4201483045022100f3054837f1aa88101e983b28466b679fc43fac26c48d0359c4b881ed3cf8e73b02204f4579fffdb2a96fc82b0ede6bb5eab53a86b0bc04e1ea0fc1bae9953ff5a7f90147304402207e3132303406ca2924ad934d49e4027f52083638f08d73bed7138b6d267948ea022018720dc7f47cb13d3d37555c0abeca90cb00060ce1308a3de317d63da5d1f4f1014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000002f3020000000000fd4301473044022012cdbabc4d98705db72c8a6448d8b5e5483b404ce0ff26fad16b3751ccd667be022044bda8f743f51515281b41e7b7a0347e8d52e9aff07ad0c4c84486034b2257a201473044022021e2029bd87c699931a5eb4f997e56f1efe1dcc26a7fb8c8147a762bbea389ac02202ff5feed06720f5bba0c12107dca3d83c349a35c43256429b8f12eacc6b5df16014730440220269980cf3256b87c9ff83b29e3005b6af9c85c5a8fb7a501055b486bb51fe3e0022073196474be83c9cc24a843777835b97c4eca0ddf7956547f6cdc48a1cb42ddb3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000030f9020000000000fd450147304402204442fa3cbc6d9ca9a217069f984e8fa1bda35a825d6bf39804c92faed97f2b8d02200976a8fa7653918fa23dc3adf15408d09a8069b033f4addb723a028f9928fbda01483045022100a36c038f7e49fea33c9f2a26a4d8e510095d42912ba1cabdd7da19ceccd2466802203b78e02c86e2fec18eea2ae2b94f1e6736dfa7a3453be9ee571c90832978509901483045022100af4a52bf94e8618d387c8f54feccdf0ef49b6b5503102cd405ab28c48c6597ea022031053ae17323c0fb24dc2cbd3107ca374279cac0b54c4a6633747effec83fc9c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006508030000000000fd440147304402206391486dc686a643c26e9b43038b2cb1b465ee6ba81ba9203d0d82f9550bd26602202ca9ebd53b20a6238c3d8abd4236b10d9ca304f5e5808db4d5004ab9ede10ad101483045022100e419233679a71a3380b662425657eca9ad26cb425a8aaaceb10de1847b3957ae02206780b4b428765f9bcee90443aaa497ade22fb813951205fd53c999f7ae27c168014730440220656dd7808be7db3a7e448608ccd3b8696cdefb687de8a4e37a58a2a0db8c714b022044683a0d0299b6eaed1ef4162d85f563aced9306d3e4a84961a301604234649d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000cb13030000000000fd440147304402200ec0afc3a6b07063c61400fdd53e5419bb755bfcc99b1d6d2cf275b3f6848c75022036fa64742979454ce0c6b2e16963dca1d4509360cd35cb85fe533982a2ad9e690147304402206ca03acffd0198c7ea1d78e713eb45812dd0a296929bd679137dfe56f3f0601702201b9d6b25d85e86bc747b8c9d19a1a2329b02ca9de22a914419cec25bcd68e6fe01483045022100d22e473e19fda86e31314fedde03e1710f5de93efa284dab5f86817c99c9417602200bc43a59bee8a46bbc813ce52cdc76066903820af43d4caf173000543f672c6d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000005efd020000000000fd440147304402205b7ef22035cfb0b11b62354d91f97910937de864369708e3e35376d4f620a1780220496aa95cdab061367e571568269afdc2d0a8fbb83b1bfd08c062d057041fada001483045022100db48444eda15deaab63bc6f3f5f31546d3b44a5a9412323445eb3e0853b440c80220201d7c95c47b5beed4a1139c27a79dbcd30f288ac106a208f43c61a0989f3e0801473044022040693ca2c95a33d5e8e7218b99a6df8507e9b8bf72014d19cae77d489b57b76102201883ef3c9df0b017e8fcf5b0af1bfbd61556d5bf347d77544e3bd263641fcd1c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000046fb020000000000fd4301473044022005b50050f39f996b5f91e8e2c297e66521d5f72f2f4bec9993dbc44908f662be022001734f6df9a192f00e131fd8627b37b2afdbac8afafc38513b02c52ff305efad014730440220749ef2b72f235127bcc7c949f171ccfcc1ee87a9979543df9b568b323d9707260220440ae76846f8691bc471aa479e876a5fe01ce0e1f8311045cd8de1e7e58e4f890147304402201f5d4ea7f8fe9f08dac2020f4a2242645b27541b3cc591235fe927bfeed375a8022077627f400a799de27063ca0f2ea2d3ba5b8398bbac368a0501066097ad649e3a014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000009a09030000000000fd4501483045022100b83b84be0a0e613dd5e9dd7c6bec573a81bf95f83b546a4a717e3b708cc18bc802204bf97f877e03608103e30aeb9aab39c8f8d6e22cd9e89e66e7be74277bd7b7e201483045022100a9fe153e507bdebaf820f73e016ca8c7c61e3a07cfe7c79d74d674219c3aeb94022030abb047356650b8bb79d9eee45a9030c5cc55466568285e6d6f97cd6be577830147304402203da91297cb8e456b0089bbae43ad02d636f3f3777017c5b0224da4cf31ac5e5c022002a0ab0167395ee04f8e0f37fa261a80bff532c118a6f9fef229c5a9315115bb014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000cd0e030000000000fd4401473044022002012e7841bfd2dca62a5667f9bda8cc9b9e4ef86f8d0d6cca322b5bdb06a9b702206d5428e45ed0e18cb980b385cebc86eb6dcac59ee19f84afdc0c95ef0e782bfd01473044022073c34e5025fda812d8990f1f8cb16fbd4665554e8c467527b2c5c21132fbdf0b02206c22e854f2b704932e8134c46c40c738fc65939b2a516cceeafadd0c80f85efe01483045022100d808f0812a4a124c074166d87084bb3a38a6c632846a8ab4be7c69bda49b880702201a3625f5641c6a3a414196dc3b356f59b9bb8a44cd7623d601f28cb03691cd39014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000007fb020000000000fd4401483045022100e5357e2b20252b028d10d746c386959ba296630f08d94906a36251bf331073b102202c55e9e3a482e27326731821d25fa4c09103ec60c4cdced3f6774e6f1bc929970147304402202e58674ad455a4172f87439beaee82f59842d0c4565a88a358ebe8db35e2204702203cbffea9df59094f823f598cdb252927b1af451f522c8ca24d52274baa2c5f440147304402201a078f0959625258d4ef1bf7918b852bcf1629f8e79e2a95c1e6ffca017b7417022071dfe182e18f1debf0163e85ece438f1f3326e2eae39eab8d9c7a6cd8f179bd6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000043fc020000000000fd450147304402201d697bdcd56006044872b398bac35b2c736f6bc22080ba7c81f5038de6a87a3702204c832224547a5bd89dbc5639e763dba74e3b35e77234078b425b7157b67f401e01483045022100bd54f0d2fdb584b15215e1eb8af08a2094f9ebabe0db8b01e107d0d6ca7c55f402204b1c9e9d368361903fe3cc99e48d045672818ae5ee15b3fc46e1c84049ed1c7a01483045022100e62ff90682934ec550ea89646de3a8d8bad4919bfd7d04a343aa490ca380d49d022046b3c4d8354661d6061798201cdec35744471b61bc55d83fa403d1aa6aa3b2b0014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000000cf6020000000000fd450147304402202d47018ec27c8d18a170cf22fa9f16df5da26c9ec85b5e7ab1220d7619d3bfb80220391bce1206a19b54496101317aa853f59fe8685f4ef1080525ec13149bfbe0e801483045022100ebab59d9714232e70788b14713364d8fa9c6284e0c2f173a3376521fffcdc80e0220624b5b801a7127e41c4378582026262e917a35c69ddb6fdf0de7ecaa26017904014830450221008905e07026703b5c1f6793fa9d749981369235d540fe0127be7b5d8cd18e401f022017b7e658adf3ad47487dc49c6aa8e88cc4d3b97f87ed0771299ef4026a8d8995014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a00000000c506030000000000fd4401483045022100c73924d67aaa9977d5b85fbad560b5315d36a657a402913be2aa90a1d12300e2022048b79577559462b4fd8c348a0b4d98caa20cff2c8a0363eda46037fd11782432014730440220345499c9d88b6989f158f7f980fa6f78f0a4b57d70f159739bf631b788fdbd3702207340e0fb636061cd9b7de0723d196b5f406d24120cb84f1236e3873f6e2fd6540147304402203332474e7a96938334a32052a802abb131d7a5000fb8fe1b903ca2474217d514022022533f3d58829d55689671efa27901878cfb75b20f5c696bb00ef4fcabde125f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000002afd020000000000fd4601483045022100e7e10b3fa44e3bac1a8a5baa89ee3f34f437a37572a111d75ebc323007220956022007332c4fa0a81b99711a87342f7e13e720d09122f6f8d91ba093346d17cacbbf01483045022100d8f075435a1f1e6b3352beb482e899b95ee611551a499539c0004d703588a4cc02201b339915bf5cc5b1f4ca95d507576e58dfe20b2a2a3f9e97adc37c8f1f2921b301483045022100a25df71a3f33e602652db2a3ea88a65522edaf1e05c0900c87ef7149dafa9ed80220300d23d4abf09c979ad36916da78de09656f4c38a4fdac72454a888babc3f22b014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b90f030000000000fd4501483045022100b6240b6c0f35b38bfc074939789eb53550f6c570487245666cdb825c13b95fe002200789f35de73db6342d1a3fd381ae55f5e801e1d170e7c9672c8179ddb7c9738101483045022100fb1a47917fef805908d2e02ff317a685a9550e5edc0aa8ef8568282575d4f6c802204a6b7a54338332ccff52ba21310cb0dca6a6b97370f1a621566c9214e03b49d20147304402203ef5647f608b1e921ed139dc54bfaf5e5ac3d959b88f3dc7a2ade0729f62818002205df3b44fab36a105bcecbe1f6b2794f692e0fe65eaa6d78126279c52db2a438e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004b10030000000000fd450147304402204cdd0d63816bd37ceaf959d323788b35a629d6b167cb2337ac81c0adae1366450220271435beb70cd8a18ddbcf98356de296ec16847a7a3e1a7c36d400f332374af601483045022100842dbb08884f9834eae867feedfa0ed158175348ee671aca1b470a9c5a723ee202201b9c8ffb52dc70fde054f18ab5a0419b603880bba4c9dec47d46bafb25c061dc01483045022100848c18295767444c5e0670930a0e0436fad251964bd4884ff5671db1a3f19d76022016edbdb3e65f99cdede5e957a941f3f35889342818f6805e0126e715a335aefe014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001402030000000000fd430147304402202b7fe153e0a3f19123f677cfde86578aa546f8a46550806758b053d0b45b89710220792803175b29dab2c6e8c751436b30213a3b409c8d7cb0299f29a69522b120880147304402204e67807d8a30c8358cec44173c7a0727f542b4dc131f9701f2555a27cc17b628022044487b404d9edc2e2e8f553ac874c5dc549d9b261694752759d101ec00cc44c10147304402206d537d0aa88752db936ee48e986afd68338411ff68797ee2f80ce537bbf41d9002201d276c24365b68d18ce4020792c5d9558c7134495af96bcc55721d795c7662c2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aee854ed0a00000000f1fe020000000000fd4501483045022100a53176fb86d2190592abe80725917bfd5ab910b942d6f9648510e91730f3986b02205db19b272d2a2be3e315f2c9526a02c23f043c7b643128284faf734ce1d8930401483045022100be116b856b1f0ecac3c621c21a09d484a4bb86468a93e37f7c6d684c74c9031202205bcb65f89495e4575a1e5babb08cbd2aa21b200bbb9af1bd789b2a5b68c804f50147304402202a0dffdd955e6a5764e06113b5b712e789e956e7b6bcfaa225da8ef66e446c1b0220455f54d546976dd492b94f1693aa9d7e76dfffe3a7e861cec5f809fd5ac18caa014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b305030000000000fd450148304502210098e4071de0540dd5d502e71a962803452695d3033ae1df83855e238197e2073e02202b825fb2190c784c050c375e1f520c0a2a85f9925fb43ffe58cdd25708e2877701483045022100892cb91af82982c870671e956805fa7aec721b82962311f4c51f959889ca8efc022051766e50a95d2ac480851b98f129889a4dd566c69b6ecda9654b3d4064aa40760147304402206ad1b9cd9af3c1e69ce32f42f1eb9ceab84e99b9f04cd14ab8842ad065a5589e022020b98e515e17d59e415d11b1be3e4fc34783d547200960ec96a4f67b6741aa58014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000018f8020000000000fd4501483045022100e500b89edaca4c2408069dd40b9645eec796f536ed10811016aceaaeba48b9b102201bc364c56b9647a7c6c53b235418a78c8d8faddf793bf3ffdbba41818b9a401601483045022100a9695d1776c81442dfc4931a960188be256f58c46ff546d1e872015c8ddf56a2022012443c230d3257b64bc8ee05d18269c211becf4b9ed3a5c7a3b63e2e216dabd8014730440220073a21f37e0d3cf9d706dde6e4fc2232395f4bf1982785ed04c61b1144f7c00f02202c7b18f4893413590bb4fb139896826bba6fc3e1205fa43fae30d8e7cce48edc014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000fe08030000000000fd4401483045022100ed3f9d7fedec8c9f8bbf1afb2b7b1edb3e4e8b9dfaf789f1effe19a32dea4728022046cba3fe18f0c1107d3fe2436d037aa8fee47e830cef788fa86541c84ac02a490147304402207326ffc63de54f4554f56a7da2072cb4bf89479bbdcf22d00169f4c3d7054a5c0220717c3c86a5470ad7f7a4900cc13e05a0a929ecc00e3ad58ac5850508ebcf7a6a01473044022052cfcc5fcd981d38c150af8897bbad4541a8aa2742d31369bf0c37c4670ec5eb02206031643bea000d5d33e180a75627cfa7bacd9eedc0926eaec773e529255c6262014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a00000000e001030000000000fd450147304402206c7e38bf498a7ff831513413e5bf283b93fa456993162fb698a88933e1451fa60220669d5f75d9817f3ec08607f4b3b0ffc3f4190b5f7cbfa3336f1b64dac28178f001483045022100d8a58946dc73974ead5766e023a11eb2f32d3b7d7e482ff92255137fa6aede21022040c02e902a208a25b10296ee0a5466c6a8ce211ae2a6e2b7a541d2ea69fa4fbb014830450221009bafaf344ef873797281daf907d4e1171d8220e47bac5888e5cc72f3e219ef85022029773c52b7785abb77d72c22fbda1f9cc2a8536ccca43ec756bc9655ce6a9a84014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000980a030000000000fd45014830450221009be858cd5d4a8bce15d433d040be529a38cf38d9358919a9c04a1ca26127e3e402203de44aed9159f37e4b04ca449621d82040eab2528e2cdb7539708574899175c80147304402207271dd94d087a7b2174f3c3ccea7b92e29c84c6471224269f30faea34f2b77bb022000c9e94e92ea9f311d7a6260a2283f879115eb84d319d0da3cdb724b2b66da890148304502210093397c395481625411641fec29b5f8c37b4163e6bf886843080082f14dc716c402202f1b776d07f69ac148ec36a257ed43a2a016cc3257b091f1180bdffe9fb8fd39014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aee854ed0a0000000047f3020000000000fd4401483045022100e59a2bba52457cd3a884568aefb50a2500309e0262243553b31da735cd281d1402201c238950f19cff6cecb3e75a7308c6a0e9addb39001b36e12454575dd695ef07014730440220679cb79f57fdabea76ea42e3f145aea59f3cfd4401c4c5b1693b2ba03a2054b202205e3e2accd4d889e6fe4ce0a4ee8d6618dc79aa1f3b2d3bfb664d83d25d6562b501473044022043b80b0e55ea34ff22377a57c4633c1c5fdb57d53d04176c1673cc67e56d63a802207081bacf923edaa05c4848a11fbac160eaf3a33bb495c26361f85f3ff1fc482b014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000be08030000000000fd4601483045022100ebac21191cfac6123e0a66e7c6af7febc1a1ce2e63c4e6fa1e4f75d6d2f39dab02203d592a16f40f8c8c88817d0788457d66bdb35dde14b02b90c7c353d040d4af9101483045022100827b7e2ef669a5d1407b98562a4cd45f302f6e2489c1bef3b8bdc6a0592bd922022036fd1961511848f84c9e8c633044033501091c5e756e28a4787846156cb00f9201483045022100e330508c7b1072b3644bdaab4a6219a7cd7ddd364ec0729534385e2e6e7fdf5502200dfa2bc091014c8cc134728c60c40f21f737203e5c5c04740fbea54bba8eed04014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000f5f2020000000000fd45014830450221009db0475dc62ee80b8b49bac4d0138de52f985642f5ac40b75f46b643e4115767022042a464c53ebf57bfcefa05634ceb14e7669b86e94bea517c68e2264d676c70f80147304402205e563709bdcb24975892a67eddf3958a31d3a58471c6c0e0cb6891be80b9eeb802203c69bdf9042af7429f1555cba2180108834b3d3b139514274fe5c30aeb9f054901483045022100f792db494bef64ca96ec49891434bd33f511db09d214a3ac323343d758e2d3c702203a86fd817a81cb84610fb948d6b3ec7f4d4fed24da15ae9ee245606c4f5c42ab014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000bef8020000000000fd4501483045022100ca7a5b358614ba7615381667ed1d6ee7b2332d520caa6c5c6c1b4aab12fc4bd9022062990a8aef2fa44dc073cafaa2f3e95c3534ee075ba998beb716756f985b8f4601483045022100eb982763c3fdacf7bb59642df124f090cc5e55287b28c206a47c8a3ec8f08add0220668510362e0da663f117235f3aacdfa7fce13abfdd2c54425cf63d1b32c0b56d014730440220447bb198dc7466bfcf815e6b91e2431b970f6eba34a02260c224e503622e126902207c8fd1e1bdd32def46f7442df011f58a561b04477b372da26888c2a38d2bbb53014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000580a030000000000fd45014730440220702dd862dda92c7b3b214909a1f9dab7653a2f9103b7a62d6646d02c651b34e7022022e3c90cbf41c1fa2a5aa89ee139eee2e656a640947e8431c63c54e1e944763501483045022100f0781151a884d97b3de74df629265914f8d0253581e789a2eb511ef76568a4d602200f33782718c74ed75c25d66426f2037690d0ef3734fd06017bbd65d992b68bbe014830450221009d56003be4df82e009e4584088807d5f4a4322151865681dd04645c63b7a5a010220584ade2f5b18756f6c3cec7dd391cd174fa9103c747c8ea0d0fdf6877a4fc882014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006407030000000000fd440147304402205d00ab5aa7656681bcde46fb484802c2a304249dddee6555e680067577805f2b02207541b235dbef5e123e7b392c63497ea074c26f08d004e766a7711dca4e2696ec01483045022100fed3c2fa5de22702bfb31075897e03b433fa593ae0fb80794103566c490a595102203f5b01c8e7296be4e2f1a836b9be4d073bd2378816ed09baf416188d9b25c3b00147304402202cc6e2988cf32e02b25b2a2d853a507857ff6eac50427c4def421f20aa36e9cc02201313060f0fedb86d1dc00b41b437705fae3e5e3df23b254a2343afde89e17ee9014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000074f3020000000000fd44014730440220681a9e9bcbffa35a8328fbbdb173d803042ab1a858c4ca9ed0e4f5a67d453c8e02205820f9fcbb4414d41c28e3509806443a869168cd1e17b428549ec2495161f5180147304402201339021529c4f6eb6dd22b56752ad5b2df7e1e8d978439c9578f0e907974711002202c63c6b1386bea4536364610c38ac0994ae863f9f35bef25850d6173901f794701483045022100bb021e7846352c38f3fd4fd21179b448a8d34295de73fa41c57a5f972e91c355022008f06f8bd66571e4027556cfa9124b4e1e518a9e82470261db3f0a3972efbcb6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000e1f8020000000000fd4601483045022100f30681d06bbe18241bfeeb7cdd484bfb44efcd579f2c2e2ae53cdb68fa5032e202200d96812f571f9d8e8fa0756e351c7a779c46cf4ac62a6825f14a5020b771988c01483045022100996344d3a16c60b94789cd5ae4d3cfd8f0b660520c8e7602e0f8a7aae705b127022008d6d5577a2b90e2462bbccb88c79cfa2936d001e1b14ea530261235460c0778014830450221009a64402bdfdaaa64d429576b0c77601e8da643dab7437e8cfd27db5a56c05d2002202d263058b96116381e1879ad483c664341174f341537665c1ba7b8a33e378e75014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000086f9020000000000fd46014830450221009b8535b0fbf8f854787cd30b5109010183dae5807fa34e66e15cd6066dcb6b2502200783da464daec7780b546c0b6a4c274afe5f4698987820d36d6f9d5798ba2b7b01483045022100ccf647bcc6a89c6cca668aa5d4cb8577b8b1ebde8caf476c4270d96c631f6b8b022072e9d434ae2130352841477494a39168fda21ba51af04f372416a12b2a3eeb3601483045022100b71cf0b31f05baf22586a01f0e1869e8a37c9fb50e1470c6d82f0e8c7e709bff022050bb9d7638620264be1f8d69a477be454dd753beaf0ad6dd92f3d996f62910ac014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000007e08030000000000fd44014730440220563b7ed04dfec4aa05efd0911b384ae4995b1ec21be2572013f924268db89c9902205b3c939ecf522cfcd9d4678bcbfe23a31a3fab94043725be1d1e0c2a1ac6faf001483045022100bcfa16f8de773f97dbf9dcab27ead17ac1b3bfaeb16733d1bc355380595730a7022000d0125f418f48606a41c8b6f3e7dbcc21fd45f8c64be25513d58f56a0cbb0bc01473044022038b52dabadc8e617c7efaea42019fb8732a1bec5fa7a696c4c050b36968b637f02205b0bb3befba6a9fad35b34b99280523ebc37c20ba2eb9996d91c3f25a8d2a933014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001c0b030000000000fd4601483045022100df6c07633b6669c8a73e33a004924e1a1e488329a165bf73871a6153cd222b9b0220240131a2687dcb6ce091ddb241d9e533362b29e351a7e181180b09a6c395a3e301483045022100884e519da7405180a348e67a6f9065d6a28520168924cebdc291f00aa28901e402204ff72a4d9bdb0f4fe667eeb0ef658a0414fe978c71193649235251ec99e0ea1d01483045022100c54d7fc08cca52150fe97c943e8d174306f125a459f3e71b489e7d497cea63270220101d71cd023d9131e98a1630a1566ba445b8e006aed4171d09f859aedbfb010a014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000befa020000000000fd4401483045022100e1c1a88390bc698025e6e40d1005a028e02f48f4708a2d8adb6b388e8ff83c33022013afc0b581e7b70d7f515d32bb1c7da68ff84dd741dbfb35855213467fc04cd30147304402205775454f05bf5fdd98df0d27b91eeb68955477768b9f50015f41a79b6f55b67002206861d59d11023c98d3a8df4edbc30a6008f615624fb18bc7d09c76d91f3abace01473044022055e4148ace2edcef6fd14e2aa7524ba7cdc397eaa025d2decfe8dff9ef93cfd402203cbdf55e08018990bcea1255149f31661fd568f1542c3c34ad9865161382a845014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000005507030000000000fd4501483045022100db1d65c4c10c62a9a727e8702730347528a810c18734666efbb8a9fb372d553c02205e3c8dee1c43ada5aab36e5e91291129dc06bca5dbcb2f72b143e98bbf8b506a01483045022100c824496528cf45264250c975ad4926e216c51a757aafe4d374b43d20fc3f108802205f3555d6c1c819be780e0fdb0b73e90727a9ffd86620c2eae6e71dba4bbb696001473044022045e218224cb0e51b36a8fedba5309d4bd0e0f3b3c89eaca58c1100a658f5370702202aefbb0c45f6044af9e8f873909e122f317c0d70872374308b6a4506ca4918fe014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ba10030000000000fd4501483045022100a5ad7fa4026ee61e4c1e390a9d7cad33e7a23c8e84eac552ed5bd226350f2cec0220053a68add02401a90cbe70dd3ca95e02f7f3fa9c7554c9b85eb2ef9dbd7570d3014730440220458d2a918740dd329e7d3cfdb2035251503a472138b9ab19f18361ba1c05c40e0220523a8a265bbab021a019d54934bbccf0d7070342acf3ddbfb4c4d85671b34fcb01483045022100f8a7da4ff094abe375185b727847cbcf4dfb9edc7aa2c7d067153c6e4f59a479022001e0b4d4b71c818e3790accac1fcc7ab94c7949c9942224ab8146f31fe5b1fd8014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000003d0b030000000000fd44014830450221009ff8642e68b10e870a62eef426786fd90385f27ad7aeb2ed86313ef5572cf69202206ae5e31a87ee7537329c5a791ad06666797542d0d53e5e803565c46e850fdbe10147304402206477af187868ba99a2fd16ef32c5718e0710e497cf3b63c8d0451fc91aa2973e0220756a79e9d3c7ab5478caaa194c620ef6c5cb051105cd8d5906d2234226bf17420147304402200b6bec9b2956c32bdc2d39dd77790ee8469253cbed7ae4b1006eea6786bff7ab02207fde3b16eb595c5cbe2c022907f157a64a5d8dde2ce2a354228f6bf447a7b874014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a00000000f301030000000000fd4601483045022100b927e3f030945624fb57a1c78083dd5ecbd16bee095f1427fee2dcd8fe3571b4022059fae4cbe0bea4848e4d1e398f4d2aa4b2a17614fdbad4f1448a920cf73e189001483045022100f7fff756ed604f5239b0312d195e67880242c4090d92df66c2ee925c88acad9e02201aa1785336c8bdb7c604ba39809d889fcf5ee8eb63c90f4a04fe2726c58418fa01483045022100bda45c1641278939fa116163fbefbe1977628506882f873768a3c478739727d802200498f9e44d1e820ebc8e5d77e961aaf889bb72935b9274085fed3fa15f77e2f2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000078f7020000000000fd440147304402207d4a786eaf1b2a169916850e5452c4e220b790fb3f2ad669a75aa54aed1a469c02205bce95b79ea705713d6c78fed676d2f0a0af31e4716b3d91f4f3722ac571357a01483045022100e5aa71c4430cc8acbec03d7a666c90e675df226bfd9e64f36e72da3fa3615a1c022038b8e99f6a16de52780723ee3f2b72ecd193002222df75db1035629ecce2a03b014730440220097f37a7c2996aa2e49aa2f36dc1669912a4cf2e5dd092751ed9df53188e078c0220676faaa01133f821bb044cf2d6f50a459c25c4b19eaf0145b4430967db73b01d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aee854ed0a00000000dcfd020000000000fd4501483045022100e60da48ef5ba5b29ab98cbe2ef99ff0a2bc92cd85024e49e5ca3597f12bcf300022048e6b0090e5c8057477dd7eb3789ae0b8fbdf5362421eda08c76d859ad7b544b01483045022100b4cdbbd212bbda1f70d6793d9956039d7edaa8fd70bfea7fb6a04bed7319e3780220425f48e5b29e7452107e19fddaec28e5846e04a2df14b80fc143bfd71e6266df014730440220279c65bad108d0007d821385aaad55ecad775a436de2b4e32988815020764847022067a86c7c33ece430d743102f84a9c48b29b9de9da821b6666929b13fb34e96e0014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000000f8020000000000fd4501483045022100e0d21364935dc638b62cc87e9a198a47afaa348143c28c35e3c1d4424bdf4121022008bd790132b5f6192ea6187f5cb5a9ffc6435eff720357382739d1a961fd9d890147304402203aab999b99e7753f39e2ef8d450241b911f6ffc4d9e858ca6743b2a5b7ff236802206344c728587b60b56672c4ada3fa58778cf4905697d46d026e1019eba70d1e3d014830450221009525cca77d25075ea465237b5259c3227c2924507e74851db33a71d6434a90fa02207fad277b64c89ade6a42a682f049b462e8257be13ee27cd16145b52bf60847ec014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000004ef5020000000000fd430147304402205879477cf645049300fbcb532853fd3672c4f00ebe8866df4479feec600e4217022021e95feaf3015d9d03fe78b3a0b5b61003e4182709aa7db198a050d9ca6d54be0147304402202df5b7711a3ca99433ca9a6bc6894e95ee4f1c5c94811f771e22fc7eba8514ed02206a2763d0ccd7a641d2bbca36110a4d0972bf5ab2e4660306bf8af866117759670147304402202a22e0d36d9f55d479414abfdba71a933083f44704997d0eeafca51a3dd8d37802201a4ec8031ee9325834f31805fa8bd50e5f9fa165b965f2027500ef0ee0b8138d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a000000004412030000000000fd43014730440220221ca9f2149986b521ded423d8bf044b33502c128d5820c7ef6ea240a5bac8cb0220567cfd17e603a28a961f2a2ee88443ae709f5fab0d3cea9aa6232f5ea2570e000147304402202e38267764d91a348d25b328c11ad05323228bb3769334527418cc83df6815b7022072d856c6d2d45b1947f99e9c8e695da78c97e956eec9d75e2ac2f7cb450ae4240147304402202baa2f128838ad113ebdd95bd16db144ccd1f29be98fe435f145e7ca954c44b40220279934a60ff066f77c05031d6624cd1c7de4e120835162a2d43021526ad84bd1014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006c0a030000000000fd4601483045022100c858877b7ebab9df2f7b0681239c92e6c03e95d963e42a075fad8c452e045b3a02202e04bbef330e7fbec49a7ba9859a6cadfd61830ab858362a2a695ad4db84ec8c014830450221009139aef771bb21f6ccd0b357d5e62f0991f98e67039b4ef146ca74c7a95883a00220268ea8e98573e4199ddaa75d2bb08f6b694af8bd383614f59e8699eacd7850be014830450221009694018de0f453d6732941e22309064ef42127d412ad135808acd9031e058fbd02202b4d893c63a553fe2401c983d4ab5a2f1ae9d411c195470be7d895ea4284b86f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ed10030000000000fd450147304402204a295e4d135f4f6421bea46bdd97d452b23b262408d77fe38c1f090f5f88651f02206f79e3a47f198091c92cf5733f9ab0acb3347e21f1d66ebc53e01db3dbd6845701483045022100a606efb31d0eb4cc4112721878cc3bf2fd0fa2b2cd3337a98cd3304e0cd2e38c02204b6117547daae39c06d7477c8f5e9e0e0d767007326df7b027cf80ec36282af1014830450221009d3a3360ead284b47ccc8a13eb1ad8de55563dbc5ff0a98744fde8947ff5484202203c350ab88bd4650aaa762bb1379022abedfd9316e9771e3c516190d07fce0349014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000be09030000000000fd4501483045022100bc76c0450c83c1a704cebf734d36f64e1ecdc76fdc1cd6e5f7986caab2d1dea902206bd1907f48bde52c8761bea4b7cbb848b2331ea58e6fbe683ecf2d60990001400147304402203287582617de76c70e8f73fba20100471cd40b4d6cff4b883a9ed88922c96dde02206e36f90628e7822206d93fb668e759ed62d6b73d3ecde935187d239c84d296a401483045022100957d7302576e215c6e97eeab33854a139f88839377eaf10edd1f80d7567ed5e102204452ec2dd7f273d586d7ca83f80c554c73531ec689bd961b524c2d0f2b16049e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000fa10030000000000fd4501483045022100abeb8758b4ccf148e91c24c43b46c34330210bfa56eda2afa91c64f496215b21022046c5ac2cdb04ff964369cc6137b8c6b1133422640769d9f7bcd66cc45bea71b501483045022100901da0b92045f0c0195447fd2ef83b9cd6944e75e685e38a6ebafb16a03087a0022031f2261fa0f6486d000daa590875596ddd40fe99e943ee9d6bb8bd9129b0f90801473044022021e942e7c2e562631492f4c7bdab659af69efec054e5cf8432b9cca6e2b35579022051ce1c6da198abca7414783ef73e9119d6a345a0f0f989c53d74fb506c5c8fc8014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000850a030000000000fd44014730440220132a4fcc7f1a8f6f3fe4cd9ccb32fe3433a4e450f8594424e2389368f6a7203b0220075d01f8b9ed12b416d1d04cb9e95ceccfeeb420fb49720b4a3f0d1404be2f390147304402203707b8551104cac7c862aab1a2f730208f8ecee7180ac474b409be4c6af73fe0022023e293d0368a43397a93928d61586bc41c6c7bb1793b2bd2639ba8520014783a01483045022100d4504304bfa64c675045ffedf26e9b85a3ab839df273d93e2b1b9ff12338c7c50220080f395e7663fb9539181eee186a6960d69319333cbeb4fd5d596226c7cafa34014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000066f7020000000000fd44014730440220048efe6d5dd87a48a1deba890723e0e568ba8fe0112d29537237e0a1ea1c38070220197fda66031d9df627b79538eb4de824b9b49d94fab8aed185674ea4beac7bb901473044022006bc3ef529905db1a6729c0c8755267f63b3de05a0e1df68f9a2a983596e780e0220017582ffba0935ae9c8739bd6be3d21f9aec531ebe36e12073a5ee985df0110a01483045022100ef338afebdcddc9f46ac98ae9d9f936e44241cc46584096980690174b223da3b022062e0cbe00bc6ee9588ddf92e3c9c74f55632743d3d43dba4e43747fb380a4727014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000003901030000000000fd4601483045022100b21b940e774245ff2cbd5e797973a36ae61bd59c5a24081e73894f5823150d4202207c90190110b9607b151db47b81e7008b3fbf18ab952f83cae3189e4324a21709014830450221008949ea570a61d78294fbe8c2453a211e152b0b2190078b3cb085ef58fa2d47de02206dcede7043213254f01ed0e4b73d1f5d4550ac5fdcb815972b788edd1dc626b901483045022100f8b59f01836c28cbde52114bc8bebf8ece08f45ffe1660266e0dcdc187687bed02200b59ef8ca9b9a4d8d9c937014ac9bf66b7638101c89f10549cb28ee2bd7ce63f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004313030000000000fd440147304402205cfc98a5c55837d45207a22a7e20b646d0805a4d35e4bd10b2bbf3fff6ec0f1502207df92aa9d79c5eca866ddc95c446c774947a824e0901361dd42073f277212b66014830450221008845063e4b5c6387f1333d76010493b23d79d47f19b227b659daf8c12369e87a02203b7002cdd2ebf3974be41a5c0dd6ed267cad19b2fdd0526260e52a738bba2de4014730440220575a271b955e464d335bb32cece0813fc730be1c98f3b41d70739e9457c8b6d602206dba0e5fb0f0b3465736cfed2bab04e8f7defbb9f11c35cf1601b95fdaaa8829014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000a8f6020000000000fd45014830450221009cb6793416713945a3b6b08e10139d5c58f3ad21ba52f45913bbeb33d8ce1f5d02202e550c9d430adfaa81b4c9f6f27c2de234dfdf3936c5ad8a3beead34a8a62fde01483045022100807dfe6e8df9e514b6850f94c04efae0e927466c88bae4985d1f5a8014a5504302205265e75bb3d7b2ecbed322b35e998d904a2e4f2a44d5906807ed06566dbf14320147304402204a629c423c8ddb65ebfd05dccc9510aa3e8119b02d73e5997d559b7f9ad6a87d022029190b7200278ad005bf2f1a7aed9c864f9733ac3ec5bd7102ab18de6f44cd17014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000a6f5020000000000fd450148304502210094ef0e131bead508301ecf03a7ffa18ec9263bc3666450d2bddcfa752107310002207b8cac555f7a132aa98e41d2feff833fed68752601d0f84031eb870307630a480147304402203dffac6f1e5b22325cf2ff59a98665a827852a36225bb7db58c013022ce037cf0220502c495cfae8edbd53ca9e8b6b9bb4efbd1278966fd9156297959696d445c06f01483045022100f9d9f9b2058abc9aeb02bcebf6f5b22faaa6356ca640550b3c20744cbdee77fb022051c28736be4150081d5aef3e2253a5ce9d3ab9c4578beb398561029be530ebf3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000003307030000000000fd44014730440220521bdce46b83ae123bcc4f0a008d2bd5cf76e76e0f98f21e029cdb2d0a4f868d02201d3abecb1216d4b41e6c6b23ad87feb8f7ba7735508a4f33d701289f736f366d0147304402203d15fd7c4c578a8acc93a1ec867199efec7f3cd532adf75e9a689af0a2b9399d0220598e77fbdb195843a2699c3464bbb5ccc55e608ec06fb2723c449469766d212c01483045022100e82cc5d9dde9a9086518ec2c18fa867ebb46112e37d65d9f34d88ac741996c95022053e900ac59bb0a391677ea209611b858a89540e59d66056852f8bf23fe5670d6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000007eff020000000000fd4401483045022100d71d8a50ea69c55f7fd08defa76bf1b4da053e3575544b0471c461bf5aef137902207fd9f81b4a551bb7afa8dd1b69a0f6b1f2c4e24aa93c992fa82f82471aa8f72701473044022047ab50ee3683939bfef7410135d6b8de00f9405ca5f2faf22979df74bd4ce974022077f23743ab8ae449862ffd48caf40cbac62fdad49e005b7212852d085e82ee020147304402202e98296f1568f3b1fae805d2d6bc6fccd59bf4c5d6f1565b644d142297d7c40902207abe8a1988115e0ab88180cf90821005e645d97ce360d372f2f329fe2a284f76014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008604030000000000fd4601483045022100d0befa3dba35057e2ef9b8b6d873dbe983166816ceb84eb875b346ce720a6b33022018d3acee74afd13496e6f1c06e7802d7813ad42a890be8a83280dc06ea23941c01483045022100822d27a60f0e4a8c37acfaadbb0be6d49c0981613610fa63c1f162d24e79e4500220316448804078b2df0d126fb9ec97dc1584ca84567332aa3dc2acab6f573856b0014830450221008fd2b5a6421611baaa3deb3739720dfec37f5cf95f93aff572dd92dca8925fa202205d2b99181fc69d7a46c7c58367a3927b1f6fdcf136142cf85f250837fe643454014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000180c030000000000fd44014830450221009c3ccf4b91b6e425a70216bafbcd277682da8bdbdff336b6f58cb97dfc3204cb022058f650ea3ad9d26b2c66f17cacdd10bee5446caa1ee34145061c227a56620225014730440220092afde83e1995e9964b85224dfc4a3782df00a0f982eb4ad3b38d083428149b02207669684a16f7af5563fe061874c80d9e35e745abbaf7396c7f71267f50fe52010147304402202858c626b1245cdfceefa6440e0c0ca838c10d6114098fca6df76559577de29302203fac70696457e9bfec335fa3a892ec3a2161a34c5bdec4a375d63f76a8d51fbe014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000066f4020000000000fd440147304402202397ebef3e1c8008050009f306560d15c5395fa07fae571fb55910749c3fb321022044acc8eab9dc7e1a9b4ab2cb167d0f944398a0b5e2c4d6e007f6f64779ab011301483045022100f4da2295f9558216c77cd9a0c56e0afc84e3ff1e6ec6b286ea5d7ab5a9f0b4b9022047d7353598ee12ea23234fa4fcdc05d7f9930f276cf4ecee76d128fe148596700147304402205216b5502fa0f62c6d5c9844f7b29ada8cda0fdce7d4dbacf8466adffb0eb6680220735d293adb02fb781f1cc02f9d1ef5a64c9436a95b4fc111dc0d69525ebadb03014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004e09030000000000fd4501483045022100972f0d3af3ad98263fb8f500d6dc9bc75a589148b712ac0c7f95c78feb5a516102207316e12601bfb099b54075e67563a09df9e023094411ad19cbb5850a3778f067014830450221008ef3b3fc80b147b0ec47663d8756ee4d59e6ebbfd53a6d8629d1f30e9d17b70c02206583b2dd5b4f25d219e05da7fc34203ef667e0d49530e6211827f9f3041d3092014730440220088d12eb6a4424bb67b1aa85b8854cec0a959a62ee26e825a64bdc69c5e1219602206912f9adc20c5fe9f46ec852a6f433f276d1480f6a495a7984cf676a925a4f02014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000d80b030000000000fd4401473044022061236f509bf2232223af7219d64a2064dc4c12de828d46a1313b8f97b8a1b73b022043e93a6c2884668dc499ef67e370a6d736318ad39708183d2a702945a53e7cb2014830450221008e6deec7ff876824ee0f5a61f874eb5c303edd2717f5ca866afac208d591cb1502202fef21def83459e46dcb60387fa6ecac65adaf2ebec8d1c4bad92d1b0a928dda014730440220298c9412d02c1c39acfcb9dffa66cb5745904dae9d5f619b29d909cb9d6538ea02201dd6ca43c5fbe2038b6c347ce4e6a35e6d3e6f9703647661f0aa941cfd6136bc014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b70c030000000000fd4501483045022100ed44d291e5e7c67d7817a0c1efead142048af4b2925df521f5ee011a6bbb797c0220268bc28e0b7f3cefd25b765801d25bc6de760361d5efc4ff4cc37721f191770c0147304402203425355cf89de0872aac696e9a54e1eb47d71b67a878b4291e505f5358b4c8f102205b7bf0aecb2c022fb93d72512fe820b1f9c0f396a80a75dbc72517d41006e12001483045022100b73867e6de73cf880d7eaa7a70f74685284ea52f3fae71eca4e8905d88367248022033f60a882ec544e5607201602229ddc7508c39ec25de2ee9e18e0bffc8f1130f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000dcfe020000000000fd4501483045022100e565de2989fb12d59dceb8717fa7efe1d430031701a178e93a860b6e7c593b5302207a2c8ee53cc8deceb79e959fdbe72f7fc30e97e51652ee6043687a9f68e24eee01473044022035da650da8f643511b343b3314aab45635f5c7a70d09a34eccf208ee8c0285a602206d3125eeb0ba309c14dbac2a67fc807f8e805d210e8b4d36966ca67e80e8c5a001483045022100d2336814da44438d4e6792dec2ed9017ffe295dff7877a326eda47e76bfa587302200e5ddfdf57537fb32b0ff333744c4a426488b8199923de93691e138e7a00928a014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ca0e030000000000fd440148304502210085f57e21fb1cd388db4e5c62353dc7f520eab422f3d7ef8624552d1385199ce502206565aa1221fc512ae6236bafc08cb8157d774e1d499ebbbe712c10032f5aaf1601473044022047a029d05cfc8da77b76625abb93ee65fa4ffd23672a7d0348e45c145cba13050220740e4bb703957e56da185bfcaece5e4d3cc602271201dadb2d5afb5475c96283014730440220573c5fd3b203915a057765b361f08082e2d891e57bcd010ecaf6e8d307358b54022018848d5a770f33d667e792853f454359d1bf1c1c846dd00ee3cb3296602dd5b8014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000000bf8020000000000fd450147304402203b89df3d311981d4918681f0ee17a629ea632592342085cf1c1794f75626b7d00220141f2bb0d18b5f07d5549d279437c82319ba23b5e23d8b37ee217150331fe05f01483045022100a5b0d516a0457646836b850c195c8389b2747abbf747a557e96059190ad1e38302202ab7e8d5001290350544299dc3556e9abb9da2e8f91dd2ce3b00fa0c718cdec601483045022100f7f5b03dd0f3d6a7edf18847427ac60fb49cb626b074cae1ea95f233960a0729022000aff8e788a856b194effca3a08594ce28d97138143f0b205556886b4a098fec014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008705030000000000fd4501483045022100c9728ad82c64ab5d548752adf590654a58eb4bfaba85f1f504b57708abc3eb5902204c36886103e1aa4da4c96bb52182710806b25b9b18310d4b9eb574027eddb3100147304402205017577d2e107371b7c8bd17bc15e128e4e1186cef93ed260d6f54947e94382602200ef247802da72844a33163e6d12136027538c6855ff5761aa5ffc65fda7c810a01483045022100b56e938da3ea2ae0248afa7f6be5e8f7ddb81cd3c82e16a41919864482c28ca102202eeb3e20e95448e46c340736d980fc1b75d860264edb814f01e0e430521f51d4014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004203030000000000fd4501473044022032a1358398cad98c16a8829ffc75fc7b44f77b93d5627dc717b66a44b59dff7102207b0472f44654abdfe1a24eda879f28838bd5184dfba238fb1b3d4ed0adba15a7014830450221008cbaf2f1616afd756d773443a52e55acafdf5202c7cecc5d0775d681de0e903902201218f989b0300a0d75672676a20c3136ef671f5a4c52ea34213eed22b3ee382401483045022100bb7ccc2f7822a511aac2104ef57e34a04c37f3e09bcff1bc779df446a0840d020220789dece4f3172564a56b6001dfddfc1b79e41ab6d156a40f020bb5dde18877a6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000054fc020000000000fd4401473044022021c6612e0d9d3a57628d54fdfb7ee9756d41ad3247c8c460c499164b6ced3fb302206cb072bf1b753b8ff407b767160acf074f6f34286f4caf3ab5061fb4c980fd270147304402203b4f06ae4d5314e5dd95341c9668b16bc4a71efa1ea4bda2403d2be25e9baf8e02200f09352867d755d614bdb6af4cf22211c93c420fec4fa6fcdacae32b11d1036a01483045022100efd849b2c87782427ce6241c9cb87c60eb0202c12d1ce5f2306b25e857aa8f8d02202eac2b493b0be06093bd4c35d111fb5bdcf24d5795449ebdcbbf234237ecb837014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000c2f3020000000000fd450147304402202649f1955b9a482cab0eef5bde264adf912f5c9351c5d9c10ebd74a7b41dda4602205675c9cfb36ad8ed29e7d431bb7b362e001e999b52d10939636147d19d8de00a01483045022100cbe00b8a033296b9b9e269388a63fb96c4a8f75085c941fa32b037a5991932cb0220020788969972594ccac489afad39bcad1534628f6a649ad5b34f14e5e9421d8401483045022100b7160b1edb5919751ecf6d18c98cceeac9f5941c1854af8560432b34b77dd74b02207105457d94cc4839df8af02f9abc34302eca1b061666e6516d859ec73d89a748014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000009209030000000000fd4401483045022100a54e526956343cfc84ce3e428c138d40381003386375d4ebf9d246101ff678e402201c80e61034511ae79e486380e3a652af58ad5b489aa15f29ec56fb84864e768f0147304402203bdeb20123223d56f13e68817d4a29c87e690540019813b2cdcb4fecbab7044b0220396a1d6971b69b4ef3e63e8727f093b3d7bface1d677a798b4a2d5ece218e8fb0147304402203ff11e576e45e7254fa5959c93956a97c09bb123e95da708001f2ede5f9ba8c602200f9f36ba15933a54a4758d97b7842776bd9706c6a8fab6a58649b9040824634f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000670a030000000000fd4301473044022029bb920d11e2bf9201d18e7730f3bc5f6553e62bc8314336ae9c8a5859914c1502203bdacff928d4d666507dd2583b9442959c88726a817c67348817cd65e6c3606601473044022077a8ad215d7005f651f3b73a4f5a2e33e98dd2168268e04eb41af2ed6435ce2d02200af0d91cd2880f4158d175e28bce4ddc3fe50ad78b87f969c3662ae242930d980147304402205479fac1941e63c1de3cb25c4760b13f6b62ca59cf7942fb3170fd2d195678f9022040e827f224ea5551a4dad03daaf0ad63ea470b4fceffa8e4bd495e7560f32289014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000fc11030000000000fd440147304402205c09825c601853704b1b0231fbee7591c79b6cbaf30e2e955a0b6a72793b370502203fa13d433613f7e6e63419d5db0ade84cb18d699da64a2138fc086afb6066e8301483045022100eafd46546faf8c4e1152145d0a5c30ef070d5e455b6b3b685e1cb8f47cc2a0030220452568d3b52d7a6f7b1f30f321cb4481aaf8af3d7223d5e3de2d6612fa2c41bc01473044022012fc1168c457070445b883dfbbad6996a9dca5eb006b3755daeee5908918a6bc022063b589fae666c446199742cd78eb1843385660f2a962492476c6cacf3997c9eb014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000a60f030000000000fd4501483045022100872b40bd06e8bcf66cbc8ce478d9ccb62972670682146d19b06e63b919a86da0022071cf9163b60e61078f4a5be7a46cee4978585f7eb27387c0b27ae696c3d79f4701483045022100848eb32ab0284db06df29338a34ec5d63660d8ff8838501acc00694b203a346b02203f67e1f2a8d05894f14e965748ef9391675c263afc356519f3516df5adbc87ff0147304402201a20d9f8b3e236b0807d84a7d7e041d77ee26f002d2b77c695fecca316c01b5e022035edbd8bbd6917b892166fcf4b8202b93609e04440ef7b6d850c12409cb3c1e5014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008409030000000000fd4401473044022009153c7b1bfdfe8ac504213c3ba3321ede43e2dc89261d803601ed6e87cace55022051969259b11cacb80d06ca68563ebdbf8a6e85fa88e9c14afc5797dc82f0fdca01483045022100951209e2d941dd5ba148ef2a0d4a0eaf09c9e0c22051719cb5f3a762517964ab02204acdcfddb8b8aa6a1f61b5126dfb607a472b22d5f731e1318f2405732fc54e750147304402200cd5be575d4b681975316726c211db5fbbc3037d29acdeb06bb7c72119fce5db0220514ea54faba32014d3422ca09c2ebcd332888370249af76809a9f5e4c380dfe6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000e600030000000000fd45014830450221008d99ed1beda44a35e04ed4b6a20a9335c29d6fa974011c06f7a4467420b7ea39022041faf1da827bf32bc43e56a3de62512491209b1175c8779afe3e07731db7620001483045022100f38d3d3adec74d05204c0c9f0b79521abdc57f23d9599a0423d0227294a0ae3e02204ffe4c18ccf208ce95ade49bb78cfdd86aded1071e1fcede37e19252ee0b7882014730440220477a3fa0d716f8ca73d0f5cd55508231dc05d90d14ee670527f30c7f68406e4302201ee6504b744a53375afb2e84fab902ff8d299d6d897bed9db8dcbd85fd890bad014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ed13030000000000fd4301473044022075d9d2d1d73ff65f1c3cb5fc705aa3f117b866901a527607c81807408fc924180220062031fca37eed36da2f119ad7f5018f8fdd6ca01f42c4bfebbe8550c17e8d690147304402202897a5b6ecb4426de79bc5be348c4b359c7a9adfec0c152345538bfd5279a3e8022053992fd5f382089e1f41e0c47a1ecc11cdd604dfc46210e6053b7f690e7ec0bb01473044022031a31eeaac96cce93cd3a5fe3d75e7faf99f17a5bba04a6a8387057815663ced0220266867c47ad462ab2e5a794b5db1325e0dd75588aa45e10dfb9c219918eb7af3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae \ No newline at end of file From 47806df63d2eba938fd1c27ff55b69ed7651daf4 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 23:45:31 -0800 Subject: [PATCH 002/108] txscript: Add benchmark for CalcWitnessSigHash --- txscript/bench_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index b129b80386..037ce531f7 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -50,3 +50,23 @@ func BenchmarkCalcSigHash(b *testing.B) { } } } + +// BenchmarkCalcWitnessSigHash benchmarks how long it takes to calculate the +// witness signature hashes for all inputs of a transaction with many inputs. +func BenchmarkCalcWitnessSigHash(b *testing.B) { + sigHashes := NewTxSigHashes(&manyInputsBenchTx) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < len(manyInputsBenchTx.TxIn); j++ { + _, err := CalcWitnessSigHash( + prevOutScript, sigHashes, SigHashAll, + &manyInputsBenchTx, j, 5, + ) + if err != nil { + b.Fatalf("failed to calc signature hash: %v", err) + } + } + } +} From bcb9643d39d86d21ce7bf0ac11464663b22df931 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:02 -0500 Subject: [PATCH 003/108] txscript: Add benchmark for script parsing. --- txscript/bench_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 037ce531f7..51f9aeab4c 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -70,3 +70,43 @@ func BenchmarkCalcWitnessSigHash(b *testing.B) { } } } + +// genComplexScript returns a script comprised of half as many opcodes as the +// maximum allowed followed by as many max size data pushes fit without +// exceeding the max allowed script size. +func genComplexScript() ([]byte, error) { + var scriptLen int + builder := NewScriptBuilder() + for i := 0; i < MaxOpsPerScript/2; i++ { + builder.AddOp(OP_TRUE) + scriptLen++ + } + maxData := bytes.Repeat([]byte{0x02}, MaxScriptElementSize) + for i := 0; i < (MaxScriptSize-scriptLen)/(MaxScriptElementSize+3); i++ { + builder.AddData(maxData) + } + return builder.Script() +} + +// BenchmarkScriptParsing benchmarks how long it takes to parse a very large +// script. +func BenchmarkScriptParsing(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + pops, err := parseScript(script) + if err != nil { + b.Fatalf("failed to parse script: %v", err) + } + + for _, pop := range pops { + _ = pop.opcode + _ = pop.data + } + } +} From c997417978f04881cb0cce570fd9c2cb6d74ed27 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:03 -0500 Subject: [PATCH 004/108] txscript: Introduce zero-alloc script tokenizer. This implements an efficient and zero-allocation script tokenizer that is exported to both provide a new capability to tokenize scripts to external consumers of the API as well as to serve as a base for refactoring the existing highly inefficient internal code. It is important to note that this tokenizer is intended to be used in consensus critical code in the future, so it must exactly follow the existing semantics. The current script parsing mechanism used throughout the txscript module is to fully tokenize the scripts into an array of internal parsed opcodes which are then examined and passed around in order to implement virtually everything related to scripts. While that approach does simplify the analysis of certain scripts and thus provide some nice properties in that regard, it is both extremely inefficient in many cases, and makes it impossible for external consumers of the API to implement any form of custom script analysis without manually implementing a bunch of error prone tokenizing code or, alternatively, the script engine exposing internal structures. For example, as shown by profiling the total memory allocations of an initial sync, the existing script parsing code allocates a total of around 295.12GB, which equates to around 50% of all allocations performed. The zero-alloc tokenizer this introduces will allow that to be reduced to virtually zero. The following is a before and after comparison of tokenizing a large script with a high opcode count using the existing code versus the tokenizer this introduces for both speed and memory allocations: benchmark old ns/op new ns/op delta BenchmarkScriptParsing-8 63464 677 -98.93% benchmark old allocs new allocs delta BenchmarkScriptParsing-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkScriptParsing-8 311299 0 -100.00% The following is an overview of the changes: - Introduce new error code ErrUnsupportedScriptVersion - Implement zero-allocation script tokenizer - Add a full suite of tests to ensure the tokenizer works as intended and follows the required consensus semantics - Add an example of using the new tokenizer to count the number of opcodes in a script - Update README.md to include the new example - Update script parsing benchmark to use the new tokenizer --- txscript/README.md | 4 + txscript/bench_test.go | 15 ++- txscript/error.go | 6 + txscript/error_test.go | 2 + txscript/example_test.go | 32 +++++ txscript/tokenizer.go | 186 ++++++++++++++++++++++++++ txscript/tokenizer_test.go | 259 +++++++++++++++++++++++++++++++++++++ 7 files changed, 497 insertions(+), 7 deletions(-) create mode 100644 txscript/tokenizer.go create mode 100644 txscript/tokenizer_test.go diff --git a/txscript/README.md b/txscript/README.md index 004c586d61..f0abb51884 100644 --- a/txscript/README.md +++ b/txscript/README.md @@ -37,6 +37,10 @@ $ go get -u github.com/btcsuite/btcd/txscript * [Manually Signing a Transaction Output](https://pkg.go.dev/github.com/btcsuite/btcd/txscript#example-SignTxOutput) Demonstrates manually creating and signing a redeem transaction. +* [Counting Opcodes in Scripts](http://godoc.org/github.com/decred/dcrd/txscript#example-ScriptTokenizer) + Demonstrates creating a script tokenizer instance and using it to count the + number of opcodes a script contains. + ## GPG Verification Key All official release tags are signed by Conformal so users can ensure the code diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 51f9aeab4c..673d1cc31d 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -96,17 +96,18 @@ func BenchmarkScriptParsing(b *testing.B) { b.Fatalf("failed to create benchmark script: %v", err) } + const scriptVersion = 0 b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - pops, err := parseScript(script) - if err != nil { - b.Fatalf("failed to parse script: %v", err) + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + _ = tokenizer.Opcode() + _ = tokenizer.Data() + _ = tokenizer.ByteIndex() } - - for _, pop := range pops { - _ = pop.opcode - _ = pop.data + if err := tokenizer.Err(); err != nil { + b.Fatalf("failed to parse script: %v", err) } } } diff --git a/txscript/error.go b/txscript/error.go index a61d02729f..f42b893ea4 100644 --- a/txscript/error.go +++ b/txscript/error.go @@ -1,4 +1,5 @@ // Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2015-2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -47,6 +48,10 @@ const ( // the provided data exceeds MaxDataCarrierSize. ErrTooMuchNullData + // ErrUnsupportedScriptVersion is returned when an unsupported script + // version is passed to a function which deals with script analysis. + ErrUnsupportedScriptVersion + // ------------------------------------------ // Failures related to final execution state. // ------------------------------------------ @@ -352,6 +357,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrNotMultisigScript: "ErrNotMultisigScript", ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs", ErrTooMuchNullData: "ErrTooMuchNullData", + ErrUnsupportedScriptVersion: "ErrUnsupportedScriptVersion", ErrEarlyReturn: "ErrEarlyReturn", ErrEmptyStack: "ErrEmptyStack", ErrEvalFalse: "ErrEvalFalse", diff --git a/txscript/error_test.go b/txscript/error_test.go index ebac85fdaa..abfa156577 100644 --- a/txscript/error_test.go +++ b/txscript/error_test.go @@ -1,4 +1,5 @@ // Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2015-2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -22,6 +23,7 @@ func TestErrorCodeStringer(t *testing.T) { {ErrUnsupportedAddress, "ErrUnsupportedAddress"}, {ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"}, {ErrTooMuchNullData, "ErrTooMuchNullData"}, + {ErrUnsupportedScriptVersion, "ErrUnsupportedScriptVersion"}, {ErrNotMultisigScript, "ErrNotMultisigScript"}, {ErrEarlyReturn, "ErrEarlyReturn"}, {ErrEmptyStack, "ErrEmptyStack"}, diff --git a/txscript/example_test.go b/txscript/example_test.go index 7bf2b3f059..6e17341c4a 100644 --- a/txscript/example_test.go +++ b/txscript/example_test.go @@ -1,4 +1,5 @@ // Copyright (c) 2014-2016 The btcsuite developers +// Copyright (c) 2015-2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -180,3 +181,34 @@ func ExampleSignTxOutput() { // Output: // Transaction successfully signed } + +// This example demonstrates creating a script tokenizer instance and using it +// to count the number of opcodes a script contains. +func ExampleScriptTokenizer() { + // Create a script to use in the example. Ordinarily this would come from + // some other source. + hash160 := btcutil.Hash160([]byte("example")) + script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_DUP). + AddOp(txscript.OP_HASH160).AddData(hash160). + AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG).Script() + if err != nil { + fmt.Printf("failed to build script: %v\n", err) + return + } + + // Create a tokenizer to iterate the script and count the number of opcodes. + const scriptVersion = 0 + var numOpcodes int + tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + numOpcodes++ + } + if tokenizer.Err() != nil { + fmt.Printf("script failed to parse: %v\n", err) + } else { + fmt.Printf("script contains %d opcode(s)\n", numOpcodes) + } + + // Output: + // script contains 5 opcode(s) +} diff --git a/txscript/tokenizer.go b/txscript/tokenizer.go new file mode 100644 index 0000000000..72e00f07e2 --- /dev/null +++ b/txscript/tokenizer.go @@ -0,0 +1,186 @@ +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript + +import ( + "encoding/binary" + "fmt" +) + +// opcodeArrayRef is used to break initialization cycles. +var opcodeArrayRef *[256]opcode + +func init() { + opcodeArrayRef = &opcodeArray +} + +// ScriptTokenizer provides a facility for easily and efficiently tokenizing +// transaction scripts without creating allocations. Each successive opcode is +// parsed with the Next function, which returns false when iteration is +// complete, either due to successfully tokenizing the entire script or +// encountering a parse error. In the case of failure, the Err function may be +// used to obtain the specific parse error. +// +// Upon successfully parsing an opcode, the opcode and data associated with it +// may be obtained via the Opcode and Data functions, respectively. +// +// The ByteIndex function may be used to obtain the tokenizer's current offset +// into the raw script. +type ScriptTokenizer struct { + script []byte + version uint16 + offset int32 + op *opcode + data []byte + err error +} + +// Done returns true when either all opcodes have been exhausted or a parse +// failure was encountered and therefore the state has an associated error. +func (t *ScriptTokenizer) Done() bool { + return t.err != nil || t.offset >= int32(len(t.script)) +} + +// Next attempts to parse the next opcode and returns whether or not it was +// successful. It will not be successful if invoked when already at the end of +// the script, a parse failure is encountered, or an associated error already +// exists due to a previous parse failure. +// +// In the case of a true return, the parsed opcode and data can be obtained with +// the associated functions and the offset into the script will either point to +// the next opcode or the end of the script if the final opcode was parsed. +// +// In the case of a false return, the parsed opcode and data will be the last +// successfully parsed values (if any) and the offset into the script will +// either point to the failing opcode or the end of the script if the function +// was invoked when already at the end of the script. +// +// Invoking this function when already at the end of the script is not +// considered an error and will simply return false. +func (t *ScriptTokenizer) Next() bool { + if t.Done() { + return false + } + + op := &opcodeArrayRef[t.script[t.offset]] + switch { + // No additional data. Note that some of the opcodes, notably OP_1NEGATE, + // OP_0, and OP_[1-16] represent the data themselves. + case op.length == 1: + t.offset++ + t.op = op + t.data = nil + return true + + // Data pushes of specific lengths -- OP_DATA_[1-75]. + case op.length > 1: + script := t.script[t.offset:] + if len(script) < op.length { + str := fmt.Sprintf("opcode %s requires %d bytes, but script only "+ + "has %d remaining", op.name, op.length, len(script)) + t.err = scriptError(ErrMalformedPush, str) + return false + } + + // Move the offset forward and set the opcode and data accordingly. + t.offset += int32(op.length) + t.op = op + t.data = script[1:op.length] + return true + + // Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}. + case op.length < 0: + script := t.script[t.offset+1:] + if len(script) < -op.length { + str := fmt.Sprintf("opcode %s requires %d bytes, but script only "+ + "has %d remaining", op.name, -op.length, len(script)) + t.err = scriptError(ErrMalformedPush, str) + return false + } + + // Next -length bytes are little endian length of data. + var dataLen int32 + switch op.length { + case -1: + dataLen = int32(script[0]) + case -2: + dataLen = int32(binary.LittleEndian.Uint16(script[:2])) + case -4: + dataLen = int32(binary.LittleEndian.Uint32(script[:4])) + default: + // In practice it should be impossible to hit this + // check as each op code is predefined, and only uses + // the specified lengths. + str := fmt.Sprintf("invalid opcode length %d", op.length) + t.err = scriptError(ErrMalformedPush, str) + return false + } + + // Move to the beginning of the data. + script = script[-op.length:] + + // Disallow entries that do not fit script or were sign extended. + if dataLen > int32(len(script)) || dataLen < 0 { + str := fmt.Sprintf("opcode %s pushes %d bytes, but script only "+ + "has %d remaining", op.name, dataLen, len(script)) + t.err = scriptError(ErrMalformedPush, str) + return false + } + + // Move the offset forward and set the opcode and data accordingly. + t.offset += 1 + int32(-op.length) + dataLen + t.op = op + t.data = script[:dataLen] + return true + } + + // The only remaining case is an opcode with length zero which is + // impossible. + panic("unreachable") +} + +// Script returns the full script associated with the tokenizer. +func (t *ScriptTokenizer) Script() []byte { + return t.script +} + +// ByteIndex returns the current offset into the full script that will be parsed +// next and therefore also implies everything before it has already been parsed. +func (t *ScriptTokenizer) ByteIndex() int32 { + return t.offset +} + +// Opcode returns the current opcode associated with the tokenizer. +func (t *ScriptTokenizer) Opcode() byte { + return t.op.value +} + +// Data returns the data associated with the most recently successfully parsed +// opcode. +func (t *ScriptTokenizer) Data() []byte { + return t.data +} + +// Err returns any errors currently associated with the tokenizer. This will +// only be non-nil in the case a parsing error was encountered. +func (t *ScriptTokenizer) Err() error { + return t.err +} + +// MakeScriptTokenizer returns a new instance of a script tokenizer. Passing +// an unsupported script version will result in the returned tokenizer +// immediately having an err set accordingly. +// +// See the docs for ScriptTokenizer for more details. +func MakeScriptTokenizer(scriptVersion uint16, script []byte) ScriptTokenizer { + // Only version 0 scripts are currently supported. + var err error + if scriptVersion != 0 { + str := fmt.Sprintf("script version %d is not supported", scriptVersion) + err = scriptError(ErrUnsupportedScriptVersion, str) + + } + return ScriptTokenizer{version: scriptVersion, script: script, err: err} +} diff --git a/txscript/tokenizer_test.go b/txscript/tokenizer_test.go new file mode 100644 index 0000000000..fd008bfc54 --- /dev/null +++ b/txscript/tokenizer_test.go @@ -0,0 +1,259 @@ +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript + +import ( + "bytes" + "fmt" + "testing" +) + +// TestScriptTokenizer ensures a wide variety of behavior provided by the script +// tokenizer performs as expected. +func TestScriptTokenizer(t *testing.T) { + t.Skip() + + type expectedResult struct { + op byte // expected parsed opcode + data []byte // expected parsed data + index int32 // expected index into raw script after parsing token + } + + type tokenizerTest struct { + name string // test description + script []byte // the script to tokenize + expected []expectedResult // the expected info after parsing each token + finalIdx int32 // the expected final byte index + err error // expected error + } + + // Add both positive and negative tests for OP_DATA_1 through OP_DATA_75. + const numTestsHint = 100 // Make prealloc linter happy. + tests := make([]tokenizerTest, 0, numTestsHint) + for op := byte(OP_DATA_1); op < OP_DATA_75; op++ { + data := bytes.Repeat([]byte{0x01}, int(op)) + tests = append(tests, tokenizerTest{ + name: fmt.Sprintf("OP_DATA_%d", op), + script: append([]byte{op}, data...), + expected: []expectedResult{{op, data, 1 + int32(op)}}, + finalIdx: 1 + int32(op), + err: nil, + }) + + // Create test that provides one less byte than the data push requires. + tests = append(tests, tokenizerTest{ + name: fmt.Sprintf("short OP_DATA_%d", op), + script: append([]byte{op}, data[1:]...), + expected: nil, + finalIdx: 0, + err: scriptError(ErrMalformedPush, ""), + }) + } + + // Add both positive and negative tests for OP_PUSHDATA{1,2,4}. + data := mustParseShortForm("0x01{76}") + tests = append(tests, []tokenizerTest{{ + name: "OP_PUSHDATA1", + script: mustParseShortForm("OP_PUSHDATA1 0x4c 0x01{76}"), + expected: []expectedResult{{OP_PUSHDATA1, data, 2 + int32(len(data))}}, + finalIdx: 2 + int32(len(data)), + err: nil, + }, { + name: "OP_PUSHDATA1 no data length", + script: mustParseShortForm("OP_PUSHDATA1"), + expected: nil, + finalIdx: 0, + err: scriptError(ErrMalformedPush, ""), + }, { + name: "OP_PUSHDATA1 short data by 1 byte", + script: mustParseShortForm("OP_PUSHDATA1 0x4c 0x01{75}"), + expected: nil, + finalIdx: 0, + err: scriptError(ErrMalformedPush, ""), + }, { + name: "OP_PUSHDATA2", + script: mustParseShortForm("OP_PUSHDATA2 0x4c00 0x01{76}"), + expected: []expectedResult{{OP_PUSHDATA2, data, 3 + int32(len(data))}}, + finalIdx: 3 + int32(len(data)), + err: nil, + }, { + name: "OP_PUSHDATA2 no data length", + script: mustParseShortForm("OP_PUSHDATA2"), + expected: nil, + finalIdx: 0, + err: scriptError(ErrMalformedPush, ""), + }, { + name: "OP_PUSHDATA2 short data by 1 byte", + script: mustParseShortForm("OP_PUSHDATA2 0x4c00 0x01{75}"), + expected: nil, + finalIdx: 0, + err: scriptError(ErrMalformedPush, ""), + }, { + name: "OP_PUSHDATA4", + script: mustParseShortForm("OP_PUSHDATA4 0x4c000000 0x01{76}"), + expected: []expectedResult{{OP_PUSHDATA4, data, 5 + int32(len(data))}}, + finalIdx: 5 + int32(len(data)), + err: nil, + }, { + name: "OP_PUSHDATA4 no data length", + script: mustParseShortForm("OP_PUSHDATA4"), + expected: nil, + finalIdx: 0, + err: scriptError(ErrMalformedPush, ""), + }, { + name: "OP_PUSHDATA4 short data by 1 byte", + script: mustParseShortForm("OP_PUSHDATA4 0x4c000000 0x01{75}"), + expected: nil, + finalIdx: 0, + err: scriptError(ErrMalformedPush, ""), + }}...) + + // Add tests for OP_0, and OP_1 through OP_16 (small integers/true/false). + opcodes := []byte{OP_0} + for op := byte(OP_1); op < OP_16; op++ { + opcodes = append(opcodes, op) + } + for _, op := range opcodes { + tests = append(tests, tokenizerTest{ + name: fmt.Sprintf("OP_%d", op), + script: []byte{op}, + expected: []expectedResult{{op, nil, 1}}, + finalIdx: 1, + err: nil, + }) + } + + // Add various positive and negative tests for multi-opcode scripts. + tests = append(tests, []tokenizerTest{{ + name: "pay-to-pubkey-hash", + script: mustParseShortForm("DUP HASH160 DATA_20 0x01{20} EQUAL CHECKSIG"), + expected: []expectedResult{ + {OP_DUP, nil, 1}, {OP_HASH160, nil, 2}, + {OP_DATA_20, mustParseShortForm("0x01{20}"), 23}, + {OP_EQUAL, nil, 24}, {OP_CHECKSIG, nil, 25}, + }, + finalIdx: 25, + err: nil, + }, { + name: "almost pay-to-pubkey-hash (short data)", + script: mustParseShortForm("DUP HASH160 DATA_20 0x01{17} EQUAL CHECKSIG"), + expected: []expectedResult{ + {OP_DUP, nil, 1}, {OP_HASH160, nil, 2}, + }, + finalIdx: 2, + err: scriptError(ErrMalformedPush, ""), + }, { + name: "almost pay-to-pubkey-hash (overlapped data)", + script: mustParseShortForm("DUP HASH160 DATA_20 0x01{19} EQUAL CHECKSIG"), + expected: []expectedResult{ + {OP_DUP, nil, 1}, {OP_HASH160, nil, 2}, + {OP_DATA_20, mustParseShortForm("0x01{19} EQUAL"), 23}, + {OP_CHECKSIG, nil, 24}, + }, + finalIdx: 24, + err: nil, + }, { + name: "pay-to-script-hash", + script: mustParseShortForm("HASH160 DATA_20 0x01{20} EQUAL"), + expected: []expectedResult{ + {OP_HASH160, nil, 1}, + {OP_DATA_20, mustParseShortForm("0x01{20}"), 22}, + {OP_EQUAL, nil, 23}, + }, + finalIdx: 23, + err: nil, + }, { + name: "almost pay-to-script-hash (short data)", + script: mustParseShortForm("HASH160 DATA_20 0x01{18} EQUAL"), + expected: []expectedResult{ + {OP_HASH160, nil, 1}, + }, + finalIdx: 1, + err: scriptError(ErrMalformedPush, ""), + }, { + name: "almost pay-to-script-hash (overlapped data)", + script: mustParseShortForm("HASH160 DATA_20 0x01{19} EQUAL"), + expected: []expectedResult{ + {OP_HASH160, nil, 1}, + {OP_DATA_20, mustParseShortForm("0x01{19} EQUAL"), 22}, + }, + finalIdx: 22, + err: nil, + }}...) + + const scriptVersion = 0 + for _, test := range tests { + tokenizer := MakeScriptTokenizer(scriptVersion, test.script) + var opcodeNum int + for tokenizer.Next() { + // Ensure Next never returns true when there is an error set. + if err := tokenizer.Err(); err != nil { + t.Fatalf("%q: Next returned true when tokenizer has err: %v", + test.name, err) + } + + // Ensure the test data expects a token to be parsed. + op := tokenizer.Opcode() + data := tokenizer.Data() + if opcodeNum >= len(test.expected) { + t.Fatalf("%q: unexpected token '%d' (data: '%x')", test.name, + op, data) + } + expected := &test.expected[opcodeNum] + + // Ensure the opcode and data are the expected values. + if op != expected.op { + t.Fatalf("%q: unexpected opcode -- got %v, want %v", test.name, + op, expected.op) + } + if !bytes.Equal(data, expected.data) { + t.Fatalf("%q: unexpected data -- got %x, want %x", test.name, + data, expected.data) + } + + tokenizerIdx := tokenizer.ByteIndex() + if tokenizerIdx != expected.index { + t.Fatalf("%q: unexpected byte index -- got %d, want %d", + test.name, tokenizerIdx, expected.index) + } + + opcodeNum++ + } + + // Ensure the tokenizer claims it is done. This should be the case + // regardless of whether or not there was a parse error. + if !tokenizer.Done() { + t.Fatalf("%q: tokenizer claims it is not done", test.name) + } + + // Ensure the error is as expected. + if test.err == nil && tokenizer.Err() != nil { + t.Fatalf("%q: unexpected tokenizer err -- got %v, want nil", + test.name, tokenizer.Err()) + } else if test.err != nil { + if !IsErrorCode(tokenizer.Err(), test.err.(Error).ErrorCode) { + t.Fatalf("%q: unexpected tokenizer err -- got %v, want %v", + test.name, tokenizer.Err(), test.err.(Error).ErrorCode) + } + } + + // Ensure the final index is the expected value. + tokenizerIdx := tokenizer.ByteIndex() + if tokenizerIdx != test.finalIdx { + t.Fatalf("%q: unexpected final byte index -- got %d, want %d", + test.name, tokenizerIdx, test.finalIdx) + } + } +} + +// TestScriptTokenizerUnsupportedVersion ensures the tokenizer fails immediately +// with an unsupported script version. +func TestScriptTokenizerUnsupportedVersion(t *testing.T) { + const scriptVersion = 65535 + tokenizer := MakeScriptTokenizer(scriptVersion, nil) + if !IsErrorCode(tokenizer.Err(), ErrUnsupportedScriptVersion) { + t.Fatalf("script tokenizer did not error with unsupported version") + } +} From 099784267e52bc6ea11a0012aab56a78e083cb9c Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:04 -0500 Subject: [PATCH 005/108] txscript: Add benchmark for DisasmString. --- txscript/bench_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 673d1cc31d..3b1ed40d8e 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -111,3 +111,21 @@ func BenchmarkScriptParsing(b *testing.B) { } } } + +// BenchmarkDisasmString benchmarks how long it takes to disassemble a very +// large script. +func BenchmarkDisasmString(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := DisasmString(script) + if err != nil { + b.Fatalf("failed to disasm script: %v", err) + } + } +} From f980c9a28d943e926e2a4241b4e89de3e0a6fca6 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:05 -0500 Subject: [PATCH 006/108] txscript: Optimize script disasm. This converts the DisasmString function to make use of the new zero-allocation script tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to facilitate this, the opcode disassembly functionality is split into a separate function called disasmOpcode that accepts the opcode struct and data independently as opposed to requiring a parsed opcode. The new function also accepts a pointer to a string builder so the disassembly can be more efficiently be built. While here, the comment is modified to explicitly call out the script version semantics. The following is a before and after comparison of a large script: benchmark old ns/op new ns/op delta BenchmarkDisasmString-8 102902 40124 -61.01% benchmark old allocs new allocs delta BenchmarkDisasmString-8 46 51 +10.87% benchmark old bytes new bytes delta BenchmarkDisasmString-8 389324 130552 -66.47% --- txscript/opcode.go | 65 +++++++++++++++++++++++++++++----------------- txscript/script.go | 30 ++++++++++++++------- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index a878a9667b..7383f881e9 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -9,8 +9,10 @@ import ( "crypto/sha1" "crypto/sha256" "encoding/binary" + "encoding/hex" "fmt" "hash" + "strings" "golang.org/x/crypto/ripemd160" @@ -815,45 +817,60 @@ func (pop *parsedOpcode) checkMinimalDataPush() error { return nil } -// print returns a human-readable string representation of the opcode for use -// in script disassembly. -func (pop *parsedOpcode) print(oneline bool) string { - // The reference implementation one-line disassembly replaces opcodes - // which represent values (e.g. OP_0 through OP_16 and OP_1NEGATE) - // with the raw value. However, when not doing a one-line dissassembly, - // we prefer to show the actual opcode names. Thus, only replace the - // opcodes in question when the oneline flag is set. - opcodeName := pop.opcode.name - if oneline { +// disasmOpcode writes a human-readable disassembly of the provided opcode and +// data into the provided buffer. The compact flag indicates the disassembly +// should print a more compact representation of data-carrying and small integer +// opcodes. For example, OP_0 through OP_16 are replaced with the numeric value +// and data pushes are printed as only the hex representation of the data as +// opposed to including the opcode that specifies the amount of data to push as +// well. +func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) { + // Replace opcode which represent values (e.g. OP_0 through OP_16 and + // OP_1NEGATE) with the raw value when performing a compact disassembly. + opcodeName := op.name + if compact { if replName, ok := opcodeOnelineRepls[opcodeName]; ok { opcodeName = replName } - // Nothing more to do for non-data push opcodes. - if pop.opcode.length == 1 { - return opcodeName + // Either write the human-readable opcode or the parsed data in hex for + // data-carrying opcodes. + switch { + case op.length == 1: + buf.WriteString(opcodeName) + + default: + buf.WriteString(hex.EncodeToString(data)) } - return fmt.Sprintf("%x", pop.data) + return } - // Nothing more to do for non-data push opcodes. - if pop.opcode.length == 1 { - return opcodeName - } + buf.WriteString(opcodeName) + + switch op.length { + // Only write the opcode name for non-data push opcodes. + case 1: + return // Add length for the OP_PUSHDATA# opcodes. - retString := opcodeName - switch pop.opcode.length { case -1: - retString += fmt.Sprintf(" 0x%02x", len(pop.data)) + buf.WriteString(fmt.Sprintf(" 0x%02x", len(data))) case -2: - retString += fmt.Sprintf(" 0x%04x", len(pop.data)) + buf.WriteString(fmt.Sprintf(" 0x%04x", len(data))) case -4: - retString += fmt.Sprintf(" 0x%08x", len(pop.data)) + buf.WriteString(fmt.Sprintf(" 0x%08x", len(data))) } - return fmt.Sprintf("%s 0x%02x", retString, pop.data) + buf.WriteString(fmt.Sprintf(" 0x%02x", data)) +} + +// print returns a human-readable string representation of the opcode for use +// in script disassembly. +func (pop *parsedOpcode) print(compact bool) string { + var buf strings.Builder + disasmOpcode(&buf, pop.opcode, pop.data, compact) + return buf.String() } // bytes returns any data associated with the opcode encoded as it would be in diff --git a/txscript/script.go b/txscript/script.go index 92a50e3761..b7c2ef96f6 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/binary" "fmt" + "strings" "time" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -275,20 +276,29 @@ func unparseScript(pops []parsedOpcode) ([]byte, error) { // script up to the point the failure occurred along with the string '[error]' // appended. In addition, the reason the script failed to parse is returned // if the caller wants more information about the failure. -func DisasmString(buf []byte) (string, error) { - var disbuf bytes.Buffer - opcodes, err := parseScript(buf) - for _, pop := range opcodes { - disbuf.WriteString(pop.print(true)) +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func DisasmString(script []byte) (string, error) { + const scriptVersion = 0 + + var disbuf strings.Builder + tokenizer := MakeScriptTokenizer(scriptVersion, script) + if tokenizer.Next() { + disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true) + } + for tokenizer.Next() { disbuf.WriteByte(' ') + disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true) } - if disbuf.Len() > 0 { - disbuf.Truncate(disbuf.Len() - 1) - } - if err != nil { + if tokenizer.Err() != nil { + if tokenizer.ByteIndex() != 0 { + disbuf.WriteByte(' ') + } disbuf.WriteString("[error]") } - return disbuf.String(), err + return disbuf.String(), tokenizer.Err() } // removeOpcode will remove any opcode matching ``opcode'' from the opcode From af757d3d0d21468c994401da82816837a9314f62 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 18 Apr 2019 20:11:36 -0700 Subject: [PATCH 007/108] txscript: Introduce raw script sighash calc func. This introduces a new function named calcSignatureHashRaw which accepts the raw script bytes to calculate the script hash versus requiring the parsed opcode only to unparse them later in order to make it more flexible for working with raw scripts. Since there are several places in the rest of the code that currently only have access to the parsed opcodes, this modifies the existing calcSignatureHash to first unparse the script before calling the new function. Backport of decred/dcrd:f306a72a16eaabfb7054a26f9d9f850b87b00279 --- txscript/opcode.go | 10 ++++++++-- txscript/reference_test.go | 7 ++++++- txscript/script.go | 41 ++++++++++++++++++++++++++++---------- txscript/sign.go | 5 ++++- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 7383f881e9..893bebdf8d 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -2198,7 +2198,10 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { // to sign itself. subScript = removeOpcodeByData(subScript, fullSigBytes) - hash = calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx) + hash, err = calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx) + if err != nil { + return err + } } pubKey, err := btcec.ParsePubKey(pkBytes, btcec.S256()) @@ -2467,7 +2470,10 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { return err } } else { - hash = calcSignatureHash(script, hashType, &vm.tx, vm.txIdx) + hash, err = calcSignatureHash(script, hashType, &vm.tx, vm.txIdx) + if err != nil { + return err + } } var valid bool diff --git a/txscript/reference_test.go b/txscript/reference_test.go index 5015960b94..6ac9b68f51 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -863,8 +863,13 @@ func TestCalcSignatureHash(t *testing.T) { } hashType := SigHashType(testVecF64ToUint32(test[3].(float64))) - hash := calcSignatureHash(parsedScript, hashType, &tx, + hash, err := calcSignatureHash(parsedScript, hashType, &tx, int(test[2].(float64))) + if err != nil { + t.Errorf("TestCalcSignatureHash failed test #%d: "+ + "Failed to compute sighash: %v", i, err) + continue + } expectedHash, _ := chainhash.NewHashFromStr(test[4].(string)) if !bytes.Equal(hash, expectedHash[:]) { diff --git a/txscript/script.go b/txscript/script.go index b7c2ef96f6..52bb5b6b35 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -572,13 +572,12 @@ func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx if err != nil { return nil, fmt.Errorf("cannot parse output script: %v", err) } - return calcSignatureHash(parsedScript, hashType, tx, idx), nil + return calcSignatureHash(parsedScript, hashType, tx, idx) } -// calcSignatureHash will, given a script and hash type for the current script -// engine instance, calculate the signature hash to be used for signing and -// verification. -func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.MsgTx, idx int) []byte { +// calcSignatureHashRaw computes the signature hash for the specified input of +// the target transaction observing the desired signature hash type. +func calcSignatureHashRaw(sigScript []byte, hashType SigHashType, tx *wire.MsgTx, idx int) []byte { // The SigHashSingle signature type signs only the corresponding input // and output (the output with the same index number as the input). // @@ -606,17 +605,24 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg } // Remove all instances of OP_CODESEPARATOR from the script. - script = removeOpcode(script, OP_CODESEPARATOR) + filteredScript := make([]byte, 0, len(sigScript)) + const scriptVersion = 0 + tokenizer := MakeScriptTokenizer(scriptVersion, sigScript) + var prevOffset int32 + for tokenizer.Next() { + if tokenizer.Opcode() != OP_CODESEPARATOR { + filteredScript = append(filteredScript, + sigScript[prevOffset:tokenizer.ByteIndex()]...) + } + prevOffset = tokenizer.ByteIndex() + } // Make a shallow copy of the transaction, zeroing out the script for // all inputs that are not currently being processed. txCopy := shallowCopyTx(tx) for i := range txCopy.TxIn { if i == idx { - // UnparseScript cannot fail here because removeOpcode - // above only returns a valid script. - sigScript, _ := unparseScript(script) - txCopy.TxIn[idx].SignatureScript = sigScript + txCopy.TxIn[idx].SignatureScript = filteredScript } else { txCopy.TxIn[i].SignatureScript = nil } @@ -670,6 +676,21 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg return chainhash.DoubleHashB(wbuf.Bytes()) } +// calcSignatureHash computes the signature hash for the specified input of the +// target transaction observing the desired signature hash type. +// +// DEPRECATED: Use calcSignatureHashRaw instead +func calcSignatureHash(prevOutScript []parsedOpcode, hashType SigHashType, + tx *wire.MsgTx, idx int) ([]byte, error) { + + sigScript, err := unparseScript(prevOutScript) + if err != nil { + return nil, err + } + + return calcSignatureHashRaw(sigScript, hashType, tx, idx), nil +} + // asSmallInt returns the passed opcode, which must be true according to // isSmallInt(), as an integer. func asSmallInt(op *opcode) int { diff --git a/txscript/sign.go b/txscript/sign.go index 42af9686cb..b9f8b2dbb4 100644 --- a/txscript/sign.go +++ b/txscript/sign.go @@ -345,7 +345,10 @@ sigLoop: // however, assume no sigs etc are in the script since that // would make the transaction nonstandard and thus not // MultiSigTy, so we just need to hash the full thing. - hash := calcSignatureHash(pkPops, hashType, tx, idx) + hash, err := calcSignatureHash(pkPops, hashType, tx, idx) + if err != nil { + panic(fmt.Sprintf("cannot compute sighash: %v", err)) + } for _, addr := range addresses { // All multisig addresses should be pubkey addresses From c19535b1454325ac363070fe1cdbfe2fbd12bdb0 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:07 -0500 Subject: [PATCH 008/108] txscript: Optimize CalcSignatureHash. This modifies the CalcSignatureHash function to make use of the new signature hash calculation function that accepts raw scripts without needing to first parse them. Consequently, it also doubles as a slight optimization to the execution time and a significant reduction in the number of allocations. In order to convert the CalcScriptHash function and keep the same semantics, a new function named checkScriptParses is introduced which will quickly determine if a script can be fully parsed without failure and return the parse failure in the case it can't. The following is a before and after comparison of analyzing a large multiple input transaction: benchmark old ns/op new ns/op delta BenchmarkCalcSigHash-8 3627895 3619477 -0.23% benchmark old allocs new allocs delta BenchmarkCalcSigHash-8 1335 801 -40.00% benchmark old bytes new bytes delta BenchmarkCalcSigHash-8 1373812 1293354 -5.86% --- txscript/script.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 52bb5b6b35..112c24acb1 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -567,12 +567,17 @@ func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx { // CalcSignatureHash will, given a script and hash type for the current script // engine instance, calculate the signature hash to be used for signing and // verification. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) { - parsedScript, err := parseScript(script) - if err != nil { - return nil, fmt.Errorf("cannot parse output script: %v", err) + const scriptVersion = 0 + if err := checkScriptParses(scriptVersion, script); err != nil { + return nil, err } - return calcSignatureHash(parsedScript, hashType, tx, idx) + + return calcSignatureHashRaw(script, hashType, tx, idx), nil } // calcSignatureHashRaw computes the signature hash for the specified input of @@ -850,6 +855,15 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int { return 0 } +// checkScriptParses returns an error if the provided script fails to parse. +func checkScriptParses(scriptVersion uint16, script []byte) error { + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + // Nothing to do. + } + return tokenizer.Err() +} + // IsUnspendable returns whether the passed public key script is unspendable, or // guaranteed to fail at execution. This allows inputs to be pruned instantly // when entering the UTXO set. From c6f4cafe57cac8fb527be53b6418fe96c655265e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 18 Apr 2019 21:12:34 -0700 Subject: [PATCH 009/108] txscript/reference_test: Convert sighash calc test This converts the tests for calculating signature hashes to use the exported function which handles the raw script versus the now deprecated variant requiring parsed opcodes. Backport of 06f769ef72e6042e7f2b5ff1c512ef1371d615e5 --- txscript/reference_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/txscript/reference_test.go b/txscript/reference_test.go index 6ac9b68f51..9d9c8f39ed 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -836,6 +836,7 @@ func TestCalcSignatureHash(t *testing.T) { err) } + const scriptVersion = 0 for i, test := range tests { if i == 0 { // Skip first line -- contains comments only. @@ -855,15 +856,14 @@ func TestCalcSignatureHash(t *testing.T) { } subScript, _ := hex.DecodeString(test[1].(string)) - parsedScript, err := parseScript(subScript) - if err != nil { + if err := checkScriptParses(scriptVersion, subScript); err != nil { t.Errorf("TestCalcSignatureHash failed test #%d: "+ "Failed to parse sub-script: %v", i, err) continue } hashType := SigHashType(testVecF64ToUint32(test[3].(float64))) - hash, err := calcSignatureHash(parsedScript, hashType, &tx, + hash, err := CalcSignatureHash(subScript, hashType, &tx, int(test[2].(float64))) if err != nil { t.Errorf("TestCalcSignatureHash failed test #%d: "+ From 583b74040dc92978e9c258b7ea64e3da01f361f2 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:09 -0500 Subject: [PATCH 010/108] txscript: Make isSmallInt accept raw opcode. This converts the isSmallInt function to accept an opcode as a byte instead of the internal opcode data struct in order to make it more flexible for raw script analysis. The comment is modified to explicitly call out the script version semantics. Finally, it updates all callers accordingly. --- txscript/script.go | 14 ++++++++------ txscript/standard.go | 10 +++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 112c24acb1..58b8e69f0f 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -1,4 +1,5 @@ // Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2015-2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -45,11 +46,12 @@ const ( // isSmallInt returns whether or not the opcode is considered a small integer, // which is an OP_0, or OP_1 through OP_16. -func isSmallInt(op *opcode) bool { - if op.value == OP_0 || (op.value >= OP_1 && op.value <= OP_16) { - return true - } - return false +// +// NOTE: This function is only valid for version 0 opcodes. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func isSmallInt(op byte) bool { + return op == OP_0 || (op >= OP_1 && op <= OP_16) } // isScriptHash returns true if the script passed is a pay-to-script-hash @@ -136,7 +138,7 @@ func IsWitnessProgram(script []byte) bool { // bytes. func isWitnessProgram(pops []parsedOpcode) bool { return len(pops) == 2 && - isSmallInt(pops[0].opcode) && + isSmallInt(pops[0].opcode.value) && canonicalPush(pops[1]) && (len(pops[1].data) >= 2 && len(pops[1].data) <= 40) } diff --git a/txscript/standard.go b/txscript/standard.go index 2cad218e95..a447303d62 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -115,10 +115,10 @@ func isMultiSig(pops []parsedOpcode) bool { if l < 4 { return false } - if !isSmallInt(pops[0].opcode) { + if !isSmallInt(pops[0].opcode.value) { return false } - if !isSmallInt(pops[l-2].opcode) { + if !isSmallInt(pops[l-2].opcode.value) { return false } if pops[l-1].opcode.value != OP_CHECKMULTISIG { @@ -153,7 +153,7 @@ func isNullData(pops []parsedOpcode) bool { return l == 2 && pops[0].opcode.value == OP_RETURN && - (isSmallInt(pops[1].opcode) || pops[1].opcode.value <= + (isSmallInt(pops[1].opcode.value) || pops[1].opcode.value <= OP_PUSHDATA4) && len(pops[1].data) <= MaxDataCarrierSize } @@ -705,7 +705,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa return nil, nil } pushes.SecretSize = int64(locktime) - } else if op := pops[2].opcode; isSmallInt(op) { + } else if op := pops[2].opcode; isSmallInt(op.value) { pushes.SecretSize = int64(asSmallInt(op)) } else { return nil, nil @@ -716,7 +716,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa return nil, nil } pushes.LockTime = int64(locktime) - } else if op := pops[11].opcode; isSmallInt(op) { + } else if op := pops[11].opcode; isSmallInt(op.value) { pushes.LockTime = int64(asSmallInt(op)) } else { return nil, nil From dfb1a6797b724873f8d3f785b76f86b9c58a6200 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:11 -0500 Subject: [PATCH 011/108] txscript: Make asSmallInt accept raw opcode. This converts the asSmallInt function to accept an opcode as a byte instead of the internal opcode data struct in order to make it more flexible for raw script analysis. It also updates all callers accordingly. --- txscript/script.go | 10 +++++----- txscript/standard.go | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 58b8e69f0f..3dfd82e139 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -159,7 +159,7 @@ func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) { "unable to extract version or witness program") } - witnessVersion := asSmallInt(pops[0].opcode) + witnessVersion := asSmallInt(pops[0].opcode.value) witnessProgram := pops[1].data return witnessVersion, witnessProgram, nil @@ -700,12 +700,12 @@ func calcSignatureHash(prevOutScript []parsedOpcode, hashType SigHashType, // asSmallInt returns the passed opcode, which must be true according to // isSmallInt(), as an integer. -func asSmallInt(op *opcode) int { - if op.value == OP_0 { +func asSmallInt(op byte) int { + if op == OP_0 { return 0 } - return int(op.value - (OP_1 - 1)) + return int(op - (OP_1 - 1)) } // getSigOpCount is the implementation function for counting the number of @@ -730,7 +730,7 @@ func getSigOpCount(pops []parsedOpcode, precise bool) int { if precise && i > 0 && pops[i-1].opcode.value >= OP_1 && pops[i-1].opcode.value <= OP_16 { - nSigs += asSmallInt(pops[i-1].opcode) + nSigs += asSmallInt(pops[i-1].opcode.value) } else { nSigs += MaxPubKeysPerMultiSig } diff --git a/txscript/standard.go b/txscript/standard.go index a447303d62..94b3cce57b 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -127,7 +127,7 @@ func isMultiSig(pops []parsedOpcode) bool { // Verify the number of pubkeys specified matches the actual number // of pubkeys provided. - if l-2-1 != asSmallInt(pops[l-2].opcode) { + if l-2-1 != asSmallInt(pops[l-2].opcode.value) { return false } @@ -238,7 +238,7 @@ func expectedInputs(pops []parsedOpcode, class ScriptClass) int { // the original bitcoind bug where OP_CHECKMULTISIG pops an // additional item from the stack, add an extra expected input // for the extra push that is required to compensate. - return asSmallInt(pops[0].opcode) + 1 + return asSmallInt(pops[0].opcode.value) + 1 case NullDataTy: fallthrough @@ -395,8 +395,8 @@ func CalcMultiSigStats(script []byte) (int, int, error) { return 0, 0, scriptError(ErrNotMultisigScript, str) } - numSigs := asSmallInt(pops[0].opcode) - numPubKeys := asSmallInt(pops[len(pops)-2].opcode) + numSigs := asSmallInt(pops[0].opcode.value) + numPubKeys := asSmallInt(pops[len(pops)-2].opcode.value) return numPubKeys, numSigs, nil } @@ -617,8 +617,8 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script // Therefore the number of required signatures is the 1st item // on the stack and the number of public keys is the 2nd to last // item on the stack. - requiredSigs = asSmallInt(pops[0].opcode) - numPubKeys := asSmallInt(pops[len(pops)-2].opcode) + requiredSigs = asSmallInt(pops[0].opcode.value) + numPubKeys := asSmallInt(pops[len(pops)-2].opcode.value) // Extract the public keys while skipping any that are invalid. addrs = make([]btcutil.Address, 0, numPubKeys) @@ -706,7 +706,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa } pushes.SecretSize = int64(locktime) } else if op := pops[2].opcode; isSmallInt(op.value) { - pushes.SecretSize = int64(asSmallInt(op)) + pushes.SecretSize = int64(asSmallInt(op.value)) } else { return nil, nil } @@ -717,7 +717,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa } pushes.LockTime = int64(locktime) } else if op := pops[11].opcode; isSmallInt(op.value) { - pushes.LockTime = int64(asSmallInt(op)) + pushes.LockTime = int64(asSmallInt(op.value)) } else { return nil, nil } From 05aa488a877faa2fabdac38e024f9f72aa3abe4a Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:38 -0500 Subject: [PATCH 012/108] txscript: Add benchmark for IsPayToPubKey --- txscript/bench_test.go | 15 +++++++++++++++ txscript/script.go | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 3b1ed40d8e..6415c595a5 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -129,3 +129,18 @@ func BenchmarkDisasmString(b *testing.B) { } } } + +// BenchmarkIsPubKeyScript benchmarks how long it takes to analyze a very large +// script to determine if it is a standard pay-to-pubkey script. +func BenchmarkIsPubKeyScript(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsPayToPubKey(script) + } +} diff --git a/txscript/script.go b/txscript/script.go index 3dfd82e139..c642069ced 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -63,6 +63,16 @@ func isScriptHash(pops []parsedOpcode) bool { pops[2].opcode.value == OP_EQUAL } +// IsPayToPubKey returns true if the script is in the standard pay-to-pubkey +// (P2PK) format, false otherwise. +func IsPayToPubKey(script []byte) bool { + pops, err := parseScript(script) + if err != nil { + return false + } + return isPubkey(pops) +} + // IsPayToScriptHash returns true if the script is in the standard // pay-to-script-hash (P2SH) format, false otherwise. func IsPayToScriptHash(script []byte) bool { From 99cb679b6f170003834de4ca326bfcf960bc1629 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 14:06:56 -0800 Subject: [PATCH 013/108] txscript: Optimize IsPayToPubKey This converts the IsPayToScriptHash function to analyze the raw script instead of using the far less efficient parseScript, thereby significantly optimizing the function. In order to accomplish this, it introduces four new functions: extractCompressedPubKey, extractUncompressedPubKey, extractPubKey, and isPubKeyScript. The extractPubKey function makes use of extractCompressedPubKey and extractUncompressedPubKey to combine their functionality as a convenience and isPubKeyScript is defined in terms of extractPubKey. The extractCompressedPubKey works with the raw script bytes to simultaneously determine if the script is a pay-to-compressed-pubkey script, and in the case it is, extract and return the raw compressed pubkey bytes. Similarly, the extractUncompressedPubKey works in the same way except it determines if the script is a pay-to-uncompressed-pubkey script and returns the raw uncompressed pubkey bytes in the case it is. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkIsPubKeyScript-8 62323 2.97 -100.00% benchmark old allocs new allocs delta BenchmarkIsPubKeyScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPubKeyScript-8 311299 0 -100.00% --- txscript/script.go | 6 +---- txscript/standard.go | 58 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index c642069ced..00df52167b 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -66,11 +66,7 @@ func isScriptHash(pops []parsedOpcode) bool { // IsPayToPubKey returns true if the script is in the standard pay-to-pubkey // (P2PK) format, false otherwise. func IsPayToPubKey(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false - } - return isPubkey(pops) + return isPubKeyScript(script) } // IsPayToScriptHash returns true if the script is in the standard diff --git a/txscript/standard.go b/txscript/standard.go index 94b3cce57b..0a835dbb4f 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -85,6 +85,64 @@ func (t ScriptClass) String() string { return scriptClassToName[t] } +// extractCompressedPubKey extracts a compressed public key from the passed +// script if it is a standard pay-to-compressed-secp256k1-pubkey script. It +// will return nil otherwise. +func extractCompressedPubKey(script []byte) []byte { + // A pay-to-compressed-pubkey script is of the form: + // OP_DATA_33 <33-byte compressed pubkey> OP_CHECKSIG + + // All compressed secp256k1 public keys must start with 0x02 or 0x03. + if len(script) == 35 && + script[34] == OP_CHECKSIG && + script[0] == OP_DATA_33 && + (script[1] == 0x02 || script[1] == 0x03) { + + return script[1:34] + } + + return nil +} + +// extractUncompressedPubKey extracts an uncompressed public key from the +// passed script if it is a standard pay-to-uncompressed-secp256k1-pubkey +// script. It will return nil otherwise. +func extractUncompressedPubKey(script []byte) []byte { + // A pay-to-uncompressed-pubkey script is of the form: + // OP_DATA_65 <65-byte uncompressed pubkey> OP_CHECKSIG + // + // All non-hybrid uncompressed secp256k1 public keys must start with 0x04. + // Hybrid uncompressed secp256k1 public keys start with 0x06 or 0x07: + // - 0x06 => hybrid format for even Y coords + // - 0x07 => hybrid format for odd Y coords + if len(script) == 67 && + script[66] == OP_CHECKSIG && + script[0] == OP_DATA_65 && + (script[1] == 0x04 || script[1] == 0x06 || script[1] == 0x07) { + + return script[1:66] + } + return nil +} + +// extractPubKey extracts either compressed or uncompressed public key from the +// passed script if it is a either a standard pay-to-compressed-secp256k1-pubkey +// or pay-to-uncompressed-secp256k1-pubkey script, respectively. It will return +// nil otherwise. +func extractPubKey(script []byte) []byte { + if pubKey := extractCompressedPubKey(script); pubKey != nil { + return pubKey + } + return extractUncompressedPubKey(script) +} + +// isPubKeyScript returns whether or not the passed script is either a standard +// pay-to-compressed-secp256k1-pubkey or pay-to-uncompressed-secp256k1-pubkey +// script. +func isPubKeyScript(script []byte) bool { + return extractPubKey(script) != nil +} + // isPubkey returns true if the script passed is a pay-to-pubkey transaction, // false otherwise. func isPubkey(pops []parsedOpcode) bool { From 2d2608c34e293f5d95b132aba67bf1b58de8926f Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 14:52:11 -0800 Subject: [PATCH 014/108] txscript: Add benchmark for IsPayToPubKeyHash --- txscript/bench_test.go | 15 +++++++++++++++ txscript/script.go | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 6415c595a5..401b9683a5 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -144,3 +144,18 @@ func BenchmarkIsPubKeyScript(b *testing.B) { _ = IsPayToPubKey(script) } } + +// BenchmarkIsPubKeyHashScript benchmarks how long it takes to analyze a very +// large script to determine if it is a standard pay-to-pubkey-hash script. +func BenchmarkIsPubKeyHashScript(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsPayToPubKeyHash(script) + } +} diff --git a/txscript/script.go b/txscript/script.go index 00df52167b..fb4fc8bc88 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -69,6 +69,16 @@ func IsPayToPubKey(script []byte) bool { return isPubKeyScript(script) } +// IsPayToPubKeyHash returns true if the script is in the standard +// pay-to-pubkey-hash (P2PKH) format, false otherwise. +func IsPayToPubKeyHash(script []byte) bool { + pops, err := parseScript(script) + if err != nil { + return false + } + return isPubkeyHash(pops) +} + // IsPayToScriptHash returns true if the script is in the standard // pay-to-script-hash (P2SH) format, false otherwise. func IsPayToScriptHash(script []byte) bool { From c771f4fb386e42a9363b614c58b536504feea0c2 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 14:09:28 -0800 Subject: [PATCH 015/108] txscript: Optimize IsPayToPubKeyHash This converts the IsPayToPubKeyHash function to analyze the raw script instead of using the far less efficient parseScript, thereby significantly optimization the function. In order to accomplish this, it introduces two new functions. The first one is named extractPubKeyHash and works with the raw script bytes to simultaneously determine if the script is a pay-to-pubkey-hash script, and in the case it is, extract and return the hash. The second new function is named isPubKeyHashScript and is defined in terms of the former. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkIsPubKeyHashScript-8 62228 0.45 -100.00% benchmark old allocs new allocs delta BenchmarkIsPubKeyHashScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPubKeyHashScript-8 311299 0 -100.00% --- txscript/script.go | 6 +----- txscript/standard.go | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index fb4fc8bc88..b154c1106d 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -72,11 +72,7 @@ func IsPayToPubKey(script []byte) bool { // IsPayToPubKeyHash returns true if the script is in the standard // pay-to-pubkey-hash (P2PKH) format, false otherwise. func IsPayToPubKeyHash(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false - } - return isPubkeyHash(pops) + return isPubKeyHashScript(script) } // IsPayToScriptHash returns true if the script is in the standard diff --git a/txscript/standard.go b/txscript/standard.go index 0a835dbb4f..f7948f7930 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -143,6 +143,30 @@ func isPubKeyScript(script []byte) bool { return extractPubKey(script) != nil } +// extractPubKeyHash extracts the public key hash from the passed script if it +// is a standard pay-to-pubkey-hash script. It will return nil otherwise. +func extractPubKeyHash(script []byte) []byte { + // A pay-to-pubkey-hash script is of the form: + // OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG + if len(script) == 25 && + script[0] == OP_DUP && + script[1] == OP_HASH160 && + script[2] == OP_DATA_20 && + script[23] == OP_EQUALVERIFY && + script[24] == OP_CHECKSIG { + + return script[3:23] + } + + return nil +} + +// isPubKeyHashScript returns whether or not the passed script is a standard +// pay-to-pubkey-hash script. +func isPubKeyHashScript(script []byte) bool { + return extractPubKeyHash(script) != nil +} + // isPubkey returns true if the script passed is a pay-to-pubkey transaction, // false otherwise. func isPubkey(pops []parsedOpcode) bool { From 665c29802ed3b6034396e4195b2abad8c77d2ab5 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:13 -0500 Subject: [PATCH 016/108] txscript: Add benchmark for IsPayToScriptHash. --- txscript/bench_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 401b9683a5..e47cf21fbe 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -159,3 +159,18 @@ func BenchmarkIsPubKeyHashScript(b *testing.B) { _ = IsPayToPubKeyHash(script) } } + +// BenchmarkIsPayToScriptHash benchmarks how long it takes IsPayToScriptHash to +// analyze a very large script. +func BenchmarkIsPayToScriptHash(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsPayToScriptHash(script) + } +} From 215af7ff5427d405d0443aa9b0c4bdb120f55f80 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:14 -0500 Subject: [PATCH 017/108] txscript: Optimize IsPayToScriptHash. This converts the IsPayToScriptHash function to analyze the raw script instead of using the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractScriptHash and works with the raw script bytes to simultaneously determine if the script is a p2sh script, and in the case it is, extract and return the hash. The second new function is named isScriptHashScript and is defined in terms of the former. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. Finally, this also deprecates the isScriptHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToScriptHash to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script that is not a p2sh script: benchmark old ns/op new ns/op delta BenchmarkIsPayToScriptHash-8 62393 0.60 -100.00% benchmark old allocs new allocs delta BenchmarkIsPayToScriptHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPayToScriptHash-8 311299 0 -100.00% --- txscript/script.go | 14 +++++++++----- txscript/standard.go | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index b154c1106d..a8cfd16656 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -56,6 +56,8 @@ func isSmallInt(op byte) bool { // isScriptHash returns true if the script passed is a pay-to-script-hash // transaction, false otherwise. +// +// DEPRECATED. Use isScriptHashScript or extractScriptHash instead. func isScriptHash(pops []parsedOpcode) bool { return len(pops) == 3 && pops[0].opcode.value == OP_HASH160 && @@ -77,12 +79,14 @@ func IsPayToPubKeyHash(script []byte) bool { // IsPayToScriptHash returns true if the script is in the standard // pay-to-script-hash (P2SH) format, false otherwise. +// +// WARNING: This function always treats the passed script as version 0. Great +// care must be taken if introducing a new script version because it is used in +// consensus which, unfortunately as of the time of this writing, does not check +// script versions before determining if the script is a P2SH which means nodes +// on existing rules will analyze new version scripts as if they were version 0. func IsPayToScriptHash(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false - } - return isScriptHash(pops) + return isScriptHashScript(script) } // isWitnessScriptHash returns true if the passed script is a diff --git a/txscript/standard.go b/txscript/standard.go index f7948f7930..a02f87232d 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -167,6 +167,32 @@ func isPubKeyHashScript(script []byte) bool { return extractPubKeyHash(script) != nil } +// extractScriptHash extracts the script hash from the passed script if it is a +// standard pay-to-script-hash script. It will return nil otherwise. +// +// NOTE: This function is only valid for version 0 opcodes. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func extractScriptHash(script []byte) []byte { + // A pay-to-script-hash script is of the form: + // OP_HASH160 <20-byte scripthash> OP_EQUAL + if len(script) == 23 && + script[0] == OP_HASH160 && + script[1] == OP_DATA_20 && + script[22] == OP_EQUAL { + + return script[2:22] + } + + return nil +} + +// isScriptHashScript returns whether or not the passed script is a standard +// pay-to-script-hash script. +func isScriptHashScript(script []byte) bool { + return extractScriptHash(script) != nil +} + // isPubkey returns true if the script passed is a pay-to-pubkey transaction, // false otherwise. func isPubkey(pops []parsedOpcode) bool { From 4d31d1599dc351183246aa4e85c9d77b199105f9 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:15 -0500 Subject: [PATCH 018/108] txscript: Add benchmarks for IsMutlsigScript. --- txscript/bench_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++ txscript/standard.go | 21 ++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index e47cf21fbe..49be4958c6 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -174,3 +174,48 @@ func BenchmarkIsPayToScriptHash(b *testing.B) { _ = IsPayToScriptHash(script) } } + +// BenchmarkIsMultisigScriptLarge benchmarks how long it takes IsMultisigScript +// to analyze a very large script. +func BenchmarkIsMultisigScriptLarge(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + isMultisig, err := IsMultisigScript(script) + if err != nil { + b.Fatalf("unexpected err: %v", err) + } + if isMultisig { + b.Fatalf("script should NOT be reported as mutisig script") + } + } +} + +// BenchmarkIsMultisigScript benchmarks how long it takes IsMultisigScript to +// analyze a 1-of-2 multisig public key script. +func BenchmarkIsMultisigScript(b *testing.B) { + multisigShortForm := "1 " + + "DATA_33 " + + "0x030478aaaa2be30772f1e69e581610f1840b3cf2fe7228ee0281cd599e5746f81e " + + "DATA_33 " + + "0x0284f4d078b236a9ff91661f8ffbe012737cd3507566f30fd97d25f2b23539f3cd " + + "2 CHECKMULTISIG" + pkScript := mustParseShortForm(multisigShortForm) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + isMultisig, err := IsMultisigScript(pkScript) + if err != nil { + b.Fatalf("unexpected err: %v", err) + } + if !isMultisig { + b.Fatalf("script should be reported as a mutisig script") + } + } +} diff --git a/txscript/standard.go b/txscript/standard.go index a02f87232d..25483aebe5 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -248,6 +248,27 @@ func isMultiSig(pops []parsedOpcode) bool { return true } +// IsMultisigScript returns whether or not the passed script is a standard +// multisignature script. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +// +// The error is DEPRECATED and will be removed in the major version bump. +func IsMultisigScript(script []byte) (bool, error) { + if len(script) == 0 || script == nil { + return false, nil + } + + pops, err := parseScript(script) + if err != nil { + return false, err + } + + return isMultiSig(pops), nil +} + // isNullData returns true if the passed script is a null data transaction, // false otherwise. func isNullData(pops []parsedOpcode) bool { From 0eaae2663ba9955de61a2c6bf52b008dd40cc7f6 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:16 -0500 Subject: [PATCH 019/108] txscript: Optimize IsMultisigScript. This converts the IsMultisigScript function to make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractMultisigScriptDetails and works with the raw script bytes to simultaneously determine if the script is a multisignature script, and in the case it is, extract and return the relevant details. The second new function is named isMultisigScript and is defined in terms of the former. The extract function accepts the script version, raw script bytes, and a flag to determine whether or not the public keys should also be extracted. The flag is provided because extracting pubkeys results in an allocation that the caller might wish to avoid. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. It is important to note that this new implementation intentionally has a semantic difference from the existing implementation in that it will now correctly identify a multisig script with zero pubkeys whereas previously it incorrectly required at least one pubkey. This change is acceptable because the function only deals with standardness rather than consensus rules. Finally, this also deprecates the isMultiSig function that requires opcodes in favor of the new functions and deprecates the error return on the export IsMultisigScript function since it really does not make sense given the purpose of the function. The following is a before and after comparison of analyzing both a large script that is not a multisig script and a 1-of-2 multisig public key script: benchmark old ns/op new ns/op delta BenchmarkIsMultisigScriptLarge-8 64166 5.52 -99.99% BenchmarkIsMultisigScript-8 630 59.4 -90.57% benchmark old allocs new allocs delta BenchmarkIsMultisigScriptLarge-8 1 0 -100.00% BenchmarkIsMultisigScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigScriptLarge-8 311299 0 -100.00% BenchmarkIsMultisigScript-8 2304 0 -100.00% --- txscript/engine.go | 21 ++++++++ txscript/standard.go | 116 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 127 insertions(+), 10 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index f2d7b303c1..3c76b74794 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -580,6 +580,27 @@ func (vm *Engine) checkHashTypeEncoding(hashType SigHashType) error { return nil } +// isStrictPubKeyEncoding returns whether or not the passed public key adheres +// to the strict encoding requirements. +func isStrictPubKeyEncoding(pubKey []byte) bool { + if len(pubKey) == 33 && (pubKey[0] == 0x02 || pubKey[0] == 0x03) { + // Compressed + return true + } + if len(pubKey) == 65 { + switch pubKey[0] { + case 0x04: + // Uncompressed + return true + + case 0x06, 0x07: + // Hybrid + return true + } + } + return false +} + // checkPubKeyEncoding returns whether or not the passed public key adheres to // the strict encoding requirements if enabled. func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error { diff --git a/txscript/standard.go b/txscript/standard.go index 25483aebe5..096de4e561 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -216,6 +216,8 @@ func isPubkeyHash(pops []parsedOpcode) bool { // isMultiSig returns true if the passed script is a multisig transaction, false // otherwise. +// +// DEPECATED. Use isMultisigScript or extractMultisigScriptDetails instead. func isMultiSig(pops []parsedOpcode) bool { // The absolute minimum is 1 pubkey: // OP_0/OP_1-16 OP_1 OP_CHECKMULTISIG @@ -248,6 +250,108 @@ func isMultiSig(pops []parsedOpcode) bool { return true } +// multiSigDetails houses details extracted from a standard multisig script. +type multiSigDetails struct { + requiredSigs int + numPubKeys int + pubKeys [][]byte + valid bool +} + +// extractMultisigScriptDetails attempts to extract details from the passed +// script if it is a standard multisig script. The returned details struct will +// have the valid flag set to false otherwise. +// +// The extract pubkeys flag indicates whether or not the pubkeys themselves +// should also be extracted and is provided because extracting them results in +// an allocation that the caller might wish to avoid. The pubKeys member of +// the returned details struct will be nil when the flag is false. +// +// NOTE: This function is only valid for version 0 scripts. The returned +// details struct will always be empty and have the valid flag set to false for +// other script versions. +func extractMultisigScriptDetails(scriptVersion uint16, script []byte, extractPubKeys bool) multiSigDetails { + // The only currently supported script version is 0. + if scriptVersion != 0 { + return multiSigDetails{} + } + + // A multi-signature script is of the form: + // NUM_SIGS PUBKEY PUBKEY PUBKEY ... NUM_PUBKEYS OP_CHECKMULTISIG + + // The script can't possibly be a multisig script if it doesn't end with + // OP_CHECKMULTISIG or have at least two small integer pushes preceding it. + // Fail fast to avoid more work below. + if len(script) < 3 || script[len(script)-1] != OP_CHECKMULTISIG { + return multiSigDetails{} + } + + // The first opcode must be a small integer specifying the number of + // signatures required. + tokenizer := MakeScriptTokenizer(scriptVersion, script) + if !tokenizer.Next() || !isSmallInt(tokenizer.Opcode()) { + return multiSigDetails{} + } + requiredSigs := asSmallInt(tokenizer.Opcode()) + + // The next series of opcodes must either push public keys or be a small + // integer specifying the number of public keys. + var numPubKeys int + var pubKeys [][]byte + if extractPubKeys { + pubKeys = make([][]byte, 0, MaxPubKeysPerMultiSig) + } + for tokenizer.Next() { + if isSmallInt(tokenizer.Opcode()) { + break + } + + data := tokenizer.Data() + numPubKeys++ + if !isStrictPubKeyEncoding(data) { + continue + } + if extractPubKeys { + pubKeys = append(pubKeys, data) + } + } + if tokenizer.Done() { + return multiSigDetails{} + } + + // The next opcode must be a small integer specifying the number of public + // keys required. + op := tokenizer.Opcode() + if !isSmallInt(op) || asSmallInt(op) != numPubKeys { + return multiSigDetails{} + } + + // There must only be a single opcode left unparsed which will be + // OP_CHECKMULTISIG per the check above. + if int32(len(tokenizer.Script()))-tokenizer.ByteIndex() != 1 { + return multiSigDetails{} + } + + return multiSigDetails{ + requiredSigs: requiredSigs, + numPubKeys: numPubKeys, + pubKeys: pubKeys, + valid: true, + } +} + +// isMultisigScript returns whether or not the passed script is a standard +// multisig script. +// +// NOTE: This function is only valid for version 0 scripts. It will always +// return false for other script versions. +func isMultisigScript(scriptVersion uint16, script []byte) bool { + // Since this is only checking the form of the script, don't extract the + // public keys to avoid the allocation. + details := extractMultisigScriptDetails(scriptVersion, script, false) + return details.valid +} + // IsMultisigScript returns whether or not the passed script is a standard // multisignature script. // @@ -257,16 +361,8 @@ func isMultiSig(pops []parsedOpcode) bool { // // The error is DEPRECATED and will be removed in the major version bump. func IsMultisigScript(script []byte) (bool, error) { - if len(script) == 0 || script == nil { - return false, nil - } - - pops, err := parseScript(script) - if err != nil { - return false, err - } - - return isMultiSig(pops), nil + const scriptVersion = 0 + return isMultisigScript(scriptVersion, script), nil } // isNullData returns true if the passed script is a null data transaction, From 02dab1695f3d99299f6448523529197cfca584d3 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:17 -0500 Subject: [PATCH 020/108] txscript: Add benchmarks for IsMutlsigSigScript. --- txscript/bench_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++ txscript/standard.go | 18 ++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 49be4958c6..41d79ccba1 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -219,3 +219,51 @@ func BenchmarkIsMultisigScript(b *testing.B) { } } } + +// BenchmarkIsMultisigSigScript benchmarks how long it takes IsMultisigSigScript +// to analyze a very large script. +func BenchmarkIsMultisigSigScriptLarge(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if IsMultisigSigScript(script) { + b.Fatalf("script should NOT be reported as mutisig sig script") + } + } +} + +// BenchmarkIsMultisigSigScript benchmarks how long it takes IsMultisigSigScript +// to analyze both a 1-of-2 multisig public key script (which should be false) +// and a signature script comprised of a pay-to-script-hash 1-of-2 multisig +// redeem script (which should be true). +func BenchmarkIsMultisigSigScript(b *testing.B) { + multisigShortForm := "1 " + + "DATA_33 " + + "0x030478aaaa2be30772f1e69e581610f1840b3cf2fe7228ee0281cd599e5746f81e " + + "DATA_33 " + + "0x0284f4d078b236a9ff91661f8ffbe012737cd3507566f30fd97d25f2b23539f3cd " + + "2 CHECKMULTISIG" + pkScript := mustParseShortForm(multisigShortForm) + + sigHex := "0x304402205795c3ab6ba11331eeac757bf1fc9c34bef0c7e1a9c8bd5eebb8" + + "82f3b79c5838022001e0ab7b4c7662e4522dc5fa479e4b4133fa88c6a53d895dc1d5" + + "2eddc7bbcf2801 " + sigScript := mustParseShortForm("DATA_71 " + sigHex + "DATA_71 " + + multisigShortForm) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if IsMultisigSigScript(pkScript) { + b.Fatalf("script should NOT be reported as mutisig sig script") + } + if !IsMultisigSigScript(sigScript) { + b.Fatalf("script should be reported as a mutisig sig script") + } + } +} diff --git a/txscript/standard.go b/txscript/standard.go index 096de4e561..51dcbfb9ca 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -365,6 +365,24 @@ func IsMultisigScript(script []byte) (bool, error) { return isMultisigScript(scriptVersion, script), nil } +// IsMultisigSigScript takes a script, parses it, then returns whether or +// not it is a multisignature script. +func IsMultisigSigScript(script []byte) bool { + if len(script) == 0 || script == nil { + return false + } + pops, err := parseScript(script) + if err != nil { + return false + } + subPops, err := parseScript(pops[len(pops)-1].data) + if err != nil { + return false + } + + return isMultiSig(subPops) +} + // isNullData returns true if the passed script is a null data transaction, // false otherwise. func isNullData(pops []parsedOpcode) bool { From 34ebf0f32f4729eb5f37133cae9f9f2b4fdc7033 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:18 -0500 Subject: [PATCH 021/108] txscript: Optimize IsMultisigSigScript. This converts the IsMultisigSigScript function to analyze the raw script and make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it first rejects scripts that can't possibly fit the bill due to the final byte of what would be the redeem script not being the appropriate opcode or the overall script not having enough bytes. Then, it uses a new function that is introduced named finalOpcodeData that uses the tokenizer to return any data associated with the final opcode in the signature script (which will be nil for non-push opcodes or if the script fails to parse) and analyzes it as if it were a redeem script when it is non nil. It is also worth noting that this new implementation intentionally has the same semantic difference from the existing implementation as the updated IsMultisigScript function in regards to allowing zero pubkeys whereas previously it incorrectly required at least one pubkey. Finally, the comment is modified to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script that is not a multisig script and both a 1-of-2 multisig public key script (which should be false) and a signature script comprised of a pay-to-script-hash 1-of-2 multisig redeem script (which should be true): benchmark old ns/op new ns/op delta BenchmarkIsMultisigSigScriptLarge-8 69328 2.93 -100.00% BenchmarkIsMultisigSigScript-8 2375 146 -93.85% benchmark old allocs new allocs delta BenchmarkIsMultisigSigScriptLarge-8 5 0 -100.00% BenchmarkIsMultisigSigScript-8 3 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigSigScriptLarge-8 330035 0 -100.00% BenchmarkIsMultisigSigScript-8 9472 0 -100.00% --- txscript/script.go | 19 +++++++++++++++++++ txscript/standard.go | 37 +++++++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index a8cfd16656..eb5be8b7ca 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -769,6 +769,25 @@ func GetSigOpCount(script []byte) int { return getSigOpCount(pops, false) } +// finalOpcodeData returns the data associated with the final opcode in the +// script. It will return nil if the script fails to parse. +func finalOpcodeData(scriptVersion uint16, script []byte) []byte { + // Avoid unnecessary work. + if len(script) == 0 { + return nil + } + + var data []byte + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + data = tokenizer.Data() + } + if tokenizer.Err() != nil { + return nil + } + return data +} + // GetPreciseSigOpCount returns the number of signature operations in // scriptPubKey. If bip16 is true then scriptSig may be searched for the // Pay-To-Script-Hash script in order to find the precise number of signature diff --git a/txscript/standard.go b/txscript/standard.go index 51dcbfb9ca..272b42b8fa 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -365,22 +365,39 @@ func IsMultisigScript(script []byte) (bool, error) { return isMultisigScript(scriptVersion, script), nil } -// IsMultisigSigScript takes a script, parses it, then returns whether or -// not it is a multisignature script. +// IsMultisigSigScript returns whether or not the passed script appears to be a +// signature script which consists of a pay-to-script-hash multi-signature +// redeem script. Determining if a signature script is actually a redemption of +// pay-to-script-hash requires the associated public key script which is often +// expensive to obtain. Therefore, this makes a fast best effort guess that has +// a high probability of being correct by checking if the signature script ends +// with a data push and treating that data push as if it were a p2sh redeem +// script +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func IsMultisigSigScript(script []byte) bool { - if len(script) == 0 || script == nil { - return false - } - pops, err := parseScript(script) - if err != nil { + const scriptVersion = 0 + + // The script can't possibly be a multisig signature script if it doesn't + // end with OP_CHECKMULTISIG in the redeem script or have at least two small + // integers preceding it, and the redeem script itself must be preceded by + // at least a data push opcode. Fail fast to avoid more work below. + if len(script) < 4 || script[len(script)-1] != OP_CHECKMULTISIG { return false } - subPops, err := parseScript(pops[len(pops)-1].data) - if err != nil { + + // Parse through the script to find the last opcode and any data it might + // push and treat it as a p2sh redeem script even though it might not + // actually be one. + possibleRedeemScript := finalOpcodeData(scriptVersion, script) + if possibleRedeemScript == nil { return false } - return isMultiSig(subPops) + // Finally, return if that possible redeem script is a multisig script. + return isMultisigScript(scriptVersion, possibleRedeemScript) } // isNullData returns true if the passed script is a null data transaction, From ce1513df03cfc708b2f6eacd1080e6f078455c84 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:25 -0500 Subject: [PATCH 022/108] txscript: Add benchmark for IsPushOnlyScript. --- txscript/bench_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 41d79ccba1..ca64267a8e 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -267,3 +267,18 @@ func BenchmarkIsMultisigSigScript(b *testing.B) { } } } + +// BenchmarkIsPushOnlyScript benchmarks how long it takes IsPushOnlyScript to +// analyze a very large script. +func BenchmarkIsPushOnlyScript(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsPushOnlyScript(script) + } +} From 2d5f7cf82540247f716d65f91dcbc934e67ccb67 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:26 -0500 Subject: [PATCH 023/108] txscript: Optimize IsPushOnlyScript. This converts the IsPushOnlyScript function to make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. It also deprecates the isPushOnly function that requires opcodes in favor of the new function and modifies the comment on IsPushOnlyScript to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkIsPushOnlyScript-8 62412 622 -99.00% benchmark old allocs new allocs delta BenchmarkIsPushOnlyScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPushOnlyScript-8 311299 0 -100.00% --- txscript/script.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index eb5be8b7ca..43048e9526 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -182,6 +182,8 @@ func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) { } // isPushOnly returns true if the script only pushes data, false otherwise. +// +// DEPRECATED. Use IsPushOnlyScript instead. func isPushOnly(pops []parsedOpcode) bool { // NOTE: This function does NOT verify opcodes directly since it is // internal and is only called with parsed opcodes for scripts that did @@ -199,15 +201,27 @@ func isPushOnly(pops []parsedOpcode) bool { return true } -// IsPushOnlyScript returns whether or not the passed script only pushes data. +// IsPushOnlyScript returns whether or not the passed script only pushes data +// according to the consensus definition of pushing data. // -// False will be returned when the script does not parse. +// WARNING: This function always treats the passed script as version 0. Great +// care must be taken if introducing a new script version because it is used in +// consensus which, unfortunately as of the time of this writing, does not check +// script versions before checking if it is a push only script which means nodes +// on existing rules will treat new version scripts as if they were version 0. func IsPushOnlyScript(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false + const scriptVersion = 0 + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + // All opcodes up to OP_16 are data push instructions. + // NOTE: This does consider OP_RESERVED to be a data push instruction, + // but execution of OP_RESERVED will fail anyway and matches the + // behavior required by consensus. + if tokenizer.Opcode() > OP_16 { + return false + } } - return isPushOnly(pops) + return tokenizer.Err() == nil } // parseScriptTemplate is the same as parseScript but allows the passing of the From e422d42d7fdde45c222810363d24b8eb3340cf2a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 01:43:18 -0700 Subject: [PATCH 024/108] txscript: Add benchmark IsPayToWitnessPubkeyHash --- txscript/bench_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index ca64267a8e..de6636d9e7 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -282,3 +282,18 @@ func BenchmarkIsPushOnlyScript(b *testing.B) { _ = IsPushOnlyScript(script) } } + +// BenchmarkIsWitnessPubKeyHash benchmarks how long it takes to analyze a very +// large script to determine if it is a standard witness pubkey hash script. +func BenchmarkIsWitnessPubKeyHash(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsPayToWitnessPubKeyHash(script) + } +} From 54d08ebd5fa1706790e2cf73e87804b071368a0c Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 13:22:40 -0800 Subject: [PATCH 025/108] txscript: Optimize IsPayToWitnessPubKeyHash This converts the IsPayToWitnessPubKeyHash function to analyze the raw script instead of the far less efficient parseScript, thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractWitnessPubKeyHash and works with the raw script bytes to simultaneously deteremine if the script is a p2wkh, and in case it is, extract and return the hash. The second new function is name isWitnessPubKeyHashScript which is defined in terms of the former. The extract function is approach was chosen because it is common for callers to want to only extract relevant details from the script if the script is of the specific type. Extracting those details requires the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two and define the type determination in terms of the result so long as the extraction does not require allocations. Finally, this deprecates the isWitnessPubKeyHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToWitnessPubKeyHash to explicitly call out the script version semantics. The following is a before and after comparison of executing IsPayToWitnessPubKeyHash on a large script: benchmark old ns/op new ns/op delta BenchmarkIsWitnessPubKeyHash-8 68927 0.53 -100.00% benchmark old allocs new allocs delta BenchmarkIsWitnessPubKeyHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsWitnessPubKeyHash-8 311299 0 -100.00% --- txscript/script.go | 6 +----- txscript/standard.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 43048e9526..59dc2e272b 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -110,11 +110,7 @@ func IsPayToWitnessScriptHash(script []byte) bool { // IsPayToWitnessPubKeyHash returns true if the is in the standard // pay-to-witness-pubkey-hash (P2WKH) format, false otherwise. func IsPayToWitnessPubKeyHash(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false - } - return isWitnessPubKeyHash(pops) + return isWitnessPubKeyHashScript(script) } // isWitnessPubKeyHash returns true if the passed script is a diff --git a/txscript/standard.go b/txscript/standard.go index 272b42b8fa..513c9ce181 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -400,6 +400,27 @@ func IsMultisigSigScript(script []byte) bool { return isMultisigScript(scriptVersion, possibleRedeemScript) } +// extractWitnessPubKeyHash extracts the witness public key hash from the passed +// script if it is a standard pay-to-witness-pubkey-hash script. It will return +// nil otherwise. +func extractWitnessPubKeyHash(script []byte) []byte { + // A pay-to-witness-pubkey-hash script is of the form: + // OP_0 OP_DATA_20 <20-byte-hash> + if len(script) == 22 && + script[0] == OP_0 && + script[1] == OP_DATA_20 { + + return script[2:22] + } + return nil +} + +// isWitnessPubKeyHashScript returns whether or not the passed script is a +// standard pay-to-witness-pubkey-hash script. +func isWitnessPubKeyHashScript(script []byte) bool { + return extractWitnessPubKeyHash(script) != nil +} + // isNullData returns true if the passed script is a null data transaction, // false otherwise. func isNullData(pops []parsedOpcode) bool { From 728ce1001d210f291796a0ad1748be2fc3abd505 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 01:57:31 -0700 Subject: [PATCH 026/108] txscript: Add benchmark for IsPayToWitnessScriptHash --- txscript/bench_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index de6636d9e7..529d32f7ff 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -297,3 +297,18 @@ func BenchmarkIsWitnessPubKeyHash(b *testing.B) { _ = IsPayToWitnessPubKeyHash(script) } } + +// BenchmarkIsWitnessScriptHash benchmarks how long it takes to analyze a very +// large script to determine if it is a standard witness script hash script. +func BenchmarkIsWitnessScriptHash(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsPayToWitnessScriptHash(script) + } +} From 55a6bb5e325b8a9511e485160420c0acf236b44a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 13:39:10 -0800 Subject: [PATCH 027/108] txscript: Optimize IsPayToWitnessScriptHash This converts the IsPayToWitnessScriptHash function to analyze the raw script instead of using the far less efficient parseScript, thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractWitnessScriptHash and works with the raw script byte to simultaneously deteremine if the script is a p2wsh script, and in the case that is is, extract and return the hash. The second new function is named isWitnessScriptHashScript and is defined in terms of the former. The extract function approach was chosed because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result, so long as the extraction does not require allocations. Finally, this also deprecates the isWitnessScriptHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToWitnessScriptHash to call out the script version semantics. The following is a before and after comparison of executing IsPayToWitnessScriptHash on a large script: benchmark old ns/op new ns/op delta BenchmarkIsWitnessScriptHash-8 62774 0.63 -100.00% benchmark old allocs new allocs delta BenchmarkIsWitnessScriptHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsWitnessScriptHash-8 311299 0 -100.00% --- txscript/script.go | 6 +----- txscript/standard.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 59dc2e272b..5968cec72a 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -100,11 +100,7 @@ func isWitnessScriptHash(pops []parsedOpcode) bool { // IsPayToWitnessScriptHash returns true if the is in the standard // pay-to-witness-script-hash (P2WSH) format, false otherwise. func IsPayToWitnessScriptHash(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false - } - return isWitnessScriptHash(pops) + return isWitnessScriptHashScript(script) } // IsPayToWitnessPubKeyHash returns true if the is in the standard diff --git a/txscript/standard.go b/txscript/standard.go index 513c9ce181..a53180f148 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -421,6 +421,28 @@ func isWitnessPubKeyHashScript(script []byte) bool { return extractWitnessPubKeyHash(script) != nil } +// extractWitnessScriptHash extracts the witness script hash from the passed +// script if it is standard pay-to-witness-script-hash script. It will return +// nil otherwise. +func extractWitnessScriptHash(script []byte) []byte { + // A pay-to-witness-script-hash script is of the form: + // OP_0 OP_DATA_32 <32-byte-hash> + if len(script) == 34 && + script[0] == OP_0 && + script[1] == OP_DATA_32 { + + return script[2:34] + } + + return nil +} + +// isWitnessScriptHashScript returns whether or not the passed script is a +// standard pay-to-witness-script-hash script. +func isWitnessScriptHashScript(script []byte) bool { + return extractWitnessScriptHash(script) != nil +} + // isNullData returns true if the passed script is a null data transaction, // false otherwise. func isNullData(pops []parsedOpcode) bool { From e4118c914fb8cdcced379a41b62f95402796cbed Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:49 -0500 Subject: [PATCH 028/108] txscript: Add benchmark for IsNullData --- txscript/bench_test.go | 15 +++++++++++++++ txscript/script.go | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 529d32f7ff..fda8377141 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -312,3 +312,18 @@ func BenchmarkIsWitnessScriptHash(b *testing.B) { _ = IsPayToWitnessScriptHash(script) } } + +// BenchmarkIsNullDataScript benchmarks how long it takes to analyze a very +// large script to determine if it is a standard nulldata script. +func BenchmarkIsNullDataScript(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsNullData(script) + } +} diff --git a/txscript/script.go b/txscript/script.go index 5968cec72a..7f812207f9 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -151,6 +151,16 @@ func isWitnessProgram(pops []parsedOpcode) bool { (len(pops[1].data) >= 2 && len(pops[1].data) <= 40) } +// IsNullData returns true if the passed script is a null data script, false +// otherwise. +func IsNullData(script []byte) bool { + pops, err := parseScript(script) + if err != nil { + return false + } + return isNullData(pops) +} + // ExtractWitnessProgramInfo attempts to extract the witness program version, // as well as the witness program itself from the passed script. func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) { From 5f771c1aaa5db7427011de1478182ed0d95ecf37 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 15:15:34 -0800 Subject: [PATCH 029/108] txscript: Optimize IsNullData This converts the IsNullData function to analyze the raw script instead of using the far less efficient parseScript, thereby significantly optimizing the function. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkIsNullDataScript-8 62495 2.65 -100.00% benchmark old allocs new allocs delta BenchmarkIsNullDataScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsNullDataScript-8 311299 0 -100.00% --- txscript/script.go | 7 ++----- txscript/standard.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 7f812207f9..691e31cc64 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -154,11 +154,8 @@ func isWitnessProgram(pops []parsedOpcode) bool { // IsNullData returns true if the passed script is a null data script, false // otherwise. func IsNullData(script []byte) bool { - pops, err := parseScript(script) - if err != nil { - return false - } - return isNullData(pops) + const scriptVersion = 0 + return isNullDataScript(scriptVersion, script) } // ExtractWitnessProgramInfo attempts to extract the witness program version, diff --git a/txscript/standard.go b/txscript/standard.go index a53180f148..9825f61bea 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -461,6 +461,41 @@ func isNullData(pops []parsedOpcode) bool { len(pops[1].data) <= MaxDataCarrierSize } +// isNullDataScript returns whether or not the passed script is a standard +// null data script. +// +// NOTE: This function is only valid for version 0 scripts. It will always +// return false for other script versions. +func isNullDataScript(scriptVersion uint16, script []byte) bool { + // The only currently supported script version is 0. + if scriptVersion != 0 { + return false + } + + // A null script is of the form: + // OP_RETURN + // + // Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a + // data push up to MaxDataCarrierSize bytes. + + // The script can't possibly be a a null data script if it doesn't start + // with OP_RETURN. Fail fast to avoid more work below. + if len(script) < 1 || script[0] != OP_RETURN { + return false + } + + // Single OP_RETURN. + if len(script) == 1 { + return true + } + + // OP_RETURN followed by data push up to MaxDataCarrierSize bytes. + tokenizer := MakeScriptTokenizer(scriptVersion, script[1:]) + return tokenizer.Next() && tokenizer.Done() && + (isSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= OP_PUSHDATA4) && + len(tokenizer.Data()) <= MaxDataCarrierSize +} + // scriptType returns the type of the script being inspected from the known // standard types. func typeOfScript(pops []parsedOpcode) ScriptClass { From 77660b7fb0e2de0fd4d159fd09e29bf4025690df Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:20 -0500 Subject: [PATCH 030/108] txscript: Add benchmark for IsUnspendable. --- txscript/bench_test.go | 14 ++++++++++++++ txscript/script_test.go | 13 ------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index fda8377141..c8f7d0f790 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -327,3 +327,17 @@ func BenchmarkIsNullDataScript(b *testing.B) { _ = IsNullData(script) } } + +// BenchmarkIsUnspendable benchmarks how long it takes IsUnspendable to analyze +// a very large script. +func BenchmarkIsUnspendable(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = IsUnspendable(script) + } +} diff --git a/txscript/script_test.go b/txscript/script_test.go index 34c8ef9740..665d9ef633 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -4334,16 +4334,3 @@ func TestIsUnspendable(t *testing.T) { } } } - -// BenchmarkIsUnspendable adds a benchmark to compare the time and allocations -// necessary for the IsUnspendable function. -func BenchmarkIsUnspendable(b *testing.B) { - pkScriptToUse := []byte{0xa9, 0x14, 0x82, 0x1d, 0xba, 0x94, 0xbc, 0xfb, 0xa2, 0x57, 0x36, 0xa3, 0x9e, 0x5d, 0x14, 0x5d, 0x69, 0x75, 0xba, 0x8c, 0x0b, 0x42, 0x87} - var res bool = false - for i := 0; i < b.N; i++ { - res = IsUnspendable(pkScriptToUse) - } - if res { - b.Fatalf("Benchmark should never have res be %t\n", res) - } -} From e98b7c1a9a10ae18e2e724aceeee57c216095517 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:21 -0500 Subject: [PATCH 031/108] txscript: Optimize IsUnspendable. This converts the IsUnspendable function to make use of a combination of raw script analysis and the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. It is important to note that this new implementation intentionally has a semantic difference from the existing implementation in that it will now report scripts that are larger than the max allowed script size are unspendable as well. Finally, the comment is modified to explicitly call out the script version semantics. Note: this function was recently optimized in master, so the gains here are less noticable than other optimizations. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkIsUnspendable-8 656 584 -10.98% benchmark old allocs new allocs delta BenchmarkIsUnspendable-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsUnspendable-8 1 0 -100.00% --- txscript/script.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 691e31cc64..e933167d4c 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -917,15 +917,22 @@ func checkScriptParses(scriptVersion uint16, script []byte) error { // IsUnspendable returns whether the passed public key script is unspendable, or // guaranteed to fail at execution. This allows inputs to be pruned instantly // when entering the UTXO set. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func IsUnspendable(pkScript []byte) bool { - // Not provably unspendable - if len(pkScript) == 0 { - return false - } - firstOpcode, err := checkScriptTemplateParseable(pkScript, &opcodeArray) - if err != nil { + // The script is unspendable if starts with OP_RETURN or is guaranteed + // to fail at execution due to being larger than the max allowed script + // size. + switch { + case len(pkScript) > 0 && pkScript[0] == OP_RETURN: + return true + case len(pkScript) > MaxScriptSize: return true } - return firstOpcode != nil && *firstOpcode == OP_RETURN + // The script is unspendable if it is guaranteed to fail at execution. + const scriptVersion = 0 + return checkScriptParses(scriptVersion, pkScript) != nil } From 549a1f26f2de278362b0b7643954c2e5a5a5dd58 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 00:35:28 -0700 Subject: [PATCH 032/108] txscript/engine: Optimize new engine push only script This modifies the check for whether or not a pay-to-script-hash signature script is a push only script to make use of the new and more efficient raw script function. Also, since the script will have already been checked further above when the ScriptVerifySigPushOnly flags is set, avoid checking it again in that case. Backport of af67951b9a66df3aac1bf3d6376af0730287bbf2 --- txscript/engine.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/txscript/engine.go b/txscript/engine.go index 3c76b74794..06f454d372 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -946,7 +946,11 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags if vm.hasFlag(ScriptBip16) && isScriptHash(vm.scripts[1]) { // Only accept input scripts that push data for P2SH. - if !isPushOnly(vm.scripts[0]) { + // Notice that the push only checks have already been done when + // the flag to verify signature scripts are push only is set + // above, so avoid checking again. + alreadyChecked := vm.hasFlag(ScriptVerifySigPushOnly) + if !alreadyChecked && !isPushOnly(vm.scripts[0]) { return nil, scriptError(ErrNotPushOnly, "pay to script hash is not push only") } From a59e01c23c057d904cf4b23df0d85620b42a39cd Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 00:43:38 -0700 Subject: [PATCH 033/108] txscript/engine: Use optimized IsPushOnlyScript --- txscript/engine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/txscript/engine.go b/txscript/engine.go index 06f454d372..f0a9485164 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -950,7 +950,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags // the flag to verify signature scripts are push only is set // above, so avoid checking again. alreadyChecked := vm.hasFlag(ScriptVerifySigPushOnly) - if !alreadyChecked && !isPushOnly(vm.scripts[0]) { + if !alreadyChecked && !IsPushOnlyScript(scriptSig) { return nil, scriptError(ErrNotPushOnly, "pay to script hash is not push only") } From 1be3450efb84a7eda89fe1b2040f570e4809a9ae Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 00:49:23 -0700 Subject: [PATCH 034/108] txscript/engine: Use optimized isScriptHashScript --- txscript/engine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/txscript/engine.go b/txscript/engine.go index f0a9485164..703baef79b 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -944,7 +944,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags vm.scriptIdx++ } - if vm.hasFlag(ScriptBip16) && isScriptHash(vm.scripts[1]) { + if vm.hasFlag(ScriptBip16) && isScriptHashScript(scriptPubKey) { // Only accept input scripts that push data for P2SH. // Notice that the push only checks have already been done when // the flag to verify signature scripts are push only is set From 98dd6a900f3a1f997ffab68651e49f2772b25d5e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 00:50:54 -0700 Subject: [PATCH 035/108] txscript/engine: Check ps2h push before parsing script This moves the check for non push-only pay-to-script-hash signature scripts before the script parsing logic when creating a new engine instance to avoid the extra overhead in the error case. --- txscript/engine.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 703baef79b..576adc7188 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -918,6 +918,21 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags "signature script is not push only") } + // The signature script must only contain data pushes for PS2H which is + // determined based on the form of the public key script. + if vm.hasFlag(ScriptBip16) && isScriptHashScript(scriptPubKey) { + // Only accept input scripts that push data for P2SH. + // Notice that the push only checks have already been done when + // the flag to verify signature scripts are push only is set + // above, so avoid checking again. + alreadyChecked := vm.hasFlag(ScriptVerifySigPushOnly) + if !alreadyChecked && !IsPushOnlyScript(scriptSig) { + return nil, scriptError(ErrNotPushOnly, + "pay to script hash is not push only") + } + vm.bip16 = true + } + // The engine stores the scripts in parsed form using a slice. This // allows multiple scripts to be executed in sequence. For example, // with a pay-to-script-hash transaction, there will be ultimately be @@ -943,19 +958,6 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags if len(scripts[0]) == 0 { vm.scriptIdx++ } - - if vm.hasFlag(ScriptBip16) && isScriptHashScript(scriptPubKey) { - // Only accept input scripts that push data for P2SH. - // Notice that the push only checks have already been done when - // the flag to verify signature scripts are push only is set - // above, so avoid checking again. - alreadyChecked := vm.hasFlag(ScriptVerifySigPushOnly) - if !alreadyChecked && !IsPushOnlyScript(scriptSig) { - return nil, scriptError(ErrNotPushOnly, - "pay to script hash is not push only") - } - vm.bip16 = true - } if vm.hasFlag(ScriptVerifyMinimalData) { vm.dstack.verifyMinimalData = true vm.astack.verifyMinimalData = true From 8316a06a0ee4f90512d232ce4ccd00f072ff4e0c Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:19 -0500 Subject: [PATCH 036/108] txscript: Add benchmark for GetSigOpCount. --- txscript/bench_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index c8f7d0f790..7d0b90c4ad 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -341,3 +341,18 @@ func BenchmarkIsUnspendable(b *testing.B) { _ = IsUnspendable(script) } } + +// BenchmarkGetSigOpCount benchmarks how long it takes to count the signature +// operations of a very large script. +func BenchmarkGetSigOpCount(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = GetSigOpCount(script) + } +} From 4d5c0b25295855676463942e487635a5cdc45a15 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:20 -0500 Subject: [PATCH 037/108] txscript: Optimize GetSigOpCount. This converts the GetSigOpCount function to make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. A new function named countSigOpsV0 which accepts the raw script is introduced to perform the bulk of the work so it can be reused for precise signature operation counting as well in a later commit. It retains the same semantics in terms of counting the number of signature operations either up to the first parse error or the end of the script in the case it parses successfully as required by consensus. Finally, this also deprecates the getSigOpCount function that requires opcodes in favor of the new function and modifies the comment on GetSigOpCount to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkGetSigOpCount-8 61051 677 -98.89% benchmark old allocs new allocs delta BenchmarkGetSigOpCount-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkGetSigOpCount-8 311299 0 -100.00% --- txscript/script.go | 66 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index e933167d4c..32654f788d 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -741,6 +741,8 @@ func asSmallInt(op byte) int { // signature operations in the script provided by pops. If precise mode is // requested then we attempt to count the number of operations for a multisig // op. Otherwise we use the maximum. +// +// DEPRECATED. Use countSigOpsV0 instead. func getSigOpCount(pops []parsedOpcode, precise bool) int { nSigs := 0 for i, pop := range pops { @@ -771,15 +773,71 @@ func getSigOpCount(pops []parsedOpcode, precise bool) int { return nSigs } +// countSigOpsV0 returns the number of signature operations in the provided +// script up to the point of the first parse failure or the entire script when +// there are no parse failures. The precise flag attempts to accurately count +// the number of operations for a multisig operation versus using the maximum +// allowed. +// +// WARNING: This function always treats the passed script as version 0. Great +// care must be taken if introducing a new script version because it is used in +// consensus which, unfortunately as of the time of this writing, does not check +// script versions before counting their signature operations which means nodes +// on existing rules will count new version scripts as if they were version 0. +func countSigOpsV0(script []byte, precise bool) int { + const scriptVersion = 0 + + numSigOps := 0 + tokenizer := MakeScriptTokenizer(scriptVersion, script) + prevOp := byte(OP_INVALIDOPCODE) + for tokenizer.Next() { + switch tokenizer.Opcode() { + case OP_CHECKSIG, OP_CHECKSIGVERIFY: + numSigOps++ + + case OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY: + // Note that OP_0 is treated as the max number of sigops here in + // precise mode despite it being a valid small integer in order to + // highly discourage multisigs with zero pubkeys. + // + // Also, even though this is referred to as "precise" counting, it's + // not really precise at all due to the small int opcodes only + // covering 1 through 16 pubkeys, which means this will count any + // more than that value (e.g. 17, 18 19) as the maximum number of + // allowed pubkeys. This is, unfortunately, now part of + // the Bitcion consensus rules, due to historical + // reasons. This could be made more correct with a new + // script version, however, ideally all multisignaure + // operations in new script versions should move to + // aggregated schemes such as Schnorr instead. + if precise && prevOp >= OP_1 && prevOp <= OP_16 { + numSigOps += asSmallInt(prevOp) + } else { + numSigOps += MaxPubKeysPerMultiSig + } + + default: + // Not a sigop. + } + + prevOp = tokenizer.Opcode() + } + + return numSigOps +} + // GetSigOpCount provides a quick count of the number of signature operations // in a script. a CHECKSIG operations counts for 1, and a CHECK_MULTISIG for 20. // If the script fails to parse, then the count up to the point of failure is // returned. +// +// WARNING: This function always treats the passed script as version 0. Great +// care must be taken if introducing a new script version because it is used in +// consensus which, unfortunately as of the time of this writing, does not check +// script versions before counting their signature operations which means nodes +// on existing rules will count new version scripts as if they were version 0. func GetSigOpCount(script []byte) int { - // Don't check error since parseScript returns the parsed-up-to-error - // list of pops. - pops, _ := parseScript(script) - return getSigOpCount(pops, false) + return countSigOpsV0(script, false) } // finalOpcodeData returns the data associated with the final opcode in the From cc802d14079debe347b699bb69c913069e679c6a Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:30 -0500 Subject: [PATCH 038/108] txscript: Add benchmark for GetPreciseSigOpCount. --- txscript/bench_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 7d0b90c4ad..817eb98f04 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -356,3 +356,29 @@ func BenchmarkGetSigOpCount(b *testing.B) { _ = GetSigOpCount(script) } } + +// BenchmarkGetPreciseSigOpCount benchmarks how long it takes to count the +// signature operations of a very large script using the more precise counting +// method. +func BenchmarkGetPreciseSigOpCount(b *testing.B) { + redeemScript, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + // Create a fake pay-to-script-hash to pass the necessary checks and create + // the signature script accordingly by pushing the generated "redeem" script + // as the final data push so the benchmark will cover the p2sh path. + scriptHash := "0x0000000000000000000000000000000000000001" + pkScript := mustParseShortForm("HASH160 DATA_20 " + scriptHash + " EQUAL") + sigScript, err := NewScriptBuilder().AddFullData(redeemScript).Script() + if err != nil { + b.Fatalf("failed to create signature script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = GetPreciseSigOpCount(sigScript, pkScript, true) + } +} From a4a21c0b087b9e73ec33def05d4a8d06671eea16 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:31 -0500 Subject: [PATCH 039/108] txscript: Optimize GetPreciseSigOpCount. This converts the GetPreciseSigOpCount function to use a combination of raw script analysis and the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In particular it uses the recently converted isScriptHashScript, IsPushOnlyScript, and countSigOpsV0 functions along with the recently added finalOpcodeData functions. It also modifies the comment to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkGetPreciseSigOpCount-8 130223 742 -99.43% benchmark old allocs new allocs delta BenchmarkGetPreciseSigOpCount-8 3 0 -100.00% benchmark old bytes new bytes delta BenchmarkGetPreciseSigOpCount-8 623367 0 -100.00% --- txscript/script.go | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 32654f788d..aee1aaacad 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -864,44 +864,44 @@ func finalOpcodeData(scriptVersion uint16, script []byte) []byte { // Pay-To-Script-Hash script in order to find the precise number of signature // operations in the transaction. If the script fails to parse, then the count // up to the point of failure is returned. -func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int { - // Don't check error since parseScript returns the parsed-up-to-error - // list of pops. - pops, _ := parseScript(scriptPubKey) - - // Treat non P2SH transactions as normal. - if !(bip16 && isScriptHash(pops)) { - return getSigOpCount(pops, true) - } +// +// WARNING: This function always treats the passed script as version 0. Great +// care must be taken if introducing a new script version because it is used in +// consensus which, unfortunately as of the time of this writing, does not check +// script versions before counting their signature operations which means nodes +// on existing rules will count new version scripts as if they were version 0. +// +// The third parameter is DEPRECATED and is unused. +func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, _ bool) int { + const scriptVersion = 0 - // The public key script is a pay-to-script-hash, so parse the signature - // script to get the final item. Scripts that fail to fully parse count - // as 0 signature operations. - sigPops, err := parseScript(scriptSig) - if err != nil { - return 0 + // Treat non P2SH transactions as normal. Note that signature operation + // counting includes all operations up to the first parse failure. + if !isScriptHashScript(scriptPubKey) { + return countSigOpsV0(scriptPubKey, true) } // The signature script must only push data to the stack for P2SH to be // a valid pair, so the signature operation count is 0 when that is not // the case. - if !isPushOnly(sigPops) || len(sigPops) == 0 { + if len(scriptSig) == 0 || !IsPushOnlyScript(scriptSig) { return 0 } // The P2SH script is the last item the signature script pushes to the // stack. When the script is empty, there are no signature operations. - shScript := sigPops[len(sigPops)-1].data - if len(shScript) == 0 { + // + // Notice that signature scripts that fail to fully parse count as 0 + // signature operations unlike public key and redeem scripts. + redeemScript := finalOpcodeData(scriptVersion, scriptSig) + if len(redeemScript) == 0 { return 0 } - // Parse the P2SH script and don't check the error since parseScript - // returns the parsed-up-to-error list of pops and the consensus rules - // dictate signature operations are counted up to the first parse - // failure. - shPops, _ := parseScript(shScript) - return getSigOpCount(shPops, true) + // Return the more precise sigops count for the redeem script. Note that + // signature operation counting includes all operations up to the first + // parse failure. + return countSigOpsV0(redeemScript, true) } // GetWitnessSigOpCount returns the number of signature operations generated by From 26d63c61dce4be4721d27506203cd00a5dbd5e4a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 12:58:03 -0800 Subject: [PATCH 040/108] txscript: add GetWitnessSigOpCountBenchmarks --- txscript/bench_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 817eb98f04..52965dca93 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -382,3 +382,44 @@ func BenchmarkGetPreciseSigOpCount(b *testing.B) { _ = GetPreciseSigOpCount(sigScript, pkScript, true) } } + +// BenchmarkGetWitnessSigOpCount benchmarks how long it takes to count the +// witness signature operations of a very large script. +func BenchmarkGetWitnessSigOpCountP2WKH(b *testing.B) { + pkScript := mustParseShortForm("OP_0 DATA_20 0x0000000000000000000000000000000000000000") + redeemScript, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + witness := wire.TxWitness{ + redeemScript, + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = GetWitnessSigOpCount(nil, pkScript, witness) + } +} + +// BenchmarkGetWitnessSigOpCount benchmarks how long it takes to count the +// witness signature operations of a very large script. +func BenchmarkGetWitnessSigOpCountNested(b *testing.B) { + pkScript := mustParseShortForm("HASH160 DATA_20 0x0000000000000000000000000000000000000000 OP_EQUAL") + sigScript := mustParseShortForm("DATA_22 0x001600000000000000000000000000000000000000000000") + redeemScript, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + witness := wire.TxWitness{ + redeemScript, + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = GetWitnessSigOpCount(sigScript, pkScript, witness) + } +} From 77863f5649cbd55e58e2c3ebdd8587de9edcaef7 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 14:14:53 -0800 Subject: [PATCH 041/108] txscript: Optimize GetWitnessSigOpCount This converts the GetWitnessSigOpCount function to use a combination of raw script analysis and the new tokenizer instead of the far less efficeint parseScript, thereby significantly optimizing the funciton. In particular, it use the recently added countSigOpsv0 in precise mode to avoid calling paseScript. --- txscript/script.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index aee1aaacad..b69c915afc 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -955,8 +955,7 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int { len(witness) > 0: witnessScript := witness[len(witness)-1] - pops, _ := parseScript(witnessScript) - return getSigOpCount(pops, true) + return countSigOpsV0(witnessScript, true) } } From 69d560c03cea944cf48114732304e2285b7cbbb5 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:32 -0500 Subject: [PATCH 042/108] txscript: Add benchmark for GetScriptClass. --- txscript/bench_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 52965dca93..aba3d1239e 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -423,3 +423,18 @@ func BenchmarkGetWitnessSigOpCountNested(b *testing.B) { _ = GetWitnessSigOpCount(sigScript, pkScript, witness) } } + +// BenchmarkGetScriptClass benchmarks how long it takes GetScriptClass to +// analyze a very large script. +func BenchmarkGetScriptClass(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = GetScriptClass(script) + } +} From 9b06388f7696fe6e1a4398c2bac6e7277a916067 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:33 -0500 Subject: [PATCH 043/108] txscript: Make typeOfScript accept raw script. This converts the typeOfScript function to accept a script version and raw script instead of an array of internal parsed opcodes in order to make it more flexible for raw script analysis. Also, this adds a comment to CalcScriptInfo to call out the specific version semantics and deprecates the function since nothing currently uses it, and the relevant information can now be obtained by callers more directly through the use of the new script tokenizer. All other callers are updated accordingly. --- txscript/standard.go | 46 ++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 9825f61bea..54c043181b 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -498,7 +498,19 @@ func isNullDataScript(scriptVersion uint16, script []byte) bool { // scriptType returns the type of the script being inspected from the known // standard types. -func typeOfScript(pops []parsedOpcode) ScriptClass { +// +// NOTE: All scripts that are not version 0 are currently considered non +// standard. +func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { + if scriptVersion != 0 { + return NonStandardTy + } + + pops, err := parseScript(script) + if err != nil { + return NonStandardTy + } + if isPubkey(pops) { return PubKeyTy } else if isPubkeyHash(pops) { @@ -521,11 +533,8 @@ func typeOfScript(pops []parsedOpcode) ScriptClass { // // NonStandardTy will be returned when the script does not parse. func GetScriptClass(script []byte) ScriptClass { - pops, err := parseScript(script) - if err != nil { - return NonStandardTy - } - return typeOfScript(pops) + const scriptVersion = 0 + return typeOfScript(scriptVersion, script) } // NewScriptClass returns the ScriptClass corresponding to the string name @@ -608,9 +617,17 @@ type ScriptInfo struct { // pair. It will error if the pair is in someway invalid such that they can not // be analysed, i.e. if they do not parse or the pkScript is not a push-only // script +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +// +// DEPRECATED. This will be removed in the next major version bump. func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, bip16, segwit bool) (*ScriptInfo, error) { + const scriptVersion = 0 + sigPops, err := parseScript(sigScript) if err != nil { return nil, err @@ -621,9 +638,8 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, return nil, err } - // Push only sigScript makes little sense. si := new(ScriptInfo) - si.PkScriptClass = typeOfScript(pkPops) + si.PkScriptClass = typeOfScript(scriptVersion, pkScript) // Can't have a signature script that doesn't just push data. if !isPushOnly(sigPops) { @@ -644,7 +660,8 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, return nil, err } - shInputs := expectedInputs(shPops, typeOfScript(shPops)) + redeemClass := typeOfScript(scriptVersion, script) + shInputs := expectedInputs(shPops, redeemClass) if shInputs == -1 { si.ExpectedInputs = -1 } else { @@ -671,7 +688,9 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, // Extract the pushed witness program from the sigScript so we // can determine the number of expected inputs. pkPops, _ := parseScript(sigScript[1:]) - shInputs := expectedInputs(pkPops, typeOfScript(pkPops)) + + redeemClass := typeOfScript(scriptVersion, sigScript[1:]) + shInputs := expectedInputs(pkPops, redeemClass) if shInputs == -1 { si.ExpectedInputs = -1 } else { @@ -691,7 +710,8 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, witnessScript := witness[len(witness)-1] pops, _ := parseScript(witnessScript) - shInputs := expectedInputs(pops, typeOfScript(pops)) + redeemClass := typeOfScript(scriptVersion, witnessScript) + shInputs := expectedInputs(pops, redeemClass) if shInputs == -1 { si.ExpectedInputs = -1 } else { @@ -888,7 +908,9 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return NonStandardTy, nil, 0, err } - scriptClass := typeOfScript(pops) + const scriptVersion = 0 + scriptClass := typeOfScript(scriptVersion, pkScript) + switch scriptClass { case PubKeyHashTy: // A pay-to-pubkey-hash script is of the form: From 3b86e0a0e2df9f20caa68c1dc6fcd67f0b9aa524 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:34 -0500 Subject: [PATCH 044/108] txscript: Optimize typeOfScript pay-to-script-hash. This begins the process of converting the typeOfScript function to use a combination of raw script analysis and the new tokenizer instead of the far less efficient parsed opcodes with the intent of significantly optimizing the function. In order to ease the review process, each script type will be converted in a separate commit and the typeOfScript function will be updated such that the script is only parsed as a fallback for the cases that are not already converted to more efficient raw script variants. In particular, for this commit, since the ability to detect pay-to-script-hash via raw script analysis is now available, the function is simply updated to make use of it. --- txscript/standard.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 54c043181b..fb3bb9821c 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -506,6 +506,11 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return NonStandardTy } + switch { + case isScriptHashScript(script): + return ScriptHashTy + } + pops, err := parseScript(script) if err != nil { return NonStandardTy @@ -517,8 +522,6 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return PubKeyHashTy } else if isWitnessPubKeyHash(pops) { return WitnessV0PubKeyHashTy - } else if isScriptHash(pops) { - return ScriptHashTy } else if isWitnessScriptHash(pops) { return WitnessV0ScriptHashTy } else if isMultiSig(pops) { From 671b5fefeff4cf07ae1805106c68b90fba291f79 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:35 -0500 Subject: [PATCH 045/108] txscript: Remove unused isScriptHash function. --- txscript/script.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index b69c915afc..ca6520c8be 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -54,17 +54,6 @@ func isSmallInt(op byte) bool { return op == OP_0 || (op >= OP_1 && op <= OP_16) } -// isScriptHash returns true if the script passed is a pay-to-script-hash -// transaction, false otherwise. -// -// DEPRECATED. Use isScriptHashScript or extractScriptHash instead. -func isScriptHash(pops []parsedOpcode) bool { - return len(pops) == 3 && - pops[0].opcode.value == OP_HASH160 && - pops[1].opcode.value == OP_DATA_20 && - pops[2].opcode.value == OP_EQUAL -} - // IsPayToPubKey returns true if the script is in the standard pay-to-pubkey // (P2PK) format, false otherwise. func IsPayToPubKey(script []byte) bool { From 71bf51e82c57b0d4a68ed3f6d35d94d3e7928d9d Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:36 -0500 Subject: [PATCH 046/108] txscript: Optimize typeOfScript multisig. This continues the process of converting the typeOfScript function to use a combination of raw script analysis and the new tokenizer instead of the far less efficient parsed opcodes. In particular, for this commit, since the ability to detect multisig scripts via the new tokenizer is now available, the function is simply updated to make use of it. --- txscript/standard.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index fb3bb9821c..dcd75214d3 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -509,6 +509,8 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { switch { case isScriptHashScript(script): return ScriptHashTy + case isMultisigScript(scriptVersion, script): + return MultiSigTy } pops, err := parseScript(script) @@ -524,8 +526,6 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return WitnessV0PubKeyHashTy } else if isWitnessScriptHash(pops) { return WitnessV0ScriptHashTy - } else if isMultiSig(pops) { - return MultiSigTy } else if isNullData(pops) { return NullDataTy } From 6e86f0d09bc57573312a9b63044dea64992ff480 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:37 -0500 Subject: [PATCH 047/108] txscript: Remove unused isMultiSig function. --- txscript/standard.go | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index dcd75214d3..2722bc4156 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -214,42 +214,6 @@ func isPubkeyHash(pops []parsedOpcode) bool { } -// isMultiSig returns true if the passed script is a multisig transaction, false -// otherwise. -// -// DEPECATED. Use isMultisigScript or extractMultisigScriptDetails instead. -func isMultiSig(pops []parsedOpcode) bool { - // The absolute minimum is 1 pubkey: - // OP_0/OP_1-16 OP_1 OP_CHECKMULTISIG - l := len(pops) - if l < 4 { - return false - } - if !isSmallInt(pops[0].opcode.value) { - return false - } - if !isSmallInt(pops[l-2].opcode.value) { - return false - } - if pops[l-1].opcode.value != OP_CHECKMULTISIG { - return false - } - - // Verify the number of pubkeys specified matches the actual number - // of pubkeys provided. - if l-2-1 != asSmallInt(pops[l-2].opcode.value) { - return false - } - - for _, pop := range pops[1 : l-2] { - // Valid pubkeys are either 33 or 65 bytes. - if len(pop.data) != 33 && len(pop.data) != 65 { - return false - } - } - return true -} - // multiSigDetails houses details extracted from a standard multisig script. type multiSigDetails struct { requiredSigs int From 8b64adc234cfbfd68394898f2eeea0199fdefdb6 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 14:07:19 -0800 Subject: [PATCH 048/108] txscript: Optimze typeOfScript pay-to-pubkey This continues the process of converting the typeOfScript function to use a combination of raw script analysis and the new tokenizer instead of the face less efficient parsed opcodes, with the intent of significantly optimizing the function. In particular, it converts the detection of pay-to-pubkey scripts to use raw script analysis. --- txscript/standard.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 2722bc4156..52a3747838 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -471,6 +471,8 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { } switch { + case isPubKeyScript(script): + return PubKeyTy case isScriptHashScript(script): return ScriptHashTy case isMultisigScript(scriptVersion, script): @@ -482,9 +484,7 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return NonStandardTy } - if isPubkey(pops) { - return PubKeyTy - } else if isPubkeyHash(pops) { + if isPubkeyHash(pops) { return PubKeyHashTy } else if isWitnessPubKeyHash(pops) { return WitnessV0PubKeyHashTy From 1133ea0bb3c95654e827a8ab31531c6163046f4a Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:40 -0500 Subject: [PATCH 049/108] txscript: Remove unused isPubkey function. --- txscript/standard.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 52a3747838..d2aa0e99eb 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -193,15 +193,6 @@ func isScriptHashScript(script []byte) bool { return extractScriptHash(script) != nil } -// isPubkey returns true if the script passed is a pay-to-pubkey transaction, -// false otherwise. -func isPubkey(pops []parsedOpcode) bool { - // Valid pubkeys are either 33 or 65 bytes. - return len(pops) == 2 && - (len(pops[0].data) == 33 || len(pops[0].data) == 65) && - pops[1].opcode.value == OP_CHECKSIG -} - // isPubkeyHash returns true if the script passed is a pay-to-pubkey-hash // transaction, false otherwise. func isPubkeyHash(pops []parsedOpcode) bool { From 13f646243d26d34ec46dfd7f9b40d5c8aaae5d59 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:45 -0500 Subject: [PATCH 050/108] txscript: Optimize typeOfScript pay-to-pubkey-hash. This continues the process of converting the typeOfScript function to use a combination of raw script analysis and the new tokenizer instead of the far less efficient parsed opcodes. In particular, it converts the detection of pay-to-pubkey-hash scripts to use raw script analysis. --- txscript/standard.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index d2aa0e99eb..08c6054e03 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -464,6 +464,8 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { switch { case isPubKeyScript(script): return PubKeyTy + case isPubKeyHashScript(script): + return PubKeyHashTy case isScriptHashScript(script): return ScriptHashTy case isMultisigScript(scriptVersion, script): @@ -475,9 +477,7 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return NonStandardTy } - if isPubkeyHash(pops) { - return PubKeyHashTy - } else if isWitnessPubKeyHash(pops) { + if isWitnessPubKeyHash(pops) { return WitnessV0PubKeyHashTy } else if isWitnessScriptHash(pops) { return WitnessV0ScriptHashTy From d02f97e04a5243e0343763b30c798863d63f577b Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:46 -0500 Subject: [PATCH 051/108] txscript: Remove unused isPubkeyHash function. --- txscript/standard.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 08c6054e03..ff382537f4 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -193,18 +193,6 @@ func isScriptHashScript(script []byte) bool { return extractScriptHash(script) != nil } -// isPubkeyHash returns true if the script passed is a pay-to-pubkey-hash -// transaction, false otherwise. -func isPubkeyHash(pops []parsedOpcode) bool { - return len(pops) == 5 && - pops[0].opcode.value == OP_DUP && - pops[1].opcode.value == OP_HASH160 && - pops[2].opcode.value == OP_DATA_20 && - pops[3].opcode.value == OP_EQUALVERIFY && - pops[4].opcode.value == OP_CHECKSIG - -} - // multiSigDetails houses details extracted from a standard multisig script. type multiSigDetails struct { requiredSigs int From d80863da921d21dcea851605ca8d74b621bb339e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 4 Feb 2021 15:15:45 -0800 Subject: [PATCH 052/108] txscript: Optimize typeOfScript for null data scripts This continues the process of converting the typeOfScript function to use a combination of raw script analysize and the tokenizer instead of parsed opcode, with the intent of significanty optimizing the function. In particular, it converts the detection of null data scripts to use raw script analysis. --- txscript/standard.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index ff382537f4..bae2b0c5d0 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -458,6 +458,8 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return ScriptHashTy case isMultisigScript(scriptVersion, script): return MultiSigTy + case isNullDataScript(scriptVersion, script): + return NullDataTy } pops, err := parseScript(script) @@ -469,9 +471,8 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return WitnessV0PubKeyHashTy } else if isWitnessScriptHash(pops) { return WitnessV0ScriptHashTy - } else if isNullData(pops) { - return NullDataTy } + return NonStandardTy } From 78046b3815ac525e86edde8f302b757369856f9e Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:11:51 -0500 Subject: [PATCH 053/108] txscript: Remove unused isNullData function. --- txscript/standard.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index bae2b0c5d0..85b285826d 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -386,24 +386,6 @@ func isWitnessScriptHashScript(script []byte) bool { return extractWitnessScriptHash(script) != nil } -// isNullData returns true if the passed script is a null data transaction, -// false otherwise. -func isNullData(pops []parsedOpcode) bool { - // A nulldata transaction is either a single OP_RETURN or an - // OP_RETURN SMALLDATA (where SMALLDATA is a data push up to - // MaxDataCarrierSize bytes). - l := len(pops) - if l == 1 && pops[0].opcode.value == OP_RETURN { - return true - } - - return l == 2 && - pops[0].opcode.value == OP_RETURN && - (isSmallInt(pops[1].opcode.value) || pops[1].opcode.value <= - OP_PUSHDATA4) && - len(pops[1].data) <= MaxDataCarrierSize -} - // isNullDataScript returns whether or not the passed script is a standard // null data script. // From 847a262d78933c57e6062151ceef8aba17896264 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 01:46:52 -0700 Subject: [PATCH 054/108] txscript: Optimize typeOfScript witness-pubkey-hash This continues the process of converting the typeOfScript function to use a combination of raw script analysis and the new tokenizer instead of the far less efficient parsed opcodes. In particular, it converts the detection of witness pubkey hash scripts to use raw script analysis and the new tokenizer. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkIsWitnessPubKeyHash-8 61688 62839 +1.87% benchmark old allocs new allocs delta BenchmarkIsWitnessPubKeyHash-8 1 1 +0.00% benchmark old bytes new bytes delta BenchmarkIsWitnessPubKeyHash-8 311299 311299 +0.00% --- txscript/standard.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 85b285826d..5d5c668794 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -355,6 +355,7 @@ func extractWitnessPubKeyHash(script []byte) []byte { return script[2:22] } + return nil } @@ -438,6 +439,8 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return PubKeyHashTy case isScriptHashScript(script): return ScriptHashTy + case isWitnessPubKeyHashScript(script): + return WitnessV0PubKeyHashTy case isMultisigScript(scriptVersion, script): return MultiSigTy case isNullDataScript(scriptVersion, script): @@ -449,9 +452,7 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return NonStandardTy } - if isWitnessPubKeyHash(pops) { - return WitnessV0PubKeyHashTy - } else if isWitnessScriptHash(pops) { + if isWitnessScriptHash(pops) { return WitnessV0ScriptHashTy } From 1a60e11da7ab917a6725ce10d37da7b7f8634ed5 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 02:04:01 -0700 Subject: [PATCH 055/108] txscript: Optimize typeOfScript for witness-script-hash This concludes the process of converting the typeOfScript function to use a combination of raw script analysis and the new tokenizer instead of the far less efficient parsed opcodes. In particular, it converts the detection of witness script hash scripts to use raw script analysis and the new tokenizer. With all of the limbs now useing optimized variants, the following is a before an after comparison of calling GetScriptClass on a large script: benchmark old ns/op new ns/op delta BenchmarkGetScriptClass-8 61515 15.3 -99.98% benchmark old allocs new allocs delta BenchmarkGetScriptClass-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkGetScriptClass-8 311299 0 -100.00% --- txscript/standard.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 5d5c668794..dd751cbe98 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -441,22 +441,15 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass { return ScriptHashTy case isWitnessPubKeyHashScript(script): return WitnessV0PubKeyHashTy + case isWitnessScriptHashScript(script): + return WitnessV0ScriptHashTy case isMultisigScript(scriptVersion, script): return MultiSigTy case isNullDataScript(scriptVersion, script): return NullDataTy - } - - pops, err := parseScript(script) - if err != nil { + default: return NonStandardTy } - - if isWitnessScriptHash(pops) { - return WitnessV0ScriptHashTy - } - - return NonStandardTy } // GetScriptClass returns the class of the script passed. From 43846b1edf9f281a88b02e91b361ebdd30b692db Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 02:05:04 -0700 Subject: [PATCH 056/108] txscript: Remove unused isWitnessScriptHash --- txscript/script.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index ca6520c8be..0d041a8bf9 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -78,14 +78,6 @@ func IsPayToScriptHash(script []byte) bool { return isScriptHashScript(script) } -// isWitnessScriptHash returns true if the passed script is a -// pay-to-witness-script-hash transaction, false otherwise. -func isWitnessScriptHash(pops []parsedOpcode) bool { - return len(pops) == 2 && - pops[0].opcode.value == OP_0 && - pops[1].opcode.value == OP_DATA_32 -} - // IsPayToWitnessScriptHash returns true if the is in the standard // pay-to-witness-script-hash (P2WSH) format, false otherwise. func IsPayToWitnessScriptHash(script []byte) bool { From 705d24cab4bb7f9f0891736c4a983abd8d846146 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:09 -0500 Subject: [PATCH 057/108] txscript: Convert CalcScriptInfo. This converts CalcScriptInfo and dependent expectedInputs to make use of the new script tokenizer as well as several of the other recently added raw script analysis functions in order to remove the reliance on parsed opcodes as a step towards utlimately removing them altogether. It is worth noting that this has the side effect of significantly optimizing the function as well, however, since it is deprecated, no benchmarks are provided. --- txscript/standard.go | 72 +++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index dd751cbe98..fea6799f76 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -481,7 +481,11 @@ func NewScriptClass(name string) (*ScriptClass, error) { // then -1 is returned. We are an internal function and thus assume that class // is the real class of pops (and we can thus assume things that were determined // while finding out the type). -func expectedInputs(pops []parsedOpcode, class ScriptClass) int { +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func expectedInputs(script []byte, class ScriptClass) int { switch class { case PubKeyTy: return 1 @@ -508,7 +512,7 @@ func expectedInputs(pops []parsedOpcode, class ScriptClass) int { // the original bitcoind bug where OP_CHECKMULTISIG pops an // additional item from the stack, add an extra expected input // for the extra push that is required to compensate. - return asSmallInt(pops[0].opcode.value) + 1 + return asSmallInt(script[0]) + 1 case NullDataTy: fallthrough @@ -549,52 +553,52 @@ type ScriptInfo struct { func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, bip16, segwit bool) (*ScriptInfo, error) { + // Count the number of opcodes in the signature script while also ensuring + // that successfully parses. Since there is a check below to ensure the + // script is push only, this equates to the number of inputs to the public + // key script. const scriptVersion = 0 - - sigPops, err := parseScript(sigScript) - if err != nil { + var numInputs int + tokenizer := MakeScriptTokenizer(scriptVersion, sigScript) + for tokenizer.Next() { + numInputs++ + } + if err := tokenizer.Err(); err != nil { return nil, err } - pkPops, err := parseScript(pkScript) - if err != nil { + if err := checkScriptParses(scriptVersion, pkScript); err != nil { return nil, err } - si := new(ScriptInfo) - si.PkScriptClass = typeOfScript(scriptVersion, pkScript) - // Can't have a signature script that doesn't just push data. - if !isPushOnly(sigPops) { + if !IsPushOnlyScript(sigScript) { return nil, scriptError(ErrNotPushOnly, "signature script is not push only") } - si.ExpectedInputs = expectedInputs(pkPops, si.PkScriptClass) + si := new(ScriptInfo) + si.PkScriptClass = typeOfScript(scriptVersion, pkScript) + + si.ExpectedInputs = expectedInputs(pkScript, si.PkScriptClass) switch { // Count sigops taking into account pay-to-script-hash. case si.PkScriptClass == ScriptHashTy && bip16 && !segwit: - // The pay-to-hash-script is the final data push of the - // signature script. - script := sigPops[len(sigPops)-1].data - shPops, err := parseScript(script) - if err != nil { - return nil, err - } - - redeemClass := typeOfScript(scriptVersion, script) - shInputs := expectedInputs(shPops, redeemClass) - if shInputs == -1 { + // The redeem script is the final data push of the signature script. + redeemScript := finalOpcodeData(scriptVersion, sigScript) + reedeemClass := typeOfScript(scriptVersion, redeemScript) + rsInputs := expectedInputs(redeemScript, reedeemClass) + if rsInputs == -1 { si.ExpectedInputs = -1 } else { - si.ExpectedInputs += shInputs + si.ExpectedInputs += rsInputs } - si.SigOps = getSigOpCount(shPops, true) + si.SigOps = countSigOpsV0(redeemScript, true) // All entries pushed to stack (or are OP_RESERVED and exec // will fail). - si.NumInputs = len(sigPops) + si.NumInputs = numInputs // If segwit is active, and this is a regular p2wkh output, then we'll // treat the script as a p2pkh output in essence. @@ -610,10 +614,8 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, // Extract the pushed witness program from the sigScript so we // can determine the number of expected inputs. - pkPops, _ := parseScript(sigScript[1:]) - redeemClass := typeOfScript(scriptVersion, sigScript[1:]) - shInputs := expectedInputs(pkPops, redeemClass) + shInputs := expectedInputs(sigScript[1:], redeemClass) if shInputs == -1 { si.ExpectedInputs = -1 } else { @@ -623,18 +625,14 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness) si.NumInputs = len(witness) - si.NumInputs += len(sigPops) + si.NumInputs += numInputs // If segwit is active, and this is a p2wsh output, then we'll need to // examine the witness script to generate accurate script info. case si.PkScriptClass == WitnessV0ScriptHashTy && segwit: - // The witness script is the final element of the witness - // stack. witnessScript := witness[len(witness)-1] - pops, _ := parseScript(witnessScript) - redeemClass := typeOfScript(scriptVersion, witnessScript) - shInputs := expectedInputs(pops, redeemClass) + shInputs := expectedInputs(witnessScript, redeemClass) if shInputs == -1 { si.ExpectedInputs = -1 } else { @@ -645,11 +643,11 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, si.NumInputs = len(witness) default: - si.SigOps = getSigOpCount(pkPops, true) + si.SigOps = countSigOpsV0(pkScript, true) // All entries pushed to stack (or are OP_RESERVED and exec // will fail). - si.NumInputs = len(sigPops) + si.NumInputs = numInputs } return si, nil From 6c212fd7ee9e0ab2fcd6287d0c5ed26c0b9cf043 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:10 -0500 Subject: [PATCH 058/108] txscript: Remove unused isPushOnly function. --- txscript/script.go | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 0d041a8bf9..7e0e3669d2 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -161,26 +161,6 @@ func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) { return witnessVersion, witnessProgram, nil } -// isPushOnly returns true if the script only pushes data, false otherwise. -// -// DEPRECATED. Use IsPushOnlyScript instead. -func isPushOnly(pops []parsedOpcode) bool { - // NOTE: This function does NOT verify opcodes directly since it is - // internal and is only called with parsed opcodes for scripts that did - // not have any parse errors. Thus, consensus is properly maintained. - - for _, pop := range pops { - // All opcodes up to OP_16 are data push instructions. - // NOTE: This does consider OP_RESERVED to be a data push - // instruction, but execution of OP_RESERVED will fail anyways - // and matches the behavior required by consensus. - if pop.opcode.value > OP_16 { - return false - } - } - return true -} - // IsPushOnlyScript returns whether or not the passed script only pushes data // according to the consensus definition of pushing data. // @@ -901,11 +881,7 @@ func GetWitnessSigOpCount(sigScript, pkScript []byte, witness wire.TxWitness) in // Next, we'll check the sigScript to see if this is a nested p2sh // witness program. This is a case wherein the sigScript is actually a // datapush of a p2wsh witness program. - sigPops, err := parseScript(sigScript) - if err != nil { - return 0 - } - if IsPayToScriptHash(pkScript) && isPushOnly(sigPops) && + if IsPayToScriptHash(pkScript) && IsPushOnlyScript(sigScript) && IsWitnessProgram(sigScript[1:]) { return getWitnessSigOps(sigScript[1:], witness) } From 8c54905959569b573f019ffc8e4eeb624505a8d9 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:11 -0500 Subject: [PATCH 059/108] txscript: Remove unused getSigOpCount function. --- txscript/script.go | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 7e0e3669d2..10ff4315e0 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -698,42 +698,6 @@ func asSmallInt(op byte) int { return int(op - (OP_1 - 1)) } -// getSigOpCount is the implementation function for counting the number of -// signature operations in the script provided by pops. If precise mode is -// requested then we attempt to count the number of operations for a multisig -// op. Otherwise we use the maximum. -// -// DEPRECATED. Use countSigOpsV0 instead. -func getSigOpCount(pops []parsedOpcode, precise bool) int { - nSigs := 0 - for i, pop := range pops { - switch pop.opcode.value { - case OP_CHECKSIG: - fallthrough - case OP_CHECKSIGVERIFY: - nSigs++ - case OP_CHECKMULTISIG: - fallthrough - case OP_CHECKMULTISIGVERIFY: - // If we are being precise then look for familiar - // patterns for multisig, for now all we recognize is - // OP_1 - OP_16 to signify the number of pubkeys. - // Otherwise, we use the max of 20. - if precise && i > 0 && - pops[i-1].opcode.value >= OP_1 && - pops[i-1].opcode.value <= OP_16 { - nSigs += asSmallInt(pops[i-1].opcode.value) - } else { - nSigs += MaxPubKeysPerMultiSig - } - default: - // Not a sigop. - } - } - - return nSigs -} - // countSigOpsV0 returns the number of signature operations in the provided // script up to the point of the first parse failure or the entire script when // there are no parse failures. The precise flag attempts to accurately count From 7791f92f6f09390f310c918966178fe189c1632a Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:13 -0500 Subject: [PATCH 060/108] txscript: Optimize CalcMultiSigStats. This converts the CalcMultiSigStats function to make use of the new extractMultisigScriptDetails function instead of the far less efficient parseScript thereby significantly optimizing the function. The tests are also updated accordingly. The following is a before and after comparison of analyzing a standard multisig script: benchmark old ns/op new ns/op delta --------------------------------------------------------------- BenchmarkCalcMultiSigStats 972 79.5 -91.82% benchmark old allocs new allocs delta --------------------------------------------------------------- BenchmarkCalcMultiSigStats 1 0 -100.00% benchmark old bytes new bytes delta --------------------------------------------------------------- BenchmarkCalcMultiSigStats 2304 0 -100.00% --- txscript/standard.go | 26 ++++++++++---------------- txscript/standard_test.go | 8 ++------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index fea6799f76..3bb12b9aba 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -656,27 +656,21 @@ func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, // CalcMultiSigStats returns the number of public keys and signatures from // a multi-signature transaction script. The passed script MUST already be // known to be a multi-signature script. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func CalcMultiSigStats(script []byte) (int, int, error) { - pops, err := parseScript(script) - if err != nil { - return 0, 0, err - } - - // A multi-signature script is of the pattern: - // NUM_SIGS PUBKEY PUBKEY PUBKEY... NUM_PUBKEYS OP_CHECKMULTISIG - // Therefore the number of signatures is the oldest item on the stack - // and the number of pubkeys is the 2nd to last. Also, the absolute - // minimum for a multi-signature script is 1 pubkey, so at least 4 - // items must be on the stack per: - // OP_1 PUBKEY OP_1 OP_CHECKMULTISIG - if len(pops) < 4 { + // The public keys are not needed here, so pass false to avoid the extra + // allocation. + const scriptVersion = 0 + details := extractMultisigScriptDetails(scriptVersion, script, false) + if !details.valid { str := fmt.Sprintf("script %x is not a multisig script", script) return 0, 0, scriptError(ErrNotMultisigScript, str) } - numSigs := asSmallInt(pops[0].opcode.value) - numPubKeys := asSmallInt(pops[len(pops)-2].opcode.value) - return numPubKeys, numSigs, nil + return details.numPubKeys, details.requiredSigs, nil } // payToPubKeyHashScript creates a new script to pay a transaction diff --git a/txscript/standard_test.go b/txscript/standard_test.go index 37dd8f8a37..582d30eee4 100644 --- a/txscript/standard_test.go +++ b/txscript/standard_test.go @@ -832,7 +832,7 @@ func TestCalcMultiSigStats(t *testing.T) { name: "short script", script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828" + "e03909a67962e0ea1f61d", - err: scriptError(ErrMalformedPush, ""), + err: scriptError(ErrNotMultisigScript, ""), }, { name: "stack underflow", @@ -843,11 +843,7 @@ func TestCalcMultiSigStats(t *testing.T) { }, { name: "multisig script", - script: "0 DATA_72 0x30450220106a3e4ef0b51b764a2887226" + - "2ffef55846514dacbdcbbdd652c849d395b4384022100" + - "e03ae554c3cbb40600d31dd46fc33f25e47bf8525b1fe" + - "07282e3b6ecb5f3bb2801 CODESEPARATOR 1 DATA_33 " + - "0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" + + script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da45329a0" + "0357b3a7886211ab414d55a 1 CHECKMULTISIG", err: nil, }, From c4f6302189c7c928a2e89212d3727a308512920d Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:18 -0500 Subject: [PATCH 061/108] txscript: Add benchmark for PushedData. --- txscript/bench_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index aba3d1239e..26b7c9feb8 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -438,3 +438,21 @@ func BenchmarkGetScriptClass(b *testing.B) { _ = GetScriptClass(script) } } + +// BenchmarkPushedData benchmarks how long it takes to extract the pushed data +// from a very large script. +func BenchmarkPushedData(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := PushedData(script) + if err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} From 0a4f228dd10296da4c06484577112bf0411e7fff Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:19 -0500 Subject: [PATCH 062/108] txscript: Optimize PushedData. This converts the PushedData function to make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. Also, the comment is modified to explicitly call out the script version semantics. The following is a before and after comparison of extracting the data from a very large script: benchmark old ns/op new ns/op delta BenchmarkPushedData-8 64837 1790 -97.24% benchmark old allocs new allocs delta BenchmarkPushedData-8 7 6 -14.29% benchmark old bytes new bytes delta BenchmarkPushedData-8 312816 1520 -99.51% --- txscript/standard.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 3bb12b9aba..0607b305f3 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -791,20 +791,25 @@ func MultiSigScript(pubkeys []*btcutil.AddressPubKey, nrequired int) ([]byte, er // PushedData returns an array of byte slices containing any pushed data found // in the passed script. This includes OP_0, but not OP_1 - OP_16. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func PushedData(script []byte) ([][]byte, error) { - pops, err := parseScript(script) - if err != nil { - return nil, err - } + const scriptVersion = 0 var data [][]byte - for _, pop := range pops { - if pop.data != nil { - data = append(data, pop.data) - } else if pop.opcode.value == OP_0 { + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + if tokenizer.Data() != nil { + data = append(data, tokenizer.Data()) + } else if tokenizer.Opcode() == OP_0 { data = append(data, nil) } } + if err := tokenizer.Err(); err != nil { + return nil, err + } return data, nil } From da9fdabbd5bae2c07aca03af811bfb33b05dd3d6 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:22 -0500 Subject: [PATCH 063/108] txscript: Make canonicalPush accept raw opcode. This renames the canonicalPush function to isCanonicalPush and converts it to accept an opcode as a byte and the associate data as a byte slice instead of the internal parse opcode data struct in order to make it more flexible for raw script analysis. It also updates all callers and tests accordingly. --- txscript/engine.go | 5 +++- txscript/script.go | 23 ++++++++------ txscript/script_test.go | 66 ++++++++++++++++++----------------------- txscript/standard.go | 4 +-- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 576adc7188..ef7ad33e3c 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -875,6 +875,7 @@ func (vm *Engine) SetAltStack(data [][]byte) { // engine according to the description provided by each flag. func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags, sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) { + const scriptVersion = 0 // The provided transaction input index must refer to a valid input. if txIdx < 0 || txIdx >= len(tx.TxIn) { @@ -994,7 +995,9 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags // data push of the witness program, otherwise we // reintroduce malleability. sigPops := vm.scripts[0] - if len(sigPops) == 1 && canonicalPush(sigPops[0]) && + if len(sigPops) == 1 && + isCanonicalPush(sigPops[0].opcode.value, + sigPops[0].data) && IsWitnessProgram(sigPops[0].data) { witProgram = sigPops[0].data diff --git a/txscript/script.go b/txscript/script.go index 10ff4315e0..52bedf2699 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -128,7 +128,7 @@ func IsWitnessProgram(script []byte) bool { func isWitnessProgram(pops []parsedOpcode) bool { return len(pops) == 2 && isSmallInt(pops[0].opcode.value) && - canonicalPush(pops[1]) && + isCanonicalPush(pops[1].opcode.value, pops[1].data) && (len(pops[1].data) >= 2 && len(pops[1].data) <= 40) } @@ -305,13 +305,16 @@ func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode { return retScript } -// canonicalPush returns true if the object is either not a push instruction -// or the push instruction contained wherein is matches the canonical form -// or using the smallest instruction to do the job. False otherwise. -func canonicalPush(pop parsedOpcode) bool { - opcode := pop.opcode.value - data := pop.data - dataLen := len(pop.data) +// isCanonicalPush returns true if the opcode is either not a push instruction +// or the data associated with the push instruction uses the smallest +// instruction to do the job. False otherwise. +// +// For example, it is possible to push a value of 1 to the stack as "OP_1", +// "OP_DATA_1 0x01", "OP_PUSHDATA1 0x01 0x01", and others, however, the first +// only takes a single byte, while the rest take more. Only the first is +// considered canonical. +func isCanonicalPush(opcode byte, data []byte) bool { + dataLen := len(data) if opcode > OP_16 { return true } @@ -336,7 +339,9 @@ func canonicalPush(pop parsedOpcode) bool { func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode { retScript := make([]parsedOpcode, 0, len(pkscript)) for _, pop := range pkscript { - if !canonicalPush(pop) || !bytes.Contains(pop.data, data) { + if !isCanonicalPush(pop.opcode.value, pop.data) || + !bytes.Contains(pop.data, data) { + retScript = append(retScript, pop) } } diff --git a/txscript/script_test.go b/txscript/script_test.go index 665d9ef633..62c51e4181 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -3740,31 +3740,25 @@ func TestPushedData(t *testing.T) { } } -// TestHasCanonicalPush ensures the canonicalPush function works as expected. +// TestHasCanonicalPush ensures the isCanonicalPush function works as expected. func TestHasCanonicalPush(t *testing.T) { t.Parallel() + const scriptVersion = 0 for i := 0; i < 65535; i++ { script, err := NewScriptBuilder().AddInt64(int64(i)).Script() if err != nil { - t.Errorf("Script: test #%d unexpected error: %v\n", i, - err) + t.Errorf("Script: test #%d unexpected error: %v\n", i, err) continue } - if result := IsPushOnlyScript(script); !result { - t.Errorf("IsPushOnlyScript: test #%d failed: %x\n", i, - script) + if !IsPushOnlyScript(script) { + t.Errorf("IsPushOnlyScript: test #%d failed: %x\n", i, script) continue } - pops, err := parseScript(script) - if err != nil { - t.Errorf("parseScript: #%d failed: %v", i, err) - continue - } - for _, pop := range pops { - if result := canonicalPush(pop); !result { - t.Errorf("canonicalPush: test #%d failed: %x\n", - i, script) + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + if !isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) { + t.Errorf("isCanonicalPush: test #%d failed: %x\n", i, script) break } } @@ -3774,21 +3768,17 @@ func TestHasCanonicalPush(t *testing.T) { builder.AddData(bytes.Repeat([]byte{0x49}, i)) script, err := builder.Script() if err != nil { - t.Errorf("StandardPushesTests test #%d unexpected error: %v\n", i, err) - continue - } - if result := IsPushOnlyScript(script); !result { - t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, script) + t.Errorf("Script: test #%d unexpected error: %v\n", i, err) continue } - pops, err := parseScript(script) - if err != nil { - t.Errorf("StandardPushesTests #%d failed to TstParseScript: %v", i, err) + if !IsPushOnlyScript(script) { + t.Errorf("IsPushOnlyScript: test #%d failed: %x\n", i, script) continue } - for _, pop := range pops { - if result := canonicalPush(pop); !result { - t.Errorf("StandardPushesTests TstHasCanonicalPushes test #%d failed: %x\n", i, script) + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + if !isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) { + t.Errorf("isCanonicalPush: test #%d failed: %x\n", i, script) break } } @@ -4213,11 +4203,13 @@ func TestIsPayToWitnessPubKeyHash(t *testing.T) { } } -// TestHasCanonicalPushes ensures the canonicalPush function properly determines -// what is considered a canonical push for the purposes of removeOpcodeByData. +// TestHasCanonicalPushes ensures the isCanonicalPush function properly +// determines what is considered a canonical push for the purposes of +// removeOpcodeByData. func TestHasCanonicalPushes(t *testing.T) { t.Parallel() + const scriptVersion = 0 tests := []struct { name string script string @@ -4236,20 +4228,20 @@ func TestHasCanonicalPushes(t *testing.T) { }, } - for i, test := range tests { + for _, test := range tests { script := mustParseShortForm(test.script) - pops, err := parseScript(script) - if err != nil { + if err := checkScriptParses(scriptVersion, script); err != nil { if test.expected { - t.Errorf("TstParseScript #%d failed: %v", i, err) + t.Errorf("%q: script parse failed: %v", test.name, err) } continue } - for _, pop := range pops { - if canonicalPush(pop) != test.expected { - t.Errorf("canonicalPush: #%d (%s) wrong result"+ - "\ngot: %v\nwant: %v", i, test.name, - true, test.expected) + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + result := isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) + if result != test.expected { + t.Errorf("%q: isCanonicalPush wrong result\ngot: %v\nwant: %v", + test.name, result, test.expected) break } } diff --git a/txscript/standard.go b/txscript/standard.go index 0607b305f3..d97072f155 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -953,7 +953,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa } isAtomicSwap := pops[0].opcode.value == OP_IF && pops[1].opcode.value == OP_SIZE && - canonicalPush(pops[2]) && + isCanonicalPush(pops[2].opcode.value, pops[2].data) && pops[3].opcode.value == OP_EQUALVERIFY && pops[4].opcode.value == OP_SHA256 && pops[5].opcode.value == OP_DATA_32 && @@ -962,7 +962,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa pops[8].opcode.value == OP_HASH160 && pops[9].opcode.value == OP_DATA_20 && pops[10].opcode.value == OP_ELSE && - canonicalPush(pops[11]) && + isCanonicalPush(pops[11].opcode.value, pops[11].data) && pops[12].opcode.value == OP_CHECKLOCKTIMEVERIFY && pops[13].opcode.value == OP_DROP && pops[14].opcode.value == OP_DUP && From 81b80328bd21ae8845a8ca3cb0760898b7119e43 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:24 -0500 Subject: [PATCH 064/108] txscript: Add ExtractAtomicSwapDataPushes benches. --- txscript/bench_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 26b7c9feb8..456db25d52 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -456,3 +456,44 @@ func BenchmarkPushedData(b *testing.B) { } } } + +// BenchmarkExtractAtomicSwapDataPushesLarge benchmarks how long it takes +// ExtractAtomicSwapDataPushes to analyze a very large script. +func BenchmarkExtractAtomicSwapDataPushesLarge(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + const scriptVersion = 0 + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := ExtractAtomicSwapDataPushes(scriptVersion, script) + if err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} + +// BenchmarkExtractAtomicSwapDataPushesLarge benchmarks how long it takes +// ExtractAtomicSwapDataPushes to analyze a standard atomic swap script. +func BenchmarkExtractAtomicSwapDataPushes(b *testing.B) { + secret := "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + recipient := "0000000000000000000000000000000000000001" + refund := "0000000000000000000000000000000000000002" + script := mustParseShortForm(fmt.Sprintf("IF SIZE 32 EQUALVERIFY SHA256 "+ + "DATA_32 0x%s EQUALVERIFY DUP HASH160 DATA_20 0x%s ELSE 300000 "+ + "CHECKLOCKTIMEVERIFY DROP DUP HASH160 DATA_20 0x%s ENDIF "+ + "EQUALVERIFY CHECKSIG", secret, recipient, refund)) + + const scriptVersion = 0 + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := ExtractAtomicSwapDataPushes(scriptVersion, script) + if err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} From 6ec9b73a53a1f7480a5c251e639b754d95f25c27 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 03:05:55 -0700 Subject: [PATCH 065/108] txscript/scriptnum: add maxscriptnum and maxcltvlength --- txscript/scriptnum.go | 20 ++++-- txscript/scriptnum_test.go | 126 ++++++++++++++++++------------------- txscript/stack.go | 4 +- 3 files changed, 81 insertions(+), 69 deletions(-) diff --git a/txscript/scriptnum.go b/txscript/scriptnum.go index a89d5f39cc..81f2636121 100644 --- a/txscript/scriptnum.go +++ b/txscript/scriptnum.go @@ -12,9 +12,21 @@ const ( maxInt32 = 1<<31 - 1 minInt32 = -1 << 31 - // defaultScriptNumLen is the default number of bytes - // data being interpreted as an integer may be. - defaultScriptNumLen = 4 + // maxScriptNumLen is the maximum number of bytes data being interpreted + // as an integer may be for the majority of op codes. + maxScriptNumLen = 4 + + // cltvMaxScriptNumLen is the maximum number of bytes data being interpreted + // as an integer may be for by-time and by-height locks as interpreted by + // CHECKLOCKTIMEVERIFY. + // + // The value comes from the fact that the current transaction locktime + // is a uint32 resulting in a maximum locktime of 2^32-1 (the year + // 2106). However, scriptNums are signed and therefore a standard + // 4-byte scriptNum would only support up to a maximum of 2^31-1 (the + // year 2038). Thus, a 5-byte scriptNum is needed since it will support + // up to 2^39-1 which allows dates beyond the current locktime limit. + cltvMaxScriptNumLen = 5 ) // scriptNum represents a numeric value used in the scripting engine with @@ -178,7 +190,7 @@ func (n scriptNum) Int32() int32 { // before an ErrStackNumberTooBig is returned. This effectively limits the // range of allowed values. // WARNING: Great care should be taken if passing a value larger than -// defaultScriptNumLen, which could lead to addition and multiplication +// maxScriptNumLen, which could lead to addition and multiplication // overflows. // // See the Bytes function documentation for example encodings. diff --git a/txscript/scriptnum_test.go b/txscript/scriptnum_test.go index e32862b736..668f912f6f 100644 --- a/txscript/scriptnum_test.go +++ b/txscript/scriptnum_test.go @@ -104,35 +104,35 @@ func TestMakeScriptNum(t *testing.T) { err error }{ // Minimal encoding must reject negative 0. - {hexToBytes("80"), 0, defaultScriptNumLen, true, errMinimalData}, + {hexToBytes("80"), 0, maxScriptNumLen, true, errMinimalData}, // Minimally encoded valid values with minimal encoding flag. // Should not error and return expected integral number. - {nil, 0, defaultScriptNumLen, true, nil}, - {hexToBytes("01"), 1, defaultScriptNumLen, true, nil}, - {hexToBytes("81"), -1, defaultScriptNumLen, true, nil}, - {hexToBytes("7f"), 127, defaultScriptNumLen, true, nil}, - {hexToBytes("ff"), -127, defaultScriptNumLen, true, nil}, - {hexToBytes("8000"), 128, defaultScriptNumLen, true, nil}, - {hexToBytes("8080"), -128, defaultScriptNumLen, true, nil}, - {hexToBytes("8100"), 129, defaultScriptNumLen, true, nil}, - {hexToBytes("8180"), -129, defaultScriptNumLen, true, nil}, - {hexToBytes("0001"), 256, defaultScriptNumLen, true, nil}, - {hexToBytes("0081"), -256, defaultScriptNumLen, true, nil}, - {hexToBytes("ff7f"), 32767, defaultScriptNumLen, true, nil}, - {hexToBytes("ffff"), -32767, defaultScriptNumLen, true, nil}, - {hexToBytes("008000"), 32768, defaultScriptNumLen, true, nil}, - {hexToBytes("008080"), -32768, defaultScriptNumLen, true, nil}, - {hexToBytes("ffff00"), 65535, defaultScriptNumLen, true, nil}, - {hexToBytes("ffff80"), -65535, defaultScriptNumLen, true, nil}, - {hexToBytes("000008"), 524288, defaultScriptNumLen, true, nil}, - {hexToBytes("000088"), -524288, defaultScriptNumLen, true, nil}, - {hexToBytes("000070"), 7340032, defaultScriptNumLen, true, nil}, - {hexToBytes("0000f0"), -7340032, defaultScriptNumLen, true, nil}, - {hexToBytes("00008000"), 8388608, defaultScriptNumLen, true, nil}, - {hexToBytes("00008080"), -8388608, defaultScriptNumLen, true, nil}, - {hexToBytes("ffffff7f"), 2147483647, defaultScriptNumLen, true, nil}, - {hexToBytes("ffffffff"), -2147483647, defaultScriptNumLen, true, nil}, + {nil, 0, maxScriptNumLen, true, nil}, + {hexToBytes("01"), 1, maxScriptNumLen, true, nil}, + {hexToBytes("81"), -1, maxScriptNumLen, true, nil}, + {hexToBytes("7f"), 127, maxScriptNumLen, true, nil}, + {hexToBytes("ff"), -127, maxScriptNumLen, true, nil}, + {hexToBytes("8000"), 128, maxScriptNumLen, true, nil}, + {hexToBytes("8080"), -128, maxScriptNumLen, true, nil}, + {hexToBytes("8100"), 129, maxScriptNumLen, true, nil}, + {hexToBytes("8180"), -129, maxScriptNumLen, true, nil}, + {hexToBytes("0001"), 256, maxScriptNumLen, true, nil}, + {hexToBytes("0081"), -256, maxScriptNumLen, true, nil}, + {hexToBytes("ff7f"), 32767, maxScriptNumLen, true, nil}, + {hexToBytes("ffff"), -32767, maxScriptNumLen, true, nil}, + {hexToBytes("008000"), 32768, maxScriptNumLen, true, nil}, + {hexToBytes("008080"), -32768, maxScriptNumLen, true, nil}, + {hexToBytes("ffff00"), 65535, maxScriptNumLen, true, nil}, + {hexToBytes("ffff80"), -65535, maxScriptNumLen, true, nil}, + {hexToBytes("000008"), 524288, maxScriptNumLen, true, nil}, + {hexToBytes("000088"), -524288, maxScriptNumLen, true, nil}, + {hexToBytes("000070"), 7340032, maxScriptNumLen, true, nil}, + {hexToBytes("0000f0"), -7340032, maxScriptNumLen, true, nil}, + {hexToBytes("00008000"), 8388608, maxScriptNumLen, true, nil}, + {hexToBytes("00008080"), -8388608, maxScriptNumLen, true, nil}, + {hexToBytes("ffffff7f"), 2147483647, maxScriptNumLen, true, nil}, + {hexToBytes("ffffffff"), -2147483647, maxScriptNumLen, true, nil}, {hexToBytes("ffffffff7f"), 549755813887, 5, true, nil}, {hexToBytes("ffffffffff"), -549755813887, 5, true, nil}, {hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil}, @@ -145,50 +145,50 @@ func TestMakeScriptNum(t *testing.T) { // Minimally encoded values that are out of range for data that // is interpreted as script numbers with the minimal encoding // flag set. Should error and return 0. - {hexToBytes("0000008000"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000008080"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000009000"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000009080"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000000001"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000000081"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000008000"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000008080"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000009000"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000009080"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffff00"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffff80"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000000001"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000000081"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffff00"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffff80"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffff7f"), 0, maxScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffffff"), 0, maxScriptNumLen, true, errNumTooBig}, // Non-minimally encoded, but otherwise valid values with // minimal encoding flag. Should error and return 0. - {hexToBytes("00"), 0, defaultScriptNumLen, true, errMinimalData}, // 0 - {hexToBytes("0100"), 0, defaultScriptNumLen, true, errMinimalData}, // 1 - {hexToBytes("7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 127 - {hexToBytes("800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 128 - {hexToBytes("810000"), 0, defaultScriptNumLen, true, errMinimalData}, // 129 - {hexToBytes("000100"), 0, defaultScriptNumLen, true, errMinimalData}, // 256 - {hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 32767 - {hexToBytes("00800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 32768 - {hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, errMinimalData}, // 65535 - {hexToBytes("00000800"), 0, defaultScriptNumLen, true, errMinimalData}, // 524288 - {hexToBytes("00007000"), 0, defaultScriptNumLen, true, errMinimalData}, // 7340032 - {hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520 + {hexToBytes("00"), 0, maxScriptNumLen, true, errMinimalData}, // 0 + {hexToBytes("0100"), 0, maxScriptNumLen, true, errMinimalData}, // 1 + {hexToBytes("7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 127 + {hexToBytes("800000"), 0, maxScriptNumLen, true, errMinimalData}, // 128 + {hexToBytes("810000"), 0, maxScriptNumLen, true, errMinimalData}, // 129 + {hexToBytes("000100"), 0, maxScriptNumLen, true, errMinimalData}, // 256 + {hexToBytes("ff7f00"), 0, maxScriptNumLen, true, errMinimalData}, // 32767 + {hexToBytes("00800000"), 0, maxScriptNumLen, true, errMinimalData}, // 32768 + {hexToBytes("ffff0000"), 0, maxScriptNumLen, true, errMinimalData}, // 65535 + {hexToBytes("00000800"), 0, maxScriptNumLen, true, errMinimalData}, // 524288 + {hexToBytes("00007000"), 0, maxScriptNumLen, true, errMinimalData}, // 7340032 + {hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520 // Non-minimally encoded, but otherwise valid values without // minimal encoding flag. Should not error and return expected // integral number. - {hexToBytes("00"), 0, defaultScriptNumLen, false, nil}, - {hexToBytes("0100"), 1, defaultScriptNumLen, false, nil}, - {hexToBytes("7f00"), 127, defaultScriptNumLen, false, nil}, - {hexToBytes("800000"), 128, defaultScriptNumLen, false, nil}, - {hexToBytes("810000"), 129, defaultScriptNumLen, false, nil}, - {hexToBytes("000100"), 256, defaultScriptNumLen, false, nil}, - {hexToBytes("ff7f00"), 32767, defaultScriptNumLen, false, nil}, - {hexToBytes("00800000"), 32768, defaultScriptNumLen, false, nil}, - {hexToBytes("ffff0000"), 65535, defaultScriptNumLen, false, nil}, - {hexToBytes("00000800"), 524288, defaultScriptNumLen, false, nil}, - {hexToBytes("00007000"), 7340032, defaultScriptNumLen, false, nil}, + {hexToBytes("00"), 0, maxScriptNumLen, false, nil}, + {hexToBytes("0100"), 1, maxScriptNumLen, false, nil}, + {hexToBytes("7f00"), 127, maxScriptNumLen, false, nil}, + {hexToBytes("800000"), 128, maxScriptNumLen, false, nil}, + {hexToBytes("810000"), 129, maxScriptNumLen, false, nil}, + {hexToBytes("000100"), 256, maxScriptNumLen, false, nil}, + {hexToBytes("ff7f00"), 32767, maxScriptNumLen, false, nil}, + {hexToBytes("00800000"), 32768, maxScriptNumLen, false, nil}, + {hexToBytes("ffff0000"), 65535, maxScriptNumLen, false, nil}, + {hexToBytes("00000800"), 524288, maxScriptNumLen, false, nil}, + {hexToBytes("00007000"), 7340032, maxScriptNumLen, false, nil}, {hexToBytes("0009000100"), 16779520, 5, false, nil}, } diff --git a/txscript/stack.go b/txscript/stack.go index eb1d8cfdfe..923047d93e 100644 --- a/txscript/stack.go +++ b/txscript/stack.go @@ -86,7 +86,7 @@ func (s *stack) PopInt() (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) + return makeScriptNum(so, s.verifyMinimalData, maxScriptNumLen) } // PopBool pops the value off the top of the stack, converts it into a bool, and @@ -123,7 +123,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) + return makeScriptNum(so, s.verifyMinimalData, maxScriptNumLen) } // PeekBool returns the Nth item on the stack as a bool without removing it. From 367a75a3f410da2eda19b19b06e9b2eba24511b8 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:25 -0500 Subject: [PATCH 066/108] txscript: Optimize ExtractAtomicSwapDataPushes. This converts the ExtractAtomicSwapDataPushes function to make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. The new implementation is designed such that it should be fairly easy to move the function into the atomic swap tools where it more naturally belongs now that the tokenizer makes it possible to analyze scripts outside of the txscript module. Consequently, this also deprecates the function. The following is a before and after comparison of attempting to extract from both a typical atomic swap script and a very large non-atomic swap script: benchmark old ns/op new ns/op delta BenchmarkExtractAtomicSwapDataPushesLarge-8 61332 44.4 -99.93% BenchmarkExtractAtomicSwapDataPushes-8 990 260 -73.74% benchmark old allocs new allocs delta BenchmarkExtractAtomicSwapDataPushesLarge-8 1 0 -100.00% BenchmarkExtractAtomicSwapDataPushes-8 2 1 -50.00% benchmark old bytes new bytes delta BenchmarkExtractAtomicSwapDataPushesLarge-8 311299 0 -100.00% BenchmarkExtractAtomicSwapDataPushes-8 3168 96 -96.97% --- txscript/standard.go | 140 +++++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index d97072f155..041516e2d5 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -942,64 +942,102 @@ type AtomicSwapDataPushes struct { // // This function is only defined in the txscript package due to API limitations // which prevent callers using txscript to parse nonstandard scripts. +// +// DEPRECATED. This will be removed in the next major version bump. The error +// should also likely be removed if the code is reimplemented by any callers +// since any errors result in a nil result anyway. func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDataPushes, error) { - pops, err := parseScript(pkScript) - if err != nil { - return nil, err + // An atomic swap is of the form: + // IF + // SIZE EQUALVERIFY SHA256 <32-byte secret> EQUALVERIFY DUP + // HASH160 <20-byte recipient hash> + // ELSE + // CHECKLOCKTIMEVERIFY DROP DUP HASH160 <20-byte refund hash> + // ENDIF + // EQUALVERIFY CHECKSIG + type templateMatch struct { + expectCanonicalInt bool + maxIntBytes int + opcode byte + extractedInt int64 + extractedData []byte } - - if len(pops) != 20 { - return nil, nil - } - isAtomicSwap := pops[0].opcode.value == OP_IF && - pops[1].opcode.value == OP_SIZE && - isCanonicalPush(pops[2].opcode.value, pops[2].data) && - pops[3].opcode.value == OP_EQUALVERIFY && - pops[4].opcode.value == OP_SHA256 && - pops[5].opcode.value == OP_DATA_32 && - pops[6].opcode.value == OP_EQUALVERIFY && - pops[7].opcode.value == OP_DUP && - pops[8].opcode.value == OP_HASH160 && - pops[9].opcode.value == OP_DATA_20 && - pops[10].opcode.value == OP_ELSE && - isCanonicalPush(pops[11].opcode.value, pops[11].data) && - pops[12].opcode.value == OP_CHECKLOCKTIMEVERIFY && - pops[13].opcode.value == OP_DROP && - pops[14].opcode.value == OP_DUP && - pops[15].opcode.value == OP_HASH160 && - pops[16].opcode.value == OP_DATA_20 && - pops[17].opcode.value == OP_ENDIF && - pops[18].opcode.value == OP_EQUALVERIFY && - pops[19].opcode.value == OP_CHECKSIG - if !isAtomicSwap { - return nil, nil + var template = [20]templateMatch{ + {opcode: OP_IF}, + {opcode: OP_SIZE}, + {expectCanonicalInt: true, maxIntBytes: maxScriptNumLen}, + {opcode: OP_EQUALVERIFY}, + {opcode: OP_SHA256}, + {opcode: OP_DATA_32}, + {opcode: OP_EQUALVERIFY}, + {opcode: OP_DUP}, + {opcode: OP_HASH160}, + {opcode: OP_DATA_20}, + {opcode: OP_ELSE}, + {expectCanonicalInt: true, maxIntBytes: cltvMaxScriptNumLen}, + {opcode: OP_CHECKLOCKTIMEVERIFY}, + {opcode: OP_DROP}, + {opcode: OP_DUP}, + {opcode: OP_HASH160}, + {opcode: OP_DATA_20}, + {opcode: OP_ENDIF}, + {opcode: OP_EQUALVERIFY}, + {opcode: OP_CHECKSIG}, } - pushes := new(AtomicSwapDataPushes) - copy(pushes.SecretHash[:], pops[5].data) - copy(pushes.RecipientHash160[:], pops[9].data) - copy(pushes.RefundHash160[:], pops[16].data) - if pops[2].data != nil { - locktime, err := makeScriptNum(pops[2].data, true, 5) - if err != nil { + var templateOffset int + tokenizer := MakeScriptTokenizer(version, pkScript) + for tokenizer.Next() { + // Not an atomic swap script if it has more opcodes than expected in the + // template. + if templateOffset >= len(template) { return nil, nil } - pushes.SecretSize = int64(locktime) - } else if op := pops[2].opcode; isSmallInt(op.value) { - pushes.SecretSize = int64(asSmallInt(op.value)) - } else { - return nil, nil - } - if pops[11].data != nil { - locktime, err := makeScriptNum(pops[11].data, true, 5) - if err != nil { - return nil, nil + + op := tokenizer.Opcode() + data := tokenizer.Data() + tplEntry := &template[templateOffset] + if tplEntry.expectCanonicalInt { + switch { + case data != nil: + val, err := makeScriptNum(data, true, tplEntry.maxIntBytes) + if err != nil { + return nil, err + } + tplEntry.extractedInt = int64(val) + + case isSmallInt(op): + tplEntry.extractedInt = int64(asSmallInt(op)) + + // Not an atomic swap script if the opcode does not push an int. + default: + return nil, nil + } + } else { + if op != tplEntry.opcode { + return nil, nil + } + + tplEntry.extractedData = data } - pushes.LockTime = int64(locktime) - } else if op := pops[11].opcode; isSmallInt(op.value) { - pushes.LockTime = int64(asSmallInt(op.value)) - } else { + + templateOffset++ + } + if err := tokenizer.Err(); err != nil { + return nil, err + } + if !tokenizer.Done() || templateOffset != len(template) { return nil, nil } - return pushes, nil + + // At this point, the script appears to be an atomic swap, so populate and + // return the extacted data. + pushes := AtomicSwapDataPushes{ + SecretSize: template[2].extractedInt, + LockTime: template[11].extractedInt, + } + copy(pushes.SecretHash[:], template[5].extractedData) + copy(pushes.RecipientHash160[:], template[9].extractedData) + copy(pushes.RefundHash160[:], template[16].extractedData) + return &pushes, nil } From 33ee3e2f53476fa96b80093aa3cbbaaea7091682 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:26 -0500 Subject: [PATCH 067/108] txscript: Add ExtractPkScriptAddrs benchmarks. --- txscript/bench_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/txscript/bench_test.go b/txscript/bench_test.go index 456db25d52..cac2920280 100644 --- a/txscript/bench_test.go +++ b/txscript/bench_test.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "testing" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" ) @@ -497,3 +498,40 @@ func BenchmarkExtractAtomicSwapDataPushes(b *testing.B) { } } } + +// BenchmarkExtractPkScriptAddrsLarge benchmarks how long it takes to analyze +// and potentially extract addresses from a very large non-standard script. +func BenchmarkExtractPkScriptAddrsLarge(b *testing.B) { + script, err := genComplexScript() + if err != nil { + b.Fatalf("failed to create benchmark script: %v", err) + } + + params := &chaincfg.MainNetParams + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _, _, err := ExtractPkScriptAddrs(script, params) + if err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} + +// BenchmarkExtractPkScriptAddrs benchmarks how long it takes to analyze and +// potentially extract addresses from a typical script. +func BenchmarkExtractPkScriptAddrs(b *testing.B) { + script := mustParseShortForm("OP_DUP HASH160 " + + "DATA_20 0x0102030405060708090a0b0c0d0e0f1011121314 " + + "EQUAL") + + params := &chaincfg.MainNetParams + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _, _, err := ExtractPkScriptAddrs(script, params) + if err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} From 055be988c06449007774923b6ff7a8adf8bab945 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:27 -0500 Subject: [PATCH 068/108] txscript: Optimize ExtractPkScriptAddrs scripthash. This begins the process of converting the ExtractPkScriptAddrs function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In order to ease the review process, the detection of each script type will be converted in a separate commit such that the script is only parsed as a fallback for the cases that are not already converted to more efficient variants. In particular, this converts the detection for pay-to-script-hash scripts. --- txscript/standard.go | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 041516e2d5..f55cbf9849 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -813,11 +813,36 @@ func PushedData(script []byte) ([][]byte, error) { return data, nil } +// scriptHashToAddrs is a convenience function to attempt to convert the passed +// hash to a pay-to-script-hash address housed within an address slice. It is +// used to consolidate common code. +func scriptHashToAddrs(hash []byte, params *chaincfg.Params) []btcutil.Address { + // Skip the hash if it's invalid for some reason. + var addrs []btcutil.Address + addr, err := btcutil.NewAddressScriptHashFromHash(hash, params) + if err == nil { + addrs = append(addrs, addr) + } + return addrs +} + // ExtractPkScriptAddrs returns the type of script, addresses and required // signatures associated with the passed PkScript. Note that it only works for // 'standard' transaction script types. Any data such as public keys which are // invalid are omitted from the results. func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) { + + // Avoid parsing the script for the cases that already have the able to + // work with raw scripts. + + // Check for pay-to-script-hash. + if hash := extractScriptHash(pkScript); hash != nil { + return ScriptHashTy, scriptHashToAddrs(hash, chainParams), 1, nil + } + + // Fall back to slow path. Ultimately these are intended to be replaced by + // faster variants based on the unparsed raw scripts. + var addrs []btcutil.Address var requiredSigs int @@ -867,18 +892,6 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script addrs = append(addrs, addr) } - case ScriptHashTy: - // A pay-to-script-hash script is of the form: - // OP_HASH160 OP_EQUAL - // Therefore the script hash is the 2nd item on the stack. - // Skip the script hash if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressScriptHashFromHash(pops[1].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - case WitnessV0ScriptHashTy: // A pay-to-witness-script-hash script is of the form: // OP_0 <32-byte hash> From 16bd6633b64f2fa8b91c580900136545e4cbf350 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:28 -0500 Subject: [PATCH 069/108] txscript: Optimize ExtractPkScriptAddrs pubkeyhash. This continues the process of converting the ExtractPkScriptAddrs function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In particular, this converts the detection for pay-to-pubkey-hash scripts. --- txscript/standard.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index f55cbf9849..af50660635 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -813,6 +813,19 @@ func PushedData(script []byte) ([][]byte, error) { return data, nil } +// pubKeyHashToAddrs is a convenience function to attempt to convert the +// passed hash to a pay-to-pubkey-hash address housed within an address +// slice. It is used to consolidate common code. +func pubKeyHashToAddrs(hash []byte, params *chaincfg.Params) []btcutil.Address { + // Skip the pubkey hash if it's invalid for some reason. + var addrs []btcutil.Address + addr, err := btcutil.NewAddressPubKeyHash(hash, params) + if err == nil { + addrs = append(addrs, addr) + } + return addrs +} + // scriptHashToAddrs is a convenience function to attempt to convert the passed // hash to a pay-to-script-hash address housed within an address slice. It is // used to consolidate common code. @@ -835,6 +848,11 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script // Avoid parsing the script for the cases that already have the able to // work with raw scripts. + // Check for pay-to-pubkey-hash script. + if hash := extractPubKeyHash(pkScript); hash != nil { + return PubKeyHashTy, pubKeyHashToAddrs(hash, chainParams), 1, nil + } + // Check for pay-to-script-hash. if hash := extractScriptHash(pkScript); hash != nil { return ScriptHashTy, scriptHashToAddrs(hash, chainParams), 1, nil @@ -857,18 +875,6 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script scriptClass := typeOfScript(scriptVersion, pkScript) switch scriptClass { - case PubKeyHashTy: - // A pay-to-pubkey-hash script is of the form: - // OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG - // Therefore the pubkey hash is the 3rd item on the stack. - // Skip the pubkey hash if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressPubKeyHash(pops[2].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - case WitnessV0PubKeyHashTy: // A pay-to-witness-pubkey-hash script is of thw form: // OP_0 <20-byte hash> From 0e810b4ef4a06de24f804f71bbbe12b96edd71e9 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:31 -0500 Subject: [PATCH 070/108] txscript: Optimize ExtractPkScriptAddrs pubkey. This continues the process of converting the ExtractPkScriptAddrs function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In particular, this converts the detection for pay-to-pubkey scripts. --- txscript/standard.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index af50660635..6377598c93 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -858,6 +858,16 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return ScriptHashTy, scriptHashToAddrs(hash, chainParams), 1, nil } + // Check for pay-to-pubkey script. + if data := extractPubKey(pkScript); data != nil { + var addrs []btcutil.Address + addr, err := btcutil.NewAddressPubKey(data, chainParams) + if err == nil { + addrs = append(addrs, addr) + } + return PubKeyTy, addrs, 1, nil + } + // Fall back to slow path. Ultimately these are intended to be replaced by // faster variants based on the unparsed raw scripts. @@ -887,17 +897,6 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script addrs = append(addrs, addr) } - case PubKeyTy: - // A pay-to-pubkey script is of the form: - // OP_CHECKSIG - // Therefore the pubkey is the first item on the stack. - // Skip the pubkey if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressPubKey(pops[0].data, chainParams) - if err == nil { - addrs = append(addrs, addr) - } - case WitnessV0ScriptHashTy: // A pay-to-witness-script-hash script is of the form: // OP_0 <32-byte hash> From 0bc18254d403ea3d1a77626fa86dc0091c83be15 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:33 -0500 Subject: [PATCH 071/108] txscript: Optimize ExtractPkScriptAddrs multisig. This continues the process of converting the ExtractPkScriptAddrs function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In particular, this converts the detection for multisig scripts. Also, since the remaining slow path cases are all recursive calls, the parsed opcodes are no longer used, so parsing is removed. --- txscript/standard.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 6377598c93..b187ac012e 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -868,11 +868,27 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return PubKeyTy, addrs, 1, nil } + // Check for multi-signature script. + const scriptVersion = 0 + details := extractMultisigScriptDetails(scriptVersion, pkScript, true) + if details.valid { + // Convert the public keys while skipping any that are invalid. + addrs := make([]btcutil.Address, 0, len(details.pubKeys)) + for _, pubkey := range details.pubKeys { + addr, err := btcutil.NewAddressPubKey(pubkey, chainParams) + if err == nil { + addrs = append(addrs, addr) + } + } + return MultiSigTy, addrs, details.requiredSigs, nil + } + // Fall back to slow path. Ultimately these are intended to be replaced by // faster variants based on the unparsed raw scripts. var addrs []btcutil.Address var requiredSigs int + var err error // No valid addresses or required signatures if the script doesn't // parse. @@ -881,7 +897,6 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return NonStandardTy, nil, 0, err } - const scriptVersion = 0 scriptClass := typeOfScript(scriptVersion, pkScript) switch scriptClass { @@ -909,25 +924,6 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script addrs = append(addrs, addr) } - case MultiSigTy: - // A multi-signature script is of the form: - // ... OP_CHECKMULTISIG - // Therefore the number of required signatures is the 1st item - // on the stack and the number of public keys is the 2nd to last - // item on the stack. - requiredSigs = asSmallInt(pops[0].opcode.value) - numPubKeys := asSmallInt(pops[len(pops)-2].opcode.value) - - // Extract the public keys while skipping any that are invalid. - addrs = make([]btcutil.Address, 0, numPubKeys) - for i := 0; i < numPubKeys; i++ { - addr, err := btcutil.NewAddressPubKey(pops[i+1].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - } - case NullDataTy: // Null data transactions have no addresses or required // signatures. From 507a4dcc005dea7bf3c1742498cd68de6977a9de Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:38 -0500 Subject: [PATCH 072/108] txscript: Optimize ExtractPkScriptAddrs nulldata. This continues the process of converting the ExtractPkScriptAddrs function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In particular, this converts the detection for nulldata scripts, removes the slow path fallback code since it is the final case, and modifies the comment to call out the script version semantics. The following is a before and after comparison of analyzing both a typical standard script and a very large non-standard script: benchmark old ns/op new ns/op delta ----------------------------------------------------------------------- BenchmarkExtractPkScriptAddrsLarge 132400 44.4 -99.97% BenchmarkExtractPkScriptAddrs 1265 231 -81.74% benchmark old allocs new allocs delta ----------------------------------------------------------------------- BenchmarkExtractPkScriptAddrsLarge 1 0 -100.00% BenchmarkExtractPkScriptAddrs 5 2 -60.00% benchmark old bytes new bytes delta ----------------------------------------------------------------------- BenchmarkExtractPkScriptAddrsLarge 466944 0 -100.00% BenchmarkExtractPkScriptAddrs 1600 48 -97.00% --- txscript/standard.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index b187ac012e..89c2196663 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -843,11 +843,12 @@ func scriptHashToAddrs(hash []byte, params *chaincfg.Params) []btcutil.Address { // signatures associated with the passed PkScript. Note that it only works for // 'standard' transaction script types. Any data such as public keys which are // invalid are omitted from the results. +// +// NOTE: This function only attempts to identify version 0 scripts. The return +// value will indicate a nonstandard script type for other script versions along +// with an invalid script version error. func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) { - // Avoid parsing the script for the cases that already have the able to - // work with raw scripts. - // Check for pay-to-pubkey-hash script. if hash := extractPubKeyHash(pkScript); hash != nil { return PubKeyHashTy, pubKeyHashToAddrs(hash, chainParams), 1, nil @@ -883,6 +884,12 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return MultiSigTy, addrs, details.requiredSigs, nil } + // Check for null data script. + if isNullDataScript(scriptVersion, pkScript) { + // Null data transactions have no addresses or required signatures. + return NullDataTy, nil, 0, nil + } + // Fall back to slow path. Ultimately these are intended to be replaced by // faster variants based on the unparsed raw scripts. @@ -924,15 +931,13 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script addrs = append(addrs, addr) } - case NullDataTy: - // Null data transactions have no addresses or required - // signatures. - case NonStandardTy: // Don't attempt to extract addresses or required signatures for // nonstandard transactions. } + // Don't attempt to extract addresses or required signatures for nonstandard + // transactions. return scriptClass, addrs, requiredSigs, nil } From ae7fffbe52af902984f38c55e558a2334ef1fe69 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:18:51 -0700 Subject: [PATCH 073/108] txscript: Optimize ExtractPkScriptAddrs witness pubkey hash This continues the process of converting the ExtractPkScriptAddrs function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In particular, this converts the extraction for witness-pubkey-hash scripts. --- txscript/standard.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 89c2196663..c0178250c9 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -890,6 +890,15 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return NullDataTy, nil, 0, nil } + if hash := extractWitnessPubKeyHash(pkScript); hash != nil { + var addrs []btcutil.Address + addr, err := btcutil.NewAddressWitnessPubKeyHash(hash, chainParams) + if err == nil { + addrs = append(addrs, addr) + } + return WitnessV0PubKeyHashTy, addrs, 1, nil + } + // Fall back to slow path. Ultimately these are intended to be replaced by // faster variants based on the unparsed raw scripts. @@ -907,18 +916,6 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script scriptClass := typeOfScript(scriptVersion, pkScript) switch scriptClass { - case WitnessV0PubKeyHashTy: - // A pay-to-witness-pubkey-hash script is of thw form: - // OP_0 <20-byte hash> - // Therefore, the pubkey hash is the second item on the stack. - // Skip the pubkey hash if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressWitnessPubKeyHash(pops[1].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - case WitnessV0ScriptHashTy: // A pay-to-witness-script-hash script is of the form: // OP_0 <32-byte hash> From a83152214ccf3fd4972a011e065e37a982343778 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:22:52 -0700 Subject: [PATCH 074/108] txscript: Optimize ExtractPkScriptAddrs witness script hash This continues the process of converting the ExtractPkScriptAddrs function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In particular, this converts the extract of witness-pay-to-script-hash scripts. --- txscript/standard.go | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index c0178250c9..d9f8a9a3e3 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -899,35 +899,24 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return WitnessV0PubKeyHashTy, addrs, 1, nil } + if hash := extractWitnessScriptHash(pkScript); hash != nil { + var addrs []btcutil.Address + addr, err := btcutil.NewAddressWitnessScriptHash(hash, chainParams) + if err == nil { + addrs = append(addrs, addr) + } + return WitnessV0ScriptHashTy, addrs, 1, nil + } + // Fall back to slow path. Ultimately these are intended to be replaced by // faster variants based on the unparsed raw scripts. var addrs []btcutil.Address var requiredSigs int - var err error - - // No valid addresses or required signatures if the script doesn't - // parse. - pops, err := parseScript(pkScript) - if err != nil { - return NonStandardTy, nil, 0, err - } scriptClass := typeOfScript(scriptVersion, pkScript) switch scriptClass { - case WitnessV0ScriptHashTy: - // A pay-to-witness-script-hash script is of the form: - // OP_0 <32-byte hash> - // Therefore, the script hash is the second item on the stack. - // Skip the script hash if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressWitnessScriptHash(pops[1].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - case NonStandardTy: // Don't attempt to extract addresses or required signatures for // nonstandard transactions. From 1034a66b357483b61c368232a4df9fc40ddc45ad Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:32:37 -0700 Subject: [PATCH 075/108] txscript: Optimize ExtractPkScriptAddr assume non-standard if no success This completes the process of converting the ExtractPkScriptAddr function to use the optimized extraction functions recently introduced as part of the typeOfScript conversion. In particular, this cleans up the final remaining case for non-standard transactions. The method now returns NonStandardTy direclty if no other branch was taken. The following is a before and after comparison of attempting to extract pkscript addrs from a very large, non-standard script. benchmark old ns/op new ns/op delta BenchmarkExtractPkScriptAddrsLarge-8 60713 17.0 -99.97% BenchmarkExtractPkScriptAddrs-8 289 17.0 -94.12% benchmark old allocs new allocs delta BenchmarkExtractPkScriptAddrsLarge-8 1 0 -100.00% BenchmarkExtractPkScriptAddrs-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkExtractPkScriptAddrsLarge-8 311299 0 -100.00% BenchmarkExtractPkScriptAddrs-8 768 0 -100.00% --- txscript/standard.go | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index d9f8a9a3e3..326d14d828 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -908,23 +908,8 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script return WitnessV0ScriptHashTy, addrs, 1, nil } - // Fall back to slow path. Ultimately these are intended to be replaced by - // faster variants based on the unparsed raw scripts. - - var addrs []btcutil.Address - var requiredSigs int - - scriptClass := typeOfScript(scriptVersion, pkScript) - - switch scriptClass { - case NonStandardTy: - // Don't attempt to extract addresses or required signatures for - // nonstandard transactions. - } - - // Don't attempt to extract addresses or required signatures for nonstandard - // transactions. - return scriptClass, addrs, requiredSigs, nil + // If none of the above passed, then the address must be non-standard. + return NonStandardTy, nil, 0, nil } // AtomicSwapDataPushes houses the data pushes found in atomic swap contracts. From 6fb1c82fe5d14b4b2d7bba13c2d727051107aa25 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 18:58:28 -0700 Subject: [PATCH 076/108] txscript: Optimize IsWitnessProgram --- txscript/script.go | 17 +++-------------- txscript/standard.go | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 52bedf2699..1ca953dea4 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -103,20 +103,7 @@ func isWitnessPubKeyHash(pops []parsedOpcode) bool { // witness program must be a small integer (from 0-16), followed by 2-40 bytes // of pushed data. func IsWitnessProgram(script []byte) bool { - // The length of the script must be between 4 and 42 bytes. The - // smallest program is the witness version, followed by a data push of - // 2 bytes. The largest allowed witness program has a data push of - // 40-bytes. - if len(script) < 4 || len(script) > 42 { - return false - } - - pops, err := parseScript(script) - if err != nil { - return false - } - - return isWitnessProgram(pops) + return isWitnessProgramScript(script) } // isWitnessProgram returns true if the passed script is a witness program, and @@ -125,6 +112,8 @@ func IsWitnessProgram(script []byte) bool { // first opcode MUST be a small integer (0-16), the push data MUST be // canonical, and finally the size of the push data must be between 2 and 40 // bytes. +// +// DEPRECATED: Use isWitnessProgramScript instead. func isWitnessProgram(pops []parsedOpcode) bool { return len(pops) == 2 && isSmallInt(pops[0].opcode.value) && diff --git a/txscript/standard.go b/txscript/standard.go index 326d14d828..4b7d9be5bb 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -387,6 +387,51 @@ func isWitnessScriptHashScript(script []byte) bool { return extractWitnessScriptHash(script) != nil } +// isWitnessProgramScript returns true if the passed script is a witness +// program, and false otherwise. A witness program MUST adhere to the following +// constraints: there must be exactly two pops (program version and the program +// itself), the first opcode MUST be a small integer (0-16), the push data MUST +// be canonical, and finally the size of the push data must be between 2 and 40 +// bytes. +// +// The length of the script must be between 4 and 42 bytes. The +// smallest program is the witness version, followed by a data push of +// 2 bytes. The largest allowed witness program has a data push of +// 40-bytes. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func isWitnessProgramScript(script []byte) bool { + // Skip parsing if we know the program is invalid based on size. + if len(script) < 4 || len(script) > 42 { + return false + } + + const scriptVersion = 0 + tokenizer := MakeScriptTokenizer(scriptVersion, script) + + // The first opcode must be a small int. + if !tokenizer.Next() || + !isSmallInt(tokenizer.Opcode()) { + + return false + } + + // The second opcode must be a canonical data push, the length of the + // data push is bounded to 40 by the initial check on overall script + // length. + if !tokenizer.Next() || + !isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) { + + return false + } + + // The witness program is valid if there are no more opcodes, and we + // terminated without a parsing error. + return tokenizer.Done() && tokenizer.Err() == nil +} + // isNullDataScript returns whether or not the passed script is a standard // null data script. // From 8b706344a1fbbc5307058d2d9beaa054cb43ab49 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:39:28 -0700 Subject: [PATCH 077/108] txscript: Return witness version and program in one pass --- txscript/standard.go | 52 +++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/txscript/standard.go b/txscript/standard.go index 4b7d9be5bb..e3cf770730 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -387,25 +387,13 @@ func isWitnessScriptHashScript(script []byte) bool { return extractWitnessScriptHash(script) != nil } -// isWitnessProgramScript returns true if the passed script is a witness -// program, and false otherwise. A witness program MUST adhere to the following -// constraints: there must be exactly two pops (program version and the program -// itself), the first opcode MUST be a small integer (0-16), the push data MUST -// be canonical, and finally the size of the push data must be between 2 and 40 -// bytes. -// -// The length of the script must be between 4 and 42 bytes. The -// smallest program is the witness version, followed by a data push of -// 2 bytes. The largest allowed witness program has a data push of -// 40-bytes. -// -// NOTE: This function is only valid for version 0 scripts. Since the function -// does not accept a script version, the results are undefined for other script -// versions. -func isWitnessProgramScript(script []byte) bool { +// extractWitnessProgramInfo returns the version and program if the passed +// script constitutes a valid witness program. The alst return value indicates +// whether or not the script is a valid witness program. +func extractWitnessProgramInfo(script []byte) (int, []byte, bool) { // Skip parsing if we know the program is invalid based on size. if len(script) < 4 || len(script) > 42 { - return false + return 0, nil, false } const scriptVersion = 0 @@ -415,8 +403,9 @@ func isWitnessProgramScript(script []byte) bool { if !tokenizer.Next() || !isSmallInt(tokenizer.Opcode()) { - return false + return 0, nil, false } + version := asSmallInt(tokenizer.Opcode()) // The second opcode must be a canonical data push, the length of the // data push is bounded to 40 by the initial check on overall script @@ -424,12 +413,35 @@ func isWitnessProgramScript(script []byte) bool { if !tokenizer.Next() || !isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) { - return false + return 0, nil, false } + program := tokenizer.Data() // The witness program is valid if there are no more opcodes, and we // terminated without a parsing error. - return tokenizer.Done() && tokenizer.Err() == nil + valid := tokenizer.Done() && tokenizer.Err() == nil + + return version, program, valid +} + +// isWitnessProgramScript returns true if the passed script is a witness +// program, and false otherwise. A witness program MUST adhere to the following +// constraints: there must be exactly two pops (program version and the program +// itself), the first opcode MUST be a small integer (0-16), the push data MUST +// be canonical, and finally the size of the push data must be between 2 and 40 +// bytes. +// +// The length of the script must be between 4 and 42 bytes. The +// smallest program is the witness version, followed by a data push of +// 2 bytes. The largest allowed witness program has a data push of +// 40-bytes. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func isWitnessProgramScript(script []byte) bool { + _, _, valid := extractWitnessProgramInfo(script) + return valid } // isNullDataScript returns whether or not the passed script is a standard From 4b03b593912e1513937d98d7351bf8a58176063f Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 5 Feb 2021 01:58:59 -0800 Subject: [PATCH 078/108] txscript: Use internal analysis methods for GetWitnessSigOpCount --- txscript/script.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 1ca953dea4..57c7b9be17 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -832,15 +832,15 @@ func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, _ bool) int { func GetWitnessSigOpCount(sigScript, pkScript []byte, witness wire.TxWitness) int { // If this is a regular witness program, then we can proceed directly // to counting its signature operations without any further processing. - if IsWitnessProgram(pkScript) { + if isWitnessProgramScript(pkScript) { return getWitnessSigOps(pkScript, witness) } // Next, we'll check the sigScript to see if this is a nested p2sh // witness program. This is a case wherein the sigScript is actually a // datapush of a p2wsh witness program. - if IsPayToScriptHash(pkScript) && IsPushOnlyScript(sigScript) && - IsWitnessProgram(sigScript[1:]) { + if isScriptHashScript(pkScript) && IsPushOnlyScript(sigScript) && + len(sigScript) > 0 && isWitnessProgramScript(sigScript[1:]) { return getWitnessSigOps(sigScript[1:], witness) } From d410d7d7d4fe116508e7aa9a180bd77dd5b9b8c3 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:43:43 -0700 Subject: [PATCH 079/108] txscript: Optimize ExtractWitnessProgramInfo --- txscript/script.go | 13 +++---------- txscript/standard.go | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 57c7b9be17..9d7de56cbd 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -131,23 +131,16 @@ func IsNullData(script []byte) bool { // ExtractWitnessProgramInfo attempts to extract the witness program version, // as well as the witness program itself from the passed script. func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) { - pops, err := parseScript(script) - if err != nil { - return 0, nil, err - } - // If at this point, the scripts doesn't resemble a witness program, // then we'll exit early as there isn't a valid version or program to // extract. - if !isWitnessProgram(pops) { + version, program, valid := extractWitnessProgramInfo(script) + if !valid { return 0, nil, fmt.Errorf("script is not a witness program, " + "unable to extract version or witness program") } - witnessVersion := asSmallInt(pops[0].opcode.value) - witnessProgram := pops[1].data - - return witnessVersion, witnessProgram, nil + return version, program, nil } // IsPushOnlyScript returns whether or not the passed script only pushes data diff --git a/txscript/standard.go b/txscript/standard.go index e3cf770730..44902971e9 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -388,7 +388,7 @@ func isWitnessScriptHashScript(script []byte) bool { } // extractWitnessProgramInfo returns the version and program if the passed -// script constitutes a valid witness program. The alst return value indicates +// script constitutes a valid witness program. The last return value indicates // whether or not the script is a valid witness program. func extractWitnessProgramInfo(script []byte) (int, []byte, bool) { // Skip parsing if we know the program is invalid based on size. From 7ad3a10442d80b204e8276f524d4859aa4d55887 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:46 -0500 Subject: [PATCH 080/108] txscript: mergeMultiSig function def order cleanup. This moves the function definition for mergeMultiSig so it is more consistent with the preferred order used through the codebase. In particular, the functions are defined before they're first used and generally as close as possible to the first use when they're defined in the same file. --- txscript/sign.go | 135 ++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/txscript/sign.go b/txscript/sign.go index b9f8b2dbb4..587910649c 100644 --- a/txscript/sign.go +++ b/txscript/sign.go @@ -212,73 +212,6 @@ func sign(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, } } -// mergeScripts merges sigScript and prevScript assuming they are both -// partial solutions for pkScript spending output idx of tx. class, addresses -// and nrequired are the result of extracting the addresses from pkscript. -// The return value is the best effort merging of the two scripts. Calling this -// function with addresses, class and nrequired that do not match pkScript is -// an error and results in undefined behaviour. -func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, - pkScript []byte, class ScriptClass, addresses []btcutil.Address, - nRequired int, sigScript, prevScript []byte) []byte { - - // TODO: the scripthash and multisig paths here are overly - // inefficient in that they will recompute already known data. - // some internal refactoring could probably make this avoid needless - // extra calculations. - switch class { - case ScriptHashTy: - // Remove the last push in the script and then recurse. - // this could be a lot less inefficient. - sigPops, err := parseScript(sigScript) - if err != nil || len(sigPops) == 0 { - return prevScript - } - prevPops, err := parseScript(prevScript) - if err != nil || len(prevPops) == 0 { - return sigScript - } - - // assume that script in sigPops is the correct one, we just - // made it. - script := sigPops[len(sigPops)-1].data - - // We already know this information somewhere up the stack. - class, addresses, nrequired, _ := - ExtractPkScriptAddrs(script, chainParams) - - // regenerate scripts. - sigScript, _ := unparseScript(sigPops) - prevScript, _ := unparseScript(prevPops) - - // Merge - mergedScript := mergeScripts(chainParams, tx, idx, script, - class, addresses, nrequired, sigScript, prevScript) - - // Reappend the script and return the result. - builder := NewScriptBuilder() - builder.AddOps(mergedScript) - builder.AddData(script) - finalScript, _ := builder.Script() - return finalScript - case MultiSigTy: - return mergeMultiSig(tx, idx, addresses, nRequired, pkScript, - sigScript, prevScript) - - // It doesn't actually make sense to merge anything other than multiig - // and scripthash (because it could contain multisig). Everything else - // has either zero signature, can't be spent, or has a single signature - // which is either present or not. The other two cases are handled - // above. In the conflict case here we just assume the longest is - // correct (this matches behaviour of the reference implementation). - default: - if len(sigScript) > len(prevScript) { - return sigScript - } - return prevScript - } -} - // mergeMultiSig combines the two signature scripts sigScript and prevScript // that both provide signatures for pkScript in output idx of tx. addresses // and nRequired should be the results from extracting the addresses from @@ -397,6 +330,74 @@ sigLoop: return script } +// mergeScripts merges sigScript and prevScript assuming they are both +// partial solutions for pkScript spending output idx of tx. class, addresses +// and nrequired are the result of extracting the addresses from pkscript. +// The return value is the best effort merging of the two scripts. Calling this +// function with addresses, class and nrequired that do not match pkScript is +// an error and results in undefined behaviour. +func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, + pkScript []byte, class ScriptClass, addresses []btcutil.Address, + nRequired int, sigScript, prevScript []byte) []byte { + + // TODO(oga) the scripthash and multisig paths here are overly + // inefficient in that they will recompute already known data. + // some internal refactoring could probably make this avoid needless + // extra calculations. + switch class { + case ScriptHashTy: + // Remove the last push in the script and then recurse. + // this could be a lot less inefficient. + sigPops, err := parseScript(sigScript) + if err != nil || len(sigPops) == 0 { + return prevScript + } + prevPops, err := parseScript(prevScript) + if err != nil || len(prevPops) == 0 { + return sigScript + } + + // assume that script in sigPops is the correct one, we just + // made it. + script := sigPops[len(sigPops)-1].data + + // We already know this information somewhere up the stack, + // therefore the error is ignored. + class, addresses, nrequired, _ := + ExtractPkScriptAddrs(script, chainParams) + + // regenerate scripts. + sigScript, _ := unparseScript(sigPops) + prevScript, _ := unparseScript(prevPops) + + // Merge + mergedScript := mergeScripts(chainParams, tx, idx, script, + class, addresses, nrequired, sigScript, prevScript) + + // Reappend the script and return the result. + builder := NewScriptBuilder() + builder.AddOps(mergedScript) + builder.AddData(script) + finalScript, _ := builder.Script() + return finalScript + case MultiSigTy: + return mergeMultiSig(tx, idx, addresses, nRequired, pkScript, + sigScript, prevScript) + + // It doesn't actually make sense to merge anything other than multiig + // and scripthash (because it could contain multisig). Everything else + // has either zero signature, can't be spent, or has a single signature + // which is either present or not. The other two cases are handled + // above. In the conflict case here we just assume the longest is + // correct (this matches behaviour of the reference implementation). + default: + if len(sigScript) > len(prevScript) { + return sigScript + } + return prevScript + } +} + // KeyDB is an interface type provided to SignTxOutput, it encapsulates // any user state required to get the private keys for an address. type KeyDB interface { From dd609d6e36f3e6c7a4a0262cd0004733854d35c1 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 15:25:18 -0700 Subject: [PATCH 081/108] txscript: Introduce calcWitnessSignatureHashRaw --- txscript/script.go | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 9d7de56cbd..4651798541 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -385,7 +385,7 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash { return chainhash.DoubleHashH(b.Bytes()) } -// calcWitnessSignatureHash computes the sighash digest of a transaction's +// calcWitnessSignatureHashRaw computes the sighash digest of a transaction's // segwit input using the new, optimized digest calculation algorithm defined // in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki. // This function makes use of pre-calculated sighash fragments stored within @@ -396,7 +396,7 @@ func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash { // being spent, in addition to the final transaction fee. In the case the // wallet if fed an invalid input amount, the real sighash will differ causing // the produced signature to be invalid. -func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, +func calcWitnessSignatureHashRaw(scriptSig []byte, sigHashes *TxSigHashes, hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { // As a sanity check, ensure the passed input index for the transaction @@ -446,7 +446,7 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, binary.LittleEndian.PutUint32(bIndex[:], txIn.PreviousOutPoint.Index) sigHash.Write(bIndex[:]) - if isWitnessPubKeyHash(subScript) { + if isWitnessPubKeyHashScript(scriptSig) { // The script code for a p2wkh is a length prefix varint for // the next 25 bytes, followed by a re-creation of the original // p2pkh pk script. @@ -454,15 +454,14 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, sigHash.Write([]byte{OP_DUP}) sigHash.Write([]byte{OP_HASH160}) sigHash.Write([]byte{OP_DATA_20}) - sigHash.Write(subScript[1].data) + sigHash.Write(extractWitnessPubKeyHash(scriptSig)) sigHash.Write([]byte{OP_EQUALVERIFY}) sigHash.Write([]byte{OP_CHECKSIG}) } else { // For p2wsh outputs, and future outputs, the script code is // the original script, with all code separators removed, // serialized with a var int length prefix. - rawScript, _ := unparseScript(subScript) - wire.WriteVarBytes(&sigHash, 0, rawScript) + wire.WriteVarBytes(&sigHash, 0, scriptSig) } // Next, add the input amount, and sequence number of the input being @@ -501,6 +500,30 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, return chainhash.DoubleHashB(sigHash.Bytes()), nil } +// calcWitnessSignatureHash computes the sighash digest of a transaction's +// segwit input using the new, optimized digest calculation algorithm defined +// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki. +// This function makes use of pre-calculated sighash fragments stored within +// the passed HashCache to eliminate duplicate hashing computations when +// calculating the final digest, reducing the complexity from O(N^2) to O(N). +// Additionally, signatures now cover the input value of the referenced unspent +// output. This allows offline, or hardware wallets to compute the exact amount +// being spent, in addition to the final transaction fee. In the case the +// wallet if fed an invalid input amount, the real sighash will differ causing +// the produced signature to be invalid. +// +// DEPRECATED: Use calcWitnessSignatureHashRaw instead. +func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, + hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { + + script, err := unparseScript(subScript) + if err != nil { + return nil, err + } + + return calcWitnessSignatureHashRaw(script, sigHashes, hashType, tx, idx, amt) +} + // CalcWitnessSigHash computes the sighash digest for the specified input of // the target transaction observing the desired sig hash type. func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType, From ed9e17a043bdab0427b3f3b8113b9cc27e90cd84 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 20:09:07 -0700 Subject: [PATCH 082/108] txscript: Remove unused isWitnessPubKeyHash --- txscript/script.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 4651798541..10e0a1ca61 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -90,14 +90,6 @@ func IsPayToWitnessPubKeyHash(script []byte) bool { return isWitnessPubKeyHashScript(script) } -// isWitnessPubKeyHash returns true if the passed script is a -// pay-to-witness-pubkey-hash, and false otherwise. -func isWitnessPubKeyHash(pops []parsedOpcode) bool { - return len(pops) == 2 && - pops[0].opcode.value == OP_0 && - pops[1].opcode.value == OP_DATA_20 -} - // IsWitnessProgram returns true if the passed script is a valid witness // program which is encoded according to the passed witness program version. A // witness program must be a small integer (from 0-16), followed by 2-40 bytes From e00fec15571157f1241c8347893cb030ee28e034 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 15:26:10 -0700 Subject: [PATCH 083/108] txscript: Use optimized calcWitnessSignatureHashRaw w/o parsing --- txscript/script.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 10e0a1ca61..6798be1cb5 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -521,13 +521,12 @@ func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { - parsedScript, err := parseScript(script) - if err != nil { - return nil, fmt.Errorf("cannot parse output script: %v", err) + const scriptVersion = 0 + if err := checkScriptParses(scriptVersion, script); err != nil { + return nil, err } - return calcWitnessSignatureHash(parsedScript, sigHashes, hType, tx, idx, - amt) + return calcWitnessSignatureHashRaw(script, sigHashes, hType, tx, idx, amt) } // shallowCopyTx creates a shallow copy of the transaction for use when From f3354beb12ceaf97b0ab25a2ac3d9817231fa663 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:50 -0500 Subject: [PATCH 084/108] txscript: Use raw scripts in SignTxOutput. This converts SignTxOutput and supporting funcs, namely sign, mergeScripts and mergeMultiSig, to make use of the new tokenizer as well as some recently added funcs that deal with raw scripts in order to remove the reliance on parsed opcodes as a step towards utlimately removing them altogether and updates the comments to explicitly call out the script version semantics. It is worth noting that this has the side effect of optimizing the function as well, however, since this change is not focused on the optimization aspects, no benchmarks are provided. --- txscript/sign.go | 88 ++++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/txscript/sign.go b/txscript/sign.go index 587910649c..4d63a9b245 100644 --- a/txscript/sign.go +++ b/txscript/sign.go @@ -218,37 +218,44 @@ func sign(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, // pkScript. Since this function is internal only we assume that the arguments // have come from other functions internally and thus are all consistent with // each other, behaviour is undefined if this contract is broken. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func mergeMultiSig(tx *wire.MsgTx, idx int, addresses []btcutil.Address, nRequired int, pkScript, sigScript, prevScript []byte) []byte { - // This is an internal only function and we already parsed this script - // as ok for multisig (this is how we got here), so if this fails then - // all assumptions are broken and who knows which way is up? - pkPops, _ := parseScript(pkScript) - - sigPops, err := parseScript(sigScript) - if err != nil || len(sigPops) == 0 { + // Nothing to merge if either the new or previous signature scripts are + // empty. + if len(sigScript) == 0 { return prevScript } - - prevPops, err := parseScript(prevScript) - if err != nil || len(prevPops) == 0 { + if len(prevScript) == 0 { return sigScript } // Convenience function to avoid duplication. - extractSigs := func(pops []parsedOpcode, sigs [][]byte) [][]byte { - for _, pop := range pops { - if len(pop.data) != 0 { - sigs = append(sigs, pop.data) + var possibleSigs [][]byte + extractSigs := func(script []byte) error { + const scriptVersion = 0 + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + if data := tokenizer.Data(); len(data) != 0 { + possibleSigs = append(possibleSigs, data) } } - return sigs + return tokenizer.Err() } - possibleSigs := make([][]byte, 0, len(sigPops)+len(prevPops)) - possibleSigs = extractSigs(sigPops, possibleSigs) - possibleSigs = extractSigs(prevPops, possibleSigs) + // Attempt to extract signatures from the two scripts. Return the other + // script that is intended to be merged in the case signature extraction + // fails for some reason. + if err := extractSigs(sigScript); err != nil { + return prevScript + } + if err := extractSigs(prevScript); err != nil { + return sigScript + } // Now we need to match the signatures to pubkeys, the only real way to // do that is to try to verify them all and match it to the pubkey @@ -278,10 +285,7 @@ sigLoop: // however, assume no sigs etc are in the script since that // would make the transaction nonstandard and thus not // MultiSigTy, so we just need to hash the full thing. - hash, err := calcSignatureHash(pkPops, hashType, tx, idx) - if err != nil { - panic(fmt.Sprintf("cannot compute sighash: %v", err)) - } + hash := calcSignatureHashRaw(pkScript, hashType, tx, idx) for _, addr := range addresses { // All multisig addresses should be pubkey addresses @@ -336,6 +340,10 @@ sigLoop: // The return value is the best effort merging of the two scripts. Calling this // function with addresses, class and nrequired that do not match pkScript is // an error and results in undefined behaviour. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, pkScript []byte, class ScriptClass, addresses []btcutil.Address, nRequired int, sigScript, prevScript []byte) []byte { @@ -344,32 +352,34 @@ func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, // inefficient in that they will recompute already known data. // some internal refactoring could probably make this avoid needless // extra calculations. + const scriptVersion = 0 switch class { case ScriptHashTy: - // Remove the last push in the script and then recurse. - // this could be a lot less inefficient. - sigPops, err := parseScript(sigScript) - if err != nil || len(sigPops) == 0 { + // Nothing to merge if either the new or previous signature + // scripts are empty or fail to parse. + if len(sigScript) == 0 || + checkScriptParses(scriptVersion, sigScript) != nil { + return prevScript } - prevPops, err := parseScript(prevScript) - if err != nil || len(prevPops) == 0 { + if len(prevScript) == 0 || + checkScriptParses(scriptVersion, prevScript) != nil { + return sigScript } - // assume that script in sigPops is the correct one, we just - // made it. - script := sigPops[len(sigPops)-1].data + // Remove the last push in the script and then recurse. + // this could be a lot less inefficient. + // + // Assume that final script is the correct one since it was just + // made and it is a pay-to-script-hash. + script := finalOpcodeData(scriptVersion, sigScript) // We already know this information somewhere up the stack, // therefore the error is ignored. class, addresses, nrequired, _ := ExtractPkScriptAddrs(script, chainParams) - // regenerate scripts. - sigScript, _ := unparseScript(sigPops) - prevScript, _ := unparseScript(prevPops) - // Merge mergedScript := mergeScripts(chainParams, tx, idx, script, class, addresses, nrequired, sigScript, prevScript) @@ -380,6 +390,7 @@ func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, builder.AddData(script) finalScript, _ := builder.Script() return finalScript + case MultiSigTy: return mergeMultiSig(tx, idx, addresses, nRequired, pkScript, sigScript, prevScript) @@ -408,8 +419,7 @@ type KeyDB interface { type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error) // GetKey implements KeyDB by returning the result of calling the closure. -func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, - bool, error) { +func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, bool, error) { return kc(address) } @@ -434,6 +444,10 @@ func (sc ScriptClosure) GetScript(address btcutil.Address) ([]byte, error) { // getScript. If previousScript is provided then the results in previousScript // will be merged in a type-dependent manner with the newly generated. // signature script. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB, previousScript []byte) ([]byte, error) { From 30874ff76b167796960cbd57178305cfefb2b0da Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:51 -0500 Subject: [PATCH 085/108] txscript: Implement efficient opcode data removal. This introduces a new function named removeOpcodeByDataRaw which accepts the raw scripts and data to remove versus requiring the parsed opcodes to both significantly optimize it as well as make it more flexible for working with raw scripts. There are several places in the rest of the code that currently only have access to the parsed opcodes, so this only introduces the function for use in the future and deprecates the existing one. Note that, in practice, the script will never actually contain the data that is intended to be removed since the function is only used during signature verification to remove the signature itself which would require some incredibly non-standard code to create. Thus, as an optimization, it avoids allocating a new script unless there is actually a match that needs to be removed. Finally, it updates the tests to use the new function. --- txscript/script.go | 55 +++++++++++++++++++++++++++++++++++++++++ txscript/script_test.go | 13 +++++----- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 6798be1cb5..336a2c8b29 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -310,6 +310,8 @@ func isCanonicalPush(opcode byte, data []byte) bool { // removeOpcodeByData will return the script minus any opcodes that would push // the passed data to the stack. +// +// DEPRECATED. Use removeOpcodeByDataRaw instead. func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode { retScript := make([]parsedOpcode, 0, len(pkscript)) for _, pop := range pkscript { @@ -323,6 +325,59 @@ func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode { } +// removeOpcodeByDataRaw will return the script minus any opcodes that perform a +// canonical push of data that contains the passed data to remove. This +// function assumes it is provided a version 0 script as any future version of +// script should avoid this functionality since it is unncessary due to the +// signature scripts not being part of the witness-free transaction hash. +// +// WARNING: This will return the passed script unmodified unless a modification +// is necessary in which case the modified script is returned. This implies +// callers may NOT rely on being able to safely mutate either the passed or +// returned script without potentially modifying the same data. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func removeOpcodeByDataRaw(script []byte, dataToRemove []byte) []byte { + // Avoid work when possible. + if len(script) == 0 || len(dataToRemove) == 0 { + return script + } + + // Parse through the script looking for a canonical data push that contains + // the data to remove. + const scriptVersion = 0 + var result []byte + var prevOffset int32 + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + // In practice, the script will basically never actually contain the + // data since this function is only used during signature verification + // to remove the signature itself which would require some incredibly + // non-standard code to create. + // + // Thus, as an optimization, avoid allocating a new script unless there + // is actually a match that needs to be removed. + op, data := tokenizer.Opcode(), tokenizer.Data() + if isCanonicalPush(op, data) && bytes.Contains(data, dataToRemove) { + if result == nil { + fullPushLen := tokenizer.ByteIndex() - prevOffset + result = make([]byte, 0, int32(len(script))-fullPushLen) + result = append(result, script[0:prevOffset]...) + } + } else if result != nil { + result = append(result, script[prevOffset:tokenizer.ByteIndex()]...) + } + + prevOffset = tokenizer.ByteIndex() + } + if result == nil { + result = script + } + return result +} + // calcHashPrevOuts calculates a single hash of all the previous outputs // (txid:index) referenced within the passed transaction. This calculated hash // can be re-used when validating all inputs spending segwit outputs, with a diff --git a/txscript/script_test.go b/txscript/script_test.go index 62c51e4181..9a64865e35 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -4129,16 +4129,15 @@ func TestRemoveOpcodeByData(t *testing.T) { }, } - // tstRemoveOpcodeByData is a convenience function to parse the provided - // raw script, remove the passed data, then unparse the result back - // into a raw script. + // tstRemoveOpcodeByData is a convenience function to ensure the provided + // script parses before attempting to remove the passed data. + const scriptVersion = 0 tstRemoveOpcodeByData := func(script []byte, data []byte) ([]byte, error) { - pops, err := parseScript(script) - if err != nil { + if err := checkScriptParses(scriptVersion, script); err != nil { return nil, err } - pops = removeOpcodeByData(pops, data) - return unparseScript(pops) + + return removeOpcodeByDataRaw(script, data), nil } for _, test := range tests { From a4720f30e532c6c2815dd8ea626cf371ad2dca0e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 16:26:22 -0700 Subject: [PATCH 086/108] txscript: Optimize removeOpcodeRaw --- txscript/script.go | 39 +++++++++++++++++++++++++++++++++++++++ txscript/script_test.go | 7 +++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 336a2c8b29..7bfe44b3b9 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -269,6 +269,8 @@ func DisasmString(script []byte) (string, error) { // removeOpcode will remove any opcode matching ``opcode'' from the opcode // stream in pkscript +// +// DEPRECATED. Use removeOpcodeRaw instead. func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode { retScript := make([]parsedOpcode, 0, len(pkscript)) for _, pop := range pkscript { @@ -279,6 +281,43 @@ func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode { return retScript } +// removeOpcodeRaw will return the script after removing any opcodes that match +// `opcode`. If the opcode does not appear in script, the original script will +// be returned unmodified. Otherwise, a new script will be allocated to contain +// the filtered script. This metehod assumes that the script parses +// successfully. +// +// NOTE: This function is only valid for version 0 scripts. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func removeOpcodeRaw(script []byte, opcode byte) []byte { + // Avoid work when possible. + if len(script) == 0 { + return script + } + + const scriptVersion = 0 + var result []byte + var prevOffset int32 + + tokenizer := MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + if tokenizer.Opcode() == opcode { + if result == nil { + result = make([]byte, 0, len(script)) + result = append(result, script[:prevOffset]...) + } + } else if result != nil { + result = append(result, script[prevOffset:tokenizer.ByteIndex()]...) + } + prevOffset = tokenizer.ByteIndex() + } + if result == nil { + return script + } + return result +} + // isCanonicalPush returns true if the opcode is either not a push instruction // or the data associated with the push instruction uses the smallest // instruction to do the job. False otherwise. diff --git a/txscript/script_test.go b/txscript/script_test.go index 9a64865e35..02c364ce31 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -3981,13 +3981,12 @@ func TestRemoveOpcodes(t *testing.T) { // tstRemoveOpcode is a convenience function to parse the provided // raw script, remove the passed opcode, then unparse the result back // into a raw script. + const scriptVersion = 0 tstRemoveOpcode := func(script []byte, opcode byte) ([]byte, error) { - pops, err := parseScript(script) - if err != nil { + if err := checkScriptParses(scriptVersion, script); err != nil { return nil, err } - pops = removeOpcode(pops, opcode) - return unparseScript(pops) + return removeOpcodeRaw(script, opcode), nil } for _, test := range tests { From 2ddcdb91f5bcb8df7fab43cbf1b92cccaadeb6fc Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 20:11:34 -0700 Subject: [PATCH 087/108] txscript: Remove unused removeOpcode --- txscript/script.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 7bfe44b3b9..7c8423bb50 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -267,20 +267,6 @@ func DisasmString(script []byte) (string, error) { return disbuf.String(), tokenizer.Err() } -// removeOpcode will remove any opcode matching ``opcode'' from the opcode -// stream in pkscript -// -// DEPRECATED. Use removeOpcodeRaw instead. -func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode { - retScript := make([]parsedOpcode, 0, len(pkscript)) - for _, pop := range pkscript { - if pop.opcode.value != opcode { - retScript = append(retScript, pop) - } - } - return retScript -} - // removeOpcodeRaw will return the script after removing any opcodes that match // `opcode`. If the opcode does not appear in script, the original script will // be returned unmodified. Otherwise, a new script will be allocated to contain From a2ab5b66816c8270185d43d20de7f11681be8afa Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 16:28:26 -0700 Subject: [PATCH 088/108] txscript: Use removeOpcodeRaw for CODESEP in calcSigHash --- txscript/script.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 7c8423bb50..0be0a7b8b3 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -683,24 +683,14 @@ func calcSignatureHashRaw(sigScript []byte, hashType SigHashType, tx *wire.MsgTx } // Remove all instances of OP_CODESEPARATOR from the script. - filteredScript := make([]byte, 0, len(sigScript)) - const scriptVersion = 0 - tokenizer := MakeScriptTokenizer(scriptVersion, sigScript) - var prevOffset int32 - for tokenizer.Next() { - if tokenizer.Opcode() != OP_CODESEPARATOR { - filteredScript = append(filteredScript, - sigScript[prevOffset:tokenizer.ByteIndex()]...) - } - prevOffset = tokenizer.ByteIndex() - } + sigScript = removeOpcodeRaw(sigScript, OP_CODESEPARATOR) // Make a shallow copy of the transaction, zeroing out the script for // all inputs that are not currently being processed. txCopy := shallowCopyTx(tx) for i := range txCopy.TxIn { if i == idx { - txCopy.TxIn[idx].SignatureScript = filteredScript + txCopy.TxIn[idx].SignatureScript = sigScript } else { txCopy.TxIn[i].SignatureScript = nil } From 484f7b1fef6a62eabe31f8c8a9fa44d15e2310b2 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:52 -0500 Subject: [PATCH 089/108] txscript: Make isDisabled accept raw opcode. This converts the isDisabled function defined on a parsed opcode to a standalone function which accepts an opcode as a byte instead in order to make it more flexible for raw script analysis. It also updates all callers accordingly. --- txscript/engine.go | 42 +++++++++++++++++++++++++++++++++++++++++- txscript/opcode.go | 39 --------------------------------------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index ef7ad33e3c..00973a03a4 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -154,12 +154,52 @@ func (vm *Engine) isBranchExecuting() bool { return vm.condStack[len(vm.condStack)-1] == OpCondTrue } +// isOpcodeDisabled returns whether or not the opcode is disabled and thus is +// always bad to see in the instruction stream (even if turned off by a +// conditional). +func isOpcodeDisabled(opcode byte) bool { + switch opcode { + case OP_CAT: + return true + case OP_SUBSTR: + return true + case OP_LEFT: + return true + case OP_RIGHT: + return true + case OP_INVERT: + return true + case OP_AND: + return true + case OP_OR: + return true + case OP_XOR: + return true + case OP_2MUL: + return true + case OP_2DIV: + return true + case OP_MUL: + return true + case OP_DIV: + return true + case OP_MOD: + return true + case OP_LSHIFT: + return true + case OP_RSHIFT: + return true + default: + return false + } +} + // executeOpcode peforms execution on the passed opcode. It takes into account // whether or not it is hidden by conditionals, but some rules still must be // tested in this case. func (vm *Engine) executeOpcode(pop *parsedOpcode) error { // Disabled opcodes are fail on program counter. - if pop.isDisabled() { + if isOpcodeDisabled(pop.opcode.value) { str := fmt.Sprintf("attempt to execute disabled opcode %s", pop.opcode.name) return scriptError(ErrDisabledOpcode, str) diff --git a/txscript/opcode.go b/txscript/opcode.go index 893bebdf8d..62c6649d3d 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -619,45 +619,6 @@ type parsedOpcode struct { data []byte } -// isDisabled returns whether or not the opcode is disabled and thus is always -// bad to see in the instruction stream (even if turned off by a conditional). -func (pop *parsedOpcode) isDisabled() bool { - switch pop.opcode.value { - case OP_CAT: - return true - case OP_SUBSTR: - return true - case OP_LEFT: - return true - case OP_RIGHT: - return true - case OP_INVERT: - return true - case OP_AND: - return true - case OP_OR: - return true - case OP_XOR: - return true - case OP_2MUL: - return true - case OP_2DIV: - return true - case OP_MUL: - return true - case OP_DIV: - return true - case OP_MOD: - return true - case OP_LSHIFT: - return true - case OP_RSHIFT: - return true - default: - return false - } -} - // checkParseableInScript checks whether or not the current opcode is able to be // parsed at a certain position in a script. // This returns the position of the next opcode to be parsed in the script. From c6410257eb2894ee086fda5bb5ce88e33a44011a Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:53 -0500 Subject: [PATCH 090/108] txscript: Make alwaysIllegal accept raw opcode. This converts the alwaysIllegal function defined on a parsed opcode to a standalone function named isOpcodeAlwaysIllegal which accepts an opcode as a byte instead in order to make it more flexible for raw script analysis. It also updates all callers accordingly. --- txscript/engine.go | 16 +++++++++++++++- txscript/opcode.go | 14 -------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 00973a03a4..1c6a124b0c 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -194,6 +194,20 @@ func isOpcodeDisabled(opcode byte) bool { } } +// isOpcodeAlwaysIllegal returns whether or not the opcode is always illegal +// when passed over by the program counter even if in a non-executed branch (it +// isn't a coincidence that they are conditionals). +func isOpcodeAlwaysIllegal(opcode byte) bool { + switch opcode { + case OP_VERIF: + return true + case OP_VERNOTIF: + return true + default: + return false + } +} + // executeOpcode peforms execution on the passed opcode. It takes into account // whether or not it is hidden by conditionals, but some rules still must be // tested in this case. @@ -206,7 +220,7 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error { } // Always-illegal opcodes are fail on program counter. - if pop.alwaysIllegal() { + if isOpcodeAlwaysIllegal(pop.opcode.value) { str := fmt.Sprintf("attempt to execute reserved opcode %s", pop.opcode.name) return scriptError(ErrReservedOpcode, str) diff --git a/txscript/opcode.go b/txscript/opcode.go index 62c6649d3d..dc4ec50eb5 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -692,20 +692,6 @@ func (pop *parsedOpcode) checkParseableInScript(script []byte, scriptPos int) (i return scriptPos, nil } -// alwaysIllegal returns whether or not the opcode is always illegal when passed -// over by the program counter even if in a non-executed branch (it isn't a -// coincidence that they are conditionals). -func (pop *parsedOpcode) alwaysIllegal() bool { - switch pop.opcode.value { - case OP_VERIF: - return true - case OP_VERNOTIF: - return true - default: - return false - } -} - // isConditional returns whether or not the opcode is a conditional opcode which // changes the conditional execution stack when executed. func (pop *parsedOpcode) isConditional() bool { From 62c608f2654367caedef75e4370155d2cd0c0024 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:54 -0500 Subject: [PATCH 091/108] txscript: Make isConditional accept raw opcode. This converts the isConditional function defined on a parsed opcode to a standalone function named isOpcodeConditional which accepts an opcode as a byte instead in order to make it more flexible for raw script analysis. It also updates all callers accordingly. --- txscript/engine.go | 19 ++++++++++++++++++- txscript/opcode.go | 17 ----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 1c6a124b0c..ddb26de58f 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -208,6 +208,23 @@ func isOpcodeAlwaysIllegal(opcode byte) bool { } } +// isOpcodeConditional returns whether or not the opcode is a conditional opcode +// which changes the conditional execution stack when executed. +func isOpcodeConditional(opcode byte) bool { + switch opcode { + case OP_IF: + return true + case OP_NOTIF: + return true + case OP_ELSE: + return true + case OP_ENDIF: + return true + default: + return false + } +} + // executeOpcode peforms execution on the passed opcode. It takes into account // whether or not it is hidden by conditionals, but some rules still must be // tested in this case. @@ -243,7 +260,7 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error { // Nothing left to do when this is not a conditional opcode and it is // not in an executing branch. - if !vm.isBranchExecuting() && !pop.isConditional() { + if !vm.isBranchExecuting() && !isOpcodeConditional(pop.opcode.value) { return nil } diff --git a/txscript/opcode.go b/txscript/opcode.go index dc4ec50eb5..7705f59b02 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -692,23 +692,6 @@ func (pop *parsedOpcode) checkParseableInScript(script []byte, scriptPos int) (i return scriptPos, nil } -// isConditional returns whether or not the opcode is a conditional opcode which -// changes the conditional execution stack when executed. -func (pop *parsedOpcode) isConditional() bool { - switch pop.opcode.value { - case OP_IF: - return true - case OP_NOTIF: - return true - case OP_ELSE: - return true - case OP_ENDIF: - return true - default: - return false - } -} - // checkMinimalDataPush returns whether or not the current data push uses the // smallest possible opcode to represent it. For example, the value 15 could // be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is a From 710bd5646e55a520054c4620e507b8867b864e64 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:55 -0500 Subject: [PATCH 092/108] txscript: Make min push accept raw opcode and data. This converts the checkMinimalDataPush function defined on a parsed opcode to a standalone function which accepts an opcode and data slice instead in order to make it more flexible for raw script analysis. It also updates all callers accordingly. --- txscript/engine.go | 51 +++++++++++++++++++++++++++++++++++++++++- txscript/opcode.go | 55 ---------------------------------------------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index ddb26de58f..a2dfad4c32 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -225,6 +225,55 @@ func isOpcodeConditional(opcode byte) bool { } } +// checkMinimalDataPush returns whether or not the provided opcode is the +// smallest possible way to represent the given data. For example, the value 15 +// could be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is +// a single opcode that represents the same value and is only a single byte +// versus two bytes. +func checkMinimalDataPush(op *opcode, data []byte) error { + opcodeVal := op.value + dataLen := len(data) + switch { + case dataLen == 0 && opcodeVal != OP_0: + str := fmt.Sprintf("zero length data push is encoded with opcode %s "+ + "instead of OP_0", op.name) + return scriptError(ErrMinimalData, str) + case dataLen == 1 && data[0] >= 1 && data[0] <= 16: + if opcodeVal != OP_1+data[0]-1 { + // Should have used OP_1 .. OP_16 + str := fmt.Sprintf("data push of the value %d encoded with opcode "+ + "%s instead of OP_%d", data[0], op.name, data[0]) + return scriptError(ErrMinimalData, str) + } + case dataLen == 1 && data[0] == 0x81: + if opcodeVal != OP_1NEGATE { + str := fmt.Sprintf("data push of the value -1 encoded with opcode "+ + "%s instead of OP_1NEGATE", op.name) + return scriptError(ErrMinimalData, str) + } + case dataLen <= 75: + if int(opcodeVal) != dataLen { + // Should have used a direct push + str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+ + "instead of OP_DATA_%d", dataLen, op.name, dataLen) + return scriptError(ErrMinimalData, str) + } + case dataLen <= 255: + if opcodeVal != OP_PUSHDATA1 { + str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+ + "instead of OP_PUSHDATA1", dataLen, op.name) + return scriptError(ErrMinimalData, str) + } + case dataLen <= 65535: + if opcodeVal != OP_PUSHDATA2 { + str := fmt.Sprintf("data push of %d bytes encoded with opcode %s "+ + "instead of OP_PUSHDATA2", dataLen, op.name) + return scriptError(ErrMinimalData, str) + } + } + return nil +} + // executeOpcode peforms execution on the passed opcode. It takes into account // whether or not it is hidden by conditionals, but some rules still must be // tested in this case. @@ -269,7 +318,7 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error { if vm.dstack.verifyMinimalData && vm.isBranchExecuting() && pop.opcode.value >= 0 && pop.opcode.value <= OP_PUSHDATA4 { - if err := pop.checkMinimalDataPush(); err != nil { + if err := checkMinimalDataPush(pop.opcode, pop.data); err != nil { return err } } diff --git a/txscript/opcode.go b/txscript/opcode.go index 7705f59b02..95b4758055 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -692,61 +692,6 @@ func (pop *parsedOpcode) checkParseableInScript(script []byte, scriptPos int) (i return scriptPos, nil } -// checkMinimalDataPush returns whether or not the current data push uses the -// smallest possible opcode to represent it. For example, the value 15 could -// be pushed with OP_DATA_1 15 (among other variations); however, OP_15 is a -// single opcode that represents the same value and is only a single byte versus -// two bytes. -func (pop *parsedOpcode) checkMinimalDataPush() error { - data := pop.data - dataLen := len(data) - opcode := pop.opcode.value - - if dataLen == 0 && opcode != OP_0 { - str := fmt.Sprintf("zero length data push is encoded with "+ - "opcode %s instead of OP_0", pop.opcode.name) - return scriptError(ErrMinimalData, str) - } else if dataLen == 1 && data[0] >= 1 && data[0] <= 16 { - if opcode != OP_1+data[0]-1 { - // Should have used OP_1 .. OP_16 - str := fmt.Sprintf("data push of the value %d encoded "+ - "with opcode %s instead of OP_%d", data[0], - pop.opcode.name, data[0]) - return scriptError(ErrMinimalData, str) - } - } else if dataLen == 1 && data[0] == 0x81 { - if opcode != OP_1NEGATE { - str := fmt.Sprintf("data push of the value -1 encoded "+ - "with opcode %s instead of OP_1NEGATE", - pop.opcode.name) - return scriptError(ErrMinimalData, str) - } - } else if dataLen <= 75 { - if int(opcode) != dataLen { - // Should have used a direct push - str := fmt.Sprintf("data push of %d bytes encoded "+ - "with opcode %s instead of OP_DATA_%d", dataLen, - pop.opcode.name, dataLen) - return scriptError(ErrMinimalData, str) - } - } else if dataLen <= 255 { - if opcode != OP_PUSHDATA1 { - str := fmt.Sprintf("data push of %d bytes encoded "+ - "with opcode %s instead of OP_PUSHDATA1", - dataLen, pop.opcode.name) - return scriptError(ErrMinimalData, str) - } - } else if dataLen <= 65535 { - if opcode != OP_PUSHDATA2 { - str := fmt.Sprintf("data push of %d bytes encoded "+ - "with opcode %s instead of OP_PUSHDATA2", - dataLen, pop.opcode.name) - return scriptError(ErrMinimalData, str) - } - } - return nil -} - // disasmOpcode writes a human-readable disassembly of the provided opcode and // data into the provided buffer. The compact flag indicates the disassembly // should print a more compact representation of data-carrying and small integer From 54036e8bab1d8c8a596b2afbf2b0f4641b69a49f Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:56 -0500 Subject: [PATCH 093/108] txscript: Convert to use non-parsed opcode disasm. This converts the engine's current program counter disasembly to make use of the standalone disassembly function to remove the dependency on the parsed opcode struct. It also updates the tests accordingly. --- txscript/engine.go | 7 +++++-- txscript/opcode.go | 8 -------- txscript/opcode_test.go | 10 ++++++---- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index a2dfad4c32..191de29f5c 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -10,6 +10,7 @@ import ( "crypto/sha256" "fmt" "math/big" + "strings" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" @@ -331,8 +332,10 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error { // provided position in the script. It does no error checking and leaves that // to the caller to provide a valid offset. func (vm *Engine) disasm(scriptIdx int, scriptOff int) string { - return fmt.Sprintf("%02x:%04x: %s", scriptIdx, scriptOff, - vm.scripts[scriptIdx][scriptOff].print(false)) + var buf strings.Builder + pop := vm.scripts[scriptIdx][scriptOff] + disasmOpcode(&buf, pop.opcode, pop.data, false) + return fmt.Sprintf("%02x:%04x: %s", scriptIdx, scriptOff, buf.String()) } // validPC returns an error if the current script position is valid for diff --git a/txscript/opcode.go b/txscript/opcode.go index 95b4758055..a0d050529a 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -740,14 +740,6 @@ func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) { buf.WriteString(fmt.Sprintf(" 0x%02x", data)) } -// print returns a human-readable string representation of the opcode for use -// in script disassembly. -func (pop *parsedOpcode) print(compact bool) string { - var buf strings.Builder - disasmOpcode(&buf, pop.opcode, pop.data, compact) - return buf.String() -} - // bytes returns any data associated with the opcode encoded as it would be in // a script. This is used for unparsing scripts from parsed opcodes. func (pop *parsedOpcode) bytes() ([]byte, error) { diff --git a/txscript/opcode_test.go b/txscript/opcode_test.go index 1487dde590..3c5abf9da9 100644 --- a/txscript/opcode_test.go +++ b/txscript/opcode_test.go @@ -127,8 +127,9 @@ func TestOpcodeDisasm(t *testing.T) { expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) } - pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data} - gotStr := pop.print(true) + var buf strings.Builder + disasmOpcode(&buf, &opcodeArray[opcodeVal], data, true) + gotStr := buf.String() if gotStr != expectedStr { t.Errorf("pop.print (opcode %x): Unexpected disasm "+ "string - got %v, want %v", opcodeVal, gotStr, @@ -193,8 +194,9 @@ func TestOpcodeDisasm(t *testing.T) { expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) } - pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: data} - gotStr := pop.print(false) + var buf strings.Builder + disasmOpcode(&buf, &opcodeArray[opcodeVal], data, false) + gotStr := buf.String() if gotStr != expectedStr { t.Errorf("pop.print (opcode %x): Unexpected disasm "+ "string - got %v, want %v", opcodeVal, gotStr, From d6b968c3ea24a55b51af05f764975e782b9bb677 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:58 -0500 Subject: [PATCH 094/108] txscript: Refactor engine to use raw scripts. This refactors the script engine to store and step through raw scripts by making using of the new zero-allocation script tokenizer as opposed to the less efficient method of storing and stepping through parsed opcodes. It also improves several aspects while refactoring such as optimizing the disassembly trace, showing all scripts in the trace in the case of execution failure, and providing additional comments describing the purpose of each field in the engine. It should be noted that this is a step towards removing the parsed opcode struct and associated supporting code altogether, however, in order to ease the review process, this retains the struct and all function signatures for opcode execution which make use of an individual parsed opcode. Those will be updated in future commits. The following is an overview of the changes: - Modify internal engine scripts slice to use raw scripts instead of parsed opcodes - Introduce a tokenizer to the engine to track the current script - Remove no longer needed script offset parameter from the engine since that is tracked by the tokenizer - Add an opcode index counter for disassembly purposes to the engine - Update check for valid program counter to only consider the script index - Update tests for bad program counter accordingly - Rework the NewEngine function - Store the raw scripts - Setup the initial tokenizer - Explicitly check against version 0 instead of DefaultScriptVersion which would break consensus if changed - Check the scripts parse according to version 0 semantics to retain current consensus rules - Improve comments throughout - Rework the Step function - Use the tokenizer and raw scripts - Create a parsed opcode on the fly for now to retain existing opcode execution function signatures - Improve comments throughout - Update the Execute function - Explicitly check against version 0 instead of DefaultScriptVersion which would break consensus if changed - Improve the disassembly tracing in the case of error - Update the CheckErrorCondition function - Modify clean stack error message to make sense in all cases - Improve the comments - Update the DisasmPC and DisasmScript functions on the engine - Use the tokenizer - Optimize construction via the use of strings.Builder - Modify the subScript function to return the raw script bytes since the parsed opcodes are no longer stored - Update the various signature checking opcodes to use the raw opcode data removal and signature hash calculation functions since the subscript is now a raw script - opcodeCheckSig - opcodeCheckMultiSig - opcodeCheckSigAlt --- txscript/engine.go | 373 +++++++++++++++++++++++++++------------- txscript/engine_test.go | 19 +- txscript/opcode.go | 20 +-- 3 files changed, 266 insertions(+), 146 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 191de29f5c..1afb8324dd 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -119,21 +119,84 @@ var halfOrder = new(big.Int).Rsh(btcec.S256().N, 1) // Engine is the virtual machine that executes scripts. type Engine struct { - scripts [][]parsedOpcode + // The following fields are set when the engine is created and must not be + // changed afterwards. The entries of the signature cache are mutated + // during execution, however, the cache pointer itself is not changed. + // + // flags specifies the additional flags which modify the execution behavior + // of the engine. + // + // tx identifies the transaction that contains the input which in turn + // contains the signature script being executed. + // + // txIdx identifies the input index within the transaction that contains + // the signature script being executed. + // + // version specifies the version of the public key script to execute. Since + // signature scripts redeem public keys scripts, this means the same version + // also extends to signature scripts and redeem scripts in the case of + // pay-to-script-hash. + // + // bip16 specifies that the public key script is of a special form that + // indicates it is a BIP16 pay-to-script-hash and therefore the + // execution must be treated as such. + // + // sigCache caches the results of signature verifications. This is useful + // since transaction scripts are often executed more than once from various + // contexts (e.g. new block templates, when transactions are first seen + // prior to being mined, part of full block verification, etc). + flags ScriptFlags + tx wire.MsgTx + txIdx int + version uint16 + bip16 bool + sigCache *SigCache + hashCache *TxSigHashes + + // The following fields handle keeping track of the current execution state + // of the engine. + // + // scripts houses the raw scripts that are executed by the engine. This + // includes the signature script as well as the public key script. It also + // includes the redeem script in the case of pay-to-script-hash. + // + // scriptIdx tracks the index into the scripts array for the current program + // counter. + // + // opcodeIdx tracks the number of the opcode within the current script for + // the current program counter. Note that it differs from the actual byte + // index into the script and is really only used for disassembly purposes. + // + // lastCodeSep specifies the position within the current script of the last + // OP_CODESEPARATOR. + // + // tokenizer provides the token stream of the current script being executed + // and doubles as state tracking for the program counter within the script. + // + // savedFirstStack keeps a copy of the stack from the first script when + // performing pay-to-script-hash execution. + // + // dstack is the primary data stack the various opcodes push and pop data + // to and from during execution. + // + // astack is the alternate data stack the various opcodes push and pop data + // to and from during execution. + // + // condStack tracks the conditional execution state with support for + // multiple nested conditional execution opcodes. + // + // numOps tracks the total number of non-push operations in a script and is + // primarily used to enforce maximum limits. + scripts [][]byte scriptIdx int - scriptOff int + opcodeIdx int lastCodeSep int - dstack stack // data stack - astack stack // alt stack - tx wire.MsgTx - txIdx int + tokenizer ScriptTokenizer + savedFirstStack [][]byte + dstack stack + astack stack condStack []int numOps int - flags ScriptFlags - sigCache *SigCache - hashCache *TxSigHashes - bip16 bool // treat execution as pay-to-script-hash - savedFirstStack [][]byte // stack from first script for bip16 scripts witnessVersion int witnessProgram []byte inputAmount int64 @@ -327,44 +390,17 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error { return pop.opcode.opfunc(pop, vm) } -// disasm is a helper function to produce the output for DisasmPC and -// DisasmScript. It produces the opcode prefixed by the program counter at the -// provided position in the script. It does no error checking and leaves that -// to the caller to provide a valid offset. -func (vm *Engine) disasm(scriptIdx int, scriptOff int) string { - var buf strings.Builder - pop := vm.scripts[scriptIdx][scriptOff] - disasmOpcode(&buf, pop.opcode, pop.data, false) - return fmt.Sprintf("%02x:%04x: %s", scriptIdx, scriptOff, buf.String()) -} - -// validPC returns an error if the current script position is valid for -// execution, nil otherwise. -func (vm *Engine) validPC() error { +// checkValidPC returns an error if the current script position is not valid for +// execution. +func (vm *Engine) checkValidPC() error { if vm.scriptIdx >= len(vm.scripts) { - str := fmt.Sprintf("past input scripts %v:%v %v:xxxx", - vm.scriptIdx, vm.scriptOff, len(vm.scripts)) - return scriptError(ErrInvalidProgramCounter, str) - } - if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) { - str := fmt.Sprintf("past input scripts %v:%v %v:%04d", - vm.scriptIdx, vm.scriptOff, vm.scriptIdx, - len(vm.scripts[vm.scriptIdx])) + str := fmt.Sprintf("script index %d beyond total scripts %d", + vm.scriptIdx, len(vm.scripts)) return scriptError(ErrInvalidProgramCounter, str) } return nil } -// curPC returns either the current script and offset, or an error if the -// position isn't valid. -func (vm *Engine) curPC() (script int, off int, err error) { - err = vm.validPC() - if err != nil { - return 0, 0, err - } - return vm.scriptIdx, vm.scriptOff, nil -} - // isWitnessVersionActive returns true if a witness program was extracted // during the initialization of the Engine, and the program's version matches // the specified version. @@ -392,7 +428,9 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error { if err != nil { return err } - pops, err := parseScript(pkScript) + + const scriptVersion = 0 + err = checkScriptParses(vm.version, pkScript) if err != nil { return err } @@ -400,7 +438,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error { // Set the stack to the provided witness stack, then // append the pkScript generated above as the next // script to execute. - vm.scripts = append(vm.scripts, pops) + vm.scripts = append(vm.scripts, pkScript) vm.SetStack(witness) case payToWitnessScriptHashDataSize: // P2WSH @@ -430,10 +468,10 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error { "witness program hash mismatch") } - // With all the validity checks passed, parse the - // script into individual op-codes so w can execute it - // as the next script. - pops, err := parseScript(witnessScript) + // With all the validity checks passed, assert that the + // script parses without failure. + const scriptVersion = 0 + err := checkScriptParses(vm.version, witnessScript) if err != nil { return err } @@ -441,7 +479,7 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error { // The hash matched successfully, so use the witness as // the stack, and set the witnessScript to be the next // script executed. - vm.scripts = append(vm.scripts, pops) + vm.scripts = append(vm.scripts, witnessScript) vm.SetStack(witness[:len(witness)-1]) default: @@ -482,18 +520,50 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error { } // DisasmPC returns the string for the disassembly of the opcode that will be -// next to execute when Step() is called. +// next to execute when Step is called. func (vm *Engine) DisasmPC() (string, error) { - scriptIdx, scriptOff, err := vm.curPC() - if err != nil { + if err := vm.checkValidPC(); err != nil { return "", err } - return vm.disasm(scriptIdx, scriptOff), nil + + // Create a copy of the current tokenizer and parse the next opcode in the + // copy to avoid mutating the current one. + peekTokenizer := vm.tokenizer + if !peekTokenizer.Next() { + // Note that due to the fact that all scripts are checked for parse + // failures before this code ever runs, there should never be an error + // here, but check again to be safe in case a refactor breaks that + // assumption or new script versions are introduced with different + // semantics. + if err := peekTokenizer.Err(); err != nil { + return "", err + } + + // Note that this should be impossible to hit in practice because the + // only way it could happen would be for the final opcode of a script to + // already be parsed without the script index having been updated, which + // is not the case since stepping the script always increments the + // script index when parsing and executing the final opcode of a script. + // + // However, check again to be safe in case a refactor breaks that + // assumption or new script versions are introduced with different + // semantics. + str := fmt.Sprintf("program counter beyond script index %d (bytes %x)", + vm.scriptIdx, vm.scripts[vm.scriptIdx]) + return "", scriptError(ErrInvalidProgramCounter, str) + } + + var buf strings.Builder + disasmOpcode(&buf, peekTokenizer.op, peekTokenizer.Data(), false) + return fmt.Sprintf("%02x:%04x: %s", vm.scriptIdx, vm.opcodeIdx, + buf.String()), nil } // DisasmScript returns the disassembly string for the script at the requested // offset index. Index 0 is the signature script and 1 is the public key -// script. +// script. In the case of pay-to-script-hash, index 2 is the redeem script once +// the execution has progressed far enough to have successfully verified script +// hash and thus add the script to the scripts to execute. func (vm *Engine) DisasmScript(idx int) (string, error) { if idx >= len(vm.scripts) { str := fmt.Sprintf("script index %d >= total scripts %d", idx, @@ -501,19 +571,25 @@ func (vm *Engine) DisasmScript(idx int) (string, error) { return "", scriptError(ErrInvalidIndex, str) } - var disstr string - for i := range vm.scripts[idx] { - disstr = disstr + vm.disasm(idx, i) + "\n" + var disbuf strings.Builder + script := vm.scripts[idx] + tokenizer := MakeScriptTokenizer(vm.version, script) + var opcodeIdx int + for tokenizer.Next() { + disbuf.WriteString(fmt.Sprintf("%02x:%04x: ", idx, opcodeIdx)) + disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), false) + disbuf.WriteByte('\n') + opcodeIdx++ } - return disstr, nil + return disbuf.String(), tokenizer.Err() } // CheckErrorCondition returns nil if the running script has ended and was // successful, leaving a a true boolean on the stack. An error otherwise, // including if the script has not finished. func (vm *Engine) CheckErrorCondition(finalScript bool) error { - // Check execution is actually done. When pc is past the end of script - // array there are no more scripts to run. + // Check execution is actually done by ensuring the script index is after + // the final script in the array script. if vm.scriptIdx < len(vm.scripts) { return scriptError(ErrScriptUnfinished, "error check when script unfinished") @@ -527,11 +603,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error { "have clean stack") } + // The final script must end with exactly one data stack item when the + // verify clean stack flag is set. Otherwise, there must be at least one + // data stack item in order to interpret it as a boolean. if finalScript && vm.hasFlag(ScriptVerifyCleanStack) && vm.dstack.Depth() != 1 { - str := fmt.Sprintf("stack contains %d unexpected items", - vm.dstack.Depth()-1) + str := fmt.Sprintf("stack must contain exactly one item (contains %d)", + vm.dstack.Depth()) return scriptError(ErrCleanStack, str) } else if vm.dstack.Depth() < 1 { return scriptError(ErrEmptyStack, @@ -545,10 +624,14 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error { if !v { // Log interesting data. log.Tracef("%v", newLogClosure(func() string { - dis0, _ := vm.DisasmScript(0) - dis1, _ := vm.DisasmScript(1) - return fmt.Sprintf("scripts failed: script0: %s\n"+ - "script1: %s", dis0, dis1) + var buf strings.Builder + buf.WriteString("scripts failed:\n") + for i := range vm.scripts { + dis, _ := vm.DisasmScript(i) + buf.WriteString(fmt.Sprintf("script%d:\n", i)) + buf.WriteString(dis) + } + return buf.String() })) return scriptError(ErrEvalFalse, "false stack entry at end of script execution") @@ -556,25 +639,39 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error { return nil } -// Step will execute the next instruction and move the program counter to the -// next opcode in the script, or the next script if the current has ended. Step -// will return true in the case that the last opcode was successfully executed. +// Step executes the next instruction and moves the program counter to the next +// opcode in the script, or the next script if the current has ended. Step will +// return true in the case that the last opcode was successfully executed. // // The result of calling Step or any other method is undefined if an error is // returned. func (vm *Engine) Step() (done bool, err error) { - // Verify that it is pointing to a valid script address. - err = vm.validPC() - if err != nil { + // Verify the engine is pointing to a valid program counter. + if err := vm.checkValidPC(); err != nil { return true, err } - opcode := &vm.scripts[vm.scriptIdx][vm.scriptOff] - vm.scriptOff++ + + // Attempt to parse the next opcode from the current script. + if !vm.tokenizer.Next() { + // Note that due to the fact that all scripts are checked for parse + // failures before this code ever runs, there should never be an error + // here, but check again to be safe in case a refactor breaks that + // assumption or new script versions are introduced with different + // semantics. + if err := vm.tokenizer.Err(); err != nil { + return false, err + } + + str := fmt.Sprintf("attempt to step beyond script index %d (bytes %x)", + vm.scriptIdx, vm.scripts[vm.scriptIdx]) + return true, scriptError(ErrInvalidProgramCounter, str) + } // Execute the opcode while taking into account several things such as - // disabled opcodes, illegal opcodes, maximum allowed operations per - // script, maximum script element sizes, and conditionals. - err = vm.executeOpcode(opcode) + // disabled opcodes, illegal opcodes, maximum allowed operations per script, + // maximum script element sizes, and conditionals. + pop := parsedOpcode{opcode: vm.tokenizer.op, data: vm.tokenizer.Data()} + err = vm.executeOpcode(&pop) if err != nil { return true, err } @@ -589,43 +686,53 @@ func (vm *Engine) Step() (done bool, err error) { } // Prepare for next instruction. - if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) { - // Illegal to have an `if' that straddles two scripts. - if err == nil && len(vm.condStack) != 0 { + vm.opcodeIdx++ + if vm.tokenizer.Done() { + // Illegal to have a conditional that straddles two scripts. + if len(vm.condStack) != 0 { return false, scriptError(ErrUnbalancedConditional, "end of script reached in conditional execution") } - // Alt stack doesn't persist. + // Alt stack doesn't persist between scripts. _ = vm.astack.DropN(vm.astack.Depth()) - vm.numOps = 0 // number of ops is per script. - vm.scriptOff = 0 - if vm.scriptIdx == 0 && vm.bip16 { + // The number of operations is per script. + vm.numOps = 0 + + // Reset the opcode index for the next script. + vm.opcodeIdx = 0 + + // Advance to the next script as needed. + switch { + case vm.scriptIdx == 0 && vm.bip16: vm.scriptIdx++ vm.savedFirstStack = vm.GetStack() - } else if vm.scriptIdx == 1 && vm.bip16 { + + case vm.scriptIdx == 1 && vm.bip16: // Put us past the end for CheckErrorCondition() vm.scriptIdx++ - // Check script ran successfully and pull the script - // out of the first stack and execute that. + + // Check script ran successfully. err := vm.CheckErrorCondition(false) if err != nil { return false, err } + // Obtain the redeem script from the first stack and ensure it + // parses. script := vm.savedFirstStack[len(vm.savedFirstStack)-1] - pops, err := parseScript(script) - if err != nil { + if err := checkScriptParses(vm.version, script); err != nil { return false, err } - vm.scripts = append(vm.scripts, pops) + vm.scripts = append(vm.scripts, script) - // Set stack to be the stack from first script minus the + // Set stack to be the stack from first script minus the redeem // script itself vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1]) - } else if (vm.scriptIdx == 1 && vm.witnessProgram != nil) || - (vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16) { // Nested P2SH. + + case vm.scriptIdx == 1 && vm.witnessProgram != nil, + vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16: // np2sh vm.scriptIdx++ @@ -633,30 +740,46 @@ func (vm *Engine) Step() (done bool, err error) { if err := vm.verifyWitnessProgram(witness); err != nil { return false, err } - } else { + + default: vm.scriptIdx++ } - // there are zero length scripts in the wild - if vm.scriptIdx < len(vm.scripts) && vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) { + + // Skip empty scripts. + if vm.scriptIdx < len(vm.scripts) && len(vm.scripts[vm.scriptIdx]) == 0 { vm.scriptIdx++ } + vm.lastCodeSep = 0 if vm.scriptIdx >= len(vm.scripts) { return true, nil } + + // Finally, update the current tokenizer used to parse through scripts + // one opcode at a time to start from the beginning of the new script + // associated with the program counter. + vm.tokenizer = MakeScriptTokenizer(vm.version, vm.scripts[vm.scriptIdx]) } + return false, nil } // Execute will execute all scripts in the script engine and return either nil // for successful validation or an error if one occurred. func (vm *Engine) Execute() (err error) { + // All script versions other than 0 currently execute without issue, + // making all outputs to them anyone can pay. In the future this + // will allow for the addition of new scripting languages. + if vm.version != 0 { + return nil + } + done := false for !done { log.Tracef("%v", newLogClosure(func() string { dis, err := vm.DisasmPC() if err != nil { - return fmt.Sprintf("stepping (%v)", err) + return fmt.Sprintf("stepping - failed to disasm pc: %v", err) } return fmt.Sprintf("stepping %v", dis) })) @@ -668,7 +791,7 @@ func (vm *Engine) Execute() (err error) { log.Tracef("%v", newLogClosure(func() string { var dstr, astr string - // if we're tracing, dump the stacks. + // Log the non-empty stacks when tracing. if vm.dstack.Depth() != 0 { dstr = "Stack:\n" + vm.dstack.String() } @@ -684,7 +807,7 @@ func (vm *Engine) Execute() (err error) { } // subScript returns the script since the last OP_CODESEPARATOR. -func (vm *Engine) subScript() []parsedOpcode { +func (vm *Engine) subScript() []byte { return vm.scripts[vm.scriptIdx][vm.lastCodeSep:] } @@ -1008,10 +1131,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags } scriptSig := tx.TxIn[txIdx].SignatureScript - // When both the signature script and public key script are empty the - // result is necessarily an error since the stack would end up being - // empty which is equivalent to a false top element. Thus, just return - // the relevant error now as an optimization. + // When both the signature script and public key script are empty the result + // is necessarily an error since the stack would end up being empty which is + // equivalent to a false top element. Thus, just return the relevant error + // now as an optimization. if len(scriptSig) == 0 && len(scriptPubKey) == 0 { return nil, scriptError(ErrEvalFalse, "false stack entry at end of script execution") @@ -1057,29 +1180,28 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags vm.bip16 = true } - // The engine stores the scripts in parsed form using a slice. This - // allows multiple scripts to be executed in sequence. For example, - // with a pay-to-script-hash transaction, there will be ultimately be - // a third script to execute. + // The engine stores the scripts using a slice. This allows multiple + // scripts to be executed in sequence. For example, with a + // pay-to-script-hash transaction, there will be ultimately be a third + // script to execute. scripts := [][]byte{scriptSig, scriptPubKey} - vm.scripts = make([][]parsedOpcode, len(scripts)) - for i, scr := range scripts { + for _, scr := range scripts { if len(scr) > MaxScriptSize { - str := fmt.Sprintf("script size %d is larger than max "+ - "allowed size %d", len(scr), MaxScriptSize) + str := fmt.Sprintf("script size %d is larger than max allowed "+ + "size %d", len(scr), MaxScriptSize) return nil, scriptError(ErrScriptTooBig, str) } - var err error - vm.scripts[i], err = parseScript(scr) - if err != nil { + + const scriptVersion = 0 + if err := checkScriptParses(scriptVersion, scr); err != nil { return nil, err } } + vm.scripts = scripts // Advance the program counter to the public key script if the signature - // script is empty since there is nothing to execute for it in that - // case. - if len(scripts[0]) == 0 { + // script is empty since there is nothing to execute for it in that case. + if len(scriptSig) == 0 { vm.scriptIdx++ } if vm.hasFlag(ScriptVerifyMinimalData) { @@ -1103,7 +1225,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags var witProgram []byte switch { - case isWitnessProgram(vm.scripts[1]): + case IsWitnessProgram(vm.scripts[1]): // The scriptSig must be *empty* for all native witness // programs, otherwise we introduce malleability. if len(scriptSig) != 0 { @@ -1118,12 +1240,11 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags // data push of the witness program, otherwise we // reintroduce malleability. sigPops := vm.scripts[0] - if len(sigPops) == 1 && - isCanonicalPush(sigPops[0].opcode.value, - sigPops[0].data) && - IsWitnessProgram(sigPops[0].data) { + if len(sigPops) > 2 && + isCanonicalPush(sigPops[0], sigPops[1:]) && + IsWitnessProgram(sigPops[1:]) { - witProgram = sigPops[0].data + witProgram = sigPops[1:] } else { errStr := "signature script for witness " + "nested p2sh is not canonical" @@ -1150,6 +1271,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags } + // 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]) + vm.tx = *tx vm.txIdx = txIdx diff --git a/txscript/engine_test.go b/txscript/engine_test.go index 2e8c522c1a..5818080dfd 100644 --- a/txscript/engine_test.go +++ b/txscript/engine_test.go @@ -1,4 +1,5 @@ // Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2015-2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -11,16 +12,16 @@ import ( "github.com/btcsuite/btcd/wire" ) -// TestBadPC sets the pc to a deliberately bad result then confirms that Step() +// TestBadPC sets the pc to a deliberately bad result then confirms that Step // and Disasm fail correctly. func TestBadPC(t *testing.T) { t.Parallel() tests := []struct { - script, off int + scriptIdx int }{ - {script: 2, off: 0}, - {script: 0, off: 2}, + {scriptIdx: 2}, + {scriptIdx: 3}, } // tx with almost empty scripts. @@ -59,20 +60,20 @@ func TestBadPC(t *testing.T) { t.Errorf("Failed to create script: %v", err) } - // set to after all scripts - vm.scriptIdx = test.script - vm.scriptOff = test.off + // Set to after all scripts. + vm.scriptIdx = test.scriptIdx + // Ensure attempting to step fails. _, err = vm.Step() if err == nil { t.Errorf("Step with invalid pc (%v) succeeds!", test) continue } + // Ensure attempting to disassemble the current program counter fails. _, err = vm.DisasmPC() if err == nil { - t.Errorf("DisasmPC with invalid pc (%v) succeeds!", - test) + t.Errorf("DisasmPC with invalid pc (%v) succeeds!", test) } } } diff --git a/txscript/opcode.go b/txscript/opcode.go index a0d050529a..950121c69d 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -1981,7 +1981,7 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error { // // This opcode does not change the contents of the data stack. func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error { - vm.lastCodeSep = vm.scriptOff + vm.lastCodeSep = int(vm.tokenizer.ByteIndex()) return nil } @@ -2055,7 +2055,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { sigHashes = NewTxSigHashes(&vm.tx) } - hash, err = calcWitnessSignatureHash(subScript, sigHashes, hashType, + hash, err = calcWitnessSignatureHashRaw(subScript, sigHashes, hashType, &vm.tx, vm.txIdx, vm.inputAmount) if err != nil { return err @@ -2063,12 +2063,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { } else { // Remove the signature since there is no way for a signature // to sign itself. - subScript = removeOpcodeByData(subScript, fullSigBytes) + subScript = removeOpcodeByDataRaw(subScript, fullSigBytes) - hash, err = calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx) - if err != nil { - return err - } + hash = calcSignatureHashRaw(subScript, hashType, &vm.tx, vm.txIdx) } pubKey, err := btcec.ParsePubKey(pkBytes, btcec.S256()) @@ -2239,7 +2236,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { // no way for a signature to sign itself. if !vm.isWitnessVersionActive(0) { for _, sigInfo := range signatures { - script = removeOpcodeByData(script, sigInfo.signature) + script = removeOpcodeByDataRaw(script, sigInfo.signature) } } @@ -2331,16 +2328,13 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { sigHashes = NewTxSigHashes(&vm.tx) } - hash, err = calcWitnessSignatureHash(script, sigHashes, hashType, + hash, err = calcWitnessSignatureHashRaw(script, sigHashes, hashType, &vm.tx, vm.txIdx, vm.inputAmount) if err != nil { return err } } else { - hash, err = calcSignatureHash(script, hashType, &vm.tx, vm.txIdx) - if err != nil { - return err - } + hash = calcSignatureHashRaw(script, hashType, &vm.tx, vm.txIdx) } var valid bool From 06c8bea6c76f73f8afa214a99d45fe9c65a20d2d Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:05:20 -0700 Subject: [PATCH 095/108] txscript: Remove unused calcSignatureHash --- txscript/script.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 0be0a7b8b3..8ff39e209a 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -744,21 +744,6 @@ func calcSignatureHashRaw(sigScript []byte, hashType SigHashType, tx *wire.MsgTx return chainhash.DoubleHashB(wbuf.Bytes()) } -// calcSignatureHash computes the signature hash for the specified input of the -// target transaction observing the desired signature hash type. -// -// DEPRECATED: Use calcSignatureHashRaw instead -func calcSignatureHash(prevOutScript []parsedOpcode, hashType SigHashType, - tx *wire.MsgTx, idx int) ([]byte, error) { - - sigScript, err := unparseScript(prevOutScript) - if err != nil { - return nil, err - } - - return calcSignatureHashRaw(sigScript, hashType, tx, idx), nil -} - // asSmallInt returns the passed opcode, which must be true according to // isSmallInt(), as an integer. func asSmallInt(op byte) int { From 03d1fb0f86ac8dc65c6e67b31069630fce70fb69 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 20:09:59 -0700 Subject: [PATCH 096/108] txscript: Remove unused isWitnessProgram --- txscript/script.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 8ff39e209a..6557c64d1e 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -98,21 +98,6 @@ func IsWitnessProgram(script []byte) bool { return isWitnessProgramScript(script) } -// isWitnessProgram returns true if the passed script is a witness program, and -// false otherwise. A witness program MUST adhere to the following constraints: -// there must be exactly two pops (program version and the program itself), the -// first opcode MUST be a small integer (0-16), the push data MUST be -// canonical, and finally the size of the push data must be between 2 and 40 -// bytes. -// -// DEPRECATED: Use isWitnessProgramScript instead. -func isWitnessProgram(pops []parsedOpcode) bool { - return len(pops) == 2 && - isSmallInt(pops[0].opcode.value) && - isCanonicalPush(pops[1].opcode.value, pops[1].data) && - (len(pops[1].data) >= 2 && len(pops[1].data) <= 40) -} - // IsNullData returns true if the passed script is a null data script, false // otherwise. func IsNullData(script []byte) bool { From 07ab66b790e0b732868548e0624c62a7b03a2863 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:12:59 -0500 Subject: [PATCH 097/108] txscript: Remove unused removeOpcodeByData func. --- txscript/script.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 6557c64d1e..242fc2d875 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -318,23 +318,6 @@ func isCanonicalPush(opcode byte, data []byte) bool { return true } -// removeOpcodeByData will return the script minus any opcodes that would push -// the passed data to the stack. -// -// DEPRECATED. Use removeOpcodeByDataRaw instead. -func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode { - retScript := make([]parsedOpcode, 0, len(pkscript)) - for _, pop := range pkscript { - if !isCanonicalPush(pop.opcode.value, pop.data) || - !bytes.Contains(pop.data, data) { - - retScript = append(retScript, pop) - } - } - return retScript - -} - // removeOpcodeByDataRaw will return the script minus any opcodes that perform a // canonical push of data that contains the passed data to remove. This // function assumes it is provided a version 0 script as any future version of From 911db90858acff024c54715b5336c95285e695ae Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:13:00 -0500 Subject: [PATCH 098/108] txscript: Rename removeOpcodeByDataRaw func. This renames the removeOpcodeByDataRaw to removeOpcodeByData now that the old version has been removed. --- txscript/opcode.go | 4 ++-- txscript/script.go | 4 ++-- txscript/script_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 950121c69d..1d8ee25ea8 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -2063,7 +2063,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { } else { // Remove the signature since there is no way for a signature // to sign itself. - subScript = removeOpcodeByDataRaw(subScript, fullSigBytes) + subScript = removeOpcodeByData(subScript, fullSigBytes) hash = calcSignatureHashRaw(subScript, hashType, &vm.tx, vm.txIdx) } @@ -2236,7 +2236,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { // no way for a signature to sign itself. if !vm.isWitnessVersionActive(0) { for _, sigInfo := range signatures { - script = removeOpcodeByDataRaw(script, sigInfo.signature) + script = removeOpcodeByData(script, sigInfo.signature) } } diff --git a/txscript/script.go b/txscript/script.go index 242fc2d875..266c36ddce 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -318,7 +318,7 @@ func isCanonicalPush(opcode byte, data []byte) bool { return true } -// removeOpcodeByDataRaw will return the script minus any opcodes that perform a +// removeOpcodeByData will return the script minus any opcodes that perform a // canonical push of data that contains the passed data to remove. This // function assumes it is provided a version 0 script as any future version of // script should avoid this functionality since it is unncessary due to the @@ -332,7 +332,7 @@ func isCanonicalPush(opcode byte, data []byte) bool { // NOTE: This function is only valid for version 0 scripts. Since the function // does not accept a script version, the results are undefined for other script // versions. -func removeOpcodeByDataRaw(script []byte, dataToRemove []byte) []byte { +func removeOpcodeByData(script []byte, dataToRemove []byte) []byte { // Avoid work when possible. if len(script) == 0 || len(dataToRemove) == 0 { return script diff --git a/txscript/script_test.go b/txscript/script_test.go index 02c364ce31..7db065de44 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -4136,7 +4136,7 @@ func TestRemoveOpcodeByData(t *testing.T) { return nil, err } - return removeOpcodeByDataRaw(script, data), nil + return removeOpcodeByData(script, data), nil } for _, test := range tests { From 94e99cf6b7d572ebe46eebc79d04a4a2cb071001 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:07:18 -0700 Subject: [PATCH 099/108] txscript: Rename calcSignatureHashRaw --- txscript/opcode.go | 4 ++-- txscript/script.go | 8 ++++---- txscript/sign.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 1d8ee25ea8..9e0320aed9 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -2065,7 +2065,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { // to sign itself. subScript = removeOpcodeByData(subScript, fullSigBytes) - hash = calcSignatureHashRaw(subScript, hashType, &vm.tx, vm.txIdx) + hash = calcSignatureHash(subScript, hashType, &vm.tx, vm.txIdx) } pubKey, err := btcec.ParsePubKey(pkBytes, btcec.S256()) @@ -2334,7 +2334,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { return err } } else { - hash = calcSignatureHashRaw(script, hashType, &vm.tx, vm.txIdx) + hash = calcSignatureHash(script, hashType, &vm.tx, vm.txIdx) } var valid bool diff --git a/txscript/script.go b/txscript/script.go index 266c36ddce..2e3431d025 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -618,12 +618,12 @@ func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx return nil, err } - return calcSignatureHashRaw(script, hashType, tx, idx), nil + return calcSignatureHash(script, hashType, tx, idx), nil } -// calcSignatureHashRaw computes the signature hash for the specified input of -// the target transaction observing the desired signature hash type. -func calcSignatureHashRaw(sigScript []byte, hashType SigHashType, tx *wire.MsgTx, idx int) []byte { +// calcSignatureHash computes the signature hash for the specified input of the +// target transaction observing the desired signature hash type. +func calcSignatureHash(sigScript []byte, hashType SigHashType, tx *wire.MsgTx, idx int) []byte { // The SigHashSingle signature type signs only the corresponding input // and output (the output with the same index number as the input). // diff --git a/txscript/sign.go b/txscript/sign.go index 4d63a9b245..51c69103cf 100644 --- a/txscript/sign.go +++ b/txscript/sign.go @@ -285,7 +285,7 @@ sigLoop: // however, assume no sigs etc are in the script since that // would make the transaction nonstandard and thus not // MultiSigTy, so we just need to hash the full thing. - hash := calcSignatureHashRaw(pkScript, hashType, tx, idx) + hash := calcSignatureHash(pkScript, hashType, tx, idx) for _, addr := range addresses { // All multisig addresses should be pubkey addresses From 69f3a39c1c96ba024ed332dd12f86f8f5cf7ba33 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:10:48 -0700 Subject: [PATCH 100/108] txscript/sign: Use calcWitnessSigHashRaw for witness sigs --- txscript/sign.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/txscript/sign.go b/txscript/sign.go index 51c69103cf..138e31cdd4 100644 --- a/txscript/sign.go +++ b/txscript/sign.go @@ -22,12 +22,7 @@ func RawTxInWitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int, amt int64, subScript []byte, hashType SigHashType, key *btcec.PrivateKey) ([]byte, error) { - parsedScript, err := parseScript(subScript) - if err != nil { - return nil, fmt.Errorf("cannot parse output script: %v", err) - } - - hash, err := calcWitnessSignatureHash(parsedScript, sigHashes, hashType, tx, + hash, err := calcWitnessSignatureHashRaw(subScript, sigHashes, hashType, tx, idx, amt) if err != nil { return nil, err From 753367299342906c9323cf94a14b151f282c9e4a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:31:33 -0700 Subject: [PATCH 101/108] txscript/pkscript: Use finalOpcodeData to extract redeem script --- txscript/pkscript.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/txscript/pkscript.go b/txscript/pkscript.go index 0703ef5d05..f5b11e6d53 100644 --- a/txscript/pkscript.go +++ b/txscript/pkscript.go @@ -211,11 +211,12 @@ func computeNonWitnessPkScript(sigScript []byte) (PkScript, error) { // The redeem script will always be the last data push of the // signature script, so we'll parse the script into opcodes to // obtain it. - parsedOpcodes, err := parseScript(sigScript) + const scriptVersion = 0 + err := checkScriptParses(scriptVersion, sigScript) if err != nil { return PkScript{}, err } - redeemScript := parsedOpcodes[len(parsedOpcodes)-1].data + redeemScript := finalOpcodeData(scriptVersion, sigScript) scriptHash := hash160(redeemScript) script, err := payToScriptHashScript(scriptHash) From 6e5fbf8ea85ca1689d00421f2966436166c2e072 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:13:03 -0500 Subject: [PATCH 102/108] txscript: Remove unused parseScript func. --- txscript/script.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 2e3431d025..1a8890b914 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -202,12 +202,6 @@ func checkScriptTemplateParseable(script []byte, opcodes *[256]opcode) (*byte, e return &firstOpcode, nil } -// parseScript preparses the script in bytes into a list of parsedOpcodes while -// applying a number of sanity checks. -func parseScript(script []byte) ([]parsedOpcode, error) { - return parseScriptTemplate(script, &opcodeArray) -} - // unparseScript reversed the action of parseScript and returns the // parsedOpcodes as a list of bytes func unparseScript(pops []parsedOpcode) ([]byte, error) { From e06b11a999da91b6e0179795def1f2ee67e3b159 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 19 Apr 2019 19:48:25 -0700 Subject: [PATCH 103/108] txscript: Remove unused calcWitnessSignatureHash --- txscript/script.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 1a8890b914..97c2f40102 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -534,30 +534,6 @@ func calcWitnessSignatureHashRaw(scriptSig []byte, sigHashes *TxSigHashes, return chainhash.DoubleHashB(sigHash.Bytes()), nil } -// calcWitnessSignatureHash computes the sighash digest of a transaction's -// segwit input using the new, optimized digest calculation algorithm defined -// in BIP0143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki. -// This function makes use of pre-calculated sighash fragments stored within -// the passed HashCache to eliminate duplicate hashing computations when -// calculating the final digest, reducing the complexity from O(N^2) to O(N). -// Additionally, signatures now cover the input value of the referenced unspent -// output. This allows offline, or hardware wallets to compute the exact amount -// being spent, in addition to the final transaction fee. In the case the -// wallet if fed an invalid input amount, the real sighash will differ causing -// the produced signature to be invalid. -// -// DEPRECATED: Use calcWitnessSignatureHashRaw instead. -func calcWitnessSignatureHash(subScript []parsedOpcode, sigHashes *TxSigHashes, - hashType SigHashType, tx *wire.MsgTx, idx int, amt int64) ([]byte, error) { - - script, err := unparseScript(subScript) - if err != nil { - return nil, err - } - - return calcWitnessSignatureHashRaw(script, sigHashes, hashType, tx, idx, amt) -} - // CalcWitnessSigHash computes the sighash digest for the specified input of // the target transaction observing the desired sig hash type. func CalcWitnessSigHash(script []byte, sigHashes *TxSigHashes, hType SigHashType, From 491b7b59fc4ad2ed8fb516a0105c2ff44a014397 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:13:04 -0500 Subject: [PATCH 104/108] txscript: Remove unused unparseScript func. Also remove tests associated with unparsing opcodes accordingly. --- txscript/script.go | 14 - txscript/script_test.go | 3651 --------------------------------------- 2 files changed, 3665 deletions(-) diff --git a/txscript/script.go b/txscript/script.go index 97c2f40102..0dbe4683d7 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -202,20 +202,6 @@ func checkScriptTemplateParseable(script []byte, opcodes *[256]opcode) (*byte, e return &firstOpcode, nil } -// unparseScript reversed the action of parseScript and returns the -// parsedOpcodes as a list of bytes -func unparseScript(pops []parsedOpcode) ([]byte, error) { - script := make([]byte, 0, len(pops)) - for _, pop := range pops { - b, err := pop.bytes() - if err != nil { - return nil, err - } - script = append(script, b...) - } - return script, nil -} - // DisasmString formats a disassembled script for one line printing. When the // script fails to parse, the returned string will contain the disassembled // script up to the point the failure occurred along with the string '[error]' diff --git a/txscript/script_test.go b/txscript/script_test.go index 7db065de44..b6e7ff4203 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -28,3657 +28,6 @@ func TestParseOpcode(t *testing.T) { } } -// TestUnparsingInvalidOpcodes tests for errors when unparsing invalid parsed -// opcodes. -func TestUnparsingInvalidOpcodes(t *testing.T) { - tests := []struct { - name string - pop *parsedOpcode - expectedErr error - }{ - { - name: "OP_FALSE", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_FALSE], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_FALSE long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_FALSE], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_1 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_1], - data: nil, - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_1", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_1], - data: make([]byte, 1), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_1 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_1], - data: make([]byte, 2), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_2 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_2], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_2", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_2], - data: make([]byte, 2), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_2 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_2], - data: make([]byte, 3), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_3 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_3], - data: make([]byte, 2), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_3", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_3], - data: make([]byte, 3), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_3 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_3], - data: make([]byte, 4), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_4 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_4], - data: make([]byte, 3), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_4", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_4], - data: make([]byte, 4), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_4 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_4], - data: make([]byte, 5), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_5 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_5], - data: make([]byte, 4), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_5", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_5], - data: make([]byte, 5), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_5 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_5], - data: make([]byte, 6), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_6 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_6], - data: make([]byte, 5), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_6", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_6], - data: make([]byte, 6), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_6 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_6], - data: make([]byte, 7), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_7 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_7], - data: make([]byte, 6), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_7", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_7], - data: make([]byte, 7), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_7 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_7], - data: make([]byte, 8), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_8 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_8], - data: make([]byte, 7), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_8", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_8], - data: make([]byte, 8), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_8 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_8], - data: make([]byte, 9), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_9 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_9], - data: make([]byte, 8), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_9", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_9], - data: make([]byte, 9), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_9 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_9], - data: make([]byte, 10), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_10 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_10], - data: make([]byte, 9), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_10", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_10], - data: make([]byte, 10), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_10 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_10], - data: make([]byte, 11), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_11 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_11], - data: make([]byte, 10), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_11", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_11], - data: make([]byte, 11), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_11 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_11], - data: make([]byte, 12), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_12 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_12], - data: make([]byte, 11), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_12", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_12], - data: make([]byte, 12), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_12 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_12], - data: make([]byte, 13), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_13 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_13], - data: make([]byte, 12), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_13", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_13], - data: make([]byte, 13), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_13 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_13], - data: make([]byte, 14), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_14 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_14], - data: make([]byte, 13), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_14", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_14], - data: make([]byte, 14), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_14 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_14], - data: make([]byte, 15), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_15 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_15], - data: make([]byte, 14), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_15", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_15], - data: make([]byte, 15), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_15 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_15], - data: make([]byte, 16), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_16 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_16], - data: make([]byte, 15), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_16", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_16], - data: make([]byte, 16), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_16 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_16], - data: make([]byte, 17), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_17 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_17], - data: make([]byte, 16), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_17", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_17], - data: make([]byte, 17), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_17 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_17], - data: make([]byte, 18), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_18 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_18], - data: make([]byte, 17), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_18", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_18], - data: make([]byte, 18), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_18 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_18], - data: make([]byte, 19), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_19 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_19], - data: make([]byte, 18), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_19", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_19], - data: make([]byte, 19), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_19 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_19], - data: make([]byte, 20), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_20 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_20], - data: make([]byte, 19), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_20", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_20], - data: make([]byte, 20), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_20 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_20], - data: make([]byte, 21), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_21 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_21], - data: make([]byte, 20), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_21", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_21], - data: make([]byte, 21), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_21 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_21], - data: make([]byte, 22), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_22 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_22], - data: make([]byte, 21), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_22", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_22], - data: make([]byte, 22), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_22 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_22], - data: make([]byte, 23), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_23 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_23], - data: make([]byte, 22), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_23", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_23], - data: make([]byte, 23), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_23 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_23], - data: make([]byte, 24), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_24 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_24], - data: make([]byte, 23), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_24", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_24], - data: make([]byte, 24), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_24 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_24], - data: make([]byte, 25), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_25 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_25], - data: make([]byte, 24), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_25", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_25], - data: make([]byte, 25), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_25 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_25], - data: make([]byte, 26), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_26 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_26], - data: make([]byte, 25), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_26", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_26], - data: make([]byte, 26), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_26 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_26], - data: make([]byte, 27), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_27 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_27], - data: make([]byte, 26), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_27", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_27], - data: make([]byte, 27), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_27 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_27], - data: make([]byte, 28), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_28 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_28], - data: make([]byte, 27), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_28", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_28], - data: make([]byte, 28), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_28 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_28], - data: make([]byte, 29), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_29 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_29], - data: make([]byte, 28), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_29", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_29], - data: make([]byte, 29), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_29 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_29], - data: make([]byte, 30), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_30 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_30], - data: make([]byte, 29), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_30", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_30], - data: make([]byte, 30), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_30 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_30], - data: make([]byte, 31), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_31 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_31], - data: make([]byte, 30), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_31", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_31], - data: make([]byte, 31), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_31 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_31], - data: make([]byte, 32), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_32 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_32], - data: make([]byte, 31), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_32", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_32], - data: make([]byte, 32), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_32 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_32], - data: make([]byte, 33), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_33 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_33], - data: make([]byte, 32), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_33", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_33], - data: make([]byte, 33), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_33 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_33], - data: make([]byte, 34), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_34 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_34], - data: make([]byte, 33), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_34", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_34], - data: make([]byte, 34), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_34 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_34], - data: make([]byte, 35), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_35 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_35], - data: make([]byte, 34), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_35", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_35], - data: make([]byte, 35), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_35 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_35], - data: make([]byte, 36), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_36 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_36], - data: make([]byte, 35), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_36", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_36], - data: make([]byte, 36), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_36 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_36], - data: make([]byte, 37), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_37 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_37], - data: make([]byte, 36), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_37", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_37], - data: make([]byte, 37), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_37 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_37], - data: make([]byte, 38), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_38 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_38], - data: make([]byte, 37), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_38", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_38], - data: make([]byte, 38), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_38 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_38], - data: make([]byte, 39), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_39 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_39], - data: make([]byte, 38), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_39", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_39], - data: make([]byte, 39), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_39 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_39], - data: make([]byte, 40), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_40 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_40], - data: make([]byte, 39), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_40", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_40], - data: make([]byte, 40), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_40 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_40], - data: make([]byte, 41), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_41 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_41], - data: make([]byte, 40), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_41", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_41], - data: make([]byte, 41), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_41 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_41], - data: make([]byte, 42), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_42 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_42], - data: make([]byte, 41), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_42", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_42], - data: make([]byte, 42), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_42 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_42], - data: make([]byte, 43), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_43 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_43], - data: make([]byte, 42), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_43", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_43], - data: make([]byte, 43), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_43 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_43], - data: make([]byte, 44), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_44 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_44], - data: make([]byte, 43), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_44", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_44], - data: make([]byte, 44), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_44 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_44], - data: make([]byte, 45), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_45 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_45], - data: make([]byte, 44), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_45", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_45], - data: make([]byte, 45), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_45 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_45], - data: make([]byte, 46), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_46 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_46], - data: make([]byte, 45), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_46", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_46], - data: make([]byte, 46), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_46 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_46], - data: make([]byte, 47), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_47 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_47], - data: make([]byte, 46), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_47", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_47], - data: make([]byte, 47), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_47 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_47], - data: make([]byte, 48), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_48 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_48], - data: make([]byte, 47), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_48", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_48], - data: make([]byte, 48), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_48 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_48], - data: make([]byte, 49), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_49 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_49], - data: make([]byte, 48), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_49", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_49], - data: make([]byte, 49), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_49 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_49], - data: make([]byte, 50), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_50 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_50], - data: make([]byte, 49), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_50", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_50], - data: make([]byte, 50), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_50 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_50], - data: make([]byte, 51), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_51 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_51], - data: make([]byte, 50), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_51", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_51], - data: make([]byte, 51), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_51 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_51], - data: make([]byte, 52), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_52 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_52], - data: make([]byte, 51), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_52", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_52], - data: make([]byte, 52), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_52 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_52], - data: make([]byte, 53), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_53 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_53], - data: make([]byte, 52), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_53", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_53], - data: make([]byte, 53), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_53 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_53], - data: make([]byte, 54), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_54 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_54], - data: make([]byte, 53), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_54", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_54], - data: make([]byte, 54), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_54 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_54], - data: make([]byte, 55), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_55 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_55], - data: make([]byte, 54), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_55", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_55], - data: make([]byte, 55), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_55 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_55], - data: make([]byte, 56), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_56 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_56], - data: make([]byte, 55), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_56", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_56], - data: make([]byte, 56), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_56 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_56], - data: make([]byte, 57), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_57 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_57], - data: make([]byte, 56), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_57", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_57], - data: make([]byte, 57), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_57 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_57], - data: make([]byte, 58), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_58 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_58], - data: make([]byte, 57), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_58", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_58], - data: make([]byte, 58), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_58 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_58], - data: make([]byte, 59), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_59 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_59], - data: make([]byte, 58), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_59", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_59], - data: make([]byte, 59), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_59 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_59], - data: make([]byte, 60), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_60 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_60], - data: make([]byte, 59), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_60", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_60], - data: make([]byte, 60), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_60 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_60], - data: make([]byte, 61), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_61 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_61], - data: make([]byte, 60), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_61", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_61], - data: make([]byte, 61), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_61 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_61], - data: make([]byte, 62), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_62 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_62], - data: make([]byte, 61), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_62", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_62], - data: make([]byte, 62), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_62 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_62], - data: make([]byte, 63), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_63 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_63], - data: make([]byte, 62), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_63", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_63], - data: make([]byte, 63), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_63 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_63], - data: make([]byte, 64), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_64 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_64], - data: make([]byte, 63), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_64", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_64], - data: make([]byte, 64), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_64 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_64], - data: make([]byte, 65), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_65 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_65], - data: make([]byte, 64), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_65", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_65], - data: make([]byte, 65), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_65 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_65], - data: make([]byte, 66), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_66 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_66], - data: make([]byte, 65), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_66", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_66], - data: make([]byte, 66), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_66 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_66], - data: make([]byte, 67), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_67 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_67], - data: make([]byte, 66), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_67", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_67], - data: make([]byte, 67), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_67 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_67], - data: make([]byte, 68), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_68 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_68], - data: make([]byte, 67), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_68", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_68], - data: make([]byte, 68), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_68 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_68], - data: make([]byte, 69), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_69 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_69], - data: make([]byte, 68), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_69", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_69], - data: make([]byte, 69), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_69 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_69], - data: make([]byte, 70), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_70 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_70], - data: make([]byte, 69), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_70", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_70], - data: make([]byte, 70), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_70 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_70], - data: make([]byte, 71), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_71 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_71], - data: make([]byte, 70), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_71", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_71], - data: make([]byte, 71), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_71 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_71], - data: make([]byte, 72), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_72 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_72], - data: make([]byte, 71), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_72", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_72], - data: make([]byte, 72), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_72 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_72], - data: make([]byte, 73), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_73 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_73], - data: make([]byte, 72), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_73", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_73], - data: make([]byte, 73), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_73 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_73], - data: make([]byte, 74), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_74 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_74], - data: make([]byte, 73), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_74", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_74], - data: make([]byte, 74), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_74 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_74], - data: make([]byte, 75), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_75 short", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_75], - data: make([]byte, 74), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DATA_75", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_75], - data: make([]byte, 75), - }, - expectedErr: nil, - }, - { - name: "OP_DATA_75 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DATA_75], - data: make([]byte, 76), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_PUSHDATA1", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PUSHDATA1], - data: []byte{0, 1, 2, 3, 4}, - }, - expectedErr: nil, - }, - { - name: "OP_PUSHDATA2", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PUSHDATA2], - data: []byte{0, 1, 2, 3, 4}, - }, - expectedErr: nil, - }, - { - name: "OP_PUSHDATA4", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PUSHDATA1], - data: []byte{0, 1, 2, 3, 4}, - }, - expectedErr: nil, - }, - { - name: "OP_1NEGATE", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_1NEGATE], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_1NEGATE long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_1NEGATE], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_RESERVED", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RESERVED], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_RESERVED long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RESERVED], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_TRUE", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_TRUE], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_TRUE long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_TRUE], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_3", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_3], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_3 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_3], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_4", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_4], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_4 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_4], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_5", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_5], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_5 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_5], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_6", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_6], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_6 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_6], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_7", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_7], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_7 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_7], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_8", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_8], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_8 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_8], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_9", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_9], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_9 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_9], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_10", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_10], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_10 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_10], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_11", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_11], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_11 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_11], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_12", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_12], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_12 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_12], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_13", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_13], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_13 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_13], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_14", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_14], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_14 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_14], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_15", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_15], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_15 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_15], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_16", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_16], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_16 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_16], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_VER", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VER], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_VER long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VER], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_IF", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_IF], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_IF long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_IF], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOTIF", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOTIF], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOTIF long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOTIF], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_VERIF", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VERIF], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_VERIF long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VERIF], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_VERNOTIF", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VERNOTIF], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_VERNOTIF long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VERNOTIF], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_ELSE", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ELSE], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_ELSE long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ELSE], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_ENDIF", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ENDIF], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_ENDIF long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ENDIF], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_VERIFY", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VERIFY], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_VERIFY long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_VERIFY], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_RETURN", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RETURN], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_RETURN long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RETURN], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_TOALTSTACK", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_TOALTSTACK], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_TOALTSTACK long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_TOALTSTACK], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_FROMALTSTACK", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_FROMALTSTACK], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_FROMALTSTACK long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_FROMALTSTACK], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2DROP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2DROP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2DROP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2DROP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2DUP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2DUP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2DUP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2DUP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_3DUP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_3DUP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_3DUP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_3DUP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2OVER", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2OVER], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2OVER long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2OVER], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2ROT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2ROT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2ROT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2ROT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2SWAP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2SWAP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2SWAP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2SWAP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_IFDUP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_IFDUP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_IFDUP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_IFDUP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DEPTH", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DEPTH], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_DEPTH long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DEPTH], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DROP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DROP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_DROP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DROP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DUP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DUP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_DUP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DUP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NIP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NIP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NIP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NIP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_OVER", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_OVER], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_OVER long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_OVER], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_PICK", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PICK], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_PICK long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PICK], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_ROLL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ROLL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_ROLL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ROLL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_ROT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ROT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_ROT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ROT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_SWAP", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SWAP], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_SWAP long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SWAP], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_TUCK", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_TUCK], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_TUCK long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_TUCK], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_CAT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CAT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_CAT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CAT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_SUBSTR", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SUBSTR], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_SUBSTR long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SUBSTR], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_LEFT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LEFT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_LEFT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LEFT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_LEFT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LEFT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_LEFT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LEFT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_RIGHT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RIGHT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_RIGHT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RIGHT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_SIZE", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SIZE], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_SIZE long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SIZE], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_INVERT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_INVERT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_INVERT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_INVERT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_AND", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_AND], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_AND long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_AND], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_OR", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_OR], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_OR long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_OR], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_XOR", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_XOR], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_XOR long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_XOR], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_EQUAL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_EQUAL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_EQUAL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_EQUAL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_EQUALVERIFY", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_EQUALVERIFY], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_EQUALVERIFY long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_EQUALVERIFY], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_RESERVED1", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RESERVED1], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_RESERVED1 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RESERVED1], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_RESERVED2", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RESERVED2], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_RESERVED2 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RESERVED2], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_1ADD", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_1ADD], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_1ADD long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_1ADD], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_1SUB", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_1SUB], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_1SUB long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_1SUB], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2MUL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2MUL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2MUL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2MUL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_2DIV", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2DIV], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_2DIV long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_2DIV], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NEGATE", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NEGATE], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NEGATE long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NEGATE], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_ABS", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ABS], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_ABS long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ABS], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_0NOTEQUAL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_0NOTEQUAL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_0NOTEQUAL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_0NOTEQUAL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_ADD", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ADD], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_ADD long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_ADD], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_SUB", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SUB], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_SUB long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SUB], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_MUL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MUL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_MUL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MUL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_DIV", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DIV], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_DIV long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_DIV], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_MOD", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MOD], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_MOD long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MOD], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_LSHIFT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LSHIFT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_LSHIFT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LSHIFT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_RSHIFT", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RSHIFT], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_RSHIFT long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RSHIFT], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_BOOLAND", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_BOOLAND], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_BOOLAND long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_BOOLAND], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_BOOLOR", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_BOOLOR], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_BOOLOR long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_BOOLOR], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NUMEQUAL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NUMEQUAL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NUMEQUAL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NUMEQUAL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NUMEQUALVERIFY", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NUMEQUALVERIFY], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NUMEQUALVERIFY long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NUMEQUALVERIFY], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NUMNOTEQUAL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NUMNOTEQUAL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NUMNOTEQUAL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NUMNOTEQUAL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_LESSTHAN", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LESSTHAN], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_LESSTHAN long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LESSTHAN], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_GREATERTHAN", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_GREATERTHAN], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_GREATERTHAN long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_GREATERTHAN], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_LESSTHANOREQUAL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LESSTHANOREQUAL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_LESSTHANOREQUAL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_LESSTHANOREQUAL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_GREATERTHANOREQUAL", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_GREATERTHANOREQUAL], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_GREATERTHANOREQUAL long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_GREATERTHANOREQUAL], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_MIN", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MIN], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_MIN long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MIN], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_MAX", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MAX], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_MAX long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_MAX], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_WITHIN", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_WITHIN], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_WITHIN long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_WITHIN], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_RIPEMD160", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RIPEMD160], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_RIPEMD160 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_RIPEMD160], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_SHA1", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SHA1], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_SHA1 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SHA1], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_SHA256", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SHA256], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_SHA256 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_SHA256], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_HASH160", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_HASH160], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_HASH160 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_HASH160], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_HASH256", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_HASH256], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_HASH256 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_HASH256], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_CODESAPERATOR", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CODESEPARATOR], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_CODESEPARATOR long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CODESEPARATOR], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_CHECKSIG", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKSIG], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_CHECKSIG long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKSIG], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_CHECKSIGVERIFY", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKSIGVERIFY], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_CHECKSIGVERIFY long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKSIGVERIFY], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_CHECKMULTISIG", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKMULTISIG], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_CHECKMULTISIG long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKMULTISIG], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_CHECKMULTISIGVERIFY", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKMULTISIGVERIFY], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_CHECKMULTISIGVERIFY long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_CHECKMULTISIGVERIFY], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP1", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP1], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP1 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP1], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP2", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP2], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP2 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP2], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP3", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP3], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP3 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP3], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP4", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP4], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP4 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP4], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP5", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP5], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP5 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP5], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP6", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP6], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP6 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP6], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP7", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP7], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP7 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP7], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP8", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP8], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP8 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP8], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP9", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP9], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP9 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP9], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_NOP10", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP10], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_NOP10 long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_NOP10], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_PUBKEYHASH", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PUBKEYHASH], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_PUBKEYHASH long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PUBKEYHASH], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_PUBKEY", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PUBKEY], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_PUBKEY long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_PUBKEY], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - { - name: "OP_INVALIDOPCODE", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_INVALIDOPCODE], - data: nil, - }, - expectedErr: nil, - }, - { - name: "OP_INVALIDOPCODE long", - pop: &parsedOpcode{ - opcode: &opcodeArray[OP_INVALIDOPCODE], - data: make([]byte, 1), - }, - expectedErr: scriptError(ErrInternal, ""), - }, - } - - for _, test := range tests { - _, err := test.pop.bytes() - if e := tstCheckScriptError(err, test.expectedErr); e != nil { - t.Errorf("Parsed opcode test '%s': %v", test.name, e) - continue - } - } -} - // TestPushedData ensured the PushedData function extracts the expected data out // of various scripts. func TestPushedData(t *testing.T) { From ca044fefcb80f4cc0ed966895ea390d4d956cd5d Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:13:05 -0500 Subject: [PATCH 105/108] txscript: Remove unused parsedOpcode.bytes func. --- txscript/opcode.go | 57 ---------------------------------------------- 1 file changed, 57 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 9e0320aed9..515e1cf2b1 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -8,7 +8,6 @@ import ( "bytes" "crypto/sha1" "crypto/sha256" - "encoding/binary" "encoding/hex" "fmt" "hash" @@ -740,62 +739,6 @@ func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) { buf.WriteString(fmt.Sprintf(" 0x%02x", data)) } -// bytes returns any data associated with the opcode encoded as it would be in -// a script. This is used for unparsing scripts from parsed opcodes. -func (pop *parsedOpcode) bytes() ([]byte, error) { - var retbytes []byte - if pop.opcode.length > 0 { - retbytes = make([]byte, 1, pop.opcode.length) - } else { - retbytes = make([]byte, 1, 1+len(pop.data)- - pop.opcode.length) - } - - retbytes[0] = pop.opcode.value - if pop.opcode.length == 1 { - if len(pop.data) != 0 { - str := fmt.Sprintf("internal consistency error - "+ - "parsed opcode %s has data length %d when %d "+ - "was expected", pop.opcode.name, len(pop.data), - 0) - return nil, scriptError(ErrInternal, str) - } - return retbytes, nil - } - nbytes := pop.opcode.length - if pop.opcode.length < 0 { - l := len(pop.data) - // tempting just to hardcode to avoid the complexity here. - switch pop.opcode.length { - case -1: - retbytes = append(retbytes, byte(l)) - nbytes = int(retbytes[1]) + len(retbytes) - case -2: - retbytes = append(retbytes, byte(l&0xff), - byte(l>>8&0xff)) - nbytes = int(binary.LittleEndian.Uint16(retbytes[1:])) + - len(retbytes) - case -4: - retbytes = append(retbytes, byte(l&0xff), - byte((l>>8)&0xff), byte((l>>16)&0xff), - byte((l>>24)&0xff)) - nbytes = int(binary.LittleEndian.Uint32(retbytes[1:])) + - len(retbytes) - } - } - - retbytes = append(retbytes, pop.data...) - - if len(retbytes) != nbytes { - str := fmt.Sprintf("internal consistency error - "+ - "parsed opcode %s has data length %d when %d was "+ - "expected", pop.opcode.name, len(retbytes), nbytes) - return nil, scriptError(ErrInternal, str) - } - - return retbytes, nil -} - // ******************************************* // Opcode implementation functions start here. // ******************************************* From 595d379fa6c1a588b9e3f58210132da6c6de3825 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:13:06 -0500 Subject: [PATCH 106/108] txscript: Remove unused parseScriptTemplate func. Also remove tests associated with the func accordingly. --- txscript/opcode.go | 73 ----------------------------------------- txscript/script.go | 59 --------------------------------- txscript/script_test.go | 16 --------- 3 files changed, 148 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 515e1cf2b1..d6fc494c89 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -618,79 +618,6 @@ type parsedOpcode struct { data []byte } -// checkParseableInScript checks whether or not the current opcode is able to be -// parsed at a certain position in a script. -// This returns the position of the next opcode to be parsed in the script. -func (pop *parsedOpcode) checkParseableInScript(script []byte, scriptPos int) (int, error) { - // Parse data out of instruction. - switch { - // No additional data. Note that some of the opcodes, notably - // OP_1NEGATE, OP_0, and OP_[1-16] represent the data - // themselves. - case pop.opcode.length == 1: - scriptPos++ - - // Data pushes of specific lengths -- OP_DATA_[1-75]. - case pop.opcode.length > 1: - if len(script[scriptPos:]) < pop.opcode.length { - str := fmt.Sprintf("opcode %s requires %d "+ - "bytes, but script only has %d remaining", - pop.opcode.name, pop.opcode.length, len(script[scriptPos:])) - return 0, scriptError(ErrMalformedPush, str) - } - - // Slice out the data. - pop.data = script[scriptPos+1 : scriptPos+pop.opcode.length] - scriptPos += pop.opcode.length - - // Data pushes with parsed lengths -- OP_PUSHDATAP{1,2,4}. - case pop.opcode.length < 0: - var l uint - off := scriptPos + 1 - - if len(script[off:]) < -pop.opcode.length { - str := fmt.Sprintf("opcode %s requires %d "+ - "bytes, but script only has %d remaining", - pop.opcode.name, -pop.opcode.length, len(script[off:])) - return 0, scriptError(ErrMalformedPush, str) - } - - // Next -length bytes are little endian length of data. - switch pop.opcode.length { - case -1: - l = uint(script[off]) - case -2: - l = ((uint(script[off+1]) << 8) | - uint(script[off])) - case -4: - l = ((uint(script[off+3]) << 24) | - (uint(script[off+2]) << 16) | - (uint(script[off+1]) << 8) | - uint(script[off])) - default: - str := fmt.Sprintf("invalid opcode length %d", - pop.opcode.length) - return 0, scriptError(ErrMalformedPush, str) - } - - // Move offset to beginning of the data. - off += -pop.opcode.length - - // Disallow entries that do not fit script or were - // sign extended. - if int(l) > len(script[off:]) || int(l) < 0 { - str := fmt.Sprintf("opcode %s pushes %d bytes, "+ - "but script only has %d remaining", - pop.opcode.name, int(l), len(script[off:])) - return 0, scriptError(ErrMalformedPush, str) - } - - pop.data = script[off : off+int(l)] - scriptPos += 1 - pop.opcode.length + int(l) - } - return scriptPos, nil -} - // disasmOpcode writes a human-readable disassembly of the provided opcode and // data into the provided buffer. The compact flag indicates the disassembly // should print a more compact representation of data-carrying and small integer diff --git a/txscript/script.go b/txscript/script.go index 0dbe4683d7..696bfe2d17 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -143,65 +143,6 @@ func IsPushOnlyScript(script []byte) bool { return tokenizer.Err() == nil } -// parseScriptTemplate is the same as parseScript but allows the passing of the -// template list for testing purposes. When there are parse errors, it returns -// the list of parsed opcodes up to the point of failure along with the error. -func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode, error) { - retScript := make([]parsedOpcode, 0, len(script)) - var err error - for i := 0; i < len(script); { - instr := script[i] - op := &opcodes[instr] - pop := parsedOpcode{opcode: op} - i, err = pop.checkParseableInScript(script, i) - if err != nil { - return retScript, err - } - - retScript = append(retScript, pop) - } - - return retScript, nil -} - -// checkScriptTemplateParseable is the same as parseScriptTemplate but does not -// return the list of opcodes up until the point of failure so that this can be -// used in functions which do not necessarily have a need for the failed list of -// opcodes, such as IsUnspendable. -// -// This function returns a pointer to a byte. This byte is nil if the parsing -// has an error, or if the script length is zero. If the script length is not -// zero and parsing succeeds, then the first opcode parsed will be returned. -// -// Not returning the full opcode list up until failure also has the benefit of -// reducing GC pressure, as the list would get immediately thrown away. -func checkScriptTemplateParseable(script []byte, opcodes *[256]opcode) (*byte, error) { - var err error - - // A script of length zero is an unspendable script but it is parseable. - var firstOpcode byte - var numParsedInstr uint = 0 - - for i := 0; i < len(script); { - instr := script[i] - op := &opcodes[instr] - pop := parsedOpcode{opcode: op} - i, err = pop.checkParseableInScript(script, i) - if err != nil { - return nil, err - } - - // if this is a op_return then it is unspendable so we set the first - // parsed instruction in case it's an op_return - if numParsedInstr == 0 { - firstOpcode = pop.opcode.value - } - numParsedInstr++ - } - - return &firstOpcode, nil -} - // DisasmString formats a disassembled script for one line printing. When the // script fails to parse, the returned string will contain the disassembled // script up to the point the failure occurred along with the string '[error]' diff --git a/txscript/script_test.go b/txscript/script_test.go index b6e7ff4203..86f94d84f4 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -12,22 +12,6 @@ import ( "github.com/btcsuite/btcd/wire" ) -// TestParseOpcode tests for opcode parsing with bad data templates. -func TestParseOpcode(t *testing.T) { - // Deep copy the array and make one of the opcodes invalid by setting it - // to the wrong length. - fakeArray := opcodeArray - fakeArray[OP_PUSHDATA4] = opcode{value: OP_PUSHDATA4, - name: "OP_PUSHDATA4", length: -8, opfunc: opcodePushData} - - // This script would be fine if -8 was a valid length. - _, err := parseScriptTemplate([]byte{OP_PUSHDATA4, 0x1, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00}, &fakeArray) - if err == nil { - t.Errorf("no error with dodgy opcode array!") - } -} - // TestPushedData ensured the PushedData function extracts the expected data out // of various scripts. func TestPushedData(t *testing.T) { From ef3d06e62b76818163e8302384afcdb44874cf46 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:13:07 -0500 Subject: [PATCH 107/108] txscript: Make executeOpcode take opcode and data. This converts the executeOpcode function defined on the engine to accept an opcode and data slice instead of a parsed opcode as a step towards removing the parsed opcode struct and associated supporting code altogether. It also updates all callers accordingly. --- txscript/engine.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index 1afb8324dd..a91dc9612f 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -341,23 +341,21 @@ func checkMinimalDataPush(op *opcode, data []byte) error { // executeOpcode peforms execution on the passed opcode. It takes into account // whether or not it is hidden by conditionals, but some rules still must be // tested in this case. -func (vm *Engine) executeOpcode(pop *parsedOpcode) error { +func (vm *Engine) executeOpcode(op *opcode, data []byte) error { // Disabled opcodes are fail on program counter. - if isOpcodeDisabled(pop.opcode.value) { - str := fmt.Sprintf("attempt to execute disabled opcode %s", - pop.opcode.name) + if isOpcodeDisabled(op.value) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name) return scriptError(ErrDisabledOpcode, str) } // Always-illegal opcodes are fail on program counter. - if isOpcodeAlwaysIllegal(pop.opcode.value) { - str := fmt.Sprintf("attempt to execute reserved opcode %s", - pop.opcode.name) + if isOpcodeAlwaysIllegal(op.value) { + str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name) return scriptError(ErrReservedOpcode, str) } // Note that this includes OP_RESERVED which counts as a push operation. - if pop.opcode.value > OP_16 { + if op.value > OP_16 { vm.numOps++ if vm.numOps > MaxOpsPerScript { str := fmt.Sprintf("exceeded max operation limit of %d", @@ -365,29 +363,30 @@ func (vm *Engine) executeOpcode(pop *parsedOpcode) error { return scriptError(ErrTooManyOperations, str) } - } else if len(pop.data) > MaxScriptElementSize { + } else if len(data) > MaxScriptElementSize { str := fmt.Sprintf("element size %d exceeds max allowed size %d", - len(pop.data), MaxScriptElementSize) + len(data), MaxScriptElementSize) return scriptError(ErrElementTooBig, str) } // Nothing left to do when this is not a conditional opcode and it is // not in an executing branch. - if !vm.isBranchExecuting() && !isOpcodeConditional(pop.opcode.value) { + if !vm.isBranchExecuting() && !isOpcodeConditional(op.value) { return nil } // Ensure all executed data push opcodes use the minimal encoding when // the minimal data verification flag is set. if vm.dstack.verifyMinimalData && vm.isBranchExecuting() && - pop.opcode.value >= 0 && pop.opcode.value <= OP_PUSHDATA4 { + op.value >= 0 && op.value <= OP_PUSHDATA4 { - if err := checkMinimalDataPush(pop.opcode, pop.data); err != nil { + if err := checkMinimalDataPush(op, data); err != nil { return err } } - return pop.opcode.opfunc(pop, vm) + pop := parsedOpcode{opcode: op, data: data} + return op.opfunc(&pop, vm) } // checkValidPC returns an error if the current script position is not valid for @@ -670,8 +669,7 @@ func (vm *Engine) Step() (done bool, err error) { // Execute the opcode while taking into account several things such as // disabled opcodes, illegal opcodes, maximum allowed operations per script, // maximum script element sizes, and conditionals. - pop := parsedOpcode{opcode: vm.tokenizer.op, data: vm.tokenizer.Data()} - err = vm.executeOpcode(&pop) + err = vm.executeOpcode(vm.tokenizer.op, vm.tokenizer.Data()) if err != nil { return true, err } From b95ba0ac95e04336f552b42864af824ba221d345 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 13 Mar 2019 01:13:08 -0500 Subject: [PATCH 108/108] txscript: Make op callbacks take opcode and data. This converts the callback function defined on the internal opcode struct to accept the opcode and data slice instead of a parsed opcode as the final step towards removing the parsed opcode struct and associated supporting code altogether. It also updates all of the callbacks and tests accordingly and finally removes the now unused parsedOpcode struct. The final results for the raw script analysis and tokenizer optimizations are as follows: benchmark old ns/op new ns/op delta BenchmarkIsPayToScriptHash-8 62393 0.51 -100.00% BenchmarkIsPubKeyHashScript-8 62228 0.56 -100.00% BenchmarkGetSigOpCount-8 61051 658 -98.92% BenchmarkExtractPkScriptAddrsLarge-8 60713 17.2 -99.97% BenchmarkExtractPkScriptAddrs-8 289 17.9 -93.81% BenchmarkIsWitnessPubKeyHash-8 61688 0.42 -100.00% BenchmarkIsUnspendable-8 656 520 -20.73% BenchmarkExtractAtomicSwapDataPushesLarge-8 61332 44.0 -99.93% BenchmarkExtractAtomicSwapDataPushes-8 990 260 -73.74% BenchmarkDisasmString-8 102902 39754 -61.37% BenchmarkGetPreciseSigOpCount-8 130223 715 -99.45% BenchmarkScriptParsing-8 63464 681 -98.93% BenchmarkIsMultisigScriptLarge-8 64166 5.83 -99.99% BenchmarkIsMultisigScript-8 630 58.5 -90.71% BenchmarkPushedData-8 64837 1779 -97.26% BenchmarkCalcSigHash-8 3627895 3605459 -0.62% BenchmarkIsPubKeyScript-8 62323 2.83 -100.00% BenchmarkIsPushOnlyScript-8 62412 569 -99.09% BenchmarkIsWitnessScriptHash-8 61243 0.56 -100.00% BenchmarkGetScriptClass-8 61515 16.4 -99.97% BenchmarkIsNullDataScript-8 62495 2.53 -100.00% BenchmarkIsMultisigSigScriptLarge-8 69328 2.52 -100.00% BenchmarkIsMultisigSigScript-8 2375 141 -94.06% BenchmarkGetWitnessSigOpCountP2WKH-8 504 72.0 -85.71% BenchmarkGetWitnessSigOpCountNested-8 1158 136 -88.26% BenchmarkIsWitnessPubKeyHash-8 68927 0.53 -100.00% BenchmarkIsWitnessScriptHash-8 62774 0.63 -100.00% benchmark old allocs new allocs delta BenchmarkIsPayToScriptHash-8 1 0 -100.00% BenchmarkIsPubKeyHashScript-8 1 0 -100.00% BenchmarkGetSigOpCount-8 1 0 -100.00% BenchmarkExtractPkScriptAddrsLarge-8 1 0 -100.00% BenchmarkExtractPkScriptAddrs-8 1 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 1 0 -100.00% BenchmarkIsUnspendable-8 1 0 -100.00% BenchmarkExtractAtomicSwapDataPushesLarge-8 1 0 -100.00% BenchmarkExtractAtomicSwapDataPushes-8 2 1 -50.00% BenchmarkDisasmString-8 46 51 +10.87% BenchmarkGetPreciseSigOpCount-8 3 0 -100.00% BenchmarkScriptParsing-8 1 0 -100.00% BenchmarkIsMultisigScriptLarge-8 1 0 -100.00% BenchmarkIsMultisigScript-8 1 0 -100.00% BenchmarkPushedData-8 7 6 -14.29% BenchmarkCalcSigHash-8 1335 712 -46.67% BenchmarkIsPubKeyScript-8 1 0 -100.00% BenchmarkIsPushOnlyScript-8 1 0 -100.00% BenchmarkIsWitnessScriptHash-8 1 0 -100.00% BenchmarkGetScriptClass-8 1 0 -100.00% BenchmarkIsNullDataScript-8 1 0 -100.00% BenchmarkIsMultisigSigScriptLarge-8 5 0 -100.00% BenchmarkIsMultisigSigScript-8 3 0 -100.00% BenchmarkGetWitnessSigOpCountP2WKH-8 2 0 -100.00% BenchmarkGetWitnessSigOpCountNested-8 4 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 1 0 -100.00% BenchmarkIsWitnessScriptHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPayToScriptHash-8 311299 0 -100.00% BenchmarkIsPubKeyHashScript-8 311299 0 -100.00% BenchmarkGetSigOpCount-8 311299 0 -100.00% BenchmarkExtractPkScriptAddrsLarge-8 311299 0 -100.00% BenchmarkExtractPkScriptAddrs-8 768 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 311299 0 -100.00% BenchmarkIsUnspendable-8 1 0 -100.00% BenchmarkExtractAtomicSwapDataPushesLarge-8 311299 0 -100.00% BenchmarkExtractAtomicSwapDataPushes-8 3168 96 -96.97% BenchmarkDisasmString-8 389324 130552 -66.47% BenchmarkGetPreciseSigOpCount-8 623367 0 -100.00% BenchmarkScriptParsing-8 311299 0 -100.00% BenchmarkIsMultisigScriptLarge-8 311299 0 -100.00% BenchmarkIsMultisigScript-8 2304 0 -100.00% BenchmarkPushedData-8 312816 1520 -99.51% BenchmarkCalcSigHash-8 1373812 1290507 -6.06% BenchmarkIsPubKeyScript-8 311299 0 -100.00% BenchmarkIsPushOnlyScript-8 311299 0 -100.00% BenchmarkIsWitnessScriptHash-8 311299 0 -100.00% BenchmarkGetScriptClass-8 311299 0 -100.00% BenchmarkIsNullDataScript-8 311299 0 -100.00% BenchmarkIsMultisigSigScriptLarge-8 330035 0 -100.00% BenchmarkIsMultisigSigScript-8 9472 0 -100.00% BenchmarkGetWitnessSigOpCountP2WKH-8 1408 0 -100.00% BenchmarkGetWitnessSigOpCountNested-8 3200 0 -100.00% BenchmarkIsWitnessPubKeyHash-8 311299 0 -100.00% BenchmarkIsWitnessScriptHash-8 311299 0 -100.00% --- txscript/engine.go | 3 +- txscript/opcode.go | 183 +++++++++++++++++++--------------------- txscript/opcode_test.go | 4 +- 3 files changed, 90 insertions(+), 100 deletions(-) diff --git a/txscript/engine.go b/txscript/engine.go index a91dc9612f..0814e7eb96 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -385,8 +385,7 @@ func (vm *Engine) executeOpcode(op *opcode, data []byte) error { } } - pop := parsedOpcode{opcode: op, data: data} - return op.opfunc(&pop, vm) + return op.opfunc(op, data, vm) } // checkValidPC returns an error if the current script position is not valid for diff --git a/txscript/opcode.go b/txscript/opcode.go index d6fc494c89..4c31be3f75 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -28,7 +28,7 @@ type opcode struct { value byte name string length int - opfunc func(*parsedOpcode, *Engine) error + opfunc func(*opcode, []byte, *Engine) error } // These constants are the values of the official opcodes used on the btc wiki, @@ -611,13 +611,6 @@ var opcodeOnelineRepls = map[string]string{ "OP_16": "16", } -// parsedOpcode represents an opcode that has been parsed and includes any -// potential data associated with it. -type parsedOpcode struct { - opcode *opcode - data []byte -} - // disasmOpcode writes a human-readable disassembly of the provided opcode and // data into the provided buffer. The compact flag indicates the disassembly // should print a more compact representation of data-carrying and small integer @@ -676,45 +669,42 @@ func disasmOpcode(buf *strings.Builder, op *opcode, data []byte, compact bool) { // opcodes before executing in an initial parse step, the consensus rules // dictate the script doesn't fail until the program counter passes over a // disabled opcode (even when they appear in a branch that is not executed). -func opcodeDisabled(op *parsedOpcode, vm *Engine) error { - str := fmt.Sprintf("attempt to execute disabled opcode %s", - op.opcode.name) +func opcodeDisabled(op *opcode, data []byte, vm *Engine) error { + str := fmt.Sprintf("attempt to execute disabled opcode %s", op.name) return scriptError(ErrDisabledOpcode, str) } // opcodeReserved is a common handler for all reserved opcodes. It returns an // appropriate error indicating the opcode is reserved. -func opcodeReserved(op *parsedOpcode, vm *Engine) error { - str := fmt.Sprintf("attempt to execute reserved opcode %s", - op.opcode.name) +func opcodeReserved(op *opcode, data []byte, vm *Engine) error { + str := fmt.Sprintf("attempt to execute reserved opcode %s", op.name) return scriptError(ErrReservedOpcode, str) } // opcodeInvalid is a common handler for all invalid opcodes. It returns an // appropriate error indicating the opcode is invalid. -func opcodeInvalid(op *parsedOpcode, vm *Engine) error { - str := fmt.Sprintf("attempt to execute invalid opcode %s", - op.opcode.name) +func opcodeInvalid(op *opcode, data []byte, vm *Engine) error { + str := fmt.Sprintf("attempt to execute invalid opcode %s", op.name) return scriptError(ErrReservedOpcode, str) } // opcodeFalse pushes an empty array to the data stack to represent false. Note // that 0, when encoded as a number according to the numeric encoding consensus // rules, is an empty array. -func opcodeFalse(op *parsedOpcode, vm *Engine) error { +func opcodeFalse(op *opcode, data []byte, vm *Engine) error { vm.dstack.PushByteArray(nil) return nil } // opcodePushData is a common handler for the vast majority of opcodes that push // raw data (bytes) to the data stack. -func opcodePushData(op *parsedOpcode, vm *Engine) error { - vm.dstack.PushByteArray(op.data) +func opcodePushData(op *opcode, data []byte, vm *Engine) error { + vm.dstack.PushByteArray(data) return nil } // opcode1Negate pushes -1, encoded as a number, to the data stack. -func opcode1Negate(op *parsedOpcode, vm *Engine) error { +func opcode1Negate(op *opcode, data []byte, vm *Engine) error { vm.dstack.PushInt(scriptNum(-1)) return nil } @@ -722,23 +712,24 @@ func opcode1Negate(op *parsedOpcode, vm *Engine) error { // opcodeN is a common handler for the small integer data push opcodes. It // pushes the numeric value the opcode represents (which will be from 1 to 16) // onto the data stack. -func opcodeN(op *parsedOpcode, vm *Engine) error { +func opcodeN(op *opcode, data []byte, vm *Engine) error { // The opcodes are all defined consecutively, so the numeric value is // the difference. - vm.dstack.PushInt(scriptNum((op.opcode.value - (OP_1 - 1)))) + vm.dstack.PushInt(scriptNum((op.value - (OP_1 - 1)))) return nil } // opcodeNop is a common handler for the NOP family of opcodes. As the name // implies it generally does nothing, however, it will return an error when // the flag to discourage use of NOPs is set for select opcodes. -func opcodeNop(op *parsedOpcode, vm *Engine) error { - switch op.opcode.value { +func opcodeNop(op *opcode, data []byte, vm *Engine) error { + switch op.value { case OP_NOP1, OP_NOP4, OP_NOP5, OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10: + if vm.hasFlag(ScriptDiscourageUpgradableNops) { - str := fmt.Sprintf("OP_NOP%d reserved for soft-fork "+ - "upgrades", op.opcode.value-(OP_NOP1-1)) + str := fmt.Sprintf("%v reserved for soft-fork "+ + "upgrades", op.name) return scriptError(ErrDiscourageUpgradableNOPs, str) } } @@ -801,7 +792,7 @@ func popIfBool(vm *Engine) (bool, error) { // // Data stack transformation: [... bool] -> [...] // Conditional stack transformation: [...] -> [... OpCondValue] -func opcodeIf(op *parsedOpcode, vm *Engine) error { +func opcodeIf(op *opcode, data []byte, vm *Engine) error { condVal := OpCondFalse if vm.isBranchExecuting() { ok, err := popIfBool(vm) @@ -835,7 +826,7 @@ func opcodeIf(op *parsedOpcode, vm *Engine) error { // // Data stack transformation: [... bool] -> [...] // Conditional stack transformation: [...] -> [... OpCondValue] -func opcodeNotIf(op *parsedOpcode, vm *Engine) error { +func opcodeNotIf(op *opcode, data []byte, vm *Engine) error { condVal := OpCondFalse if vm.isBranchExecuting() { ok, err := popIfBool(vm) @@ -858,10 +849,10 @@ func opcodeNotIf(op *parsedOpcode, vm *Engine) error { // An error is returned if there has not already been a matching OP_IF. // // Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue] -func opcodeElse(op *parsedOpcode, vm *Engine) error { +func opcodeElse(op *opcode, data []byte, vm *Engine) error { if len(vm.condStack) == 0 { str := fmt.Sprintf("encountered opcode %s with no matching "+ - "opcode to begin conditional execution", op.opcode.name) + "opcode to begin conditional execution", op.name) return scriptError(ErrUnbalancedConditional, str) } @@ -884,10 +875,10 @@ func opcodeElse(op *parsedOpcode, vm *Engine) error { // An error is returned if there has not already been a matching OP_IF. // // Conditional stack transformation: [... OpCondValue] -> [...] -func opcodeEndif(op *parsedOpcode, vm *Engine) error { +func opcodeEndif(op *opcode, data []byte, vm *Engine) error { if len(vm.condStack) == 0 { str := fmt.Sprintf("encountered opcode %s with no matching "+ - "opcode to begin conditional execution", op.opcode.name) + "opcode to begin conditional execution", op.name) return scriptError(ErrUnbalancedConditional, str) } @@ -900,14 +891,14 @@ func opcodeEndif(op *parsedOpcode, vm *Engine) error { // item on the stack or when that item evaluates to false. In the latter case // where the verification fails specifically due to the top item evaluating // to false, the returned error will use the passed error code. -func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error { +func abstractVerify(op *opcode, vm *Engine, c ErrorCode) error { verified, err := vm.dstack.PopBool() if err != nil { return err } if !verified { - str := fmt.Sprintf("%s failed", op.opcode.name) + str := fmt.Sprintf("%s failed", op.name) return scriptError(c, str) } return nil @@ -915,13 +906,13 @@ func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error { // opcodeVerify examines the top item on the data stack as a boolean value and // verifies it evaluates to true. An error is returned if it does not. -func opcodeVerify(op *parsedOpcode, vm *Engine) error { +func opcodeVerify(op *opcode, data []byte, vm *Engine) error { return abstractVerify(op, vm, ErrVerify) } // opcodeReturn returns an appropriate error since it is always an error to // return early from a script. -func opcodeReturn(op *parsedOpcode, vm *Engine) error { +func opcodeReturn(op *opcode, data []byte, vm *Engine) error { return scriptError(ErrEarlyReturn, "script returned early") } @@ -951,7 +942,7 @@ func verifyLockTime(txLockTime, threshold, lockTime int64) error { // validating if the transaction outputs are spendable yet. If flag // ScriptVerifyCheckLockTimeVerify is not set, the code continues as if OP_NOP2 // were executed. -func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error { +func opcodeCheckLockTimeVerify(op *opcode, data []byte, vm *Engine) error { // If the ScriptVerifyCheckLockTimeVerify script flag is not set, treat // opcode as OP_NOP2 instead. if !vm.hasFlag(ScriptVerifyCheckLockTimeVerify) { @@ -1025,7 +1016,7 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error { // validating if the transaction outputs are spendable yet. If flag // ScriptVerifyCheckSequenceVerify is not set, the code continues as if OP_NOP3 // were executed. -func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error { +func opcodeCheckSequenceVerify(op *opcode, data []byte, vm *Engine) error { // If the ScriptVerifyCheckSequenceVerify script flag is not set, treat // opcode as OP_NOP3 instead. if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) { @@ -1102,7 +1093,7 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error { // // Main data stack transformation: [... x1 x2 x3] -> [... x1 x2] // Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2 y3 x3] -func opcodeToAltStack(op *parsedOpcode, vm *Engine) error { +func opcodeToAltStack(op *opcode, data []byte, vm *Engine) error { so, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1117,7 +1108,7 @@ func opcodeToAltStack(op *parsedOpcode, vm *Engine) error { // // Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3] // Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2] -func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error { +func opcodeFromAltStack(op *opcode, data []byte, vm *Engine) error { so, err := vm.astack.PopByteArray() if err != nil { return err @@ -1130,35 +1121,35 @@ func opcodeFromAltStack(op *parsedOpcode, vm *Engine) error { // opcode2Drop removes the top 2 items from the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1] -func opcode2Drop(op *parsedOpcode, vm *Engine) error { +func opcode2Drop(op *opcode, data []byte, vm *Engine) error { return vm.dstack.DropN(2) } // opcode2Dup duplicates the top 2 items on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2 x3] -func opcode2Dup(op *parsedOpcode, vm *Engine) error { +func opcode2Dup(op *opcode, data []byte, vm *Engine) error { return vm.dstack.DupN(2) } // opcode3Dup duplicates the top 3 items on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x1 x2 x3] -func opcode3Dup(op *parsedOpcode, vm *Engine) error { +func opcode3Dup(op *opcode, data []byte, vm *Engine) error { return vm.dstack.DupN(3) } // opcode2Over duplicates the 2 items before the top 2 items on the data stack. // // Stack transformation: [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2] -func opcode2Over(op *parsedOpcode, vm *Engine) error { +func opcode2Over(op *opcode, data []byte, vm *Engine) error { return vm.dstack.OverN(2) } // opcode2Rot rotates the top 6 items on the data stack to the left twice. // // Stack transformation: [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2] -func opcode2Rot(op *parsedOpcode, vm *Engine) error { +func opcode2Rot(op *opcode, data []byte, vm *Engine) error { return vm.dstack.RotN(2) } @@ -1166,7 +1157,7 @@ func opcode2Rot(op *parsedOpcode, vm *Engine) error { // before them. // // Stack transformation: [... x1 x2 x3 x4] -> [... x3 x4 x1 x2] -func opcode2Swap(op *parsedOpcode, vm *Engine) error { +func opcode2Swap(op *opcode, data []byte, vm *Engine) error { return vm.dstack.SwapN(2) } @@ -1174,7 +1165,7 @@ func opcode2Swap(op *parsedOpcode, vm *Engine) error { // // Stack transformation (x1==0): [... x1] -> [... x1] // Stack transformation (x1!=0): [... x1] -> [... x1 x1] -func opcodeIfDup(op *parsedOpcode, vm *Engine) error { +func opcodeIfDup(op *opcode, data []byte, vm *Engine) error { so, err := vm.dstack.PeekByteArray(0) if err != nil { return err @@ -1194,7 +1185,7 @@ func opcodeIfDup(op *parsedOpcode, vm *Engine) error { // Stack transformation: [...] -> [... ] // Example with 2 items: [x1 x2] -> [x1 x2 2] // Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3] -func opcodeDepth(op *parsedOpcode, vm *Engine) error { +func opcodeDepth(op *opcode, data []byte, vm *Engine) error { vm.dstack.PushInt(scriptNum(vm.dstack.Depth())) return nil } @@ -1202,28 +1193,28 @@ func opcodeDepth(op *parsedOpcode, vm *Engine) error { // opcodeDrop removes the top item from the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2] -func opcodeDrop(op *parsedOpcode, vm *Engine) error { +func opcodeDrop(op *opcode, data []byte, vm *Engine) error { return vm.dstack.DropN(1) } // opcodeDup duplicates the top item on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x3] -func opcodeDup(op *parsedOpcode, vm *Engine) error { +func opcodeDup(op *opcode, data []byte, vm *Engine) error { return vm.dstack.DupN(1) } // opcodeNip removes the item before the top item on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x3] -func opcodeNip(op *parsedOpcode, vm *Engine) error { +func opcodeNip(op *opcode, data []byte, vm *Engine) error { return vm.dstack.NipN(1) } // opcodeOver duplicates the item before the top item on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2] -func opcodeOver(op *parsedOpcode, vm *Engine) error { +func opcodeOver(op *opcode, data []byte, vm *Engine) error { return vm.dstack.OverN(1) } @@ -1233,7 +1224,7 @@ func opcodeOver(op *parsedOpcode, vm *Engine) error { // Stack transformation: [xn ... x2 x1 x0 n] -> [xn ... x2 x1 x0 xn] // Example with n=1: [x2 x1 x0 1] -> [x2 x1 x0 x1] // Example with n=2: [x2 x1 x0 2] -> [x2 x1 x0 x2] -func opcodePick(op *parsedOpcode, vm *Engine) error { +func opcodePick(op *opcode, data []byte, vm *Engine) error { val, err := vm.dstack.PopInt() if err != nil { return err @@ -1248,7 +1239,7 @@ func opcodePick(op *parsedOpcode, vm *Engine) error { // Stack transformation: [xn ... x2 x1 x0 n] -> [... x2 x1 x0 xn] // Example with n=1: [x2 x1 x0 1] -> [x2 x0 x1] // Example with n=2: [x2 x1 x0 2] -> [x1 x0 x2] -func opcodeRoll(op *parsedOpcode, vm *Engine) error { +func opcodeRoll(op *opcode, data []byte, vm *Engine) error { val, err := vm.dstack.PopInt() if err != nil { return err @@ -1260,14 +1251,14 @@ func opcodeRoll(op *parsedOpcode, vm *Engine) error { // opcodeRot rotates the top 3 items on the data stack to the left. // // Stack transformation: [... x1 x2 x3] -> [... x2 x3 x1] -func opcodeRot(op *parsedOpcode, vm *Engine) error { +func opcodeRot(op *opcode, data []byte, vm *Engine) error { return vm.dstack.RotN(1) } // opcodeSwap swaps the top two items on the stack. // // Stack transformation: [... x1 x2] -> [... x2 x1] -func opcodeSwap(op *parsedOpcode, vm *Engine) error { +func opcodeSwap(op *opcode, data []byte, vm *Engine) error { return vm.dstack.SwapN(1) } @@ -1275,7 +1266,7 @@ func opcodeSwap(op *parsedOpcode, vm *Engine) error { // second-to-top item. // // Stack transformation: [... x1 x2] -> [... x2 x1 x2] -func opcodeTuck(op *parsedOpcode, vm *Engine) error { +func opcodeTuck(op *opcode, data []byte, vm *Engine) error { return vm.dstack.Tuck() } @@ -1283,7 +1274,7 @@ func opcodeTuck(op *parsedOpcode, vm *Engine) error { // stack. // // Stack transformation: [... x1] -> [... x1 len(x1)] -func opcodeSize(op *parsedOpcode, vm *Engine) error { +func opcodeSize(op *opcode, data []byte, vm *Engine) error { so, err := vm.dstack.PeekByteArray(0) if err != nil { return err @@ -1297,7 +1288,7 @@ func opcodeSize(op *parsedOpcode, vm *Engine) error { // bytes, and pushes the result, encoded as a boolean, back to the stack. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeEqual(op *parsedOpcode, vm *Engine) error { +func opcodeEqual(op *opcode, data []byte, vm *Engine) error { a, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1318,8 +1309,8 @@ func opcodeEqual(op *parsedOpcode, vm *Engine) error { // evaluates to true. An error is returned if it does not. // // Stack transformation: [... x1 x2] -> [... bool] -> [...] -func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error { - err := opcodeEqual(op, vm) +func opcodeEqualVerify(op *opcode, data []byte, vm *Engine) error { + err := opcodeEqual(op, data, vm) if err == nil { err = abstractVerify(op, vm, ErrEqualVerify) } @@ -1330,7 +1321,7 @@ func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error { // it with its incremented value (plus 1). // // Stack transformation: [... x1 x2] -> [... x1 x2+1] -func opcode1Add(op *parsedOpcode, vm *Engine) error { +func opcode1Add(op *opcode, data []byte, vm *Engine) error { m, err := vm.dstack.PopInt() if err != nil { return err @@ -1344,7 +1335,7 @@ func opcode1Add(op *parsedOpcode, vm *Engine) error { // it with its decremented value (minus 1). // // Stack transformation: [... x1 x2] -> [... x1 x2-1] -func opcode1Sub(op *parsedOpcode, vm *Engine) error { +func opcode1Sub(op *opcode, data []byte, vm *Engine) error { m, err := vm.dstack.PopInt() if err != nil { return err @@ -1358,7 +1349,7 @@ func opcode1Sub(op *parsedOpcode, vm *Engine) error { // it with its negation. // // Stack transformation: [... x1 x2] -> [... x1 -x2] -func opcodeNegate(op *parsedOpcode, vm *Engine) error { +func opcodeNegate(op *opcode, data []byte, vm *Engine) error { m, err := vm.dstack.PopInt() if err != nil { return err @@ -1372,7 +1363,7 @@ func opcodeNegate(op *parsedOpcode, vm *Engine) error { // it with its absolute value. // // Stack transformation: [... x1 x2] -> [... x1 abs(x2)] -func opcodeAbs(op *parsedOpcode, vm *Engine) error { +func opcodeAbs(op *opcode, data []byte, vm *Engine) error { m, err := vm.dstack.PopInt() if err != nil { return err @@ -1397,7 +1388,7 @@ func opcodeAbs(op *parsedOpcode, vm *Engine) error { // Stack transformation (x2==0): [... x1 0] -> [... x1 1] // Stack transformation (x2!=0): [... x1 1] -> [... x1 0] // Stack transformation (x2!=0): [... x1 17] -> [... x1 0] -func opcodeNot(op *parsedOpcode, vm *Engine) error { +func opcodeNot(op *opcode, data []byte, vm *Engine) error { m, err := vm.dstack.PopInt() if err != nil { return err @@ -1417,7 +1408,7 @@ func opcodeNot(op *parsedOpcode, vm *Engine) error { // Stack transformation (x2==0): [... x1 0] -> [... x1 0] // Stack transformation (x2!=0): [... x1 1] -> [... x1 1] // Stack transformation (x2!=0): [... x1 17] -> [... x1 1] -func opcode0NotEqual(op *parsedOpcode, vm *Engine) error { +func opcode0NotEqual(op *opcode, data []byte, vm *Engine) error { m, err := vm.dstack.PopInt() if err != nil { return err @@ -1434,7 +1425,7 @@ func opcode0NotEqual(op *parsedOpcode, vm *Engine) error { // them with their sum. // // Stack transformation: [... x1 x2] -> [... x1+x2] -func opcodeAdd(op *parsedOpcode, vm *Engine) error { +func opcodeAdd(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1454,7 +1445,7 @@ func opcodeAdd(op *parsedOpcode, vm *Engine) error { // entry. // // Stack transformation: [... x1 x2] -> [... x1-x2] -func opcodeSub(op *parsedOpcode, vm *Engine) error { +func opcodeSub(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1476,7 +1467,7 @@ func opcodeSub(op *parsedOpcode, vm *Engine) error { // Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 0] // Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 0] // Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1] -func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error { +func opcodeBoolAnd(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1503,7 +1494,7 @@ func opcodeBoolAnd(op *parsedOpcode, vm *Engine) error { // Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 1] // Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 1] // Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1] -func opcodeBoolOr(op *parsedOpcode, vm *Engine) error { +func opcodeBoolOr(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1528,7 +1519,7 @@ func opcodeBoolOr(op *parsedOpcode, vm *Engine) error { // // Stack transformation (x1==x2): [... 5 5] -> [... 1] // Stack transformation (x1!=x2): [... 5 7] -> [... 0] -func opcodeNumEqual(op *parsedOpcode, vm *Engine) error { +func opcodeNumEqual(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1556,8 +1547,8 @@ func opcodeNumEqual(op *parsedOpcode, vm *Engine) error { // to true. An error is returned if it does not. // // Stack transformation: [... x1 x2] -> [... bool] -> [...] -func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error { - err := opcodeNumEqual(op, vm) +func opcodeNumEqualVerify(op *opcode, data []byte, vm *Engine) error { + err := opcodeNumEqual(op, data, vm) if err == nil { err = abstractVerify(op, vm, ErrNumEqualVerify) } @@ -1569,7 +1560,7 @@ func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error { // // Stack transformation (x1==x2): [... 5 5] -> [... 0] // Stack transformation (x1!=x2): [... 5 7] -> [... 1] -func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error { +func opcodeNumNotEqual(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1594,7 +1585,7 @@ func opcodeNumNotEqual(op *parsedOpcode, vm *Engine) error { // otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeLessThan(op *parsedOpcode, vm *Engine) error { +func opcodeLessThan(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1619,7 +1610,7 @@ func opcodeLessThan(op *parsedOpcode, vm *Engine) error { // with a 1, otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error { +func opcodeGreaterThan(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1643,7 +1634,7 @@ func opcodeGreaterThan(op *parsedOpcode, vm *Engine) error { // replaced with a 1, otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error { +func opcodeLessThanOrEqual(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1667,7 +1658,7 @@ func opcodeLessThanOrEqual(op *parsedOpcode, vm *Engine) error { // item, they are replaced with a 1, otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error { +func opcodeGreaterThanOrEqual(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1691,7 +1682,7 @@ func opcodeGreaterThanOrEqual(op *parsedOpcode, vm *Engine) error { // them with the minimum of the two. // // Stack transformation: [... x1 x2] -> [... min(x1, x2)] -func opcodeMin(op *parsedOpcode, vm *Engine) error { +func opcodeMin(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1715,7 +1706,7 @@ func opcodeMin(op *parsedOpcode, vm *Engine) error { // them with the maximum of the two. // // Stack transformation: [... x1 x2] -> [... max(x1, x2)] -func opcodeMax(op *parsedOpcode, vm *Engine) error { +func opcodeMax(op *opcode, data []byte, vm *Engine) error { v0, err := vm.dstack.PopInt() if err != nil { return err @@ -1743,7 +1734,7 @@ func opcodeMax(op *parsedOpcode, vm *Engine) error { // the third-to-top item is the value to test. // // Stack transformation: [... x1 min max] -> [... bool] -func opcodeWithin(op *parsedOpcode, vm *Engine) error { +func opcodeWithin(op *opcode, data []byte, vm *Engine) error { maxVal, err := vm.dstack.PopInt() if err != nil { return err @@ -1777,7 +1768,7 @@ func calcHash(buf []byte, hasher hash.Hash) []byte { // replaces it with ripemd160(data). // // Stack transformation: [... x1] -> [... ripemd160(x1)] -func opcodeRipemd160(op *parsedOpcode, vm *Engine) error { +func opcodeRipemd160(op *opcode, data []byte, vm *Engine) error { buf, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1791,7 +1782,7 @@ func opcodeRipemd160(op *parsedOpcode, vm *Engine) error { // with sha1(data). // // Stack transformation: [... x1] -> [... sha1(x1)] -func opcodeSha1(op *parsedOpcode, vm *Engine) error { +func opcodeSha1(op *opcode, data []byte, vm *Engine) error { buf, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1806,7 +1797,7 @@ func opcodeSha1(op *parsedOpcode, vm *Engine) error { // it with sha256(data). // // Stack transformation: [... x1] -> [... sha256(x1)] -func opcodeSha256(op *parsedOpcode, vm *Engine) error { +func opcodeSha256(op *opcode, data []byte, vm *Engine) error { buf, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1821,7 +1812,7 @@ func opcodeSha256(op *parsedOpcode, vm *Engine) error { // it with ripemd160(sha256(data)). // // Stack transformation: [... x1] -> [... ripemd160(sha256(x1))] -func opcodeHash160(op *parsedOpcode, vm *Engine) error { +func opcodeHash160(op *opcode, data []byte, vm *Engine) error { buf, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1836,7 +1827,7 @@ func opcodeHash160(op *parsedOpcode, vm *Engine) error { // it with sha256(sha256(data)). // // Stack transformation: [... x1] -> [... sha256(sha256(x1))] -func opcodeHash256(op *parsedOpcode, vm *Engine) error { +func opcodeHash256(op *opcode, data []byte, vm *Engine) error { buf, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1850,7 +1841,7 @@ func opcodeHash256(op *parsedOpcode, vm *Engine) error { // seen OP_CODESEPARATOR which is used during signature checking. // // This opcode does not change the contents of the data stack. -func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error { +func opcodeCodeSeparator(op *opcode, data []byte, vm *Engine) error { vm.lastCodeSep = int(vm.tokenizer.ByteIndex()) return nil } @@ -1869,7 +1860,7 @@ func opcodeCodeSeparator(op *parsedOpcode, vm *Engine) error { // cryptographic methods against the provided public key. // // Stack transformation: [... signature pubkey] -> [... bool] -func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { +func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error { pkBytes, err := vm.dstack.PopByteArray() if err != nil { return err @@ -1984,9 +1975,9 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { // The opcodeCheckSig function is invoked followed by opcodeVerify. See the // documentation for each of those opcodes for more details. // -// Stack transformation: signature pubkey] -> [... bool] -> [...] -func opcodeCheckSigVerify(op *parsedOpcode, vm *Engine) error { - err := opcodeCheckSig(op, vm) +// Stack transformation: [... signature pubkey] -> [... bool] -> [...] +func opcodeCheckSigVerify(op *opcode, data []byte, vm *Engine) error { + err := opcodeCheckSig(op, data, vm) if err == nil { err = abstractVerify(op, vm, ErrCheckSigVerify) } @@ -2021,7 +2012,7 @@ type parsedSigInfo struct { // // Stack transformation: // [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { +func opcodeCheckMultiSig(op *opcode, data []byte, vm *Engine) error { numKeys, err := vm.dstack.PopInt() if err != nil { return err @@ -2247,8 +2238,8 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { // // Stack transformation: // [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...] -func opcodeCheckMultiSigVerify(op *parsedOpcode, vm *Engine) error { - err := opcodeCheckMultiSig(op, vm) +func opcodeCheckMultiSigVerify(op *opcode, data []byte, vm *Engine) error { + err := opcodeCheckMultiSig(op, data, vm) if err == nil { err = abstractVerify(op, vm, ErrCheckMultiSigVerify) } diff --git a/txscript/opcode_test.go b/txscript/opcode_test.go index 3c5abf9da9..91263c21b1 100644 --- a/txscript/opcode_test.go +++ b/txscript/opcode_test.go @@ -23,8 +23,8 @@ func TestOpcodeDisabled(t *testing.T) { OP_LSHIFT, OP_RSHIFT, } for _, opcodeVal := range tests { - pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: nil} - err := opcodeDisabled(&pop, nil) + op := &opcodeArray[opcodeVal] + err := opcodeDisabled(op, nil, nil) if !IsErrorCode(err, ErrDisabledOpcode) { t.Errorf("opcodeDisabled: unexpected error - got %v, "+ "want %v", err, ErrDisabledOpcode)