From 0281bd05afa3b541841fc03d379a436fce5e32e8 Mon Sep 17 00:00:00 2001 From: Bushstar Date: Tue, 9 Jan 2024 11:15:11 +0000 Subject: [PATCH 01/19] Implement BIP 340-342 validation (Schnorr/taproot/tapscript) --- configure.ac | 2 +- src/dfi/govvariables/attributes.cpp | 4 +- src/dfi/govvariables/attributes.h | 4 +- src/ffi/cxx.h | 24 +- src/hash.cpp | 16 + src/hash.h | 38 +- src/policy/policy.cpp | 34 +- src/policy/policy.h | 8 +- src/pubkey.cpp | 22 + src/pubkey.h | 24 +- src/script/descriptor.cpp | 6 +- src/script/interpreter.cpp | 572 ++- src/script/interpreter.h | 104 +- src/script/script.cpp | 11 + src/script/script.h | 17 + src/script/script_error.cpp | 22 +- src/script/script_error.h | 12 + src/script/sigcache.cpp | 45 +- src/script/sigcache.h | 4 +- src/script/sign.cpp | 24 +- src/script/sign.h | 1 + src/script/standard.cpp | 8 +- src/script/standard.h | 4 +- src/span.h | 118 +- src/test/key_tests.cpp | 29 + src/test/script_tests.cpp | 138 +- src/test/transaction_tests.cpp | 1 + src/uint256.cpp | 10 +- src/uint256.h | 31 +- src/validation.cpp | 73 +- src/wallet/ismine.cpp | 1 + src/wallet/rpcdump.cpp | 1 + test/functional/feature_taproot.py | 3071 +++++++++++++++++ test/functional/p2p_segwit.py | 11 +- .../test_framework/bip340_test_vectors.csv | 17 + test/functional/test_framework/blocktools.py | 17 +- test/functional/test_framework/key.py | 262 +- test/functional/test_framework/script.py | 276 +- test/functional/test_runner.py | 1 + 39 files changed, 4826 insertions(+), 237 deletions(-) create mode 100644 test/functional/feature_taproot.py create mode 100644 test/functional/test_framework/bip340_test_vectors.csv diff --git a/configure.ac b/configure.ac index 6b11061832..c8877df481 100644 --- a/configure.ac +++ b/configure.ac @@ -1444,7 +1444,7 @@ if test x$need_bundled_univalue = xyes; then AC_CONFIG_SUBDIRS([src/univalue]) fi -ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --disable-jni" +ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --enable-module-schnorrsig --enable-experimental --disable-jni" AC_CONFIG_SUBDIRS([src/secp256k1]) AC_OUTPUT diff --git a/src/dfi/govvariables/attributes.cpp b/src/dfi/govvariables/attributes.cpp index 6d177b4753..f21ee364b5 100644 --- a/src/dfi/govvariables/attributes.cpp +++ b/src/dfi/govvariables/attributes.cpp @@ -774,8 +774,8 @@ static inline void rtrim(std::string &s, unsigned char remove) { s.erase(std::find_if(s.rbegin(), s.rend(), [&remove](unsigned char ch) { return ch != remove; }).base(), s.end()); } -const std::map(const std::string &)>>> - &ATTRIBUTES::parseValue() { +const std::map(const std::string &)>>> & +ATTRIBUTES::parseValue() { static const std::map(const std::string &)>>> parsers{ {AttributeTypes::Token, diff --git a/src/dfi/govvariables/attributes.h b/src/dfi/govvariables/attributes.h index 2910c376bc..9fe4a8723c 100644 --- a/src/dfi/govvariables/attributes.h +++ b/src/dfi/govvariables/attributes.h @@ -590,8 +590,8 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator &allowedVaultIDs(); static const std::map &allowedRulesIDs(); static const std::map> &allowedKeys(); - static const std::map(const std::string &)>>> - &parseValue(); + static const std::map(const std::string &)>>> & + parseValue(); Res ProcessVariable(const std::string &key, const std::optional &value, diff --git a/src/ffi/cxx.h b/src/ffi/cxx.h index 1fc163b2d8..b4a4bc7f30 100644 --- a/src/ffi/cxx.h +++ b/src/ffi/cxx.h @@ -55,8 +55,8 @@ namespace rust { static String lossy(const char16_t *) noexcept; static String lossy(const char16_t *, std::size_t) noexcept; - String &operator=(const String &) &noexcept; - String &operator=(String &&) &noexcept; + String &operator=(const String &) & noexcept; + String &operator=(String &&) & noexcept; explicit operator std::string() const; @@ -115,7 +115,7 @@ namespace rust { Str(const char *); Str(const char *, std::size_t); - Str &operator=(const Str &) &noexcept = default; + Str &operator=(const Str &) & noexcept = default; explicit operator std::string() const; @@ -163,8 +163,8 @@ namespace rust { struct copy_assignable_if { copy_assignable_if() noexcept = default; copy_assignable_if(const copy_assignable_if &) noexcept = default; - copy_assignable_if &operator=(const copy_assignable_if &) &noexcept = delete; - copy_assignable_if &operator=(copy_assignable_if &&) &noexcept = default; + copy_assignable_if &operator=(const copy_assignable_if &) & noexcept = delete; + copy_assignable_if &operator=(copy_assignable_if &&) & noexcept = default; }; } // namespace detail @@ -177,8 +177,8 @@ namespace rust { Slice() noexcept; Slice(T *, std::size_t count) noexcept; - Slice &operator=(const Slice &) &noexcept = default; - Slice &operator=(Slice &&) &noexcept = default; + Slice &operator=(const Slice &) & noexcept = default; + Slice &operator=(Slice &&) & noexcept = default; T *data() const noexcept; std::size_t size() const noexcept; @@ -265,7 +265,7 @@ namespace rust { explicit Box(const T &); explicit Box(T &&); - Box &operator=(Box &&) &noexcept; + Box &operator=(Box &&) & noexcept; const T *operator->() const noexcept; const T &operator*() const noexcept; @@ -310,7 +310,7 @@ namespace rust { Vec(Vec &&) noexcept; ~Vec() noexcept; - Vec &operator=(Vec &&) &noexcept; + Vec &operator=(Vec &&) & noexcept; Vec &operator=(const Vec &) &; std::size_t size() const noexcept; @@ -391,7 +391,7 @@ namespace rust { ~Error() noexcept override; Error &operator=(const Error &) &; - Error &operator=(Error &&) &noexcept; + Error &operator=(Error &&) & noexcept; const char *what() const noexcept override; @@ -758,7 +758,7 @@ namespace rust { } template - Box &Box::operator=(Box &&other) &noexcept { + Box &Box::operator=(Box &&other) & noexcept { if (this->ptr) { this->drop(); } @@ -849,7 +849,7 @@ namespace rust { } template - Vec &Vec::operator=(Vec &&other) &noexcept { + Vec &Vec::operator=(Vec &&other) & noexcept { this->drop(); this->repr = other.repr; new (&other) Vec(); diff --git a/src/hash.cpp b/src/hash.cpp index 83d59d9dd8..8f32bfa6ea 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -77,3 +77,19 @@ void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char he num[3] = (nChild >> 0) & 0xFF; CHMAC_SHA512(chainCode.begin(), chainCode.size()).Write(&header, 1).Write(data, 32).Write(num, 4).Finalize(output); } + +uint256 SHA256Uint256(const uint256& input) +{ + uint256 result; + CSHA256().Write(input.begin(), 32).Finalize(result.begin()); + return result; +} + +CHashWriter TaggedHash(const std::string& tag) +{ + CHashWriter writer(SER_GETHASH, 0); + uint256 taghash; + CSHA256().Write((const unsigned char*)tag.data(), tag.size()).Finalize(taghash.begin()); + writer << taghash << taghash; + return writer; +} diff --git a/src/hash.h b/src/hash.h index bc6a9928f7..947b7eecf7 100644 --- a/src/hash.h +++ b/src/hash.h @@ -6,6 +6,7 @@ #ifndef DEFI_HASH_H #define DEFI_HASH_H +#include #include #include #include @@ -15,6 +16,7 @@ #include #include +#include #include typedef uint256 ChainCode; @@ -141,7 +143,7 @@ inline uint160 EthHash160(const std::vector& vch) { class CHashWriter { private: - CHash256 ctx; + CSHA256 ctx; const int nType; const int nVersion; @@ -156,10 +158,24 @@ class CHashWriter ctx.Write((const unsigned char*)pch, size); } - // invalidates the object + /** Compute the double-SHA256 hash of all data written to this object. + * + * Invalidates this object. + */ uint256 GetHash() { uint256 result; - ctx.Finalize((unsigned char*)&result); + ctx.Finalize(result.begin()); + ctx.Reset().Write(result.begin(), CSHA256::OUTPUT_SIZE).Finalize(result.begin()); + return result; + } + + /** Compute the SHA256 hash of all data written to this object. + * + * Invalidates this object. + */ + uint256 GetSHA256() { + uint256 result; + ctx.Finalize(result.begin()); return result; } @@ -167,9 +183,8 @@ class CHashWriter * Returns the first 64 bits from the resulting hash. */ inline uint64_t GetCheapHash() { - unsigned char result[CHash256::OUTPUT_SIZE]; - ctx.Finalize(result); - return ReadLE64(result); + uint256 result = GetHash(); + return ReadLE64(result.begin()); } template @@ -224,8 +239,19 @@ uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL return ss.GetHash(); } +/** Single-SHA256 a 32-byte input (represented as uint256). */ +NODISCARD uint256 SHA256Uint256(const uint256& input); + unsigned int MurmurHash3(unsigned int nHashSeed, const uint8_t *vDataToHash, uint32_t vDataSize); void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]); +/** Return a CHashWriter primed for tagged hashes (as specified in BIP 340). + * + * The returned object will have SHA256(tag) written to it twice (= 64 bytes). + * A tagged hash can be computed by feeding the message into this object, and + * then calling CHashWriter::GetSHA256(). + */ +CHashWriter TaggedHash(const std::string& tag); + #endif // DEFI_HASH_H diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 14cd51481c..c9ef8ac2a2 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -9,7 +9,7 @@ #include #include - +#include CAmount GetDustThreshold(const CTxOut& txout, int32_t txVersion, const CFeeRate& dustRelayFeeIn) { @@ -205,6 +205,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // get the scriptPubKey corresponding to this input: CScript prevScript = prev.scriptPubKey; + bool p2sh{}; if (prevScript.IsPayToScriptHash()) { std::vector > stack; // If the scriptPubKey is P2SH, we try to extract the redeemScript casually by converting the scriptSig @@ -215,6 +216,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) if (stack.empty()) return false; prevScript = CScript(stack.back().begin(), stack.back().end()); + p2sh = true; } int witnessversion = 0; @@ -236,6 +238,36 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) return false; } } + + // Check policy limits for Taproot spends: + // - MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit for stack item size + // - No annexes + if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) { + // Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341) + auto stack = MakeSpan(tx.vin[i].scriptWitness.stack); + if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { + // Annexes are nonstandard as long as no semantics are defined for them. + return false; + } + if (stack.size() >= 2) { + // Script path spend (2 or more stack elements after removing optional annex) + const auto& control_block = SpanPopBack(stack); + SpanPopBack(stack); // Ignore script + if (control_block.empty()) return false; // Empty control block is invalid + if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) { + // Leaf version 0xc0 (aka Tapscript, see BIP 342) + for (const auto& item : stack) { + if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) return false; + } + } + } else if (stack.size() == 1) { + // Key path spend (1 stack element after removing optional annex) + // (no policy rules apply) + } else { + // 0 stack elements; this is already invalid by consensus rules + return false; + } + } } return true; } diff --git a/src/policy/policy.h b/src/policy/policy.h index da98d11836..def631ee13 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -40,6 +40,8 @@ static const bool DEFAULT_PERMIT_BAREMULTISIG = true; static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100; /** The maximum size of each witness stack item in a standard P2WSH script */ static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80; +/** The maximum size of each witness stack item in a standard BIP 342 script (Taproot, leaf version 0xc0) */ +static const unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE = 80; /** The maximum size of a standard witnessScript */ static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600; /** Min feerate for defining dust. Historically this has been based on the @@ -68,7 +70,11 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VE SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE | - SCRIPT_VERIFY_CONST_SCRIPTCODE; + SCRIPT_VERIFY_CONST_SCRIPTCODE | + SCRIPT_VERIFY_TAPROOT | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION | + SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE; /** For convenience, standard but not mandatory verify flags. */ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 8c1d5fb4ed..ce2a7b0181 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace { @@ -166,6 +167,27 @@ static int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1 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_verify, &pubkey, m_keydata.data())) return false; + return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), 32, &pubkey); +} + +bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const +{ + secp256k1_xonly_pubkey base_point; + if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &base_point, base.data())) return false; + return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &base_point, hash.begin()); +} + 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 f63e3e0966..68c4480a49 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -9,6 +9,7 @@ #include #include +#include #include