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

Add OP_TXHASH and OP_CHECKTXHASHVERIFY #51

Closed
wants to merge 12 commits into from
Closed
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 build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ClCompile Include="..\..\src\pubkey.cpp" />
<ClCompile Include="..\..\src\script\bitcoinconsensus.cpp" />
<ClCompile Include="..\..\src\script\interpreter.cpp" />
<ClCompile Include="..\..\src\script\txhash.cpp" />
<ClCompile Include="..\..\src\script\script.cpp" />
<ClCompile Include="..\..\src\script\script_error.cpp" />
<ClCompile Include="..\..\src\uint256.cpp" />
Expand Down
3 changes: 3 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,8 @@ libbitcoin_consensus_a_SOURCES = \
script/script.h \
script/script_error.cpp \
script/script_error.h \
script/txhash.cpp \
script/txhash.h \
serialize.h \
span.h \
tinyformat.h \
Expand Down Expand Up @@ -942,6 +944,7 @@ libbitcoinkernel_la_SOURCES = \
script/script_error.cpp \
script/sigcache.cpp \
script/standard.cpp \
script/txhash.cpp \
shutdown.cpp \
signet.cpp \
support/cleanse.cpp \
Expand Down
3 changes: 2 additions & 1 deletion src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ FUZZ_BINARY=test/fuzz/fuzz$(EXEEXT)

JSON_TEST_FILES = \
test/data/script_tests.json \
test/data/txhash_tests.json \
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 \
test/data/sighash.json \
test/data/tx_invalid.json \
test/data/tx_valid.json
Expand Down Expand Up @@ -134,6 +134,7 @@ BITCOIN_TESTS =\
test/script_segwit_tests.cpp \
test/script_standard_tests.cpp \
test/script_tests.cpp \
test/txhash_tests.cpp \
test/scriptnum10.h \
test/scriptnum_tests.cpp \
test/serfloat_tests.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 @@ -34,6 +34,7 @@ enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
DEPLOYMENT_CHECKTEMPLATEVERIFY, // Deployment of CTV (BIP 119)
DEPLOYMENT_ANYPREVOUT,
DEPLOYMENT_TXHASH, // Deployment of TXHASH/CHECKTXHASHVERIFY
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
MAX_VERSION_BITS_DEPLOYMENTS
};
Expand Down
2 changes: 2 additions & 0 deletions src/core_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class CBlock;
class CBlockHeader;
class CScript;
class CTransaction;
class CTxOut;
struct CMutableTransaction;
class SigningProvider;
class uint256;
Expand All @@ -33,6 +34,7 @@ enum class TxVerbosity {
CScript ParseScript(const std::string& s);
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
[[nodiscard]] bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness = false, bool try_witness = true);
[[nodiscard]] bool DecodeHexTxOut(CTxOut& txout, const std::string& hex);
[[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);

Expand Down
14 changes: 14 additions & 0 deletions src/core_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no
return DecodeTx(tx, txData, try_no_witness, try_witness);
}

bool DecodeHexTxOut(CTxOut& txout, const std::string& hex_txout)
{
if (!IsHex(hex_txout)) return false;

const std::vector<unsigned char> txout_data{ParseHex(hex_txout)};
DataStream ser_txout{txout_data};
try {
ser_txout >> txout;
} catch (const std::exception&) {
return false;
}
return true;
}

bool DecodeHexBlockHeader(CBlockHeader& header, const std::string& hex_header)
{
if (!IsHex(hex_header)) return false;
Expand Down
5 changes: 5 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 =*/ "txhash",
/*.gbt_force =*/ true,
},
{
/*.name =*/ "anyprevout",
/*.gbt_force =*/ true,
Expand Down Expand Up @@ -89,6 +93,7 @@ const std::map<std::string, uint32_t> g_verify_flag_names{
FLAG_NAME(DISCOURAGE_CHECK_TEMPLATE_VERIFY_HASH)
FLAG_NAME(ANYPREVOUT)
FLAG_NAME(DISCOURAGE_ANYPREVOUT)
FLAG_NAME(TXHASH)
};
#undef FLAG_NAME

Expand Down
9 changes: 9 additions & 0 deletions src/hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ class HashWriter
CSHA256 ctx;

public:
/**
* Create a new HashWriter
*/
HashWriter(CSHA256 ctx = CSHA256()) : ctx(ctx) {}

CSHA256 GetHashCtx() {
return ctx;
}

void write(Span<const std::byte> src)
{
ctx.Write(UCharCast(src.data()), src.size());
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.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.vDeployments[Consensus::DEPLOYMENT_TXHASH] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .never = true};

consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000044a50fe819c39ad624021859");
consensus.defaultAssumeValid = uint256S("0x000000000000000000035c3f0d31e71a5ee24c5aaf3354689f65bd7b07dee632"); // 784000
Expand Down Expand Up @@ -246,6 +247,7 @@ class CTestNetParams : public CChainParams {
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.vDeployments[Consensus::DEPLOYMENT_TXHASH] = SetupDeployment{.activate = 0x60007700, .abandon = 0x40007700, .never = true};

consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000977edb0244170858d07");
consensus.defaultAssumeValid = uint256S("0x0000000000000021bc50a89cde4870d4a81ffe0153b3c8de77b435a2fd3f6761"); // 2429000
Expand Down Expand Up @@ -394,6 +396,12 @@ class SigNetParams : public CChainParams {
.activate = 0x60007600,
.abandon = 0x40007600,
};
consensus.vDeployments[Consensus::DEPLOYMENT_TXHASH] = SetupDeployment{
.start = 1654041600, // 2022-06-01
.timeout = 1969660800, // 2032-06-01
.activate = 0x60007700,
.abandon = 0x40007700,
};

RenounceDeployments(options.renounce, consensus.vDeployments);

Expand Down Expand Up @@ -461,6 +469,7 @@ class CRegTestParams : public CChainParams
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.vDeployments[Consensus::DEPLOYMENT_TXHASH] = 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 @@ -223,6 +223,11 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// pre-activation disallowing enforced via discouraged logic in the
// interpreter.
if (tx.vin[i].scriptSig.size() != 0) return false;
} else if (whichType == TxoutType::BARE_CHECK_TXHASH_VERIFY) {
// 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
3 changes: 2 additions & 1 deletion src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH |
SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH |
SCRIPT_VERIFY_ANYPREVOUT};
SCRIPT_VERIFY_ANYPREVOUT |
SCRIPT_VERIFY_TXHASH};

