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 118 validation (SIGHASH_ANYPREVOUT) #56

Merged
merged 8 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ test_fuzz_fuzz_SOURCES = \
$(FUZZ_WALLET_SRC) \
test/fuzz/addition_overflow.cpp \
test/fuzz/addrman.cpp \
test/fuzz/anyprevout.cpp \
test/fuzz/asmap.cpp \
test/fuzz/asmap_direct.cpp \
test/fuzz/autofile.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_
enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
DEPLOYMENT_CHECKTEMPLATEVERIFY, // Deployment of CTV (BIP 119)
DEPLOYMENT_ANYPREVOUT,
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
MAX_VERSION_BITS_DEPLOYMENTS
};
Expand Down
6 changes: 6 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
/*.name =*/ "checktemplateverify",
/*.gbt_force =*/ true,
},
{
/*.name =*/ "anyprevout",
/*.gbt_force =*/ true,
},
};

std::string DeploymentName(Consensus::BuriedDeployment dep)
Expand Down Expand Up @@ -83,6 +87,8 @@ const std::map<std::string, uint32_t> g_verify_flag_names{
FLAG_NAME(DEFAULT_CHECK_TEMPLATE_VERIFY_HASH),
FLAG_NAME(DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH),
FLAG_NAME(DISCOURAGE_CHECK_TEMPLATE_VERIFY_HASH),
FLAG_NAME(ANYPREVOUT),
FLAG_NAME(DISCOURAGE_ANYPREVOUT),
};
#undef FLAG_NAME

Expand Down
9 changes: 9 additions & 0 deletions src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class CMainParams : public CChainParams {
consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = SetupDeployment{.activate = 0x30000000, .abandon = 0, .never = true};
consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .never = true};
consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .never = true};

consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000063c4ebd298db40af57541800");
consensus.defaultAssumeValid = uint256S("0x000000000000000000026811d149d4d261995ec5b3f64f439a0a10e1a464af9a"); // 824000
Expand Down Expand Up @@ -243,6 +244,7 @@ class CTestNetParams : public CChainParams {
consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = SetupDeployment{.activate = 0x30000000, .abandon = 0, .never = true};
consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .never = true};
consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .never = true};

consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000c59b14e264ba6c15db9");
consensus.defaultAssumeValid = uint256S("0x000000000001323071f38f21ea5aae529ece491eadaccce506a59bcc2d968917"); // 2550000
Expand Down Expand Up @@ -378,6 +380,12 @@ class SigNetParams : public CChainParams {
.activate = 0x60007700,
.abandon = 0x40007700,
};
consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{
.start = 1625875200, // 2021-07-10
.timeout = 1941408000, // 2031-07-10
.activate = 0x60007600,
.abandon = 0x40007600,
};

RenounceDeployments(options.renounce, consensus.vDeployments);

Expand Down Expand Up @@ -451,6 +459,7 @@ class CRegTestParams : public CChainParams
// 0x3000_0000 = bit 28 plus versionbits signalling; 0x5000_0000 = bit 38 plus VERSIONBITS_TOP_ABANDON
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = SetupDeployment{.start = 0, .timeout = Consensus::HereticalDeployment::NO_TIMEOUT, .activate = 0x30000000, .abandon = 0x50000000};
consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .always = true};
consensus.vDeployments[Consensus::DEPLOYMENT_ANYPREVOUT] = SetupDeployment{.activate = 0x60007600, .abandon = 0x40007600, .always = true};

consensus.nMinimumChainWork = uint256{};
consensus.defaultAssumeValid = uint256{};
Expand Down
3 changes: 2 additions & 1 deletion src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH |
SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH};
SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH |
SCRIPT_VERIFY_ANYPREVOUT};

