Merge ElementsProject/elements#1002: Implement Taproot Sighash

14f93579a5 Add pegins and issuance test (sanket1729)
edf8455fe4 Fix bug in CAssetIssuance decoding (sanket1729)
cdd75d4251 Update OP_SUCCESS for elements with allowed opcodes (sanket1729)
4618097ab4 Implement taphash for elements (sanket1729)
6c985308d7 Implement Taphash in test framework (sanket1729)
a26f6fead6 Expose only blockchain hash twice in header (sanket1729)
afb9e7b727 taproot: feed genesis hash and parent pegged asset to sighash function (Andrew Poelstra)

Pull request description:

ACKs for top commit:
  apoelstra:
    ACK 14f93579a5

Tree-SHA512: f0a3f6ef9f8958bc948cc42375600767f39aadc0dac096fe8e7823dc69ddc4204360e504af7801d05bb19ae4e6ace6a271842531c192e330167381cf2690c856
This commit is contained in:
Andrew Poelstra 2021-07-07 20:58:36 +00:00
commit 1ba24fe9b3
No known key found for this signature in database
GPG key ID: C588D63CE41B97C1
17 changed files with 458 additions and 77 deletions

View file

@ -68,6 +68,7 @@ static void VerifyScriptBench(benchmark::Bench& bench)
CDataStream streamVal(SER_NETWORK, PROTOCOL_VERSION);
streamVal << txCredit.vout[0].nValue;
int csuccess = bitcoinconsensus_verify_script_with_amount(
NULL,
txCredit.vout[0].scriptPubKey.data(),
txCredit.vout[0].scriptPubKey.size(),
(const unsigned char*)&streamVal[0], streamVal.size(),

View file

@ -97,7 +97,8 @@ public:
const CCheckpointData& Checkpoints() const { return checkpointData; }
const ChainTxData& TxData() const { return chainTxData; }
// ELEMENTS extra fields:
const uint256 ParentGenesisBlockHash() const { return parentGenesisBlockHash; }
const uint256& ParentGenesisBlockHash() const { return parentGenesisBlockHash; }
const uint256& HashGenesisBlock() const { return consensus.hashGenesisBlock; }
bool anyonecanspend_aremine;
const std::string& ParentBech32HRP() const { return parent_bech32_hrp; }
const std::string& ParentBlech32HRP() const { return parent_blech32_hrp; }

View file

@ -8,6 +8,7 @@
#include <primitives/transaction.h>
#include <pubkey.h>
#include <script/interpreter.h>
#include <streams.h>
#include <version.h>
namespace {
@ -76,7 +77,8 @@ static bool verify_flags(unsigned int flags)
return (flags & ~(bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL)) == 0;
}
static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CConfidentialValue amount,
static int verify_script(const unsigned char *hash_genesis_block,
const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CConfidentialValue amount,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
{
@ -94,7 +96,9 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
// Regardless of the verification result, the tx did not error.
set_error(err, bitcoinconsensus_ERR_OK);
PrecomputedTransactionData txdata(tx);
auto hash_genesis_block_ = hash_genesis_block ? uint256{hash_genesis_block, 32} : uint256{};
PrecomputedTransactionData txdata(hash_genesis_block_);
txdata.Init(tx, {});
const CScriptWitness* pScriptWitness = (tx.witness.vtxinwit.size() > nIn ? &tx.witness.vtxinwit[nIn].scriptWitness : NULL);
return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), pScriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata), NULL);
} catch (const std::exception&) {
@ -102,7 +106,8 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
}
}
int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
int bitcoinconsensus_verify_script_with_amount(const unsigned char *hash_genesis_block,
const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *amount, unsigned int amountLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
@ -112,14 +117,15 @@ int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey
CConfidentialValue am;
stream >> am;
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
return ::verify_script(hash_genesis_block, scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
} catch (const std::exception&) {
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
}
int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
int bitcoinconsensus_verify_script(const unsigned char *hash_genesis_block,
const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
{
@ -128,7 +134,7 @@ int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned i
}
CConfidentialValue am(0);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
return ::verify_script(hash_genesis_block, scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
}
unsigned int bitcoinconsensus_version()

View file

@ -64,11 +64,13 @@ enum
/// txTo correctly spends the scriptPubKey pointed to by scriptPubKey under
/// the additional constraints specified by flags.
/// If not nullptr, err will contain an error/success code for the operation
EXPORT_SYMBOL int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
EXPORT_SYMBOL int bitcoinconsensus_verify_script(const unsigned char *hash_genesis_block,
const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);
EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char *hash_genesis_block,
const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *amount, unsigned int amountLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);

View file

@ -1757,6 +1757,17 @@ public:
}
};
/** Compute the (single) SHA256 of the concatenation of all outpoint flags of a tx. */
template <class T>
uint256 GetOutpointFlagsSHA256(const T& txTo)
{
CHashWriter ss(SER_GETHASH, 0);
for (const auto& txin : txTo.vin) {
ss << (unsigned char) ((!txin.assetIssuance.IsNull() << 7) + (txin.m_is_pegin << 6));
}
return ss.GetSHA256();
}
/** Compute the (single) SHA256 of the concatenation of all prevouts of a tx. */
template <class T>
uint256 GetPrevoutsSHA256(const T& txTo)
@ -1779,7 +1790,8 @@ uint256 GetSequencesSHA256(const T& txTo)
return ss.GetSHA256();
}
/** Compute the (single) SHA256 of the concatenation of all txouts of a tx. */
/** Compute the (single) SHA256 of the concatenation of all issuances of a tx. */
// Used for segwitv0/taproot sighash calculation
template <class T>
uint256 GetIssuanceSHA256(const T& txTo)
{
@ -1793,6 +1805,34 @@ uint256 GetIssuanceSHA256(const T& txTo)
return ss.GetSHA256();
}
/** Compute the (single) SHA256 of the concatenation of all output witnesses
* (rangeproof and surjection proof) in `CTxWitness`*/
// Used in taphash calculation
template <class T>
uint256 GetOutputWitnessesSHA256(const T& txTo)
{
CHashWriter ss(SER_GETHASH, 0);
for (const auto& outwit : txTo.witness.vtxoutwit) {
ss << outwit;
}
return ss.GetSHA256();
}
/** Compute the (single) SHA256 of the concatenation of all input issuance witnesses
* (vchIssuanceAmountRangeproof and vchInflationKeysRangeproof proof) in `CTxInWitness`*/
// Used in taphash calculation
template <class T>
uint256 GetIssuanceRangeproofsSHA256(const T& txTo)
{
CHashWriter ss(SER_GETHASH, 0);
for (const auto& inwit : txTo.witness.vtxinwit) {
ss << inwit.vchIssuanceAmountRangeproof;
ss << inwit.vchInflationKeysRangeproof;
}
return ss.GetSHA256();
}
// Compute a (single) SHA256 of the concatenation of all outputs
template <class T>
uint256 GetOutputsSHA256(const T& txTo)
{
@ -1803,11 +1843,13 @@ uint256 GetOutputsSHA256(const T& txTo)
return ss.GetSHA256();
}
/** Compute the (single) SHA256 of the concatenation of all amounts spent by a tx. */
uint256 GetSpentAmountsSHA256(const std::vector<CTxOut>& outputs_spent)
/** Compute the (single) SHA256 of the concatenation of all asset and amounts commitments spent by a tx. */
// Elements TapHash only
uint256 GetSpentAssetsAmountsSHA256(const std::vector<CTxOut>& outputs_spent)
{
CHashWriter ss(SER_GETHASH, 0);
for (const auto& txout : outputs_spent) {
ss << txout.nAsset;
ss << txout.nValue;
}
return ss.GetSHA256();
@ -1878,17 +1920,21 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
m_prevouts_single_hash = GetPrevoutsSHA256(txTo);
m_sequences_single_hash = GetSequencesSHA256(txTo);
m_outputs_single_hash = GetOutputsSHA256(txTo);
m_issuances_single_hash = GetIssuanceSHA256(txTo);
}
if (uses_bip143_segwit) {
hashPrevouts = SHA256Uint256(m_prevouts_single_hash);
hashSequence = SHA256Uint256(m_sequences_single_hash);
hashIssuance = SHA256Uint256(GetIssuanceSHA256(txTo));
hashIssuance = SHA256Uint256(m_issuances_single_hash);
hashOutputs = SHA256Uint256(m_outputs_single_hash);
hashRangeproofs = GetRangeproofsHash(txTo);
m_bip143_segwit_ready = true;
}
if (uses_bip341_taproot) {
m_spent_amounts_single_hash = GetSpentAmountsSHA256(m_spent_outputs);
m_outpoints_flag_single_hash = GetOutpointFlagsSHA256(txTo);
m_spent_asset_amounts_single_hash = GetSpentAssetsAmountsSHA256(m_spent_outputs);
m_issuance_rangeproofs_single_hash = GetIssuanceRangeproofsSHA256(txTo);
m_output_witnesses_single_hash = GetOutputWitnessesSHA256(txTo);
m_spent_scripts_single_hash = GetSpentScriptsSHA256(m_spent_outputs);
m_bip341_taproot_ready = true;
}
@ -1896,6 +1942,7 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
template <class T>
PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo)
: PrecomputedTransactionData(uint256{})
{
Init(txTo, {});
}
@ -1906,10 +1953,14 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo,
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
static const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
static const CHashWriter HASHER_TAPTWEAK_ELEMENTS = TaggedHash("TapTweak/elements");
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
PrecomputedTransactionData::PrecomputedTransactionData(const uint256& hash_genesis_block)
: m_tapsighash_hasher(CHashWriter(HASHER_TAPSIGHASH_ELEMENTS) << hash_genesis_block << hash_genesis_block) {}
template<typename T>
bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache)
@ -1934,11 +1985,11 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata
assert(in_pos < tx_to.vin.size());
assert(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready);
CHashWriter ss = HASHER_TAPSIGHASH;
CHashWriter ss = cache.m_tapsighash_hasher;
// Epoch
static constexpr uint8_t EPOCH = 0;
ss << EPOCH;
// no epoch in elements taphash
// static constexpr uint8_t EPOCH = 0;
// ss << EPOCH;
// 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
@ -1950,37 +2001,56 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata
ss << tx_to.nVersion;
ss << tx_to.nLockTime;
if (input_type != SIGHASH_ANYONECANPAY) {
ss << cache.m_outpoints_flag_single_hash;
ss << cache.m_prevouts_single_hash;
ss << cache.m_spent_amounts_single_hash;
ss << cache.m_spent_asset_amounts_single_hash;
ss << cache.m_spent_scripts_single_hash;
ss << cache.m_sequences_single_hash;
ss << cache.m_issuances_single_hash;
ss << cache.m_issuance_rangeproofs_single_hash;
}
if (output_type == SIGHASH_ALL) {
ss << cache.m_outputs_single_hash;
ss << cache.m_output_witnesses_single_hash;
}
// Data about the input/prevout being spent
assert(execdata.m_annex_init);
const bool have_annex = execdata.m_annex_present;
const uint8_t spend_type = (ext_flag << 1) + (have_annex ? 1 : 0); // The low bit indicates whether an annex is present.
ss << spend_type;
if (input_type == SIGHASH_ANYONECANPAY) {
ss << (unsigned char) ((!tx_to.vin[in_pos].assetIssuance.IsNull() << 7) + (tx_to.vin[in_pos].m_is_pegin << 6));
ss << tx_to.vin[in_pos].prevout;
ss << cache.m_spent_outputs[in_pos];
ss << cache.m_spent_outputs[in_pos].nAsset;
ss << cache.m_spent_outputs[in_pos].nValue;
ss << cache.m_spent_outputs[in_pos].scriptPubKey;
ss << tx_to.vin[in_pos].nSequence;
if (tx_to.vin[in_pos].assetIssuance.IsNull()) {
ss << (unsigned char)0;
} else {
ss << tx_to.vin[in_pos].assetIssuance;
CHashWriter sha_single_input_issuance_witness(SER_GETHASH, 0);
sha_single_input_issuance_witness << tx_to.witness.vtxinwit[in_pos].vchIssuanceAmountRangeproof;
sha_single_input_issuance_witness << tx_to.witness.vtxinwit[in_pos].vchInflationKeysRangeproof;
ss << sha_single_input_issuance_witness.GetSHA256();
}
} else {
ss << in_pos;
}
if (have_annex) {
ss << execdata.m_annex_hash;
}
// Data about the output (if only one).
if (output_type == SIGHASH_SINGLE) {
if (in_pos >= tx_to.vout.size()) return false;
CHashWriter sha_single_output(SER_GETHASH, 0);
sha_single_output << tx_to.vout[in_pos];
ss << sha_single_output.GetSHA256();
CHashWriter sha_single_output_witness(SER_GETHASH, 0);
sha_single_output_witness << tx_to.witness.vtxoutwit[in_pos];
ss << sha_single_output_witness.GetSHA256();
}
// Additional data for BIP 342 signatures
@ -2297,10 +2367,10 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
const XOnlyPubKey q{uint256(program)};
tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
tapleaf_hash = (CHashWriter(HASHER_TAPLEAF_ELEMENTS) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
uint256 k = tapleaf_hash;
for (int i = 0; i < path_len; ++i) {
CHashWriter ss_branch{HASHER_TAPBRANCH};
CHashWriter ss_branch = CHashWriter{HASHER_TAPBRANCH_ELEMENTS};
Span<const unsigned char> node(control.data() + TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE);
if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) {
ss_branch << k << node;
@ -2309,7 +2379,7 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
}
k = ss_branch.GetSHA256();
}
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
k = (CHashWriter(HASHER_TAPTWEAK_ELEMENTS) << MakeSpan(p) << k).GetSHA256();
return q.CheckPayToContract(p, k, control[0] & 1);
}