/** 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
5 changes: 4 additions & 1 deletion src/psbt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,12 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
return false;
}

TxHashCache txhash_cache;
if (txdata) {
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, *txdata, MissingDataBehavior::FAIL});
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, *txdata, &txhash_cache, MissingDataBehavior::FAIL});
} else {
//TODO(stevenroose) I cant pass in txhash_cache here without passing in a non-null txdata..
//should I create a new constructor? is it fine to not have txhashcache if there's no txdata?
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, MissingDataBehavior::FAIL});
}
}
Expand Down
1 change: 1 addition & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CHECKTEMPLATEVERIFY);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_ANYPREVOUT);
SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TXHASH);
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 @@ -479,6 +479,8 @@ static RPCHelpMan decodescript()
case TxoutType::WITNESS_V1_TAPROOT:
// don't wrap CTV because P2SH CTV is a hash cycle
case TxoutType::TX_BARE_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH:
// don't wrap TXHASH because P2SH TXHASH is a hash cycle
case TxoutType::BARE_CHECK_TXHASH_VERIFY:
case TxoutType::ANCHOR:
// Should not be wrapped
return false;
Expand Down Expand Up @@ -524,6 +526,8 @@ static RPCHelpMan decodescript()
case TxoutType::WITNESS_V1_TAPROOT:
// don't wrap CTV because P2SH CTV is a hash cycle
case TxoutType::TX_BARE_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH:
// don't wrap TXHASH because P2SH TXHASH is a hash cycle
case TxoutType::BARE_CHECK_TXHASH_VERIFY:
case TxoutType::ANCHOR:
// Should not be wrapped
return false;
Expand Down
4 changes: 3 additions & 1 deletion src/script/bitcoinconsensus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <pubkey.h>
#include <script/interpreter.h>
#include <version.h>
#include <script/txhash.h>

namespace {

Expand Down Expand Up @@ -89,7 +90,8 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
set_error(err, bitcoinconsensus_ERR_OK);

PrecomputedTransactionData txdata(tx);
return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), nullptr);
TxHashCache txhash_cache;
return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, &txhash_cache, MissingDataBehavior::FAIL), nullptr);
} catch (const std::exception&) {
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
Expand Down
78 changes: 76 additions & 2 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include <crypto/sha256.h>
#include <pubkey.h>
#include <script/script.h>
#include <script/txhash.h>
#include <uint256.h>
#include <vector>

typedef std::vector<unsigned char> valtype;

Expand Down Expand Up @@ -642,8 +644,54 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
}
break;

case OP_NOP1: case OP_NOP5:
case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10:
case OP_TXHASH: case OP_CHECKTXHASHVERIFY:
{
// if flags not enabled; treat as a NOP4
if (!(flags & SCRIPT_VERIFY_TXHASH)) {
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
break;
}

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

Span<const unsigned char> tx_field_selector { stack.back() };
if (opcode == OP_CHECKTXHASHVERIFY) {
if (stack.back().size() < 32) {
return set_error(serror, SCRIPT_ERR_CHECKTXHASHVERIFY);
}
tx_field_selector = tx_field_selector.subspan(32);
}

if (sigversion == SigVersion::TAPSCRIPT) {
assert(execdata.m_validation_weight_left_init);
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_TXHASH;
if (execdata.m_validation_weight_left < 0) {
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
}
}

uint256 txhash;
if (!checker.CalculateTxHash(txhash, tx_field_selector, execdata)) {
return set_error(serror, SCRIPT_ERR_INVALID_TXFIELDSELECTOR);
}

if (opcode == OP_CHECKTXHASHVERIFY) {
if (!memcmp(txhash.begin(), stack.back().data(), 32)) {
return set_error(serror, SCRIPT_ERR_CHECKTXHASHVERIFY);
}
// nothing more to do, leave item on stack
} else {
popstack(stack);
valtype vch(txhash.begin(), txhash.end());
stack.push_back(vch);
}
}
break;

case OP_NOP1: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10:
{
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
Expand Down Expand Up @@ -1946,6 +1994,32 @@ bool GenericTransactionSignatureChecker<T>::CheckDefaultCheckTemplateVerifyHash(
return HandleMissingData(m_mdb);
}
}

template <class T>
bool GenericTransactionSignatureChecker<T>::CalculateTxHash(
uint256& hash_out, const Span<const unsigned char>& tx_field_selector, ScriptExecutionData& execdata
) const
{
assert(txTo != nullptr);
assert(txhash_cache != nullptr);
assert(txdata != nullptr && txdata->m_spent_outputs_ready);

uint32_t codeseparator_pos = 0xFFFFFFFFUL;
if (execdata.m_codeseparator_pos_init) {
codeseparator_pos = execdata.m_codeseparator_pos;
}

return calculate_txhash(
hash_out,
tx_field_selector,
*txhash_cache,
*txTo,
txdata->m_spent_outputs,
codeseparator_pos,
(uint32_t) nIn
);
}

// explicit instantiation
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;
Expand Down
Loading