diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b214875661..dbfebe62f8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -70,12 +70,18 @@ jobs: - name: Build binaries run: ./make.sh build + - name: Unit tests + run: ./make.sh test-cpp + # Temp. workaround to save space to prevent GH runners from running out - - name: Cleanup cargo deps - run: rm -rf build/lib/target/debug/deps - - - name: E2E tests - run: ./make.sh test + - name: Cleanup deps + run: | + rm -rf build/lib/target/debug + rm -rf build/depends + find build/src -name "*.o" -type f -delete + + - name: Functional tests + run: ./make.sh test-py rust-tests: runs-on: ubuntu-latest diff --git a/configure.ac b/configure.ac index ee083634bd..01213fe692 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/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py index 6c21f57c50..20518f5145 100755 --- a/contrib/linearize/linearize-data.py +++ b/contrib/linearize/linearize-data.py @@ -311,9 +311,9 @@ def run(self): if "netmagic" not in settings: settings["netmagic"] = "f9beb4d9" if "genesis" not in settings: - settings["genesis"] = ( - "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" - ) + settings[ + "genesis" + ] = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" if "input" not in settings: settings["input"] = "input" if "hashlist" not in settings: 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/logging.cpp b/src/logging.cpp index b346b9c805..0331210793 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -159,6 +159,8 @@ const CLogCategoryDesc LogCategories[] = {BCLog::TOKENSPLIT, "tokensplit"}, {BCLog::RPCCACHE, "rpccache"}, {BCLog::CUSTOMTXBENCH, "customtxbench"}, + {BCLog::CONNECTBLOCK, "connectblock"}, + {BCLog::SIGNTX, "signtx"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; diff --git a/src/logging.h b/src/logging.h index b8ca7f4f90..659dbaf276 100644 --- a/src/logging.h +++ b/src/logging.h @@ -65,8 +65,8 @@ namespace BCLog { TOKENSPLIT = (1ull << 28ull), RPCCACHE = (1ull << 29ull), CUSTOMTXBENCH = (1ull << 30ull), - CONNECT = (1ull << 31ull), - SIGN = (1ull << 32ull), + CONNECTBLOCK = (1ull << 31ull), + SIGNTX = (1ull << 32ull), ALL = ~(0ull), }; 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