diff --git a/README.mediawiki b/README.mediawiki index ed4e1dd2..c9cd1451 100644 --- a/README.mediawiki +++ b/README.mediawiki @@ -1171,6 +1171,13 @@ users (see also: [https://en.bitcoin.it/wiki/Economic_majority economic majority | Specification | Closed |- +| [[bip-0346.md|346]] +| Consensus (soft fork) +| OP_TXHASH +| Steven Roose, Brandon Black +| Specification +| Draft +|- | [[bip-0347.mediawiki|347]] | Consensus (soft fork) | OP_CAT in Tapscript diff --git a/bip-0346.md b/bip-0346.md new file mode 100644 index 00000000..2e7b89ec --- /dev/null +++ b/bip-0346.md @@ -0,0 +1,441 @@ +``` + BIP: 346 + Layer: Consensus (soft fork) + Title: OP_TXHASH + Authors: Steven Roose + Brandon Black + Status: Draft + Type: Specification + Assigned: 2024-04-24 + License: BSD-3-Clause +``` + +# Abstract + +This BIP proposes a new opcode `OP_TXHASH`, to be activated as a change to the +semantics of `OP_SUCCESS189` in tapscript contexts. + +This opcode provides a generalized method for introspecting certain details of +the spending transaction, which enables non-interactive enforcement of certain +properties of the transaction spending a certain UTXO. + +Together with an opcode like `OP_CHECKSIGFROMSTACK`, this opcode effectively +provides a fully generalized signature hash construction, fully supporting +all existing SIGHASH flags, the proposed sighash flags from +[BIP-118](bip-0118.mediawiki) (`SIGHASH_ANYPREVOUT`) and many other new signature hash +combinations. + +The constructions specified in this BIP also open up the way for other +potential updates; see Motivation section for more details. + + +# Specification + + +## OP_TXHASH + +`OP_TXHASH` redefines the `OP_SUCCESS189` tapscript opcode (`0xbd`) as a soft +fork upgrade. This opcode is only active in tapscript contexts. + +Note that `OP_SUCCESS187` is used by [BIP-443](bip-0443.mediawiki) (`OP_CHECKCONTRACTVERIFY`) and +`OP_SUCCESS188` was used by [BIP-345](bip-0345.mediawiki) (`OP_VAULT`) at the time of first draft +of this BIP, making `OP_SUCCESS189` the next available opcode for this purpose. + +It has the following semantics: + +* There is at least one element on the stack, fail otherwise. +* The element is interpreted as the TxFieldSelector and is popped off the stack. +* The TxFieldSelector must be valid, fail otherwise. +* The 32-byte TxHash of the transaction at the current input index, calculated + using the given TxFieldSelector is pushed onto the stack. + +## TxFieldSelector + +The TxFieldSelector has the following encoding. We will give a brief conceptual +summary, followed by a reference implementation of the CalculateTxHash function. + +In the following specifications, the `|` operator is used for the bitwise OR +operation. + +* There are two special cases for the TxFieldSelector: + * the empty value, zero bytes long: it is set equal to `TXFS_SPECIAL_TEMPLATE`, + the de-facto default value which means everything except the prevouts and + the prevout scriptPubkeys and amounts. + + Special case `TXFS_SPECIAL_TEMPLATE` is 4 bytes long, as follows: + * 1: `TXFS_VERSION | TXFS_LOCKTIME | TXFS_CURRENT_INPUT_IDX` + * 2: `TXFS_INPUTS_SEQUENCES | TXFS_INPUTS_SCRIPTSIGS | TXFS_OUTPUTS_ALL` + * 3: `TXFS_INOUT_NUMBER | TXFS_INOUT_SELECTION_ALL` + * 4: `TXFS_INOUT_NUMBER | TXFS_INOUT_SELECTION_ALL` + + * If the TxFieldSelector has exactly 1 byte, we use a _short notation_. + It has its 8 bits assigned as follows, from lowest to highest: + * 2/1: Inputs + * 00: `TXFS_INOUT_SELECTION_NONE` + * 01: `TXFS_INOUT_SELECTION_CURRENT` + * 11: `TXFS_INOUT_SELECTION_ALL` + * 4/3: Outputs + * 00: `TXFS_INOUT_SELECTION_NONE` + * 01: `TXFS_INOUT_SELECTION_CURRENT` + * 11: `TXFS_INOUT_SELECTION_ALL` + * 5: `TXFS_INPUTS_PREVOUTS` + * 6: `TXFS_INPUTS_PREV_SCRIPTPUBKEYS | TXFS_INPUTS_PREV_VALUES` + * 7: `TXFS_CURRENT_INPUT_CONTROL_BLOCK | TXFS_CURRENT_INPUT_SPENTSCRIPT | TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS` + * 8: `TXFS_CURRENT_INPUT_IDX` + + Additionally, it includes `TXFS_VERSION | TXFS_LOCKTIME | TXFS_CONTROL | TXFS_CURRENT_INPUT_TAPROOT_ANNEX` + as global fields and `TXFS_INPUTS_SEQUENCES | TXFS_INPUTS_SCRIPTSIGS | TXFS_OUTPUTS_ALL` + as input and output fields. + + These 1-byte selections allow the TxFieldSelector to emulate current + signature hashing modes and those defined in [BIP-118](bip-0118.mediawiki): + +| BIP-341/118 sighash type | 1-byte TxFieldSelector | +| :--------------------------- | :--------------------- | +| `ALL` | `0b11111111` | +| `SINGLE` | `0b11110111` | +| `NONE` | `0b11110011` | +| `ALL|ANYONECANPAY` | `0b11111101` | +| `SINGLE|ANYONECANPAY` | `0b11110101` | +| `NONE|ANYONECANPAY` | `0b11110001` | +| `ALL|ANYPREVOUT` | `0b11101101` | +| `SINGLE|ANYPREVOUT` | `0b11100101` | +| `NONE|ANYPREVOUT` | `0b11100001` | +| `ALL|ANYPREVOUTANYSCRIPT` | `0b11001101` | +| `SINGLE|ANYPREVOUTANYSCRIPT` | `0b11000101` | +| `NONE|ANYPREVOUTANYSCRIPT` | `0b11000001` | + + +* If the TxFieldSelector is longer than one byte, the first byte of the TxFieldSelector + has its 8 bits assigned as follows, from lowest to highest: + * 1: version (`TXFS_VERSION`) + * 2: locktime (`TXFS_LOCKTIME`) + * 3: current input index (`TXFS_CURRENT_INPUT_IDX`) + * 4: current input control block (`TXFS_CURRENT_INPUT_CONTROL_BLOCK`) + * 5: current input spent script (`TXFS_CURRENT_INPUT_SPENTSCRIPT`) + * 6: current script last `OP_CODESEPARATOR` position (or 0xffffffff) + (`TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS`) + * 7: current input annex including prefix byte (or empty) (`TXFS_CURRENT_INPUT_TAPROOT_ANNEX`) + * 8: `TXFS_CONTROL` (i.e. include TxFieldSelector into hash) + +* The highest bit of the first byte (`TXFS_CONTROL`), we will call the + "control bit", and it can be used to control the behavior of the opcode. For + `OP_TXHASH`, the control bit is used to determine + whether the TxFieldSelector itself has to be included in the resulting hash. + (For potential other uses of the TxFieldSelector (like a hypothetical + `OP_TX`), this bit can be repurposed.) + +* The second byte will be used to indicate fields from the inputs and outputs. + The 8 bits are assigned the following variables, from lowest to highest: + * Specifying which fields of the inputs will be selected: + * 1: prevouts (`TXFS_INPUTS_PREVOUTS`) + * 2: sequences (`TXFS_INPUTS_SEQUENCES`) + * 3: scriptSigs (`TXFS_INPUTS_SCRIPTSIGS`) + * 4: prevout scriptPubkeys (`TXFS_INPUTS_PREV_SCRIPTPUBKEYS`) + * 5: prevout values (`TXFS_INPUTS_PREV_VALUES`) + * 6: taproot annexes (`TXFS_INPUTS_TAPROOT_ANNEXES`) + + * Specifying which fields of the outputs will be selected: + * 7: scriptPubkeys (`TXFS_OUTPUTS_SCRIPTPUBKEYS`) + * 8: values (`TXFS_OUTPUTS_VALUES`) + +* We define as follows: + * `TXFS_ALL = TXFS_VERSION | TXFS_LOCKTIME | TXFS_CURRENT_INPUT_IDX | TXFS_CURRENT_INPUT_CONTROL_BLOCK | TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS | TXFS_CONTROL` + * `TXFS_INPUTS_ALL = TXFS_INPUTS_PREVOUTS | TXFS_INPUTS_SEQUENCES | TXFS_INPUTS_SCRIPTSIGS | TXFS_INPUTS_PREV_SCRIPTPUBKEYS | TXFS_INPUTS_PREV_VALUES | TXFS_INPUTS_TAPROOT_ANNEXES` + * `TXFS_OUTPUTS_ALL = TXFS_OUTPUTS_SCRIPTPUBKEYS | TXFS_OUTPUTS_VALUES` + + +* For both inputs and then outputs, expect an additional byte as follows: + * The highest bit (`TXFS_INOUT_NUMBER`) indicates whether the "number of + in-/outputs" should be committed to. + * For the remaining bits, there are three exceptional values: + * 0x00 (`TXFS_INOUT_SELECTION_NONE`) means "no in/outputs" (hence only the + number of them as `0x80` (`TXFS_INOUT_NUMBER`)). + * `0x40` (`TXFS_INOUT_SELECTION_CURRENT`) means "select only the in/output + of the current input index" (it is invalid when current index exceeds + number of outputs). + * `0x3f` (`TXFS_INOUT_SELECTION_ALL`) means "select all in/outputs". + + * The second highest bit (`TXFS_INOUT_SELECTION_MODE`) is the "specification mode": + * Set to 0 it means "leading mode". + * Set to 1 it means "individual mode". + + * In "leading mode", the third highest bit (`TXFS_INOUT_LEADING_SIZE`) is + used to indicate the "index size", i.e. the number of bytes will be used to + represent the number of in/output. + * With "index size" set to 0, the remaining lowest 5 bits of the first byte + will be interpreted as the number of leading in/outputs to select. + * With "index size" set to 1, the remaining lowest 5 bits of the first byte + together with the 8 bits of the next byte will be interpreted as the + number of leading in/outputs to select. + + * In "individual mode", the third highest bit (`TXFS_INOUT_INDIVIDUAL_MODE`) + indicates whether we are passing absolute indices (0) or indices relative + to the current input (1), the remaining lowest 5 bits will be interpreted + as `n`, the number of individual in/outputs follow. + * In absolute mode (second highest bit is 0), for each of the `n` indices, + at least one extra byte is expected. + * If that byte's highest bit is set to 0, the remaining 7 bits represent + the absolute index to select. + * If that byte's highest bit is set to 1, the remaining 7 bits, together + with the next byte's 8 bits represent the absolute index to select. + * In relative mode (second highest bit is 1), for each of the `n` indices, + at least one extra byte is expected. + * If that byte's highest bit is set to 0, the remaining 7 bits represent + the relative index in two's complement. + * If that byte's highest bit is set to 1, the remaining 7 bits, together + with the next byte's 8 bits represent the relative index in two's + complement. + + +Effectively, this allows a user to select +* all in/outputs +* the current input index +* the leading in/outputs up to 8,191 +* up to 32 individually selected in/outputs +** using absolute indices up to 32,767 +** using indices relative to the current input index from -16382 to +16383. + + +### TxFieldSelector malleability + +It is possible to represent the same selected data using multiple different +TxFieldSelectors. For this reason, users are advised to always set the +`TXFS_CONTROL` field flag that commits to the TxFieldSelector that was used +to get the hash. + + +### Visualization + +* first byte + +``` +1 1 1 1 1 1 1 1 +| | | | | | | ^ version +| | | | | | ^ locktime +| | | | | ^ current input index +| | | | ^ current input control block +| | | ^ current input spent script +| | ^ current script last OP_CODESEPARATOR +| ^ current input taproot annex +^ control bit (ie. include TXFS in hash) +``` + +* second byte + +``` +<-> outputs +| | <---------> inputs +1 1 1 1 1 1 1 1 +| | | | | | | ^ prevouts +| | | | | | ^ sequences +| | | | | ^ scriptSigs +| | | | ^ prevout scriptPubkeys +| | | ^ prevout values +| | ^ taproot annexes +| ^ scriptPubkeys +^ values +``` + +* in/output selector byte + +"leading 3 in/outputs" +``` +1 0 0 0 0 0 1 1 +| | | <-------> integer 0b00011 == 3 +| | ^ index size: single byte +| ^ leading mode +^ commit the number of in/outputs +``` + +"leading 257 in/outputs" +``` +1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 +| | | <------------------------> integer 0b00001 00000001 == 257 +| | ^ index size 1: two bytes +| ^ leading mode +^ commit the number of in/outputs +``` + +"indices 1 and 3" +``` +0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 +| | | | <--------------> second idx: 3 +| | | | <-------------> first idx: 1 +| | | | <-----> selection count: 0b0010 == 2 indices +| | | ^ index size: single byte per index +| | ^ absolute index +| ^ individual mode +^ don't commit the number of in/outputs +``` + +* total example + +``` +ff ff c2 01 03 83 + | | ^ commit number of outputs + leading 3 outputs + | | <------> commit number of inputs + inputs at indices 1 and 3 + | ^ all input and output fields + ^ all regular fields +``` + + +## Resource limits + +Using the same validation budget ("sigops budget") introduced in BIP-0342, +each TransactionHash decreases the validation budget by 25. If this brings the +budget below zero, the script fails immediately.
The following +considerations should be made: + +* All fields that can be of arbitrary size are cacheable as TransactionHash + always hashes their hashed values. +* In "individual mode", a user can at most commit 32 inputs or outputs, + which we don't consider excessive for potential repeated use. +* In "leading mode", a caching strategy can be used where the SHA256 context + is stored every N in/outputs so that multiple executions of the + TransactionHash function can use the caches and only have to hash an + additional N-1 items at most. + + +# Motivation + +This BIP specifies a basic transaction introspection primitive that is useful +to either reduce interactivity in multi-user protocols or to enforce some basic +constraints on transactions. + +Additionally, the constructions specified in this BIP can lay the groundwork for +some potential future upgrades: +* The TxFieldSelector construction would work well with a hypothetical opcode + `OP_TX` that allows for directly introspecting the transaction by putting the + fields selected on the stack instead of hashing them together. +* The TransactionHash obtained by `OP_TXHASH` can be combined with `OP_CHECKSIGFROMSTACK` + (see [BIP-348](bip-0348.md)) to effectively create an + incredibly flexible signature hash, which would enable constructions like + `SIGHASH_ANYPREVOUT`. +* The TransactionHash obtained by `OP_TXHASH` can be introduced as a native + sighash calculation in a future segwit upgrade, so that signatures using the + TransactionHash as their sighash can be used in keyspend context. + + +## Comparing with some alternative proposals + +* This proposal strictly generalizes BIP-119's `OP_CHECKTEMPLATEVERIFY`, as the + default mode of our TxFieldSelector is semantically the same (though not + byte-for-byte identical) as what `OP_CTV` accomplishes, without costing any + additional bytes. Additionally, using `OP_TXHASH` allows for more + flexibility which can help in the case for + * enabling adding fees to a transaction without breaking a multi-tx protocol; + * multi-user protocols where users are only concerned about their own inputs and outputs. + +* Constructions like `OP_IN_OUT_VALUE` used with `OP_EQUALVERIFY` can be + emulated by two `OP_TXHASH` instances by using the TxFieldSelector to select + a single input value first and a single output value second and enforcing + equality on the hashes. Neither of these alternatives can be used to enforce + small value differences without the availability of 64-bit arithmetic in + Script. + +* Like mentioned above, `SIGHASH_ANYPREVOUT` can be emulated using `OP_TXHASH` + when combined with `OP_CHECKSIGFROMSTACK`: + ` OP_TXHASH OP_CHECKSIGFROMSTACK` effectively emulates `SIGHASH_ANYPREVOUT`. + + +# Reference Implementation + +A reference implementation in Rust is provided attached as part of this BIP +together with a JSON file of test vectors generated using the reference +implementation. + + +# Design Considerations + +This specification in in _Draft_ and there is definitely still room for feedback +and improvements. Some considerations that were made but could be revisited: + +- The `0b10` in/output selector for the shorthand is unused. + Could possibly be filled in with "current + next", "current + previous" or + any other semantics. +- The individual index selection semantics allow for absolute indices up to ~32k + and relative ones up to +/- ~16k, which is probably excessive. When removing + the second byte there would reduce that to just 256 and +/- 128 respectively. +- Similar to `OP_TEMPLATEHASH` (BIP number to be assigned, [PR](https://github.com/bitcoin/bips/pull/1974)), + we could not support `scriptSigs` anymore and remove + `TXFS_INPUTS_SCRIPTSIGS`. This field could then possibly be repurposed. + + +# Backwards Compatibility + +`OP_TXHASH` replaces `OP_SUCCESS189`. The `SUCCESS` opcodes were +introduced in taproot (BIP-342) to support changing the semantics of opcodes in +ways that do allow the new semantics to change the stack. For this reason, +`OP_TXHASH` only works in tapscript context. Since it is overriding a `SUCCESS` +opcode, any older version of the software will always accept any script that +uses the opcode, while the new versions of the software will validate the +scripts according to the semantics outlined in this BIP. As such, this is also a +soft fork change. + +## Interactions with other BIPs + +This proposal interacts with several other BIPs: + +* **[BIP-118](bip-0118.mediawiki) (SIGHASH_ANYPREVOUT)**: `OP_TXHASH` can be combined with + `OP_CHECKSIGFROMSTACK` (BIP-348) to emulate `SIGHASH_ANYPREVOUT` and other + signature hash modes defined in BIP-118. The 1-byte TxFieldSelector format + explicitly supports these modes. + +* **[BIP-119](bip-0119.mediawiki) (OP_CHECKTEMPLATEVERIFY)**: `OP_TXHASH` with the empty + TxFieldSelector produces a hash semantically equivalent to BIP-119's + `OP_CHECKTEMPLATEVERIFY`, making this proposal a generalization of BIP-119. + +* **[BIP-347](bip-0347.mediawiki) (OP_CAT)**: When combined with `OP_CAT`, `OP_TXHASH` enables + powerful transaction introspection capabilities. The bit encoding format is + designed to be explicit about endianness to ensure correct interaction with + concatenation operations. + +* **[BIP-348](bip-0348.md) (OP_CHECKSIGFROMSTACK)**: Together with `OP_CHECKSIGFROMSTACK`, + `OP_TXHASH` provides a fully generalized signature hash construction, + enabling flexible covenant designs and multi-user protocols. + + +# Implementation + +A reference implementation is included as part of the BIP, see +[here](./bip-0346/ref-impl/src/main.rs). This implementation focusses on clarity +and correctness, not on efficiency. A rudimentary set of test vectors is also +generated from this implementation and included +[here](./bip-0346/ref-impl/txhash_vectors.json). + +Furthermore, following other implementation attempts exist: + +* A proposed implementation for Bitcoin Core is available here: + https://github.com/bitcoin/bitcoin/pull/29050 +* A proposed implementation for rust-bitcoin is available here: + https://github.com/rust-bitcoin/rust-bitcoin/pull/2275 + +NOTE: These implementations are slightly outdated as they were made for an +earlier version of this specification. Updates are in progress. + +Both of the above implementations perform effective caching to avoid potential +denial-of-service attack vectors. + + +# Deployment + +This BIP can be deployed using a BIP 9 VersionBits deployment. The specific +strategy and bit assignment are left unspecified and can later be amended to the +BIP depending on community preference. + + +# Acknowledgement + +Credit for this proposal mostly goes to Jeremy Rubin for his work on BIP-119's +`OP_CHECKTEMPLATEVERIFY` and to Russell O'Connor for the original idea of +generalizing `OP_CHECKTEMPLATEVERIFY` into `OP_TXHASH`. + +Additional thanks to Andrew Poelstra, Greg Sanders, Rearden Code, Rusty Russell +and others for their feedback on the specification. + + +# Copyright + +This document is licensed under the 3-clause BSD license. + diff --git a/bip-0346/ref-impl/Cargo.lock b/bip-0346/ref-impl/Cargo.lock new file mode 100644 index 00000000..fd4f7d6b --- /dev/null +++ b/bip-0346/ref-impl/Cargo.lock @@ -0,0 +1,206 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "txhash-ref" +version = "0.0.0" +dependencies = [ + "bitcoin", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/bip-0346/ref-impl/Cargo.toml b/bip-0346/ref-impl/Cargo.toml new file mode 100644 index 00000000..79c8cd29 --- /dev/null +++ b/bip-0346/ref-impl/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "txhash-ref" +version = "0.0.0" +edition = "2021" + +[dependencies] +bitcoin = { version = "=0.32.8", features = [ "serde" ] } +serde_json = "1" diff --git a/bip-0346/ref-impl/src/main.rs b/bip-0346/ref-impl/src/main.rs new file mode 100644 index 00000000..b5193588 --- /dev/null +++ b/bip-0346/ref-impl/src/main.rs @@ -0,0 +1,693 @@ + +use bitcoin::{Transaction, TxOut}; +use bitcoin::consensus::encode::Encodable; +use bitcoin::hashes::{sha256, Hash, HashEngine}; + +pub const TXFS_VERSION: u8 = 1 << 0; +pub const TXFS_LOCKTIME: u8 = 1 << 1; +pub const TXFS_CURRENT_INPUT_IDX: u8 = 1 << 2; +pub const TXFS_CURRENT_INPUT_CONTROL_BLOCK: u8 = 1 << 3; +pub const TXFS_CURRENT_INPUT_SPENTSCRIPT: u8 = 1 << 4; +pub const TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS: u8 = 1 << 5; +pub const TXFS_CURRENT_INPUT_TAPROOT_ANNEX: u8 = 1 << 6; +pub const TXFS_CONTROL: u8 = 1 << 7; + +pub const TXFS_INPUTS_PREVOUTS: u8 = 1 << 0; +pub const TXFS_INPUTS_SEQUENCES: u8 = 1 << 1; +pub const TXFS_INPUTS_SCRIPTSIGS: u8 = 1 << 2; +pub const TXFS_INPUTS_PREV_SCRIPTPUBKEYS: u8 = 1 << 3; +pub const TXFS_INPUTS_PREV_VALUES: u8 = 1 << 4; +pub const TXFS_INPUTS_TAPROOT_ANNEXES: u8 = 1 << 5; +pub const TXFS_OUTPUTS_SCRIPTPUBKEYS: u8 = 1 << 6; +pub const TXFS_OUTPUTS_VALUES: u8 = 1 << 7; + +pub const TXFS_INPUTS_ALL: u8 = TXFS_INPUTS_PREVOUTS + | TXFS_INPUTS_SEQUENCES + | TXFS_INPUTS_SCRIPTSIGS + | TXFS_INPUTS_PREV_SCRIPTPUBKEYS + | TXFS_INPUTS_PREV_VALUES + | TXFS_INPUTS_TAPROOT_ANNEXES; +pub const TXFS_OUTPUTS_ALL: u8 = TXFS_OUTPUTS_SCRIPTPUBKEYS | TXFS_OUTPUTS_VALUES; + +pub const TXFS_INOUT_NUMBER: u8 = 1 << 7; +pub const TXFS_INOUT_SELECTION_NONE: u8 = 0x00; +pub const TXFS_INOUT_SELECTION_CURRENT: u8 = 0x40; +pub const TXFS_INOUT_SELECTION_ALL: u8 = 0x3f; +pub const TXFS_INOUT_SELECTION_MODE: u8 = 1 << 6; +pub const TXFS_INOUT_LEADING_SIZE: u8 = 1 << 5; +pub const TXFS_INOUT_INDIVIDUAL_MODE: u8 = 1 << 5; +pub const TXFS_INOUT_SELECTION_MASK: u8 = 0xff ^ (1 << 7) ^ (1 << 6) ^ (1 << 5); + + +pub const TXFS_SPECIAL_TEMPLATE: [u8; 4] = [ + TXFS_VERSION | TXFS_LOCKTIME | TXFS_CURRENT_INPUT_IDX, + TXFS_INPUTS_SEQUENCES | TXFS_INPUTS_SCRIPTSIGS | TXFS_OUTPUTS_ALL, + TXFS_INOUT_NUMBER | TXFS_INOUT_SELECTION_ALL, + TXFS_INOUT_NUMBER | TXFS_INOUT_SELECTION_ALL, +]; + +const SHA256_EMPTY: sha256::Hash = sha256::Hash::const_hash(&[]); + +/// Interpret the bits of the input byte as a signed 7-bit integer and return the +/// value as an i8. +fn read_i7(input: u8) -> i8 { + let masked = input & 0x7f; + if (masked & 0x40) == 0 { + masked as i8 + } else { + 0i8 - ((!(masked-1)) & 0x7f) as i8 + } +} + +/// Interpret the bits of the input bytes as a signed 15-bit integer and return the +/// value as an i16. +fn read_i15(input: u16) -> i16 { + let masked = input & 0x7fff; + if (masked & 0x4000) == 0 { + masked as i16 + } else { + 0i16 - ((!(masked-1)) & 0x7fff) as i16 + } +} + +fn convert_short_txfs(txfs: u8) -> Result<[u8; 4], &'static str> { + let mut base = TXFS_VERSION | TXFS_LOCKTIME | TXFS_CONTROL | TXFS_CURRENT_INPUT_TAPROOT_ANNEX; + let mut inout_fields = TXFS_OUTPUTS_ALL | TXFS_INPUTS_SEQUENCES | TXFS_INPUTS_SCRIPTSIGS; + + let input_selection = match txfs & 0b00000011 { + 0b00000000 => TXFS_INOUT_SELECTION_NONE, + 0b00000001 => TXFS_INOUT_SELECTION_CURRENT, + 0b00000011 => TXFS_INOUT_SELECTION_ALL, + _ => return Err("0b10 is not a valid input selection"), + }; + let output_selection = match txfs & 0b00001100 { + 0b00000000 => TXFS_INOUT_SELECTION_NONE, + 0b00000100 => TXFS_INOUT_SELECTION_CURRENT, + 0b00001100 => TXFS_INOUT_SELECTION_ALL, + _ => return Err("0b10 is not a valid output selection"), + }; + + if txfs & 0b00010000 != 0 { + inout_fields = inout_fields | TXFS_INPUTS_PREVOUTS; + } + + if txfs & 0b00100000 != 0 { + inout_fields = inout_fields | TXFS_INPUTS_PREV_SCRIPTPUBKEYS | TXFS_INPUTS_PREV_VALUES; + } + + if txfs & 0b01000000 != 0 { + base = base | TXFS_CURRENT_INPUT_CONTROL_BLOCK | TXFS_CURRENT_INPUT_SPENTSCRIPT + | TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS; + } + + if txfs & 0b10000000 != 0 { + base = base | TXFS_CURRENT_INPUT_IDX; + } + + Ok([base, inout_fields, input_selection, output_selection]) +} + +/// Parse an input or output selection from the TxFieldSelector bytes. +/// +/// Returns the selected indices and a flag whether to commit the number of items. +fn parse_inout_selection( + first_byte: u8, + bytes: &mut impl Iterator, + nb_items: usize, + current_input_idx: u32, +) -> Result<(Vec, bool), &'static str> { + let commit_number = (first_byte & TXFS_INOUT_NUMBER) != 0; + let selection = first_byte & (0xff ^ TXFS_INOUT_NUMBER); + + let selected = if selection == TXFS_INOUT_SELECTION_NONE { + vec![] + } else if selection == TXFS_INOUT_SELECTION_ALL { + (0..nb_items).collect() + } else if selection == TXFS_INOUT_SELECTION_CURRENT { + if current_input_idx as usize >= nb_items { + // NB can only happen for outputs + return Err("current input index exceeds number of outputs and current output selected"); + } + vec![current_input_idx as usize] + } else if (selection & TXFS_INOUT_SELECTION_MODE) == 0 { + // leading mode + let count = if (selection & TXFS_INOUT_LEADING_SIZE) == 0 { + (selection & TXFS_INOUT_SELECTION_MASK) as usize + } else { + let next_byte = bytes.next().ok_or("second leading selection byte missing")?; + (((selection & TXFS_INOUT_SELECTION_MASK) as usize) << 8) + next_byte as usize + }; + assert_ne!(count, 0, "this should be interpreted as NONE above"); + if count > nb_items { + return Err("selected number of leading in/outputs out of bounds"); + } + (0..count).collect() + } else { + // individual mode + let absolute = (selection & TXFS_INOUT_INDIVIDUAL_MODE) == 0; + + let count = (selection & TXFS_INOUT_SELECTION_MASK) as usize; + + let mut selected = Vec::with_capacity(count as usize); + for _ in 0..count { + let first = bytes.next().ok_or("expected an index byte")?; + let single_byte = (first & (1 << 7)) == 0; + let number = if single_byte { + first as usize + } else { + let next_byte = bytes.next().ok_or("expected another index byte")?; + (((first & (1 << 7)) as usize) << 8) + next_byte as usize + }; + + let idx = if absolute { + number + } else { + let rel = if single_byte { + read_i7(number as u8) as isize + } else { + read_i15(number as u16) as isize + }; + + if rel.is_negative() && rel.abs() > current_input_idx as isize { + return Err("relative index out of bounds"); + } + (current_input_idx as isize + rel) as usize + }; + + if idx > nb_items { + return Err("selected index out of bounds"); + } + if let Some(last) = selected.last() { + if idx <= *last { + return Err("selected indices not in increasing order") + } + } + selected.push(idx); + } + selected + }; + Ok((selected, commit_number)) +} + +/// +/// +/// Assumes that TxFieldSelector is valid. +pub fn calculate_txhash( + txfs: &[u8], + tx: &Transaction, + prevouts: &[TxOut], + current_input_idx: u32, + current_input_last_codeseparator_pos: Option, +) -> Result { + assert_eq!(tx.input.len(), prevouts.len()); + + let txfs = if txfs.is_empty() { + TXFS_SPECIAL_TEMPLATE.to_vec() + } else if txfs.len() == 1 { + convert_short_txfs(txfs[0])?.to_vec() + } else { + txfs.to_vec() + }; + let txfs = &txfs; + + let mut engine = sha256::Hash::engine(); + + if (txfs[0] & TXFS_CONTROL) != 0 { + engine.input(txfs); + } + + let mut bytes = txfs.iter().copied().peekable(); + let global = bytes.next().unwrap(); + + if (global & TXFS_VERSION) != 0 { + tx.version.consensus_encode(&mut engine).unwrap(); + } + + if (global & TXFS_LOCKTIME) != 0 { + tx.lock_time.consensus_encode(&mut engine).unwrap(); + } + + if (global & TXFS_CURRENT_INPUT_IDX) != 0 { + (current_input_idx as u32).consensus_encode(&mut engine).unwrap(); + } + + let current_prevout = &prevouts[current_input_idx as usize]; + let current_input = &tx.input[current_input_idx as usize]; + + if (global & TXFS_CURRENT_INPUT_CONTROL_BLOCK) != 0 { + assert!(current_prevout.script_pubkey.is_p2tr(), "only active in taproot context"); + if let Some(cb) = current_input.witness.taproot_control_block() { + engine.input(&sha256::Hash::hash(&cb)[..]); + } else { + // keyspend + engine.input(&SHA256_EMPTY[..]); + } + } + + if (global & TXFS_CURRENT_INPUT_SPENTSCRIPT) != 0 { + assert!(current_prevout.script_pubkey.is_p2tr(), "only active in taproot context"); + if let Some(script) = current_input.witness.taproot_leaf_script() { + let mut eng = sha256::Hash::engine(); + script.version.to_consensus().consensus_encode(&mut eng).unwrap(); + script.script.consensus_encode(&mut eng).unwrap(); + engine.input(&sha256::Hash::from_engine(eng)[..]); + } else { + engine.input(&SHA256_EMPTY[..]); + } + } + + if (global & TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS) != 0 { + let pos = current_input_last_codeseparator_pos.unwrap_or(u32::MAX); + (pos as u32).consensus_encode(&mut engine).unwrap(); + } + + if (global & TXFS_CURRENT_INPUT_TAPROOT_ANNEX) != 0 { + if let Some(annex) = current_input.witness.taproot_annex() { + engine.input(&sha256::Hash::hash(annex)[..]); + } else { + engine.input(&SHA256_EMPTY[..]); + } + } + + let inout_fields = bytes.next().unwrap_or(0x00); + + // Inputs + let (input_selection, commit_number_inputs) = if let Some(first_byte) = bytes.next() { + parse_inout_selection(first_byte, &mut bytes, tx.input.len(), current_input_idx)? + } else { + (vec![], false) + }; + + if commit_number_inputs { + (tx.input.len() as u32).consensus_encode(&mut engine).unwrap(); + } + + if !input_selection.is_empty() && (inout_fields & TXFS_INPUTS_PREVOUTS) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &input_selection { + tx.input[*i].previous_output.consensus_encode(&mut engine).unwrap(); + } + sha256::Hash::from_engine(engine) + }; + engine.input(&hash[..]); + } + + if !input_selection.is_empty() && (inout_fields & TXFS_INPUTS_SEQUENCES) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &input_selection { + tx.input[*i].sequence.consensus_encode(&mut engine).unwrap(); + } + sha256::Hash::from_engine(engine) + }; + engine.input(&hash[..]); + } + + if !input_selection.is_empty() && (inout_fields & TXFS_INPUTS_SCRIPTSIGS) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &input_selection { + engine.input(&sha256::Hash::hash(&tx.input[*i].script_sig.as_bytes())[..]); + } + sha256::Hash::from_engine(engine) + }; + engine.input(&hash[..]); + } + + if !input_selection.is_empty() && (inout_fields & TXFS_INPUTS_PREV_SCRIPTPUBKEYS) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &input_selection { + engine.input(&sha256::Hash::hash(&prevouts[*i].script_pubkey.as_bytes())[..]); + } + sha256::Hash::from_engine(engine) + }; + engine.input(&hash[..]); + } + + if !input_selection.is_empty() && (inout_fields & TXFS_INPUTS_PREV_VALUES) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &input_selection { + prevouts[*i].value.consensus_encode(&mut engine).unwrap(); + } + sha256::Hash::from_engine(engine) + }; + engine.input(&hash[..]); + } + + if !input_selection.is_empty() && (inout_fields & TXFS_INPUTS_TAPROOT_ANNEXES) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &input_selection { + if prevouts[*i].script_pubkey.is_p2tr() { + if let Some(annex) = tx.input[*i].witness.taproot_annex() { + engine.input(&sha256::Hash::hash(annex)[..]); + } else { + engine.input(&SHA256_EMPTY[..]); + } + } else { + engine.input(&SHA256_EMPTY[..]); + } + } + sha256::Hash::from_engine(engine) + }; + engine.input(&hash[..]); + } + + // Outputs + let (output_selection, commit_number_outputs) = if let Some(first_byte) = bytes.next() { + parse_inout_selection(first_byte, &mut bytes, tx.output.len(), current_input_idx)? + } else { + (vec![], false) + }; + + if commit_number_outputs { + (tx.output.len() as u32).consensus_encode(&mut engine).unwrap(); + } + + if !output_selection.is_empty() && (inout_fields & TXFS_OUTPUTS_SCRIPTPUBKEYS) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &output_selection { + engine.input(&sha256::Hash::hash(&tx.output[*i].script_pubkey.as_bytes())[..]); + } + sha256::Hash::from_engine(engine) + }; + hash.consensus_encode(&mut engine).unwrap(); + } + + if !output_selection.is_empty() && (inout_fields & TXFS_OUTPUTS_VALUES) != 0 { + let hash = { + let mut engine = sha256::Hash::engine(); + for i in &output_selection { + tx.output[*i].value.consensus_encode(&mut engine).unwrap(); + } + sha256::Hash::from_engine(engine) + }; + hash.consensus_encode(&mut engine).unwrap(); + } + + if bytes.next().is_some() { + return Err("unexpected additional txfs bytes"); + } + Ok(sha256::Hash::from_engine(engine)) +} + +mod test_vectors { + use super::*; + use std::any::Any; + use std::ops::{self, RangeBounds}; + use bitcoin::hex::DisplayHex; + use bitcoin::{Amount, ScriptBuf, Sequence, Witness}; + use bitcoin::blockdata::transaction::{self, TxIn}; + use bitcoin::opcodes::all::*; + + fn test_vector_tx() -> (Transaction, Vec) { + let tx = Transaction { + version: transaction::Version::TWO, + lock_time: bitcoin::absolute::LockTime::from_consensus(42), + input: vec![ + TxIn { + previous_output: "3333333333333333333333333333333333333333333333333333333333333333:3".parse().unwrap(), + script_sig: ScriptBuf::new(), + sequence: Sequence::from_consensus(2), + witness: { + let mut buf = Witness::new(); + buf.push(vec![0x12]); + buf + }, + }, + TxIn { + previous_output: "4444444444444444444444444444444444444444444444444444444444444444:4".parse().unwrap(), + script_sig: ScriptBuf::new(), + sequence: Sequence::from_consensus(3), + witness: { + let mut buf = Witness::new(); + buf.push(vec![0x13]); + buf.push(vec![0x14]); + buf.push(vec![0x50, 0x42]); // annex + buf + }, + }, + TxIn { + previous_output: "1111111111111111111111111111111111111111111111111111111111111111:1".parse().unwrap(), + script_sig: vec![0x23].into(), + sequence: Sequence::from_consensus(1), + witness: Witness::new(), + }, + TxIn { + previous_output: "2222222222222222222222222222222222222222222222222222222222222222:2".parse().unwrap(), + script_sig: ScriptBuf::new(), + sequence: Sequence::from_consensus(3), + witness: { // p2wsh annex-like stack element + let mut buf = Witness::new(); + buf.push(vec![0x13]); + buf.push(vec![0x14]); + buf.push(vec![0x50, 0x42]); // annex + buf + }, + }, + ], + output: vec![ + TxOut { + script_pubkey: vec![OP_PUSHNUM_6.to_u8()].into(), + value: Amount::from_sat(350), + }, + TxOut { + script_pubkey: vec![OP_PUSHNUM_7.to_u8()].into(), + value: Amount::from_sat(351), + }, + TxOut { + script_pubkey: vec![OP_PUSHNUM_8.to_u8()].into(), + value: Amount::from_sat(353), + }, + ], + }; + let prevs = vec![ + TxOut { + script_pubkey: vec![ // p2tr + 0x51, 0x20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ].into(), + value: Amount::from_sat(361), + }, + TxOut { + script_pubkey: vec![ // p2tr + 0x51, 0x20, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ].into(), + value: Amount::from_sat(362), + }, + TxOut { + script_pubkey: vec![OP_PUSHNUM_16.to_u8()].into(), + value: Amount::from_sat(360), + }, + TxOut { + script_pubkey: vec![ // p2wsh + 0x00, 0x20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ].into(), + value: Amount::from_sat(361), + }, + ]; + (tx, prevs) + } + + #[derive(Debug)] + struct TestCase { + tx: Transaction, + prevs: Vec, + vectors: Vec + } + + #[derive(Debug)] + struct TestVector { + txfs: Vec, + input: usize, + codeseparator: Option, + txhash: sha256::Hash, + } + + fn generate_vectors() -> Vec { + let all = 0xff; + let allio = TXFS_INPUTS_ALL | TXFS_OUTPUTS_ALL; + let selnone = TXFS_INOUT_SELECTION_NONE; // 0x00 + let selcur = TXFS_INOUT_SELECTION_CURRENT; + let selall = TXFS_INOUT_SELECTION_ALL; + let number = TXFS_INOUT_NUMBER; + let leading = 0; + let individual = TXFS_INOUT_SELECTION_MODE; + let absolute = 0; + let relative = TXFS_INOUT_INDIVIDUAL_MODE; + + fn r + 'static>(t: T) -> Option> { + Some(Box::new(t)) + } + + // txfs and range of inputs to run it on + let selectors: &[(&[u8], Option>)] = &[ + // global + (&[1 << 0, 0], None), + (&[1 << 1, 0], None), + (&[1 << 2, 0], None), + (&[1 << 3, 0], None), + (&[1 << 4, 0], None), + (&[1 << 5, 0], None), + (&[1 << 6, 0], None), + (&[1 << 7, 0], None), + // outputs + (&[all, 0, 0, number | selnone], None), + (&[all, TXFS_OUTPUTS_SCRIPTPUBKEYS, 0, selcur], None), + (&[all, TXFS_OUTPUTS_VALUES, 0, selcur], None), + (&[all, TXFS_OUTPUTS_ALL, 0, selcur], None), + (&[all, TXFS_OUTPUTS_SCRIPTPUBKEYS, 0, selall], None), + (&[all, TXFS_OUTPUTS_VALUES, 0, selall], None), + (&[all, TXFS_OUTPUTS_ALL, 0, selall], None), + (&[all, TXFS_OUTPUTS_SCRIPTPUBKEYS, 0, number | selcur], None), + (&[all, TXFS_OUTPUTS_VALUES, 0, number | selcur], None), + (&[all, TXFS_OUTPUTS_ALL, 0, number | selcur], None), + (&[all, TXFS_OUTPUTS_SCRIPTPUBKEYS, 0, number | selall], None), + (&[all, TXFS_OUTPUTS_VALUES, 0, number | selall], None), + (&[all, TXFS_OUTPUTS_ALL, 0, number | selall], None), + // inputs + (&[all, 0, number | selnone], None), + (&[all, TXFS_INPUTS_PREVOUTS, selcur], None), + (&[all, TXFS_INPUTS_SEQUENCES, selcur], None), + (&[all, TXFS_INPUTS_SCRIPTSIGS, selcur], None), + (&[all, TXFS_INPUTS_PREV_SCRIPTPUBKEYS, selcur], None), + (&[all, TXFS_INPUTS_PREV_VALUES, selcur], None), + (&[all, TXFS_INPUTS_TAPROOT_ANNEXES, selcur], None), + (&[all, TXFS_INPUTS_ALL, selcur], None), + (&[all, TXFS_INPUTS_PREVOUTS, selall], None), + (&[all, TXFS_INPUTS_SEQUENCES, selall], None), + (&[all, TXFS_INPUTS_SCRIPTSIGS, selall], None), + (&[all, TXFS_INPUTS_PREV_SCRIPTPUBKEYS, selall], None), + (&[all, TXFS_INPUTS_PREV_VALUES, selall], None), + (&[all, TXFS_INPUTS_TAPROOT_ANNEXES, selall], None), + (&[all, TXFS_INPUTS_ALL, selall], None), + (&[all, TXFS_INPUTS_PREVOUTS, number | selcur], None), + (&[all, TXFS_INPUTS_SEQUENCES, number | selcur], None), + (&[all, TXFS_INPUTS_SCRIPTSIGS, number | selcur], None), + (&[all, TXFS_INPUTS_PREV_SCRIPTPUBKEYS, number | selcur], None), + (&[all, TXFS_INPUTS_PREV_VALUES, number | selcur], None), + (&[all, TXFS_INPUTS_TAPROOT_ANNEXES, number | selcur], None), + (&[all, TXFS_INPUTS_ALL, number | selcur], None), + (&[all, TXFS_INPUTS_PREVOUTS, number | selall], None), + (&[all, TXFS_INPUTS_SEQUENCES, number | selall], None), + (&[all, TXFS_INPUTS_SCRIPTSIGS, number | selall], None), + (&[all, TXFS_INPUTS_PREV_SCRIPTPUBKEYS, number | selall], None), + (&[all, TXFS_INPUTS_PREV_VALUES, number | selall], None), + (&[all, TXFS_INPUTS_TAPROOT_ANNEXES, number | selall], None), + (&[all, TXFS_INPUTS_ALL, number | selall], None), + // both + (&[all, allio, selall, selall], None), + (&[all, allio, selcur, selcur], None), + (&[all, 0, number | selnone, number | selnone], None), + (&[all, allio, number | selall, number | selall], None), + (&[all, allio, number | selcur, number | selcur], None), + (&[all, allio, selcur, selall], None), + (&[all, allio, selall, selcur], None), + // leading + (&[all, allio, leading | 0x01, number | leading | 0x02], None), + (&[all, allio, number | selcur, leading | 0x02], None), + // individual absolute + (&[all, allio, individual | absolute | 0x01, 0x01, + individual | absolute | 0x02, 0x00, 0x02], None), + (&[all, allio, number | individual | absolute | 0x01, 0x01, + number | individual | absolute | 0x02, 0x00, 0x02], None), + // individual relative + (&[all, allio, individual | relative | 0x01, (-1i8 as u8) >> 1, + individual | relative | 0x02, (-1i8 as u8) >> 1, 0], r(1..2)), + (&[all, allio, number | individual | relative | 0x01, (-1i8 as u8) >> 1, + number | individual | relative | 0x02, (-1i8 as u8) >> 1, 0], r(1..2)), + //TODO(stevenroose) test index size, but for that we need > 32 in/outputs + // special case template + (&[], None), + // shorthand txfs, sighash examples + (&[0b11111111], None), + (&[0b11110111], None), + (&[0b11110011], None), + (&[0b11111101], None), + (&[0b11110101], None), + (&[0b11110001], None), + (&[0b11101101], None), + (&[0b11100101], None), + (&[0b11100001], None), + (&[0b11001101], None), + (&[0b11000101], None), + (&[0b11000001], None), + ]; + + let cases = vec![ + test_vector_tx(), + ]; + + fn check_range(r: &Box, idx: usize) -> bool { + if let Some(ref range) = r.downcast_ref::() { + return range.contains(&idx); + } + if let Some(ref range) = r.downcast_ref::>() { + return range.contains(&idx); + } + if let Some(ref range) = r.downcast_ref::>() { + return range.contains(&idx); + } + if let Some(ref range) = r.downcast_ref::>() { + return range.contains(&idx); + } + unreachable!("invalid range type used: {:?}", r.type_id()); + } + + cases.into_iter().enumerate().map(|(cidx, (tx, prevs))| { + let mut vectors = Vec::new(); + for (_sidx, (txfs, idx_range)) in selectors.iter().enumerate() { + for i in 0..tx.input.len() { + let default = r(..2); // only 2 fist inputs are taproot + let range = idx_range.as_ref().unwrap_or(default.as_ref().unwrap()); + if !check_range(range, i) { + continue; + } + // println!("{} >> #{} ({}) >> {}", cidx, _sidx, txfs.as_hex(), i); + + match calculate_txhash(txfs, &tx, &prevs, i as u32, None) { + Ok(txhash) => vectors.push(TestVector { + txfs: txfs.to_vec(), + input: i, + codeseparator: None, + txhash: txhash, + }), + Err(e) => panic!("Error in vector #{} for selector {}: {}", + cidx, txfs.as_hex(), e, + ), + } + } + } + TestCase { tx, prevs, vectors } + }).collect() + } + + pub fn write_vector_file(path: impl AsRef) { + use bitcoin::consensus::encode::serialize_hex; + + let ret = generate_vectors().into_iter().enumerate().map(|(i_tx, c)| serde_json::json!({ + "tx": serialize_hex(&c.tx), + "prevs": c.prevs.iter().map(|p| serialize_hex(p)).collect::>(), + "vectors": c.vectors.into_iter().enumerate().map(|(i_v, v)| serde_json::json!({ + "id": format!("{}:{} ({} #{})", i_tx, i_v, v.txfs.as_hex(), v.input), + "txfs": v.txfs.as_hex().to_string(), + "input": v.input, + "codeseparator": v.codeseparator, + "txhash": v.txhash, + })).collect::>(), + })).collect::>(); + + let mut file = std::fs::File::create(path).unwrap(); + serde_json::to_writer_pretty(&mut file, &ret).unwrap(); + } +} + +fn main() { + test_vectors::write_vector_file("./txhash_vectors.json"); +} diff --git a/bip-0346/ref-impl/txhash_vectors.json b/bip-0346/ref-impl/txhash_vectors.json new file mode 100644 index 00000000..232bc185 --- /dev/null +++ b/bip-0346/ref-impl/txhash_vectors.json @@ -0,0 +1,1063 @@ +[ + { + "prevs": [ + "69010000000000002251200100000000000000000000000000000000000000000000000000000000000000", + "6a010000000000002251200200000000000000000000000000000000000000000000000000000000000000", + "68010000000000000160", + "69010000000000002200200100000000000000000000000000000000000000000000000000000000000000" + ], + "tx": "02000000000104333333333333333333333333333333333333333333333333333333333333333303000000000200000044444444444444444444444444444444444444444444444444444444444444440400000000030000001111111111111111111111111111111111111111111111111111111111111111010000000123010000002222222222222222222222222222222222222222222222222222222222222222020000000003000000035e0100000000000001565f0100000000000001576101000000000000015801011203011301140250420003011301140250422a000000", + "vectors": [ + { + "codeseparator": null, + "id": "0:0 (0100 #0)", + "input": 0, + "txfs": "0100", + "txhash": "26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6ece" + }, + { + "codeseparator": null, + "id": "0:1 (0100 #1)", + "input": 1, + "txfs": "0100", + "txhash": "26b25d457597a7b0463f9620f666dd10aa2c4373a505967c7c8d70922a2d6ece" + }, + { + "codeseparator": null, + "id": "0:2 (0200 #0)", + "input": 0, + "txfs": "0200", + "txhash": "e8a4b2ee7ede79a3afb332b5b6cc3d952a65fd8cffb897f5d18016577c33d7cc" + }, + { + "codeseparator": null, + "id": "0:3 (0200 #1)", + "input": 1, + "txfs": "0200", + "txhash": "e8a4b2ee7ede79a3afb332b5b6cc3d952a65fd8cffb897f5d18016577c33d7cc" + }, + { + "codeseparator": null, + "id": "0:4 (0400 #0)", + "input": 0, + "txfs": "0400", + "txhash": "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119" + }, + { + "codeseparator": null, + "id": "0:5 (0400 #1)", + "input": 1, + "txfs": "0400", + "txhash": "67abdd721024f0ff4e0b3f4c2fc13bc5bad42d0b7851d456d88d203d15aaa450" + }, + { + "codeseparator": null, + "id": "0:6 (0800 #0)", + "input": 0, + "txfs": "0800", + "txhash": "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456" + }, + { + "codeseparator": null, + "id": "0:7 (0800 #1)", + "input": 1, + "txfs": "0800", + "txhash": "d703d3da6a87bd8e0b453f3b6c41edcc9bf331b2b88ef26eb39dc7abee4e00a3" + }, + { + "codeseparator": null, + "id": "0:8 (1000 #0)", + "input": 0, + "txfs": "1000", + "txhash": "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456" + }, + { + "codeseparator": null, + "id": "0:9 (1000 #1)", + "input": 1, + "txfs": "1000", + "txhash": "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456" + }, + { + "codeseparator": null, + "id": "0:10 (2000 #0)", + "input": 0, + "txfs": "2000", + "txhash": "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" + }, + { + "codeseparator": null, + "id": "0:11 (2000 #1)", + "input": 1, + "txfs": "2000", + "txhash": "ad95131bc0b799c0b1af477fb14fcf26a6a9f76079e48bf090acb7e8367bfd0e" + }, + { + "codeseparator": null, + "id": "0:12 (4000 #0)", + "input": 0, + "txfs": "4000", + "txhash": "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456" + }, + { + "codeseparator": null, + "id": "0:13 (4000 #1)", + "input": 1, + "txfs": "4000", + "txhash": "227747766d19539b54f018e7ccfde16bd7c38ebbf5649357ecf67bdfb9755b5c" + }, + { + "codeseparator": null, + "id": "0:14 (8000 #0)", + "input": 0, + "txfs": "8000", + "txhash": "8509b81230019d2ad970d970f791dfbdc8caf54f5c594fcd327cef9feed206c1" + }, + { + "codeseparator": null, + "id": "0:15 (8000 #1)", + "input": 1, + "txfs": "8000", + "txhash": "8509b81230019d2ad970d970f791dfbdc8caf54f5c594fcd327cef9feed206c1" + }, + { + "codeseparator": null, + "id": "0:16 (ff000080 #0)", + "input": 0, + "txfs": "ff000080", + "txhash": "6286469e7f9ac95ff97d41ab1cf8a1d8a3872e45f5bac80a1acf8fd9d4397c18" + }, + { + "codeseparator": null, + "id": "0:17 (ff000080 #1)", + "input": 1, + "txfs": "ff000080", + "txhash": "74256c5211c8209c1be86fa0d7b92993a03d5e19647b5d1f83fdc65ebee51b86" + }, + { + "codeseparator": null, + "id": "0:18 (ff400040 #0)", + "input": 0, + "txfs": "ff400040", + "txhash": "66c3a2d99e0eccce1e769d61b879f6003252912982a9efc138b24365d6c68879" + }, + { + "codeseparator": null, + "id": "0:19 (ff400040 #1)", + "input": 1, + "txfs": "ff400040", + "txhash": "f1dfe060222dfa92e1f91d5afd5790ac9de5b1ff5db777282c85e0a968c25d0e" + }, + { + "codeseparator": null, + "id": "0:20 (ff800040 #0)", + "input": 0, + "txfs": "ff800040", + "txhash": "52bc63b1b02c3d375d13236938b25d5249df08fb4d891686a70e5702ea12c0e4" + }, + { + "codeseparator": null, + "id": "0:21 (ff800040 #1)", + "input": 1, + "txfs": "ff800040", + "txhash": "40351ff507344dd2493e80df868843bac4cff20c456e2ca28cfa39b2a0cf0557" + }, + { + "codeseparator": null, + "id": "0:22 (ffc00040 #0)", + "input": 0, + "txfs": "ffc00040", + "txhash": "75ae3a008d82c09aa7afd34abd46f03afe940758d33763b188d1fa2bd068cd80" + }, + { + "codeseparator": null, + "id": "0:23 (ffc00040 #1)", + "input": 1, + "txfs": "ffc00040", + "txhash": "6c3adf154dec8e01f323c0ac20ee5731bfbc5af4259a9a87c8ad0c77dd9c4417" + }, + { + "codeseparator": null, + "id": "0:24 (ff40003f #0)", + "input": 0, + "txfs": "ff40003f", + "txhash": "8dea16353d8dbb04764bc5a21fa36236aa3f736761cbb9be809a0a870fa6a366" + }, + { + "codeseparator": null, + "id": "0:25 (ff40003f #1)", + "input": 1, + "txfs": "ff40003f", + "txhash": "f3d00fee00e8841e38f465e211ede292fde56e1223c0f211786bc1d357593545" + }, + { + "codeseparator": null, + "id": "0:26 (ff80003f #0)", + "input": 0, + "txfs": "ff80003f", + "txhash": "d0b05b4c01125da54d473da6d5f21bef264cfa236e9dde70ff140027cef94931" + }, + { + "codeseparator": null, + "id": "0:27 (ff80003f #1)", + "input": 1, + "txfs": "ff80003f", + "txhash": "5eb3c700160c0b8b185babc8d2f2be4370f666c660ddb2113a187aacbfbf2b59" + }, + { + "codeseparator": null, + "id": "0:28 (ffc0003f #0)", + "input": 0, + "txfs": "ffc0003f", + "txhash": "a28ff0cd1109352b4dcbf74a0d2a6c73732235b532bb8284ad388b8e49838ea5" + }, + { + "codeseparator": null, + "id": "0:29 (ffc0003f #1)", + "input": 1, + "txfs": "ffc0003f", + "txhash": "ec672278f9ab6f4a21e9406721c27173e7887b75ab82cdee81988692324b3e47" + }, + { + "codeseparator": null, + "id": "0:30 (ff4000c0 #0)", + "input": 0, + "txfs": "ff4000c0", + "txhash": "af6e1adf1ff16c7bae08e4127b30dab4fedee683277c8b65e27897e9013d63cf" + }, + { + "codeseparator": null, + "id": "0:31 (ff4000c0 #1)", + "input": 1, + "txfs": "ff4000c0", + "txhash": "a0f1b5db81c1bba78772f6306107809c29c2f01d41e5d74e39fcf3463e666a91" + }, + { + "codeseparator": null, + "id": "0:32 (ff8000c0 #0)", + "input": 0, + "txfs": "ff8000c0", + "txhash": "8ce6884e8b3a11d81f69b9642981770cf2f7ed59cd7748e66f443e10cb16094d" + }, + { + "codeseparator": null, + "id": "0:33 (ff8000c0 #1)", + "input": 1, + "txfs": "ff8000c0", + "txhash": "d62d70495e26ba054e60328d35595ea3caf74d8faec2a34e08fb0da8ac6e4514" + }, + { + "codeseparator": null, + "id": "0:34 (ffc000c0 #0)", + "input": 0, + "txfs": "ffc000c0", + "txhash": "29c5f8ace77d67bc684be308d8bba076ddbccb7db86890f1affde88243111d66" + }, + { + "codeseparator": null, + "id": "0:35 (ffc000c0 #1)", + "input": 1, + "txfs": "ffc000c0", + "txhash": "9d7f7313cf89e95b4984f6864fe6103e26fe02d8e930221d1bdf47b8222b5d8d" + }, + { + "codeseparator": null, + "id": "0:36 (ff4000bf #0)", + "input": 0, + "txfs": "ff4000bf", + "txhash": "a111bf0e5c46831243ec5d8095ffdb959b316c00e00296dfce12b5dc817f1d29" + }, + { + "codeseparator": null, + "id": "0:37 (ff4000bf #1)", + "input": 1, + "txfs": "ff4000bf", + "txhash": "1ac5b588e154bbf4cf1a462eb83718bcd72c1b544867300004950afa15a416a0" + }, + { + "codeseparator": null, + "id": "0:38 (ff8000bf #0)", + "input": 0, + "txfs": "ff8000bf", + "txhash": "2ecd12402dd1443629037d5864d5464d9c8812bd44449bfc25b16b0c76bfff79" + }, + { + "codeseparator": null, + "id": "0:39 (ff8000bf #1)", + "input": 1, + "txfs": "ff8000bf", + "txhash": "c5a5ac672a574311029e25b4d381f844745aad80e23040119aa9df76570d9ae2" + }, + { + "codeseparator": null, + "id": "0:40 (ffc000bf #0)", + "input": 0, + "txfs": "ffc000bf", + "txhash": "6b7d5990b3435e42a4212c88967eb0f9c8b3c3738ecc2fc79d4b48bb386f178c" + }, + { + "codeseparator": null, + "id": "0:41 (ffc000bf #1)", + "input": 1, + "txfs": "ffc000bf", + "txhash": "ba92bbf9b3151efaaa0582c5db076066fdc842037daf5c8d51ee425130cd2181" + }, + { + "codeseparator": null, + "id": "0:42 (ff0080 #0)", + "input": 0, + "txfs": "ff0080", + "txhash": "3a23df77318fd630a1b004bd49ee23e2f6ffb588a3e38d50577a307401f67127" + }, + { + "codeseparator": null, + "id": "0:43 (ff0080 #1)", + "input": 1, + "txfs": "ff0080", + "txhash": "c8c9cb44b4405c2efb77e6fd2a1258d4479042d6dc1fb51bf8aa0448f6514635" + }, + { + "codeseparator": null, + "id": "0:44 (ff0140 #0)", + "input": 0, + "txfs": "ff0140", + "txhash": "e2fcdd4957e7891edac19f58776806218069192b0e94d1d77260a555342d06ac" + }, + { + "codeseparator": null, + "id": "0:45 (ff0140 #1)", + "input": 1, + "txfs": "ff0140", + "txhash": "3f87e78b8be3e4b28b29c255bd5ed7c10ab69b02963b02b9394aee1b5f788434" + }, + { + "codeseparator": null, + "id": "0:46 (ff0240 #0)", + "input": 0, + "txfs": "ff0240", + "txhash": "2589b0924c45f17484e681c5df41fa136833c44b985ef3fbf17974ca46d7ecd4" + }, + { + "codeseparator": null, + "id": "0:47 (ff0240 #1)", + "input": 1, + "txfs": "ff0240", + "txhash": "871bc1ff067094336c1860b01a38673cedce057568f036202e7a3e2410847e44" + }, + { + "codeseparator": null, + "id": "0:48 (ff0440 #0)", + "input": 0, + "txfs": "ff0440", + "txhash": "91509f1fd4fb675a936c5cf9c79528ea6441750f41ca8851125c1d78f1880a6c" + }, + { + "codeseparator": null, + "id": "0:49 (ff0440 #1)", + "input": 1, + "txfs": "ff0440", + "txhash": "6e18b0171ce7aee2307a4e9ecc1e8d625d07116063e52604a85bc6d1064cfc32" + }, + { + "codeseparator": null, + "id": "0:50 (ff0840 #0)", + "input": 0, + "txfs": "ff0840", + "txhash": "d9f97f4e62518332b2bd5d0bad604973d5dfef0945a95685d258ce9743d079ec" + }, + { + "codeseparator": null, + "id": "0:51 (ff0840 #1)", + "input": 1, + "txfs": "ff0840", + "txhash": "0575703690b0c557080ff3bef27cfe517abacbe420aa9ddaf53cfe3c733ec077" + }, + { + "codeseparator": null, + "id": "0:52 (ff1040 #0)", + "input": 0, + "txfs": "ff1040", + "txhash": "eb53fa90d5e2617ec342007a1bf7ac62dbe66c5147ba60a294a7db1704620f75" + }, + { + "codeseparator": null, + "id": "0:53 (ff1040 #1)", + "input": 1, + "txfs": "ff1040", + "txhash": "6dc2f01a3b22b761df91d9a6ba9b58d85f26d881585bd252fcf75eed4ab74e94" + }, + { + "codeseparator": null, + "id": "0:54 (ff2040 #0)", + "input": 0, + "txfs": "ff2040", + "txhash": "1a7a2897721b14ebabac2dd83964323864d6f54610a21c255b7a61be4042f269" + }, + { + "codeseparator": null, + "id": "0:55 (ff2040 #1)", + "input": 1, + "txfs": "ff2040", + "txhash": "953b14f68d47206c29b780c13234a8e2017804367c4b67012626d19e2f21ceff" + }, + { + "codeseparator": null, + "id": "0:56 (ff3f40 #0)", + "input": 0, + "txfs": "ff3f40", + "txhash": "e94869fa522e4acd229958b360dd6b9d87f9e59fcead016875b0f97f10c18595" + }, + { + "codeseparator": null, + "id": "0:57 (ff3f40 #1)", + "input": 1, + "txfs": "ff3f40", + "txhash": "3262c8c9f7b2538e123f11e7b31aa7c7c5f6c7bf031ac89ead194991a9df74b1" + }, + { + "codeseparator": null, + "id": "0:58 (ff013f #0)", + "input": 0, + "txfs": "ff013f", + "txhash": "3ab80fffdf1301f056762b0e2e09c11699a479adbaa1bea38a95a6ec49e94c0e" + }, + { + "codeseparator": null, + "id": "0:59 (ff013f #1)", + "input": 1, + "txfs": "ff013f", + "txhash": "2ccab90e40aaab0304458362ba31ce910c41ed446ad14b15a15613a84be0a147" + }, + { + "codeseparator": null, + "id": "0:60 (ff023f #0)", + "input": 0, + "txfs": "ff023f", + "txhash": "29ebfe25e2d5e33a416808b36e2d23538c7e94c77ada313fd78a3137471fb618" + }, + { + "codeseparator": null, + "id": "0:61 (ff023f #1)", + "input": 1, + "txfs": "ff023f", + "txhash": "eddeb813dabe380be2f59cf04b4e0528a2033933ffea03bb49b45a262ca2d566" + }, + { + "codeseparator": null, + "id": "0:62 (ff043f #0)", + "input": 0, + "txfs": "ff043f", + "txhash": "ebd4f5e2636391b007d0c1e68e0d088d6e5a638a3e40c00e322b0a9363b3e477" + }, + { + "codeseparator": null, + "id": "0:63 (ff043f #1)", + "input": 1, + "txfs": "ff043f", + "txhash": "9f1cd389104b23b29d534b16d06ba3f0839d58d443f798ada9155bf2f4ebe4f6" + }, + { + "codeseparator": null, + "id": "0:64 (ff083f #0)", + "input": 0, + "txfs": "ff083f", + "txhash": "4b596fbefb903426720ba9a3906306371253d2de874537e722a47de29c925647" + }, + { + "codeseparator": null, + "id": "0:65 (ff083f #1)", + "input": 1, + "txfs": "ff083f", + "txhash": "ef50137ead871d0dd9c1b4c6cb0c57ea00f7650d111897d0bcd0441622e9375d" + }, + { + "codeseparator": null, + "id": "0:66 (ff103f #0)", + "input": 0, + "txfs": "ff103f", + "txhash": "874afe3e5a88a2cc3afc3239ea78e6d0cd91c83ae384880830f0daf1096838d1" + }, + { + "codeseparator": null, + "id": "0:67 (ff103f #1)", + "input": 1, + "txfs": "ff103f", + "txhash": "7002a2f133407c98c08f17caac86fdeac6e5f2ae86c059567028a9e2f2718929" + }, + { + "codeseparator": null, + "id": "0:68 (ff203f #0)", + "input": 0, + "txfs": "ff203f", + "txhash": "589f39f55732242d97de0fdb8aa8a3736b197d4f319885c32651cd565021da6b" + }, + { + "codeseparator": null, + "id": "0:69 (ff203f #1)", + "input": 1, + "txfs": "ff203f", + "txhash": "46fc739044047fc00053ec7532ceee2ea53fe75029a2d65c90d5da2738937b8e" + }, + { + "codeseparator": null, + "id": "0:70 (ff3f3f #0)", + "input": 0, + "txfs": "ff3f3f", + "txhash": "ece064b4f1551cfbcff6792e32842bb08335fb2cb5ee430f7707a00d15f89c41" + }, + { + "codeseparator": null, + "id": "0:71 (ff3f3f #1)", + "input": 1, + "txfs": "ff3f3f", + "txhash": "e41fd96f9d8f48c6ac04ce5039d850535b668c3b8b83f3913e2a131edfe5b4df" + }, + { + "codeseparator": null, + "id": "0:72 (ff01c0 #0)", + "input": 0, + "txfs": "ff01c0", + "txhash": "35aa24440a7e833ed0f6503688a7a7009ebc1999cebd58c10724a2d321d41174" + }, + { + "codeseparator": null, + "id": "0:73 (ff01c0 #1)", + "input": 1, + "txfs": "ff01c0", + "txhash": "d3bbb1e58851725a94a5538189b5dad23c9c7cec53549f959e6f498dce737828" + }, + { + "codeseparator": null, + "id": "0:74 (ff02c0 #0)", + "input": 0, + "txfs": "ff02c0", + "txhash": "79ee870774e05726bbf47780154ff33e9ded3c67bae7ec02a12663106adbcc68" + }, + { + "codeseparator": null, + "id": "0:75 (ff02c0 #1)", + "input": 1, + "txfs": "ff02c0", + "txhash": "01bf86cb8dd28adf6807c505b4c538a2490c213be8b1224b55c7e4b5ab523d97" + }, + { + "codeseparator": null, + "id": "0:76 (ff04c0 #0)", + "input": 0, + "txfs": "ff04c0", + "txhash": "8dbd343cd6dd7f92c0c9e0c278d6189f8ae08400e7ffbe0e932fb4675bc0d4f1" + }, + { + "codeseparator": null, + "id": "0:77 (ff04c0 #1)", + "input": 1, + "txfs": "ff04c0", + "txhash": "8b566825b9c36b5e0b6916827ad7573cfe74f8a903c1ff06a06329aaa28b9c1d" + }, + { + "codeseparator": null, + "id": "0:78 (ff08c0 #0)", + "input": 0, + "txfs": "ff08c0", + "txhash": "98d344d199bb540a91c6a9d70e3daa3ead46c30a377470ffbe1199d12a606aec" + }, + { + "codeseparator": null, + "id": "0:79 (ff08c0 #1)", + "input": 1, + "txfs": "ff08c0", + "txhash": "b68c12f03396664d494b2af4363efc89667f76d9bf962e52c2a52fcb0ed877b9" + }, + { + "codeseparator": null, + "id": "0:80 (ff10c0 #0)", + "input": 0, + "txfs": "ff10c0", + "txhash": "2fb64ddf05090b11ec14848963af6aac97ea2394623e66d8f9237eb712b5bbe8" + }, + { + "codeseparator": null, + "id": "0:81 (ff10c0 #1)", + "input": 1, + "txfs": "ff10c0", + "txhash": "8eaa22ece3e6e61340a013c607ca487270b14143fdb666d021879f5c84fbf661" + }, + { + "codeseparator": null, + "id": "0:82 (ff20c0 #0)", + "input": 0, + "txfs": "ff20c0", + "txhash": "70bf9abee31014fc3e454049535e115948ea16022323d58eda379801763b00c5" + }, + { + "codeseparator": null, + "id": "0:83 (ff20c0 #1)", + "input": 1, + "txfs": "ff20c0", + "txhash": "5abbe67209f513d56380f45578dc5479ab76e3ed9121f4d100dca09793da2584" + }, + { + "codeseparator": null, + "id": "0:84 (ff3fc0 #0)", + "input": 0, + "txfs": "ff3fc0", + "txhash": "791a302cbb5e2a711ef54911a62b9968223bfda2d6c1f91f72ad8680883b4568" + }, + { + "codeseparator": null, + "id": "0:85 (ff3fc0 #1)", + "input": 1, + "txfs": "ff3fc0", + "txhash": "5449e0e4fd52db193c70b0804fafd53093753b936cbf6582c33e375ca0c55ed2" + }, + { + "codeseparator": null, + "id": "0:86 (ff01bf #0)", + "input": 0, + "txfs": "ff01bf", + "txhash": "0c86d8789f7a98e7aed70940dd78a1ea6b42d3a4adf16fe343d6ddac78c5fdc3" + }, + { + "codeseparator": null, + "id": "0:87 (ff01bf #1)", + "input": 1, + "txfs": "ff01bf", + "txhash": "770c254d1a603e09243bd0d193ec89aaaee20958abe3c2249c57565cfb3ebc90" + }, + { + "codeseparator": null, + "id": "0:88 (ff02bf #0)", + "input": 0, + "txfs": "ff02bf", + "txhash": "b2f3145c3caaddb1344fd170e62eadd93b34adb5a8262c8e1c2d542f8ce0e045" + }, + { + "codeseparator": null, + "id": "0:89 (ff02bf #1)", + "input": 1, + "txfs": "ff02bf", + "txhash": "fbf7a5bec0037e3ed35f0fe243688f6e0fe3583adce9ca221af02bed1cced8cb" + }, + { + "codeseparator": null, + "id": "0:90 (ff04bf #0)", + "input": 0, + "txfs": "ff04bf", + "txhash": "d08f2c883d8bc078ee42aa2647307d455a53caeeda3a2d1ce1bcaaaf223b2dc8" + }, + { + "codeseparator": null, + "id": "0:91 (ff04bf #1)", + "input": 1, + "txfs": "ff04bf", + "txhash": "0db8760a71982cf0c354be8b286e1a202695aac3c18a952ec0af6c79247a50bd" + }, + { + "codeseparator": null, + "id": "0:92 (ff08bf #0)", + "input": 0, + "txfs": "ff08bf", + "txhash": "acb8c88af64ecd6c65931c7549e6e913c7eb8a6305f5abdea1c21ada1c57c1fb" + }, + { + "codeseparator": null, + "id": "0:93 (ff08bf #1)", + "input": 1, + "txfs": "ff08bf", + "txhash": "528f4d8c24cc929b21b2bf2306c12225b4a58119a57b46905a4748117a1f671e" + }, + { + "codeseparator": null, + "id": "0:94 (ff10bf #0)", + "input": 0, + "txfs": "ff10bf", + "txhash": "dfc30a7df4cd7f105274ce5913b56e1fdb15f842d16259cac5e2142b8e328b72" + }, + { + "codeseparator": null, + "id": "0:95 (ff10bf #1)", + "input": 1, + "txfs": "ff10bf", + "txhash": "fc7eb663ecafee91eaeb327be8ce4141eba15be928411b13b1145e825a25c7a5" + }, + { + "codeseparator": null, + "id": "0:96 (ff20bf #0)", + "input": 0, + "txfs": "ff20bf", + "txhash": "68ddc75edee9e075116b3a92da91934b873bc5319b7d843964a1bdbbcc98a3fc" + }, + { + "codeseparator": null, + "id": "0:97 (ff20bf #1)", + "input": 1, + "txfs": "ff20bf", + "txhash": "70b258975fd720f72300eb935e9ad1d6cb627201535d205ea2547f6379290f2c" + }, + { + "codeseparator": null, + "id": "0:98 (ff3fbf #0)", + "input": 0, + "txfs": "ff3fbf", + "txhash": "af99fb1bb1d843814f205517718b70ff2729c3cc0169e97863d4de1b5f97d102" + }, + { + "codeseparator": null, + "id": "0:99 (ff3fbf #1)", + "input": 1, + "txfs": "ff3fbf", + "txhash": "d51e4053eae324623ac8b45e6c756f54dc4f674c9024b1739976e481b58e4c29" + }, + { + "codeseparator": null, + "id": "0:100 (ffff3f3f #0)", + "input": 0, + "txfs": "ffff3f3f", + "txhash": "2b8b2247712581368099f351dc3c3fea0ee5a46158c2ab8ea023dfd02483656e" + }, + { + "codeseparator": null, + "id": "0:101 (ffff3f3f #1)", + "input": 1, + "txfs": "ffff3f3f", + "txhash": "efc150fc72f4d9ade4c88c6bf507c85abcac3c09e6ffb6e57e2b372460a9b58b" + }, + { + "codeseparator": null, + "id": "0:102 (ffff4040 #0)", + "input": 0, + "txfs": "ffff4040", + "txhash": "bbc351d3a61ff0531c48883202c347ba6e2e889d0dae526cc302a8978f54194f" + }, + { + "codeseparator": null, + "id": "0:103 (ffff4040 #1)", + "input": 1, + "txfs": "ffff4040", + "txhash": "56789e76148add29a6e11a71f9eee1d587967f475a7b7adead3a38aad9753070" + }, + { + "codeseparator": null, + "id": "0:104 (ff008080 #0)", + "input": 0, + "txfs": "ff008080", + "txhash": "fc87d178387418de163b5cf92d7ec20236bc4e64d8013926e8202d7ffd35eaff" + }, + { + "codeseparator": null, + "id": "0:105 (ff008080 #1)", + "input": 1, + "txfs": "ff008080", + "txhash": "ef7074fa8f70e1c36b9c392afdca434320e9fd58a8e35a5b7722fb832f11cb15" + }, + { + "codeseparator": null, + "id": "0:106 (ffffbfbf #0)", + "input": 0, + "txfs": "ffffbfbf", + "txhash": "d872d9eda08df40dc5a6761f90d886720c24b392307df90c00d1647902967ca7" + }, + { + "codeseparator": null, + "id": "0:107 (ffffbfbf #1)", + "input": 1, + "txfs": "ffffbfbf", + "txhash": "b3d3393fd149ec4bb0ecf50119f36ba0621259b6d6288d9a2e8b8332bc75bcd7" + }, + { + "codeseparator": null, + "id": "0:108 (ffffc0c0 #0)", + "input": 0, + "txfs": "ffffc0c0", + "txhash": "addc2bf0570b09cf5dae3b8535158261e3e7c91db0fb313133343763cd3958dd" + }, + { + "codeseparator": null, + "id": "0:109 (ffffc0c0 #1)", + "input": 1, + "txfs": "ffffc0c0", + "txhash": "66e8cf6e41e1f2963d4dd1a78d3e19318b27f34abc5011ef6359ecf5151ab812" + }, + { + "codeseparator": null, + "id": "0:110 (ffff403f #0)", + "input": 0, + "txfs": "ffff403f", + "txhash": "9413abd29279ea6ad5a0675d1007ccd3d6d61ef6633eb9415961282f271d6c31" + }, + { + "codeseparator": null, + "id": "0:111 (ffff403f #1)", + "input": 1, + "txfs": "ffff403f", + "txhash": "8cd9e9133c585c31e207db07732fb5f6a3c392118c90b4d20e9e7dd470dc38ed" + }, + { + "codeseparator": null, + "id": "0:112 (ffff3f40 #0)", + "input": 0, + "txfs": "ffff3f40", + "txhash": "404f72fe8f7365a27bc5b1c496dd11a689a2f7d0f3b7b3c9755a8f7bf11d7b89" + }, + { + "codeseparator": null, + "id": "0:113 (ffff3f40 #1)", + "input": 1, + "txfs": "ffff3f40", + "txhash": "59628e1df03ce68564e9035f376ac9c76f7a11d5b1b0b71c5a593da5b4cf5c6d" + }, + { + "codeseparator": null, + "id": "0:114 (ffff0182 #0)", + "input": 0, + "txfs": "ffff0182", + "txhash": "ab8c241a251a98288b1758710961c0924e1c27b0df974cd669b2e154101e36b5" + }, + { + "codeseparator": null, + "id": "0:115 (ffff0182 #1)", + "input": 1, + "txfs": "ffff0182", + "txhash": "eb561e62a71274921a7e0025e66c51299d1904cfba98debb4a23cee2f49f9209" + }, + { + "codeseparator": null, + "id": "0:116 (ffffc002 #0)", + "input": 0, + "txfs": "ffffc002", + "txhash": "d5c3e2f80a574f8f80c016c2ddd4bee2298f4e91a49d07fbf2ff96ee1ebdbbc2" + }, + { + "codeseparator": null, + "id": "0:117 (ffffc002 #1)", + "input": 1, + "txfs": "ffffc002", + "txhash": "99efebafd498376dfced0515ae0d1949cb7673bff30bd73a693011edf1541db6" + }, + { + "codeseparator": null, + "id": "0:118 (ffff4101420002 #0)", + "input": 0, + "txfs": "ffff4101420002", + "txhash": "72f86b1e8e4721e3aa8c4ddfc744e93e2f4628fe63116e1c592449608077d0ea" + }, + { + "codeseparator": null, + "id": "0:119 (ffff4101420002 #1)", + "input": 1, + "txfs": "ffff4101420002", + "txhash": "6dfab74a32a90fd024f5faeab5935486de4c07b76ac788f3a80fb4e426e63dfc" + }, + { + "codeseparator": null, + "id": "0:120 (ffffc101c20002 #0)", + "input": 0, + "txfs": "ffffc101c20002", + "txhash": "8bcbd63009fea26f96e1736f2f2b517c7274feef626b3d9524e2dce22b81ec97" + }, + { + "codeseparator": null, + "id": "0:121 (ffffc101c20002 #1)", + "input": 1, + "txfs": "ffffc101c20002", + "txhash": "d801364c0d3d9468c17101f1906e89e84d0e325d904ce54a1305885296228055" + }, + { + "codeseparator": null, + "id": "0:122 (ffff617f627f00 #1)", + "input": 1, + "txfs": "ffff617f627f00", + "txhash": "a6eab06ad0e6053b0221d50463c6fa5c87dd4926bdff88dfbaae60147fa91beb" + }, + { + "codeseparator": null, + "id": "0:123 (ffffe17fe27f00 #1)", + "input": 1, + "txfs": "ffffe17fe27f00", + "txhash": "9f5dee0abdcbdfc6016e7186ad639b50728771eebbd7464e3ba0b5d8f3ef68ea" + }, + { + "codeseparator": null, + "id": "0:124 ( #0)", + "input": 0, + "txfs": "", + "txhash": "a1601773a4ddd7a2a1117fffaa670bd67fb3b85f91e2793e25de8c4f848fe0d9" + }, + { + "codeseparator": null, + "id": "0:125 ( #1)", + "input": 1, + "txfs": "", + "txhash": "6ae6fe854e08cbf0a25636b575d8bbe63c120ba496796dd30b4924b79f909fbd" + }, + { + "codeseparator": null, + "id": "0:126 (ff #0)", + "input": 0, + "txfs": "ff", + "txhash": "45da8ae2f210c2d6985d5c3e1067817838273495da515941e18fa554dc5e740d" + }, + { + "codeseparator": null, + "id": "0:127 (ff #1)", + "input": 1, + "txfs": "ff", + "txhash": "335bfdcf79f6ae2ccb7d4972e26106f043813de634ba9a752b4808192f5e8c38" + }, + { + "codeseparator": null, + "id": "0:128 (f7 #0)", + "input": 0, + "txfs": "f7", + "txhash": "bba33c04d64d580f27ef181c3482ed7243212176a721613f522415234b194df6" + }, + { + "codeseparator": null, + "id": "0:129 (f7 #1)", + "input": 1, + "txfs": "f7", + "txhash": "13b100680de28fd5d7ecd40389c49add2298ea464152c06b859150e7018ea9e6" + }, + { + "codeseparator": null, + "id": "0:130 (f3 #0)", + "input": 0, + "txfs": "f3", + "txhash": "27163ce496cf96f4dd5a0dc275ed3aeb4f13fac742011ae657c64536b7523896" + }, + { + "codeseparator": null, + "id": "0:131 (f3 #1)", + "input": 1, + "txfs": "f3", + "txhash": "4f45f4c2a2390634e6591cffba66bec810fa17c88734fedc6ce974f765533771" + }, + { + "codeseparator": null, + "id": "0:132 (fd #0)", + "input": 0, + "txfs": "fd", + "txhash": "66b2523a0e8ccc2388b401043dcd08bcd9848ce07ba90309a3194a88cceefc58" + }, + { + "codeseparator": null, + "id": "0:133 (fd #1)", + "input": 1, + "txfs": "fd", + "txhash": "bf51c6a7993a1abc7f62d92ceb1abaf03ce10df620cab77a9c68bc8b2d993768" + }, + { + "codeseparator": null, + "id": "0:134 (f5 #0)", + "input": 0, + "txfs": "f5", + "txhash": "4ac1bd781328f1a24dc514d686b1c0ea78ca28bf4b37e60472f98e9b1c07568c" + }, + { + "codeseparator": null, + "id": "0:135 (f5 #1)", + "input": 1, + "txfs": "f5", + "txhash": "be93ef8b84789a7e522f73bb3c0c00afe91075e3a02dc82fb766f31a5e2810db" + }, + { + "codeseparator": null, + "id": "0:136 (f1 #0)", + "input": 0, + "txfs": "f1", + "txhash": "0d09ed34a6bf5fbb8d447549c88a9aba198498f8351ddc32c89450cb8e2be34a" + }, + { + "codeseparator": null, + "id": "0:137 (f1 #1)", + "input": 1, + "txfs": "f1", + "txhash": "38d07d3216b3a2096ac879292cc4d2eca1eee87c3a8ed0d4e53b11adda28d7b6" + }, + { + "codeseparator": null, + "id": "0:138 (ed #0)", + "input": 0, + "txfs": "ed", + "txhash": "4978bd282206c9ad2f781e0ca252e9a37e4a366ca619c09c0a1d37028c8f7f28" + }, + { + "codeseparator": null, + "id": "0:139 (ed #1)", + "input": 1, + "txfs": "ed", + "txhash": "009af4edad039ac16da71104b4fdd715a8068a56bd5b27e08a7a5c1eedbfb4e4" + }, + { + "codeseparator": null, + "id": "0:140 (e5 #0)", + "input": 0, + "txfs": "e5", + "txhash": "83dc16b7f5dc131be5b21057a9a411e24a773579e5777953598ccfeee3bebcaa" + }, + { + "codeseparator": null, + "id": "0:141 (e5 #1)", + "input": 1, + "txfs": "e5", + "txhash": "a5a38488a0ac5600cb790ebc61200e92e4bd2421f0e9cb412e53634efdb4f014" + }, + { + "codeseparator": null, + "id": "0:142 (e1 #0)", + "input": 0, + "txfs": "e1", + "txhash": "bcc69369370c5083a378d0c9d6501f30bf61bc9c2e53ca0703d4da81b629ea37" + }, + { + "codeseparator": null, + "id": "0:143 (e1 #1)", + "input": 1, + "txfs": "e1", + "txhash": "8746f7789df6c561577f84965b6db3a12eb8f66f34af3006713fef6db9620455" + }, + { + "codeseparator": null, + "id": "0:144 (cd #0)", + "input": 0, + "txfs": "cd", + "txhash": "4b75f5635bbceb991d5c3f9c0c2a3d367a9b6b12cb150b0db632e2a8d81e337c" + }, + { + "codeseparator": null, + "id": "0:145 (cd #1)", + "input": 1, + "txfs": "cd", + "txhash": "e48d0a29d86078a17a9283f6f1c314499eab7343855f4b9f7140c68d4b346b9f" + }, + { + "codeseparator": null, + "id": "0:146 (c5 #0)", + "input": 0, + "txfs": "c5", + "txhash": "6f774daf5b1b163f35b4ca367df2798aa8e66c2ff30b77ea9f2e98a5db2b0ec6" + }, + { + "codeseparator": null, + "id": "0:147 (c5 #1)", + "input": 1, + "txfs": "c5", + "txhash": "b21e18937bbfadfb37ef10ebf9dacff13801503e475f2524e9c3bb620c0c1567" + }, + { + "codeseparator": null, + "id": "0:148 (c1 #0)", + "input": 0, + "txfs": "c1", + "txhash": "4f3d9c36d74b027ce9487907e36a59076f6a6760c7c5902b4dccac1199379a05" + }, + { + "codeseparator": null, + "id": "0:149 (c1 #1)", + "input": 1, + "txfs": "c1", + "txhash": "f6abfe1167fecd2809b28fb8d3b0fc98829c49a8f80f47492a0a910ff5799aa5" + } + ] + } +] \ No newline at end of file