/** 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
1 change: 1 addition & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_ANYPREVOUT);
return softforks;
}
} // anon namespace
Expand Down
84 changes: 63 additions & 21 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu
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);
assert(execdata.m_internal_key); // caller must provide the internal key

/*
* The following validation sequence is consensus critical. Please note how --
Expand All @@ -363,12 +364,27 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr
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)) {
if (success && !checker.CheckSchnorrSignature(sig, KeyVersion::TAPROOT, pubkey, sigversion, execdata, serror)) {
return false; // serror is set
}
} else if ((pubkey.size() == 1 || pubkey.size() == 33) && pubkey[0] == BIP118_PUBKEY_PREFIX) {
if ((flags & SCRIPT_VERIFY_DISCOURAGE_ANYPREVOUT) != 0) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_ANYPREVOUT);
} else if ((flags & SCRIPT_VERIFY_ANYPREVOUT) == 0) {
return true;
} else if (pubkey.size() == 1) {
if (success && !checker.CheckSchnorrSignature(sig, KeyVersion::ANYPREVOUT, *execdata.m_internal_key, sigversion, execdata, serror)) {
return false; // serror is set
}
} else { // pubkey.size() == 33
if (success && !checker.CheckSchnorrSignature(sig, KeyVersion::ANYPREVOUT, Span(pubkey).subspan(1), sigversion, execdata, serror)) {
return false; // serror is set
}
}
} else {
/*
* New public key version softforks should be defined before this `else` block.
Expand Down Expand Up @@ -1583,21 +1599,20 @@ static bool HandleMissingData(MissingDataBehavior mdb)
}

template<typename T>
bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb)
bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, KeyVersion keyversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb)
{
uint8_t ext_flag, key_version;
uint8_t ext_flag;
assert(
(keyversion == KeyVersion::TAPROOT) ||
(keyversion == KeyVersion::ANYPREVOUT && sigversion == SigVersion::TAPSCRIPT)
);
switch (sigversion) {
case SigVersion::TAPROOT:
ext_flag = 0;
// key_version is not used and left uninitialized.
// keyversion is not used.
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);
Expand All @@ -1616,13 +1631,27 @@ bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, cons
// 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;

switch(hash_type) {
case 0: case 1: case 2: case 3:
case 0x81: case 0x82: case 0x83:
break;
case 0x41: case 0x42: case 0x43:
case 0xc1: case 0xc2: case 0xc3:
if (keyversion == KeyVersion::ANYPREVOUT) {
break;
} else {
return false;
}
default:
return false;
}
ss << hash_type;

// Transaction level data
ss << tx_to.nVersion;
ss << tx_to.nLockTime;
if (input_type != SIGHASH_ANYONECANPAY) {
if (input_type != SIGHASH_ANYONECANPAY && input_type != SIGHASH_ANYPREVOUT && input_type != SIGHASH_ANYPREVOUTANYSCRIPT) {
ss << cache.m_prevouts_single_hash;
ss << cache.m_spent_amounts_single_hash;
ss << cache.m_spent_scripts_single_hash;
Expand All @@ -1641,6 +1670,13 @@ bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, cons
ss << tx_to.vin[in_pos].prevout;
ss << cache.m_spent_outputs[in_pos];
ss << tx_to.vin[in_pos].nSequence;
} else if (input_type == SIGHASH_ANYPREVOUT) {
assert(keyversion == KeyVersion::ANYPREVOUT);
ss << cache.m_spent_outputs[in_pos];
ss << tx_to.vin[in_pos].nSequence;
} else if (input_type == SIGHASH_ANYPREVOUTANYSCRIPT) {
assert(keyversion == KeyVersion::ANYPREVOUT);
ss << tx_to.vin[in_pos].nSequence;
} else {
ss << in_pos;
}
Expand All @@ -1662,8 +1698,12 @@ bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, cons
// Additional data for BIP 342 signatures
if (sigversion == SigVersion::TAPSCRIPT) {
assert(execdata.m_tapleaf_hash_init);
ss << execdata.m_tapleaf_hash;
ss << key_version;
if (input_type != SIGHASH_ANYPREVOUTANYSCRIPT) {
ss << execdata.m_tapleaf_hash;
} else {
assert(keyversion == KeyVersion::ANYPREVOUT);
}
ss << uint8_t(keyversion);
assert(execdata.m_codeseparator_pos_init);
ss << execdata.m_codeseparator_pos;
}
Expand Down Expand Up @@ -1778,18 +1818,19 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
}

template <class T>
bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey_in, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const
bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const unsigned char> sig, KeyVersion pubkeyver, Span<const unsigned char> pubkey, SigVersion sigversion, 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);
assert(pubkey.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};
XOnlyPubKey pubkey_xonly{pubkey};

uint8_t hashtype = SIGHASH_DEFAULT;
if (sig.size() == 65) {
Expand All @@ -1798,10 +1839,10 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns
}
uint256 sighash;
if (!this->txdata) return HandleMissingData(m_mdb);
if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) {
if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, pubkeyver, *this->txdata, m_mdb)) {
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
}
if (!VerifySchnorrSignature(sig, pubkey, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
if (!VerifySchnorrSignature(sig, pubkey_xonly, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
return true;
}

Expand Down Expand Up @@ -1980,12 +2021,13 @@ uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint25
return k;
}

static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash)
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash, std::optional<XOnlyPubKey>& internal_key)
{
assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE);
assert(program.size() >= uint256::size());
//! The internal pubkey (x-only, so no Y coordinate parity).
const XOnlyPubKey p{Span{control}.subspan(1, TAPROOT_CONTROL_BASE_SIZE - 1)};
internal_key = p;
//! The output pubkey (taken from the scriptPubKey).
const XOnlyPubKey q{program};
// Compute the Merkle root from the leaf and the provided path.
Expand Down Expand Up @@ -2039,7 +2081,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
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)) {
if (!checker.CheckSchnorrSignature(stack.front(), KeyVersion::TAPROOT, program, SigVersion::TAPROOT, execdata, serror)) {
return false; // serror is set
}
return set_success(serror);
Expand All @@ -2051,7 +2093,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
}
execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script);
if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) {
if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash, execdata.m_internal_key)) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
}
execdata.m_tapleaf_hash_init = true;
Expand Down
Loading