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 1 commit
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
106 changes: 86 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,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;
}
Expand All @@ -1907,13 +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_TAPLEAF = TaggedHash("TapLeaf");
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");

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) << hash_genesis_block << 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 @@ -1940,9 +1987,9 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata

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 @@ -1954,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 @@ -2301,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 @@ -2313,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
10 changes: 8 additions & 2 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,18 @@ 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;
Expand Down
32 changes: 26 additions & 6 deletions test/functional/feature_taproot.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
CTxIn,
CTxInWitness,
CTxOut,
CTxOutValue,
ToHex,
CTxOutValue, CTxOutWitness,
ToHex, uint256_from_str,
)
from test_framework.script import (
ANNEX_TAG,
Expand Down Expand Up @@ -123,6 +123,11 @@
#
# 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)."""
Expand Down Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -373,6 +380,8 @@ def default_scriptsig(ctx):
"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").
Expand All @@ -382,6 +391,7 @@ def default_scriptsig(ctx):
# - 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):
Expand Down Expand Up @@ -433,7 +443,7 @@ def to_script(elem):

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)
Expand All @@ -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:
Expand Down Expand Up @@ -1216,7 +1230,6 @@ def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_w
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:
Expand Down Expand Up @@ -1309,9 +1322,12 @@ def test_spenders(self, node, spenders, input_counts):
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)
Expand Down Expand Up @@ -1387,6 +1403,7 @@ def test_spenders(self, node, spenders, input_counts):
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:
Expand All @@ -1399,6 +1416,7 @@ def test_spenders(self, node, spenders, input_counts):
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)
Expand Down Expand Up @@ -1453,6 +1471,8 @@ def run_test(self):
# 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.
Expand Down