Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: implement BIP 341 and 342 a.k.a complete taproot and tapscript consensus verification logic #1787

Merged
merged 32 commits into from
Mar 16, 2022

Conversation

Roasbeef
Copy link
Member

@Roasbeef Roasbeef commented Jan 7, 2022

In this PR, we implement full validation logic for BIP 341 and BIP 342. Along the way, we've created a series of utility functions to be used in wallet libraries to handle things like parsing the control block, generating merkle tapscript inclusion proofs, and computing the two sighash variants introduced in the BIPs.

See each commit message for a detailed description w.r.t the scope of the incremental changes, as well as notes for reviewers w.r.t improvements to the logic/routines or things to doubly verify.

This PR builds on a series of PRs including:

Steps to Graduate from a Draft PR

  • Merge in the BIP 340 implementation linked above, along with the new v2 module for btcec.
  • Merge in the modified BIP 9 implementation linked above, that implements the new min activation height vector.
  • Extract the generated test vectors from the bitcoind implementation (the BIPs contain no test vectors, but they have some good coverage saved from their fuzz testing) to ensure the logic implemented is compliant with consensus rules.
  • Add additional test coverage for the utillity methods included
  • Re-examine the current routine for generating the script tree, to examine more efficient variants, or variants that produce a non-leaning binary tree structure

Fixes everything in #1735, other than the btcwallet specific components.

Copy link
Collaborator

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very excited about this PR, amazing work! Did a first quick pass, mostly to get a sense of the size of the diff. I have to say, it's not as large as I expected.
It'll probably grow quite a bit once tests with all test vectors are added.

txscript/script.go Outdated Show resolved Hide resolved
txscript/script.go Outdated Show resolved Hide resolved
txscript/script.go Outdated Show resolved Hide resolved
txscript/engine.go Show resolved Hide resolved
txscript/taproot.go Outdated Show resolved Hide resolved
@gcsfred2
Copy link

In script.go, calls to checkScriptParses have scriptVersion hardcoded to 0. Is that ok?

txscript/taproot.go Outdated Show resolved Hide resolved
Copy link

@positiveblue positiveblue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really excited to see btcd supporting taproot! First pass done with a couple of small nits

txscript/standard.go Outdated Show resolved Hide resolved
txscript/sigcache.go Show resolved Hide resolved
txscript/opcode.go Show resolved Hide resolved
Copy link
Collaborator

@wpaulino wpaulino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work putting all of this together! I've completed an initial pass in which I mostly focused on spec compat.

The PR will become a bit unwieldy once tests have been added, I think it would be useful to break it up into smaller parts to help reviewers focus on some of the independent changes being added. An example breakdown could be (each point represents its own PR):

  • Prep work for Taproot integration
  • Taproot key spend path
  • Control block construction/verification
  • Taproot script spend path (including sig opcode changes)

