From 43ba4e5e9000a1a702ba6f04fddeddfdadf829e8 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 11 Sep 2020 14:33:23 -0700 Subject: [PATCH 01/67] 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- Co-authored-by: Pieter Wuille --- src/pubkey.h | 2 +- src/script/interpreter.cpp | 12 ++++++------ src/script/interpreter.h | 10 +++++----- src/script/sigcache.cpp | 8 ++++---- src/script/sigcache.h | 2 +- src/script/sign.cpp | 8 ++++---- src/test/fuzz/script_sigcache.cpp | 2 +- src/test/fuzz/signature_checker.cpp | 2 +- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/pubkey.h b/src/pubkey.h index a877e866c58a..30a96330dda4 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -174,7 +174,7 @@ class CPubKey /* * Check syntactic correctness. * - * Note that this is consensus critical as CheckSig() calls it! + * Note that this is consensus critical as CheckECDSASignature() calls it! */ bool IsValid() const { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index f997090edd62..f978d2f520cb 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -334,7 +334,7 @@ static bool EvalChecksig(const valtype& vchSig, const valtype& vchPubKey, CScrip //serror is set return false; } - fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion); + fSuccess = checker.CheckECDSASignature(vchSig, vchPubKey, scriptCode, sigversion); if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && vchSig.size()) return set_error(serror, SCRIPT_ERR_SIG_NULLFAIL); @@ -1144,7 +1144,7 @@ bool EvalScript(std::vector >& stack, const CScript& } // Check signature - bool fOk = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion); + bool fOk = checker.CheckECDSASignature(vchSig, vchPubKey, scriptCode, sigversion); if (fOk) { isig++; @@ -1543,7 +1543,7 @@ bool BaseSignatureChecker::CheckDataSig(const std::vector& vchSig } template -bool GenericTransactionSignatureChecker::VerifySignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash) const +bool GenericTransactionSignatureChecker::VerifyECDSASignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash) const { return pubkey.Verify(sighash, vchSig); } @@ -1557,11 +1557,11 @@ bool GenericTransactionSignatureChecker::CheckDataSig(const std::vector -bool GenericTransactionSignatureChecker::CheckSig(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const +bool GenericTransactionSignatureChecker::CheckECDSASignature(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { CPubKey pubkey(vchPubKey); if (!pubkey.IsValid()) @@ -1576,7 +1576,7 @@ bool GenericTransactionSignatureChecker::CheckSig(const std::vectortxdata); - if (!VerifySignature(vchSig, pubkey, sighash)) + if (!VerifyECDSASignature(vchSig, pubkey, sighash)) return false; return true; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index e6ec7d3392aa..8f9c8b0ba546 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -141,7 +141,7 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn class BaseSignatureChecker { public: - virtual bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const + virtual bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { return false; } @@ -183,12 +183,12 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker const PrecomputedTransactionData* txdata; protected: - virtual bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; + virtual bool VerifyECDSASignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; public: GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr) {} GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; + bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; bool CheckDataSig(const std::vector& vchSig, const std::vector& vchMessage, @@ -206,9 +206,9 @@ class DeferringSignatureChecker : public BaseSignatureChecker public: DeferringSignatureChecker(const BaseSignatureChecker& checker) : m_checker(checker) {} - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override + bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { - return m_checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion); + return m_checker.CheckECDSASignature(scriptSig, vchPubKey, scriptCode, sigversion); } bool CheckLockTime(const CScriptNum& nLockTime) const override { diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 90447bc61fea..33406c785188 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -45,7 +45,7 @@ class CSignatureCache } void - ComputeEntry(uint256& entry, const uint256 &hash, const std::vector& vchSig, const CPubKey& pubkey) + ComputeEntryECDSA(uint256& entry, const uint256 &hash, const std::vector& vchSig, const CPubKey& pubkey) { CSHA256 hasher = m_salted_hasher; hasher.Write(hash.begin(), 32).Write(pubkey.data(), pubkey.size()).Write(vchSig.data(), vchSig.size()).Finalize(entry.begin()); @@ -91,13 +91,13 @@ bool InitSignatureCache(size_t max_size_bytes) return true; } -bool CachingTransactionSignatureChecker::VerifySignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash) const +bool CachingTransactionSignatureChecker::VerifyECDSASignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash) const { uint256 entry; - signatureCache.ComputeEntry(entry, sighash, vchSig, pubkey); + signatureCache.ComputeEntryECDSA(entry, sighash, vchSig, pubkey); if (signatureCache.Get(entry, !store)) return true; - if (!TransactionSignatureChecker::VerifySignature(vchSig, pubkey, sighash)) + if (!TransactionSignatureChecker::VerifyECDSASignature(vchSig, pubkey, sighash)) return false; if (store) signatureCache.Set(entry); diff --git a/src/script/sigcache.h b/src/script/sigcache.h index eea44e6a6e6a..c3f08aed5f1e 100644 --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -27,7 +27,7 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker public: CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, PrecomputedTransactionData& txdataIn, bool storeIn=true) : TransactionSignatureChecker(txToIn, nInIn, amount, txdataIn, MissingDataBehavior::ASSERT_FAIL), store(storeIn) {} - bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override; + bool VerifyECDSASignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override; }; [[nodiscard]] bool InitSignatureCache(size_t max_size_bytes); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 8df3235a24d8..f053622d9d45 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -230,9 +230,9 @@ class SignatureExtractorChecker final : public DeferringSignatureChecker public: SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : DeferringSignatureChecker(checker), sigdata(sigdata) {} - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override + bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { - if (m_checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion)) { + if (m_checker.CheckECDSASignature(scriptSig, vchPubKey, scriptCode, sigversion)) { CPubKey pubkey(vchPubKey); sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig)); return true; @@ -295,7 +295,7 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI for (unsigned int i = last_success_key; i < num_pubkeys; ++i) { const valtype& pubkey = solutions[i+1]; // We either have a signature for this pubkey, or we have found a signature and it is valid - if (data.signatures.count(CPubKey(pubkey).GetID()) || extractor_checker.CheckSig(sig, pubkey, next_script, sigversion)) { + if (data.signatures.count(CPubKey(pubkey).GetID()) || extractor_checker.CheckECDSASignature(sig, pubkey, next_script, sigversion)) { last_success_key = i + 1; break; } @@ -352,7 +352,7 @@ class DummySignatureChecker final : public BaseSignatureChecker { public: DummySignatureChecker() = default; - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } + bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } }; } diff --git a/src/test/fuzz/script_sigcache.cpp b/src/test/fuzz/script_sigcache.cpp index d5b4eb8ea699..c923a775c41e 100644 --- a/src/test/fuzz/script_sigcache.cpp +++ b/src/test/fuzz/script_sigcache.cpp @@ -41,7 +41,7 @@ FUZZ_TARGET(script_sigcache, .init = initialize_script_sigcache) if (pub_key) { const std::vector random_bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider); if (!random_bytes.empty()) { - (void)caching_transaction_signature_checker.VerifySignature(random_bytes, *pub_key, ConsumeUInt256(fuzzed_data_provider)); + (void)caching_transaction_signature_checker.VerifyECDSASignature(random_bytes, *pub_key, ConsumeUInt256(fuzzed_data_provider)); } } } diff --git a/src/test/fuzz/signature_checker.cpp b/src/test/fuzz/signature_checker.cpp index 4b49c4748427..b8c95c4ca73d 100644 --- a/src/test/fuzz/signature_checker.cpp +++ b/src/test/fuzz/signature_checker.cpp @@ -24,7 +24,7 @@ class FuzzedSignatureChecker : public BaseSignatureChecker { } - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override + bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return m_fuzzed_data_provider.ConsumeBool(); } From 59e141ef4732b951b039f978fd63f49af65d80b2 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 11 Sep 2020 14:33:00 -0700 Subject: [PATCH 02/67] 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. Co-authored-by: Pieter Wuille --- src/validation.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 9b7c0c4a0905..d0eddd4ec8c3 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2200,13 +2200,20 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, } if (!txdata.m_ready) { - txdata.Init(tx, {}); + std::vector spent_outputs; + spent_outputs.reserve(tx.vin.size()); + + for (const auto& txin : tx.vin) { + const COutPoint& prevout = txin.prevout; + const Coin& coin = inputs.AccessCoin(prevout); + assert(!coin.IsSpent()); + spent_outputs.emplace_back(coin.out); + } + txdata.Init(tx, std::move(spent_outputs)); } + assert(txdata.m_spent_outputs.size() == tx.vin.size()); for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - const Coin& coin = inputs.AccessCoin(prevout); - assert(!coin.IsSpent()); // We very carefully only pass in things to CScriptCheck which // are clearly committed to by tx' witness hash. This provides @@ -2215,7 +2222,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, // spent being checked as a part of CScriptCheck. // Verify signature - CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata); + CScriptCheck check(txdata.m_spent_outputs[i], tx, i, flags, cacheSigStore, &txdata); if (pvChecks) { pvChecks->emplace_back(std::move(check)); } else if (!check()) { @@ -2230,7 +2237,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, // splitting the network between upgraded and // non-upgraded nodes by banning CONSENSUS-failing // data providers. - CScriptCheck check2(coin.out, tx, i, + CScriptCheck check2(txdata.m_spent_outputs[i], tx, i, (flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS), cacheSigStore, &txdata); if (check2()) return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); From 8781489e0d5e9ddd166971b51708eaa086d9372c Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 11 Sep 2020 14:33:10 -0700 Subject: [PATCH 03/67] 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. Co-authored-by: Johnson Lau --- src/script/interpreter.cpp | 104 ++++++++++++++++++++++++++++++++++++- src/script/interpreter.h | 29 +++++++++-- src/script/script.h | 5 ++ src/validation.cpp | 2 +- 4 files changed, 134 insertions(+), 6 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index f978d2f520cb..c456cb43a3ff 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1468,16 +1468,62 @@ uint256 GetOutputsSHA256(const T& txTo) return ss.GetSHA256(); } +/** Compute the (single) SHA256 of the concatenation of all amounts spent by a tx. */ +uint256 GetSpentAmountsSHA256(const std::vector& outputs_spent) +{ + CHashWriter ss(SER_GETHASH, 0); + for (const auto& txout : outputs_spent) { + ss << txout.nValue; + } + return ss.GetSHA256(); +} + +/** Compute the (single) SHA256 of the concatenation of all scriptPubKeys spent by a tx. */ +uint256 GetSpentScriptsSHA256(const std::vector& outputs_spent) +{ + CHashWriter ss(SER_GETHASH, 0); + for (const auto& txout : outputs_spent) { + ss << txout.scriptPubKey; + } + return ss.GetSHA256(); +} + + } // namespace template void PrecomputedTransactionData::Init(const T& txTo, std::vector&& spent_outputs, bool force) { - assert(!m_ready); + assert(!m_spent_outputs_ready); m_spent_outputs = std::move(spent_outputs); + if (!m_spent_outputs.empty()) { + assert(m_spent_outputs.size() == txTo.vin.size()); + m_spent_outputs_ready = true; + } - m_ready = true; + // TODO: replace it to real detection; by tx version probably + // TODO: replace it to real detection; by tx version probably + // TODO: replace it to real detection; by tx version probably + // TODO: replace it to real detection; by tx version probably + // TODO: replace it to real detection; by tx version probably + // TODO: replace it to real detection; by tx version probably + // it's used in VerifyScript, EvalScript, etc + bool uses_bip341_taproot = true; + for (size_t inpos = 0; inpos < txTo.vin.size(); ++inpos) { + if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + SIG_TAPROOT_SIZE) { + // TODO: replace it to real bip341 detection; by tx version probably + } + } + if (uses_bip341_taproot) { + m_prevouts_single_hash = GetPrevoutsSHA256(txTo); + m_sequences_single_hash = GetSequencesSHA256(txTo); + m_outputs_single_hash = GetOutputsSHA256(txTo); + + m_spent_amounts_single_hash = GetSpentAmountsSHA256(m_spent_outputs); + m_spent_scripts_single_hash = GetSpentScriptsSHA256(m_spent_outputs); + m_bip341_taproot_ready = true; + } } template @@ -1504,6 +1550,60 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTr assert(!"Unknown MissingDataBehavior value"); } +static const HashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash"); + +template +bool SignatureHashSchnorr(uint256& hash_out, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache) +{ + // TODO: either remove attribute or variable ext_flag. It's not used but it kept for backport compatibility + [[maybe_unused]] uint8_t ext_flag; + switch (sigversion) { + case SigVersion::TAPROOT: + ext_flag = 0; + break; + default: + assert(false); + } + assert(in_pos < tx_to.vin.size()); + assert(cache.m_spent_outputs_ready); + + CHashWriter ss = HASHER_TAPSIGHASH; + + // Epoch + static constexpr uint8_t EPOCH = 0; + ss << EPOCH; + + // Hash type + 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 + const uint8_t input_type = hash_type & SIGHASH_INPUT_MASK; + if (!(hash_type <= 0x03 || (hash_type >= 0x81 && hash_type <= 0x83))) return false; + ss << hash_type; + + // Transaction level data + ss << tx_to.nVersion; + ss << tx_to.nLockTime; + if (input_type != SIGHASH_ANYONECANPAY) { + ss << cache.m_prevouts_single_hash; + ss << cache.m_spent_amounts_single_hash; + ss << cache.m_spent_scripts_single_hash; + ss << cache.m_sequences_single_hash; + } + if (output_type == SIGHASH_ALL) { + ss << cache.m_outputs_single_hash; + } + + // Data about the output (if only one). + if (output_type == SIGHASH_SINGLE) { + if (in_pos >= tx_to.vout.size()) return false; + CHashWriter sha_single_output(SER_GETHASH, 0); + sha_single_output << tx_to.vout[in_pos]; + ss << sha_single_output.GetSHA256(); + } + + hash_out = ss.GetSHA256(); + return true; +} + template uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache) { diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 8f9c8b0ba546..514383337e54 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -28,6 +28,10 @@ enum SIGHASH_NONE = 2, SIGHASH_SINGLE = 3, SIGHASH_ANYONECANPAY = 0x80, + + SIGHASH_DEFAULT = 0, //!< Taproot only; implied when sighash byte is missing, and equivalent to SIGHASH_ALL + SIGHASH_OUTPUT_MASK = 3, + SIGHASH_INPUT_MASK = 0x80, }; /** Script verification flags. @@ -117,9 +121,24 @@ bool CheckSignatureEncoding(const std::vector &vchSig, unsigned i struct PrecomputedTransactionData { - uint256 hashPrevouts, hashSequence, hashOutputs; - bool m_ready = false; + // BIP341 precomputed data. + // These are single-SHA256, see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-15. + uint256 m_prevouts_single_hash; + uint256 m_sequences_single_hash; + uint256 m_outputs_single_hash; + uint256 m_spent_amounts_single_hash; + uint256 m_spent_scripts_single_hash; + //! Whether the 5 fields above are initialized. + bool m_bip341_taproot_ready = false; + + // BIP143 precomputed data (double-SHA256). +// uint256 hashPrevouts, hashSequence, hashOutputs; + //! Whether the 3 fields above are initialized. + //bool m_bip143_segwit_ready = false; + std::vector m_spent_outputs; + //! Whether m_spent_outputs is initialized. + bool m_spent_outputs_ready = false; PrecomputedTransactionData() = default; @@ -132,9 +151,13 @@ struct PrecomputedTransactionData enum class SigVersion { - BASE = 0, + BASE = 0, //!< Bare scripts and BIP16 P2SH-wrapped redeemscripts + TAPROOT = 2, //!< Witness v1 with 32-byte program, not BIP16 P2SH-wrapped, key path spending; see BIP 341 }; +/** Signature hash sizes */ +static constexpr size_t SIG_TAPROOT_SIZE = 32; + template uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr); diff --git a/src/script/script.h b/src/script/script.h index 409fdc88f9d4..fde3a7227e3f 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -45,6 +45,11 @@ static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 // SEQUENCE_FINAL). static const uint32_t LOCKTIME_MAX = 0xFFFFFFFFU; +// Tag for input annex. If there are at least two witness elements for a transaction input, +// and the first byte of the last element is 0x50, this last element is called annex, and +// has meanings independent of the script +static constexpr unsigned int ANNEX_TAG = 0x50; + template std::vector ToByteVector(const T& in) { diff --git a/src/validation.cpp b/src/validation.cpp index d0eddd4ec8c3..50764cd56be8 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2199,7 +2199,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, return true; } - if (!txdata.m_ready) { + if (!txdata.m_spent_outputs_ready) { std::vector spent_outputs; spent_outputs.reserve(tx.vin.size()); From 7602e1a09862ef0cdc3c1576a7c8f7616a8eaaa2 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 11 Sep 2020 14:33:30 -0700 Subject: [PATCH 04/67] 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. Co-authored-by: Pieter Wuille --- configure.ac | 2 +- src/pubkey.cpp | 15 +++++++++++++ src/pubkey.h | 20 +++++++++++++++++ src/script/interpreter.cpp | 34 ++++++++++++++++++++++++++-- src/script/interpreter.h | 7 ++++++ src/script/script_error.cpp | 6 +++++ src/script/script_error.h | 5 +++++ src/script/sigcache.cpp | 35 ++++++++++++++++++++++++----- src/script/sigcache.h | 2 ++ src/script/sign.h | 1 + src/test/fuzz/script_sigcache.cpp | 18 ++++++++++----- src/test/fuzz/signature_checker.cpp | 5 +++++ src/test/key_tests.cpp | 28 +++++++++++++++++++++++ 13 files changed, 164 insertions(+), 14 deletions(-) diff --git a/configure.ac b/configure.ac index 92e8f3381e12..5b0a06d05590 100644 --- a/configure.ac +++ b/configure.ac @@ -2064,7 +2064,7 @@ CPPFLAGS="$CPPFLAGS_TEMP" dnl The ECDH module is required by the wallet's Platform key provider dnl (DashPay contact request encryption, wallet/platformkeys.cpp). -ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-ecdh --disable-openssl-tests" +ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-ecdh --disable-openssl-tests --enable-module-schnorrsig" AC_CONFIG_SUBDIRS([src/dashbls src/secp256k1]) AC_OUTPUT diff --git a/src/pubkey.cpp b/src/pubkey.cpp index a40cee3b78f1..7fe212fc3348 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -179,6 +180,20 @@ int ecdsa_signature_parse_der_lax(secp256k1_ecdsa_signature* sig, const unsigned return 1; } +XOnlyPubKey::XOnlyPubKey(Span bytes) +{ + assert(bytes.size() == 32); + std::copy(bytes.begin(), bytes.end(), m_keydata.begin()); +} + +bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span sigbytes) const +{ + assert(sigbytes.size() == 64); + secp256k1_xonly_pubkey pubkey; + if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data())) return false; + return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey); +} + bool CPubKey::Verify(const uint256 &hash, const std::vector& vchSig) const { if (!IsValid()) return false; diff --git a/src/pubkey.h b/src/pubkey.h index 30a96330dda4..069ede3b7721 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -256,6 +257,25 @@ struct EllSwiftPubKey } }; +class XOnlyPubKey +{ +private: + uint256 m_keydata; + +public: + /** Construct an x-only pubkey from exactly 32 bytes. */ + XOnlyPubKey(Span bytes); + + /** Verify a Schnorr signature against this public key. + * + * sigbytes must be exactly 64 bytes. + */ + bool VerifySchnorr(const uint256& msg, Span sigbytes) const; + + const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); } + size_t size() const { return m_keydata.size(); } +}; + struct CExtPubKey { unsigned char version[4]; unsigned char nDepth; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index c456cb43a3ff..e2cb7847e876 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1567,7 +1567,7 @@ bool SignatureHashSchnorr(uint256& hash_out, const T& tx_to, uint32_t in_pos, ui assert(in_pos < tx_to.vin.size()); assert(cache.m_spent_outputs_ready); - CHashWriter ss = HASHER_TAPSIGHASH; + HashWriter ss{HASHER_TAPSIGHASH}; // Epoch static constexpr uint8_t EPOCH = 0; @@ -1595,7 +1595,7 @@ bool SignatureHashSchnorr(uint256& hash_out, const T& tx_to, uint32_t in_pos, ui // Data about the output (if only one). if (output_type == SIGHASH_SINGLE) { if (in_pos >= tx_to.vout.size()) return false; - CHashWriter sha_single_output(SER_GETHASH, 0); + HashWriter sha_single_output{}; sha_single_output << tx_to.vout[in_pos]; ss << sha_single_output.GetSHA256(); } @@ -1660,6 +1660,12 @@ bool GenericTransactionSignatureChecker::CheckDataSig(const std::vector +bool GenericTransactionSignatureChecker::VerifySchnorrSignature(Span sig, const XOnlyPubKey& pubkey, const uint256& sighash) const +{ + return pubkey.VerifySchnorr(sighash, sig); +} + template bool GenericTransactionSignatureChecker::CheckECDSASignature(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const { @@ -1682,6 +1688,30 @@ bool GenericTransactionSignatureChecker::CheckECDSASignature(const std::vecto return true; } +template +bool GenericTransactionSignatureChecker::CheckSchnorrSignature(Span sig, Span pubkey_in, SigVersion sigversion, ScriptError* serror) const +{ + assert(sigversion == SigVersion::TAPROOT); + // Schnorr signatures have 32-byte public keys. The caller is responsible for enforcing this. + assert(pubkey_in.size() == 32); + if (sig.size() != 64 && sig.size() != 65) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE); + + XOnlyPubKey pubkey{pubkey_in}; + + uint8_t hashtype = SIGHASH_DEFAULT; + if (sig.size() == 65) { + hashtype = SpanPopBack(sig); + if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); + } + uint256 sighash; + assert(this->txdata); + if (!SignatureHashSchnorr(sighash, *txTo, nIn, hashtype, sigversion, *this->txdata)) { + return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); + } + if (!VerifySchnorrSignature(sig, pubkey, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG); + return true; +} + template bool GenericTransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const { diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 514383337e54..89a7847a6b0a 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -169,6 +169,11 @@ class BaseSignatureChecker return false; } + virtual bool CheckSchnorrSignature(Span sig, Span pubkey, SigVersion sigversion, ScriptError* serror = nullptr) const + { + return false; + } + virtual bool CheckLockTime(const CScriptNum& nLockTime) const { return false; @@ -207,11 +212,13 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker protected: virtual bool VerifyECDSASignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; + virtual bool VerifySchnorrSignature(Span sig, const XOnlyPubKey& pubkey, const uint256& sighash) const; public: GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr) {} GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; + bool CheckSchnorrSignature(Span sig, Span pubkey, SigVersion sigversion, ScriptError* serror = nullptr) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; bool CheckDataSig(const std::vector& vchSig, const std::vector& vchMessage, diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index 9870b150efe5..3551f0dc3305 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -88,6 +88,12 @@ std::string ScriptErrorString(const ScriptError serror) return "Public key is neither compressed or uncompressed"; case SCRIPT_ERR_CLEANSTACK: return "Stack size must be exactly one after execution"; + case SCRIPT_ERR_SCHNORR_SIG_SIZE: + return "Invalid Schnorr signature size"; + case SCRIPT_ERR_SCHNORR_SIG_HASHTYPE: + return "Invalid Schnorr signature hash type"; + case SCRIPT_ERR_SCHNORR_SIG: + return "Invalid Schnorr signature"; case SCRIPT_ERR_OP_CODESEPARATOR: return "Using OP_CODESEPARATOR"; case SCRIPT_ERR_SIG_FINDANDDELETE: diff --git a/src/script/script_error.h b/src/script/script_error.h index 30e10fcb328a..bf2c7ee144c8 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -66,6 +66,11 @@ typedef enum ScriptError_t /* softfork safeness */ SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, + /* Taproot */ + SCRIPT_ERR_SCHNORR_SIG_SIZE, + SCRIPT_ERR_SCHNORR_SIG_HASHTYPE, + SCRIPT_ERR_SCHNORR_SIG, + /* Constant scriptCode */ SCRIPT_ERR_OP_CODESEPARATOR, SCRIPT_ERR_SIG_FINDANDDELETE, diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 33406c785188..60a43874efe8 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -27,8 +27,9 @@ namespace { class CSignatureCache { private: - //! Entries are SHA256(nonce || signature hash || public key || signature): - CSHA256 m_salted_hasher; + //! Entries are SHA256(nonce || 'E' or 'S' || 31 zero bytes || signature hash || public key || signature): + CSHA256 m_salted_hasher_ecdsa; + CSHA256 m_salted_hasher_schnorr; typedef CuckooCache::cache map_type; map_type setValid; std::shared_mutex cs_sigcache; @@ -39,18 +40,30 @@ class CSignatureCache uint256 nonce = GetRandHash(); // We want the nonce to be 64 bytes long to force the hasher to process // this chunk, which makes later hash computations more efficient. We - // just write our 32-byte entropy twice to fill the 64 bytes. - m_salted_hasher.Write(nonce.begin(), 32); - m_salted_hasher.Write(nonce.begin(), 32); + // just write our 32-byte entropy, and then pad with 'E' for ECDSA and + // 'S' for Schnorr (followed by 0 bytes). + static constexpr unsigned char PADDING_ECDSA[32] = {'E'}; + static constexpr unsigned char PADDING_SCHNORR[32] = {'S'}; + m_salted_hasher_ecdsa.Write(nonce.begin(), 32); + m_salted_hasher_ecdsa.Write(PADDING_ECDSA, 32); + m_salted_hasher_schnorr.Write(nonce.begin(), 32); + m_salted_hasher_schnorr.Write(PADDING_SCHNORR, 32); } void ComputeEntryECDSA(uint256& entry, const uint256 &hash, const std::vector& vchSig, const CPubKey& pubkey) { - CSHA256 hasher = m_salted_hasher; + CSHA256 hasher = m_salted_hasher_ecdsa; hasher.Write(hash.begin(), 32).Write(pubkey.data(), pubkey.size()).Write(vchSig.data(), vchSig.size()).Finalize(entry.begin()); } + void + ComputeEntrySchnorr(uint256& entry, const uint256 &hash, Span sig, const XOnlyPubKey& pubkey) + { + CSHA256 hasher = m_salted_hasher_schnorr; + hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(sig.data(), sig.size()).Finalize(entry.begin()); + } + bool Get(const uint256& entry, const bool erase) { @@ -103,3 +116,13 @@ bool CachingTransactionSignatureChecker::VerifyECDSASignature(const std::vector< signatureCache.Set(entry); return true; } + +bool CachingTransactionSignatureChecker::VerifySchnorrSignature(Span sig, const XOnlyPubKey& pubkey, const uint256& sighash) const +{ + uint256 entry; + signatureCache.ComputeEntrySchnorr(entry, sighash, sig, pubkey); + if (signatureCache.Get(entry, !store)) return true; + if (!TransactionSignatureChecker::VerifySchnorrSignature(sig, pubkey, sighash)) return false; + if (store) signatureCache.Set(entry); + return true; +} diff --git a/src/script/sigcache.h b/src/script/sigcache.h index c3f08aed5f1e..b2a2c1a7e369 100644 --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -7,6 +7,7 @@ #define BITCOIN_SCRIPT_SIGCACHE_H #include