This commit is contained in:
Niklas Gögge 2025-01-08 20:42:38 +01:00 committed by GitHub
commit a82ced19d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 199 additions and 20 deletions

View file

@ -1781,6 +1781,35 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
return true;
}
template <class T>
bool GenericTransactionSignatureChecker<T>::CheckTaprootCommitment(
const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const
{
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)};
//! The output pubkey (taken from the scriptPubKey).
const XOnlyPubKey q{program};
// Compute the Merkle root from the leaf and the provided path.
const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash);
// Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
return q.CheckTapTweak(p, merkle_root, control[0] & 1);
}
template <class T>
bool GenericTransactionSignatureChecker<T>::CheckWitnessScriptHash(
Span<const unsigned char> program,
const CScript& exec_script) const
{
assert(program.size() >= uint256::size());
uint256 hash_exec_script;
CSHA256().Write(exec_script.data(), exec_script.size()).Finalize(hash_exec_script.begin());
return memcmp(hash_exec_script.begin(), program.data(), uint256::size()) == 0;
}
// explicit instantiation
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;
@ -1856,20 +1885,6 @@ 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)
{
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)};
//! The output pubkey (taken from the scriptPubKey).
const XOnlyPubKey q{program};
// Compute the Merkle root from the leaf and the provided path.
const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash);
// Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
return q.CheckTapTweak(p, merkle_root, 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)
@ -1884,9 +1899,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
}
const valtype& script_bytes = SpanPopBack(stack);
exec_script = CScript(script_bytes.begin(), script_bytes.end());
uint256 hash_exec_script;
CSHA256().Write(exec_script.data(), exec_script.size()).Finalize(hash_exec_script.begin());
if (memcmp(hash_exec_script.begin(), program.data(), 32)) {
if (!checker.CheckWitnessScriptHash(program, exec_script)) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
}
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror);
@ -1927,7 +1940,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 (!checker.CheckTaprootCommitment(control, program, execdata.m_tapleaf_hash)) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
}
execdata.m_tapleaf_hash_init = true;

View file

@ -265,6 +265,18 @@ public:
return false;
}
virtual bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const
{
return false;
}
virtual bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const
{
return false;
}
virtual ~BaseSignatureChecker() = default;
};
@ -301,6 +313,11 @@ public:
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;
bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const override;
bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const override;
};
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
@ -332,6 +349,18 @@ public:
{
return m_checker.CheckSequence(nSequence);
}
bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const override
{
return m_checker.CheckTaprootCommitment(control, program, tapleaf_hash);
}
bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const override
{
return m_checker.CheckWitnessScriptHash(program, exec_script);
}
};
/** Compute the BIP341 tapleaf hash from leaf version & script. */

View file

@ -704,6 +704,19 @@ public:
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return sig.size() != 0; }
bool CheckLockTime(const CScriptNum& nLockTime) const override { return true; }
bool CheckSequence(const CScriptNum& nSequence) const override { return true; }
bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const override
{
return true;
}
bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const override
{
return true;
}
};
}

View file

@ -299,6 +299,18 @@ const struct CheckerContext: BaseSignatureChecker {
}
bool CheckLockTime(const CScriptNum& nLockTime) const override { return nLockTime.GetInt64() & 1; }
bool CheckSequence(const CScriptNum& nSequence) const override { return nSequence.GetInt64() & 1; }
bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const override
{
return true;
}
bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const override
{
return true;
}
} CHECKER_CTX;
//! Context to check for duplicates when instancing a Node.

