Skip to content

Commit

Permalink
Merge #19953: Implement BIP 340-342 validation (Schnorr/taproot/tapsc…
Browse files Browse the repository at this point in the history
…ript)

0e2a5e4 tests: dumping and minimizing of script assets data (Pieter Wuille)
4567ba0 tests: add generic qa-asset-based script verification unit test (Pieter Wuille)
f06e6d0 tests: functional tests for Schnorr/Taproot/Tapscript (Pieter Wuille)
3c22663 tests: add BIP340 Schnorr signature support to test framework (Pieter Wuille)
206fb18 --- [TAPROOT] Tests --- (Pieter Wuille)
d7ff237 Activate Taproot/Tapscript on regtest (BIP 341, BIP 342) (Pieter Wuille)
e9a021d Make Taproot spends standard + policy limits (Pieter Wuille)
865d2c3 --- [TAPROOT] Regtest activation and policy --- (Pieter Wuille)
72422ce Implement Tapscript script validation rules (BIP 342) (Johnson Lau)
330de89 Use ScriptExecutionData to pass through annex hash (Pieter Wuille)
8bbed4b Implement Taproot validation (BIP 341) (Pieter Wuille)
0664f5f Support for Schnorr signatures and integration in SignatureCheckers (BIP 340) (Pieter Wuille)
5de246c Implement Taproot signature hashing (BIP 341) (Johnson Lau)
9eb5908 Add TaggedHash function (BIP 340) (Pieter Wuille)
450d2b2 --- [TAPROOT] BIP340/341/342 consensus rules --- (Pieter Wuille)
5d62e3a refactor: keep spent outputs in PrecomputedTransactionData (Pieter Wuille)
8bd2b4e refactor: rename scriptPubKey in VerifyWitnessProgram to exec_script (Pieter Wuille)
107b57d scripted-diff: put ECDSA in name of signature functions (Pieter Wuille)
f8c099e --- [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](bitcoin/bitcoin#19953 (comment)). 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 bitcoin/bitcoin#17977 (see discussion following [this comment](bitcoin/bitcoin#17977 (comment))), and will have further changes squashed/rebased. The history of this PR can be found in #19997.

ACKs for top commit:
  instagibbs:
    reACK bitcoin/bitcoin@0e2a5e4
  benthecarman:
    reACK 0e2a5e4
  kallewoof:
    reACK 0e2a5e4
  jonasnick:
    ACK 0e2a5e4 almost only looked at bip340/libsecp related code
  jonatack:
    ACK 0e2a5e4 modulo the last four commits (tests) that I plan to finish reviewing tomorrow
  fjahr:
    reACK 0e2a5e4
  achow101:
    ACK 0e2a5e4

Tree-SHA512: 1b00314450a2938a22bccbb4e177230cf08bd365d72055f9d526891f334b364c997e260c10bc19ca78440b6767712c9feea7faad9a1045dd51a5b96f7ca8146e
  • Loading branch information
laanwj committed Oct 15, 2020
2 parents 8ed37f6 + 0e2a5e4 commit 3caee16
Show file tree
Hide file tree
Showing 44 changed files with 2,985 additions and 121 deletions.
2 changes: 1 addition & 1 deletion build_msvc/libsecp256k1/libsecp256k1.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
5 changes: 2 additions & 3 deletions ci/test/04_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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/"

Expand Down
4 changes: 2 additions & 2 deletions ci/test/06_script_b.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ class CMainParams : public CChainParams {
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");

Expand Down Expand Up @@ -197,6 +202,11 @@ class CTestNetParams : public CChainParams {
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");

Expand Down Expand Up @@ -380,6 +390,9 @@ class CRegTestParams : public CChainParams {
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");
Expand Down
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
10 changes: 10 additions & 0 deletions src/hash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}
9 changes: 9 additions & 0 deletions src/hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <uint256.h>
#include <version.h>

#include <string>
#include <vector>

typedef uint256 ChainCode;
Expand Down Expand Up @@ -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
34 changes: 33 additions & 1 deletion src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

#include <consensus/validation.h>
#include <coins.h>

#include <span.h>

CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
{
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
8 changes: 7 additions & 1 deletion src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 22 additions & 0 deletions src/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <secp256k1.h>
#include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h>

namespace
{
Expand Down Expand Up @@ -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;
Expand Down
24 changes: 23 additions & 1 deletion src/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <hash.h>
#include <serialize.h>
#include <span.h>
#include <uint256.h>

#include <stdexcept>
Expand Down Expand Up @@ -169,7 +170,7 @@ class CPubKey
/*
* Check syntactic correctness.
*
* Note that this is consensus critical as CheckSig() calls it!
* Note that this is consensus critical as CheckECDSASignature() calls it!
*/
bool IsValid() const
{
Expand Down Expand Up @@ -206,6 +207,27 @@ class CPubKey
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];
Expand Down
1 change: 1 addition & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 3caee16

Please sign in to comment.