txscript/hashcache.go Outdated Show resolved Hide resolved
txscript/hashcache.go Outdated Show resolved Hide resolved
txscript/script.go Outdated Show resolved Hide resolved
txscript/script.go Outdated Show resolved Hide resolved
txscript/script.go Outdated Show resolved Hide resolved
txscript/taproot.go Outdated Show resolved Hide resolved
txscript/opcode.go Outdated Show resolved Hide resolved
txscript/opcode.go Show resolved Hide resolved
txscript/opcode.go Outdated Show resolved Hide resolved
txscript/opcode.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started some end-to-end integrations (see https://github.com/guggero/btcwallet/tree/schnorr-taproot and https://github.com/guggero/lnd/tree/schnorr-taproot-signet) and found a few small-ish issues. Going to do a more in-depth review of this PR again tomorrow, just leaving those comments here.

txscript/taproot.go Outdated Show resolved Hide resolved
txscript/taproot.go Outdated Show resolved Hide resolved
txscript/hashcache.go Outdated Show resolved Hide resolved
@coveralls
Copy link

coveralls commented Feb 21, 2022

Pull Request Test Coverage Report for Build 1962253109

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage remained the same at 79.676%

Totals Coverage Status
Change from base Build 1960508882: 0.0%
Covered Lines: 1231
Relevant Lines: 1545

💛 - Coveralls

@Roasbeef
Copy link
Member Author

Just pushed a revamped version which addresses all existing review comments, and also commits the bitcoind taproot test vectors. With the latest set of commits, we pass all 2760 test cases (both success and reject sub-cases for each test case). I've now removed the "draft" label from this PR, so we can consider it to be more final-ish!

I have some smaller TODO's to clean up still, like better error messages, but I'm pretty confident/happy in the current diff. Still entertaining the option of splitting this up into smaller PRs as with the latest diff (ignore all the files committed as those are just test vectors) the PR size has swelled a bit.

@3nprob
Copy link

3nprob commented Feb 21, 2022

The little I've been looking through, this looks great!

Splitting up into smaller PRs would help a lot, if you do have the inclination ^^

Copy link
Collaborator

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a full review (third round) and was able to end-to-end test key spend, script (single leaf and multiple leaf) execution, including OP_CHECKSIG with schnorr signatures.

Found a few small issues but all in all I'd say this is very close to be ready! Given we have superb coverage of the validation code thanks to the test cases from bitcoind I'd say this can be merged very soon.

Really great work on this, the code is super clean and easy to read/understand!

txscript/standard.go Show resolved Hide resolved
txscript/standard_test.go Outdated Show resolved Hide resolved
txscript/sighash.go Show resolved Hide resolved
txscript/sighash.go Outdated Show resolved Hide resolved
txscript/sighash.go Outdated Show resolved Hide resolved
txscript/taproot.go Outdated Show resolved Hide resolved
txscript/sign.go Show resolved Hide resolved
txscript/sign.go Outdated Show resolved Hide resolved
txscript/sign.go Outdated Show resolved Hide resolved
@guggero
Copy link
Collaborator

guggero commented Feb 22, 2022

I fixed a few things I mentioned in the comments here: https://github.com/guggero/btcd/tree/1787-fixes feel free to incorporate directly into this PR.

@Roasbeef Roasbeef force-pushed the taproot-impl branch 2 times, most recently from 417425c to f5186a5 Compare February 23, 2022 00:58
@Roasbeef
Copy link
Member Author

@guggero thanks! Added those in, and also working on more tests to ensure we accept sigs/witnesses we generate.

Copy link
Collaborator

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End-to-end tested this latest iteration in lnd and all test cases were successful. Of course more unit tests are always great to get more coverage on the signing part.

Since the verification part is covered very well, I'm giving my LGTM so we can move forward with the depending PRs.

Copy link
Collaborator

@wpaulino wpaulino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking pretty good! Very happy to see such an extensive testing suite for these changes.

I'll be working on a small PoC leveraging the new APIs to produce and verify tapscript spends and report back any findings in my next review.

txscript/pkscript.go Outdated Show resolved Hide resolved
txscript/standard.go Show resolved Hide resolved
blockchain/utxoviewpoint.go Show resolved Hide resolved
txscript/hashcache.go Outdated Show resolved Hide resolved
opts...,
)
if err != nil {
// TODO(roasbeef): propagate the error here?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps worth cross-referencing with what bitcoind does in this case?

Copy link
Member Author

@Roasbeef Roasbeef Feb 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calcTaprootSignatureHashRaw only returns en error if:

  1. An invalid sighash is used, bitcoind does the same here: https://github.com/bitcoin/bitcoin/blob/623745ca74cf3f54b474dac106f5802b7929503f/src/script/interpreter.cpp#L1716
  2. An index is passed in that's greater than the total number of actual inputs
    • For this, earlier in their pipeline there's an assertion to fail early for this case. We have a return clause here as we expose this function to consumers of the library, which may pass in an invalid index.

The other error paths there are just error returns from the io.Writer when creating the sig message, which in practice don't return an error.

txscript/sigvalidate.go Outdated Show resolved Hide resolved
// remaining bytes to be a multiple of the node size, which is 32
// bytes.
case (len(ctrlBlock)-ControlBlockBaseSize)%ControlBlockNodeSize != 0:
return nil, fmt.Errorf("invalid max block size")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is the same error text as above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will resolve these in a final pass to insert proper errors.

txscript/taproot.go Outdated Show resolved Hide resolved
txscript/taproot.go Outdated Show resolved Hide resolved
txscript/engine.go Outdated Show resolved Hide resolved
@Roasbeef
Copy link
Member Author

Responded to the latest round of comments! Also added a new CalcTapscriptSignaturehash that can be used to compute the sighash of tapscript spends. This'll be useful when doing things like validating that the remote party sent a valid HTLC signature on the LN context.

On the lnd side, we've successfully done end-to-end tests of both keyspend and script spend paths (using this lnd PR and the btcwallet PR linked).

In this commit, we use the recently added checksig verifiers to validate
signatures for pre-segwit, and segwit v0 scripts.
In this commit, we add two new functions: one for signing a raw
top-level taproot keyspend, and another for generating a valid witness
for a keyspend.
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.
We'll use this to examine if a script has any OP_SUCCESS op codes during
pre-processing before we attempt full tapscript execution.
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 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 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.
…sses

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 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.
… position

We'll need this to properly generate the sighash during tapscript
validation later
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 implement OP_CHECKSIGADD which replaces
OP_CHECKMULTISIG* in the tapscript execution environment.
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 add the deployment parameters of taproot as specified
in the deployment section of BIp 341:
https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#deployment.

Take note of the custom activation threshold, as well as the specified
min activation heights for mainnet only.
This PR includes some changes to them, so we'll need to use a temporary
replace directives to ensure the build passes.
@Roasbeef
Copy link
Member Author

Mainnet sync ✅

🚨 Just received word from Merge Control: this PR is a go!! 🚨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants