From 9346a8495efa24afdf5d1d4bf0a8fbd58a5d34be Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Sun, 11 Jul 2021 11:04:23 +0000 Subject: [PATCH] Implement transaction introspection opcodes --- src/script/interpreter.cpp | 275 ++++++++++++++++++++++++++++++++++-- src/script/script.cpp | 17 +++ src/script/script.h | 27 +++- src/script/script_error.cpp | 4 + src/script/script_error.h | 2 + 5 files changed, 316 insertions(+), 9 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index a793e5be08..054b1b0298 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -61,6 +61,65 @@ static inline void popstack(std::vector& stack) stack.pop_back(); } +static inline void push4_le(std::vector& stack, uint32_t v) +{ + uint32_t v_le = htole32(v); + stack.emplace_back(reinterpret_cast(&v_le), reinterpret_cast(&v_le) + sizeof(v_le)); +} + +static inline void push8_le(std::vector& stack, uint64_t v) +{ + uint64_t v_le = htole64(v); + stack.emplace_back(reinterpret_cast(&v_le), reinterpret_cast(&v_le) + sizeof(v_le)); +} + +static inline void pushasset(std::vector& stack, const CConfidentialAsset& asset) +{ + assert(!asset.IsNull()); + stack.emplace_back(asset.vchCommitment.begin() + 1, asset.vchCommitment.end()); // Push asset without prefix + stack.emplace_back(asset.vchCommitment.begin(), asset.vchCommitment.begin() + 1); // Push prefix +} + +static inline void pushvalue(std::vector& stack, const CConfidentialValue& value) +{ + valtype vchinpValue, vchValuePref; + if (value.IsNull()) { + // If value is null, explicitly push the explicit prefix 0x01 + vchValuePref = {0x01}; + vchinpValue.assign(8, 0x00); + } else if (value.IsExplicit()) { + // Convert BE to LE by using reverse iterator + vchValuePref.assign(value.vchCommitment.begin(), value.vchCommitment.begin() + 1); + vchinpValue.assign(value.vchCommitment.rbegin(), value.vchCommitment.rbegin() + 8); + } else { // (value.IsCommitment()) + vchValuePref.assign(value.vchCommitment.begin(), value.vchCommitment.begin() + 1); + vchinpValue.assign(value.vchCommitment.begin() + 1, value.vchCommitment.end()); + } + stack.push_back(std::move(vchinpValue)); // if value is null, 0(LE 8) is pushed + stack.push_back(std::move(vchValuePref)); // always push prefix +} + +static inline void pushspk(std::vector& stack, const CScript& scriptPubKey, const uint256& scriptPubKey_sha) +{ + int witnessversion; + valtype witnessprogram; + if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { + stack.push_back(std::move(witnessprogram)); + stack.push_back(CScriptNum(witnessversion).getvch()); + } else { + stack.emplace_back(scriptPubKey_sha.begin(), scriptPubKey_sha.end()); + stack.push_back(CScriptNum(-1).getvch()); + } +} + +/** Compute the outpoint flag(u8) for a given txin **/ +template +inline unsigned char GetOutpointFlag(const T& txin) +{ + return static_cast ((!txin.assetIssuance.IsNull() ? (COutPoint::OUTPOINT_ISSUANCE_FLAG >> 24) : 0) | + (txin.m_is_pegin ? (COutPoint::OUTPOINT_PEGIN_FLAG >> 24) : 0)); +} + bool static IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) { if (vchPubKey.size() < CPubKey::COMPRESSED_SIZE) { // Non-canonical public key: too short @@ -1681,6 +1740,214 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_INSPECTINPUTOUTPOINT: + case OP_INSPECTINPUTASSET: + case OP_INSPECTINPUTVALUE: + case OP_INSPECTINPUTSCRIPTPUBKEY: + case OP_INSPECTINPUTSEQUENCE: + case OP_INSPECTINPUTISSUANCE: + { + // Input inspection opcodes only available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + if (stack.size() < 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + int idx = CScriptNum(stacktop(-1), fRequireMinimal).getint(); + popstack(stack); + + auto inps = checker.GetTxvIn(); + const PrecomputedTransactionData *cache = checker.GetPrecomputedTransactionData(); + // Return error if the evaluation context is unavailable + // TODO: Handle accoding to MissingDataBehavior + if (!inps || !cache || !cache->m_bip341_taproot_ready) + return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE); + const std::vector& spent_outputs = cache->m_spent_outputs; + // This condition is ensured when m_spent_outputs_ready is set + // which is asserted when m_bip341_taproot_ready is set + assert(spent_outputs.size() == inps->size()); + if (idx < 0 || static_cast(idx) >= inps->size()) + return set_error(serror, SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS); + const CTxIn& inp = inps->at(idx); + const CTxOut& spent_utxo = spent_outputs[idx]; + + switch (opcode) + { + case OP_INSPECTINPUTOUTPOINT: + { + // Push prev txid + stack.emplace_back(inp.prevout.hash.begin(), inp.prevout.hash.end()); + push4_le(stack, inp.prevout.n); + + // Push the outpoint flag + stack.emplace_back(1, GetOutpointFlag(inp)); + break; + } + case OP_INSPECTINPUTASSET: + { + pushasset(stack, spent_utxo.nAsset); + break; + } + case OP_INSPECTINPUTVALUE: + { + pushvalue(stack, spent_utxo.nValue); + break; + } + case OP_INSPECTINPUTSCRIPTPUBKEY: + { + pushspk(stack, spent_utxo.scriptPubKey, cache->m_spent_output_spk_single_hashes[idx]); + break; + } + case OP_INSPECTINPUTSEQUENCE: + { + push4_le(stack, inp.nSequence); + break; + } + case OP_INSPECTINPUTISSUANCE: + { + if (!inp.assetIssuance.IsNull()) { + pushvalue(stack, inp.assetIssuance.nInflationKeys); + pushvalue(stack, inp.assetIssuance.nAmount); + // Next push Asset entropy + stack.emplace_back(inp.assetIssuance.assetEntropy.begin(), inp.assetIssuance.assetEntropy.end()); + // Finally push blinding nonce + // By pushing the this order, we make sure that the stack top is empty + // iff there is no issuance. + stack.emplace_back(inp.assetIssuance.assetBlindingNonce.begin(), inp.assetIssuance.assetBlindingNonce.end()); + } else { // No issuance + stack.push_back(vchFalse); + } + break; + } + default: assert(!"invalid opcode"); break; + } + } + break; + + case OP_PUSHCURRENTINPUTINDEX: + { + // OP_PUSHCURRENTINPUTINDEX is available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + // Even tough this value should never 2^25(MAX_SIZE), this can set to any value in exotic custom contexts + // safe to check that this in 4 byte positive number before pushing it + // TODO: Handle accoding to MissingDataBehavior + if (checker.GetnIn() > MAX_SIZE) + return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE); + stack.push_back(CScriptNum(static_cast(checker.GetnIn())).getvch()); + } + break; + + case OP_INSPECTOUTPUTASSET: + case OP_INSPECTOUTPUTVALUE: + case OP_INSPECTOUTPUTNONCE: + case OP_INSPECTOUTPUTSCRIPTPUBKEY: + { + // Output instropsection codes only available post tapscript is available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + if (stack.size() < 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + int idx = CScriptNum(stacktop(-1), fRequireMinimal).getint(); + popstack(stack); + + auto outs = checker.GetTxvOut(); + const PrecomputedTransactionData *cache = checker.GetPrecomputedTransactionData(); + // Return error if the evaluation context is unavailable + // TODO: Handle accoding to MissingDataBehavior + if (!outs || !cache || !cache->m_bip341_taproot_ready) + return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE); + assert(cache->m_output_spk_single_hashes.size() == outs->size()); + + if (idx < 0 || static_cast(idx) >= outs->size()) + return set_error(serror, SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS); + const CTxOut& out = outs->at(idx); + + switch (opcode) + { + case OP_INSPECTOUTPUTASSET: + { + pushasset(stack, out.nAsset); + break; + } + case OP_INSPECTOUTPUTVALUE: + { + pushvalue(stack, out.nValue); + break; + } + case OP_INSPECTOUTPUTNONCE: + { + if (out.nNonce.IsNull()) { + stack.push_back(vchFalse); + } else { + stack.emplace_back(out.nNonce.vchCommitment); + } + break; + } + case OP_INSPECTOUTPUTSCRIPTPUBKEY: + { + pushspk(stack, out.scriptPubKey, cache->m_output_spk_single_hashes[idx]); + break; + } + default: assert(!"invalid opcode"); break; + } + } + break; + + case OP_INSPECTVERSION: + case OP_INSPECTLOCKTIME: + case OP_INSPECTNUMINPUTS: + case OP_INSPECTNUMOUTPUTS: + case OP_TXWEIGHT: + { + // Transaction introspection is available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + switch (opcode) + { + case OP_INSPECTVERSION: + { + push4_le(stack, static_cast(checker.GetTxVersion())); + break; + } + case OP_INSPECTLOCKTIME: + { + push4_le(stack, checker.GetLockTime()); + break; + } + case OP_INSPECTNUMINPUTS: + { + auto inps = checker.GetTxvIn(); + // TODO: Handle according to MissingDataBehavior + if (!inps) + return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE); + auto num_ins = inps->size(); + assert(num_ins <= MAX_SIZE); + stack.push_back(CScriptNum(static_cast(num_ins)).getvch()); + break; + } + case OP_INSPECTNUMOUTPUTS: + { + auto outs = checker.GetTxvOut(); + // TODO: Handle according to MissingDataBehavior + if (!outs) + return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE); + auto num_outs = outs->size(); + assert(num_outs <= MAX_SIZE); + stack.push_back(CScriptNum(static_cast(num_outs)).getvch()); + break; + } + case OP_TXWEIGHT: + { + push8_le(stack, checker.GetTxWeight()); + break; + } + default: assert(!"invalid opcode"); break; + } + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1825,14 +2092,6 @@ public: } }; -/** Compute the outpoint flag(u8) for a given txin **/ -template -inline unsigned char GetOutpointFlag(const T& txin) -{ - return (unsigned char) ((!txin.assetIssuance.IsNull() ? (COutPoint::OUTPOINT_ISSUANCE_FLAG >> 24) : 0) | - (txin.m_is_pegin ? (COutPoint::OUTPOINT_PEGIN_FLAG >> 24) : 0)); -} - /** Compute the (single) SHA256 of the concatenation of all outpoint flags of a tx. */ template uint256 GetOutpointFlagsSHA256(const T& txTo) diff --git a/src/script/script.cpp b/src/script/script.cpp index 82bd5d010e..3083277228 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -151,6 +151,23 @@ std::string GetOpName(opcodetype opcode) case OP_SHA256INITIALIZE : return "OP_SHA256INITIALIZE"; case OP_SHA256UPDATE : return "OP_SHA256UPDATE"; case OP_SHA256FINALIZE : return "OP_SHA256FINALIZE"; + case OP_INSPECTINPUTOUTPOINT : return "OP_INSPECTINPUTOUTPOINT"; + case OP_INSPECTINPUTASSET : return "OP_INSPECTINPUTASSET"; + case OP_INSPECTINPUTVALUE : return "OP_INSPECTINPUTVALUE"; + case OP_INSPECTINPUTSCRIPTPUBKEY : return "OP_INSPECTINPUTSCRIPTPUBKEY"; + case OP_INSPECTINPUTSEQUENCE : return "OP_INSPECTINPUTSEQUENCE"; + case OP_INSPECTINPUTISSUANCE : return "OP_INSPECTINPUTISSUANCE"; + case OP_PUSHCURRENTINPUTINDEX : return "OP_PUSHCURRENTINPUTINDEX"; + case OP_INSPECTOUTPUTASSET : return "OP_INSPECTOUTPUTASSET"; + case OP_INSPECTOUTPUTVALUE : return "OP_INSPECTOUTPUTVALUE"; + case OP_INSPECTOUTPUTNONCE : return "OP_INSPECTOUTPUTNONCE"; + case OP_INSPECTOUTPUTSCRIPTPUBKEY : return "OP_INSPECTOUTPUTSCRIPTPUBKEY"; + case OP_INSPECTVERSION : return "OP_INSPECTVERSION"; + case OP_INSPECTLOCKTIME : return "OP_INSPECTLOCKTIME"; + case OP_INSPECTNUMINPUTS : return "OP_INSPECTNUMINPUTS"; + case OP_INSPECTNUMOUTPUTS : return "OP_INSPECTNUMOUTPUTS"; + case OP_TXWEIGHT : return "OP_TXWEIGHT"; + case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; default: diff --git a/src/script/script.h b/src/script/script.h index dff1bc527d..ed23ca8d53 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -218,11 +218,36 @@ enum opcodetype OP_SHA256UPDATE = 0xc5, OP_SHA256FINALIZE = 0xc6, + // Introspection opcodes + //inputs + OP_INSPECTINPUTOUTPOINT = 0xc7, + OP_INSPECTINPUTASSET = 0xc8, + OP_INSPECTINPUTVALUE = 0xc9, + OP_INSPECTINPUTSCRIPTPUBKEY = 0xca, + OP_INSPECTINPUTSEQUENCE = 0xcb, + OP_INSPECTINPUTISSUANCE = 0xcc, + + // current index + OP_PUSHCURRENTINPUTINDEX = 0xcd, + + // outputs + OP_INSPECTOUTPUTASSET = 0xce, + OP_INSPECTOUTPUTVALUE = 0xcf, + OP_INSPECTOUTPUTNONCE = 0xd0, + OP_INSPECTOUTPUTSCRIPTPUBKEY = 0xd1, + + // transaction + OP_INSPECTVERSION = 0xd2, + OP_INSPECTLOCKTIME = 0xd3, + OP_INSPECTNUMINPUTS = 0xd4, + OP_INSPECTNUMOUTPUTS = 0xd5, + OP_TXWEIGHT = 0xd6, + OP_INVALIDOPCODE = 0xff, }; // Maximum value that an opcode can be -static const unsigned int MAX_OPCODE = OP_SHA256FINALIZE; // 0xc6 +static const unsigned int MAX_OPCODE = OP_TXWEIGHT; // 0xd7 std::string GetOpName(opcodetype opcode); diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index 158c9796ee..9e66d8d3b0 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -119,6 +119,10 @@ std::string ScriptErrorString(const ScriptError serror) return "Invalid Sha256 context object read"; case SCRIPT_ERR_SHA2_CONTEXT_WRITE: return "Invalid Sha256 context object write"; + case SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE: + return "Introspection opcode used without correct evaluation context"; + case SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS: + return "Introspection index out of bounds"; case SCRIPT_ERR_UNKNOWN_ERROR: case SCRIPT_ERR_ERROR_COUNT: default: break; diff --git a/src/script/script_error.h b/src/script/script_error.h index 5e6d0f478d..a1c3a94441 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -92,6 +92,8 @@ typedef enum ScriptError_t /* Elements: New tapscript related errors */ SCRIPT_ERR_SHA2_CONTEXT_LOAD, SCRIPT_ERR_SHA2_CONTEXT_WRITE, + SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE, + SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS, } ScriptError; #define SCRIPT_ERR_LAST SCRIPT_ERR_ERROR_COUNT