In this commit, we add a total of 2760 taproot reference tests generated
by the bitcoind functional tests located at:
https://github.com/bitcoin/bitcoin/blob/master/test/functional/feature_taproot.py.
The tests aren't deterministic (fresh private keys are generated), so we
time we go to update the set of tests, we'll end up with fresh hashes
(the file name is the sha1 of the raw json test) and tests.
In this commit, we implement the new checksig semantics as part of
tapscript validation. Namely:
* OP_CHECKSIGVERIFY no longer pops the OP_TRUE off the stack (TODO(roasbeef): verify))
* the new sig ops semantics are added where each sig deducts 50 from a
starting budget of 50+the weight of the witness
* NULLFAIL is always enforced, meaning invalid sigs MUST be an empty sig array
In this commit, we use the recently added control block and script tree
verification+generation routines to implement full script path
verification within the VM. This includes verifying the script reveal
commitment, and recursing one layer deeper to execute the revealed
witness script as specified by BIP 342.
In this commit, we add a new AssembleTaprootScriptTree function that
given a list of tapscript leaves, generates a valid tapscript root,
along with the auxiliary proof data needed to spend each output.
In this commit, we add a new function `RawTxInTapscriptSignature` that
will be used to generate signatures in the _tapscript_ context. Note
that this differs from top-level taproot as a distinct sighash is used,
and we _always_ accept a root hash to perform the proper tweak.
In this commit, we add a new function to verify the taproot merkle
commitment of a given tapscript leaf. Along the way we add some helper
functions which can be used to construct a taproot output given the raw
script root.
In this commit, we add a new struct to represent the ControlBlock
structure used to feed in the tapscript leaf inclusion proof into the
witness tack. The `ParseControlBlock` parses a would-be control block
and returns an error if it's incorrectly formatted.
In this commit, we add the initial verification logic for top-level
taproot keyspends. Keyspends use the base BIP 341 sighash digest and
don't require any tapscript level functionality for validation.
In this commit, we add a new signatureVerifier interface that will allow
us to consolidate a lot of code as we'll now have 4 distinct sig+sighash
types to verify:
1. pre-segwit
2. segwit v0
3. segwit v1 (taproot key spend)
4. tapscript spends
We'll need to be able to handle 3 of the cases for the modified
OP_CHECKSIG operator. This new abstraction allows us to keep the
implementation of the function somewhat succinct.
In this commit we implement a verifier for #3 which is needed to verify
the top-level taproot keyspend. We expose the verifier using a new
VerifyTaprootKeySpend function.
In this commit, we make the sigCache slightly more general in order to
be able to cache both ECDSA and Schnorr signatures. The cache is now
based off of byte slices (the values) rather than the direct objects. We
rely on the fact that the sighash for ecdsa and the schnorr types are
distinct, so we can keep using the same top-level sighash key.
In the future with Go type params, we can use a type param here instead
as they all have an `IsEqual` method.
In this commit, we implement the new BIP 341+342 taproot sighash digest
computation. The digest is similar, but re-orders some fragments and
also starts to commit to the input values of all the transactions in the
SIGHASH_ALL case. A new implicit sighash flag, SIGHASH_DEFAULT has been
added that allows signatures to always be 64-bytes for the common case.
The hashcache has been updated as well to store both the v0 and v1 mid
state hashes. The v0 hashes are a double-sha of the contents, while the
v1 hash is a single sha. As a result, if a transaction spends both v0
and v1 inputs, then we 're able to re-use all the intermediate hashes.
As the sighash computation needs the input values and scripts, we create
an abstraction: the PrevOutFetcher to give the caller flexibility w.r.t
how this is done. We also create a `CannedPrevOutputFetcher` that holds
the information in a map for a single input.
A series of function options are also added to allow re-use of the same
base sig hash calculation for both BIP 341 and 342.
In this commit, we create a new package to house the ECDSA-specific
logic in the new `btcec/v2` pacakge. Thsi c hange is meant to mirror the
structure of the `dcrec` package, as we'll soon slot in our own custom
BIP-340 implementation.
In this commit, we update all the btcutil imports to point to the new
sub-module.
In the same commit, we also modify the recently added `btcutil/go.mod`
file as we need to continue pointing to the _old_ version of btcd, until
we merge this PR and push a new tag.
This converts the executeOpcode function defined on the engine to accept
an opcode and data slice instead of a parsed opcode as a step towards
removing the parsed opcode struct and associated supporting code altogether.
It also updates all callers accordingly.
This refactors the script engine to store and step through raw scripts
by making using of the new zero-allocation script tokenizer as opposed
to the less efficient method of storing and stepping through parsed
opcodes. It also improves several aspects while refactoring such as
optimizing the disassembly trace, showing all scripts in the trace in
the case of execution failure, and providing additional comments
describing the purpose of each field in the engine.
It should be noted that this is a step towards removing the parsed
opcode struct and associated supporting code altogether, however, in
order to ease the review process, this retains the struct and all
function signatures for opcode execution which make use of an individual
parsed opcode. Those will be updated in future commits.
The following is an overview of the changes:
- Modify internal engine scripts slice to use raw scripts instead of
parsed opcodes
- Introduce a tokenizer to the engine to track the current script
- Remove no longer needed script offset parameter from the engine since
that is tracked by the tokenizer
- Add an opcode index counter for disassembly purposes to the engine
- Update check for valid program counter to only consider the script
index
- Update tests for bad program counter accordingly
- Rework the NewEngine function
- Store the raw scripts
- Setup the initial tokenizer
- Explicitly check against version 0 instead of DefaultScriptVersion
which would break consensus if changed
- Check the scripts parse according to version 0 semantics to retain
current consensus rules
- Improve comments throughout
- Rework the Step function
- Use the tokenizer and raw scripts
- Create a parsed opcode on the fly for now to retain existing
opcode execution function signatures
- Improve comments throughout
- Update the Execute function
- Explicitly check against version 0 instead of DefaultScriptVersion
which would break consensus if changed
- Improve the disassembly tracing in the case of error
- Update the CheckErrorCondition function
- Modify clean stack error message to make sense in all cases
- Improve the comments
- Update the DisasmPC and DisasmScript functions on the engine
- Use the tokenizer
- Optimize construction via the use of strings.Builder
- Modify the subScript function to return the raw script bytes since the
parsed opcodes are no longer stored
- Update the various signature checking opcodes to use the raw opcode
data removal and signature hash calculation functions since the
subscript is now a raw script
- opcodeCheckSig
- opcodeCheckMultiSig
- opcodeCheckSigAlt
This converts the engine's current program counter disasembly to make
use of the standalone disassembly function to remove the dependency on
the parsed opcode struct.
It also updates the tests accordingly.
This converts the checkMinimalDataPush function defined on a parsed
opcode to a standalone function which accepts an opcode and data slice
instead in order to make it more flexible for raw script analysis.
It also updates all callers accordingly.
This converts the isConditional function defined on a parsed opcode to a
standalone function named isOpcodeConditional which accepts an opcode as
a byte instead in order to make it more flexible for raw script
analysis.
It also updates all callers accordingly.
This converts the alwaysIllegal function defined on a parsed opcode to a
standalone function named isOpcodeAlwaysIllegal which accepts an opcode
as a byte instead in order to make it more flexible for raw script
analysis.
It also updates all callers accordingly.
This converts the isDisabled function defined on a parsed opcode to a
standalone function which accepts an opcode as a byte instead in order
to make it more flexible for raw script analysis.
It also updates all callers accordingly.