Implement BIP 340-342 validation (Schnorr/taproot/tapscript) #19953

pull sipa wants to merge 19 commits into bitcoin:master from sipa:taproot changing 44 files +2985 −121
  1. sipa commented at 4:53 am on September 14, 2020: member

    This is an implementation of the Schnorr/taproot consensus rules proposed by BIPs 340, 341, and 342.

    See the list of commits below. No signing or wallet support of any kind is included, as testing is done entirely through the Python test framework.

    This is a successor to #17977 (see discussion following this comment), and will have further changes squashed/rebased. The history of this PR can be found in #19997.

  2. fanquake added the label Consensus on Sep 14, 2020
  3. sipa commented at 5:15 am on September 14, 2020: member

    Here is a categorized list of all the commits:

    Refactors (https://github.com/sipa/bitcoin/compare/f8c099e220...450d2b2371) [+50 -39]:

    • 107b57df9f scripted-diff: put ECDSA in name of signature functions: In preparation for adding Schnorr versions of CheckSig, VerifySignature, and ComputeEntry, give them an ECDSA specific name.
    • 8bd2b4e784 refactor: rename scriptPubKey in VerifyWitnessProgram to exec_script: The old name is confusing, as it doesn’t store a scriptPubKey, but the actually executed script.
    • 5d62e3a68b refactor: keep spent outputs in PrecomputedTransactionData: A BIP-341 signature message may commit to the scriptPubKeys and amounts of all spent outputs (including other ones than the input being signed for spends), so keep them available to signature hashing code.

    BIP340/341/342 consensus rules (https://github.com/sipa/bitcoin/compare/450d2b2371...865d2c37e2) [+693 -50]:

    • 9eb590894f Add TaggedHash function (BIP 340): This adds the TaggedHash function as defined by BIP340 to the hash module, which is used in BIP340 and BIP341 to produce domain-separated hashes.
    • 5de246ca81 Implement Taproot signature hashing (BIP 341): This implements the new sighashing scheme from BIP341, with all relevant whole-transaction values precomputed once and cached.
    • 0664f5fe1f Support for Schnorr signatures and integration in SignatureCheckers (BIP 340): This enables the schnorrsig module in libsecp256k1, adds the relevant types and functions to src/pubkey, as well as in higher-level SignatureChecker classes. The (verification side of the) BIP340 test vectors is also added.
    • 8bbed4b7ac Implement Taproot validation (BIP 341): This includes key path spending and script path spending, but not the Tapscript execution implementation (leaf 0xc0 remains unemcumbered in this commit).
    • 330de894a9 Use ScriptExecutionData to pass through annex hash: Instead of recomputing the annex hash every time a signature is verified, compute it once and cache it in a new ScriptExecutionData structure.
    • 72422ce396 Implement Tapscript script validation rules (BIP 342): This adds a new SigVersion::TAPSCRIPT, makes the necessary interpreter changes to make it implement BIP342, and uses them for leaf version 0xc0 in Taproot script path spends.

    Regtest activation and policy (https://github.com/sipa/bitcoin/compare/865d2c37e2...206fb180ec) [+98 -8]:

    • e9a021d7e6 Make Taproot spends standard + policy limits: This adds a TxoutType::WITNESS_V1_TAPROOT for P2TR outputs, and permits spending them in standardness rules. No corresponding CTxDestination is added for it, as that isn’t needed until we want wallet integration. The taproot validation flags are also enabled for mempool transactions, and standardness rules are added (stack item size limit, no annexes).
    • d7ff237f29 Activate Taproot/Tapscript on regtest (BIP 341, BIP 342): Define a versionbits-based activation for the new consensus rules on regtest. No activation or activation mechanism is defined for testnet or mainnet.

    Tests (https://github.com/sipa/bitcoin/compare/206fb180ec...0e2a5e448f426219a6464b9aaadcc715534114e6) [+2148 -28]:

    • 3c226639eb tests: add BIP340 Schnorr signature support to test framework: Add a pure Python implementation of BIP340 signing and verification, tested against the BIP’s test vectors.
    • f06e6d0345 tests: functional tests for Schnorr/Taproot/Tapscript: A large functional test is added that automatically generates random transactions which exercise various aspects of the new rules, and verifies they are accepted into the mempool (when appropriate), and correctly accepted/rejected in (Python-constructed) blocks.
    • 4567ba034c tests: add generic qa-asset-based script verification unit test: This adds a unit test that does generic script verification tests, with positive/negative witnesses/scriptsigs, under various flags. The test data is large (several MB) so it’s stored in the qa-assets repo.
    • 0e2a5e448f tests: dumping and minimizing of script assets data: This adds a –dumptests flag to the feature_taproot.py test, to dump all its generated test cases to files, in a format compatible with the script_assets_test unit test. A fuzzer for said format is added as well, whose primary purpose is coverage-based minimization of those dumps.

    PREV="$(git rev-parse HEAD)"; git log --oneline upstream/master..HEAD | while read C L; do if [ "d${L:0:14}" == "d--- [TAPROOT] " ]; then if [ "d$PREV" != "" ]; then git diff --shortstat $C..$PREV | (read _ _ _ ADD _ DEL _; echo "### ${L:14:-4} (https://github.com/sipa/bitcoin/compare/$C...$PREV) [+$ADD -$DEL]:"); echo; fi; PREV=$C; PREVL=$L; else echo -n " * $C **${L}**: "; git show "$C" --format="%b" -s | awk '/^$/{exit} 1' | tr $'\n' ' '; echo; fi; done | tac

  4. sipa force-pushed on Sep 14, 2020
  5. jnewbery commented at 7:43 am on September 14, 2020: member

    Concept ACK :)

    Perhaps the pure refactor commits can be split into their own PR to reduce the size of this?

  6. in configure.ac:1648 in 111be541b5 outdated
    1644@@ -1645,7 +1645,7 @@ if test x$need_bundled_univalue = xyes; then
    1645   AC_CONFIG_SUBDIRS([src/univalue])
    1646 fi
    1647 
    1648-ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery"
    1649+ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --enable-module-schnorrsig --enable-experimental"
    


    jnewbery commented at 7:44 am on September 14, 2020:
    Does it make sense to remove the requirement for the -enable-experimental flag to build the schnorrsig module?

    fanquake commented at 7:50 am on September 14, 2020:

    This was discussed in the previous PR.

    luke-jr

    Should this be non-experimental before merging?

    gmaxwell

    I don’t think so: It probably shouldn’t be marked non-experimental until after it deployed for activation in Bitcoin because it wouldn’t be good to encourage third party users of it while it is still easy to make incompatible changes in Bitcoin (e.g. as was just done. :) )

    sipa

    Or, maybe more likely: the API in libsecp256k1 may change still significantly in the near future (e.g. support for variable-length messages, batch validation, …) even after we treat the scheme itself final.

  7. sipa commented at 8:34 am on September 14, 2020: member
    @jnewbery There are only two of them. The variable name one is very small, and the ECDSA naming one isn’t really a standalone useful change. I think that one could be changed into a scripted-diff though.
  8. in src/policy/policy.cpp:249 in 111be541b5 outdated
    244+        // - MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit for stack item size
    245+        // - No annexes
    246+        if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) {
    247+            // Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341)
    248+            auto stack = MakeSpan(tx.vin[i].scriptWitness.stack);
    249+            if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
    


    flack commented at 12:42 pm on September 14, 2020:
    maybe move that block down into the one below, then you don’t have to check stack.size() >= 2 twice

    sipa commented at 4:30 pm on September 14, 2020:
    I would rather not, because if later changes do add supported annexes, it’ll need to revert to this flow anyway (where the branch dealing with annexes pops it from the stack, and the next if looks at the stack size after removing it).
  9. instagibbs commented at 2:14 pm on September 14, 2020: member
    concept ACK, just confirming for now this PR is identical to the old PR #17977 at https://github.com/bitcoin/bitcoin/pull/19953/commits/111be541b5076e87bf800bc4769685d9b2340aed
  10. sipa force-pushed on Sep 14, 2020
  11. sipa commented at 6:42 pm on September 14, 2020: member
    Reordered commits a bit, replaced the ECDSA naming one with a scripted diff, and organized the commits in sections. The end state is identical to what it was before.
  12. sipa force-pushed on Sep 14, 2020
  13. sipa force-pushed on Sep 14, 2020
  14. sipa force-pushed on Sep 14, 2020
  15. sipa force-pushed on Sep 14, 2020
  16. benthecarman commented at 8:37 pm on September 14, 2020: contributor
    Could the first 2 commits be done as separate PRs? That should slightly reduce the size of this one
  17. sipa commented at 8:39 pm on September 14, 2020: member
    @benthecarman That was suggested earlier by @jnewbery. I think splitting off 28 trivial lines of refactors from a 2500-line PR (though 1700 are tests) isn’t going to change much. The refactors that were nontrivial and useful as standalone improvements have been split off already and merged (see history of the previous PR).
  18. sipa commented at 4:08 am on September 16, 2020: member

    I added a unit test too now, with test vectors that were extracted from 20000 runs of the feature_taproot.py test (with the largest tests removed, and larger groups of inputs per transaction), minimized using libfuzzer’s coverage tracking to 105.

    The code for doing so is in https://github.com/sipa/bitcoin/commits/taproot-test-creation. I’ll clean that code up a bit and integrate some parts of it in the normal Python test, so it’s easier to recreate these vectors if improvements to the Python test are made.

  19. jnewbery commented at 8:52 am on September 16, 2020: member

    I was surprised to learn that this was a 2500-line PR. By directory:

    • /test/functional - 1790 lines
    • /src/test - 134 lines
    • /src (exc test) - 817 lines

    So the majority of code in this PR is tests (which is a good thing!)

    I agree with sipa that it’s not necessary to split off the first two commits (especially now that they’re separated to the top of the branch). Equally no harm in doing so if that’d reduce the workload.

  20. instagibbs commented at 11:35 am on September 16, 2020: member
  21. laanwj added this to the "Blockers" column in a project

  22. DrahtBot commented at 1:34 pm on September 19, 2020: member

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #20121 (configure: Allow users to explicitly enable libsecp256k1’s GMP bignum support by luke-jr)
    • #20100 (Script: split policy/error consensus codes for CLEANSTACK, MINIMALIF by sanket1729)
    • #20035 (signet: Fix uninitialized read in validation by MarcoFalke)
    • #19935 (Move SaltedHashers to separate file and add some new ones by achow101)
    • #19792 (rpc: Add dumpcoinstats by fjahr)
    • #19602 (wallet: Migrate legacy wallets to descriptor wallets by achow101)
    • #19521 (Coinstats Index (without UTXO set hash) by fjahr)
    • #19438 (Introduce deploymentstatus by ajtowns)
    • #18788 (tests: Update more tests to work with descriptor wallets by achow101)
    • #16546 (External signer support - Wallet Box edition by Sjors)
    • #13533 ([tests] Reduced number of validations in tx_validationcache_tests by lucash-dev)
    • #13062 (Make script interpreter independent from storage type CScript by sipa)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  23. in src/pubkey.h:220 in beed0764df outdated
    215+    /** Construct an x-only pubkey from exactly 32 bytes. */
    216+    XOnlyPubKey(Span<const unsigned char> input);
    217+
    218+    /** Verify a 64-byte Schnorr signature.
    219+     *
    220+     * If the signature is not exactly 64 bytes, false is returned.
    


    benthecarman commented at 10:57 pm on September 19, 2020:
    VerifySchnorr has assert(sigbytes.size() == 64); so false won’t be returned. Should either change this comment or change the assertion to if (sigbytes.size() != 64) return false;

    sipa commented at 10:15 pm on September 22, 2020:
    Fixed.
  24. in src/script/script.cpp:340 in 94844a655f outdated
    335+bool IsOpSuccess(const opcodetype& opcode)
    336+{
    337+    return (opcode == 0x50 || opcode == 0x62 || opcode == 0x89 ||
    338+            opcode == 0x8a || opcode == 0x8d || opcode == 0x8e ||
    339+            (opcode >= 0x7e && opcode <= 0x81) || (opcode >= 0x83 && opcode <= 0x86) ||
    340+            (opcode >= 0x95 && opcode <= 0x99) || (opcode >= 0xbb && opcode <= 0xfe));
    


    benthecarman commented at 1:08 am on September 20, 2020:
    nit: would be easier for people to check if this was correct if they were ordered the same as the BIP

    sipa commented at 10:16 pm on September 22, 2020:
    Done. I intentionally didn’t change the implementation in test_framework, as this provides weak additional evidence that the semantics didn’t change compared to the older code.
  25. in src/script/script_error.cpp:79 in 94844a655f outdated
    74@@ -75,6 +75,10 @@ std::string ScriptErrorString(const ScriptError serror)
    75             return "Witness version reserved for soft-fork upgrades";
    76         case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION:
    77             return "Taproot version reserved for soft-fork upgrades";
    78+        case SCRIPT_ERR_DISCOURAGE_OP_SUCCESS:
    79+            return "SUCCESSx reserved for soft-fork upgrades";
    


    benthecarman commented at 1:10 am on September 20, 2020:
    nit: when referring to other op codes in the error messages they are normally prefixed with OP_, to be consistent this could be changed to OP_SUCCESSx reserved for...

    sipa commented at 10:16 pm on September 22, 2020:
    Done.
  26. jaybny commented at 11:02 am on September 22, 2020: none

    My expectation was to do a code review for taproot, but unfortunately, I cannot ACK the concept or the approach of this PR at this time.

    After initial research and deep dive, I ask the following:

    1. Will a majority of bitcoin users benefit from this PR?
      for consensus changes this answer should be a ‘YES’
    2. Does this PR create new security concerns or attack vectors for the typical bitcoin user? for consensus changes this answer should be a ‘NO’

    Below are my concerns with questions regarding this PR, answers and clarifications will hopefully bring me around.

    Background

    Original motivation to review this PR came from a tweet with a bounty. I am honored to help bitcoin, and am reviewing this PR independant of monetary expectations.

    Following documents have been read and reviewed in detail specifically for this review, skipping some code and proofs.

    Reason for not a simple “concept NACK” is because of the known information gap on my end. Specifically, I have not read most of bitcoin bips since early segwit.

    • My knowledge and use of bitcoin has been almost exclusively with P2PKH, including custom coded raw transactions on the utxo level.
    • Have not yet personally implemented BIP32 - Hierarchical Deterministic Wallets, nor SegWit

    Questions

    some of these are leading some of these are clarifying

    1. Are my current ECDSA private/public key pairs compatible with Schnorr?
    2. Where are the specific steps of transforming keys to Schnorr public/private key pairs?
    3. is there any difference in key management between ECDSA pairs and Schnorr pairs?
    4. is Schnorr compatible with BIP32?
    5. If we define complexity risk as a measure of required knowledge in relation to security expectations, does this PR add complexity risk?
    6. Can we measure the complexity risk delta of this PR?
    7. Would an expert ECDSA pk pair security manager need more knowledge to become an expert Schnorr pk pair manager?
    8. Would a future expert Schnorr pk pair manger already be an expert ECDSA pk pair manager?
    9. If we define a hard-knowledge-fork as needing more knowledge and a soft-knowledge-fork as needing less knowledge to use bitcoin, is this Taproot PR a hard-knowledge-fork or soft-knowledge-fork?
    10. Can Taproot be implemented with ECDSA? Technical: Pay-to-contract and Sign-to-contract?
    11. Is Taproot available for ECDSA in this PR?

    Concerns

    MultiSig I’m concerned that we are explicitly promoting MultiSig as a net positive for securing users private keys, and as far as i can tell, the consensus is that multisig comes down to a single leader in practice.

    poll: Is the use of multisig on a bitcoin transaction, a security feature or a false sense of security?

    Key Reuse I’m concerned that we are implicitly assuming that key reuse is wrong, but as far as i can tell, the consensus is that code reuse is fine for most cases.

    Highest concern is if the efficiency improvements, like removal of double sha256 hashing and others, are assuming no key reuse, creating a new vector for user error, since a majority of bitcoin users reuse their keys.

    similar sentiments:

    “While the key holders are expected not to reuse key pair, little could be done to stop payers to reuse an address. Unfortunately, key-pair reuse has been a social and technical norm since the creation of Bitcoin (the first tx made in block 170 reused the previous public key). I don’t see any hope to change this norm any time soon, if possible at all.” bitcoin-dev

    Overall, we are left with concerns both about the merit of doing Taproot versus alternatives, as well as the process through which we got to be here.

    1. Is Taproot actually more private than bare MAST and Schnorr separately? What are the actual anonymity set benefits compared to doing the separately?

    Yes, presuming single-pubkey-single-signature remains a common authorisation pattern. bitcoin-dev

    and fud:

    Similarly, the SIGHASH_SINGLE bug for example would have been disastrous for this scheme. In general, the Blockchain this is used in must not allow spending more than one output with a single signature.

    red flags:

    Homomorphic Payment Addresses and the Pay-to-Contract Protocol

    • Customer public key “a" in the tx cannot be used twice for same Public-Key of merchant
    • signaling variation? address(a,c) vs address(P,c) -
      • (a,c) becomes a private key

    other: DISCOURAGE_FLAG - why the need for this flag? can’t we say - ON_POLICY_CHANGE(Transform the mempool)?

    flagged for followup: BIP340

    This complicates the implementation of secure signing protocols in scenarios in which a group of mutually distrusting signers work together to produce a single joint signature

    Using the first option would be slightly more efficient for verification (around 10%), but we prioritize compactness, and therefore choose option 3. batch verification

    breaking an X only key - at most a small constant faster

    nonce - leaking secret

    every public key - has two corresponding secret keys

    not using the same private key - across different signing schemes - signatures can leak secret key through nonce reuse

    open up new nonce attack vectors

    However, if this optimization is used and additionally the signature verification at the end of the signing algorithm is dropped for increased efficiency, signers must ensure the public key is correctly calculated and not taken from untrusted sources.

    While recent academic papers claim that they are also possible with ECDSA, consensus support for Schnorr signature verification would significantly simplify the constructions.”

    BIP341

    Combining all these ideas in a single proposal would be an extensive change, be hard to review, and likely miss new discoveries that otherwise could have been made along the way.

    Just like other existing output types, taproot outputs should never reuse keys, for privacy reasons. This does not only apply to the particular leaf that was used to spend an output but to all leaves committed to in the output. If leaves were reused, it could happen that spending a different output would reuse the same Merkle branches in the Merkle proof.

    fixes the verification capabilities of offline signing devices by including amount and scriptPubKey in the signature message, avoids unnecessary hashing

    Hashes that go into the signature message and the message itself are now computed with a single SHA256 invocation instead of double SHA256. There is no expected security improvement by doubling SHA256 because this only protects against length-extension attacks against SHA256 which are not a concern for signature messages because there is no secret data. Therefore doubling SHA256 is a waste of resources.

    The public key is directly included in the output in contrast to typical earlier constructions which store a hash of the public key or script in the output. This has the same cost for senders and is more space efficient overall if the key-based spending path is taken. [2]

    BIP342

    The inefficient OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY opcodes are disabled. Instead, a new opcode OP_CHECKSIGADD is introduced to allow creating the same multisignature policies in a batch-verifiable way.

    Disabled script opcodes The following script opcodes are disabled in tapscript: OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY[2]. The disabled opcodes behave in the same way as OP_RETURN, by failing and terminating the script immediately when executed, and being ignored when found in unexecuted branch of the script.

    This way, new SIGHASH modes can be added, as well as NOINPUT-tagged public keys and a public key constant which is replaced by the taproot internal key for signature validation.

    Why is a limit on script size no longer needed? Since there is no scriptCode directly included in the signature hash (only indirectly through a precomputable tapleaf hash), the CPU time spent on a signature check is no longer proportional to the size of the script being executed.

    PR Purpose

    as stated

    privacy, efficiency, and flexibility of Bitcoin’s scripting capabilities

    This proposal aims to improve privacy, efficiency, and flexibility of Bitcoin’s scripting capabilities without adding new security assumptions[1]. Specifically, it seeks to minimize how much information about the spendability conditions of a transaction output is revealed on chain at creation or spending time and to add a number of upgrade mechanisms, while fixing a few minor but long-standing issues

    Taproot is an optional feature, and P2PKH key reusers, not concerned with privacy, do not need to us it. Yet, they still get the benefits of smaller multisig transactions.

    Is this chart the only benefits of this PR for the majority of users? Is it worth consensus soft-fork? What are the risks? image

    Taproot

    Taproot’s advantages become apparent under the assumption that most applications involve outputs that could be spent by all parties agreeing

    This use of practical reality built into the bitcoin protocol is exactly what we need more off! Maybe this abstraction can one day be made between alice and bob, because many times alice is really bob in a dress, and bob has no double spend risk on the tx from bob to bob.

    defaut spending path from what i understand, since in practice over 85% of bitcoin transaction are single key, the default path will be basically p2pkh (or the Segwit + Schnorr equivalent)

    user story If Taproot tree path spending is not for the 85% of bitcoin users, then who is it for? actual user stories will go a long way here.

  27. sipa commented at 12:42 pm on September 22, 2020: member

    @jaybny All of those questions are about the BIPs themselves, and not about the code changes to implement them, and thus out of scope here. I suggest you discuss them on the bitcoin-dev mailing list, https://bitcoin.stackexchange.com, or other media.

    I’ll give my answers for your list of short questions, but if you have follow up discussion, please take it elsewhere. It would be distracting to code reviewers here.

    1. Yes, intentionally. This is explained in BIP340.
    2. You remove the first byte of a 33-byte compressed pubkey to obtain a 32-byte x-only pubkey.
    3. No.
    4. Yes.
    5. BIP341/342 increase the functionality of the scripting language, which also implies there are more ways someone can screw up when doing nontrivial things. On the other hand, certain weaknesses that used to exist in legacy scripts and segwit scripts are fixed.
    6. That sounds subjective.
    7. No.
    8. Not sure what you mean.
    9. That sounds subjective.
    10. In theory yes, but doing most interesting things with it requires a way to have a single tweaked key that can be spent using consent of multiple parties. This is a lot harder with ECDSA than with Schnorr.
    11. This PR implements the BIP341 and BIP342 consensus rules. If those BIPs would have specified support for Taproot with ECDSA, then this PR would have needed to comply with that. BIP341 uses Schnorr signatures in its key spending path, and BIP342 explicitly specifies that all opcodes involving signatures use Schnorr signatures (OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD) or are disables (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY).
  28. in test/functional/test_framework/key.py:108 in 9673fd9999 outdated
    104@@ -86,13 +105,13 @@ def is_x_coord(self, x):
    105         return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
    106 
    107     def lift_x(self, x):
    108-        """Given an X coordinate on the curve, return a corresponding affine point."""
    109+        """Given an X coordinate on the curve, return a corresponding affine point for which the y-coordinate is even."""
    


    ysangkok commented at 6:19 pm on September 22, 2020:
    inconsistent capitalization and hyphenation

    sipa commented at 10:16 pm on September 22, 2020:
    Fixed.
  29. in test/functional/test_framework/key.py:411 in 9673fd9999 outdated
    406+        return (None, None)
    407+    P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, x)]))
    408+    return (P[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(P))
    409+
    410+def tweak_add_privkey(key, tweak, negated=False):
    411+    """Tweak a private key (after optionally negating it)."""
    


    ysangkok commented at 6:21 pm on September 22, 2020:
    unclear whether it will be negated again if it is already negated, or if it will be negated if it is not already negated

    sipa commented at 10:17 pm on September 22, 2020:
    Changed the interface to not take a negated argument, but infer it from the key. This should be cleaner and simpler.
  30. in test/functional/test_framework/key.py:484 in 9673fd9999 outdated
    479+
    480+def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
    481+    """Create a Schnorr signature (see BIP 340)."""
    482+
    483+    if aux is None:
    484+        aux = bytes(0 for _ in range(32))
    


    ysangkok commented at 6:23 pm on September 22, 2020:
    simpler to just use bytes(32). python wouldn’t let you have uninitialized memory.

    sipa commented at 10:17 pm on September 22, 2020:
    TIL. Done.
  31. in test/functional/test_framework/key.py:529 in 9673fd9999 outdated
    524+    def test_schnorr_testvectors(self):
    525+        """Implement the BIP340 test vectors (read from bip340_test_vectors.csv)."""
    526+        num_tests = 0
    527+        with open(os.path.join(sys.path[0], 'test_framework', 'bip340_test_vectors.csv'), newline='', encoding='utf8') as csvfile:
    528+            reader = csv.reader(csvfile)
    529+            reader.__next__()
    


    ysangkok commented at 6:23 pm on September 22, 2020:
    why call magic methods directly when you can use next(reader)? arguably more readable.

    sipa commented at 10:17 pm on September 22, 2020:

    This was copied from the BIP340 reference code. You may want to update it there too.

    Done.

  32. in test/functional/test_framework/key.py:486 in 9673fd9999 outdated
    481+    """Create a Schnorr signature (see BIP 340)."""
    482+
    483+    if aux is None:
    484+        aux = bytes(0 for _ in range(32))
    485+
    486+    assert(len(key) == 32)
    


    ysangkok commented at 6:29 pm on September 22, 2020:
    parens unnecessary

    sipa commented at 10:18 pm on September 22, 2020:
    Done, in many places.
  33. in test/functional/test_framework/key.py:432 in 9673fd9999 outdated
    427+    return x.to_bytes(32, 'big')
    428+
    429+def tweak_add_pubkey(key, tweak):
    430+    """Tweak a public key and return whether the result was negated."""
    431+
    432+    assert(len(key) == 32)
    


    ysangkok commented at 6:30 pm on September 22, 2020:
    parens unnecessary

    sipa commented at 10:18 pm on September 22, 2020:
    Done in many places.
  34. sipa force-pushed on Sep 22, 2020
  35. sipa force-pushed on Sep 22, 2020
  36. sipa commented at 10:21 pm on September 22, 2020: member

    I addressed comments by @benthecarman and @ysangkok. See “Updates 2020-09-22” in the new PR #19997 for the changes that were applied.

    I also moved the “keep spent outputs in PrecomputedTransactionData” to the refactors section, as it contains no semantics changes, and didn’t depend on any earlier commits.

  37. instagibbs commented at 2:10 pm on September 23, 2020: member

    reviewed updates to https://github.com/bitcoin/bitcoin/pull/19953/commits/fe5cf2ed6adbf2271553f97c61de4d59bee20c05

    IsOpSuccess was compared against the BIP directly rather than previous iteration, which I had hand-calculated painstakingly. It matches the BIP.

  38. in src/script/interpreter.cpp:1435 in 34da219826 outdated
    1430+    }
    1431+    if (have_annex) {
    1432+        ss << (CHashWriter(SER_GETHASH, 0) << witstack->back()).GetSHA256();
    1433+    }
    1434+
    1435+    // Data about the output(s)
    


    instagibbs commented at 6:55 pm on September 23, 2020:

    sipa commented at 9:18 pm on September 23, 2020:
    Fixed.
  39. in src/script/interpreter.cpp:442 in 390252cd5b outdated
    438@@ -381,6 +439,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
    439     // static const valtype vchZero(0);
    440     static const valtype vchTrue(1, 1);
    441 
    442+    // sigversion cannot be TAPROOT here, as it admits no script execution.
    443+    assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT);
    


    instagibbs commented at 7:11 pm on September 23, 2020:
    pico-nit: why not just assert(sigversion != SigVersion:: TAPROOT)?

    benthecarman commented at 8:17 pm on September 23, 2020:
    If a new sig version is added in the future, we probably want it to fail here.

    instagibbs commented at 8:19 pm on September 23, 2020:
    eh ok, guess crashing would be best in this case

    sipa commented at 8:21 pm on September 23, 2020:
    Yeah, I’m intentionally only using equality checks with sigversion (all over interpreter.cpp), so that there are no surprises when a new one is added.
  40. in src/script/interpreter.cpp:1678 in 390252cd5b outdated
    1674@@ -1565,7 +1675,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns
    1675         if (hashtype == SIGHASH_DEFAULT) return false;
    1676     }
    1677     uint256 sighash;
    1678-    if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, this->txdata)) return false;
    1679+    if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, 0x00, this->txdata)) return false;
    


    instagibbs commented at 7:19 pm on September 23, 2020:
    0    if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, 0x00 /* key_version */, this->txdata)) return false;
    

    sipa commented at 9:18 pm on September 23, 2020:
    Done.
  41. in src/test/script_tests.cpp:1653 in fe5cf2ed6a outdated
    1648+        SCRIPT_VERIFY_P2SH |
    1649+        SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY |
    1650+        SCRIPT_VERIFY_CHECKSEQUENCEVERIFY |
    1651+        SCRIPT_VERIFY_WITNESS |
    1652+        SCRIPT_VERIFY_NULLFAIL |
    1653+        SCRIPT_VERIFY_TAPROOT;
    


    instagibbs commented at 8:09 pm on September 23, 2020:
    Why no MINIMALIF?

    instagibbs commented at 8:20 pm on September 23, 2020:
    … because it’s not used for consensus, along with CLEANSTACK et al. Right.

    sipa commented at 9:18 pm on September 23, 2020:
    Added a comment in script/interpreter.h.
  42. instagibbs approved
  43. instagibbs commented at 8:10 pm on September 23, 2020: member

    ACK https://github.com/bitcoin/bitcoin/pull/19953/commits/fe5cf2ed6adbf2271553f97c61de4d59bee20c05

    Reviewed all commits one by one, side by side with my latest known-good review. Poked at new unit tests/mutated logic as a sanity check.

    One request I’d have is some documentation on how to extract the unit test, if possible. Maybe it was hacked together, but still having a general description of using the fuzzing construct could be helpful for people to look for smaller/better sets. For example, someone could create coverage for MINIMALIF. (I did a mutation test for it and it passed)

  44. in src/script/interpreter.cpp:1444 in 34da219826 outdated
    1338+        if (!txTo.vin[inpos].scriptWitness.IsNull()) {
    1339+            if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + WITNESS_V1_TAPROOT_SIZE &&
    1340+                m_spent_outputs[inpos].scriptPubKey[0] == OP_1) {
    1341+                // Treat every native witness v1 spend as a Taproot spend. This only works if spent_outputs was
    1342+                // provided as well, but if it wasn't, actual validation will fail anyway.
    1343+                uses_bip341_taproot = true;
    


    promag commented at 8:47 pm on September 23, 2020:

    34da2198264cf90a7ae7f50a912a599f6fd66291

    No need to be exhaustive?

    0uses_bip341_taproot = true;
    1if (uses_bip143_segwit) break;
    

    sipa commented at 9:30 pm on September 23, 2020:
    No, one input can be segwit and another can be taproot.

    benthecarman commented at 9:41 pm on September 23, 2020:
    This should just be an optimization, ie if there are 100 inputs and the first is segwit v0 and the second is segwit v1, with @promag’s suggestion it would then stop checking with both uses_bip341_taproot and uses_bip143_segwit set correctly, vs with the current implementation it would continue and check the remaining 98 inputs.

    sipa commented at 9:44 pm on September 23, 2020:
    Oh, I misread. Indeed, that’s a potentially useful and simple optimization. I’ll implement it when I make further changes.

    sipa commented at 3:43 am on September 25, 2020:
    Done.
  45. in src/script/interpreter.cpp:1491 in 34da219826 outdated
    1387+        break;
    1388+    default:
    1389+        assert(false);
    1390+    }
    1391+    assert(in_pos < tx_to.vin.size());
    1392+    assert(cache != nullptr && cache->m_bip341_taproot_ready && cache->m_spent_outputs_ready);
    


    promag commented at 8:53 pm on September 23, 2020:

    34da2198264cf90a7ae7f50a912a599f6fd66291

    Then change to const PrecomputedTransactionData& cache instead?


    sipa commented at 3:43 am on September 25, 2020:
    Done.
  46. sipa force-pushed on Sep 23, 2020
  47. sipa commented at 9:25 pm on September 23, 2020: member

    I addressed comments by @instagibbs, and made the unit test vectors test more and generic and made it qa-assets based (see corresponding PR https://github.com/bitcoin-core/qa-assets/pull/27). Changes are in two new sections in #19997.

    I’m still working on polishing up the tooling used to create script_assets_test.json. At this point they don’t test anything that (many iterations of) feature_taproot.py doesn’t test, so I think review should be focused on that.

  48. sipa force-pushed on Sep 25, 2020
  49. sipa commented at 3:45 am on September 25, 2020: member
    I addressed comments by @promag, added a functional test to verify CLEANSTACK is consensus-enforced in tapscript, and gave tapscript’s MINIMALIF a separate error code (effectively addressing #20009 to the extent it affects this PR).
  50. benthecarman approved
  51. benthecarman commented at 4:53 am on September 25, 2020: contributor
    ACK 80efcba
  52. in src/script/interpreter.cpp:1523 in 80efcba5e7 outdated
    1514+    ss << EPOCH;
    1515+
    1516+    // Hash type
    1517+    const uint8_t output_type = (hash_type == SIGHASH_DEFAULT) ? SIGHASH_ALL : (hash_type & SIGHASH_OUTPUT_MASK); // Default (no sighash byte) is equivalent to SIGHASH_ALL
    1518+    const uint8_t input_type = hash_type & SIGHASH_INPUT_MASK;
    1519+    if (!(hash_type <= 0x03 || (hash_type >= 0x81 && hash_type <= 0x83))) return false;
    


    ysangkok commented at 6:37 pm on September 28, 2020:

    This line is very compact.

    Looks like those numeric literals are based on SIGHASH_INPUT_MASK. If they are, could they not be constructed from it? It is weird to have logic based partly on the enum, and partly on hard coded numeric literals.

    I think this could be split up into two if’s, each returning false. Then, each of those branches could be explaining the situation and the reason for rejection.

    Every time I see a negation of a boolean operator, I do de-morgan in my head. Either way, if you prefer this with or without the outer negation, I think splitting it up would really be preferable, it would be readable for all kinds of brains.

    This is not complicated logic, and it is easy to avoid having it look complex. Just my two cents :)


    sipa commented at 6:44 pm on September 28, 2020:

    That would be incorrect.

    de-morganing it gives you !(hash_type <= 0x03) && !(hash_type >= 0x81 && hash_type <= 0x83), which is a conjunction at the top level. Splitting that up into two separate ifs would make it a disjunction.


    kallewoof commented at 6:54 am on October 12, 2020:

    50f0a10f7b2e45491775c1166c4593e7fa8cfd4b

    0    if (hash_type >= 0x84 || (hash_type >= 0x04 && hash_type <= 0x80)) return false;
    

    seems slightly more straightforward, IMO.

    Interpreting it becomes “If the hash type is above 0x84 or within 0x04 and 0x80” vs “if it is NOT the case that the hash type is <= 0x03 or within 0x81 and 0x83”.

  53. sipa force-pushed on Oct 2, 2020
  54. sipa force-pushed on Oct 2, 2020
  55. sipa force-pushed on Oct 2, 2020
  56. sipa commented at 8:23 am on October 2, 2020: member

    A few updates (see #19997 for detailed history):

    • Fixed/clarified a few code comments addressing anonymous review
    • A few generic improvements to the functional test (include ECDSA tests with uncompressed pubkeys, cache witnesses)
    • Added support to the functional test to dump its generated tests in json format compatible with the unit test, and a fuzz “test” to help minimize it (it actually works as a fuzz test itself now too, though probably not a very good one)
    • Rebased on master to adapt the test for #20006.
  57. sipa force-pushed on Oct 2, 2020
  58. in src/chainparams.cpp:91 in c849dae8ce outdated
    85@@ -86,6 +86,11 @@ class CMainParams : public CChainParams {
    86         consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
    87         consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008
    88 
    89+        // Deployment of Taproot (BIPs 340-342)
    90+        consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2;
    91+        consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = 1199145601; // January 1, 2008
    


    decryp2kanon commented at 9:20 am on October 2, 2020:
    just curious, why January 1, 2008?

    instagibbs commented at 2:16 pm on October 2, 2020:
    Copying DEPLOYMENT_TESTDUMMY I suspect

    sipa commented at 4:21 pm on October 2, 2020:
    Indeed, just whatever time sufficiently in the past.
  59. benthecarman commented at 12:09 pm on October 2, 2020: contributor
    reACK c849dae
  60. in src/test/script_tests.cpp:1503 in c849dae8ce outdated
    1499@@ -1470,6 +1500,7 @@ BOOST_AUTO_TEST_CASE(script_HasValidOps)
    1500     BOOST_CHECK(!script.HasValidOps());
    1501 }
    1502 
    1503+
    


    decryp2kanon commented at 9:39 am on October 3, 2020:
    nit: a new line?

    sipa commented at 9:59 pm on October 6, 2020:
    Fixed.
  61. in test/functional/test_framework/key.py:2 in c849dae8ce outdated
    0@@ -1,15 +1,30 @@
    1-# Copyright (c) 2019 Pieter Wuille
    2+# Copyright (c) 2019-2020 Pieter Wuille
    3+
    


    decryp2kanon commented at 9:43 am on October 3, 2020:
    nit: new line?

    sipa commented at 9:59 pm on October 6, 2020:
    Fixed.
  62. in src/script/interpreter.h:248 in 6577f465f3 outdated
    245 
    246 using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
    247 using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker<CMutableTransaction>;
    248 
    249-bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr);
    250+bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr, ScriptExecutionData execdata = {});
    


    promag commented at 8:12 pm on October 5, 2020:

    6577f465f3dad2460b2bee02ca662c7ebbe72e4c nit, avoids copying ScriptExecutionData and error is still the last one.

    0bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* error = nullptr);
    1bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr)
    2{
    3    ScriptExecutionData execdata;
    4    EvalScript(stack, script, flags, checker, sigversion, execdata, error);
    5}
    

    sipa commented at 8:53 pm on October 5, 2020:

    That won’t work, EvalScript needs a mutable ScriptExecutionData object.

    It would be slightly cleaner to split the execdata into a fixed-at-VerifyScript-time part and a changed-during-execution part, but that’d be a even more annoying interface change.


    sipa commented at 8:52 pm on October 6, 2020:

    Actually, it’s possible to avoid the copy still by just making it mutable, and using your idea of a wrapper to provide a default:

    https://github.com/sipa/bitcoin/commit/09b24d503a7b9baace0be3f51e53f2a095cf160b

     0diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
     1index 9d25ec156a..f4cf24fee5 100644
     2--- a/src/script/interpreter.cpp
     3+++ b/src/script/interpreter.cpp
     4@@ -428,8 +428,7 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con
     5     assert(false);
     6 }
     7 
     8-
     9-bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, ScriptExecutionData execdata)
    10+bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
    11 {
    12     static const CScriptNum bnZero(0);
    13     static const CScriptNum bnOne(1);
    14@@ -1257,6 +1256,12 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
    15     return set_success(serror);
    16 }
    17 
    18+bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
    19+{
    20+    ScriptExecutionData execdata;
    21+    return EvalScript(stack, script, flags, checker, sigversion, execdata, serror);
    22+}
    23+
    24 namespace {
    25 
    26 /**
    27@@ -1776,7 +1781,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
    28 template class GenericTransactionSignatureChecker<CTransaction>;
    29 template class GenericTransactionSignatureChecker<CMutableTransaction>;
    30 
    31-static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CScript& scriptPubKey, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, const ScriptExecutionData& execdata, ScriptError* serror)
    32+static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CScript& scriptPubKey, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror)
    33 {
    34     std::vector<valtype> stack{stack_span.begin(), stack_span.end()};
    35 
    36@@ -1810,7 +1815,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
    37     }
    38 
    39     // Run the script interpreter.
    40-    if (!EvalScript(stack, scriptPubKey, flags, checker, sigversion, serror, execdata)) return false;
    41+    if (!EvalScript(stack, scriptPubKey, flags, checker, sigversion, execdata, serror)) return false;
    42 
    43     // Scripts inside witness implicitly require cleanstack behaviour
    44     if (stack.size() != 1) return set_error(serror, SCRIPT_ERR_CLEANSTACK);
    45diff --git a/src/script/interpreter.h b/src/script/interpreter.h
    46index c71a125e73..1050ed1550 100644
    47--- a/src/script/interpreter.h
    48+++ b/src/script/interpreter.h
    49@@ -272,7 +272,8 @@ public:
    50 using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
    51 using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker<CMutableTransaction>;
    52 
    53-bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr, ScriptExecutionData execdata = {});
    54+bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr);
    55+bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr);
    56 bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr);
    57 
    58 size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags);
    

    sipa commented at 10:02 pm on October 6, 2020:
    I’ve incorporated this patch, as I think it conceptually makes sense: ScriptExecutionData’s contents has a linear history, so there is no need to pass it as const.
  63. --- [TAPROOT] Refactors --- f8c099e220
  64. scripted-diff: put ECDSA in name of signature functions
    In preparation for adding Schnorr versions of `CheckSig`, `VerifySignature`, and
    `ComputeEntry`, give them an ECDSA specific name.
    
    -BEGIN VERIFY SCRIPT-
    sed -i 's/CheckSig(/CheckECDSASignature(/g' $(git grep -l CheckSig ./src)
    sed -i 's/VerifySignature(/VerifyECDSASignature(/g' $(git grep -l VerifySignature ./src)
    sed -i 's/ComputeEntry(/ComputeEntryECDSA(/g' $(git grep -l ComputeEntry ./src)
    -END VERIFY SCRIPT-
    107b57df9f
  65. refactor: rename scriptPubKey in VerifyWitnessProgram to exec_script
    The old name is confusing, as it doesn't store a scriptPubKey, but the
    actually executed script.
    8bd2b4e784
  66. in src/validation.cpp:1546 in c849dae8ce outdated
    1543+    if (!txdata.m_spent_outputs_ready) {
    1544+        std::vector<CTxOut> spent_outputs;
    1545+        spent_outputs.reserve(tx.vin.size());
    1546+
    1547+        for (const auto& txin : tx.vin) {
    1548+            const COutPoint &prevout = txin.prevout;
    


    jonatack commented at 7:43 pm on October 6, 2020:

    1ef50ed µ-nit

    0            const COutPoint& prevout = txin.prevout;
    

    sipa commented at 7:34 pm on October 7, 2020:
    Done.
  67. in src/script/interpreter.cpp:1421 in c849dae8ce outdated
    1412+
    1413 } // namespace
    1414 
    1415 template <class T>
    1416-void PrecomputedTransactionData::Init(const T& txTo)
    1417+void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut> spent_outputs)
    


    jonatack commented at 7:48 pm on October 6, 2020:

    1ef50ed perhaps, per #19953 (review)

    0void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs)
    

    sipa commented at 7:34 pm on October 7, 2020:
    Done.
  68. in src/script/script.h:50 in c849dae8ce outdated
    43@@ -44,6 +44,17 @@ static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov  5 00:53:20
    44 // SEQUENCE_FINAL).
    45 static const uint32_t LOCKTIME_MAX = 0xFFFFFFFFU;
    46 
    47+// Tag for input annex. If there are at least two witness elements for a transaction input,
    48+// and the first byte of the last element is 0x50, this last element is called annex, and
    49+// has meanings independent of the script
    50+static const unsigned int ANNEX_TAG = 0x50;
    


    jonatack commented at 8:19 pm on October 6, 2020:

    31c322a perhaps use constexpr

    0static constexpr unsigned int ANNEX_TAG = 0x50;
    

    sipa commented at 7:34 pm on October 7, 2020:
    Done.
  69. in src/script/interpreter.cpp:1490 in c849dae8ce outdated
    1491+static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
    1492+static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
    1493+static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
    1494+
    1495+template<typename T>
    1496+bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, const uint32_t in_pos, const uint8_t hash_type, const SigVersion sigversion, const uint8_t key_version, const PrecomputedTransactionData& cache)
    


    jonatack commented at 8:29 pm on October 6, 2020:

    In commits 31c322a (in_pos, hash_type, sigversion) and 332dc13bdc (key_version) perhaps remove constness for by-value arguments, e.g. per discussion at #19845 (review)

    0bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, uint8_t key_version, const PrecomputedTransactionData& cache)
    

    sipa commented at 7:34 pm on October 7, 2020:
    Done.
  70. in src/script/interpreter.cpp:1480 in c849dae8ce outdated
    1481 
    1482 // explicit instantiation
    1483-template void PrecomputedTransactionData::Init(const CTransaction& txTo);
    1484-template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo);
    1485+template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut> spent_outputs);
    1486+template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut> spent_outputs);
    


    jonatack commented at 8:33 pm on October 6, 2020:

    1ef50ed perhaps, per #19953 (review)

    0-template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut> spent_outputs);
    1-template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut> spent_outputs);
    2+template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs);
    3+template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs);
    

    sipa commented at 7:34 pm on October 7, 2020:
    Done.
  71. in src/script/interpreter.h:170 in c849dae8ce outdated
    167 
    168     PrecomputedTransactionData() = default;
    169 
    170     template <class T>
    171-    void Init(const T& tx);
    172+    void Init(const T& tx, std::vector<CTxOut> spent_outputs);
    


    jonatack commented at 8:34 pm on October 6, 2020:

    1ef50ed perhaps, as the caller is txdata.Init(tx, std::move(spent_outputs));, if we want to force callers to move it

    0    void Init(const T& tx, std::vector<CTxOut>&& spent_outputs);
    

    sipa commented at 7:34 pm on October 7, 2020:
    Done.
  72. sipa force-pushed on Oct 6, 2020
  73. sipa commented at 10:04 pm on October 6, 2020: member
    Addressed comments by @decryp2kanon and @promag. See detailed history in #19997.
  74. benthecarman commented at 4:35 am on October 7, 2020: contributor
    reACK 7dfb723
  75. in src/pubkey.h:216 in 7dfb723977 outdated
    211+private:
    212+    uint256 m_keydata;
    213+
    214+public:
    215+    /** Construct an x-only pubkey from exactly 32 bytes. */
    216+    XOnlyPubKey(Span<const unsigned char> input);
    


    jonatack commented at 9:52 am on October 7, 2020:
    c018355 perhaps use the same param name in the definition XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> in) as in the declaration

    sipa commented at 7:35 pm on October 7, 2020:
    Renamed them both to bytes.
  76. in src/script/sigcache.cpp:41 in 7dfb723977 outdated
    39-        m_salted_hasher.Write(nonce.begin(), 32);
    40-        m_salted_hasher.Write(nonce.begin(), 32);
    41+        // just write our 32-byte entropy, and then pad with 'E' for ECDSA and
    42+        // 'S' for Schnorr (followed by 0 bytes).
    43+        static const unsigned char PADDING_ECDSA[32] = {'E'};
    44+        static const unsigned char PADDING_SCHNORR[32] = {'S'};
    


    jonatack commented at 9:57 am on October 7, 2020:
    c018355 perhaps use constexpr for these two declarations

    sipa commented at 7:35 pm on October 7, 2020:
    Done.
  77. in src/pubkey.cpp:183 in 7dfb723977 outdated
    178+    secp256k1_xonly_pubkey pubkey;
    179+    if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data())) return false;
    180+    return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), &pubkey);
    181+}
    182+
    183+bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, const bool parity) const
    


    jonatack commented at 10:19 am on October 7, 2020:

    3c2d549 constness not present in the declaration on by-value argument

    0bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const
    

    sipa commented at 7:35 pm on October 7, 2020:
    Done.
  78. jonatack commented at 11:03 am on October 7, 2020: member

    First pass of code review and debug build of each commit up to 3c2d549ee. No blocking issues seen so far. A few minor comments follow; feel free to pick/choose/ignore. Continuing on the second half of the commits.

    Reviewers: if helpful, there are several review club sessions on this BIP340-342 implementation at https://bitcoincore.reviews/meetings-components/#consensus

    Edit: a new dedicated taproot review club section has been added:

    https://bitcoincore.reviews/meetings-components/#taproot

  79. in src/script/interpreter.h:206 in 7dfb723977 outdated
    205+    uint256 m_annex_hash;
    206+
    207+    /** Whether m_validation_weight_left is initialized. */
    208+    bool m_validation_weight_left_init = false;
    209+    /** How much validation weight is left (decremented for every successful non-empty signature check). */
    210+    int64_t m_validation_weight_left;
    


    jonatack commented at 2:44 pm on October 7, 2020:

    332dc13b perhaps use same doc style as other members of this struct, and also per doc/developer-notes.md “To describe a member or variable”

    0-    /** Whether m_validation_weight_left is initialized. */
    1+    //! Whether m_validation_weight_left is initialized.
    2     bool m_validation_weight_left_init = false;
    3-    /** How much validation weight is left (decremented for every successful non-empty signature check). */
    4+    //! How much validation weight is left (decremented for every successful non-empty signature check).
    

    sipa commented at 7:35 pm on October 7, 2020:
    Done.
  80. in src/script/interpreter.cpp:442 in 7dfb723977 outdated
    437@@ -381,6 +438,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
    438     // static const valtype vchZero(0);
    439     static const valtype vchTrue(1, 1);
    440 
    441+    // sigversion cannot be TAPROOT here, as it admits no script execution.
    442+    assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT);
    


    jonatack commented at 3:38 pm on October 7, 2020:
    332dc13bd 4 instances of sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 in EvalScript(), could (very optionally) factor out to a const bool localvar

    sipa commented at 7:00 pm on October 7, 2020:
    I’d rather be explicit in every calling site about which sigversions are affected.
  81. in src/script/interpreter.cpp:1494 in 7dfb723977 outdated
    1498+static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
    1499+
    1500+template<typename T>
    1501+bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, const uint32_t in_pos, const uint8_t hash_type, const SigVersion sigversion, const uint8_t key_version, const PrecomputedTransactionData& cache)
    1502+{
    1503+    uint8_t ext_flag;
    


    jonatack commented at 3:57 pm on October 7, 2020:

    332dc13b currently low-risk here, but perhaps avoid future possibility of use before set

    0    uint8_t ext_flag{0};
    

    sipa commented at 7:03 pm on October 7, 2020:
    I believe that not initializing a variable is better than initializing it incorrectly. The former lets you use tooling (ubsan, valgrind) to detect invalid usage, instead of creating a potentially very hard to discover bug (I don’t think it matters much here, as it’s a very simple case, but as a matter of principle I dislike initialization of variables just to avoid a risk of them being uninitialized).
  82. in src/script/interpreter.cpp:641 in 7dfb723977 outdated
    633@@ -568,6 +634,13 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
    634                         if (stack.size() < 1)
    635                             return set_error(serror, SCRIPT_ERR_UNBALANCED_CONDITIONAL);
    636                         valtype& vch = stacktop(-1);
    637+                        // Tapscript requires minimal IF/NOTIF inputs as a consensus rule.
    638+                        if (sigversion == SigVersion::TAPSCRIPT) {
    639+                            if (vch.size() > 1 || (vch.size() == 1 && vch[0] != 1)) {
    


    jonatack commented at 4:33 pm on October 7, 2020:

    332dc13bdc perhaps add this additional code documentation from BIP342 to clarify the intention of the code

    0                         // Tapscript requires minimal IF/NOTIF inputs as a consensus rule.
    1                         if (sigversion == SigVersion::TAPSCRIPT) {
    2+                            // The input argument to the OP_IF and OP_NOTIF opcodes must be either
    3+                            // exactly 0 (the empty vector) or exactly 1 (the one-byte vector with value 1).
    4                             if (vch.size() > 1 || (vch.size() == 1 && vch[0] != 1)) {
    

    sipa commented at 7:35 pm on October 7, 2020:
    Done.
  83. in src/script/interpreter.cpp:1559 in 7dfb723977 outdated
    1565+
    1566+    // Additional data for BIP 342 signatures
    1567+    if (sigversion == SigVersion::TAPSCRIPT) {
    1568+        assert(execdata.m_tapleaf_hash_init);
    1569+        ss << execdata.m_tapleaf_hash;
    1570+        assert(key_version == 0); // key_version must be 0 for now
    


    jonatack commented at 4:44 pm on October 7, 2020:

    332dc13bdc perhaps add this additional info from BIP 342

    0-        assert(key_version == 0); // key_version must be 0 for now
    1+        // key_version must be 0 for now, representing the current version of
    2+        // public keys in the tapscript signature opcode execution.
    3+        assert(key_version == 0);
    

    sipa commented at 7:35 pm on October 7, 2020:
    Done.
  84. in src/script/script.cpp:340 in 7dfb723977 outdated
    335+bool IsOpSuccess(const opcodetype& opcode)
    336+{
    337+    return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) ||
    338+           (opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) ||
    339+           (opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
    340+           (opcode >= 187 && opcode <= 254);
    


    jonatack commented at 5:26 pm on October 7, 2020:

    332dc13b maybe simplify

    0     return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) ||
    1-           (opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) ||
    2-           (opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
    3+           (opcode >= 131 && opcode <= 134) || opcode == 137 || opcode == 138 ||
    4+           opcode == 141 || opcode == 142 || (opcode >= 149 && opcode <= 153) ||
    5            (opcode >= 187 && opcode <= 254);
    

    instagibbs commented at 6:15 pm on October 7, 2020:

    IIRC the text as-is completely “matches” the BIP text, meaning if it gives a range here, it gives a range in the BIP text. I think as-is is easier to pattern match.

    80, 98, 126-129, 131-134, 137-138, 141-142, 149-153, 187-254


    jonatack commented at 6:23 pm on October 7, 2020:
    True, though the BIP prose here is inconsistent (see the first 2 elements) and could be updated. The code could opt for the simpler version. Not a biggie though–I hesitated to comment on it.

    sipa commented at 7:30 pm on October 7, 2020:
    I prefer to keep it exactly as in the BIP for now, but no reason the BIP can’t be updated to be more clear.
  85. jonatack commented at 5:38 pm on October 7, 2020: member
    Checkpoint after review of commits 6e42c8d0721a9 and 332dc13b, cross-referencing the implementation with the BIPs. The code appears to follow the spec where I have verified. Continuing forward with the remaining commits.
  86. sipa force-pushed on Oct 7, 2020
  87. sipa commented at 7:36 pm on October 7, 2020: member
    I addressed comments by @jonatack. See detailed history in #19997.
  88. benthecarman commented at 7:47 am on October 8, 2020: contributor
    reACK 07dd29e
  89. laanwj added this to the milestone 0.21.0 on Oct 8, 2020
  90. sipa force-pushed on Oct 9, 2020
  91. sipa commented at 1:38 am on October 9, 2020: member

    I made a few more changes related to signature checking:

    • The key_version (currently always 0) is no longer passed as an argument into SignatureHashSchnorr, because if a new one was introduced, it would need a new SigVersion as well. So instead use the latter to derive the key_version. @ariard This partially reverts an earlier comment you had, but I think the comments make it clearer still.
    • Introduce distinct script validation errors for the various ways Schnorr signatures (in Taproot/Tapscript) can fail (invalid length, invalid sighash type, or actual Schnorr validation failure). This allows more accurate testing in feature_taproot.py, but did require passing down ScriptError in a few more places.

    As always, details of the changes are in #19997.

  92. benthecarman commented at 8:47 am on October 9, 2020: contributor
    reACK 4a7e017
  93. in src/script/interpreter.cpp:1834 in 4a7e0171c0 outdated
    1834     if (!CastToBool(stack.back())) return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
    1835     return true;
    1836 }
    1837 
    1838-static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
    1839+static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256* tapleaf_hash)
    


    jnewbery commented at 2:50 pm on October 9, 2020:
    Why is tapleaf_hash passed as a pointer with a non-null check? This function is always called with a non-null tapleaf_hash, so it’d make more sense to pass by reference.

    sipa commented at 0:21 am on October 13, 2020:
    Fixed.
  94. in src/validation.cpp:1555 in 6b0f2a4320 outdated
    1550+            spent_outputs.emplace_back(coin.out);
    1551+        }
    1552+        txdata.Init(tx, std::move(spent_outputs));
    1553     }
    1554 
    1555     for (unsigned int i = 0; i < tx.vin.size(); i++) {
    


    fjahr commented at 10:48 pm on October 10, 2020:

    in 6b0f2a4320aebe1b802763a559cbaf3c348c2ffc:

    Not sure if it’s needed but could add an assert for extra caution:

    0assert(txdata.m_spent_outputs.size() == tx.vin.size());
    

    sipa commented at 9:21 am on October 12, 2020:
    Added.
  95. in src/script/interpreter.cpp:1922 in 68f87cb0da outdated
    1754+                return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
    1755+            }
    1756+            if (!VerifyTaprootCommitment(control, program, exec_script)) {
    1757+                return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
    1758+            }
    1759+            if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
    


    fjahr commented at 8:42 am on October 11, 2020:

    in 68f87cb0da326bcaa2570ed6299a7ee1c6d7b5f6:

    nit: this could be at the beginning of the else block I think


    sipa commented at 8:24 am on October 12, 2020:
    This is no longer the case after later commits, where actual script semantics are added.
  96. in src/script/interpreter.cpp:414 in c3825c1bc4 outdated
    409+}
    410+
    411+/** Helper for OP_CHECKSIG, OP_CHECKSIGVERIFY, and (in Tapscript) OP_CHECKSIGADD.
    412+ *
    413+ * A return value of false means the script fails entirely. When true is returned, the
    414+ * fSuccess variable indicates whether the signature check itself succeeded.
    


    fjahr commented at 7:26 pm on October 11, 2020:

    in c3825c1bc41815d9b27d9a5dc36e46165b2f5861:

    0 * success variable indicates whether the signature check itself succeeded.
    

    sipa commented at 9:21 am on October 12, 2020:
    Fixed.
  97. in test/functional/feature_taproot.py:100 in 58c90569f7 outdated
     95+# Specifically, a context object is a dict that maps names to compositions of:
     96+# - values
     97+# - lists of values
     98+# - callables which, when fed the context object as argument, produce any of these
     99+#
    100+# # The DEFAULT_CONTEXT object specifies a standard signing process, with many overridable knobs.
    


    fjahr commented at 8:45 pm on October 11, 2020:

    in 58c90569f75221bc4652cbde5cad4dbae543a1ca:

    nit

    0# The DEFAULT_CONTEXT object specifies a standard signing process, with many overridable knobs.
    

    sipa commented at 9:21 am on October 12, 2020:
    Fixed.
  98. in test/functional/feature_taproot.py:1203 in 58c90569f7 outdated
    1198+        the transaction. This is accomplished by constructing transactions consisting
    1199+        of all valid inputs, except one invalid one.
    1200+        """
    1201+
    1202+        # Generate some coins to fund the wallet.
    1203+        node.generate(10)
    


    fjahr commented at 9:00 pm on October 11, 2020:

    in 58c90569f75221bc4652cbde5cad4dbae543a1ca:

    There are already some 101 blocks mined on the taproot node to fund it earlier and then funds are transferred to the non-taproot node to fund it. I guess this generate can be removed and the blocks could be mined earlier?


    sipa commented at 9:20 am on October 12, 2020:
    Indded, fixed.
  99. in test/functional/feature_taproot.py:1241 in 58c90569f7 outdated
    1236+
    1237+            fund_tx = CTransaction()
    1238+            # Add the 50 highest-value inputs
    1239+            unspents = node.listunspent()
    1240+            random.shuffle(unspents)
    1241+            unspents.sort(key=lambda x: -int(x["amount"] * 100000000))
    


    fjahr commented at 9:16 pm on October 11, 2020:

    in 58c90569f75221bc4652cbde5cad4dbae543a1ca:

    0            unspents.sort(key=lambda x: int(x["amount"] * 100000000), reverse=True)
    

    sipa commented at 9:20 am on October 12, 2020:
    Nice, done.
  100. in test/functional/feature_taproot.py:1069 in 58c90569f7 outdated
    1064+            ("1001push_nop", CScript([OP_0] * 1001 + [OP_NOP])),
    1065+        ]
    1066+        random.shuffle(scripts)
    1067+        tap = taproot_construct(pubs[0], scripts)
    1068+        add_spender(spenders, "opsuccess/bare", standard=False, tap=tap, leaf="bare_success", failure={"leaf": "bare_nop"}, **ERR_CLEANSTACK)
    1069+        add_spender(spenders, "opsuccess/exexecif", standard=False, tap=tap, leaf="unexecif_success", failure={"leaf": "unexecif_nop"}, **ERR_CLEANSTACK)
    


    fjahr commented at 9:47 pm on October 11, 2020:

    in 58c90569f75221bc4652cbde5cad4dbae543a1ca:

    probably?

    0        add_spender(spenders, "opsuccess/unexecif", standard=False, tap=tap, leaf="unexecif_success", failure={"leaf": "unexecif_nop"}, **ERR_CLEANSTACK)
    

    sipa commented at 9:20 am on October 12, 2020:
    Done.
  101. fjahr commented at 11:33 pm on October 11, 2020: member
    Commit 87dad64920eeaf41e5e10c503f1e0f48c5d120d4 does not have a description at the moment and it looks like you might have wanted to squash it?
  102. in src/script/interpreter.cpp:1423 in 50f0a10f7b outdated
    1418+    if (output_type == SIGHASH_ALL) {
    1419+        ss << cache.m_outputs_single_hash;
    1420+    }
    1421+
    1422+    // Data about the input/prevout being spent
    1423+    const auto* witstack = &tx_to.vin[in_pos].scriptWitness.stack;
    


    kallewoof commented at 7:07 am on October 12, 2020:
    This seems to disappear in a later commit so not really an issue, but I’m curious why this isn’t const auto& witstack = tx_to.[...] which should effectively have the same result, except as a C++ ref rather than a pointer.

    sipa commented at 9:20 am on October 12, 2020:
    Fixed.
  103. kallewoof commented at 8:25 am on October 12, 2020: member
    Tested ACK, code review up to and including regtest section (5ffd2ee94fcce072842365fe96e29169ac3c0524)
  104. refactor: keep spent outputs in PrecomputedTransactionData
    A BIP-341 signature message may commit to the scriptPubKeys and amounts
    of all spent outputs (including other ones than the input being signed
    for spends), so keep them available to signature hashing code.
    5d62e3a68b
  105. --- [TAPROOT] BIP340/341/342 consensus rules --- 450d2b2371
  106. Add TaggedHash function (BIP 340)
    This adds the TaggedHash function as defined by BIP340 to the hash module, which
    is used in BIP340 and BIP341 to produce domain-separated hashes.
    9eb590894f
  107. sipa force-pushed on Oct 12, 2020
  108. sipa commented at 9:21 am on October 12, 2020: member
    Addressed comments by @fjahr and @kallewoof above. Details are in #19997.
  109. kallewoof commented at 9:30 am on October 12, 2020: member

    Re-ACK sans the fuccess typo.

      0diff --git a/src/pubkey.cpp b/src/pubkey.cpp
      1index 689328765..4d734fc89 100644
      2--- a/src/pubkey.cpp
      3+++ b/src/pubkey.cpp
      4@@ -173,7 +173,8 @@ XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
      5     std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
      6 }
      7 
      8-bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const {
      9+bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
     10+{
     11     assert(sigbytes.size() == 64);
     12     secp256k1_xonly_pubkey pubkey;
     13     if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data())) return false;
     14diff --git a/src/pubkey.h b/src/pubkey.h
     15index 65f3fdf38..0f784b86e 100644
     16--- a/src/pubkey.h
     17+++ b/src/pubkey.h
     18@@ -207,7 +207,8 @@ public:
     19     bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
     20 };
     21 
     22-class XOnlyPubKey {
     23+class XOnlyPubKey
     24+{
     25 private:
     26     uint256 m_keydata;
     27 
     28diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
     29index 92994a56e..1067a5293 100644
     30--- a/src/script/interpreter.cpp
     31+++ b/src/script/interpreter.cpp
     32@@ -392,13 +392,13 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr
     33         return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
     34     } else if (pubkey.size() == 32) {
     35         if (success && !checker.CheckSchnorrSignature(sig, pubkey, sigversion, execdata, serror)) {
     36-            return false;
     37+            return false; // serror is set
     38         }
     39     } else {
     40         /*
     41          *  New public key version softforks should be defined before this `else` block.
     42          *  Generally, the new code should not do anything but failing the script execution. To avoid
     43-         *  consensus bugs, it should not modify any existing values (including success).
     44+         *  consensus bugs, it should not modify any existing values (including `success`).
     45          */
     46         if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) {
     47             return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
     48@@ -411,7 +411,7 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr
     49 /** Helper for OP_CHECKSIG, OP_CHECKSIGVERIFY, and (in Tapscript) OP_CHECKSIGADD.
     50  *
     51  * A return value of false means the script fails entirely. When true is returned, the
     52- * fSuccess variable indicates whether the signature check itself succeeded.
     53+ * fuccess variable indicates whether the signature check itself succeeded.
     54  */
     55 static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
     56 {
     57@@ -1814,9 +1814,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
     58         }
     59 
     60         // Tapscript enforces initial stack size limits (altstack is empty here)
     61-        if (stack.size() > MAX_STACK_SIZE) {
     62-            return set_error(serror, SCRIPT_ERR_STACK_SIZE);
     63-        }
     64+        if (stack.size() > MAX_STACK_SIZE) return set_error(serror, SCRIPT_ERR_STACK_SIZE);
     65     }
     66 
     67     // Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack
     68@@ -1900,7 +1898,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
     69         if (stack.size() == 1) {
     70             // Key path spending (stack size is 1 after removing optional annex)
     71             if (!checker.CheckSchnorrSignature(stack.front(), program, SigVersion::TAPROOT, execdata, serror)) {
     72-                return false;
     73+                return false; // serror is set
     74             }
     75             return set_success(serror);
     76         } else {
     77diff --git a/src/validation.cpp b/src/validation.cpp
     78index fdabff9bb..0b78ba677 100644
     79--- a/src/validation.cpp
     80+++ b/src/validation.cpp
     81@@ -1550,6 +1550,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState &state, const C
     82         }
     83         txdata.Init(tx, std::move(spent_outputs));
     84     }
     85+    assert(txdata.m_spent_outputs.size() == tx.vin.size());
     86 
     87     for (unsigned int i = 0; i < tx.vin.size(); i++) {
     88 
     89diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
     90index 82ccdae06..7b534c1c2 100755
     91--- a/test/functional/feature_taproot.py
     92+++ b/test/functional/feature_taproot.py
     93@@ -100,7 +100,7 @@ import random
     94 # - lists of values
     95 # - callables which, when fed the context object as argument, produce any of these
     96 #
     97-# # The DEFAULT_CONTEXT object specifies a standard signing process, with many overridable knobs.
     98+# The DEFAULT_CONTEXT object specifies a standard signing process, with many overridable knobs.
     99 #
    100 # The get(ctx, name) function can evaluate a name, and cache its result in the context.
    101 # getter(name) can be used to construct a callable that evaluates name. For example:
    102@@ -1070,7 +1070,7 @@ def spenders_taproot_active():
    103         random.shuffle(scripts)
    104         tap = taproot_construct(pubs[0], scripts)
    105         add_spender(spenders, "opsuccess/bare", standard=False, tap=tap, leaf="bare_success", failure={"leaf": "bare_nop"}, **ERR_CLEANSTACK)
    106-        add_spender(spenders, "opsuccess/exexecif", standard=False, tap=tap, leaf="unexecif_success", failure={"leaf": "unexecif_nop"}, **ERR_CLEANSTACK)
    107+        add_spender(spenders, "opsuccess/unexecif", standard=False, tap=tap, leaf="unexecif_success", failure={"leaf": "unexecif_nop"}, **ERR_CLEANSTACK)
    108         add_spender(spenders, "opsuccess/return", standard=False, tap=tap, leaf="return_success", failure={"leaf": "return_nop"}, **ERR_OP_RETURN)
    109         add_spender(spenders, "opsuccess/undecodable", standard=False, tap=tap, leaf="undecodable_success", failure={"leaf": "undecodable_nop"}, **ERR_UNDECODABLE)
    110         add_spender(spenders, "opsuccess/undecodable_bypass", standard=False, tap=tap, leaf="undecodable_success", failure={"leaf": "undecodable_bypassed_success"}, **ERR_UNDECODABLE)
    111@@ -1245,9 +1245,6 @@ class TaprootTest(BitcoinTestFramework):
    112         of all valid inputs, except one invalid one.
    113         """
    114 
    115-        # Generate some coins to fund the wallet.
    116-        node.generate(10)
    117-
    118         # Construct a bunch of sPKs that send coins back to the host wallet
    119         self.log.info("- Constructing addresses for returning coins")
    120         host_spks = []
    121@@ -1284,7 +1281,7 @@ class TaprootTest(BitcoinTestFramework):
    122             # Add the 50 highest-value inputs
    123             unspents = node.listunspent()
    124             random.shuffle(unspents)
    125-            unspents.sort(key=lambda x: -int(x["amount"] * 100000000))
    126+            unspents.sort(key=lambda x: int(x["amount"] * 100000000), reverse=True)
    127             if len(unspents) > 50:
    128                 unspents = unspents[:50]
    129             random.shuffle(unspents)
    
  110. in src/script/interpreter.cpp:414 in cc036c251c outdated
    411+}
    412+
    413+/** Helper for OP_CHECKSIG, OP_CHECKSIGVERIFY, and (in Tapscript) OP_CHECKSIGADD.
    414+ *
    415+ * A return value of false means the script fails entirely. When true is returned, the
    416+ * fuccess variable indicates whether the signature check itself succeeded.
    


    instagibbs commented at 11:41 am on October 12, 2020:
    0 * success variable indicates whether the signature check itself succeeded.
    

    sipa commented at 0:21 am on October 13, 2020:
    Done.
  111. instagibbs approved
  112. benthecarman commented at 2:36 pm on October 12, 2020: contributor
    reACK cc036c2
  113. in src/script/interpreter.cpp:1384 in 614479450e outdated
    1377@@ -1322,6 +1378,75 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo,
    1378 template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
    1379 template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
    1380 
    1381+static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
    1382+
    1383+template<typename T>
    1384+bool SignatureHashSchnorr(uint256& hash_out, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, uint8_t key_version, const PrecomputedTransactionData& cache)
    


    achow101 commented at 6:57 pm on October 12, 2020:

    In 614479450e911dba6938bb7feb85088229faf9b1 “Implement Taproot signature hashing (BIP 341)”

    nit: remove key_version here. It is removed in the next commit anyways.


    sipa commented at 0:21 am on October 13, 2020:
    Fixed.
  114. in src/script/script.h:50 in 4e6196e531 outdated
    46@@ -47,7 +47,7 @@ static const uint32_t LOCKTIME_MAX = 0xFFFFFFFFU;
    47 // Tag for input annex. If there are at least two witness elements for a transaction input,
    48 // and the first byte of the last element is 0x50, this last element is called annex, and
    49 // has meanings independent of the script
    50-static const unsigned int ANNEX_TAG = 0x50;
    51+static constexpr unsigned int ANNEX_TAG = 0x50;
    


    achow101 commented at 7:39 pm on October 12, 2020:

    In 4e6196e53109ab4ebcfe64a88be6fcf2e3d7e2a6 “Implement Taproot validation (BIP 341)”

    nit: move this change to the commit ANNEX_TAG was introduced.


    sipa commented at 0:21 am on October 13, 2020:
    Fixed.
  115. achow101 commented at 8:54 pm on October 12, 2020: member

    light ACK cc036c251c16eda1cdc558db2049e8db0f3a50b7

    Skimmed the tests, will review them in further detail soon.

  116. Implement Taproot signature hashing (BIP 341)
    This implements the new sighashing scheme from BIP341, with all relevant
    whole-transaction values precomputed once and cached.
    
    Includes changes to PrecomputedTransactionData by Pieter Wuille.
    5de246ca81
  117. Support for Schnorr signatures and integration in SignatureCheckers (BIP 340)
    This enables the schnorrsig module in libsecp256k1, adds the relevant types
    and functions to src/pubkey, as well as in higher-level `SignatureChecker`
    classes. The (verification side of the) BIP340 test vectors is also added.
    0664f5fe1f
  118. fjahr commented at 0:17 am on October 13, 2020: member

    Code review ACK cc036c251c16eda1cdc558db2049e8db0f3a50b7

    Hope I can get enough testing in soon to get to a full ACK.

  119. Implement Taproot validation (BIP 341)
    This includes key path spending and script path spending, but not the
    Tapscript execution implementation (leaf 0xc0 remains unemcumbered in
    this commit).
    
    Includes constants for various aspects of the consensus rules suggested
    by Jeremy Rubin.
    8bbed4b7ac
  120. Use ScriptExecutionData to pass through annex hash
    Instead of recomputing the annex hash every time a signature is verified, compute it
    once and cache it in a new ScriptExecutionData structure.
    330de894a9
  121. Implement Tapscript script validation rules (BIP 342)
    This adds a new `SigVersion::TAPSCRIPT`, makes the necessary interpreter
    changes to make it implement BIP342, and uses them for leaf version 0xc0
    in Taproot script path spends.
    72422ce396
  122. --- [TAPROOT] Regtest activation and policy --- 865d2c37e2
  123. Make Taproot spends standard + policy limits
    This adds a `TxoutType::WITNESS_V1_TAPROOT` for P2TR outputs, and permits spending
    them in standardness rules. No corresponding `CTxDestination` is added for it,
    as that isn't needed until we want wallet integration. The taproot validation flags
    are also enabled for mempool transactions, and standardness rules are added
    (stack item size limit, no annexes).
    e9a021d7e6
  124. Activate Taproot/Tapscript on regtest (BIP 341, BIP 342)
    Define a versionbits-based activation for the new consensus rules on regtest.
    No activation or activation mechanism is defined for testnet or mainnet.
    d7ff237f29
  125. --- [TAPROOT] Tests --- 206fb180ec
  126. tests: add BIP340 Schnorr signature support to test framework
    Add a pure Python implementation of BIP340 signing and verification, tested against
    the BIP's test vectors.
    3c226639eb
  127. tests: functional tests for Schnorr/Taproot/Tapscript
    A large functional test is added that automatically generates random transactions which
    exercise various aspects of the new rules, and verifies they are accepted into the mempool
    (when appropriate), and correctly accepted/rejected in (Python-constructed) blocks.
    
    Includes sighashing code and many tests by Johnson Lau.
    Includes a test by Matthew Zipkin.
    Includes several tests and improvements by Greg Sanders.
    f06e6d0345
  128. tests: add generic qa-asset-based script verification unit test
    This adds a unit test that does generic script verification tests,
    with positive/negative witnesses/scriptsigs, under various flags.
    The test data is large (several MB) so it's stored in the qa-assets
    repo.
    4567ba034c
  129. tests: dumping and minimizing of script assets data
    This adds a --dumptests flag to the feature_taproot.py test, to dump all its
    generated test cases to files, in a format compatible with the
    script_assets_test unit test. A fuzzer for said format is added as well, whose
    primary purpose is coverage-based minimization of those dumps.
    0e2a5e448f
  130. sipa force-pushed on Oct 13, 2020
  131. sipa commented at 0:25 am on October 13, 2020: member
    I addressed comments by @jnewbery, @instagibbs, and @achow101 above. The changes to the final tree are listed in #19997.
  132. benthecarman commented at 1:26 am on October 13, 2020: contributor
    reACK 0e2a5e4
  133. kallewoof commented at 2:14 am on October 13, 2020: member
    reACK 0e2a5e448f426219a6464b9aaadcc715534114e6
  134. jonasnick commented at 7:06 am on October 13, 2020: contributor
    ACK 0e2a5e448f426219a6464b9aaadcc715534114e6 almost only looked at bip340/libsecp related code
  135. jonatack commented at 4:35 pm on October 13, 2020: member
    ACK 0e2a5e448f426219a6464b9aaadcc715534114e6 modulo the last four commits (tests) that I plan to finish reviewing tomorrow
  136. in test/functional/test_framework/key.py:246 in 3c226639eb outdated
    241@@ -223,7 +242,7 @@ def set(self, data):
    242                 p = SECP256K1.lift_x(x)
    243                 # if the oddness of the y co-ord isn't correct, find the other
    244                 # valid y
    245-                if (p[1] & 1) != (data[0] & 1):
    246+                if data[0] & 1:
    247                     p = SECP256K1.negate(p)
    


    achow101 commented at 7:57 pm on October 13, 2020:

    In 3c226639eb134314a0640d34e4ccb6148dbde22f “tests: add BIP340 Schnorr signature support to test framework”

    nit: The code here seems to be entirely unnecessary as lift_x ensures the evenness of y is correct. I commented out these 2 lines and no tests failed.


    sipa commented at 11:02 pm on October 13, 2020:

    I think the only thing that’s wrong here is the comment: with this change, it’s not longer “correcting” the oddness; it’s just negating if an odd Y coordinate is desired.

    The code is necessary though, but possibly untested. It’s what constructs a point from a compressed public key. It’s only used for ECDSA (as BIP340 public keys are x-only, not compressed), and unused in the current tests (which only use the signing side of ECDSA).

    Going to leave this for a follow-up, as it’s not directly related to Taproot testing.


    fanquake commented at 6:30 am on October 16, 2020:
    This comment is also being addressed in #20161.
  137. in test/functional/test_framework/key.py:545 in 3c226639eb outdated
    540+                    aux_rand = bytes.fromhex(aux_rand_hex)
    541+                    try:
    542+                        sig_actual = sign_schnorr(seckey, msg, aux_rand)
    543+                        self.assertEqual(sig.hex(), sig_actual.hex(), "BIP340 test vector %i (%s): sig mismatch" % (i, comment))
    544+                    except RuntimeError as e:
    545+                        self.assertFalse("BIP340 test vector %i (%s): signing raised exception %s" % (i, comment, e))
    


    achow101 commented at 8:31 pm on October 13, 2020:

    In 3c226639eb134314a0640d34e4ccb6148dbde22f “tests: add BIP340 Schnorr signature support to test framework”

    nit: This should be self.fail rather than assertFalse.


    sipa commented at 11:27 pm on October 13, 2020:
    Indeed, will fix in a follow-up.

    fanquake commented at 3:33 am on October 16, 2020:
    This is being addressed in #20161.
  138. achow101 commented at 9:43 pm on October 13, 2020: member
    ACK 0e2a5e448f426219a6464b9aaadcc715534114e6
  139. fjahr commented at 10:28 pm on October 13, 2020: member
    reACK 0e2a5e448f426219a6464b9aaadcc715534114e6
  140. in src/validation.cpp:1548 in 5d62e3a68b outdated
    1544+        spent_outputs.reserve(tx.vin.size());
    1545+
    1546+        for (const auto& txin : tx.vin) {
    1547+            const COutPoint& prevout = txin.prevout;
    1548+            const Coin& coin = inputs.AccessCoin(prevout);
    1549+            assert(!coin.IsSpent());
    


    jamesob commented at 3:11 pm on October 14, 2020:

    Note to other reviewers: even though this is a move from existing code, I was still curious about whether this assert is safe. There are three call-sites for CheckInputScripts(); here they are with the various ways they ensure the input coins aren’t already spent (and so this assert won’t blow up):

    • MemPoolAccept::PolicyScriptChecks(): only ever called from MemPoolAccept::AcceptSingleTransaction(), which makes a call to Consensus::CheckTxInputs() via MemPoolAccept::PreChecks() beforehand,
    • CheckInputsFromMempoolAndCache(): only ever called from MemPoolAccept::ConsensusScriptChecks(), which is only ever called from AcceptSingleTransaction() (case above),
    • CChainState::ConnectBlock(): call to Consensus::CheckTxInputs() beforehand
  141. in src/script/interpreter.cpp:1428 in 5de246ca81 outdated
    1423+    const auto& witstack = tx_to.vin[in_pos].scriptWitness.stack;
    1424+    bool have_annex = witstack.size() > 1 && witstack.back().size() > 0 && witstack.back()[0] == ANNEX_TAG;
    1425+    const uint8_t spend_type = (ext_flag << 1) + (have_annex ? 1 : 0); // The low bit indicates whether an annex is present.
    1426+    ss << spend_type;
    1427+    if (input_type == SIGHASH_ANYONECANPAY) {
    1428+        ss << tx_to.vin[in_pos].prevout;
    


    jamesob commented at 5:02 pm on October 14, 2020:
    Note to reviewers: serializes as [hash][out_index] per COutPoint (c.f. BIP).
  142. in src/script/interpreter.cpp:1429 in 5de246ca81 outdated
    1424+    bool have_annex = witstack.size() > 1 && witstack.back().size() > 0 && witstack.back()[0] == ANNEX_TAG;
    1425+    const uint8_t spend_type = (ext_flag << 1) + (have_annex ? 1 : 0); // The low bit indicates whether an annex is present.
    1426+    ss << spend_type;
    1427+    if (input_type == SIGHASH_ANYONECANPAY) {
    1428+        ss << tx_to.vin[in_pos].prevout;
    1429+        ss << cache.m_spent_outputs[in_pos];
    


    jamesob commented at 5:03 pm on October 14, 2020:
    Note to reviewers: serializes as [amount i.e. nValue][scriptPubKey] per CTxOut (c.f. BIP).
  143. in src/script/interpreter.cpp:1435 in 5de246ca81 outdated
    1430+        ss << tx_to.vin[in_pos].nSequence;
    1431+    } else {
    1432+        ss << in_pos;
    1433+    }
    1434+    if (have_annex) {
    1435+        ss << (CHashWriter(SER_GETHASH, 0) << witstack.back()).GetSHA256();
    


    jamesob commented at 5:12 pm on October 14, 2020:
    So far as I can tell, the ordering of serialization is reversed here relative to what the BIP says (“the SHA256 of (compact_size(size of annex) || annex)”). Existing serialization looks like it has the size coming after the annex itself. Even if I’m right, not sure how much this matters.

    sipa commented at 5:30 pm on October 14, 2020:
    I don’t understand. Why would the length go after the data? Nothing serializes that way, and the functional tests would fail if that was the case.

    jamesob commented at 5:46 pm on October 14, 2020:
    Based on the serialization code I link to, it looks as though the size is being written after the data itself (which is the reverse of what the BIP says), but maybe I’m missing something?

    sipa commented at 5:53 pm on October 14, 2020:

    That serialization code you link to:

    • Is serializing a Span<const unsigned char> as a fixed length object, without any length at all. It’s invoking the Stream function write(const unsigned char* data, size_t length), which writes the length bytes starting at data. It doesn’t write the length itself.
    • Isn’t used here (witstack.back() is a std::vector<unsigned char>, not a Span<const unsigned char>), which has different serialization code a bit further down the file.

    jamesob commented at 5:55 pm on October 14, 2020:

    Hm yeah I guess I must be wrong because the tests encode in the right order (per ser_string’s definition). Maybe I’m looking at the wrong serializer.

    Edit: yup, my mistake!

  144. jamesob commented at 5:26 pm on October 14, 2020: member

    Partial review; more soon.

    • f8c099e220 — [TAPROOT] Refactors —
    • 107b57df9f scripted-diff: put ECDSA in name of signature functions
    • 8bd2b4e784 refactor: rename scriptPubKey in VerifyWitnessProgram to exec_script
    • 5d62e3a68b refactor: keep spent outputs in PrecomputedTransactionData
    • 450d2b2371 — [TAPROOT] BIP340/341/342 consensus rules —
    • 9eb590894f Add TaggedHash function (BIP 340)
    • 5de246ca81 Implement Taproot signature hashing (BIP 341)
  145. in src/script/interpreter.cpp:1685 in 8bbed4b7ac outdated
    1681@@ -1679,14 +1682,35 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
    1682     return true;
    1683 }
    1684 
    1685-static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
    1686+static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script)
    


    ajtowns commented at 7:01 am on October 15, 2020:
    Would have expected VerifyTaprootCommitment to take script as a const valtype& – future taproot versions might not look like current script at all.

    sipa commented at 9:56 pm on October 15, 2020:
    Without #13062 that’s annoying to do, as it means constructing both a valtype and a CScript with the same data. You’re right that future version leafs may not want a script at all, but until then, little reason to add this complication.
  146. in src/script/interpreter.cpp:1708 in 8bbed4b7ac outdated
    1705+    return q.CheckPayToContract(p, k, control[0] & 1);
    1706+}
    1707+
    1708+static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh)
    1709+{
    1710+    CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH; leaf script in P2TR)
    


    ajtowns commented at 7:05 am on October 15, 2020:
    Could move declaration of exec_script closer to its assignment, and make it const CScript exec_script(script_bytes.begin(), script_bytes.end()); for the p2wsh and p2tr cases.

    sipa commented at 9:58 pm on October 15, 2020:
    Yeah, not sure that’s worth changing without other improvements though.
  147. laanwj merged this on Oct 15, 2020
  148. laanwj closed this on Oct 15, 2020

  149. ajtowns commented at 8:58 am on October 15, 2020: member
    ACK 0e2a5e448f426219a6464b9aaadcc715534114e6 - code review, just nits. However signet activation params are missing (should be disabled as per mainnet/testnet)
  150. sidhujag referenced this in commit ca9eeb33df on Oct 16, 2020
  151. hebasto commented at 6:03 pm on October 18, 2020: member
    This regression introduced in 4567ba034c5ae6e6cc161360f7425c9e844738f0 is fixed in #20180.
  152. in test/functional/test_framework/script.py:841 in 0e2a5e448f
    836+             - a (name, CScript, leaf version) tuple
    837+             - another list of items (with the same structure)
    838+             - a function, which specifies how to compute the hashing partner
    839+               in function of the hash of whatever it is combined with
    840+
    841+    Returns: script (sPK or redeemScript), tweak, {name:(script, leaf version, negation flag, innerkey, merklepath), ...}
    


    ariard commented at 11:54 pm on October 19, 2020:
    I’m not sure how to read this description compared to the effective return. tweak is returned in 4th position, after internal pubkey in 2nd and the negation flag in 3rd. Further it seems leaves are sorted on (script, version, merklebranch) and doesn’t rely on negation flag/ innerkey.

    sipa commented at 10:36 pm on October 20, 2020:
    See #20207. All of this was just outdated, thanks for noticing.
  153. in test/functional/feature_taproot.py:445 in 0e2a5e448f
    440+    * p2sh: whether the output is P2SH wrapper (this is supported even for Taproot, where it makes the output unencumbered)
    441+    * spk_mutate_pre_psh: a callable to be applied to the script (before potentially P2SH-wrapping it)
    442+    * failure: a dict of entries to override in the context when intentionally failing to spend (if None, no_fail will be set)
    443+    * standard: whether the (valid version of) spending is expected to be standard
    444+    * err_msg: a string with an expected error message for failure (or None, if not cared about)
    445+    * sigops_weight: the pre-taproot sigops weight consumed by a successful spend
    


    ariard commented at 0:07 am on October 20, 2020:
    nit: need_vin_vout_mismatch isn’t commented

    sipa commented at 10:36 pm on October 20, 2020:
    Added in #20207.
  154. in src/script/interpreter.cpp:1837 in 0e2a5e448f
    1836-static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
    1837+static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash)
    1838 {
    1839-    CScript scriptPubKey;
    1840+    const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
    1841+    const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
    


    ariard commented at 1:10 am on October 20, 2020:
    A code comment to hint about the +1 would be great. Like “Exclude parity bit from internal pubkey”

    sipa commented at 10:37 pm on October 20, 2020:
    I added a bunch of comments around this in #20207. The first byte contains both the leaf version and the parity bit, btw.
  155. in src/pubkey.h:173 in 0e2a5e448f
    169@@ -169,7 +170,7 @@ class CPubKey
    170     /*
    171      * Check syntactic correctness.
    172      *
    173-     * Note that this is consensus critical as CheckSig() calls it!
    174+     * Note that this is consensus critical as CheckECDSASignature() calls it!
    


    ariard commented at 1:21 am on October 20, 2020:
    As a side-note, it could be worthy to document what is meaned here by “syntactic correctness” if it’s consensus criticial. AFAICT, pubkey must be superior to 0 ?

    sipa commented at 10:37 pm on October 20, 2020:
    Added comments in #20207.

    kiminuo commented at 2:14 pm on June 24, 2021:
  156. in src/script/interpreter.cpp:384 in 0e2a5e448f
    381+     *    the script execution fails when using non-empty invalid signature.
    382+     */
    383+    success = !sig.empty();
    384+    if (success) {
    385+        // Implement the sigops/witnesssize ratio test.
    386+        // Passing with an upgradable public key version is also counted.
    


    ariard commented at 1:23 am on October 20, 2020:
    Can future upgradable public key define their own sigops rules without branching inside the if (success) branch ?

    sipa commented at 10:28 pm on October 20, 2020:
    They can certainly define their own cost rules, as long as the cost is at least 50 vbytes per check. I’m not sure what you mean by “without branching”.

    ariard commented at 1:24 am on October 21, 2020:
    I meaned that we do the sigops/witnesssize ratio test before the pubkey size one. If a future softforked new pubkey type comes with its own new ratio test, maybe the code structure isn’t going to be adequate ?
  157. in src/script/interpreter.cpp:401 in 0e2a5e448f
    398+        }
    399+    } else {
    400+        /*
    401+         *  New public key version softforks should be defined before this `else` block.
    402+         *  Generally, the new code should not do anything but failing the script execution. To avoid
    403+         *  consensus bugs, it should not modify any existing values (including `success`).
    


    ariard commented at 1:24 am on October 20, 2020:
    Can we enforce this assign-once property with either some cpp magic or compiler option ? I’ve no idea.

    sipa commented at 10:32 pm on October 20, 2020:
    I’m sure there are ways to solve these softforkability guarantees more generically by encapsulating modifiable properties in an object… but the risks from refactoring consensus code to allow that are probably far bigger than the risk a bug would be missed in future consensus logic (probably a very rare event).
  158. ariard commented at 1:31 am on October 20, 2020: member

    Code Review ACK 0e2a5e4. Mainly reviewed change since my last review in #17977 at 84ec870.

    One thing I’m still inquiring is scope of test coverage. I’ve started to look at it only now and it’s still trying to map if every spec object is correctly covered. I’ll try if I can come with any comment improvement suggestion.

  159. fanquake removed this from the "Blockers" column in a project

  160. in src/policy/policy.cpp:209 in e9a021d7e6 outdated
    205@@ -206,6 +206,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
    206         // get the scriptPubKey corresponding to this input:
    207         CScript prevScript = prev.scriptPubKey;
    208 
    209+        bool p2sh = false;
    


    MarcoFalke commented at 9:49 am on October 21, 2020:

    nit in commit e9a021d7e6:

    Why not directly assign this with the correct value?

    0const bool p2sh{prevScript.IsPayToScriptHash()};
    

    sipa commented at 8:02 pm on October 21, 2020:
    Personal style, I guess. I don’t think there is much of an objective difference.

    MarcoFalke commented at 8:27 pm on October 21, 2020:
    Overall less verbose and one less LOC, but obviously a style question. Resolving.
  161. in src/policy/policy.cpp:243 in e9a021d7e6 outdated
    238@@ -237,6 +239,36 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
    239                     return false;
    240             }
    241         }
    242+
    243+        // Check policy limits for Taproot spends:
    


    MarcoFalke commented at 9:59 am on October 21, 2020:

    same commit:

    You are modifying what this function is doing, so the comment in validation is no longer applicable and should probably be adjusted.

    0    // Check for non-standard witness in P2WSH
    

    sipa commented at 8:23 pm on October 21, 2020:
    See follow-up in #20207.

    MarcoFalke commented at 8:28 pm on October 21, 2020:
    Which commit? I can’t seem to find the one that touches validation.cpp

    sipa commented at 8:45 pm on October 21, 2020:
    Oops, done for real now.

    MarcoFalke commented at 8:46 pm on October 21, 2020:
    heh, thanks!
  162. in test/functional/test_framework/key.py:526 in 3c226639eb outdated
    521+                    self.assertFalse(verify_schnorr(verify_pubkey, sig, msg))
    522+
    523+    def test_schnorr_testvectors(self):
    524+        """Implement the BIP340 test vectors (read from bip340_test_vectors.csv)."""
    525+        num_tests = 0
    526+        with open(os.path.join(sys.path[0], 'test_framework', 'bip340_test_vectors.csv'), newline='', encoding='utf8') as csvfile:
    


    MarcoFalke commented at 3:23 pm on October 21, 2020:

    I can’t run the tests here:

     0test]$ python -m unittest functional/test_framework/key.py 
     1.E
     2======================================================================
     3ERROR: test_schnorr_testvectors (functional.test_framework.key.TestFrameworkKey)
     4Implement the BIP340 test vectors (read from bip340_test_vectors.csv).
     5----------------------------------------------------------------------
     6Traceback (most recent call last):
     7  File "test/functional/test_framework/key.py", line 526, in test_schnorr_testvectors
     8    with open(os.path.join(sys.path[0], 'test_framework', 'bip340_test_vectors.csv'), newline='', encoding='utf8') as csvfile:
     9FileNotFoundError: [Errno 2] No such file or directory: 'test/test_framework/bip340_test_vectors.csv'
    10
    11----------------------------------------------------------------------
    12Ran 2 tests in 0.775s
    13
    14FAILED (errors=1)
    

    What about this diff:

     0diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
     1index a6bc187985..ceaaa474ff 100644
     2--- a/test/functional/test_framework/key.py
     3+++ b/test/functional/test_framework/key.py
     4@@ -523,7 +523,7 @@ class TestFrameworkKey(unittest.TestCase):
     5     def test_schnorr_testvectors(self):
     6         """Implement the BIP340 test vectors (read from bip340_test_vectors.csv)."""
     7         num_tests = 0
     8-        with open(os.path.join(sys.path[0], 'test_framework', 'bip340_test_vectors.csv'), newline='', encoding='utf8') as csvfile:
     9+        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'bip340_test_vectors.csv'), newline='', encoding='utf8') as csvfile:
    10             reader = csv.reader(csvfile)
    11             next(reader)
    12             for row in reader:
    

    sipa commented at 8:23 pm on October 21, 2020:
    Ok, I wasn’t aware of that mode being supported. Added in #20207.
  163. in test/functional/test_framework/key.py:25 in 3c226639eb outdated
    21+    ss += ss
    22+    ss += data
    23+    return hashlib.sha256(ss).digest()
    24+
    25+def xor_bytes(b0, b1):
    26+    return bytes(x ^ y for (x, y) in zip(b0, b1))
    


    MarcoFalke commented at 3:25 pm on October 21, 2020:
    should this assert that they are of the same length? Or at least comment that this will truncate the longer one

    sipa commented at 8:23 pm on October 21, 2020:
    Done.
  164. in test/functional/feature_taproot.py:1157 in f06e6d0345 outdated
    1152+
    1153+    def set_test_params(self):
    1154+        self.num_nodes = 2
    1155+        self.setup_clean_chain = True
    1156+        # Node 0 has Taproot inactive, Node 1 active.
    1157+        self.extra_args = [["-whitelist=127.0.0.1", "-par=1", "-vbparams=taproot:1:1"], ["-whitelist=127.0.0.1", "-par=1"]]
    


    MarcoFalke commented at 3:46 pm on October 21, 2020:
    Can you explain why legacy whitelist is needed for this test?

    sipa commented at 8:24 pm on October 21, 2020:
    I’m not sure, it may have been needed in an old version of this code. It doesn’t seem needed anymore; addressed in #20207.

    MarcoFalke commented at 8:34 pm on October 21, 2020:
    One side-effect is that it speeds up tx-relay, but tx relay isn’t tested in this script, so seems good to remove
  165. in test/functional/feature_taproot.py:1392 in f06e6d0345 outdated
    1387+        assert len(normal_utxos) == 0
    1388+        assert len(mismatching_utxos) == 0
    1389+        self.log.info("  - Done")
    1390+
    1391+    def run_test(self):
    1392+        self.connect_nodes(0, 1)
    


    MarcoFalke commented at 3:47 pm on October 21, 2020:
    The nodes are already connected. Can you explain why the nodes need to be connected twice?

    sipa commented at 8:25 pm on October 21, 2020:

    I probably copied it from somewhere ¯\(ツ)

    Fixed in #20207.

  166. in src/Makefile.test.include:1089 in 0e2a5e448f
    1082@@ -1082,6 +1083,12 @@ test_fuzz_script_interpreter_LDADD = $(FUZZ_SUITE_LD_COMMON)
    1083 test_fuzz_script_interpreter_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON)
    1084 test_fuzz_script_interpreter_SOURCES = test/fuzz/script_interpreter.cpp
    1085 
    1086+test_fuzz_script_assets_test_minimizer_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
    1087+test_fuzz_script_assets_test_minimizer_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
    1088+test_fuzz_script_assets_test_minimizer_LDADD = $(FUZZ_SUITE_LD_COMMON)
    1089+test_fuzz_script_assets_test_minimizer_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
    


    MarcoFalke commented at 4:13 pm on October 21, 2020:
    nit: FUZZ_SUITE_LDFLAGS_COMMON

    sipa commented at 8:26 pm on October 21, 2020:
    Done, in #20207.
  167. MarcoFalke commented at 4:20 pm on October 21, 2020: member

    Concept ACK 0e2a5e448f

    left some questions in the test commits

  168. in src/test/script_tests.cpp:1694 in 4567ba034c outdated
    1689+        for (const auto flags : ALL_CONSENSUS_FLAGS) {
    1690+            // "final": true tests are valid for all flags. Others are only valid with flags that are
    1691+            // a subset of test_flags.
    1692+            if (fin || ((flags & test_flags) == flags)) {
    1693+                bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
    1694+                BOOST_CHECK(ret);
    


    MarcoFalke commented at 7:40 pm on October 21, 2020:

    Can you explain what the point of this unit test is? It seems that the python test is used to generate inputs for the fuzz test, which is used to minimize the corpus. The resulting corpus is fed into the unit test.

    However, the unit test and fuzz test do the same thing. If they didn’t, the fuzz test couldn’t be used to capture the coverage.

    I think the unit test (all changes in this commit) can be reverted if the fuzz asserts the ret return code directly.


    sipa commented at 8:18 pm on October 21, 2020:

    If the fuzz tests would assert the return code directly, they wouldn’t be usable in fuzzing mode, and likely cause people to file bug reports if they’d try to run them. As is, the fuzz tests are just for assessing coverage, and can be used either as test directly (only covering things caught by sanitizers), or used to minimize a corpus generated externally (with known validity/invalidity). Only the latter can be used as a unit test.

    So I think the differences are:

    • The unit test actually tests the script validation results against known valid/invalid things.
    • The unit test can be run without building in fuzz mode.

    MarcoFalke commented at 8:40 pm on October 21, 2020:

    with known validity/invalidity

    Oh, I missed that the validity flag is included in the seed itself. Makes sense now.

    The unit test can be run without building in fuzz mode.

    Heh, I am pretty sure the developers that have the seed dir cloned, run the unit tests, but not the fuzz tests is an empty set.

  169. MarcoFalke referenced this in commit f17e8ba3a1 on Dec 1, 2020
  170. sidhujag referenced this in commit 6f21c27635 on Dec 1, 2020
  171. MarcoFalke referenced this in commit 8d6994f93d on Feb 15, 2021
  172. sidhujag referenced this in commit a8d91fa832 on Feb 15, 2021
  173. in test/functional/test_framework/script.py:780 in 0e2a5e448f
    775+        ss += sha256(ser_string(annex))
    776+    if out_type == SIGHASH_SINGLE:
    777+        if input_index < len(txTo.vout):
    778+            ss += sha256(txTo.vout[input_index].serialize())
    779+        else:
    780+            ss += bytes(0 for _ in range(32))
    


    dgpv commented at 3:34 pm on August 31, 2021:

    Post-merge comment/question after stumbling upon this while looking at the test framework code:

    Is using 32 zero bytes instead of correct BIP341 behaviour (i.e. failing) intentional here ?

    The code does not seem accidental, as it is not copied from legacy sighash function, and if I stick assert 0 here, the test/functional/feature_taproot.py test fails, but it is not clear from the backtrace where exactly, a lot of deep_eval() in that backtrace.

    If this is intentional, I think that a comment with explanation was in order here, to avoid confusion for the readers of the code.

    If this was not intentional, then even if does not cause problems now, it might result in incorrect test behaviour in the future


    dgpv commented at 6:57 pm on August 31, 2021:
    @sipa, can you please comment on this ?

    ajtowns commented at 1:19 am on September 1, 2021:

    I think it’s testing that a SIGHASH_SINGLE signature without a corresponding output will fail, even if you generate an otherwise reasonable looking signature. If you change it to bytes(42 for _ in range(32)) rather than an assert 0, the tests will still pass, demonstrating the exact value used isn’t important. (If you change the length to something other than 32, you’ll get an assertion failure slightly later when the length of hashed data is tested)

    See the need_vin_vout_mismatch argument to make_spender(), which is set to True in the “Test SIGHASH_SINGLE behavior in combination with mismatching outputs” section. If you disable those two spenders, then adding the assert 0 when there isn’t a corresponding output works fine.


    dgpv commented at 6:35 am on September 1, 2021:

    It is very much looks like you are correct, these two tests depend on this behavior of TaprootSignatureHash.

    Without any comment explaining this, It will confuse people who read only a part of the code. Some people might copy that code and have problems later. I only cross-referenced this code with C++ implementation in Core while writing my own implementation in python, but I think it is definitely a possibility that someone may just copy the whole TaprootSignatureHash, and maybe even without looking at the comments. Other possibility might be that TaprootSignatureHash is reused for some another purpose inside the test framework without taking this behavior into account.

    Might it be better to just do add_spender( ... , failure={"hashtype_actual": hashtype, "sighash": lambda(ctx): b"\x00"*32}, ... ) for these two tests ? It is not that TaprootSignatureHash itself is tested here, AFAIU, and expected behavior is incorrect hash returned, so why not just return it right away ? Alternative might be a special flag in the context “invalid sighash expected instead of failure”. Or at least a comment with explanation inside TaprootSignatureHash, to spare readers from confusion.

  174. kittywhiskers referenced this in commit 94ef082925 on Feb 28, 2022
  175. kittywhiskers referenced this in commit 2f304abfd3 on Feb 28, 2022
  176. kittywhiskers referenced this in commit acf079679b on Feb 28, 2022
  177. kittywhiskers referenced this in commit 5e5b913033 on Mar 13, 2022
  178. kittywhiskers referenced this in commit 6522e996c6 on Mar 24, 2022
  179. DrahtBot locked this on Sep 1, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-06-26 13:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me