mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-19 05:45:05 +01:00
Merge #19953: Implement BIP 340-342 validation (Schnorr/taproot/tapscript)
0e2a5e448f
tests: dumping and minimizing of script assets data (Pieter Wuille)4567ba034c
tests: add generic qa-asset-based script verification unit test (Pieter Wuille)f06e6d0345
tests: functional tests for Schnorr/Taproot/Tapscript (Pieter Wuille)3c226639eb
tests: add BIP340 Schnorr signature support to test framework (Pieter Wuille)206fb180ec
--- [TAPROOT] Tests --- (Pieter Wuille)d7ff237f29
Activate Taproot/Tapscript on regtest (BIP 341, BIP 342) (Pieter Wuille)e9a021d7e6
Make Taproot spends standard + policy limits (Pieter Wuille)865d2c37e2
--- [TAPROOT] Regtest activation and policy --- (Pieter Wuille)72422ce396
Implement Tapscript script validation rules (BIP 342) (Johnson Lau)330de894a9
Use ScriptExecutionData to pass through annex hash (Pieter Wuille)8bbed4b7ac
Implement Taproot validation (BIP 341) (Pieter Wuille)0664f5fe1f
Support for Schnorr signatures and integration in SignatureCheckers (BIP 340) (Pieter Wuille)5de246ca81
Implement Taproot signature hashing (BIP 341) (Johnson Lau)9eb590894f
Add TaggedHash function (BIP 340) (Pieter Wuille)450d2b2371
--- [TAPROOT] BIP340/341/342 consensus rules --- (Pieter Wuille)5d62e3a68b
refactor: keep spent outputs in PrecomputedTransactionData (Pieter Wuille)8bd2b4e784
refactor: rename scriptPubKey in VerifyWitnessProgram to exec_script (Pieter Wuille)107b57df9f
scripted-diff: put ECDSA in name of signature functions (Pieter Wuille)f8c099e220
--- [TAPROOT] Refactors --- (Pieter Wuille) Pull request description: This is an implementation of the Schnorr/taproot consensus rules proposed by BIPs [340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki), [341](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki), and [342](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). See the list of commits [below](https://github.com/bitcoin/bitcoin/pull/19953#issuecomment-691815830). 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 https://github.com/bitcoin/bitcoin/pull/17977 (see discussion following [this comment](https://github.com/bitcoin/bitcoin/pull/17977#issuecomment-682285983)), and will have further changes squashed/rebased. The history of this PR can be found in #19997. ACKs for top commit: instagibbs: reACK0e2a5e448f
benthecarman: reACK0e2a5e4
kallewoof: reACK0e2a5e448f
jonasnick: ACK0e2a5e448f
almost only looked at bip340/libsecp related code jonatack: ACK0e2a5e448f
modulo the last four commits (tests) that I plan to finish reviewing tomorrow fjahr: reACK0e2a5e448f
achow101: ACK0e2a5e448f
Tree-SHA512: 1b00314450a2938a22bccbb4e177230cf08bd365d72055f9d526891f334b364c997e260c10bc19ca78440b6767712c9feea7faad9a1045dd51a5b96f7ca8146e
This commit is contained in:
commit
3caee16946
@ -12,7 +12,7 @@
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>ENABLE_MODULE_ECDH;ENABLE_MODULE_RECOVERY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>ENABLE_MODULE_ECDH;ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\..\src\secp256k1;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
@ -81,11 +81,10 @@ else
|
||||
fi
|
||||
|
||||
if [ ! -d ${DIR_QA_ASSETS} ]; then
|
||||
if [ "$RUN_FUZZ_TESTS" = "true" ]; then
|
||||
DOCKER_EXEC git clone https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
|
||||
fi
|
||||
DOCKER_EXEC git clone --depth=1 https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
|
||||
fi
|
||||
export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/
|
||||
export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/
|
||||
|
||||
DOCKER_EXEC mkdir -p "${BASE_SCRATCH_DIR}/sanitizer-output/"
|
||||
|
||||
|
@ -23,13 +23,13 @@ fi
|
||||
|
||||
if [ "$RUN_UNIT_TESTS" = "true" ]; then
|
||||
BEGIN_FOLD unit-tests
|
||||
DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib make $MAKEJOBS check VERBOSE=1
|
||||
DOCKER_EXEC DIR_UNIT_TEST_DATA=${DIR_UNIT_TEST_DATA} LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib make $MAKEJOBS check VERBOSE=1
|
||||
END_FOLD
|
||||
fi
|
||||
|
||||
if [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then
|
||||
BEGIN_FOLD unit-tests-seq
|
||||
DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib "${BASE_BUILD_DIR}/bitcoin-*/src/test/test_bitcoin*" --catch_system_errors=no -l test_suite
|
||||
DOCKER_EXEC DIR_UNIT_TEST_DATA=${DIR_UNIT_TEST_DATA} LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib "${BASE_BUILD_DIR}/bitcoin-*/src/test/test_bitcoin*" --catch_system_errors=no -l test_suite
|
||||
END_FOLD
|
||||
fi
|
||||
|
||||
|
@ -1700,7 +1700,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"
|
||||
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --enable-module-schnorrsig --enable-experimental"
|
||||
AC_CONFIG_SUBDIRS([src/secp256k1])
|
||||
|
||||
AC_OUTPUT
|
||||
|
@ -129,6 +129,7 @@ FUZZ_TARGETS = \
|
||||
test/fuzz/script_deserialize \
|
||||
test/fuzz/script_flags \
|
||||
test/fuzz/script_interpreter \
|
||||
test/fuzz/script_assets_test_minimizer \
|
||||
test/fuzz/script_ops \
|
||||
test/fuzz/script_sigcache \
|
||||
test/fuzz/script_sign \
|
||||
@ -1083,6 +1084,12 @@ test_fuzz_script_interpreter_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
||||
test_fuzz_script_interpreter_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON)
|
||||
test_fuzz_script_interpreter_SOURCES = test/fuzz/script_interpreter.cpp
|
||||
|
||||
test_fuzz_script_assets_test_minimizer_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
test_fuzz_script_assets_test_minimizer_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
test_fuzz_script_assets_test_minimizer_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
||||
test_fuzz_script_assets_test_minimizer_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
test_fuzz_script_assets_test_minimizer_SOURCES = test/fuzz/script_assets_test_minimizer.cpp
|
||||
|
||||
test_fuzz_script_ops_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
test_fuzz_script_ops_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
test_fuzz_script_ops_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
||||
|
@ -86,6 +86,11 @@ public:
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008
|
||||
|
||||
// Deployment of Taproot (BIPs 340-342)
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = 1199145601; // January 1, 2008
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1230767999; // December 31, 2008
|
||||
|
||||
// The best chain should have at least this much work.
|
||||
consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000e1ab5ec9348e9f4b8eb8154");
|
||||
|
||||
@ -197,6 +202,11 @@ public:
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008
|
||||
|
||||
// Deployment of Taproot (BIPs 340-342)
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = 1199145601; // January 1, 2008
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1230767999; // December 31, 2008
|
||||
|
||||
// The best chain should have at least this much work.
|
||||
consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000000001495c1d5a01e2af8a23");
|
||||
|
||||
@ -380,6 +390,9 @@ public:
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
|
||||
|
||||
// The best chain should have at least this much work.
|
||||
consensus.nMinimumChainWork = uint256S("0x00");
|
||||
|
@ -14,6 +14,7 @@ namespace Consensus {
|
||||
enum DeploymentPos
|
||||
{
|
||||
DEPLOYMENT_TESTDUMMY,
|
||||
DEPLOYMENT_TAPROOT, // Deployment of Schnorr/Taproot (BIPs 340-342)
|
||||
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in versionbits.cpp
|
||||
MAX_VERSION_BITS_DEPLOYMENTS
|
||||
};
|
||||
|
10
src/hash.cpp
10
src/hash.cpp
@ -6,6 +6,7 @@
|
||||
#include <crypto/common.h>
|
||||
#include <crypto/hmac_sha512.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
inline uint32_t ROTL32(uint32_t x, int8_t r)
|
||||
{
|
||||
@ -84,3 +85,12 @@ uint256 SHA256Uint256(const uint256& input)
|
||||
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;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <uint256.h>
|
||||
#include <version.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
typedef uint256 ChainCode;
|
||||
@ -202,4 +203,12 @@ unsigned int MurmurHash3(unsigned int nHashSeed, Span<const unsigned char> vData
|
||||
|
||||
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 // BITCOIN_HASH_H
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#include <consensus/validation.h>
|
||||
#include <coins.h>
|
||||
|
||||
#include <span.h>
|
||||
|
||||
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
|
||||
{
|
||||
@ -206,6 +206,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
||||
// get the scriptPubKey corresponding to this input:
|
||||
CScript prevScript = prev.scriptPubKey;
|
||||
|
||||
bool p2sh = false;
|
||||
if (prevScript.IsPayToScriptHash()) {
|
||||
std::vector <std::vector<unsigned char> > stack;
|
||||
// If the scriptPubKey is P2SH, we try to extract the redeemScript casually by converting the scriptSig
|
||||
@ -216,6 +217,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;
|
||||
@ -237,6 +239,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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_recovery.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -166,6 +167,27 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
|
||||
return 1;
|
||||
}
|
||||
|
||||
XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
|
||||
{
|
||||
assert(bytes.size() == 32);
|
||||
std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
|
||||
}
|
||||
|
||||
bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> 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(), &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<unsigned char>& vchSig) const {
|
||||
if (!IsValid())
|
||||
return false;
|
||||
|
24
src/pubkey.h
24
src/pubkey.h
@ -9,6 +9,7 @@
|
||||
|
||||
#include <hash.h>
|
||||
#include <serialize.h>
|
||||
#include <span.h>
|
||||
#include <uint256.h>
|
||||
|
||||
#include <stdexcept>
|
||||
@ -169,7 +170,7 @@ public:
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
@ -206,6 +207,27 @@ public:
|
||||
bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
|
||||
};
|
||||
|
||||
class XOnlyPubKey
|
||||
{
|
||||
private:
|
||||
uint256 m_keydata;
|
||||
|
||||
public:
|
||||
/** Construct an x-only pubkey from exactly 32 bytes. */
|
||||
XOnlyPubKey(Span<const unsigned char> bytes);
|
||||
|
||||
/** Verify a Schnorr signature against this public key.
|
||||
*
|
||||
* sigbytes must be exactly 64 bytes.
|
||||
*/
|
||||
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
|
||||
bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const;
|
||||
|
||||
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
|
||||
const unsigned char* data() const { return m_keydata.begin(); }
|
||||
size_t size() const { return m_keydata.size(); }
|
||||
};
|
||||
|
||||
struct CExtPubKey {
|
||||
unsigned char nDepth;
|
||||
unsigned char vchFingerprint[4];
|
||||
|
@ -1354,6 +1354,7 @@ RPCHelpMan getblockchaininfo()
|
||||
BuriedForkDescPushBack(softforks, "csv", consensusParams.CSVHeight);
|
||||
BuriedForkDescPushBack(softforks, "segwit", consensusParams.SegwitHeight);
|
||||
BIP9SoftForkDescPushBack(softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY);
|
||||
BIP9SoftForkDescPushBack(softforks, "taproot", consensusParams, Consensus::DEPLOYMENT_TAPROOT);
|
||||
obj.pushKV("softforks", softforks);
|
||||
|
||||
obj.pushKV("warnings", GetWarnings(false).original);
|
||||
|
@ -342,13 +342,10 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
/** Helper for OP_CHECKSIG and OP_CHECKSIGVERIFY
|
||||
*
|
||||
* A return value of false means the script fails entirely. When true is returned, the
|
||||
* fSuccess variable indicates whether the signature check itself succeeded.
|
||||
*/
|
||||
static bool EvalChecksig(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess)
|
||||
static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess)
|
||||
{
|
||||
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0);
|
||||
|
||||
// Subset of script starting at the most recent codeseparator
|
||||
CScript scriptCode(pbegincodehash, pend);
|
||||
|
||||
@ -363,7 +360,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);
|
||||
@ -371,7 +368,67 @@ static bool EvalChecksig(const valtype& vchSig, const valtype& vchPubKey, CScrip
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
|
||||
static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPSCRIPT);
|
||||
|
||||
/*
|
||||
* The following validation sequence is consensus critical. Please note how --
|
||||
* upgradable public key versions precede other rules;
|
||||
* the script execution fails when using empty signature with invalid public key;
|
||||
* the script execution fails when using non-empty invalid signature.
|
||||
*/
|
||||
success = !sig.empty();
|
||||
if (success) {
|
||||
// Implement the sigops/witnesssize ratio test.
|
||||
// Passing with an upgradable public key version is also counted.
|
||||
assert(execdata.m_validation_weight_left_init);
|
||||
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
|
||||
if (execdata.m_validation_weight_left < 0) {
|
||||
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
|
||||
}
|
||||
}
|
||||
if (pubkey.size() == 0) {
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
} else if (pubkey.size() == 32) {
|
||||
if (success && !checker.CheckSchnorrSignature(sig, pubkey, sigversion, execdata, serror)) {
|
||||
return false; // serror is set
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* New public key version softforks should be defined before this `else` block.
|
||||
* Generally, the new code should not do anything but failing the script execution. To avoid
|
||||
* consensus bugs, it should not modify any existing values (including `success`).
|
||||
*/
|
||||
if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Helper for OP_CHECKSIG, OP_CHECKSIGVERIFY, and (in Tapscript) OP_CHECKSIGADD.
|
||||
*
|
||||
* A return value of false means the script fails entirely. When true is returned, the
|
||||
* success variable indicates whether the signature check itself succeeded.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
switch (sigversion) {
|
||||
case SigVersion::BASE:
|
||||
case SigVersion::WITNESS_V0:
|
||||
return EvalChecksigPreTapscript(sig, pubkey, pbegincodehash, pend, flags, checker, sigversion, serror, success);
|
||||
case SigVersion::TAPSCRIPT:
|
||||
return EvalChecksigTapscript(sig, pubkey, execdata, flags, checker, sigversion, serror, success);
|
||||
case SigVersion::TAPROOT:
|
||||
// Key path spending in Taproot has no script, so this is unreachable.
|
||||
break;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
|
||||
{
|
||||
static const CScriptNum bnZero(0);
|
||||
static const CScriptNum bnOne(1);
|
||||
@ -381,6 +438,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
// static const valtype vchZero(0);
|
||||
static const valtype vchTrue(1, 1);
|
||||
|
||||
// sigversion cannot be TAPROOT here, as it admits no script execution.
|
||||
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT);
|
||||
|
||||
CScript::const_iterator pc = script.begin();
|
||||
CScript::const_iterator pend = script.end();
|
||||
CScript::const_iterator pbegincodehash = script.begin();
|
||||
@ -389,15 +449,18 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
ConditionStack vfExec;
|
||||
std::vector<valtype> altstack;
|
||||
set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);
|
||||
if (script.size() > MAX_SCRIPT_SIZE)
|
||||
if ((sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) && script.size() > MAX_SCRIPT_SIZE) {
|
||||
return set_error(serror, SCRIPT_ERR_SCRIPT_SIZE);
|
||||
}
|
||||
int nOpCount = 0;
|
||||
bool fRequireMinimal = (flags & SCRIPT_VERIFY_MINIMALDATA) != 0;
|
||||
uint32_t opcode_pos = 0;
|
||||
execdata.m_codeseparator_pos = 0xFFFFFFFFUL;
|
||||
execdata.m_codeseparator_pos_init = true;
|
||||
|
||||
try
|
||||
{
|
||||
while (pc < pend)
|
||||
{
|
||||
for (; pc < pend; ++opcode_pos) {
|
||||
bool fExec = vfExec.all_true();
|
||||
|
||||
//
|
||||
@ -408,9 +471,12 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
if (vchPushValue.size() > MAX_SCRIPT_ELEMENT_SIZE)
|
||||
return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
|
||||
|
||||
// Note how OP_RESERVED does not count towards the opcode limit.
|
||||
if (opcode > OP_16 && ++nOpCount > MAX_OPS_PER_SCRIPT)
|
||||
return set_error(serror, SCRIPT_ERR_OP_COUNT);
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) {
|
||||
// Note how OP_RESERVED does not count towards the opcode limit.
|
||||
if (opcode > OP_16 && ++nOpCount > MAX_OPS_PER_SCRIPT) {
|
||||
return set_error(serror, SCRIPT_ERR_OP_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
if (opcode == OP_CAT ||
|
||||
opcode == OP_SUBSTR ||
|
||||
@ -568,6 +634,15 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_UNBALANCED_CONDITIONAL);
|
||||
valtype& vch = stacktop(-1);
|
||||
// Tapscript requires minimal IF/NOTIF inputs as a consensus rule.
|
||||
if (sigversion == SigVersion::TAPSCRIPT) {
|
||||
// The input argument to the OP_IF and OP_NOTIF opcodes must be either
|
||||
// exactly 0 (the empty vector) or exactly 1 (the one-byte vector with value 1).
|
||||
if (vch.size() > 1 || (vch.size() == 1 && vch[0] != 1)) {
|
||||
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_MINIMALIF);
|
||||
}
|
||||
}
|
||||
// Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF.
|
||||
if (sigversion == SigVersion::WITNESS_V0 && (flags & SCRIPT_VERIFY_MINIMALIF)) {
|
||||
if (vch.size() > 1)
|
||||
return set_error(serror, SCRIPT_ERR_MINIMALIF);
|
||||
@ -1001,6 +1076,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
|
||||
// Hash starts after the code separator
|
||||
pbegincodehash = pc;
|
||||
execdata.m_codeseparator_pos = opcode_pos;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -1015,7 +1091,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
valtype& vchPubKey = stacktop(-1);
|
||||
|
||||
bool fSuccess = true;
|
||||
if (!EvalChecksig(vchSig, vchPubKey, pbegincodehash, pend, flags, checker, sigversion, serror, fSuccess)) return false;
|
||||
if (!EvalChecksig(vchSig, vchPubKey, pbegincodehash, pend, execdata, flags, checker, sigversion, serror, fSuccess)) return false;
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
stack.push_back(fSuccess ? vchTrue : vchFalse);
|
||||
@ -1029,9 +1105,32 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_CHECKSIGADD:
|
||||
{
|
||||
// OP_CHECKSIGADD is only available in Tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
// (sig num pubkey -- num)
|
||||
if (stack.size() < 3) return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
const valtype& sig = stacktop(-3);
|
||||
const CScriptNum num(stacktop(-2), fRequireMinimal);
|
||||
const valtype& pubkey = stacktop(-1);
|
||||
|
||||
bool success = true;
|
||||
if (!EvalChecksig(sig, pubkey, pbegincodehash, pend, execdata, flags, checker, sigversion, serror, success)) return false;
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
stack.push_back((num + (success ? 1 : 0)).getvch());
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_CHECKMULTISIG:
|
||||
case OP_CHECKMULTISIGVERIFY:
|
||||
{
|
||||
if (sigversion == SigVersion::TAPSCRIPT) return set_error(serror, SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG);
|
||||
|
||||
// ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool)
|
||||
|
||||
int i = 1;
|
||||
@ -1089,7 +1188,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
}
|
||||
|
||||
// Check signature
|
||||
bool fOk = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion);
|
||||
bool fOk = checker.CheckECDSASignature(vchSig, vchPubKey, scriptCode, sigversion);
|
||||
|
||||
if (fOk) {
|
||||
isig++;
|
||||
@ -1159,6 +1258,12 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
||||
return set_success(serror);
|
||||
}
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
|
||||
{
|
||||
ScriptExecutionData execdata;
|
||||
return EvalScript(stack, script, flags, checker, sigversion, execdata, serror);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@ -1291,35 +1396,183 @@ 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<CTxOut>& 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<CTxOut>& outputs_spent)
|
||||
{
|
||||
CHashWriter ss(SER_GETHASH, 0);
|
||||
for (const auto& txout : outputs_spent) {
|
||||
ss << txout.scriptPubKey;
|
||||
}
|
||||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
template <class T>
|
||||
void PrecomputedTransactionData::Init(const T& txTo)
|
||||
void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs)
|
||||
{
|
||||
assert(!m_ready);
|
||||
assert(!m_spent_outputs_ready);
|
||||
|
||||
// Cache is calculated only for transactions with witness
|
||||
if (txTo.HasWitness()) {
|
||||
hashPrevouts = SHA256Uint256(GetPrevoutsSHA256(txTo));
|
||||
hashSequence = SHA256Uint256(GetSequencesSHA256(txTo));
|
||||
hashOutputs = SHA256Uint256(GetOutputsSHA256(txTo));
|
||||
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;
|
||||
// Determine which precomputation-impacting features this transaction uses.
|
||||
bool uses_bip143_segwit = false;
|
||||
bool uses_bip341_taproot = false;
|
||||
for (size_t inpos = 0; inpos < txTo.vin.size(); ++inpos) {
|
||||
if (!txTo.vin[inpos].scriptWitness.IsNull()) {
|
||||
if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + WITNESS_V1_TAPROOT_SIZE &&
|
||||
m_spent_outputs[inpos].scriptPubKey[0] == OP_1) {
|
||||
// Treat every witness-bearing spend with 34-byte scriptPubKey that starts with OP_1 as a Taproot
|
||||
// spend. This only works if spent_outputs was provided as well, but if it wasn't, actual validation
|
||||
// will fail anyway. Note that this branch may trigger for scriptPubKeys that aren't actually segwit
|
||||
// but in that case validation will fail as SCRIPT_ERR_WITNESS_UNEXPECTED anyway.
|
||||
uses_bip341_taproot = true;
|
||||
} else {
|
||||
// Treat every spend that's not known to native witness v1 as a Witness v0 spend. This branch may
|
||||
// also be taken for unknown witness versions, but it is harmless, and being precise would require
|
||||
// P2SH evaluation to find the redeemScript.
|
||||
uses_bip143_segwit = true;
|
||||
}
|
||||
}
|
||||
if (uses_bip341_taproot && uses_bip143_segwit) break; // No need to scan further if we already need all.
|
||||
}
|
||||
|
||||
if (uses_bip143_segwit || uses_bip341_taproot) {
|
||||
// Computations shared between both sighash schemes.
|
||||
m_prevouts_single_hash = GetPrevoutsSHA256(txTo);
|
||||
m_sequences_single_hash = GetSequencesSHA256(txTo);
|
||||
m_outputs_single_hash = GetOutputsSHA256(txTo);
|
||||
}
|
||||
if (uses_bip143_segwit) {
|
||||
hashPrevouts = SHA256Uint256(m_prevouts_single_hash);
|
||||
hashSequence = SHA256Uint256(m_sequences_single_hash);
|
||||
hashOutputs = SHA256Uint256(m_outputs_single_hash);
|
||||
m_bip143_segwit_ready = true;
|
||||
}
|
||||
if (uses_bip341_taproot) {
|
||||
m_spent_amounts_single_hash = GetSpentAmountsSHA256(m_spent_outputs);
|
||||
m_spent_scripts_single_hash = GetSpentScriptsSHA256(m_spent_outputs);
|
||||
m_bip341_taproot_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo)
|
||||
{
|
||||
Init(txTo);
|
||||
Init(txTo, {});
|
||||
}
|
||||
|
||||
// explicit instantiation
|
||||
template void PrecomputedTransactionData::Init(const CTransaction& txTo);
|
||||
template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo);
|
||||
template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs);
|
||||
template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs);
|
||||
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
|
||||
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
|
||||
|
||||
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
|
||||
static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
|
||||
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
|
||||
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
|
||||
|
||||
template<typename T>
|
||||
bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache)
|
||||
{
|
||||
uint8_t ext_flag, key_version;
|
||||
switch (sigversion) {
|
||||
case SigVersion::TAPROOT:
|
||||
ext_flag = 0;
|
||||
// key_version is not used and left uninitialized.
|
||||
break;
|
||||
case SigVersion::TAPSCRIPT:
|
||||
ext_flag = 1;
|
||||
// key_version must be 0 for now, representing the current version of
|
||||
// 32-byte public keys in the tapscript signature opcode execution.
|
||||
// An upgradable public key version (with a size not 32-byte) may
|
||||
// request a different key_version with a new sigversion.
|
||||
key_version = 0;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
assert(in_pos < tx_to.vin.size());
|
||||
assert(cache.m_bip341_taproot_ready && 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 input/prevout being spent
|
||||
assert(execdata.m_annex_init);
|
||||
const bool have_annex = execdata.m_annex_present;
|
||||
const uint8_t spend_type = (ext_flag << 1) + (have_annex ? 1 : 0); // The low bit indicates whether an annex is present.
|
||||
ss << spend_type;
|
||||
if (input_type == SIGHASH_ANYONECANPAY) {
|
||||
ss << tx_to.vin[in_pos].prevout;
|
||||
ss << cache.m_spent_outputs[in_pos];
|
||||
ss << tx_to.vin[in_pos].nSequence;
|
||||
} else {
|
||||
ss << in_pos;
|
||||
}
|
||||
if (have_annex) {
|
||||
ss << execdata.m_annex_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();
|
||||
}
|
||||
|
||||
// Additional data for BIP 342 signatures
|
||||
if (sigversion == SigVersion::TAPSCRIPT) {
|
||||
assert(execdata.m_tapleaf_hash_init);
|
||||
ss << execdata.m_tapleaf_hash;
|
||||
ss << key_version;
|
||||
assert(execdata.m_codeseparator_pos_init);
|
||||
ss << execdata.m_codeseparator_pos;
|
||||
}
|
||||
|
||||
hash_out = ss.GetSHA256();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
|
||||
{
|
||||
@ -1329,7 +1582,7 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
|
||||
uint256 hashPrevouts;
|
||||
uint256 hashSequence;
|
||||
uint256 hashOutputs;
|
||||
const bool cacheready = cache && cache->m_ready;
|
||||
const bool cacheready = cache && cache->m_bip143_segwit_ready;
|
||||
|
||||
if (!(nHashType & SIGHASH_ANYONECANPAY)) {
|
||||
hashPrevouts = cacheready ? cache->hashPrevouts : SHA256Uint256(GetPrevoutsSHA256(txTo));
|
||||
@ -1389,13 +1642,19 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool GenericTransactionSignatureChecker<T>::VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const
|
||||
bool GenericTransactionSignatureChecker<T>::VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const
|
||||
{
|
||||
return pubkey.Verify(sighash, vchSig);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool GenericTransactionSignatureChecker<T>::CheckSig(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
|
||||
bool GenericTransactionSignatureChecker<T>::VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const
|
||||
{
|
||||
return pubkey.VerifySchnorr(sighash, sig);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
|
||||
{
|
||||
CPubKey pubkey(vchPubKey);
|
||||
if (!pubkey.IsValid())
|
||||
@ -1410,12 +1669,40 @@ bool GenericTransactionSignatureChecker<T>::CheckSig(const std::vector<unsigned
|
||||
|
||||
uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata);
|
||||
|
||||
if (!VerifySignature(vchSig, pubkey, sighash))
|
||||
if (!VerifyECDSASignature(vchSig, pubkey, sighash))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey_in, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror) const
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
|
||||
// Schnorr signatures have 32-byte public keys. The caller is responsible for enforcing this.
|
||||
assert(pubkey_in.size() == 32);
|
||||
// Note that in Tapscript evaluation, empty signatures are treated specially (invalid signature that does not
|
||||
// abort script execution). This is implemented in EvalChecksigTapscript, which won't invoke
|
||||
// CheckSchnorrSignature in that case. In other contexts, they are invalid like every other signature with
|
||||
// size different from 64 or 65.
|
||||
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, execdata, *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 <class T>
|
||||
bool GenericTransactionSignatureChecker<T>::CheckLockTime(const CScriptNum& nLockTime) const
|
||||
{
|
||||
@ -1504,17 +1791,39 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
|
||||
template class GenericTransactionSignatureChecker<CTransaction>;
|
||||
template class GenericTransactionSignatureChecker<CMutableTransaction>;
|
||||
|
||||
static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CScript& scriptPubKey, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptError* serror)
|
||||
static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CScript& scriptPubKey, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror)
|
||||
{
|
||||
std::vector<valtype> stack{stack_span.begin(), stack_span.end()};
|
||||
|
||||
if (sigversion == SigVersion::TAPSCRIPT) {
|
||||
// OP_SUCCESSx processing overrides everything, including stack element size limits
|
||||
CScript::const_iterator pc = scriptPubKey.begin();
|
||||
while (pc < scriptPubKey.end()) {
|
||||
opcodetype opcode;
|
||||
if (!scriptPubKey.GetOp(pc, opcode)) {
|
||||
// Note how this condition would not be reached if an unknown OP_SUCCESSx was found
|
||||
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
}
|
||||
// New opcodes will be listed here. May use a different sigversion to modify existing opcodes.
|
||||
if (IsOpSuccess(opcode)) {
|
||||
if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS);
|
||||
}
|
||||
return set_success(serror);
|
||||
}
|
||||
}
|
||||
|
||||
// Tapscript enforces initial stack size limits (altstack is empty here)
|
||||
if (stack.size() > MAX_STACK_SIZE) return set_error(serror, SCRIPT_ERR_STACK_SIZE);
|
||||
}
|
||||
|
||||
// Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack
|
||||
for (const valtype& elem : stack) {
|
||||
if (elem.size() > MAX_SCRIPT_ELEMENT_SIZE) return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
|
||||
}
|
||||
|
||||
// Run the script interpreter.
|
||||
if (!EvalScript(stack, scriptPubKey, flags, checker, sigversion, serror)) return false;
|
||||
if (!EvalScript(stack, scriptPubKey, flags, checker, sigversion, execdata, serror)) return false;
|
||||
|
||||
// Scripts inside witness implicitly require cleanstack behaviour
|
||||
if (stack.size() != 1) return set_error(serror, SCRIPT_ERR_CLEANSTACK);
|
||||
@ -1522,40 +1831,104 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
|
||||
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash)
|
||||
{
|
||||
CScript scriptPubKey;
|
||||
const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
|
||||
const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
|
||||
const XOnlyPubKey q{uint256(program)};
|
||||
tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
|
||||
uint256 k = tapleaf_hash;
|
||||
for (int i = 0; i < path_len; ++i) {
|
||||
CHashWriter ss_branch{HASHER_TAPBRANCH};
|
||||
Span<const unsigned char> node(control.data() + TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE);
|
||||
if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) {
|
||||
ss_branch << k << node;
|
||||
} else {
|
||||
ss_branch << node << k;
|
||||
}
|
||||
k = ss_branch.GetSHA256();
|
||||
}
|
||||
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
|
||||
return q.CheckPayToContract(p, k, control[0] & 1);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH; leaf script in P2TR)
|
||||
Span<const valtype> stack{witness.stack};
|
||||
ScriptExecutionData execdata;
|
||||
|
||||
if (witversion == 0) {
|
||||
if (program.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
|
||||
// Version 0 segregated witness program: SHA256(CScript) inside the program, CScript + inputs in witness
|
||||
// BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
|
||||
if (stack.size() == 0) {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
|
||||
}
|
||||
const valtype& script_bytes = SpanPopBack(stack);
|
||||
scriptPubKey = CScript(script_bytes.begin(), script_bytes.end());
|
||||
uint256 hashScriptPubKey;
|
||||
CSHA256().Write(&scriptPubKey[0], scriptPubKey.size()).Finalize(hashScriptPubKey.begin());
|
||||
if (memcmp(hashScriptPubKey.begin(), program.data(), 32)) {
|
||||
exec_script = CScript(script_bytes.begin(), script_bytes.end());
|
||||
uint256 hash_exec_script;
|
||||
CSHA256().Write(&exec_script[0], exec_script.size()).Finalize(hash_exec_script.begin());
|
||||
if (memcmp(hash_exec_script.begin(), program.data(), 32)) {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
|
||||
}
|
||||
return ExecuteWitnessScript(stack, scriptPubKey, flags, SigVersion::WITNESS_V0, checker, serror);
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror);
|
||||
} else if (program.size() == WITNESS_V0_KEYHASH_SIZE) {
|
||||
// Special case for pay-to-pubkeyhash; signature + pubkey in witness
|
||||
// BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
|
||||
if (stack.size() != 2) {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); // 2 items in witness
|
||||
}
|
||||
scriptPubKey << OP_DUP << OP_HASH160 << program << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||
return ExecuteWitnessScript(stack, scriptPubKey, flags, SigVersion::WITNESS_V0, checker, serror);
|
||||
exec_script << OP_DUP << OP_HASH160 << program << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror);
|
||||
} else {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
|
||||
}
|
||||
} else if (witversion == 1 && program.size() == WITNESS_V1_TAPROOT_SIZE && !is_p2sh) {
|
||||
// BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
|
||||
if (!(flags & SCRIPT_VERIFY_TAPROOT)) return set_success(serror);
|
||||
if (stack.size() == 0) return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
|
||||
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
|
||||
// Drop annex (this is non-standard; see IsWitnessStandard)
|
||||
const valtype& annex = SpanPopBack(stack);
|
||||
execdata.m_annex_hash = (CHashWriter(SER_GETHASH, 0) << annex).GetSHA256();
|
||||
execdata.m_annex_present = true;
|
||||
} else {
|
||||
execdata.m_annex_present = false;
|
||||
}
|
||||
execdata.m_annex_init = true;
|
||||
if (stack.size() == 1) {
|
||||
// Key path spending (stack size is 1 after removing optional annex)
|
||||
if (!checker.CheckSchnorrSignature(stack.front(), program, SigVersion::TAPROOT, execdata, serror)) {
|
||||
return false; // serror is set
|
||||
}
|
||||
return set_success(serror);
|
||||
} else {
|
||||
// Script path spending (stack size is >1 after removing optional annex)
|
||||
const valtype& control = SpanPopBack(stack);
|
||||
const valtype& script_bytes = SpanPopBack(stack);
|
||||
exec_script = CScript(script_bytes.begin(), script_bytes.end());
|
||||
if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) {
|
||||
return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
|
||||
}
|
||||
if (!VerifyTaprootCommitment(control, program, exec_script, execdata.m_tapleaf_hash)) {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
|
||||
}
|
||||
execdata.m_tapleaf_hash_init = true;
|
||||
if ((control[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
|
||||
// Tapscript (leaf version 0xc0)
|
||||
execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack, PROTOCOL_VERSION) + VALIDATION_WEIGHT_OFFSET;
|
||||
execdata.m_validation_weight_left_init = true;
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror);
|
||||
}
|
||||
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
|
||||
}
|
||||
return set_success(serror);
|
||||
}
|
||||
} else {
|
||||
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
|
||||
}
|
||||
// Higher version witness scripts return true for future softfork compatibility
|
||||
// Other version/size/p2sh combinations return true for future softfork compatibility
|
||||
return true;
|
||||
}
|
||||
// There is intentionally no return statement here, to be able to use "control reaches end of non-void function" warnings to detect gaps in the logic above.
|
||||
@ -1601,7 +1974,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
|
||||
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
|
||||
}
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ false)) {
|
||||
return false;
|
||||
}
|
||||
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
|
||||
@ -1646,7 +2019,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
|
||||
// reintroduce malleability.
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
|
||||
}
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ true)) {
|
||||
return false;
|
||||
}
|
||||
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
|
||||
|
@ -7,14 +7,17 @@
|
||||
#define BITCOIN_SCRIPT_INTERPRETER_H
|
||||
|
||||
#include <script/script_error.h>
|
||||
#include <span.h>
|
||||
#include <primitives/transaction.h>
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
class CPubKey;
|
||||
class XOnlyPubKey;
|
||||
class CScript;
|
||||
class CTransaction;
|
||||
class CTxOut;
|
||||
class uint256;
|
||||
|
||||
/** Signature hash types/flags */
|
||||
@ -24,6 +27,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.
|
||||
@ -79,6 +86,8 @@ enum
|
||||
// "Exactly one stack element must remain, and when interpreted as a boolean, it must be true".
|
||||
// (BIP62 rule 6)
|
||||
// Note: CLEANSTACK should never be used without P2SH or WITNESS.
|
||||
// Note: WITNESS_V0 and TAPSCRIPT script execution have behavior similar to CLEANSTACK as part of their
|
||||
// consensus rules. It is automatic there and does not need this flag.
|
||||
SCRIPT_VERIFY_CLEANSTACK = (1U << 8),
|
||||
|
||||
// Verify CHECKLOCKTIMEVERIFY
|
||||
@ -101,6 +110,8 @@ enum
|
||||
|
||||
// Segwit script only: Require the argument of OP_IF/NOTIF to be exactly 0x01 or empty vector
|
||||
//
|
||||
// Note: TAPSCRIPT script execution has behavior similar to MINIMALIF as part of its consensus
|
||||
// rules. It is automatic there and does not depend on this flag.
|
||||
SCRIPT_VERIFY_MINIMALIF = (1U << 13),
|
||||
|
||||
// Signature(s) must be empty vector if a CHECK(MULTI)SIG operation failed
|
||||
@ -114,19 +125,49 @@ enum
|
||||
// Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts
|
||||
//
|
||||
SCRIPT_VERIFY_CONST_SCRIPTCODE = (1U << 16),
|
||||
|
||||
// Taproot/Tapscript validation (BIPs 341 & 342)
|
||||
//
|
||||
SCRIPT_VERIFY_TAPROOT = (1U << 17),
|
||||
|
||||
// Making unknown Taproot leaf versions non-standard
|
||||
//
|
||||
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = (1U << 18),
|
||||
|
||||
// Making unknown OP_SUCCESS non-standard
|
||||
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = (1U << 19),
|
||||
|
||||
// Making unknown public key versions (in BIP 342 scripts) non-standard
|
||||
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 20),
|
||||
};
|
||||
|
||||
bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror);
|
||||
|
||||
struct PrecomputedTransactionData
|
||||
{
|
||||
// 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;
|
||||
bool m_ready = false;
|
||||
//! Whether the 3 fields above are initialized.
|
||||
bool m_bip143_segwit_ready = false;
|
||||
|
||||
std::vector<CTxOut> m_spent_outputs;
|
||||
//! Whether m_spent_outputs is initialized.
|
||||
bool m_spent_outputs_ready = false;
|
||||
|
||||
PrecomputedTransactionData() = default;
|
||||
|
||||
template <class T>
|
||||
void Init(const T& tx);
|
||||
void Init(const T& tx, std::vector<CTxOut>&& spent_outputs);
|
||||
|
||||
template <class T>
|
||||
explicit PrecomputedTransactionData(const T& tx);
|
||||
@ -134,13 +175,48 @@ struct PrecomputedTransactionData
|
||||
|
||||
enum class SigVersion
|
||||
{
|
||||
BASE = 0,
|
||||
WITNESS_V0 = 1,
|
||||
BASE = 0, //!< Bare scripts and BIP16 P2SH-wrapped redeemscripts
|
||||
WITNESS_V0 = 1, //!< Witness v0 (P2WPKH and P2WSH); see BIP 141
|
||||
TAPROOT = 2, //!< Witness v1 with 32-byte program, not BIP16 P2SH-wrapped, key path spending; see BIP 341
|
||||
TAPSCRIPT = 3, //!< Witness v1 with 32-byte program, not BIP16 P2SH-wrapped, script path spending, leaf version 0xc0; see BIP 342
|
||||
};
|
||||
|
||||
struct ScriptExecutionData
|
||||
{
|
||||
//! Whether m_tapleaf_hash is initialized.
|
||||
bool m_tapleaf_hash_init = false;
|
||||
//! The tapleaf hash.
|
||||
uint256 m_tapleaf_hash;
|
||||
|
||||
//! Whether m_codeseparator_pos is initialized.
|
||||
bool m_codeseparator_pos_init = false;
|
||||
//! Opcode position of the last executed OP_CODESEPARATOR (or 0xFFFFFFFF if none executed).
|
||||
uint32_t m_codeseparator_pos;
|
||||
|
||||
//! Whether m_annex_present and (when needed) m_annex_hash are initialized.
|
||||
bool m_annex_init = false;
|
||||
//! Whether an annex is present.
|
||||
bool m_annex_present;
|
||||
//! Hash of the annex data.
|
||||
uint256 m_annex_hash;
|
||||
|
||||
//! Whether m_validation_weight_left is initialized.
|
||||
bool m_validation_weight_left_init = false;
|
||||
//! How much validation weight is left (decremented for every successful non-empty signature check).
|
||||
int64_t m_validation_weight_left;
|
||||
};
|
||||
|
||||
/** Signature hash sizes */
|
||||
static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32;
|
||||
static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
|
||||
static constexpr size_t WITNESS_V1_TAPROOT_SIZE = 32;
|
||||
|
||||
static constexpr uint8_t TAPROOT_LEAF_MASK = 0xfe;
|
||||
static constexpr uint8_t TAPROOT_LEAF_TAPSCRIPT = 0xc0;
|
||||
static constexpr size_t TAPROOT_CONTROL_BASE_SIZE = 33;
|
||||
static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
|
||||
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
|
||||
static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
|
||||
|
||||
template <class T>
|
||||
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
|
||||
@ -148,7 +224,12 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
|
||||
class BaseSignatureChecker
|
||||
{
|
||||
public:
|
||||
virtual bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
|
||||
virtual bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -176,12 +257,14 @@ private:
|
||||
const PrecomputedTransactionData* txdata;
|
||||
|
||||
protected:
|
||||
virtual bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
||||
virtual bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
||||
virtual bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const;
|
||||
|
||||
public:
|
||||
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
|
||||
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
|
||||
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
|
||||
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
|
||||
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
|
||||
bool CheckLockTime(const CScriptNum& nLockTime) const override;
|
||||
bool CheckSequence(const CScriptNum& nSequence) const override;
|
||||
};
|
||||
@ -189,6 +272,7 @@ public:
|
||||
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
|
||||
using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker<CMutableTransaction>;
|
||||
|
||||
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);
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr);
|
||||
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr);
|
||||
|
||||
|
@ -140,6 +140,9 @@ std::string GetOpName(opcodetype opcode)
|
||||
case OP_NOP9 : return "OP_NOP9";
|
||||
case OP_NOP10 : return "OP_NOP10";
|
||||
|
||||
// Opcode added by BIP 342 (Tapscript)
|
||||
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";
|
||||
|
||||
case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";
|
||||
|
||||
default:
|
||||
@ -328,3 +331,11 @@ bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator en
|
||||
opcodeRet = static_cast<opcodetype>(opcode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsOpSuccess(const opcodetype& opcode)
|
||||
{
|
||||
return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) ||
|
||||
(opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) ||
|
||||
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
|
||||
(opcode >= 187 && opcode <= 254);
|
||||
}
|
||||
|
@ -44,6 +44,17 @@ 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;
|
||||
|
||||
// Validation weight per passing signature (Tapscript only, see BIP 342).
|
||||
static constexpr uint64_t VALIDATION_WEIGHT_PER_SIGOP_PASSED = 50;
|
||||
|
||||
// How much weight budget is added to the witness size (Tapscript only, see BIP 342).
|
||||
static constexpr uint64_t VALIDATION_WEIGHT_OFFSET = 50;
|
||||
|
||||
template <typename T>
|
||||
std::vector<unsigned char> ToByteVector(const T& in)
|
||||
{
|
||||
@ -187,6 +198,9 @@ enum opcodetype
|
||||
OP_NOP9 = 0xb8,
|
||||
OP_NOP10 = 0xb9,
|
||||
|
||||
// Opcode added by BIP 342 (Tapscript)
|
||||
OP_CHECKSIGADD = 0xba,
|
||||
|
||||
OP_INVALIDOPCODE = 0xff,
|
||||
};
|
||||
|
||||
@ -555,4 +569,7 @@ struct CScriptWitness
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
/** Test for OP_SUCCESSx opcodes as defined by BIP342. */
|
||||
bool IsOpSuccess(const opcodetype& opcode);
|
||||
|
||||
#endif // BITCOIN_SCRIPT_SCRIPT_H
|
||||
|
@ -73,6 +73,12 @@ std::string ScriptErrorString(const ScriptError serror)
|
||||
return "NOPx reserved for soft-fork upgrades";
|
||||
case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM:
|
||||
return "Witness version reserved for soft-fork upgrades";
|
||||
case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION:
|
||||
return "Taproot version reserved for soft-fork upgrades";
|
||||
case SCRIPT_ERR_DISCOURAGE_OP_SUCCESS:
|
||||
return "OP_SUCCESSx reserved for soft-fork upgrades";
|
||||
case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE:
|
||||
return "Public key version reserved for soft-fork upgrades";
|
||||
case SCRIPT_ERR_PUBKEYTYPE:
|
||||
return "Public key is neither compressed or uncompressed";
|
||||
case SCRIPT_ERR_CLEANSTACK:
|
||||
@ -91,6 +97,20 @@ std::string ScriptErrorString(const ScriptError serror)
|
||||
return "Witness provided for non-witness script";
|
||||
case SCRIPT_ERR_WITNESS_PUBKEYTYPE:
|
||||
return "Using non-compressed keys in segwit";
|
||||
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_TAPROOT_WRONG_CONTROL_SIZE:
|
||||
return "Invalid Taproot control block size";
|
||||
case SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT:
|
||||
return "Too much signature validation relative to witness weight";
|
||||
case SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG:
|
||||
return "OP_CHECKMULTISIG(VERIFY) is not available in tapscript";
|
||||
case SCRIPT_ERR_TAPSCRIPT_MINIMALIF:
|
||||
return "OP_IF/NOTIF argument must be minimal in tapscript";
|
||||
case SCRIPT_ERR_OP_CODESEPARATOR:
|
||||
return "Using OP_CODESEPARATOR in non-witness script";
|
||||
case SCRIPT_ERR_SIG_FINDANDDELETE:
|
||||
|
@ -56,6 +56,9 @@ typedef enum ScriptError_t
|
||||
/* softfork safeness */
|
||||
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS,
|
||||
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
|
||||
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION,
|
||||
SCRIPT_ERR_DISCOURAGE_OP_SUCCESS,
|
||||
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE,
|
||||
|
||||
/* segregated witness */
|
||||
SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH,
|
||||
@ -66,6 +69,15 @@ typedef enum ScriptError_t
|
||||
SCRIPT_ERR_WITNESS_UNEXPECTED,
|
||||
SCRIPT_ERR_WITNESS_PUBKEYTYPE,
|
||||
|
||||
/* Taproot */
|
||||
SCRIPT_ERR_SCHNORR_SIG_SIZE,
|
||||
SCRIPT_ERR_SCHNORR_SIG_HASHTYPE,
|
||||
SCRIPT_ERR_SCHNORR_SIG,
|
||||
SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE,
|
||||
SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT,
|
||||
SCRIPT_ERR_TAPSCRIPT_CHECKMULTISIG,
|
||||
SCRIPT_ERR_TAPSCRIPT_MINIMALIF,
|
||||
|
||||
/* Constant scriptCode */
|
||||
SCRIPT_ERR_OP_CODESEPARATOR,
|
||||
SCRIPT_ERR_SIG_FINDANDDELETE,
|
||||
|
@ -22,8 +22,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<uint256, SignatureCacheHasher> map_type;
|
||||
map_type setValid;
|
||||
boost::shared_mutex cs_sigcache;
|
||||
@ -34,18 +35,30 @@ public:
|
||||
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
|
||||
ComputeEntry(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& vchSig, const CPubKey& pubkey)
|
||||
ComputeEntryECDSA(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& vchSig, const CPubKey& pubkey)
|
||||
{
|
||||
CSHA256 hasher = m_salted_hasher;
|
||||
CSHA256 hasher = m_salted_hasher_ecdsa;
|
||||
hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(&vchSig[0], vchSig.size()).Finalize(entry.begin());
|
||||
}
|
||||
|
||||
void
|
||||
ComputeEntrySchnorr(uint256& entry, const uint256 &hash, Span<const unsigned char> 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)
|
||||
{
|
||||
@ -85,15 +98,25 @@ void InitSignatureCache()
|
||||
(nElems*sizeof(uint256)) >>20, (nMaxCacheSize*2)>>20, nElems);
|
||||
}
|
||||
|
||||
bool CachingTransactionSignatureChecker::VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const
|
||||
bool CachingTransactionSignatureChecker::VerifyECDSASignature(const std::vector<unsigned char>& 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);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CachingTransactionSignatureChecker::VerifySchnorrSignature(Span<const unsigned char> 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;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#define BITCOIN_SCRIPT_SIGCACHE_H
|
||||
|
||||
#include <script/interpreter.h>
|
||||
#include <span.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
@ -48,7 +49,8 @@ private:
|
||||
public:
|
||||
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
|
||||
|
||||
bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
|
||||
bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
|
||||
bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;
|
||||
};
|
||||
|
||||
void InitSignatureCache();
|
||||
|
@ -111,6 +111,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
|
||||
case TxoutType::NONSTANDARD:
|
||||
case TxoutType::NULL_DATA:
|
||||
case TxoutType::WITNESS_UNKNOWN:
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
return false;
|
||||
case TxoutType::PUBKEY:
|
||||
if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false;
|
||||
@ -260,9 +261,9 @@ private:
|
||||
|
||||
public:
|
||||
SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : sigdata(sigdata), checker(checker) {}
|
||||
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
|
||||
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
|
||||
{
|
||||
if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion)) {
|
||||
if (checker.CheckECDSASignature(scriptSig, vchPubKey, scriptCode, sigversion)) {
|
||||
CPubKey pubkey(vchPubKey);
|
||||
sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig));
|
||||
return true;
|
||||
@ -339,7 +340,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;
|
||||
}
|
||||
@ -400,7 +401,7 @@ class DummySignatureChecker final : public BaseSignatureChecker
|
||||
{
|
||||
public:
|
||||
DummySignatureChecker() {}
|
||||
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; }
|
||||
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; }
|
||||
};
|
||||
const DummySignatureChecker DUMMY_CHECKER;
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <pubkey.h>
|
||||
#include <script/interpreter.h>
|
||||
#include <script/keyorigin.h>
|
||||
#include <span.h>
|
||||
#include <streams.h>
|
||||
|
||||
class CKey;
|
||||
|
@ -55,6 +55,7 @@ std::string GetTxnOutputType(TxoutType t)
|
||||
case TxoutType::NULL_DATA: return "nulldata";
|
||||
case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
|
||||
case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
|
||||
case TxoutType::WITNESS_V1_TAPROOT: return "witness_v1_taproot";
|
||||
case TxoutType::WITNESS_UNKNOWN: return "witness_unknown";
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
assert(false);
|
||||
@ -130,6 +131,11 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
|
||||
vSolutionsRet.push_back(witnessprogram);
|
||||
return TxoutType::WITNESS_V0_SCRIPTHASH;
|
||||
}
|
||||
if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE) {
|
||||
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
|
||||
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||
return TxoutType::WITNESS_V1_TAPROOT;
|
||||
}
|
||||
if (witnessversion != 0) {
|
||||
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
|
||||
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||
@ -203,7 +209,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
|
||||
std::copy(vSolutions[0].begin(), vSolutions[0].end(), hash.begin());
|
||||
addressRet = hash;
|
||||
return true;
|
||||
} else if (whichType == TxoutType::WITNESS_UNKNOWN) {
|
||||
} else if (whichType == TxoutType::WITNESS_UNKNOWN || whichType == TxoutType::WITNESS_V1_TAPROOT) {
|
||||
WitnessUnknown unk;
|
||||
unk.version = vSolutions[0][0];
|
||||
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);
|
||||
|
@ -129,6 +129,7 @@ enum class TxoutType {
|
||||
NULL_DATA, //!< unspendable OP_RETURN script that carries data
|
||||
WITNESS_V0_SCRIPTHASH,
|
||||
WITNESS_V0_KEYHASH,
|
||||
WITNESS_V1_TAPROOT,
|
||||
WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
|
||||
};
|
||||
|
||||
@ -206,7 +207,8 @@ struct WitnessUnknown
|
||||
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH)
|
||||
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH)
|
||||
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH)
|
||||
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W???)
|
||||
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN/WITNESS_V1_TAPROOT destination (P2W???)
|
||||
* (taproot outputs do not require their own type as long as no wallet support exists)
|
||||
* A CTxDestination is the internal data type encoded in a bitcoin address
|
||||
*/
|
||||
typedef boost::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;
|
||||
|
200
src/test/fuzz/script_assets_test_minimizer.cpp
Normal file
200
src/test/fuzz/script_assets_test_minimizer.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <test/fuzz/fuzz.h>
|
||||
|
||||
#include <primitives/transaction.h>
|
||||
#include <pubkey.h>
|
||||
#include <script/interpreter.h>
|
||||
#include <serialize.h>
|
||||
#include <streams.h>
|
||||
#include <univalue.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// This fuzz "test" can be used to minimize test cases for script_assets_test in
|
||||
// src/test/script_tests.cpp. While it written as a fuzz test, and can be used as such,
|
||||
// fuzzing the inputs is unlikely to construct useful test cases.
|
||||
//
|
||||
// Instead, it is primarily intended to be run on a test set that was generated
|
||||
// externally, for example using test/functional/feature_taproot.py's --dumptests mode.
|
||||
// The minimized set can then be concatenated together, surrounded by '[' and ']',
|
||||
// and used as the script_assets_test.json input to the script_assets_test unit test:
|
||||
//
|
||||
// (normal build)
|
||||
// $ mkdir dump
|
||||
// $ for N in $(seq 1 10); do TEST_DUMP_DIR=dump test/functional/feature_taproot --dumptests; done
|
||||
// $ ...
|
||||
//
|
||||
// (fuzz test build)
|
||||
// $ mkdir dump-min
|
||||
// $ ./src/test/fuzz/script_assets_test_minimizer -merge=1 dump-min/ dump/
|
||||
// $ (echo -en '[\n'; cat dump-min/* | head -c -2; echo -en '\n]') >script_assets_test.json
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<unsigned char> CheckedParseHex(const std::string& str)
|
||||
{
|
||||
if (str.size() && !IsHex(str)) throw std::runtime_error("Non-hex input '" + str + "'");
|
||||
return ParseHex(str);
|
||||
}
|
||||
|
||||
CScript ScriptFromHex(const std::string& str)
|
||||
{
|
||||
std::vector<unsigned char> data = CheckedParseHex(str);
|
||||
return CScript(data.begin(), data.end());
|
||||
}
|
||||
|
||||
CMutableTransaction TxFromHex(const std::string& str)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
try {
|
||||
VectorReader(SER_DISK, SERIALIZE_TRANSACTION_NO_WITNESS, CheckedParseHex(str), 0) >> tx;
|
||||
} catch (const std::ios_base::failure&) {
|
||||
throw std::runtime_error("Tx deserialization failure");
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
|
||||
{
|
||||
if (!univalue.isArray()) throw std::runtime_error("Prevouts must be array");
|
||||
std::vector<CTxOut> prevouts;
|
||||
for (size_t i = 0; i < univalue.size(); ++i) {
|
||||
CTxOut txout;
|
||||
try {
|
||||
VectorReader(SER_DISK, 0, CheckedParseHex(univalue[i].get_str()), 0) >> txout;
|
||||
} catch (const std::ios_base::failure&) {
|
||||
throw std::runtime_error("Prevout invalid format");
|
||||
}
|
||||
prevouts.push_back(std::move(txout));
|
||||
}
|
||||
return prevouts;
|
||||
}
|
||||
|
||||
CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
|
||||
{
|
||||
if (!univalue.isArray()) throw std::runtime_error("Script witness is not array");
|
||||
CScriptWitness scriptwitness;
|
||||
for (size_t i = 0; i < univalue.size(); ++i) {
|
||||
auto bytes = CheckedParseHex(univalue[i].get_str());
|
||||
scriptwitness.stack.push_back(std::move(bytes));
|
||||
}
|
||||
return scriptwitness;
|
||||
}
|
||||
|
||||
const std::map<std::string, unsigned int> FLAG_NAMES = {
|
||||
{std::string("P2SH"), (unsigned int)SCRIPT_VERIFY_P2SH},
|
||||
{std::string("DERSIG"), (unsigned int)SCRIPT_VERIFY_DERSIG},
|
||||
{std::string("NULLDUMMY"), (unsigned int)SCRIPT_VERIFY_NULLDUMMY},
|
||||
{std::string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY},
|
||||
{std::string("CHECKSEQUENCEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKSEQUENCEVERIFY},
|
||||
{std::string("WITNESS"), (unsigned int)SCRIPT_VERIFY_WITNESS},
|
||||
{std::string("TAPROOT"), (unsigned int)SCRIPT_VERIFY_TAPROOT},
|
||||
};
|
||||
|
||||
std::vector<unsigned int> AllFlags()
|
||||
{
|
||||
std::vector<unsigned int> ret;
|
||||
|
||||
for (unsigned int i = 0; i < 128; ++i) {
|
||||
unsigned int flag = 0;
|
||||
if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
|
||||
if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
|
||||
if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
|
||||
if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
|
||||
if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
|
||||
if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
|
||||
if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
|
||||
|
||||
// SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
|
||||
if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
|
||||
// SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
|
||||
if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
|
||||
|
||||
ret.push_back(flag);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<unsigned int> ALL_FLAGS = AllFlags();
|
||||
|
||||
unsigned int ParseScriptFlags(const std::string& str)
|
||||
{
|
||||
if (str.empty()) return 0;
|
||||
|
||||
unsigned int flags = 0;
|
||||
std::vector<std::string> words;
|
||||
boost::algorithm::split(words, str, boost::algorithm::is_any_of(","));
|
||||
|
||||
for (const std::string& word : words)
|
||||
{
|
||||
auto it = FLAG_NAMES.find(word);
|
||||
if (it == FLAG_NAMES.end()) throw std::runtime_error("Unknown verification flag " + word);
|
||||
flags |= it->second;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
void Test(const std::string& str)
|
||||
{
|
||||
UniValue test;
|
||||
if (!test.read(str) || !test.isObject()) throw std::runtime_error("Non-object test input");
|
||||
|
||||
CMutableTransaction tx = TxFromHex(test["tx"].get_str());
|
||||
const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
|
||||
if (prevouts.size() != tx.vin.size()) throw std::runtime_error("Incorrect number of prevouts");
|
||||
size_t idx = test["index"].get_int64();
|
||||
if (idx >= tx.vin.size()) throw std::runtime_error("Invalid index");
|
||||
unsigned int test_flags = ParseScriptFlags(test["flags"].get_str());
|
||||
bool final = test.exists("final") && test["final"].get_bool();
|
||||
|
||||
if (test.exists("success")) {
|
||||
tx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
|
||||
tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
|
||||
PrecomputedTransactionData txdata;
|
||||
txdata.Init(tx, std::vector<CTxOut>(prevouts));
|
||||
MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata);
|
||||
for (const auto flags : ALL_FLAGS) {
|
||||
// "final": true tests are valid for all flags. Others are only valid with flags that are
|
||||
// a subset of test_flags.
|
||||
if (final || ((flags & test_flags) == flags)) {
|
||||
(void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (test.exists("failure")) {
|
||||
tx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
|
||||
tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
|
||||
PrecomputedTransactionData txdata;
|
||||
txdata.Init(tx, std::vector<CTxOut>(prevouts));
|
||||
MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata);
|
||||
for (const auto flags : ALL_FLAGS) {
|
||||
// If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
|
||||
if ((flags & test_flags) == test_flags) {
|
||||
(void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ECCVerifyHandle handle;
|
||||
|
||||
}
|
||||
|
||||
void test_one_input(const std::vector<uint8_t>& buffer)
|
||||
{
|
||||
if (buffer.size() < 2 || buffer.back() != '\n' || buffer[buffer.size() - 2] != ',') return;
|
||||
const std::string str((const char*)buffer.data(), buffer.size() - 2);
|
||||
try {
|
||||
Test(str);
|
||||
} catch (const std::runtime_error&) {}
|
||||
}
|
@ -35,11 +35,19 @@ void test_one_input(const std::vector<uint8_t>& buffer)
|
||||
const bool store = fuzzed_data_provider.ConsumeBool();
|
||||
PrecomputedTransactionData tx_data;
|
||||
CachingTransactionSignatureChecker caching_transaction_signature_checker{mutable_transaction ? &tx : nullptr, n_in, amount, store, tx_data};
|
||||
const std::optional<CPubKey> pub_key = ConsumeDeserializable<CPubKey>(fuzzed_data_provider);
|
||||
if (pub_key) {
|
||||
const std::vector<uint8_t> random_bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider);
|
||||
if (!random_bytes.empty()) {
|
||||
(void)caching_transaction_signature_checker.VerifySignature(random_bytes, *pub_key, ConsumeUInt256(fuzzed_data_provider));
|
||||
if (fuzzed_data_provider.ConsumeBool()) {
|
||||
const auto random_bytes = fuzzed_data_provider.ConsumeBytes<unsigned char>(64);
|
||||
const XOnlyPubKey pub_key(ConsumeUInt256(fuzzed_data_provider));
|
||||
if (random_bytes.size() == 64) {
|
||||
(void)caching_transaction_signature_checker.VerifySchnorrSignature(random_bytes, pub_key, ConsumeUInt256(fuzzed_data_provider));
|
||||
}
|
||||
} else {
|
||||
const auto random_bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider);
|
||||
const auto pub_key = ConsumeDeserializable<CPubKey>(fuzzed_data_provider);
|
||||
if (pub_key) {
|
||||
if (!random_bytes.empty()) {
|
||||
(void)caching_transaction_signature_checker.VerifyECDSASignature(random_bytes, *pub_key, ConsumeUInt256(fuzzed_data_provider));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,12 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
|
||||
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
|
||||
{
|
||||
return m_fuzzed_data_provider.ConsumeBool();
|
||||
}
|
||||
|
||||
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override
|
||||
{
|
||||
return m_fuzzed_data_provider.ConsumeBool();
|
||||
}
|
||||
|
@ -264,4 +264,32 @@ BOOST_AUTO_TEST_CASE(pubkey_unserialize)
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bip340_test_vectors)
|
||||
{
|
||||
static const std::vector<std::pair<std::array<std::string, 3>, bool>> VECTORS = {
|
||||
{{"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", "0000000000000000000000000000000000000000000000000000000000000000", "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"}, true},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"}, true},
|
||||
{{"DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7"}, true},
|
||||
{{"25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3"}, true},
|
||||
{{"D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9", "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4"}, true},
|
||||
{{"EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false},
|
||||
{{"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"}, false},
|
||||
{{"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"}, false}
|
||||
};
|
||||
|
||||
for (const auto& test : VECTORS) {
|
||||
auto pubkey = ParseHex(test.first[0]);
|
||||
auto msg = ParseHex(test.first[1]);
|
||||
auto sig = ParseHex(test.first[2]);
|
||||
BOOST_CHECK_EQUAL(XOnlyPubKey(pubkey).VerifySchnorr(uint256(msg), sig), test.second);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -5,10 +5,12 @@
|
||||
#include <test/data/script_tests.json.h>
|
||||
|
||||
#include <core_io.h>
|
||||
#include <fs.h>
|
||||
#include <key.h>
|
||||
#include <rpc/util.h>
|
||||
#include <script/script.h>
|
||||
#include <script/script_error.h>
|
||||
#include <script/sigcache.h>
|
||||
#include <script/sign.h>
|
||||
#include <script/signingprovider.h>
|
||||
#include <streams.h>
|
||||
@ -1339,13 +1341,41 @@ BOOST_AUTO_TEST_CASE(script_GetScriptAsm)
|
||||
BOOST_CHECK_EQUAL(derSig + "83 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "83")) << vchPubKey));
|
||||
}
|
||||
|
||||
static CScript
|
||||
ScriptFromHex(const char* hex)
|
||||
static CScript ScriptFromHex(const std::string& str)
|
||||
{
|
||||
std::vector<unsigned char> data = ParseHex(hex);
|
||||
std::vector<unsigned char> data = ParseHex(str);
|
||||
return CScript(data.begin(), data.end());
|
||||
}
|
||||
|
||||
static CMutableTransaction TxFromHex(const std::string& str)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
VectorReader(SER_DISK, SERIALIZE_TRANSACTION_NO_WITNESS, ParseHex(str), 0) >> tx;
|
||||
return tx;
|
||||
}
|
||||
|
||||
static std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
|
||||
{
|
||||
assert(univalue.isArray());
|
||||
std::vector<CTxOut> prevouts;
|
||||
for (size_t i = 0; i < univalue.size(); ++i) {
|
||||
CTxOut txout;
|
||||
VectorReader(SER_DISK, 0, ParseHex(univalue[i].get_str()), 0) >> txout;
|
||||
prevouts.push_back(std::move(txout));
|
||||
}
|
||||
return prevouts;
|
||||
}
|
||||
|
||||
static CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
|
||||
{
|
||||
assert(univalue.isArray());
|
||||
CScriptWitness scriptwitness;
|
||||
for (size_t i = 0; i < univalue.size(); ++i) {
|
||||
auto bytes = ParseHex(univalue[i].get_str());
|
||||
scriptwitness.stack.push_back(std::move(bytes));
|
||||
}
|
||||
return scriptwitness;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_FindAndDelete)
|
||||
{
|
||||
@ -1610,5 +1640,107 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags)
|
||||
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_INVALID_FLAGS);
|
||||
}
|
||||
|
||||
static std::vector<unsigned int> AllConsensusFlags()
|
||||
{
|
||||
std::vector<unsigned int> ret;
|
||||
|
||||
for (unsigned int i = 0; i < 128; ++i) {
|
||||
unsigned int flag = 0;
|
||||
if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
|
||||
if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
|
||||
if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
|
||||
if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
|
||||
if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
|
||||
if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
|
||||
if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
|
||||
|
||||
// SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
|
||||
if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
|
||||
// SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
|
||||
if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
|
||||
|
||||
ret.push_back(flag);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Precomputed list of all valid combinations of consensus-relevant script validation flags. */
|
||||
static const std::vector<unsigned int> ALL_CONSENSUS_FLAGS = AllConsensusFlags();
|
||||
|
||||
static void AssetTest(const UniValue& test)
|
||||
{
|
||||
BOOST_CHECK(test.isObject());
|
||||
|
||||
CMutableTransaction mtx = TxFromHex(test["tx"].get_str());
|
||||
const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
|
||||
BOOST_CHECK(prevouts.size() == mtx.vin.size());
|
||||
size_t idx = test["index"].get_int64();
|
||||
unsigned int test_flags = ParseScriptFlags(test["flags"].get_str());
|
||||
bool fin = test.exists("final") && test["final"].get_bool();
|
||||
|
||||
if (test.exists("success")) {
|
||||
mtx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
|
||||
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
|
||||
CTransaction tx(mtx);
|
||||
PrecomputedTransactionData txdata;
|
||||
txdata.Init(tx, std::vector<CTxOut>(prevouts));
|
||||
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
|
||||
for (const auto flags : ALL_CONSENSUS_FLAGS) {
|
||||
// "final": true tests are valid for all flags. Others are only valid with flags that are
|
||||
// a subset of test_flags.
|
||||
if (fin || ((flags & test_flags) == flags)) {
|
||||
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
|
||||
BOOST_CHECK(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (test.exists("failure")) {
|
||||
mtx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
|
||||
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
|
||||
CTransaction tx(mtx);
|
||||
PrecomputedTransactionData txdata;
|
||||
txdata.Init(tx, std::vector<CTxOut>(prevouts));
|
||||
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
|
||||
for (const auto flags : ALL_CONSENSUS_FLAGS) {
|
||||
// If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
|
||||
if ((flags & test_flags) == test_flags) {
|
||||
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
|
||||
BOOST_CHECK(!ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_assets_test)
|
||||
{
|
||||
// See src/test/fuzz/script_assets_test_minimizer.cpp for information on how to generate
|
||||
// the script_assets_test.json file used by this test.
|
||||
|
||||
const char* dir = std::getenv("DIR_UNIT_TEST_DATA");
|
||||
BOOST_WARN_MESSAGE(dir != nullptr, "Variable DIR_UNIT_TEST_DATA unset, skipping script_assets_test");
|
||||
if (dir == nullptr) return;
|
||||
auto path = fs::path(dir) / "script_assets_test.json";
|
||||
bool exists = fs::exists(path);
|
||||
BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test");
|
||||
if (!exists) return;
|
||||
fs::ifstream file(path);
|
||||
BOOST_CHECK(file.is_open());
|
||||
file.seekg(0, std::ios::end);
|
||||
size_t length = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::string data(length, '\0');
|
||||
file.read(&data[0], data.size());
|
||||
UniValue tests = read_json(data);
|
||||
BOOST_CHECK(tests.isArray());
|
||||
BOOST_CHECK(tests.size() > 0);
|
||||
|
||||
for (size_t i = 0; i < tests.size(); i++) {
|
||||
AssetTest(tests[i]);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
#endif
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -57,6 +57,7 @@ static std::map<std::string, unsigned int> mapFlagNames = {
|
||||
{std::string("DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM},
|
||||
{std::string("WITNESS_PUBKEYTYPE"), (unsigned int)SCRIPT_VERIFY_WITNESS_PUBKEYTYPE},
|
||||
{std::string("CONST_SCRIPTCODE"), (unsigned int)SCRIPT_VERIFY_CONST_SCRIPTCODE},
|
||||
{std::string("TAPROOT"), (unsigned int)SCRIPT_VERIFY_TAPROOT},
|
||||
};
|
||||
|
||||
unsigned int ParseScriptFlags(std::string strFlags)
|
||||
|
@ -1532,14 +1532,21 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState &state, const C
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!txdata.m_ready) {
|
||||
txdata.Init(tx);
|
||||
if (!txdata.m_spent_outputs_ready) {
|
||||
std::vector<CTxOut> 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
|
||||
@ -1548,7 +1555,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState &state, const C
|
||||
// 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->push_back(CScriptCheck());
|
||||
check.swap(pvChecks->back());
|
||||
@ -1562,7 +1569,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState &state, const C
|
||||
// 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())));
|
||||
@ -1907,6 +1914,11 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens
|
||||
flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
|
||||
}
|
||||
|
||||
// Start enforcing Taproot using versionbits logic.
|
||||
if (VersionBitsState(pindex->pprev, consensusparams, Consensus::DEPLOYMENT_TAPROOT, versionbitscache) == ThresholdState::ACTIVE) {
|
||||
flags |= SCRIPT_VERIFY_TAPROOT;
|
||||
}
|
||||
|
||||
// Start enforcing BIP147 NULLDUMMY (activated simultaneously with segwit)
|
||||
if (IsWitnessEnabled(pindex->pprev, consensusparams)) {
|
||||
flags |= SCRIPT_VERIFY_NULLDUMMY;
|
||||
|
@ -11,4 +11,8 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
|
||||
/*.name =*/ "testdummy",
|
||||
/*.gbt_force =*/ true,
|
||||
},
|
||||
{
|
||||
/*.name =*/ "taproot",
|
||||
/*.gbt_force =*/ true,
|
||||
},
|
||||
};
|
||||
|
@ -932,6 +932,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
|
||||
return "unspendable script";
|
||||
case TxoutType::NONSTANDARD:
|
||||
case TxoutType::WITNESS_UNKNOWN:
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
default:
|
||||
return "unrecognized script";
|
||||
}
|
||||
|
@ -96,6 +96,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s
|
||||
case TxoutType::NONSTANDARD:
|
||||
case TxoutType::NULL_DATA:
|
||||
case TxoutType::WITNESS_UNKNOWN:
|
||||
case TxoutType::WITNESS_V1_TAPROOT:
|
||||
break;
|
||||
case TxoutType::PUBKEY:
|
||||
keyID = CPubKey(vSolutions[0]).GetID();
|
||||
|
1458
test/functional/feature_taproot.py
Executable file
1458
test/functional/feature_taproot.py
Executable file
File diff suppressed because it is too large
Load Diff
@ -55,6 +55,7 @@ from test_framework.script import (
|
||||
MAX_SCRIPT_ELEMENT_SIZE,
|
||||
OP_0,
|
||||
OP_1,
|
||||
OP_2,
|
||||
OP_16,
|
||||
OP_2DROP,
|
||||
OP_CHECKMULTISIG,
|
||||
@ -1400,7 +1401,11 @@ class SegWitTest(BitcoinTestFramework):
|
||||
assert_equal(len(self.nodes[1].getrawmempool()), 0)
|
||||
for version in list(range(OP_1, OP_16 + 1)) + [OP_0]:
|
||||
# First try to spend to a future version segwit script_pubkey.
|
||||
script_pubkey = CScript([CScriptOp(version), witness_hash])
|
||||
if version == OP_1:
|
||||
# Don't use 32-byte v1 witness (used by Taproot; see BIP 341)
|
||||
script_pubkey = CScript([CScriptOp(version), witness_hash + b'\x00'])
|
||||
else:
|
||||
script_pubkey = CScript([CScriptOp(version), witness_hash])
|
||||
tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")]
|
||||
tx.vout = [CTxOut(self.utxo[0].nValue - 1000, script_pubkey)]
|
||||
tx.rehash()
|
||||
@ -1413,9 +1418,9 @@ class SegWitTest(BitcoinTestFramework):
|
||||
self.sync_blocks()
|
||||
assert len(self.nodes[0].getrawmempool()) == 0
|
||||
|
||||
# Finally, verify that version 0 -> version 1 transactions
|
||||
# Finally, verify that version 0 -> version 2 transactions
|
||||
# are standard
|
||||
script_pubkey = CScript([CScriptOp(OP_1), witness_hash])
|
||||
script_pubkey = CScript([CScriptOp(OP_2), witness_hash])
|
||||
tx2 = CTransaction()
|
||||
tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")]
|
||||
tx2.vout = [CTxOut(tx.vout[0].nValue - 1000, script_pubkey)]
|
||||
|
@ -146,7 +146,19 @@ class BlockchainTest(BitcoinTestFramework):
|
||||
'possible': True,
|
||||
},
|
||||
},
|
||||
'active': False}
|
||||
'active': False
|
||||
},
|
||||
'taproot': {
|
||||
'type': 'bip9',
|
||||
'bip9': {
|
||||
'status': 'active',
|
||||
'start_time': -1,
|
||||
'timeout': 9223372036854775807,
|
||||
'since': 0
|
||||
},
|
||||
'height': 0,
|
||||
'active': True
|
||||
}
|
||||
})
|
||||
|
||||
def _test_getchaintxstats(self):
|
||||
|
16
test/functional/test_framework/bip340_test_vectors.csv
Normal file
16
test/functional/test_framework/bip340_test_vectors.csv
Normal file
@ -0,0 +1,16 @@
|
||||
index,secret key,public key,aux_rand,message,signature,verification result,comment
|
||||
0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE,
|
||||
1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE,
|
||||
2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE,
|
||||
3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n
|
||||
4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE,
|
||||
5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve
|
||||
6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false
|
||||
7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message
|
||||
8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value
|
||||
9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
|
||||
10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
|
||||
11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve
|
||||
12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size
|
||||
13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order
|
||||
14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size
|
|
@ -43,7 +43,9 @@ from .script import (
|
||||
from .util import assert_equal
|
||||
from io import BytesIO
|
||||
|
||||
WITNESS_SCALE_FACTOR = 4
|
||||
MAX_BLOCK_SIGOPS = 20000
|
||||
MAX_BLOCK_SIGOPS_WEIGHT = MAX_BLOCK_SIGOPS * WITNESS_SCALE_FACTOR
|
||||
|
||||
# Genesis block time (regtest)
|
||||
TIME_GENESIS_BLOCK = 1296688602
|
||||
@ -101,22 +103,31 @@ def script_BIP34_coinbase_height(height):
|
||||
return CScript([CScriptNum(height)])
|
||||
|
||||
|
||||
def create_coinbase(height, pubkey=None):
|
||||
"""Create a coinbase transaction, assuming no miner fees.
|
||||
def create_coinbase(height, pubkey=None, extra_output_script=None, fees=0):
|
||||
"""Create a coinbase transaction.
|
||||
|
||||
If pubkey is passed in, the coinbase output will be a P2PK output;
|
||||
otherwise an anyone-can-spend output."""
|
||||
otherwise an anyone-can-spend output.
|
||||
|
||||
If extra_output_script is given, make a 0-value output to that
|
||||
script. This is useful to pad block weight/sigops as needed. """
|
||||
coinbase = CTransaction()
|
||||
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff))
|
||||
coinbaseoutput = CTxOut()
|
||||
coinbaseoutput.nValue = 50 * COIN
|
||||
halvings = int(height / 150) # regtest
|
||||
coinbaseoutput.nValue >>= halvings
|
||||
if (pubkey is not None):
|
||||
coinbaseoutput.nValue += fees
|
||||
if pubkey is not None:
|
||||
coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG])
|
||||
else:
|
||||
coinbaseoutput.scriptPubKey = CScript([OP_TRUE])
|
||||
coinbase.vout = [coinbaseoutput]
|
||||
if extra_output_script is not None:
|
||||
coinbaseoutput2 = CTxOut()
|
||||
coinbaseoutput2.nValue = 0
|
||||
coinbaseoutput2.scriptPubKey = extra_output_script
|
||||
coinbase.vout.append(coinbaseoutput2)
|
||||
coinbase.calc_sha256()
|
||||
return coinbase
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2019 Pieter Wuille
|
||||
# Copyright (c) 2019-2020 Pieter Wuille
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test-only secp256k1 elliptic curve implementation
|
||||
@ -6,10 +6,24 @@
|
||||
WARNING: This code is slow, uses bad randomness, does not properly protect
|
||||
keys, and is trivially vulnerable to side channel attacks. Do not use for
|
||||
anything but tests."""
|
||||
import csv
|
||||
import hashlib
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from .util import modinv
|
||||
|
||||
def TaggedHash(tag, data):
|
||||
ss = hashlib.sha256(tag.encode('utf-8')).digest()
|
||||
ss += ss
|
||||
ss += data
|
||||
return hashlib.sha256(ss).digest()
|
||||
|
||||
def xor_bytes(b0, b1):
|
||||
return bytes(x ^ y for (x, y) in zip(b0, b1))
|
||||
|
||||
def jacobi_symbol(n, k):
|
||||
"""Compute the Jacobi symbol of n modulo k
|
||||
|
||||
@ -68,6 +82,10 @@ class EllipticCurve:
|
||||
inv_3 = (inv_2 * inv) % self.p
|
||||
return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
|
||||
|
||||
def has_even_y(self, p1):
|
||||
"""Whether the point p1 has an even Y coordinate when expressed in affine coordinates."""
|
||||
return not (p1[2] == 0 or self.affine(p1)[1] & 1)
|
||||
|
||||
def negate(self, p1):
|
||||
"""Negate a Jacobian point tuple p1."""
|
||||
x1, y1, z1 = p1
|
||||
@ -86,13 +104,13 @@ class EllipticCurve:
|
||||
return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
|
||||
|
||||
def lift_x(self, x):
|
||||
"""Given an X coordinate on the curve, return a corresponding affine point."""
|
||||
"""Given an X coordinate on the curve, return a corresponding affine point for which the Y coordinate is even."""
|
||||
x_3 = pow(x, 3, self.p)
|
||||
v = x_3 + self.a * x + self.b
|
||||
y = modsqrt(v, self.p)
|
||||
if y is None:
|
||||
return None
|
||||
return (x, y, 1)
|
||||
return (x, self.p - y if y & 1 else y, 1)
|
||||
|
||||
def double(self, p1):
|
||||
"""Double a Jacobian tuple p1
|
||||
@ -197,7 +215,8 @@ class EllipticCurve:
|
||||
r = self.add(r, p)
|
||||
return r
|
||||
|
||||
SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7)
|
||||
SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977
|
||||
SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7)
|
||||
SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
|
||||
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
|
||||
@ -223,7 +242,7 @@ class ECPubKey():
|
||||
p = SECP256K1.lift_x(x)
|
||||
# if the oddness of the y co-ord isn't correct, find the other
|
||||
# valid y
|
||||
if (p[1] & 1) != (data[0] & 1):
|
||||
if data[0] & 1:
|
||||
p = SECP256K1.negate(p)
|
||||
self.p = p
|
||||
self.valid = True
|
||||
@ -307,6 +326,10 @@ class ECPubKey():
|
||||
return False
|
||||
return True
|
||||
|
||||
def generate_privkey():
|
||||
"""Generate a valid random 32-byte private key."""
|
||||
return random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big')
|
||||
|
||||
class ECKey():
|
||||
"""A secp256k1 private key"""
|
||||
|
||||
@ -324,7 +347,7 @@ class ECKey():
|
||||
|
||||
def generate(self, compressed=True):
|
||||
"""Generate a random private key (compressed or uncompressed)."""
|
||||
self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed)
|
||||
self.set(generate_privkey(), compressed)
|
||||
|
||||
def get_bytes(self):
|
||||
"""Retrieve the 32-byte representation of this key."""
|
||||
@ -369,3 +392,161 @@ class ECKey():
|
||||
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
|
||||
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
|
||||
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
|
||||
|
||||
def compute_xonly_pubkey(key):
|
||||
"""Compute an x-only (32 byte) public key from a (32 byte) private key.
|
||||
|
||||
This also returns whether the resulting public key was negated.
|
||||
"""
|
||||
|
||||
assert len(key) == 32
|
||||
x = int.from_bytes(key, 'big')
|
||||
if x == 0 or x >= SECP256K1_ORDER:
|
||||
return (None, None)
|
||||
P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, x)]))
|
||||
return (P[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(P))
|
||||
|
||||
def tweak_add_privkey(key, tweak):
|
||||
"""Tweak a private key (after negating it if needed)."""
|
||||
|
||||
assert len(key) == 32
|
||||
assert len(tweak) == 32
|
||||
|
||||
x = int.from_bytes(key, 'big')
|
||||
if x == 0 or x >= SECP256K1_ORDER:
|
||||
return None
|
||||
if not SECP256K1.has_even_y(SECP256K1.mul([(SECP256K1_G, x)])):
|
||||
x = SECP256K1_ORDER - x
|
||||
t = int.from_bytes(tweak, 'big')
|
||||
if t >= SECP256K1_ORDER:
|
||||
return None
|
||||
x = (x + t) % SECP256K1_ORDER
|
||||
if x == 0:
|
||||
return None
|
||||
return x.to_bytes(32, 'big')
|
||||
|
||||
def tweak_add_pubkey(key, tweak):
|
||||
"""Tweak a public key and return whether the result had to be negated."""
|
||||
|
||||
assert len(key) == 32
|
||||
assert len(tweak) == 32
|
||||
|
||||
x_coord = int.from_bytes(key, 'big')
|
||||
if x_coord >= SECP256K1_FIELD_SIZE:
|
||||
return None
|
||||
P = SECP256K1.lift_x(x_coord)
|
||||
if P is None:
|
||||
return None
|
||||
t = int.from_bytes(tweak, 'big')
|
||||
if t >= SECP256K1_ORDER:
|
||||
return None
|
||||
Q = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, t), (P, 1)]))
|
||||
if Q is None:
|
||||
return None
|
||||
return (Q[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(Q))
|
||||
|
||||
def verify_schnorr(key, sig, msg):
|
||||
"""Verify a Schnorr signature (see BIP 340).
|
||||
|
||||
- key is a 32-byte xonly pubkey (computed using compute_xonly_pubkey).
|
||||
- sig is a 64-byte Schnorr signature
|
||||
- msg is a 32-byte message
|
||||
"""
|
||||
assert len(key) == 32
|
||||
assert len(msg) == 32
|
||||
assert len(sig) == 64
|
||||
|
||||
x_coord = int.from_bytes(key, 'big')
|
||||
if x_coord == 0 or x_coord >= SECP256K1_FIELD_SIZE:
|
||||
return False
|
||||
P = SECP256K1.lift_x(x_coord)
|
||||
if P is None:
|
||||
return False
|
||||
r = int.from_bytes(sig[0:32], 'big')
|
||||
if r >= SECP256K1_FIELD_SIZE:
|
||||
return False
|
||||
s = int.from_bytes(sig[32:64], 'big')
|
||||
if s >= SECP256K1_ORDER:
|
||||
return False
|
||||
e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), 'big') % SECP256K1_ORDER
|
||||
R = SECP256K1.mul([(SECP256K1_G, s), (P, SECP256K1_ORDER - e)])
|
||||
if not SECP256K1.has_even_y(R):
|
||||
return False
|
||||
if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
|
||||
"""Create a Schnorr signature (see BIP 340)."""
|
||||
|
||||
if aux is None:
|
||||
aux = bytes(32)
|
||||
|
||||
assert len(key) == 32
|
||||
assert len(msg) == 32
|
||||
assert len(aux) == 32
|
||||
|
||||
sec = int.from_bytes(key, 'big')
|
||||
if sec == 0 or sec >= SECP256K1_ORDER:
|
||||
return None
|
||||
P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)]))
|
||||
if SECP256K1.has_even_y(P) == flip_p:
|
||||
sec = SECP256K1_ORDER - sec
|
||||
t = (sec ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big')
|
||||
kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER
|
||||
assert kp != 0
|
||||
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)]))
|
||||
k = kp if SECP256K1.has_even_y(R) != flip_r else SECP256K1_ORDER - kp
|
||||
e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER
|
||||
return R[0].to_bytes(32, 'big') + ((k + e * sec) % SECP256K1_ORDER).to_bytes(32, 'big')
|
||||
|
||||
class TestFrameworkKey(unittest.TestCase):
|
||||
def test_schnorr(self):
|
||||
"""Test the Python Schnorr implementation."""
|
||||
byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, SECP256K1_ORDER - 1, SECP256K1_ORDER, 2**256 - 1]]
|
||||
keys = {}
|
||||
for privkey in byte_arrays: # build array of key/pubkey pairs
|
||||
pubkey, _ = compute_xonly_pubkey(privkey)
|
||||
if pubkey is not None:
|
||||
keys[privkey] = pubkey
|
||||
for msg in byte_arrays: # test every combination of message, signing key, verification key
|
||||
for sign_privkey, sign_pubkey in keys.items():
|
||||
sig = sign_schnorr(sign_privkey, msg)
|
||||
for verify_privkey, verify_pubkey in keys.items():
|
||||
if verify_privkey == sign_privkey:
|
||||
self.assertTrue(verify_schnorr(verify_pubkey, sig, msg))
|
||||
sig = list(sig)
|
||||
sig[random.randrange(64)] ^= (1 << (random.randrange(8))) # damaging signature should break things
|
||||
sig = bytes(sig)
|
||||
self.assertFalse(verify_schnorr(verify_pubkey, sig, msg))
|
||||
|
||||
def test_schnorr_testvectors(self):
|
||||
"""Implement the BIP340 test vectors (read from bip340_test_vectors.csv)."""
|
||||
num_tests = 0
|
||||
with open(os.path.join(sys.path[0], 'test_framework', 'bip340_test_vectors.csv'), newline='', encoding='utf8') as csvfile:
|
||||
reader = csv.reader(csvfile)
|
||||
next(reader)
|
||||
for row in reader:
|
||||
(i_str, seckey_hex, pubkey_hex, aux_rand_hex, msg_hex, sig_hex, result_str, comment) = row
|
||||
i = int(i_str)
|
||||
pubkey = bytes.fromhex(pubkey_hex)
|
||||
msg = bytes.fromhex(msg_hex)
|
||||
sig = bytes.fromhex(sig_hex)
|
||||
result = result_str == 'TRUE'
|
||||
if seckey_hex != '':
|
||||
seckey = bytes.fromhex(seckey_hex)
|
||||
pubkey_actual = compute_xonly_pubkey(seckey)[0]
|
||||
self.assertEqual(pubkey.hex(), pubkey_actual.hex(), "BIP340 test vector %i (%s): pubkey mismatch" % (i, comment))
|
||||
aux_rand = bytes.fromhex(aux_rand_hex)
|
||||
try:
|
||||
sig_actual = sign_schnorr(seckey, msg, aux_rand)
|
||||
self.assertEqual(sig.hex(), sig_actual.hex(), "BIP340 test vector %i (%s): sig mismatch" % (i, comment))
|
||||
except RuntimeError as e:
|
||||
self.assertFalse("BIP340 test vector %i (%s): signing raised exception %s" % (i, comment, e))
|
||||
result_actual = verify_schnorr(pubkey, sig, msg)
|
||||
if result:
|
||||
self.assertEqual(result, result_actual, "BIP340 test vector %i (%s): verification failed" % (i, comment))
|
||||
else:
|
||||
self.assertEqual(result, result_actual, "BIP340 test vector %i (%s): verification succeeded unexpectedly" % (i, comment))
|
||||
num_tests += 1
|
||||
self.assertTrue(num_tests >= 15) # expect at least 15 test vectors
|
||||
|
@ -6,11 +6,15 @@
|
||||
|
||||
This file is modified from python-bitcoinlib.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
import hashlib
|
||||
import struct
|
||||
import unittest
|
||||
from typing import List, Dict
|
||||
|
||||
from .key import TaggedHash, tweak_add_pubkey
|
||||
|
||||
from .messages import (
|
||||
CTransaction,
|
||||
CTxOut,
|
||||
@ -22,8 +26,13 @@ from .messages import (
|
||||
)
|
||||
|
||||
MAX_SCRIPT_ELEMENT_SIZE = 520
|
||||
LOCKTIME_THRESHOLD = 500000000
|
||||
ANNEX_TAG = 0x50
|
||||
|
||||
OPCODE_NAMES = {} # type: Dict[CScriptOp, str]
|
||||
|
||||
LEAF_VERSION_TAPSCRIPT = 0xc0
|
||||
|
||||
def hash160(s):
|
||||
return hashlib.new('ripemd160', sha256(s)).digest()
|
||||
|
||||
@ -239,11 +248,8 @@ OP_NOP8 = CScriptOp(0xb7)
|
||||
OP_NOP9 = CScriptOp(0xb8)
|
||||
OP_NOP10 = CScriptOp(0xb9)
|
||||
|
||||
# template matching params
|
||||
OP_SMALLINTEGER = CScriptOp(0xfa)
|
||||
OP_PUBKEYS = CScriptOp(0xfb)
|
||||
OP_PUBKEYHASH = CScriptOp(0xfd)
|
||||
OP_PUBKEY = CScriptOp(0xfe)
|
||||
# BIP 342 opcodes (Tapscript)
|
||||
OP_CHECKSIGADD = CScriptOp(0xba)
|
||||
|
||||
OP_INVALIDOPCODE = CScriptOp(0xff)
|
||||
|
||||
@ -359,10 +365,7 @@ OPCODE_NAMES.update({
|
||||
OP_NOP8: 'OP_NOP8',
|
||||
OP_NOP9: 'OP_NOP9',
|
||||
OP_NOP10: 'OP_NOP10',
|
||||
OP_SMALLINTEGER: 'OP_SMALLINTEGER',
|
||||
OP_PUBKEYS: 'OP_PUBKEYS',
|
||||
OP_PUBKEYHASH: 'OP_PUBKEYHASH',
|
||||
OP_PUBKEY: 'OP_PUBKEY',
|
||||
OP_CHECKSIGADD: 'OP_CHECKSIGADD',
|
||||
OP_INVALIDOPCODE: 'OP_INVALIDOPCODE',
|
||||
})
|
||||
|
||||
@ -593,6 +596,7 @@ class CScript(bytes):
|
||||
return n
|
||||
|
||||
|
||||
SIGHASH_DEFAULT = 0 # Taproot-only default, semantics same as SIGHASH_ALL
|
||||
SIGHASH_ALL = 1
|
||||
SIGHASH_NONE = 2
|
||||
SIGHASH_SINGLE = 3
|
||||
@ -615,7 +619,6 @@ def FindAndDelete(script, sig):
|
||||
r += script[last_sop_idx:]
|
||||
return CScript(r)
|
||||
|
||||
|
||||
def LegacySignatureHash(script, txTo, inIdx, hashtype):
|
||||
"""Consensus-correct SignatureHash
|
||||
|
||||
@ -738,3 +741,113 @@ class TestFrameworkScript(unittest.TestCase):
|
||||
values = [0, 1, -1, -2, 127, 128, -255, 256, (1 << 15) - 1, -(1 << 16), (1 << 24) - 1, (1 << 31), 1 - (1 << 32), 1 << 40, 1500, -1500]
|
||||
for value in values:
|
||||
self.assertEqual(CScriptNum.decode(CScriptNum.encode(CScriptNum(value))), value)
|
||||
|
||||
def TaprootSignatureHash(txTo, spent_utxos, hash_type, input_index = 0, scriptpath = False, script = CScript(), codeseparator_pos = -1, annex = None, leaf_ver = LEAF_VERSION_TAPSCRIPT):
|
||||
assert (len(txTo.vin) == len(spent_utxos))
|
||||
assert (input_index < len(txTo.vin))
|
||||
out_type = SIGHASH_ALL if hash_type == 0 else hash_type & 3
|
||||
in_type = hash_type & SIGHASH_ANYONECANPAY
|
||||
spk = spent_utxos[input_index].scriptPubKey
|
||||
ss = bytes([0, hash_type]) # epoch, hash_type
|
||||
ss += struct.pack("<i", txTo.nVersion)
|
||||
ss += struct.pack("<I", txTo.nLockTime)
|
||||
if in_type != SIGHASH_ANYONECANPAY:
|
||||
ss += sha256(b"".join(i.prevout.serialize() for i in txTo.vin))
|
||||
ss += sha256(b"".join(struct.pack("<q", u.nValue) for u in spent_utxos))
|
||||
ss += sha256(b"".join(ser_string(u.scriptPubKey) for u in spent_utxos))
|
||||
ss += sha256(b"".join(struct.pack("<I", i.nSequence) for i in txTo.vin))
|
||||
if out_type == SIGHASH_ALL:
|
||||
ss += sha256(b"".join(o.serialize() for o in txTo.vout))
|
||||
spend_type = 0
|
||||
if annex is not None:
|
||||
spend_type |= 1
|
||||
if (scriptpath):
|
||||
spend_type |= 2
|
||||
ss += bytes([spend_type])
|
||||
if in_type == SIGHASH_ANYONECANPAY:
|
||||
ss += txTo.vin[input_index].prevout.serialize()
|
||||
ss += struct.pack("<q", spent_utxos[input_index].nValue)
|
||||
ss += ser_string(spk)
|
||||
ss += struct.pack("<I", txTo.vin[input_index].nSequence)
|
||||
else:
|
||||
ss += struct.pack("<I", input_index)
|
||||
if (spend_type & 1):
|
||||
ss += sha256(ser_string(annex))
|
||||
if out_type == SIGHASH_SINGLE:
|
||||
if input_index < len(txTo.vout):
|
||||
ss += sha256(txTo.vout[input_index].serialize())
|
||||
else:
|
||||
ss += bytes(0 for _ in range(32))
|
||||
if (scriptpath):
|
||||
ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(script))
|
||||
ss += bytes([0])
|
||||
ss += struct.pack("<i", codeseparator_pos)
|
||||
assert len(ss) == 175 - (in_type == SIGHASH_ANYONECANPAY) * 49 - (out_type != SIGHASH_ALL and out_type != SIGHASH_SINGLE) * 32 + (annex is not None) * 32 + scriptpath * 37
|
||||
return TaggedHash("TapSighash", ss)
|
||||
|
||||
def taproot_tree_helper(scripts):
|
||||
if len(scripts) == 0:
|
||||
return ([], bytes(0 for _ in range(32)))
|
||||
if len(scripts) == 1:
|
||||
# One entry: treat as a leaf
|
||||
script = scripts[0]
|
||||
assert(not callable(script))
|
||||
if isinstance(script, list):
|
||||
return taproot_tree_helper(script)
|
||||
assert(isinstance(script, tuple))
|
||||
version = LEAF_VERSION_TAPSCRIPT
|
||||
name = script[0]
|
||||
code = script[1]
|
||||
if len(script) == 3:
|
||||
version = script[2]
|
||||
assert version & 1 == 0
|
||||
assert isinstance(code, bytes)
|
||||
h = TaggedHash("TapLeaf", bytes([version]) + ser_string(code))
|
||||
if name is None:
|
||||
return ([], h)
|
||||
return ([(name, version, code, bytes())], h)
|
||||
elif len(scripts) == 2 and callable(scripts[1]):
|
||||
# Two entries, and the right one is a function
|
||||
left, left_h = taproot_tree_helper(scripts[0:1])
|
||||
right_h = scripts[1](left_h)
|
||||
left = [(name, version, script, control + right_h) for name, version, script, control in left]
|
||||
right = []
|
||||
else:
|
||||
# Two or more entries: descend into each side
|
||||
split_pos = len(scripts) // 2
|
||||
left, left_h = taproot_tree_helper(scripts[0:split_pos])
|
||||
right, right_h = taproot_tree_helper(scripts[split_pos:])
|
||||
left = [(name, version, script, control + right_h) for name, version, script, control in left]
|
||||
right = [(name, version, script, control + left_h) for name, version, script, control in right]
|
||||
if right_h < left_h:
|
||||
right_h, left_h = left_h, right_h
|
||||
h = TaggedHash("TapBranch", left_h + right_h)
|
||||
return (left + right, h)
|
||||
|
||||
TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,inner_pubkey,negflag,tweak,leaves")
|
||||
TaprootLeafInfo = namedtuple("TaprootLeafInfo", "script,version,merklebranch")
|
||||
|
||||
def taproot_construct(pubkey, scripts=None):
|
||||
"""Construct a tree of Taproot spending conditions
|
||||
|
||||
pubkey: an ECPubKey object for the internal pubkey
|
||||
scripts: a list of items; each item is either:
|
||||
- a (name, CScript) tuple
|
||||
- a (name, CScript, leaf version) tuple
|
||||
- another list of items (with the same structure)
|
||||
- a function, which specifies how to compute the hashing partner
|
||||
in function of the hash of whatever it is combined with
|
||||
|
||||
Returns: script (sPK or redeemScript), tweak, {name:(script, leaf version, negation flag, innerkey, merklepath), ...}
|
||||
"""
|
||||
if scripts is None:
|
||||
scripts = []
|
||||
|
||||
ret, h = taproot_tree_helper(scripts)
|
||||
tweak = TaggedHash("TapTweak", pubkey + h)
|
||||
tweaked, negated = tweak_add_pubkey(pubkey, tweak)
|
||||
leaves = dict((name, TaprootLeafInfo(script, version, merklebranch)) for name, version, script, merklebranch in ret)
|
||||
return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves)
|
||||
|
||||
def is_op_success(o):
|
||||
return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xfe)
|
||||
|
@ -70,6 +70,7 @@ TEST_FRAMEWORK_MODULES = [
|
||||
"address",
|
||||
"blocktools",
|
||||
"muhash",
|
||||
"key",
|
||||
"script",
|
||||
"segwit_addr",
|
||||
"util",
|
||||
@ -107,6 +108,7 @@ BASE_SCRIPTS = [
|
||||
'mempool_updatefromblock.py',
|
||||
'wallet_dump.py',
|
||||
'wallet_listtransactions.py',
|
||||
'feature_taproot.py',
|
||||
# vv Tests less than 60s vv
|
||||
'p2p_sendheaders.py',
|
||||
'wallet_importmulti.py',
|
||||
|
Loading…
Reference in New Issue
Block a user