View file

@ -6,6 +6,7 @@
#ifndef BITCOIN_SCRIPT_INTERPRETER_H
#define BITCOIN_SCRIPT_INTERPRETER_H
#include <hash.h>
#include <script/script_error.h>
#include <span.h>
#include <primitives/transaction.h>
@ -166,19 +167,29 @@ struct PrecomputedTransactionData
uint256 m_outputs_single_hash;
uint256 m_spent_amounts_single_hash;
uint256 m_spent_scripts_single_hash;
//! Whether the 5 fields above are initialized.
// Elements
uint256 m_outpoints_flag_single_hash;
uint256 m_spent_asset_amounts_single_hash;
uint256 m_issuances_single_hash;
uint256 m_output_witnesses_single_hash;
uint256 m_issuance_rangeproofs_single_hash;
//! Whether the 10 fields above are initialized.
bool m_bip341_taproot_ready = false;
// BIP143 precomputed data (double-SHA256).
uint256 hashPrevouts, hashSequence, hashOutputs, hashIssuance, hashRangeproofs;
//! Whether the 3 fields above are initialized.
//! Whether the 5 fields above are initialized.
bool m_bip143_segwit_ready = false;
std::vector<CTxOut> m_spent_outputs;
//! Whether m_spent_outputs is initialized.
bool m_spent_outputs_ready = false;
PrecomputedTransactionData() = default;
//! ELEMENTS: parent genesis hash
CHashWriter m_tapsighash_hasher;
explicit PrecomputedTransactionData(const uint256& hash_genesis_block);
explicit PrecomputedTransactionData() : PrecomputedTransactionData(uint256{}) {}
template <class T>
void Init(const T& tx, std::vector<CTxOut>&& spent_outputs);

