Skip to content

Implement BIP 119 validation (OP_CHECKTEMPLATEVERIFY) #66

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

Merged
merged 14 commits into from
Oct 9, 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
2 changes: 2 additions & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ JSON_TEST_FILES = \
test/data/bip341_wallet_vectors.json \
test/data/base58_encode_decode.json \
test/data/blockfilters.json \
test/data/ctvhash.json \
test/data/key_io_valid.json \
test/data/key_io_invalid.json \
test/data/script_tests.json \
Expand Down Expand Up @@ -91,6 +92,7 @@ BITCOIN_TESTS =\
test/compilerbug_tests.cpp \
test/compress_tests.cpp \
test/crypto_tests.cpp \
test/ctvhash_tests.cpp \
test/cuckoocache_tests.cpp \
test/dbwrapper_tests.cpp \
test/denialofservice_tests.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/addresstype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
case TxoutType::MULTISIG:
case TxoutType::NULL_DATA:
case TxoutType::NONSTANDARD:
case TxoutType::TX_BARE_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH:
addressRet = CNoDestination(scriptPubKey);
return false;
} // no default case, so the compiler can warn about missing cases
Expand Down
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_

enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
DEPLOYMENT_CHECKTEMPLATEVERIFY, // Deployment of CTV (BIP 119)
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
MAX_VERSION_BITS_DEPLOYMENTS
};
Expand Down
7 changes: 7 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
/*.name =*/ "testdummy",
/*.gbt_force =*/ true,
},
{
/*.name =*/ "checktemplateverify",
/*.gbt_force =*/ true,
},
};

std::string DeploymentName(Consensus::BuriedDeployment dep)
Expand Down Expand Up @@ -76,6 +80,9 @@ const std::map<std::string, uint32_t> g_verify_flag_names{
FLAG_NAME(DISCOURAGE_UPGRADABLE_PUBKEYTYPE),
FLAG_NAME(DISCOURAGE_OP_SUCCESS),
FLAG_NAME(DISCOURAGE_UPGRADABLE_TAPROOT_VERSION),
FLAG_NAME(DEFAULT_CHECK_TEMPLATE_VERIFY_HASH),
FLAG_NAME(DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH),
FLAG_NAME(DISCOURAGE_CHECK_TEMPLATE_VERIFY_HASH),
};
#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 @@ -142,6 +142,7 @@ class CMainParams : public CChainParams {
consensus.nRuleChangeActivationThreshold = 1815; // 90% of 2016
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.nMinimumChainWork = uint256{"000000000000000000000000000000000000000088e186b70e0862c193ec44d6"};
consensus.defaultAssumeValid = uint256{"000000000000000000011c5890365bdbe5d25b97ce0057589acaef4f1a57263f"}; // 856760
Expand Down Expand Up @@ -258,6 +259,7 @@ class CTestNetParams : public CChainParams {
consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains
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.nMinimumChainWork = uint256{"000000000000000000000000000000000000000000000f209695166be8b61fa9"};
consensus.defaultAssumeValid = uint256{"000000000000000465b1a66c9f386308e8c75acef9201f3f577811da09fc90ad"}; // 2873500
Expand Down Expand Up @@ -481,6 +483,12 @@ class SigNetParams : public CChainParams {
consensus.MinBIP9WarningHeight = 0;
consensus.powLimit = uint256{"00000377ae000000000000000000000000000000000000000000000000000000"};
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY] = SetupDeployment{.activate = 0x30000000, .abandon = 0, .never = true};
consensus.vDeployments[Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY] = SetupDeployment{
.start = 1654041600, // 2022-06-01
.timeout = 1969660800, // 2032-06-01
.activate = 0x60007700,
.abandon = 0x40007700,
};

RenounceDeployments(options.renounce, consensus.vDeployments);

Expand Down Expand Up @@ -554,6 +562,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.nMinimumChainWork = uint256{};
consensus.defaultAssumeValid = uint256{};
Expand Down
5 changes: 5 additions & 0 deletions src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
return false;
}
} else if (whichType == TxoutType::TX_BARE_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH) {
// after activation, only allow bare with no scriptsig.
// pre-activation disallowing enforced via discouraged logic in the
// interpreter.
if (tx.vin[i].scriptSig.size() != 0) return false;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI
SCRIPT_VERIFY_CONST_SCRIPTCODE |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE};
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH |
SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH};

/** 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 @@ -1393,6 +1393,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY);
return softforks;
}
} // anon namespace
Expand Down
4 changes: 4 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ static RPCHelpMan decodescript()
case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
// don't wrap CTV because P2SH CTV is a hash cycle
case TxoutType::TX_BARE_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH:
// Should not be wrapped
return false;
} // no default case, so the compiler can warn about missing cases
Expand Down Expand Up @@ -601,6 +603,8 @@ static RPCHelpMan decodescript()
case TxoutType::WITNESS_V0_SCRIPTHASH:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
// don't wrap CTV because P2SH CTV is a hash cycle
case TxoutType::TX_BARE_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH:
// Should not be wrapped
return false;
} // no default case, so the compiler can warn about missing cases
Expand Down
128 changes: 126 additions & 2 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,42 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
break;
}

case OP_NOP1: case OP_NOP4: case OP_NOP5:
case OP_CHECKTEMPLATEVERIFY:
{
if (flags & SCRIPT_VERIFY_DISCOURAGE_CHECK_TEMPLATE_VERIFY_HASH) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
}

// if flags not enabled; treat as a NOP4
if (!(flags & SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH)) {
break;
}

if (stack.size() < 1) {
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
}

// If the argument was not 32 bytes, treat as OP_NOP4:
switch (stack.back().size()) {
case 32:
{
const Span<const unsigned char> hash{stack.back()};
if (!checker.CheckDefaultCheckTemplateVerifyHash(hash)) {
return set_error(serror, SCRIPT_ERR_TEMPLATE_MISMATCH);
}
break;
}
default:
// future upgrade can add semantics for this opcode with different length args
// so discourage use when applicable
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
}
}
}
break;

case OP_NOP1: case OP_NOP5:
case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10:
{
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
Expand Down Expand Up @@ -1377,6 +1412,18 @@ uint256 GetSpentAmountsSHA256(const std::vector<CTxOut>& outputs_spent)
HashWriter ss{};
for (const auto& txout : outputs_spent) {
ss << txout.nValue;

}
return ss.GetSHA256();
}

/** Compute the (single) SHA256 of the concatenation of all scriptSigs in a tx. */
template <class T>
uint256 GetScriptSigsSHA256(const T& txTo)
{
HashWriter ss{};
for (const auto& in : txTo.vin) {
ss << in.scriptSig;
}
return ss.GetSHA256();
}
Expand All @@ -1391,9 +1438,63 @@ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent)
return ss.GetSHA256();
}

/* Not Exported, just convenience */
template<typename TxType>
uint256 GetDefaultCheckTemplateVerifyHashWithScript(const TxType& tx, const uint256& outputs_hash, const uint256& sequences_hash,
const uint256& scriptSig_hash, const uint32_t input_index) {
auto h = HashWriter{}
<< tx.version
<< tx.nLockTime
<< scriptSig_hash
<< uint32_t(tx.vin.size())
<< sequences_hash
<< uint32_t(tx.vout.size())
<< outputs_hash
<< input_index;
return h.GetSHA256();
}

template<typename TxType>
uint256 GetDefaultCheckTemplateVerifyHashEmptyScript(const TxType& tx, const uint256& outputs_hash, const uint256& sequences_hash,
const uint32_t input_index) {
auto h = HashWriter{}
<< tx.version
<< tx.nLockTime
<< uint32_t(tx.vin.size())
<< sequences_hash
<< uint32_t(tx.vout.size())
<< outputs_hash
<< input_index;
return h.GetSHA256();
}

} // namespace

template<typename TxType>
uint256 GetDefaultCheckTemplateVerifyHash(const TxType& tx, uint32_t input_index) {
return GetDefaultCheckTemplateVerifyHash(tx, GetOutputsSHA256(tx), GetSequencesSHA256(tx), input_index);
}

template<typename TxType>
static bool NoScriptSigs(const TxType& tx)
{
return std::all_of(tx.vin.begin(), tx.vin.end(), [](const CTxIn& c) { return c.scriptSig.empty(); });
}

template<typename TxType>
uint256 GetDefaultCheckTemplateVerifyHash(const TxType& tx, const uint256& outputs_hash, const uint256& sequences_hash,
const uint32_t input_index) {
return NoScriptSigs(tx) ? GetDefaultCheckTemplateVerifyHashEmptyScript(tx, outputs_hash, sequences_hash, input_index) :
GetDefaultCheckTemplateVerifyHashWithScript(tx, outputs_hash, sequences_hash, GetScriptSigsSHA256(tx), input_index);
}

template
uint256 GetDefaultCheckTemplateVerifyHash(const CTransaction& tx, const uint256& outputs_hash, const uint256& sequences_hash,
const uint32_t input_index);
template
uint256 GetDefaultCheckTemplateVerifyHash(const CMutableTransaction& tx, const uint256& outputs_hash, const uint256& sequences_hash,
const uint32_t input_index);

template <class T>
void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs, bool force)
{
Expand All @@ -1405,6 +1506,8 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
m_spent_outputs_ready = true;
}

// TODO: Improve this heuristic
bool uses_bip119_ctv = true;
// Determine which precomputation-impacting features this transaction uses.
bool uses_bip143_segwit = force;
bool uses_bip341_taproot = force;
Expand All @@ -1427,11 +1530,16 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
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) {
if (uses_bip143_segwit || uses_bip341_taproot || uses_bip119_ctv) {
// Computations shared between both sighash schemes.
m_prevouts_single_hash = GetPrevoutsSHA256(txTo);
m_sequences_single_hash = GetSequencesSHA256(txTo);
m_outputs_single_hash = GetOutputsSHA256(txTo);

// 0 hash used to signal if we should skip scriptSigs
// when re-computing for different indexes.
m_scriptSigs_single_hash = NoScriptSigs(txTo) ? uint256{} : GetScriptSigsSHA256(txTo);
m_bip119_ctv_ready = true;
}
if (uses_bip143_segwit) {
hashPrevouts = SHA256Uint256(m_prevouts_single_hash);
Expand Down Expand Up @@ -1781,6 +1889,22 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
return true;
}

template <class T>
bool GenericTransactionSignatureChecker<T>::CheckDefaultCheckTemplateVerifyHash(const Span<const unsigned char>& hash) const
{
// Should already be checked before calling...
assert(hash.size() == 32);
if (txdata && txdata->m_bip119_ctv_ready) {
assert(txTo != nullptr);
uint256 hash_tmpl = txdata->m_scriptSigs_single_hash.IsNull() ?
GetDefaultCheckTemplateVerifyHashEmptyScript(*txTo, txdata->m_outputs_single_hash, txdata->m_sequences_single_hash, nIn) :
GetDefaultCheckTemplateVerifyHashWithScript(*txTo, txdata->m_outputs_single_hash, txdata->m_sequences_single_hash,
txdata->m_scriptSigs_single_hash, nIn);
return std::equal(hash_tmpl.begin(), hash_tmpl.end(), hash.data());
} else {
return HandleMissingData(m_mdb);
}
}
// explicit instantiation
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;
Expand Down
Loading
Loading