1
0
mirror of https://github.com/bitcoin/bips.git synced 2026-02-09 15:23:09 +00:00

Add BIP-346: OP_TXHASH

This commit is contained in:
Steven Roose 2023-09-06 16:16:35 +01:00
parent ecb074d487
commit 6a0636da32
6 changed files with 2418 additions and 0 deletions

View File

@ -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

441
bip-0346.md Normal file
View File

@ -0,0 +1,441 @@
```
BIP: 346
Layer: Consensus (soft fork)
Title: OP_TXHASH
Authors: Steven Roose <steven@roose.io>
Brandon Black <freedom@reardencode.com>
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.<br>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`:
`<txfs> OP_TXHASH <pubkey> 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.

206
bip-0346/ref-impl/Cargo.lock generated Normal file
View File

@ -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"

View File

@ -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"

View File

@ -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<Item = u8>,
nb_items: usize,
current_input_idx: u32,
) -> Result<(Vec<usize>, 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<u32>,
) -> Result<sha256::Hash, &'static str> {
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<TxOut>) {
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<TxOut>,
vectors: Vec<TestVector>
}
#[derive(Debug)]
struct TestVector {
txfs: Vec<u8>,
input: usize,
codeseparator: Option<u32>,
txhash: sha256::Hash,
}
fn generate_vectors() -> Vec<TestCase> {
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<T: RangeBounds<usize> + 'static>(t: T) -> Option<Box<dyn Any>> {
Some(Box::new(t))
}
// txfs and range of inputs to run it on
let selectors: &[(&[u8], Option<Box<dyn Any>>)] = &[
// 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<dyn Any>, idx: usize) -> bool {
if let Some(ref range) = r.downcast_ref::<ops::RangeFull>() {
return range.contains(&idx);
}
if let Some(ref range) = r.downcast_ref::<ops::Range<usize>>() {
return range.contains(&idx);
}
if let Some(ref range) = r.downcast_ref::<ops::RangeFrom<usize>>() {
return range.contains(&idx);
}
if let Some(ref range) = r.downcast_ref::<ops::RangeTo<usize>>() {
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<std::path::Path>) {
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::<Vec<_>>(),
"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::<Vec<_>>(),
})).collect::<Vec<_>>();
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");
}

File diff suppressed because it is too large Load Diff