View file

@ -411,8 +411,10 @@ bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator en
bool IsOpSuccess(const opcodetype& opcode)
{
return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) ||
(opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) ||
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
(opcode >= 187 && opcode <= 254);
// ELEMENTS: Don't mark opcodes (OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT) as OP_SUCCESS
return opcode == 80 || opcode == 98 || (opcode >= 137 && opcode <= 138) ||
// ELEMENTS: Don't mark OP_INVERT , OP_AND, OP_OR, OP_XOR. OP_LSHIFT, OP_RSHIFT as success
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 151) ||
// ELEMENTS: Exclude OP_DETERMINISTICRANDOM, OP_CHECKSIGFROMSTACK(VERIFY), OP_SUBSTRLAZY
(opcode >= 187 && opcode <= 191) || (opcode >= 196 && opcode <= 254);
}

View file

@ -17,6 +17,8 @@ void test_one_input(const std::vector<uint8_t>& buffer)
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const std::vector<uint8_t> random_bytes_1 = ConsumeRandomLengthByteVector(fuzzed_data_provider);
const std::vector<uint8_t> random_bytes_2 = ConsumeRandomLengthByteVector(fuzzed_data_provider);
const uint256 random_hash = ConsumeUInt256(fuzzed_data_provider);
const std::optional<CConfidentialValue> money = ConsumeDeserializable<CConfidentialValue>(fuzzed_data_provider);
bitcoinconsensus_error err;
bitcoinconsensus_error* err_p = fuzzed_data_provider.ConsumeBool() ? &err : nullptr;
@ -26,10 +28,10 @@ void test_one_input(const std::vector<uint8_t>& buffer)
if ((flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
return;
}
(void)bitcoinconsensus_verify_script(random_bytes_1.data(), random_bytes_1.size(), random_bytes_2.data(), random_bytes_2.size(), n_in, flags, err_p);
(void)bitcoinconsensus_verify_script(random_hash.begin() ,random_bytes_1.data(), random_bytes_1.size(), random_bytes_2.data(), random_bytes_2.size(), n_in, flags, err_p);
if (money) {
CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION);
data_stream << *money;
(void)bitcoinconsensus_verify_script_with_amount(random_bytes_1.data(), random_bytes_1.size(), (unsigned char*) data_stream.data(), data_stream.size(), random_bytes_2.data(), random_bytes_2.size(), n_in, flags, err_p);
(void)bitcoinconsensus_verify_script_with_amount(random_hash.begin(), random_bytes_1.data(), random_bytes_1.size(), (unsigned char*) data_stream.data(), data_stream.size(), random_bytes_2.data(), random_bytes_2.size(), n_in, flags, err_p);
}
}

View file

@ -159,10 +159,10 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, const CScript
if (libconsensus_flags == flags) {
int expectedSuccessCode = expect ? 1 : 0;
if (flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS) {
BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&streamVal[0], streamVal.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&streamVal[0], streamVal.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
} else {
BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&streamVal0[0], streamVal0.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&streamVal0[0], streamVal0.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message);
}
}
#endif
@ -1524,7 +1524,7 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_returns_true)
stream << spendTx;
bitcoinconsensus_error err;
int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
int result = bitcoinconsensus_verify_script(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
BOOST_CHECK_EQUAL(result, 1);
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_OK);
}
@ -1547,7 +1547,7 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_index_err)
stream << spendTx;
bitcoinconsensus_error err;
int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
int result = bitcoinconsensus_verify_script(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
BOOST_CHECK_EQUAL(result, 0);
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_INDEX);
}
@ -1570,7 +1570,7 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_size)
stream << spendTx;
bitcoinconsensus_error err;
int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size() * 2, nIn, libconsensus_flags, &err);
int result = bitcoinconsensus_verify_script(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size() * 2, nIn, libconsensus_flags, &err);
BOOST_CHECK_EQUAL(result, 0);
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_SIZE_MISMATCH);
}
@ -1593,7 +1593,7 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_serialization)
stream << 0xffffffff;
bitcoinconsensus_error err;
int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
int result = bitcoinconsensus_verify_script(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
BOOST_CHECK_EQUAL(result, 0);
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_TX_DESERIALIZE);
}
@ -1616,7 +1616,7 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_amount_required_err)
stream << spendTx;
bitcoinconsensus_error err;
int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
int result = bitcoinconsensus_verify_script(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
BOOST_CHECK_EQUAL(result, 0);
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_AMOUNT_REQUIRED);
}
@ -1639,7 +1639,7 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags)
stream << spendTx;
bitcoinconsensus_error err;
int result = bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
int result = bitcoinconsensus_verify_script(NULL, scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), nIn, libconsensus_flags, &err);
BOOST_CHECK_EQUAL(result, 0);
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_INVALID_FLAGS);
}
@ -1685,12 +1685,14 @@ static void AssetTest(const UniValue& test)
size_t idx = test["index"].get_int64();
unsigned int test_flags = ParseScriptFlags(test["flags"].get_str());
bool fin = test.exists("final") && test["final"].get_bool();
// ELEMENTS FIXME: update feature_taproot.py --dumptests to actually output these
uint256 hash_genesis_block = test.exists("hash_genesis_block") ? uint256S(test["hash_genesis_block"].get_str()) : uint256{};
if (test.exists("success")) {
mtx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
mtx.witness.vtxinwit[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
CTransaction tx(mtx);
PrecomputedTransactionData txdata;
PrecomputedTransactionData txdata(hash_genesis_block);
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
for (const auto flags : ALL_CONSENSUS_FLAGS) {
@ -1707,7 +1709,7 @@ static void AssetTest(const UniValue& test)
mtx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
mtx.witness.vtxinwit[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
CTransaction tx(mtx);
PrecomputedTransactionData txdata;
PrecomputedTransactionData txdata(hash_genesis_block);
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
for (const auto flags : ALL_CONSENSUS_FLAGS) {

View file

@ -16,6 +16,13 @@ base_blob<BITS>::base_blob(const std::vector<unsigned char>& vch)
memcpy(m_data, vch.data(), sizeof(m_data));
}
template <unsigned int BITS>
base_blob<BITS>::base_blob(const unsigned char* data, size_t len)
{
assert(len == sizeof(m_data));
memcpy(m_data, data, sizeof(m_data));
}
template <unsigned int BITS>
std::string base_blob<BITS>::GetHex() const
{
@ -68,6 +75,7 @@ std::string base_blob<BITS>::ToString() const
// Explicit instantiations for base_blob<160>
template base_blob<160>::base_blob(const std::vector<unsigned char>&);
template base_blob<160>::base_blob(const unsigned char*, size_t);
template std::string base_blob<160>::GetHex() const;
template std::string base_blob<160>::ToString() const;
template void base_blob<160>::SetHex(const char*);
@ -75,6 +83,7 @@ template void base_blob<160>::SetHex(const std::string&);
// Explicit instantiations for base_blob<256>
template base_blob<256>::base_blob(const std::vector<unsigned char>&);
template base_blob<256>::base_blob(const unsigned char*, size_t);
template std::string base_blob<256>::GetHex() const;
template std::string base_blob<256>::ToString() const;
template void base_blob<256>::SetHex(const char*);

View file

@ -27,6 +27,7 @@ public:
constexpr explicit base_blob(uint8_t v) : m_data{v} {}
explicit base_blob(const std::vector<unsigned char>& vch);
explicit base_blob(const unsigned char* data, size_t len);
bool IsNull() const
{
@ -126,6 +127,7 @@ public:
constexpr uint256() {}
constexpr explicit uint256(uint8_t v) : base_blob<256>(v) {}
explicit uint256(const std::vector<unsigned char>& vch) : base_blob<256>(vch) {}
explicit uint256(const unsigned char* data, size_t len) : base_blob<256>(data, len) {}
static const uint256 ZERO;
static const uint256 ONE;
};

View file

@ -1120,7 +1120,7 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs
// scripts (ie, other policy checks pass). We perform the inexpensive
// checks first and avoid hashing and signature verification unless those
// checks pass, to mitigate CPU exhaustion denial-of-service attacks.
PrecomputedTransactionData txdata;
PrecomputedTransactionData txdata(args.m_chainparams.HashGenesisBlock());
if (!PolicyScriptChecks(args, workspace, txdata)) return false;
@ -2296,7 +2296,10 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// doesn't invalidate pointers into the vector, and keep txsdata in scope
// for as long as `control`.
CCheckQueueControl<CCheck> control(fScriptChecks && g_parallel_script_checks ? &scriptcheckqueue : nullptr);
std::vector<PrecomputedTransactionData> txsdata(block.vtx.size());
std::vector<PrecomputedTransactionData> txsdata;
for (unsigned int i = 0; i< block.vtx.size(); i++ ){
txsdata.push_back(PrecomputedTransactionData(chainparams.HashGenesisBlock()));
}
std::vector<int> prevheights;
CAmountMap fee_map;

View file

@ -0,0 +1,190 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
# Test for taproot sighash algorithm with pegins and issuances
from test_framework.key import ECKey, ECPubKey, compute_xonly_pubkey, generate_privkey, sign_schnorr, tweak_add_privkey, tweak_add_pubkey, verify_schnorr
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, CTxOutValue, CTxOutWitness, FromHex, uint256_from_str
from test_framework.test_framework import BitcoinTestFramework, SkipTest
from test_framework.script import TaprootSignatureHash, taproot_construct, taproot_pad_sighash_ty, SIGHASH_DEFAULT, SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAY
VALID_SIGHASHES_ECDSA = [
SIGHASH_ALL,
SIGHASH_NONE,
SIGHASH_SINGLE,
SIGHASH_ANYONECANPAY + SIGHASH_ALL,
SIGHASH_ANYONECANPAY + SIGHASH_NONE,
SIGHASH_ANYONECANPAY + SIGHASH_SINGLE
]
VALID_SIGHASHES_TAPROOT = [SIGHASH_DEFAULT] + VALID_SIGHASHES_ECDSA
class TapHashPeginTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [
["-initialfreecoins=2100000000000000",
"-anyonecanspendaremine=1",
"-blindedaddresses=1",
"-validatepegin=0",
"-con_parent_chain_signblockscript=51",
"-parentscriptprefix=75",
"-parent_bech32_hrp=ert",
"-minrelaytxfee=0",
"-maxtxfee=100.0",
]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self, split=False):
self.setup_nodes()
def create_taproot_utxo(self):
# modify the transaction to add one output that should spend previous taproot
# Create a taproot prevout
addr = self.nodes[0].getnewaddress()
sec = generate_privkey()
pub = compute_xonly_pubkey(sec)[0]
tap = taproot_construct(pub)
spk = tap.scriptPubKey
tweak = tap.tweak
unconf_addr = self.nodes[0].getaddressinfo(addr)['unconfidential']
raw_tx = self.nodes[0].createrawtransaction([], [{unconf_addr: 1.2}])
# edit spk directly, no way to get new address.
# would need to implement bech32m in python
tx = FromHex(CTransaction(), raw_tx)
tx.vout[0].scriptPubKey = spk
tx.vout[0].nValue = CTxOutValue(12*10**7)
raw_hex = tx.serialize().hex()
fund_tx = self.nodes[0].fundrawtransaction(raw_hex, False, )["hex"]
fund_tx = FromHex(CTransaction(), fund_tx)
# Createrawtransaction might rearrage txouts
prev_vout = None
for i, out in enumerate(fund_tx.vout):
if spk == out.scriptPubKey:
prev_vout = i
tx = self.nodes[0].blindrawtransaction(fund_tx.serialize().hex())
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(tx)
_txid = self.nodes[0].sendrawtransaction(signed_raw_tx['hex'])
tx = FromHex(CTransaction(), signed_raw_tx['hex'])
tx.rehash()
self.nodes[0].generate(1)
last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash())
assert(tx.hash in last_blk['tx'])
return tx, prev_vout, spk, sec, pub, tweak
def pegin_test(self, sighash_ty):
# Peg-in prep:
# Hack: since we're not validating peg-ins in parent chain, just make
# both the funding and claim tx on same chain (printing money)
fund_info = self.nodes[0].getpeginaddress()
peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1)
raw_peg_tx = self.nodes[0].gettransaction(peg_id)["hex"]
peg_txid = self.nodes[0].sendrawtransaction(raw_peg_tx)
self.nodes[0].generate(101)
peg_prf = self.nodes[0].gettxoutproof([peg_txid])
claim_script = fund_info["claim_script"]
# Create a pegin transaction
# We have to manually supply claim script, otherwise the wallet will pick
raw_claim = self.nodes[0].createrawpegin(raw_peg_tx, peg_prf, claim_script)
raw_claim = FromHex(CTransaction(), raw_claim['hex'])
# Create a taproot utxo
tx, prev_vout, spk, sec, pub, tweak = self.create_taproot_utxo()
# Spend the pegin and taproot tx together
raw_claim.vin.append(CTxIn(COutPoint(tx.sha256, prev_vout)))
raw_claim.vout.append(CTxOut(nValue = CTxOutValue(12 * 10**7), scriptPubKey = spk)) # send back to self
signed = self.nodes[0].signrawtransactionwithwallet(raw_claim.serialize().hex())
raw_claim = FromHex(CTransaction(), signed['hex'])
genesis_hash = uint256_from_str(bytes.fromhex(self.nodes[0].getblockhash(0))[::-1])
peg_utxo = CTxOut()
peg_utxo.from_pegin_witness_data(raw_claim.wit.vtxinwit[0].peginWitness)
msg = TaprootSignatureHash(raw_claim, [peg_utxo, tx.vout[prev_vout]], sighash_ty, genesis_hash, 1)
# compute the tweak
tweak_sk = tweak_add_privkey(sec, tweak)
sig = sign_schnorr(tweak_sk, msg)
raw_claim.wit.vtxinwit[1].scriptWitness.stack = [taproot_pad_sighash_ty(sig, sighash_ty)]
pub_tweak = tweak_add_pubkey(pub, tweak)[0]
assert(verify_schnorr(pub_tweak, sig, msg))
# Since we add in/outputs the min feerate is no longer maintained.
self.nodes[0].sendrawtransaction(hexstring = raw_claim.serialize().hex())
self.nodes[0].generate(1)
last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash())
raw_claim.rehash()
assert(raw_claim.hash in last_blk['tx'])
def issuance_test(self, sighash_ty):
tx, prev_vout, spk, sec, pub, tweak = self.create_taproot_utxo()
blind_addr = self.nodes[0].getnewaddress()
nonblind_addr = self.nodes[0].validateaddress(blind_addr)['unconfidential']
raw_tx = self.nodes[0].createrawtransaction([], {nonblind_addr: 1})
raw_tx = FromHex(CTransaction(), raw_tx)
# Need to taproot outputs later because fundrawtransaction cannot estimate fees
# prev out has value 1.2 btc
in_total = tx.vout[prev_vout].nValue.getAmount()
fees = 100
raw_tx.vin.append(CTxIn(COutPoint(tx.sha256, prev_vout)))
raw_tx.vout.append(CTxOut(nValue = CTxOutValue(in_total - fees - 10**8), scriptPubKey = spk)) # send back to self
raw_tx.vout.append(CTxOut(nValue = CTxOutValue(fees)))
# issued_tx = raw_tx.serialize().hex()
blind_addr = self.nodes[0].getnewaddress()
issue_addr = self.nodes[0].validateaddress(blind_addr)['unconfidential']
issued_tx = self.nodes[0].rawissueasset(raw_tx.serialize().hex(), [{"asset_amount":2, "asset_address":issue_addr, "blind":False}])[0]["hex"]
# blind_tx = self.nodes[0].blindrawtransaction(issued_tx) # This is a no-op
genesis_hash = uint256_from_str(bytes.fromhex(self.nodes[0].getblockhash(0))[::-1])
issued_tx = FromHex(CTransaction(), issued_tx)
issued_tx.wit.vtxoutwit = [CTxOutWitness()] * len(issued_tx.vout)
issued_tx.wit.vtxinwit = [CTxInWitness()] * len(issued_tx.vin)
msg = TaprootSignatureHash(issued_tx, [tx.vout[prev_vout]], sighash_ty, genesis_hash, 0)
# compute the tweak
tweak_sk = tweak_add_privkey(sec, tweak)
sig = sign_schnorr(tweak_sk, msg)
issued_tx.wit.vtxinwit[0].scriptWitness.stack = [taproot_pad_sighash_ty(sig, sighash_ty)]
pub_tweak = tweak_add_pubkey(pub, tweak)[0]
assert(verify_schnorr(pub_tweak, sig, msg))
# Since we add in/outputs the min feerate is no longer maintained.
self.nodes[0].sendrawtransaction(hexstring = issued_tx.serialize().hex())
self.nodes[0].generate(1)
last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash())
issued_tx.rehash()
assert(issued_tx.hash in last_blk['tx'])
def run_test(self):
self.nodes[0].generate(101)
self.wait_until(lambda: self.nodes[0].getblockcount() == 101, timeout=5)
self.log.info("Testing sighash taproot pegins")
# Note that this does not test deposit to taproot pegin addresses
# because there is no support for taproot pegins in rpc. The current rpc assumes
# to shwsh tweaked address
for sighash_ty in VALID_SIGHASHES_TAPROOT:
self.pegin_test(sighash_ty)
self.log.info("Testing sighash taproot issuances")
for sighash_ty in VALID_SIGHASHES_TAPROOT:
self.issuance_test(sighash_ty)
if __name__ == '__main__':
TapHashPeginTest().main()

View file

@ -18,8 +18,8 @@ from test_framework.messages import (
CTxIn,
CTxInWitness,
CTxOut,
CTxOutValue,
ToHex,
CTxOutValue, CTxOutWitness,
ToHex, uint256_from_str,
)
from test_framework.script import (
ANNEX_TAG,
@ -123,6 +123,11 @@ import random
#
# in that ctx3 will globally use hashtype=SIGHASH_DEFAULT (including in the hashtype byte appended to the signature)
# while ctx2 only uses the modified hashtype inside the sighash calculation.
#
# ELEMENTS:
# Elements taphash calculation also depends on genesis_block_hash which is stored as
# `genesis_hash` in the test config
g_genesis_hash = None
def deep_eval(ctx, expr):
"""Recursively replace any callables c in expr (including inside lists) with c(ctx)."""
@ -191,11 +196,13 @@ def default_controlblock(ctx):
"""Default expression for "controlblock": combine leafversion, negflag, pubkey_inner, merklebranch."""
return bytes([get(ctx, "leafversion") + get(ctx, "negflag")]) + get(ctx, "pubkey_inner") + get(ctx, "merklebranch")
#ELEMENTS: taphash depends on genesis hash
def default_sighash(ctx):
"""Default expression for "sighash": depending on mode, compute BIP341, BIP143, or legacy sighash."""
tx = get(ctx, "tx")
idx = get(ctx, "idx")
hashtype = get(ctx, "hashtype_actual")
genesis_hash = get(ctx, "genesis_hash")
mode = get(ctx, "mode")
if mode == "taproot":
# BIP341 signature hash
@ -205,9 +212,9 @@ def default_sighash(ctx):
codeseppos = get(ctx, "codeseppos")
leaf_ver = get(ctx, "leafversion")
script = get(ctx, "script_taproot")
return TaprootSignatureHash(tx, utxos, hashtype, idx, scriptpath=True, script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex)
return TaprootSignatureHash(tx, utxos, hashtype, genesis_hash, idx, scriptpath=True, script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex)
else:
return TaprootSignatureHash(tx, utxos, hashtype, idx, scriptpath=False, annex=annex)
return TaprootSignatureHash(tx, utxos, hashtype, genesis_hash, idx, scriptpath=False, annex=annex)
elif mode == "witv0":
# BIP143 signature hash
scriptcode = get(ctx, "scriptcode")
@ -373,6 +380,8 @@ DEFAULT_CONTEXT = {
"leaf": None,
# The input arguments to provide to the executed script
"inputs": [],
# Genesis hash(required for taproot outputs)
"genesis_hash": None,
# == Parameters to be set before evaluation: ==
# - mode: what spending style to use ("taproot", "witv0", or "legacy").
@ -382,6 +391,7 @@ DEFAULT_CONTEXT = {
# - utxos: the UTXOs being spent (needed in mode=="witv0" and mode=="taproot").
# - idx: the input position being signed.
# - scriptcode: the scriptcode to include in legacy and witv0 sighashes.
# - genesisHash: The genesis hash of the block
}
def flatten(lst):
@ -433,7 +443,7 @@ def spend(tx, idx, utxos, **kwargs):
Spender = namedtuple("Spender", "script,comment,is_standard,sat_function,err_msg,sigops_weight,no_fail,need_vin_vout_mismatch")
def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs):
def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, genesis_hash=None, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs):
"""Helper for constructing Spender objects using the context signing framework.
* tap: a TaprootInfo object (see taproot_construct), for Taproot spends (cannot be combined with pkh, witv0, or script)
@ -449,6 +459,10 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=
"""
conf = dict()
global g_genesis_hash
if genesis_hash is None:
genesis_hash = g_genesis_hash
conf["genesis_hash"] = genesis_hash
# Compute scriptPubKey and set useful defaults based on the inputs.
if witv0:
@ -1216,7 +1230,6 @@ class TaprootTest(BitcoinTestFramework):
extra_output_script = CScript([OP_CHECKSIG]*((MAX_BLOCK_SIGOPS_WEIGHT - sigops_weight) // WITNESS_SCALE_FACTOR))
if extra_output_script == CScript():
extra_output_script = None ## ELEMENTS: an explicitly empty coinbase scriptpubkey would be rejected with bad-cb-fee
block = create_block(self.tip, create_coinbase(self.lastblockheight + 1, pubkey=cb_pubkey, extra_output_script=extra_output_script, fees=fees), self.lastblocktime + 1)
block.nVersion = 4
for tx in txs:
@ -1309,9 +1322,12 @@ class TaprootTest(BitcoinTestFramework):
amount = int(random.randrange(int(avg*0.85 + 0.5), int(avg*1.15 + 0.5)) + 0.5)
balance -= amount
fund_tx.vout.append(CTxOut(amount, spenders[done + i].script))
fund_tx.wit.vtxoutwit.append(CTxOutWitness())
# Add change
fund_tx.vout.append(CTxOut(balance - 10000, random.choice(host_spks)))
fund_tx.wit.vtxoutwit.append(CTxOutWitness())
fund_tx.vout.append(CTxOut(10000)) # ELEMENTS: and fee
fund_tx.wit.vtxoutwit.append(CTxOutWitness())
# Ask the wallet to sign
ss = BytesIO(bytes.fromhex(node.signrawtransactionwithwallet(ToHex(fund_tx))["hex"]))
fund_tx.deserialize(ss)
@ -1387,6 +1403,7 @@ class TaprootTest(BitcoinTestFramework):
assert in_value >= 0 and fee - num_outputs * DUST_LIMIT >= MIN_FEE
for i in range(num_outputs):
tx.vout.append(CTxOut())
tx.wit.vtxoutwit.append(CTxOutWitness())
if in_value <= DUST_LIMIT:
tx.vout[-1].nValue = CTxOutValue(DUST_LIMIT)
elif i < num_outputs - 1:
@ -1399,6 +1416,7 @@ class TaprootTest(BitcoinTestFramework):
fee += in_value
assert fee >= 0
tx.vout.append(CTxOut(fee))
tx.wit.vtxoutwit.append(CTxOutWitness())
# Select coinbase pubkey
cb_pubkey = random.choice(host_pubkeys)
@ -1453,6 +1471,8 @@ class TaprootTest(BitcoinTestFramework):
# Post-taproot activation tests go first (pre-taproot tests' blocks are invalid post-taproot).
self.log.info("Post-activation tests...")
self.nodes[1].generate(101)
global g_genesis_hash
g_genesis_hash = uint256_from_str(bytes.fromhex(self.nodes[1].getblockhash(0))[::-1])
self.test_spenders(self.nodes[1], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3])
# Transfer funds to pre-taproot node.

View file

@ -395,7 +395,7 @@ class CAssetIssuance():
self.nAmount = CTxOutValue()
self.nAmount.deserialize(f)
self.nInflationKeys = CTxOutValue()
self.nInflatoinKeys.deserialize(f)
self.nInflationKeys.deserialize(f)
def serialize(self):
r = b""
@ -405,6 +405,14 @@ class CAssetIssuance():
r += self.nInflationKeys.serialize()
return r
# serialization of asset issuance used in taproot sighash
def taphash_asset_issuance_serialize(self):
if self.isNull():
return b'\x00'
r = b''
r += self.serialize()
return r
def __repr__(self):
return "CAssetIssuance(assetBlindingNonce=%064x assetEntropy=%064x nAmount=%s nInflationKeys=%s)" % (self.assetBlindingNonce, self.assetEntropy, self.nAmount.vchCommitment, self.nInflationKeys.vchCommitment)
@ -492,10 +500,10 @@ class CTxOutAsset:
r += self.vchCommitment
return r
#def setToAsset(self, val):
# if len(val) != 32:
# raise 'invalid asset hash (expected 32 bytes got %d)' % len(val)
# self.vchCommitment = b'\x01' + val
def setToAsset(self, val):
if len(val) != 32:
raise 'invalid asset hash (expected 32 bytes)'
self.vchCommitment = b'\x01' + val
def __repr__(self):
return "CTxOutAsset(vchCommitment=%s)" % self.vchCommitment
@ -535,10 +543,15 @@ class CTxOutValue:
return r
def setToAmount(self, amount):
commit = [1]*9
for i in range(8): #8 bytes
commit[8-i] = ((amount >> (i*8)) & 0xff)
self.vchCommitment = bytes(commit)
if type(amount) == int:
commit = [1]*9
for i in range(8): #8 bytes
commit[8-i] = ((amount >> (i*8)) & 0xff)
self.vchCommitment = bytes(commit)
else:
if len(amount) != 8:
raise 'invalid explicit amount (expected 8 bytes)'
self.vchCommitment = b'\x01' + amount[::-1]
def getAmount(self):
if self.vchCommitment[0] != 1:
@ -621,6 +634,13 @@ class CTxOut():
r += ser_string(self.scriptPubKey)
return r
def from_pegin_witness_data(self, peg_witness):
self.nAsset = CTxOutAsset()
self.nAsset.setToAsset(peg_witness.stack[1])
self.nValue = CTxOutValue()
self.nValue.setToAmount(peg_witness.stack[0])
self.scriptPubKey = peg_witness.stack[3]
def __repr__(self):
return "CTxOut(nAsset=%s nValue=%s nNonce=%s scriptPubKey=%s)" \
% (self.nAsset, self.nValue, self.nNonce, self.scriptPubKey.hex())
@ -667,6 +687,13 @@ class CTxInWitness:
r += ser_string_vector(self.peginWitness.stack)
return r
# Used in taproot sighash calculation
def serialize_issuance_proofs(self):
r = b''
r += ser_string(self.vchIssuanceAmountRangeproof)
r += ser_string(self.vchInflationKeysRangeproof)
return r
def calc_witness_hash(self):
leaves = [
encode(hash256(ser_string(self.vchIssuanceAmountRangeproof))[::-1], 'hex_codec').decode('ascii'),

View file

@ -608,6 +608,15 @@ SIGHASH_ANYONECANPAY = 0x80
# ELEMENTS:
SIGHASH_RANGEPROOF = 0x40
# Add the sighash byte to the signature(Taproot sigs only)
# Nothing is padded if sighash is default
def taproot_pad_sighash_ty(sig, sighash_ty):
if len(sig) != 64:
raise "Schnorr sigs must be 64 bytes"
if sighash_ty != SIGHASH_DEFAULT:
sig = sig + bytes([sighash_ty])
return sig
def FindAndDelete(script, sig):
"""Consensus critical, see FindAndDelete() in Satoshi codebase"""
r = b''
@ -793,35 +802,48 @@ class TestFrameworkScript(unittest.TestCase):
for value in values:
self.assertEqual(CScriptNum.decode(CScriptNum.encode(CScriptNum(value))), value)
def TaprootSignatureHash(txTo, spent_utxos, hash_type, input_index = 0, scriptpath = False, script = CScript(), codeseparator_pos = -1, annex = None, leaf_ver = LEAF_VERSION_TAPSCRIPT):
def TaprootSignatureHash(txTo, spent_utxos, hash_type, genesis_hash, input_index = 0, scriptpath = False, script = CScript(), codeseparator_pos = -1, annex = None, leaf_ver = LEAF_VERSION_TAPSCRIPT):
assert (len(txTo.vin) == len(spent_utxos))
assert (input_index < len(txTo.vin))
out_type = SIGHASH_ALL if hash_type == 0 else hash_type & 3
in_type = hash_type & SIGHASH_ANYONECANPAY
spk = spent_utxos[input_index].scriptPubKey
ss = bytes([0, hash_type]) # epoch, hash_type
ss = b""
ss += ser_uint256(genesis_hash)
ss += ser_uint256(genesis_hash)
ss += bytes([hash_type]) # hash_type
ss += struct.pack("<i", txTo.nVersion)
ss += struct.pack("<I", txTo.nLockTime)
if in_type != SIGHASH_ANYONECANPAY:
ss += sha256(b"".join(struct.pack("B", ((not i.assetIssuance.isNull()) << 7) + (i.m_is_pegin << 6)) for i in txTo.vin))
ss += sha256(b"".join(i.prevout.serialize() for i in txTo.vin))
ss += sha256(b"".join(u.nValue.serialize() for u in spent_utxos))
ss += sha256(b"".join(u.nAsset.serialize() + u.nValue.serialize() for u in spent_utxos))
ss += sha256(b"".join(ser_string(u.scriptPubKey) for u in spent_utxos))
ss += sha256(b"".join(struct.pack("<I", i.nSequence) for i in txTo.vin))
ss += sha256(b"".join(i.assetIssuance.taphash_asset_issuance_serialize() for i in txTo.vin))
ss += sha256(b"".join(iwit.serialize_issuance_proofs() for iwit in txTo.wit.vtxinwit))
if out_type == SIGHASH_ALL:
ss += sha256(b"".join(o.serialize() for o in txTo.vout))
ss += sha256(b"".join(owit.serialize() for owit in txTo.wit.vtxoutwit))
spend_type = 0
if annex is not None:
spend_type |= 1
if (scriptpath):
spend_type |= 2
ss += bytes([spend_type])
if in_type == SIGHASH_ANYONECANPAY:
ss += struct.pack("B", ((not txTo.vin[input_index].assetIssuance.isNull()) << 7) + (txTo.vin[input_index].m_is_pegin << 6))
ss += txTo.vin[input_index].prevout.serialize()
ss += spent_utxos[input_index].nAsset.serialize()
ss += spent_utxos[input_index].nValue.serialize()
ss += spent_utxos[input_index].nNonce.serialize()
ss += ser_string(spk)
ss += struct.pack("<I", txTo.vin[input_index].nSequence)
if txTo.vin[input_index].assetIssuance.isNull():
ss += b'\x00'
else:
ss += txTo.vin[input_index].assetIssuance.serialize()
ss += sha256(txTo.wit.vtxinwit[input_index].serialize_issuance_proofs())
else:
ss += struct.pack("<I", input_index)
if (spend_type & 1):
@ -829,15 +851,25 @@ def TaprootSignatureHash(txTo, spent_utxos, hash_type, input_index = 0, scriptpa
if out_type == SIGHASH_SINGLE:
if input_index < len(txTo.vout):
ss += sha256(txTo.vout[input_index].serialize())
ss += sha256(txTo.wit.vtxoutwit[input_index].serialize())
else:
# Why do we have a case for in > len ?
# Maybe useful in testing. C++ code should never reach here
ss += bytes(0 for _ in range(32))
ss += bytes(0 for _ in range(32))
if (scriptpath):
ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(script))
ss += TaggedHash("TapLeaf/elements", bytes([leaf_ver]) + ser_string(script))
ss += bytes([0])
ss += struct.pack("<i", codeseparator_pos)
# ELEMENTS -35 since we encode nAsset (33) + nValue (9) + nNonce (1) rather than nValue (8)
assert len(ss) == 175 - (in_type == SIGHASH_ANYONECANPAY) * (49 - 35) - (out_type != SIGHASH_ALL and out_type != SIGHASH_SINGLE) * 32 + (annex is not None) * 32 + scriptpath * 37
return TaggedHash("TapSighash", ss)
exp_non_acp_len = 366 - (out_type != SIGHASH_ALL and out_type != SIGHASH_SINGLE) * 64 + (annex is not None) * 32 + scriptpath * 37
if in_type != SIGHASH_ANYONECANPAY:
assert len(ss) == exp_non_acp_len
else:
# 119 when explicit outs with no issuance. 304 when when conf tx with conf issuances.
assert len(ss) >= exp_non_acp_len - (228 - 119)
assert len(ss) <= exp_non_acp_len - (228 - 304)
return TaggedHash("TapSighash/elements", ss)
def taproot_tree_helper(scripts):
if len(scripts) == 0:
@ -856,7 +888,7 @@ def taproot_tree_helper(scripts):
version = script[2]
assert version & 1 == 0
assert isinstance(code, bytes)
h = TaggedHash("TapLeaf", bytes([version]) + ser_string(code))
h = TaggedHash("TapLeaf/elements", bytes([version]) + ser_string(code))
if name is None:
return ([], h)
return ([(name, version, code, bytes())], h)
@ -875,7 +907,7 @@ def taproot_tree_helper(scripts):
right = [(name, version, script, control + left_h) for name, version, script, control in right]
if right_h < left_h:
right_h, left_h = left_h, right_h
h = TaggedHash("TapBranch", left_h + right_h)
h = TaggedHash("TapBranch/elements", left_h + right_h)
return (left + right, h)
TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,inner_pubkey,negflag,tweak,leaves")
@ -898,10 +930,10 @@ def taproot_construct(pubkey, scripts=None):
scripts = []
ret, h = taproot_tree_helper(scripts)
tweak = TaggedHash("TapTweak", pubkey + h)
tweak = TaggedHash("TapTweak/elements", pubkey + h)
tweaked, negated = tweak_add_pubkey(pubkey, tweak)
leaves = dict((name, TaprootLeafInfo(script, version, merklebranch)) for name, version, script, merklebranch in ret)
return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves)
def is_op_success(o):
return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xfe)
return o == 80 or o == 98 or (o >= 137 and o <= 138) or (o >= 141 and o <= 142) or (o >= 149 and o <= 151) or (o >= 187 and o <= 191) or (o >= 196 and o <= 254)

View file

@ -134,6 +134,7 @@ BASE_SCRIPTS = [
'wallet_listtransactions.py',
'wallet_listtransactions.py --descriptors',
'feature_taproot.py',
'feature_taphash_pegins_issuances.py',
# vv Tests less than 60s vv
'p2p_sendheaders.py',
'wallet_importmulti.py --legacy-wallet',