View file

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/amount.h>
#include <crypto/siphash.h>
#include <primitives/transaction.h>
#include <script/interpreter.h>
#include <serialize.h>
@ -15,7 +16,75 @@
#include <utility>
#include <vector>
FUZZ_TARGET(script_flags)
namespace {
inline uint64_t HashSig(Span<const unsigned char> sig)
{
CSipHasher hasher{0xdead, 0xbeef};
hasher.Write(sig);
return hasher.Finalize();
}
// Reduce a CScriptNum to one bit
inline bool HashScriptNum(const CScriptNum& num)
{
CSipHasher hasher{0xdead, 0xbeef};
hasher.Write(num.getvch());
return hasher.Finalize() & 1;
}
class FuzzedSignatureChecker : public BaseSignatureChecker
{
public:
FuzzedSignatureChecker(const CTransaction* tx, unsigned int in,
const CAmount& amount, const PrecomputedTransactionData& tx_data,
MissingDataBehavior mdb) {}
bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& pub_key,
const CScript& script_code, SigVersion sig_version) const override
{
return !sig.empty() && (HashSig(sig) & 1);
}
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pub_key,
SigVersion sig_version, ScriptExecutionData& exec_data,
ScriptError* script_error = nullptr) const override
{
uint64_t fuzz_hash{HashSig(sig)};
bool sig_ok = fuzz_hash & 1;
if (!sig_ok && script_error) {
constexpr std::array<ScriptError, 3> schnorr_errs = {
SCRIPT_ERR_SCHNORR_SIG,
SCRIPT_ERR_SCHNORR_SIG_SIZE,
SCRIPT_ERR_SCHNORR_SIG_HASHTYPE};
*script_error = schnorr_errs[(fuzz_hash >> 1) % schnorr_errs.size()];
}
return sig_ok;
}
bool CheckLockTime(const CScriptNum& lock_time) const override
{
return HashScriptNum(lock_time);
}
bool CheckSequence(const CScriptNum& sequence) const override
{
return HashScriptNum(sequence);
}
bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const override
{
return !program.empty() && (program[0] & 1);
}
bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const override
{
return !program.empty() && (program[0] & 1);
}
};
template <typename SigChecker>
void CheckScriptFlags(FuzzBufferType buffer)
{
if (buffer.size() > 100'000) return;
DataStream ds{buffer};
@ -45,7 +114,7 @@ FUZZ_TARGET(script_flags)
for (unsigned i = 0; i < tx.vin.size(); ++i) {
const CTxOut& prevout = txdata.m_spent_outputs.at(i);
const TransactionSignatureChecker checker{&tx, i, prevout.nValue, txdata, MissingDataBehavior::ASSERT_FAIL};
const SigChecker checker{&tx, i, prevout.nValue, txdata, MissingDataBehavior::ASSERT_FAIL};
ScriptError serror;
const bool ret = VerifyScript(tx.vin.at(i).scriptSig, prevout.scriptPubKey, &tx.vin.at(i).scriptWitness, verify_flags, checker, &serror);
@ -69,3 +138,21 @@ FUZZ_TARGET(script_flags)
return;
}
}
} // namespace
/**
* Both of the following harnesses test that all script verification flags only
* tighten the interpreter rules (i.e. they represent soft-forks).
*/
FUZZ_TARGET(script_flags)
{
CheckScriptFlags<TransactionSignatureChecker>(buffer);
}
// Signature validation is mocked out through FuzzedSignatureChecker
FUZZ_TARGET(script_flags_mocked)
{
CheckScriptFlags<FuzzedSignatureChecker>(buffer);
}

View file

@ -44,6 +44,18 @@ public:
return m_fuzzed_data_provider.ConsumeBool();
}
bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const override
{
return m_fuzzed_data_provider.ConsumeBool();
}
bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const override
{
return m_fuzzed_data_provider.ConsumeBool();
}
virtual ~FuzzedSignatureChecker() = default;
};
} // namespace

View file

@ -289,6 +289,19 @@ public:
// Delegate to Satisfier.
return ctx.CheckOlder(sequence.GetInt64());
}
bool CheckTaprootCommitment(const std::vector<unsigned char>& control,
const std::vector<unsigned char>& program,
const uint256& tapleaf_hash) const override
{
return true;
}
bool CheckWitnessScriptHash(Span<const unsigned char> program,
const CScript& exec_script) const override
{
return true;
}
};
using Fragment = miniscript::Fragment;