Skip to content

Commit

Permalink
Merge pull request #67 from ajtowns/202409-inq28-apo
Browse files Browse the repository at this point in the history
Implement BIP 118 validation (SIGHASH_ANYPREVOUT)
  • Loading branch information
ajtowns authored Oct 9, 2024
2 parents ff1cc73 + cd5e10e commit aa0c738
Show file tree
Hide file tree
Showing 23 changed files with 841 additions and 93 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,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
18 changes: 16 additions & 2 deletions src/bitcoin-util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ static void SetupBitcoinUtilArgs(ArgsManager &argsman)
argsman.AddArg("-tx", "The tx (hex encoded)", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-input", "The index of the input being spent", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-spent_output", "The spent prevouts (hex encode TxOut, may be specified multiple times).", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-ipk", "The internal public key for a tapscript spend", ArgsManager::ALLOW_ANY, OptionsCategory::COMMAND_OPTIONS);

argsman.AddCommand("grind", "Perform proof of work on hex header string");
argsman.AddCommand("evalscript", "Interpret a bitcoin script", {"-sigversion", "-script_flags", "-tx", "-input", "-spent_output"});
argsman.AddCommand("evalscript", "Interpret a bitcoin script", {"-sigversion", "-script_flags", "-tx", "-input", "-spent_output", "-ipk"});

SetupChainParamsBaseOptions(argsman);
}
Expand Down Expand Up @@ -206,7 +207,7 @@ class DummySignatureChecker final : public BaseSignatureChecker
public:
DummySignatureChecker() = default;
bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return sig.size() != 0; }
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 CheckSchnorrSignature(Span<const unsigned char> sig, KeyVersion keyversion, 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; }
};
Expand Down Expand Up @@ -263,6 +264,10 @@ static int EvalScript(const ArgsManager& argsman, const std::vector<std::string>
}
}

if (sigversion == SigVersion::TAPSCRIPT) {
execdata.m_internal_key.emplace(NUMS_H);
}

if (const auto txhex = argsman.GetArg("-tx"); txhex.has_value()) {
const int input = argsman.GetIntArg("-input", 0);
const auto spent_outputs_hex = argsman.GetArgs("-spent_output");
Expand Down Expand Up @@ -308,6 +313,15 @@ static int EvalScript(const ArgsManager& argsman, const std::vector<std::string>
checker = std::make_unique<TransactionSignatureChecker>(txTo.get(), input, amount, txdata, MissingDataBehavior::ASSERT_FAIL);

if (sigversion == SigVersion::TAPSCRIPT && input >= 0 && input_in_range) {
if (const auto ipkhex = argsman.GetArg("-ipk"); ipkhex.has_value()) {
if (!IsHex(*ipkhex) || ipkhex->size() != 64) {
strPrint = strprintf("Not a valid x-only pubkey: -ipk=%s", *ipkhex);
return EXIT_FAILURE;
}
auto ipkbytes = ParseHex(*ipkhex);
std::copy(ipkbytes.begin(), ipkbytes.end(), execdata.m_internal_key->begin());
}

const CTxIn& txin = txTo->vin.at(input);
execdata.m_annex_present = false;
if (txin.scriptWitness.stack.size() <= 1) {
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 @@ -143,6 +143,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 = uint256{"000000000000000000000000000000000000000088e186b70e0862c193ec44d6"};
consensus.defaultAssumeValid = uint256{"000000000000000000011c5890365bdbe5d25b97ce0057589acaef4f1a57263f"}; // 856760
Expand Down Expand Up @@ -260,6 +261,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 = uint256{"000000000000000000000000000000000000000000000f209695166be8b61fa9"};
consensus.defaultAssumeValid = uint256{"000000000000000465b1a66c9f386308e8c75acef9201f3f577811da09fc90ad"}; // 2873500
Expand Down Expand Up @@ -489,6 +491,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 @@ -563,6 +571,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 @@ -1394,6 +1394,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.version;
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 @@ -1991,12 +2032,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 @@ -2050,7 +2092,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 @@ -2062,7 +2104,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

0 comments on commit aa0c738

Please sign in to comment.