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

Implement Taproot Sighash #1002

Merged
merged 7 commits into from
Jul 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/bench/verify_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
3 changes: 2 additions & 1 deletion src/chainparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ class CChainParams
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; }
Expand Down
18 changes: 12 additions & 6 deletions src/script/bitcoinconsensus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <primitives/transaction.h>
#include <pubkey.h>
#include <script/interpreter.h>
#include <streams.h>
#include <version.h>

namespace {
Expand Down Expand Up @@ -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)
{
Expand All @@ -94,15 +96,18 @@ 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&) {
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
}

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)
Expand All @@ -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)
{
Expand All @@ -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()
Expand Down
6 changes: 4 additions & 2 deletions src/script/bitcoinconsensus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
110 changes: 90 additions & 20 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,17 @@ class CTransactionSignatureSerializer
}
};

/** 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));
Copy link
Contributor

@roconnor-blockstream roconnor-blockstream Jul 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider instead using (COutPoint::OUTPOINT_ISSUANCE_FLAG >> 24) and (COutPoint::OUTPOINT_PEGIN_FLAG >> 24) (you can compose the flags and shift the result if you prefer).


I believe | is prefered over + for flag composition.

}
return ss.GetSHA256();
}

/** Compute the (single) SHA256 of the concatenation of all prevouts of a tx. */
template <class T>
uint256 GetPrevoutsSHA256(const T& txTo)
Expand All @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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();
Expand Down Expand Up @@ -1878,24 +1920,29 @@ 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;
}
}

template <class T>
PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo)
: PrecomputedTransactionData(uint256{})
{
Init(txTo, {});
}
Expand All @@ -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)
Expand All @@ -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
Expand 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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably abstract this flag composition into its own function as it occurs in two places.

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe comment here that we are not using the nonce and maybe even why we aren't?

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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down
17 changes: 14 additions & 3 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 6 additions & 4 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading