Merge feee029d29 into merged_master (Elements PR ElementsProject/elements#1020)

Messy merge conflicts because this PR backported some ad-hoc stuff from
upstream while keeping a few things that upstream deleted. Hopefully
reviewing is easier than doing this in the first place, since ultimately
all I did was delete code from one side or another of the conflicts.
(Ok, I also changed some boost optional stuff to std::optional, and had
to patch up a test file for test framework changes.)

When reviewing the detailed crypto, bear in mind that taptweaks, like all
hashes are the kind of crypto that cannot be subtly wrong -- it will either
fail very hard or be correct. And we have independent implementations in
C++ and Python that cross-check each other, si it's unlikely to be the former.
This commit is contained in:
Andrew Poelstra 2021-09-05 12:35:58 +00:00
commit 6387aaf765
17 changed files with 1795 additions and 48 deletions

View file

@ -5,6 +5,7 @@
#include <script/interpreter.h>
#include <consensus/consensus.h>
#include <crypto/ripemd160.h>
#include <crypto/sha1.h>
#include <crypto/sha256.h>
@ -14,6 +15,10 @@
typedef std::vector<unsigned char> valtype;
// These asserts are consensus critical for elements tapscript arithmetic opcodes
static_assert(static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) == UINT64_C(0x7FFFFFFFFFFFFFFF));
static_assert(static_cast<uint64_t>(std::numeric_limits<int64_t>::min()) == UINT64_C(0x8000000000000000));
namespace {
inline bool set_success(ScriptError* ret)
@ -60,6 +65,78 @@ static inline void popstack(std::vector<valtype>& stack)
stack.pop_back();
}
static inline int64_t cast_signed64(uint64_t v)
{
uint64_t int64_min = static_cast<uint64_t>(std::numeric_limits<int64_t>::min());
if (v >= int64_min)
return static_cast<int64_t>(v - int64_min) + std::numeric_limits<int64_t>::min();
return static_cast<int64_t>(v);
}
static inline int64_t read_le8_signed(const unsigned char* ptr)
{
return cast_signed64(ReadLE64(ptr));
}
static inline void push4_le(std::vector<valtype>& stack, uint32_t v)
{
uint32_t v_le = htole32(v);
stack.emplace_back(reinterpret_cast<unsigned char*>(&v_le), reinterpret_cast<unsigned char*>(&v_le) + sizeof(v_le));
}
static inline void push8_le(std::vector<valtype>& stack, uint64_t v)
{
uint64_t v_le = htole64(v);
stack.emplace_back(reinterpret_cast<unsigned char*>(&v_le), reinterpret_cast<unsigned char*>(&v_le) + sizeof(v_le));
}
static inline void pushasset(std::vector<valtype>& 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<valtype>& 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<valtype>& 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 <class T>
inline unsigned char GetOutpointFlag(const T& txin)
{
return static_cast<unsigned char> ((!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
@ -356,6 +433,17 @@ public:
};
}
// Check the script has sufficient sigops budget for checksig(crypto) operation
inline bool update_validation_weight(ScriptExecutionData& execdata, ScriptError* serror)
{
assert(execdata.m_validation_weight_left_init);
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
if (execdata.m_validation_weight_left < 0) {
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
}
return true;
}
static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess)
{
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0);
@ -382,6 +470,47 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu
return true;
}
static bool EvalTapScriptCheckSigFromStack(const valtype& sig, const valtype& vchPubKey, ScriptExecutionData& execdata, unsigned int flags, const valtype& msg, SigVersion sigversion, ScriptError* serror, bool& success)
{
// This code follows the behaviour of EvalCheckSigTapscript
assert(sigversion == SigVersion::TAPSCRIPT);
/*
* The following validation sequence is consensus critical. Please note how --
* upgradable public key versions precede other rules;
* the script execution fails when using empty signature with invalid public key;
* the script execution fails when using non-empty invalid signature.
*/
success = !sig.empty();
if (success) {
// Implement the sigops/witnesssize ratio test.
// Passing with an upgradable public key version is also counted.
if (!update_validation_weight(execdata, serror)) return false; // serror is set
}
if (vchPubKey.size() == 0) {
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
} else if (vchPubKey.size() == 32) {
if (success) {
if (sig.size() != 64)
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE);
const XOnlyPubKey pubkey{vchPubKey};
if (!pubkey.VerifySchnorr(msg, sig))
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
}
} else {
/*
* New public key version softforks should be defined before this `else` block.
* Generally, the new code should not do anything but failing the script execution. To avoid
* consensus bugs, it should not modify any existing values (including `success`).
*/
if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
}
}
return true;
}
static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
{
assert(sigversion == SigVersion::TAPSCRIPT);
@ -396,11 +525,7 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr
if (success) {
// Implement the sigops/witnesssize ratio test.
// Passing with an upgradable public key version is also counted.
assert(execdata.m_validation_weight_left_init);
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
if (execdata.m_validation_weight_left < 0) {
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
}
if (!update_validation_weight(execdata, serror)) return false; // serror is set
}
if (pubkey.size() == 0) {
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
@ -442,6 +567,10 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con
assert(false);
}
const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
{
static const CScriptNum bnZero(0);
@ -1587,29 +1716,521 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
valtype& vchSig = stacktop(-3);
valtype& vchData = stacktop(-2);
valtype& vchPubKey = stacktop(-1);
bool fSuccess;
// Different semantics for CHECKSIGFROMSTACK for taproot and pre-taproot
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0)
{
// Sigs from stack have no hash byte ever
if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
//serror is set
return false;
}
// Sigs from stack have no hash byte ever
if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
//serror is set
return false;
valtype vchHash(CSHA256::OUTPUT_SIZE);
CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data());
uint256 hash(vchHash);
CPubKey pubkey(vchPubKey);
fSuccess = pubkey.Verify(hash, vchSig);
// CHECKSIGFROMSTACK in pre-tapscript cannot be failed.
if (!fSuccess)
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
} else {
// New BIP 340 semantics for CHECKSIGFROMSTACK
if (!EvalTapScriptCheckSigFromStack(vchSig, vchPubKey, execdata, flags, vchData, sigversion, serror, fSuccess)) return false;
}
valtype vchHash(32);
CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data());
uint256 hash(vchHash);
CPubKey pubkey(vchPubKey);
bool fSuccess = pubkey.Verify(hash, vchSig);
popstack(stack);
popstack(stack);
popstack(stack);
stack.push_back(fSuccess ? vchTrue : vchFalse);
if (opcode == OP_CHECKSIGFROMSTACKVERIFY)
popstack(stack);
{
if (fSuccess)
popstack(stack);
else
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
}
}
break;
if (!fSuccess)
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
case OP_SHA256INITIALIZE: // (in -- sha256_ctx)
{
// OP_SHA256INITIALIZE is only available in 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);
CSHA256 ctx;
valtype& vch = stacktop(-1);
if (!ctx.SafeWrite(vch.data(), vch.size()))
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_WRITE);
popstack(stack);
stack.push_back(ctx.Save());
}
break;
case OP_SHA256UPDATE: // (sha256_ctx in -- sha256_ctx)
{
// OP_SHA256UPDATE is only available in Tapscript
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
if (stack.size() < 2)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
CSHA256 ctx;
valtype& vchCtx = stacktop(-2);
if (!ctx.Load(vchCtx))
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_LOAD);
valtype& vch = stacktop(-1);
if (!ctx.SafeWrite(vch.data(), vch.size()))
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_WRITE);
popstack(stack);
popstack(stack);
stack.push_back(ctx.Save());
}
break;
case OP_SHA256FINALIZE: // (sha256_ctx in -- hash)
{
// OP_SHA256FINALIZE is only available in Tapscript
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
if (stack.size() < 2)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
valtype& vchCtx = stacktop(-2);
CSHA256 ctx;
if (!ctx.Load(vchCtx))
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_LOAD);
valtype& vch = stacktop(-1);
if (!ctx.SafeWrite(vch.data(), vch.size()))
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_WRITE);
valtype vchHash(CHash256::OUTPUT_SIZE);
ctx.Finalize(vchHash.data());
popstack(stack);
popstack(stack);
stack.push_back(std::move(vchHash));
}
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<CTxOut>& 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<unsigned int>(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<int64_t>(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<unsigned int>(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<uint32_t>(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<int64_t>(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<int64_t>(num_outs)).getvch());
break;
}
case OP_TXWEIGHT:
{
push8_le(stack, checker.GetTxWeight());
break;
}
default: assert(!"invalid opcode"); break;
}
}
break;
case OP_ADD64:
case OP_SUB64:
case OP_MUL64:
case OP_DIV64:
case OP_LESSTHAN64:
case OP_LESSTHANOREQUAL64:
case OP_GREATERTHAN64:
case OP_GREATERTHANOREQUAL64:
{
// Opcodes only available post tapscript
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
if (stack.size() < 2)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
valtype& vcha = stacktop(-2);
valtype& vchb = stacktop(-1);
if (vchb.size() != 8 || vcha.size() != 8)
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
int64_t b = read_le8_signed(vchb.data());
int64_t a = read_le8_signed(vcha.data());
switch(opcode)
{
case OP_ADD64:
if ((a > 0 && b > std::numeric_limits<int64_t>::max() - a) ||
(a < 0 && b < std::numeric_limits<int64_t>::min() - a))
stack.push_back(vchFalse);
else {
popstack(stack);
popstack(stack);
push8_le(stack, a + b);
stack.push_back(vchTrue);
}
break;
case OP_SUB64:
if ((b > 0 && a < std::numeric_limits<int64_t>::min() + b) ||
(b < 0 && a > std::numeric_limits<int64_t>::max() + b))
stack.push_back(vchFalse);
else {
popstack(stack);
popstack(stack);
push8_le(stack, a - b);
stack.push_back(vchTrue);
}
break;
case OP_MUL64:
if ((a > 0 && b > 0 && a > std::numeric_limits<int64_t>::max() / b) ||
(a > 0 && b < 0 && b < std::numeric_limits<int64_t>::min() / a) ||
(a < 0 && b > 0 && a < std::numeric_limits<int64_t>::min() / b) ||
(a < 0 && b < 0 && b < std::numeric_limits<int64_t>::max() / a))
stack.push_back(vchFalse);
else {
popstack(stack);
popstack(stack);
push8_le(stack, a * b);
stack.push_back(vchTrue);
}
break;
case OP_DIV64:
{
if (b == 0 || (b == -1 && a == std::numeric_limits<int64_t>::min())) { stack.push_back(vchFalse); break; }
int64_t r = a % b;
int64_t q = a / b;
if (r < 0 && b > 0) { r += b; q-=1;} // ensures that 0<=r<|b|
else if (r < 0 && b < 0) { r -= b; q+=1;} // ensures that 0<=r<|b|
popstack(stack);
popstack(stack);
push8_le(stack, r);
push8_le(stack, q);
stack.push_back(vchTrue);
}
break;
break;
case OP_LESSTHAN64: popstack(stack); popstack(stack); stack.push_back( (a < b) ? vchTrue : vchFalse ); break;
case OP_LESSTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a <= b) ? vchTrue : vchFalse ); break;
case OP_GREATERTHAN64: popstack(stack); popstack(stack); stack.push_back( (a > b) ? vchTrue : vchFalse ); break;
case OP_GREATERTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a >= b) ? vchTrue : vchFalse ); break;
default: assert(!"invalid opcode"); break;
}
}
break;
case OP_NEG64:
{
// 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);
valtype& vcha = stacktop(-1);
if (vcha.size() != 8)
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
int64_t a = read_le8_signed(vcha.data());
if (a == std::numeric_limits<int64_t>::min()) { stack.push_back(vchFalse); break; }
popstack(stack);
push8_le(stack, -a);
stack.push_back(vchTrue);
}
break;
case OP_SCRIPTNUMTOLE64:
{
// 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);
int64_t num = CScriptNum(stacktop(-1), fRequireMinimal).getint();
popstack(stack);
push8_le(stack, num);
}
break;
case OP_LE64TOSCRIPTNUM:
{
// 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);
valtype& vchnum = stacktop(-1);
if (vchnum.size() != 8)
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
valtype vchscript_num = CScriptNum(read_le8_signed(vchnum.data())).getvch();
if (vchscript_num.size() > CScriptNum::nDefaultMaxNumSize) {
return set_error(serror, SCRIPT_ERR_ARITHMETIC64);
} else {
popstack(stack);
stack.push_back(std::move(vchscript_num));
}
}
break;
case OP_LE32TOLE64:
{
// 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);
valtype& vchnum = stacktop(-1);
if (vchnum.size() != 4)
return set_error(serror, SCRIPT_ERR_ARITHMETIC64);
uint32_t num = ReadLE32(vchnum.data());
popstack(stack);
push8_le(stack, static_cast<int64_t>(num));
}
break;
case OP_ECMULSCALARVERIFY:
{
// OP_ECMULSCALARVERIFY is available post tapscript
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
valtype& vchRes = stacktop(-3);
valtype& vchGenerator = stacktop(-2);
valtype& vchScalar = stacktop(-1);
CPubKey pk(vchGenerator);
CPubKey res(vchRes);
if (!pk.IsCompressed() || !res.IsCompressed())
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
if (!update_validation_weight(execdata, serror)) return false; // serror is set
if (vchScalar.size() != 32 || !res.TweakMulVerify(pk, uint256(vchScalar)))
return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL);
popstack(stack);
popstack(stack);
popstack(stack);
}
break;
//crypto opcodes
case OP_TWEAKVERIFY:
{
// OP_TWEAKVERIFY is available post tapscript
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
valtype& vchTweakedKey = stacktop(-3);
valtype& vchTweak = stacktop(-2);
valtype& vchInternalKey = stacktop(-1);
if (vchTweakedKey.size() != CPubKey::COMPRESSED_SIZE || (vchTweakedKey[0] != 0x02 && vchTweakedKey[0] != 0x03)
|| vchInternalKey.size() != 32 || vchTweak.size() != 32)
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
if (!update_validation_weight(execdata, serror)) return false; // serror is set
const XOnlyPubKey tweakedXOnlyKey{Span<const unsigned char>{vchTweakedKey.data() + 1, vchTweakedKey.data() + CPubKey::COMPRESSED_SIZE}};
const uint256 tweak(vchTweak);
const XOnlyPubKey internalKey{vchInternalKey};
if (!tweakedXOnlyKey.CheckPayToContract(internalKey, tweak, vchTweakedKey[0] & 1))
return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL);
popstack(stack);
popstack(stack);
popstack(stack);
}
break;
@ -1757,14 +2378,6 @@ public:
}
};
/** Compute the outpoint flag(u8) for a given txin **/
template <class T>
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 <class T>
uint256 GetOutpointFlagsSHA256(const T& txTo)
@ -1873,6 +2486,35 @@ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent)
return ss.GetSHA256();
}
/** Compute the vector where each element is SHA256 of scriptPubKeys spent by a tx. */
std::vector<uint256> GetSpentScriptPubKeysSHA256(const std::vector<CTxOut>& outputs_spent)
{
std::vector<uint256> spent_spk_single_hashes;
spent_spk_single_hashes.reserve(outputs_spent.size());
for (const auto& txout : outputs_spent) {
// Normal serialization using the << operater would also serialize the length, therefore we directly write using CSHA256
uint256 spent_spk_single_hash;
CSHA256().Write(txout.scriptPubKey.data(), txout.scriptPubKey.size()).Finalize(spent_spk_single_hash.data());
spent_spk_single_hashes.push_back(std::move(spent_spk_single_hash));
}
return spent_spk_single_hashes;
}
/** Compute the vector where each element is SHA256 of output scriptPubKey of a tx. */
template <class T>
std::vector<uint256> GetOutputScriptPubKeysSHA256(const T& txTo)
{
std::vector<uint256> out_spk_single_hashes;
out_spk_single_hashes.reserve(txTo.vout.size());
for (const auto& txout : txTo.vout) {
// Normal serialization using the << operater would also serialize the length, therefore we directly write using CSHA256
uint256 out_spk_single_hash;
CSHA256().Write(txout.scriptPubKey.data(), txout.scriptPubKey.size()).Finalize(out_spk_single_hash.data());
out_spk_single_hashes.push_back(std::move(out_spk_single_hash));
}
return out_spk_single_hashes;
}
template <class T>
uint256 GetRangeproofsHash(const T& txTo) {
CHashWriter ss(SER_GETHASH, 0);
@ -1944,6 +2586,8 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
m_issuance_rangeproofs_single_hash = GetIssuanceRangeproofsSHA256(txTo);
m_output_witnesses_single_hash = GetOutputWitnessesSHA256(txTo);
m_spent_scripts_single_hash = GetSpentScriptsSHA256(m_spent_outputs);
m_spent_output_spk_single_hashes = GetSpentScriptPubKeysSHA256(m_spent_outputs);
m_output_spk_single_hashes = GetOutputScriptPubKeysSHA256(txTo);
m_bip341_taproot_ready = true;
}
}
@ -1961,10 +2605,6 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo,
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
PrecomputedTransactionData::PrecomputedTransactionData(const uint256& hash_genesis_block)
: m_tapsighash_hasher(CHashWriter(HASHER_TAPSIGHASH_ELEMENTS) << hash_genesis_block << hash_genesis_block) {}
@ -2346,6 +2986,49 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
return true;
}
template <class T>
uint32_t GenericTransactionSignatureChecker<T>::GetLockTime() const
{
return txTo->nLockTime;
}
template <class T>
int32_t GenericTransactionSignatureChecker<T>::GetTxVersion() const
{
return txTo->nVersion;
}
template <class T>
const std::vector<CTxIn>* GenericTransactionSignatureChecker<T>::GetTxvIn() const
{
return &(txTo->vin);
}
template <class T>
const std::vector<CTxOut>* GenericTransactionSignatureChecker<T>::GetTxvOut() const
{
return &(txTo->vout);
}
template <class T>
uint64_t GenericTransactionSignatureChecker<T>::GetTxWeight() const
{
// line copied from GetTransactionWeight() in src/consensus/validation.h
return ::GetSerializeSize(*txTo, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(*txTo, PROTOCOL_VERSION);
}
template <class T>
const PrecomputedTransactionData* GenericTransactionSignatureChecker<T>::GetPrecomputedTransactionData() const
{
return txdata;
}
template <class T>
uint32_t GenericTransactionSignatureChecker<T>::GetnIn() const
{
return nIn;
}
// explicit instantiation
template class GenericTransactionSignatureChecker<CTransaction>;
template class GenericTransactionSignatureChecker<CMutableTransaction>;

View file

@ -173,7 +173,9 @@ struct PrecomputedTransactionData
uint256 m_issuances_single_hash;
uint256 m_output_witnesses_single_hash;
uint256 m_issuance_rangeproofs_single_hash;
//! Whether the 10 fields above are initialized.
//! transaction outputs scriptpubkey sha single hash
std::vector<uint256> m_output_spk_single_hashes;
//! Whether the 11 fields above are initialized.
bool m_bip341_taproot_ready = false;
// BIP143 precomputed data (double-SHA256).
@ -182,6 +184,8 @@ struct PrecomputedTransactionData
bool m_bip143_segwit_ready = false;
std::vector<CTxOut> m_spent_outputs;
//! ELEMENTS: spent outputs scriptpubkey sha single hash
std::vector<uint256> m_spent_output_spk_single_hashes;
//! Whether m_spent_outputs is initialized.
bool m_spent_outputs_ready = false;
@ -237,7 +241,7 @@ static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
static constexpr size_t WITNESS_V1_TAPROOT_SIZE = 32;
static constexpr uint8_t TAPROOT_LEAF_MASK = 0xfe;
static constexpr uint8_t TAPROOT_LEAF_TAPSCRIPT = 0xc0;
static constexpr uint8_t TAPROOT_LEAF_TAPSCRIPT = 0xc4;
static constexpr size_t TAPROOT_CONTROL_BASE_SIZE = 33;
static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
@ -272,6 +276,41 @@ public:
return false;
}
virtual const std::vector<CTxIn>* GetTxvIn() const
{
return nullptr;
}
virtual const std::vector<CTxOut>* GetTxvOut() const
{
return nullptr;
}
virtual uint32_t GetLockTime() const
{
return 0;
}
virtual int32_t GetTxVersion() const
{
return 0;
}
virtual uint64_t GetTxWeight() const
{
return 0;
}
virtual const PrecomputedTransactionData* GetPrecomputedTransactionData() const
{
return nullptr;
}
virtual uint32_t GetnIn() const
{
return std::numeric_limits<uint32_t>::max();
}
virtual ~BaseSignatureChecker() {}
};
@ -308,6 +347,14 @@ public:
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;
const std::vector<CTxIn>* GetTxvIn() const override;
const std::vector<CTxOut>* GetTxvOut() const override;
uint32_t GetLockTime() const override;
int32_t GetTxVersion() const override;
uint64_t GetTxWeight() const override;
const PrecomputedTransactionData* GetPrecomputedTransactionData() const override;
uint32_t GetnIn() const override;
};
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;

View file

@ -147,6 +147,45 @@ std::string GetOpName(opcodetype opcode)
// Opcode added by BIP 342 (Tapscript)
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";
// Elements Tapscript opcodes
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";
// 64 bit LE arithmetic opcodes
case OP_ADD64 : return "OP_ADD64";
case OP_SUB64 : return "OP_SUB64";
case OP_MUL64 : return "OP_MUL64";
case OP_DIV64 : return "OP_DIV64";
case OP_NEG64 : return "OP_NEG64";
case OP_LESSTHAN64 : return "OP_LESSTHAN64";
case OP_LESSTHANOREQUAL64 : return "OP_LESSTHANOREQUAL64";
case OP_GREATERTHAN64 : return "OP_GREATERTHAN64";
case OP_GREATERTHANOREQUAL64 : return "OP_GREATERTHANOREQUAL64";
case OP_SCRIPTNUMTOLE64 : return "OP_SCRIPTNUMTOLE64";
case OP_LE64TOSCRIPTNUM : return "OP_LE64TOSCRIPTNUM";
case OP_LE32TOLE64 : return "OP_LE32TOLE64";
// Crypto opcodes
case OP_ECMULSCALARVERIFY : return "OP_ECMULSCALARVERIFY";
case OP_TWEAKVERIFY : return "OP_TWEAKVERIFY";
case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";
default:
@ -415,6 +454,7 @@ bool IsOpSuccess(const opcodetype& opcode)
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);
// ELEMENTS: Exclude OP_DETERMINISTICRANDOM(192), OP_CHECKSIGFROMSTACK(VERIFY)(192-193), OP_SUBSTRLAZY(195)
// ELEMENTS: Tapscript extension from OP_SHA256INITIALIZE(196) till OP_TWEAKVERIFY(228)
(opcode >= 187 && opcode <= 191) || (opcode >= 229 && opcode <= 254);
}

View file

@ -213,11 +213,61 @@ enum opcodetype
// Opcode added by BIP 342 (Tapscript)
OP_CHECKSIGADD = 0xba,
// Elements: Tapscript (Streaming sha2 opcodes)
OP_SHA256INITIALIZE = 0xc4,
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,
// Arithmetic opcodes
OP_ADD64 = 0xd7,
OP_SUB64 = 0xd8,
OP_MUL64 = 0xd9,
OP_DIV64 = 0xda,
OP_NEG64 = 0xdb,
OP_LESSTHAN64 = 0xdc,
OP_LESSTHANOREQUAL64 = 0xdd,
OP_GREATERTHAN64 = 0xde,
OP_GREATERTHANOREQUAL64 = 0xdf,
// Conversion opcodes
OP_SCRIPTNUMTOLE64 = 0xe0,
OP_LE64TOSCRIPTNUM = 0xe1,
OP_LE32TOLE64 = 0xe2,
// Crypto opcodes
OP_ECMULSCALARVERIFY = 0xe3,
OP_TWEAKVERIFY = 0xe4,
OP_INVALIDOPCODE = 0xff,
};
// Maximum value that an opcode can be
static const unsigned int MAX_OPCODE = OP_SUBSTR_LAZY; // 0xc3
static const unsigned int MAX_OPCODE = OP_TWEAKVERIFY; // 0xe4
std::string GetOpName(opcodetype opcode);

View file

@ -115,6 +115,20 @@ std::string ScriptErrorString(const ScriptError serror)
return "Using OP_CODESEPARATOR in non-witness script";
case SCRIPT_ERR_SIG_FINDANDDELETE:
return "Signature is found in scriptCode";
case SCRIPT_ERR_SHA2_CONTEXT_LOAD:
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_EXPECTED_8BYTES:
return "Arithmetic opcodes expect 8 bytes operands";
case SCRIPT_ERR_ARITHMETIC64:
return "Arithmetic opcode error";
case SCRIPT_ERR_ECMULTVERIFYFAIL:
return "EC scalar mult verify fail";
case SCRIPT_ERR_UNKNOWN_ERROR:
case SCRIPT_ERR_ERROR_COUNT:
default: break;

View file

@ -86,10 +86,20 @@ typedef enum ScriptError_t
// ELEMENTS:
SCRIPT_ERR_RANGEPROOF,
SCRIPT_ERR_PEDERSEN_TALLY
SCRIPT_ERR_PEDERSEN_TALLY,
/* 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,
SCRIPT_ERR_EXPECTED_8BYTES,
SCRIPT_ERR_ARITHMETIC64,
SCRIPT_ERR_ECMULTVERIFYFAIL
} ScriptError;
#define SCRIPT_ERR_LAST SCRIPT_ERR_ERROR_COUNT
#define SCRIPT_ERR_LAST SCRIPT_ERR_ECMULTVERIFYFAIL
std::string ScriptErrorString(const ScriptError error);