Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement BIP 340-342 validation (Schnorr/taproot/tapscript) #19953

Merged
merged 19 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1645,7 +1645,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"
jnewbery marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1082,6 +1083,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)
maflcko marked this conversation as resolved.
Show resolved Hide resolved
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
sipa marked this conversation as resolved.
Show resolved Hide resolved
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;
maflcko marked this conversation as resolved.
Show resolved Hide resolved
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:
maflcko marked this conversation as resolved.
Show resolved Hide resolved
// - 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) {
sipa marked this conversation as resolved.
Show resolved Hide resolved
// 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!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side-note, it could be worthy to document what is meaned here by "syntactic correctness" if it's consensus criticial. AFAICT, pubkey must be superior to 0 ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments in #20207.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/** Compute the Taproot tweak as specified in BIP341, with *this as internal
- is the asterisk in *this intentional?

*/
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 @@ -1355,6 +1355,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