From 4618097ab4cefed367de9e70c3126bc0fdb7c85d Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 25 Jan 2021 19:58:56 -0800 Subject: [PATCH] Implement taphash for elements --- src/script/interpreter.cpp | 106 +++++++++++++++++++++++------ src/script/interpreter.h | 10 ++- test/functional/feature_taproot.py | 32 +++++++-- 3 files changed, 120 insertions(+), 28 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 7ba3c67aea..b8edfa54b3 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1757,6 +1757,17 @@ public: } }; +/** Compute the (single) SHA256 of the concatenation of all outpoint flags of a tx. */ +template +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 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 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 +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 +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 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& 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& 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&& 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; } @@ -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 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) @@ -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 @@ -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)); 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 @@ -2301,10 +2367,10 @@ static bool VerifyTaprootCommitment(const std::vector& control, c const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; const XOnlyPubKey p{uint256(std::vector(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 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; @@ -2313,7 +2379,7 @@ static bool VerifyTaprootCommitment(const std::vector& 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); } diff --git a/src/script/interpreter.h b/src/script/interpreter.h index a5429eae4e..2837c932d0 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -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 m_spent_outputs; diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index d7b50aab51..f5cc81cc8f 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -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.