Merge ElementsProject/elements#1020: Tapscript new opcodes

1577ed4e90 fuzz: add fuzz test for segwitv0 and tapscript script interpreters (Andrew Poelstra)
8a20fbe8a6 Add XOnlyPubKey::CreateTapTweak (Andrew Poelstra)
68961ca60e Make consensus checking of tweaks in pubkey.* Taproot-specific (Andrew Poelstra)
c3945cb39f Make XOnlyPubKey act like byte container (Pieter Wuille)
2c5c7eb87b Update tapscript leaf version (sanket1729)
937343c75c Add tests for crypto opcodes (sanket1729)
7193636d09 Add crypto opcodes (sanket1729)
3e7b8c6aed Test arithmetic opcodes (sanket1729)
4b9032209a Implement arithmetic opcodes (sanket1729)
6092d94c1d Add tests for introspection (sanket1729)
9346a8495e Implement transaction introspection opcodes (sanket1729)
97b53e0b89 Cache Input/Output ScriptPubkeys sha256 (sanket1729)
61b5152d44 Expose transaction data via BaseSignatureChecker (sanket1729)
007912eea4 Implement tests for sha256 streaming opcodes (sanket1729)
2df8e99813 Implement Streaming SHA256 opcodes (sanket1729)

Pull request description:

  Please refer to the new spec here. https://github.com/ElementsProject/elements/pull/1019

ACKs for top commit:
  apoelstra:
    ACK 1577ed4e90

Tree-SHA512: 297cfe8699982d9575a566eda2f5f4a253968552dbc9ff000440da4d6f5b175a1619d7a5074e1ddf6818e2c51f54a33142a89a63494f26ad946ca09e0cff94c5
This commit is contained in:
Andrew Poelstra 2021-08-30 15:57:29 +00:00
commit feee029d29
No known key found for this signature in database
GPG key ID: C588D63CE41B97C1
18 changed files with 1867 additions and 53 deletions

View file

@ -157,7 +157,8 @@ FUZZ_TARGETS = \
test/fuzz/txrequest \
test/fuzz/txundo_deserialize \
test/fuzz/uint160_deserialize \
test/fuzz/uint256_deserialize
test/fuzz/uint256_deserialize \
test/fuzz/witness_program
if ENABLE_FUZZ
noinst_PROGRAMS += $(FUZZ_TARGETS:=)
@ -1267,6 +1268,12 @@ test_fuzz_uint256_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_uint256_deserialize_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON)
test_fuzz_uint256_deserialize_SOURCES = test/fuzz/deserialize.cpp
test_fuzz_witness_program_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DUINT256_DESERIALIZE=1
test_fuzz_witness_program_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_witness_program_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_witness_program_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON)
test_fuzz_witness_program_SOURCES = test/fuzz/witness_program.cpp
endif # ENABLE_FUZZ
nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES)

View file

@ -705,6 +705,59 @@ void CSHA256::Midstate(unsigned char hash[OUTPUT_SIZE], uint64_t* len, unsigned
}
}
std::vector<unsigned char> CSHA256::Save() const {
size_t buf_size = bytes % 64;
std::vector<unsigned char> result(40 + buf_size);
WriteBE32(&result[ 0], s[0]);
WriteBE32(&result[ 4], s[1]);
WriteBE32(&result[ 8], s[2]);
WriteBE32(&result[12], s[3]);
WriteBE32(&result[16], s[4]);
WriteBE32(&result[20], s[5]);
WriteBE32(&result[24], s[6]);
WriteBE32(&result[28], s[7]);
WriteLE64(&result[32], bytes << 3);
if (buf_size) memcpy(&result[40], buf, buf_size);
return result;
}
bool CSHA256::Load(const std::vector<unsigned char>& vch) {
if (vch.size() < 40) return false;
uint64_t bits = ReadLE64(&vch[32]);
size_t buf_size = (bits >> 3) % 64;
if ((bits & 0x07) != 0 || vch.size() != 40 + buf_size) return false;
// We want to leave the internal state of the object unchanged if false is returned.
// So no member variables can be modified until now.
s[0] = ReadBE32(&vch[ 0]);
s[1] = ReadBE32(&vch[ 4]);
s[2] = ReadBE32(&vch[ 8]);
s[3] = ReadBE32(&vch[12]);
s[4] = ReadBE32(&vch[16]);
s[5] = ReadBE32(&vch[20]);
s[6] = ReadBE32(&vch[24]);
s[7] = ReadBE32(&vch[28]);
bytes = bits >> 3;
if (buf_size) memcpy(buf, &vch[40], buf_size);
return true;
}
bool CSHA256::SafeWrite(const unsigned char* data, size_t len) {
const uint64_t SHA256_MAX = 0x1FFFFFFFFFFFFFFF; // SHA256's maximum allowed message length in bytes.
if (SHA256_MAX < bytes || SHA256_MAX - bytes < len) return false;
Write(data, len);
return true;
}
CSHA256& CSHA256::Reset()
{
bytes = 0;

View file

@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <vector>
/** A hasher class for SHA-256. */
class CSHA256
@ -26,7 +27,10 @@ public:
//TODO: Midstate is a hack'ish speedup that probably should make way for something
//akin to the SHA256D64 speedups
void Midstate(unsigned char hash[OUTPUT_SIZE], uint64_t* len, unsigned char *buffer);
std::vector<unsigned char> Save() const;
bool Load(const std::vector<unsigned char>& vch);
CSHA256& Reset();
bool SafeWrite(const unsigned char* data, size_t len);
};
/** Autodetect the best available SHA256 implementation.

View file

@ -173,14 +173,15 @@ XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
}
bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
bool XOnlyPubKey::VerifySchnorr(const Span<const unsigned char> msg, Span<const unsigned char> sigbytes) const
{
assert(sigbytes.size() == 64);
secp256k1_xonly_pubkey pubkey;
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data())) return false;
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), 32, &pubkey);
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.data(), msg.size(), &pubkey);
}
// ELEMENTS: this is preserved from an old version of the Taproot code for use in OP_TWEAKVERIFY
bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const
{
secp256k1_xonly_pubkey base_point;
@ -188,6 +189,57 @@ bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& has
return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &base_point, hash.begin());
}
static const CHashWriter HASHER_TAPTWEAK_ELEMENTS = TaggedHash("TapTweak/elements");
uint256 XOnlyPubKey::ComputeTapTweakHash(const uint256* merkle_root) const
{
if (merkle_root == nullptr) {
// We have no scripts. The actual tweak does not matter, but follow BIP341 here to
// allow for reproducible tweaking.
return (CHashWriter(HASHER_TAPTWEAK_ELEMENTS) << m_keydata).GetSHA256();
} else {
return (CHashWriter(HASHER_TAPTWEAK_ELEMENTS) << m_keydata << *merkle_root).GetSHA256();
}
}
bool XOnlyPubKey::CheckTapTweak(const XOnlyPubKey& internal, const uint256& merkle_root, bool parity) const
{
secp256k1_xonly_pubkey internal_key;
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &internal_key, internal.data())) return false;
uint256 tweak = internal.ComputeTapTweakHash(&merkle_root);
return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &internal_key, tweak.begin());
}
bool CPubKey::TweakMulVerify(const CPubKey& untweaked, const uint256& tweak) const
{
assert(this->IsCompressed());
secp256k1_pubkey pk;
if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pk, untweaked.data(), untweaked.size())) return false;
if (!secp256k1_ec_pubkey_tweak_mul(secp256k1_context_verify, &pk, tweak.data())) return false;
unsigned char out_pk[CPubKey::COMPRESSED_SIZE];
size_t out_len = CPubKey::COMPRESSED_SIZE;
if (!secp256k1_ec_pubkey_serialize(secp256k1_context_verify, out_pk, &out_len, &pk, SECP256K1_EC_COMPRESSED)) return false;
return *this == CPubKey(out_pk, out_pk + out_len);
}
Optional<std::pair<XOnlyPubKey, bool>> XOnlyPubKey::CreateTapTweak(const uint256* merkle_root) const
{
secp256k1_xonly_pubkey base_point;
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &base_point, data())) return nullopt;
secp256k1_pubkey out;
uint256 tweak = ComputeTapTweakHash(merkle_root);
if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_verify, &out, &base_point, tweak.data())) return nullopt;
int parity = -1;
std::pair<XOnlyPubKey, bool> ret;
secp256k1_xonly_pubkey out_xonly;
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_verify, &out_xonly, &parity, &out)) return nullopt;
secp256k1_xonly_pubkey_serialize(secp256k1_context_verify, ret.first.data(), &out_xonly);
assert(parity == 0 || parity == 1);
ret.second = parity;
return ret;
}
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
if (!IsValid())
return false;

View file

@ -8,6 +8,7 @@
#define BITCOIN_PUBKEY_H
#include <hash.h>
#include <optional.h>
#include <serialize.h>
#include <span.h>
#include <uint256.h>
@ -206,6 +207,9 @@ public:
//! Derive BIP32 child pubkey.
bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc, std::vector<unsigned char>* tweak = nullptr /* ELEMENTS: vector of key tweak values that are filled out if non-null */) const;
//! Verify that when this public key is tweaked with tweak, the result is res
bool TweakMulVerify(const CPubKey& res, const uint256& tweak) const;
};
class XOnlyPubKey
@ -214,6 +218,12 @@ private:
uint256 m_keydata;
public:
/** Construct an empty x-only pubkey. */
XOnlyPubKey() = default;
XOnlyPubKey(const XOnlyPubKey&) = default;
XOnlyPubKey& operator=(const XOnlyPubKey&) = default;
/** Construct an x-only pubkey from exactly 32 bytes. */
XOnlyPubKey(Span<const unsigned char> bytes);
@ -221,12 +231,38 @@ public:
*
* sigbytes must be exactly 64 bytes.
*/
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
bool VerifySchnorr(const Span<const unsigned char> msg, Span<const unsigned char> sigbytes) const;
// ELEMENTS: this is preserved from an old version of the Taproot code for use in OP_TWEAKVERIFY
bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const;
/** Compute the Taproot tweak as specified in BIP341, with *this as internal
* key:
* - if merkle_root == nullptr: H_TapTweak(xonly_pubkey)
* - otherwise: H_TapTweak(xonly_pubkey || *merkle_root)
*
* Note that the behavior of this function with merkle_root != nullptr is
* consensus critical.
*/
uint256 ComputeTapTweakHash(const uint256* merkle_root) const;
/** Verify that this is a Taproot tweaked output point, against a specified internal key,
* Merkle root, and parity. */
bool CheckTapTweak(const XOnlyPubKey& internal, const uint256& merkle_root, bool parity) const;
/** Construct a Taproot tweaked output point with this point as internal key. */
Optional<std::pair<XOnlyPubKey, bool>> CreateTapTweak(const uint256* merkle_root) const;
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
const unsigned char* data() const { return m_keydata.begin(); }
size_t size() const { return m_keydata.size(); }
static constexpr size_t size() { return decltype(m_keydata)::size(); }
const unsigned char* begin() const { return m_keydata.begin(); }
const unsigned char* end() const { return m_keydata.end(); }
unsigned char* begin() { return m_keydata.begin(); }
unsigned char* end() { return m_keydata.end(); }
unsigned char* data() { return m_keydata.begin(); }
bool operator==(const XOnlyPubKey& other) const { return m_keydata == other.m_keydata; }
bool operator!=(const XOnlyPubKey& other) const { return m_keydata != other.m_keydata; }
bool operator<(const XOnlyPubKey& other) const { return m_keydata < other.m_keydata; }
};
struct CExtPubKey {

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);
}
static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
static 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,12 +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_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) {}
@ -2331,6 +2969,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>;
@ -2392,8 +3073,8 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
}
k = ss_branch.GetSHA256();
}
k = (CHashWriter(HASHER_TAPTWEAK_ELEMENTS) << MakeSpan(p) << k).GetSHA256();
return q.CheckPayToContract(p, k, control[0] & 1);
// Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
return q.CheckTapTweak(p, k, control[0] & 1);
}
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh)

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;
@ -269,6 +273,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() {}
};
@ -292,6 +331,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);

View file

@ -0,0 +1,147 @@
// Copyright (c) 2009-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <policy/policy.h>
#include <pubkey.h>
#include <secp256k1.h>
#include <secp256k1_schnorrsig.h>
#include <script/interpreter.h>
#include <span.h>
#include <streams.h>
#include <test/fuzz/fuzz.h>
static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
class DummySigChecker : public MutableTransactionSignatureChecker {
bool CheckECDSASignature(const std::vector<unsigned char>& vchSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override { return !vchSig.empty() && vchSig[0] > 0x80; }
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override { return !sig.empty() && sig[0] > 0x80; }
public:
DummySigChecker(const CMutableTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, const PrecomputedTransactionData& txdataIn) : MutableTransactionSignatureChecker{txToIn, nInIn, amountIn, txdataIn} {}
};
void initialize()
{
static const ECCVerifyHandle verify_handle;
}
void test_one_input(const std::vector<uint8_t>& buffer)
{
CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION);
try {
CMutableTransaction tx;
unsigned int nIn;
CConfidentialValue amountIn;
ds >> tx;
ds >> nIn;
ds >> amountIn;
if (tx.vin.empty() || tx.witness.vtxinwit.size() != tx.vin.size()) {
return; // if we have no inputs then our script environment doesn't make sense
}
nIn %= tx.vin.size();
PrecomputedTransactionData txdata{};
CScriptWitness witness;
int fuzz_control;
int flags;
ds >> fuzz_control;
ds >> witness.stack;
ds >> flags;
if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0) {
flags |= SCRIPT_VERIFY_P2SH;
flags |= SCRIPT_VERIFY_WITNESS;
}
if ((flags & SCRIPT_VERIFY_WITNESS) != 0) {
flags |= SCRIPT_VERIFY_P2SH;
}
/* segwit v0 */
std::vector<unsigned char> program;
ds >> program;
if (fuzz_control & 1) {
unsigned char hash_program[32];
CSHA256().Write(&program[0], program.size()).Finalize(hash_program);
CScript scriptPubKey = CScript{} << OP_0 << std::vector<unsigned char>(hash_program, hash_program + sizeof(hash_program));
witness.stack.push_back(program);
std::vector<CTxOut> spent_outs{};
for (unsigned int i = 0; i < tx.vin.size(); i++) {
CConfidentialAsset asset;
CConfidentialValue value;
ds >> asset;
ds >> value;
if (asset.IsNull() || value.IsNull()) {
// this asserts in the interpreter, and are impossible to hit with real transactions
return;
}
if (i == nIn) {
spent_outs.push_back(CTxOut{asset, value, scriptPubKey});
} else {
CScript script;
ds >> script;
spent_outs.push_back(CTxOut{asset, value, script});
}
tx.witness.vtxinwit[i].scriptWitness.stack.push_back(program);
}
txdata.Init(tx, std::move(spent_outs));
DummySigChecker checker{&tx, nIn, amountIn, txdata};
VerifyScript(/* scriptSig */ CScript{}, scriptPubKey, &witness, flags, checker, /* serror */ NULL);
/* segwit v1 (taproot) */
} else {
/* Generate keys */
uint256 xonlypk;
ds >> xonlypk;
XOnlyPubKey intkey(xonlypk);
uint256 tapleaf_hash = (CHashWriter(HASHER_TAPLEAF_ELEMENTS) << uint8_t(TAPROOT_LEAF_TAPSCRIPT) << program).GetSHA256();
auto extkey_parity = intkey.CreateTapTweak(&tapleaf_hash);
if (!extkey_parity) {
return;
}
/* Serialize keys */
CScript scriptPubKey = CScript{} << OP_1 << std::vector<unsigned char>(extkey_parity->first.begin(), extkey_parity->first.end());
witness.stack.push_back(program);
std::vector<unsigned char> control;
control.push_back(TAPROOT_LEAF_TAPSCRIPT | extkey_parity->second);
control.insert(control.end(), intkey.begin(), intkey.end());
witness.stack.push_back(control);
std::vector<CTxOut> spent_outs{};
for (unsigned int i = 0; i < tx.vin.size(); i++) {
CConfidentialAsset asset;
CConfidentialValue value;
ds >> asset;
ds >> value;
if (asset.IsNull() || value.IsNull()) {
// this asserts in the interpreter, and are impossible to hit with real transactions
return;
}
if (i == nIn) {
spent_outs.push_back(CTxOut{asset, value, scriptPubKey});
} else {
CScript script;
ds >> script;
spent_outs.push_back(CTxOut{asset, value, script});
}
tx.witness.vtxinwit[i].scriptWitness.stack.push_back(program);
tx.witness.vtxinwit[i].scriptWitness.stack.push_back(control);
}
txdata.Init(tx, std::move(spent_outs));
GenericTransactionSignatureChecker checker{&tx, nIn, amountIn, txdata};
VerifyScript(/* scriptSig */ CScript{}, scriptPubKey, &witness, flags, checker, /* serror */ NULL);
}
} catch (const std::ios_base::failure&) {
return;
}
}

View file

@ -1470,7 +1470,7 @@ BOOST_AUTO_TEST_CASE(script_HasValidOps)
BOOST_CHECK(script.HasValidOps());
script = ScriptFromHex("ff88ac"); // Script with OP_INVALIDOPCODE explicit
BOOST_CHECK(!script.HasValidOps());
script = ScriptFromHex("88acc4"); // Script with undefined opcode: one higher then MAX_OPCODE
script = ScriptFromHex("88ace5"); // Script with undefined opcode: one higher then MAX_OPCODE
BOOST_CHECK(!script.HasValidOps());
}

View file

@ -76,7 +76,7 @@ public:
return &m_data[WIDTH];
}
unsigned int size() const
static constexpr unsigned int size()
{
return sizeof(m_data);
}

View file

@ -0,0 +1,590 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
# Test for taproot sighash algorithm with pegins and issuances
from random import randint
from test_framework.util import BITCOIN_ASSET_BYTES, assert_raises_rpc_error, satoshi_round
from test_framework.key import ECKey, ECPubKey, compute_xonly_pubkey, generate_privkey, sign_schnorr, tweak_add_privkey, tweak_add_pubkey, verify_schnorr
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, CTxOutNonce, CTxOutValue, CTxOutWitness, FromHex, ser_uint256, sha256, uint256_from_str
from test_framework.test_framework import BitcoinTestFramework
from test_framework.script import CScript, CScriptNum, CScriptOp, OP_0, OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_7, OP_ADD64, OP_AND, OP_CAT, OP_CHECKSIGFROMSTACK, OP_DIV64, OP_DROP, OP_DUP, OP_ECMULSCALARVERIFY, OP_ELSE, OP_EQUAL, OP_EQUALVERIFY, OP_FALSE, OP_FROMALTSTACK, OP_GREATERTHAN64, OP_GREATERTHANOREQUAL64, OP_INSPECTINPUTASSET, OP_INSPECTINPUTISSUANCE, OP_INSPECTINPUTOUTPOINT, OP_INSPECTINPUTSCRIPTPUBKEY, OP_INSPECTINPUTSEQUENCE, OP_INSPECTINPUTVALUE, OP_INSPECTLOCKTIME, OP_INSPECTNUMINPUTS, OP_INSPECTNUMOUTPUTS, OP_INSPECTOUTPUTASSET, OP_INSPECTOUTPUTNONCE, OP_INSPECTOUTPUTSCRIPTPUBKEY, OP_INSPECTOUTPUTVALUE, OP_IF, OP_INSPECTVERSION, OP_LE32TOLE64, OP_LE64TOSCRIPTNUM, OP_LESSTHAN64, OP_LESSTHANOREQUAL64, OP_MUL64, OP_NEG64, OP_NOT, OP_INVERT, OP_NOTIF, OP_OR, OP_PUSHCURRENTINPUTINDEX, OP_SCRIPTNUMTOLE64, OP_SHA256FINALIZE, OP_SHA256INITIALIZE, OP_SHA256UPDATE, OP_SIZE, OP_SUB64, OP_SWAP, OP_TWEAKVERIFY, OP_TOALTSTACK, OP_TXWEIGHT, OP_VERIFY, OP_XOR, OP_XOR, TaprootSignatureHash, taproot_construct, SIGHASH_DEFAULT, SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAY
import os
VALID_SIGHASHES_ECDSA = [
SIGHASH_ALL,
SIGHASH_NONE,
SIGHASH_SINGLE,
SIGHASH_ANYONECANPAY + SIGHASH_ALL,
SIGHASH_ANYONECANPAY + SIGHASH_NONE,
SIGHASH_ANYONECANPAY + SIGHASH_SINGLE
]
VALID_SIGHASHES_TAPROOT = [SIGHASH_DEFAULT] + VALID_SIGHASHES_ECDSA
class TapHashPeginTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [
["-initialfreecoins=2100000000000000",
"-anyonecanspendaremine=1",
"-blindedaddresses=1",
"-validatepegin=0",
"-con_parent_chain_signblockscript=51",
"-parentscriptprefix=75",
"-parent_bech32_hrp=ert",
"-minrelaytxfee=0",
"-maxtxfee=100.0",
]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self, split=False):
self.setup_nodes()
def get_utxo(self, fund_tx, idx):
spent = None
# Coin selection
for utxo in self.nodes[0].listunspent():
if utxo["txid"] == ser_uint256(fund_tx.vin[idx].prevout.hash)[::-1].hex() and utxo["vout"] == fund_tx.vin[idx].prevout.n:
spent = utxo
assert(spent is not None)
assert(len(fund_tx.vin) == 2)
return spent
def create_taproot_utxo(self, scripts = None, blind = False):
# modify the transaction to add one output that should spend previous taproot
# Create a taproot prevout
addr = self.nodes[0].getnewaddress()
sec = generate_privkey()
pub = compute_xonly_pubkey(sec)[0]
tap = taproot_construct(pub, scripts)
spk = tap.scriptPubKey
# No need to test blinding in this unit test
unconf_addr = self.nodes[0].getaddressinfo(addr)['unconfidential']
raw_tx = self.nodes[0].createrawtransaction([], [{unconf_addr: 1.2}])
# edit spk directly, no way to get new address.
# would need to implement bech32m in python
tx = FromHex(CTransaction(), raw_tx)
tx.vout[0].scriptPubKey = spk
tx.vout[0].nValue = CTxOutValue(12*10**7)
raw_hex = tx.serialize().hex()
fund_tx = self.nodes[0].fundrawtransaction(raw_hex, False, )["hex"]
fund_tx = FromHex(CTransaction(), fund_tx)
# Createrawtransaction might rearrage txouts
prev_vout = None
for i, out in enumerate(fund_tx.vout):
if spk == out.scriptPubKey:
prev_vout = i
tx = self.nodes[0].blindrawtransaction(fund_tx.serialize().hex())
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(tx)
_txid = self.nodes[0].sendrawtransaction(signed_raw_tx['hex'])
tx = FromHex(CTransaction(), signed_raw_tx['hex'])
tx.rehash()
self.nodes[0].generate(1)
last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash())
assert(tx.hash in last_blk['tx'])
return tx, prev_vout, spk, sec, pub, tap
def tapscript_satisfy_test(self, script, inputs = [], add_issuance = False,
add_pegin = False, fail=None, add_prevout=False, add_asset=False,
add_value=False, add_spk = False, seq = 0, add_out_spk = None,
add_out_asset = None, add_out_value = None, add_out_nonce = None,
ver = 2, locktime = 0, add_num_outputs=False, add_weight=False, blind=False):
# Create a taproot utxo
scripts = [("s0", script)]
prev_tx, prev_vout, spk, sec, pub, tap = self.create_taproot_utxo(scripts)
if add_pegin:
fund_info = self.nodes[0].getpeginaddress()
peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1)
raw_peg_tx = self.nodes[0].gettransaction(peg_id)["hex"]
peg_txid = self.nodes[0].sendrawtransaction(raw_peg_tx)
self.nodes[0].generate(101)
peg_prf = self.nodes[0].gettxoutproof([peg_txid])
claim_script = fund_info["claim_script"]
raw_claim = self.nodes[0].createrawpegin(raw_peg_tx, peg_prf, claim_script)
tx = FromHex(CTransaction(), raw_claim['hex'])
else:
tx = CTransaction()
tx.nVersion = ver
tx.nLockTime = locktime
# Spend the pegin and taproot tx together
in_total = prev_tx.vout[prev_vout].nValue.getAmount()
fees = 1000
tap_in_pos = 0
if blind:
# Add an unrelated output
key = ECKey()
key.generate()
tx.vout.append(CTxOut(nValue = CTxOutValue(10000), scriptPubKey = spk, nNonce=CTxOutNonce(key.get_pubkey().get_bytes())))
tx_hex = self.nodes[0].fundrawtransaction(tx.serialize().hex())
tx = FromHex(CTransaction(), tx_hex['hex'])
tx.vin.append(CTxIn(COutPoint(prev_tx.sha256, prev_vout), nSequence=seq))
tx.vout.append(CTxOut(nValue = CTxOutValue(in_total - fees), scriptPubKey = spk)) # send back to self
tx.vout.append(CTxOut(CTxOutValue(fees)))
if add_issuance:
blind_addr = self.nodes[0].getnewaddress()
issue_addr = self.nodes[0].validateaddress(blind_addr)['unconfidential']
# Issuances only require one fee output and that output must the last
# one. However the way, the current code is structured, it is not possible
# to this in a super clean without special casing.
if add_pegin:
tx.vout.pop()
tx.vout.pop()
tx.vout.insert(0, CTxOut(nValue = CTxOutValue(in_total), scriptPubKey = spk)) # send back to self)
issued_tx = self.nodes[0].rawissueasset(tx.serialize().hex(), [{"asset_amount":2, "asset_address":issue_addr, "blind":False}])[0]["hex"]
tx = FromHex(CTransaction(), issued_tx)
# Sign inputs
if add_pegin:
signed = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
tx = FromHex(CTransaction(), signed['hex'])
tap_in_pos += 1
else:
# Need to create empty witness when not deserializing from rpc
tx.wit.vtxinwit.append(CTxInWitness())
if blind:
tx.vin[0], tx.vin[1] = tx.vin[1], tx.vin[0]
utxo = self.get_utxo(tx, 1)
zero_str = "0"*64
blinded_raw = self.nodes[0].rawblindrawtransaction(tx.serialize().hex(), [zero_str, utxo["amountblinder"]], [1.2, utxo['amount']], [utxo['asset'], utxo['asset']], [zero_str, utxo['assetblinder']])
tx = FromHex(CTransaction(), blinded_raw)
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
tx = FromHex(CTransaction(), signed_raw_tx['hex'])
suffix_annex = []
control_block = bytes([tap.leaves["s0"].version + tap.negflag]) + tap.inner_pubkey + tap.leaves["s0"].merklebranch
# Add the prevout to the top of inputs. The witness script will check for equality.
if add_prevout:
inputs = [prev_vout.to_bytes(4, 'little'), ser_uint256(prev_tx.sha256)]
if add_asset:
assert blind # only used with blinding in testing
utxo = self.nodes[0].gettxout(ser_uint256(tx.vin[1].prevout.hash)[::-1].hex(), tx.vin[1].prevout.n)
if "assetcommitment" in utxo:
asset = bytes.fromhex(utxo["assetcommitment"])
else:
asset = b"\x01" + bytes.fromhex(utxo["asset"])[::-1]
inputs = [asset[0:1], asset[1:33]]
if add_value:
utxo = self.nodes[0].gettxout(ser_uint256(tx.vin[1].prevout.hash)[::-1].hex(), tx.vin[1].prevout.n)
if "valuecommitment" in utxo:
value = bytes.fromhex(utxo["valuecommitment"])
inputs = [value[0:1], value[1:33]]
else:
value = b"\x01" + int(satoshi_round(utxo["value"])*COIN).to_bytes(8, 'little')
inputs = [value[0:1], value[1:9]]
if add_spk:
ver = CScriptOp.decode_op_n(int.from_bytes(spk[0:1], 'little'))
inputs = [CScriptNum.encode(CScriptNum(ver))[1:], spk[2:len(spk)]] # always segwit
# Add witness for outputs
if add_out_asset is not None:
asset = tx.vout[add_out_asset].nAsset.vchCommitment
inputs = [asset[0:1], asset[1:33]]
if add_out_value is not None:
value = tx.vout[add_out_value].nValue.vchCommitment
if len(value) == 9:
inputs = [value[0:1], value[1:9][::-1]]
else:
inputs = [value[0:1], value[1:33]]
if add_out_nonce is not None:
nonce = tx.vout[add_out_nonce].nNonce.vchCommitment
if len(nonce) == 1:
inputs = [b'']
else:
inputs = [nonce]
if add_out_spk is not None:
out_spk = tx.vout[add_out_spk].scriptPubKey
if len(out_spk) == 0:
# Python upstream encoding CScriptNum interesting behaviour where it also encodes the length
# This assumes the implicit wallet behaviour of using segwit outputs.
# This is useful while sending scripts, but not while using CScriptNums in constructing scripts
inputs = [CScriptNum.encode(CScriptNum(-1))[1:], sha256(out_spk)]
else:
ver = CScriptOp.decode_op_n(int.from_bytes(out_spk[0:1], 'little'))
inputs = [CScriptNum.encode(CScriptNum(ver))[1:], out_spk[2:len(out_spk)]] # always segwit
if add_num_outputs:
num_outs = len(tx.vout)
inputs = [CScriptNum.encode(CScriptNum(num_outs))[1:]]
if add_weight:
# Add a dummy input and check the overall weight
inputs = [int(5).to_bytes(8, 'little')]
wit = inputs + [bytes(tap.leaves["s0"].script), control_block] + suffix_annex
tx.wit.vtxinwit[tap_in_pos].scriptWitness.stack = wit
exp_weight = self.nodes[0].decoderawtransaction(tx.serialize().hex())["weight"]
inputs = [exp_weight.to_bytes(8, 'little')]
wit = inputs + [bytes(tap.leaves["s0"].script), control_block] + suffix_annex
tx.wit.vtxinwit[tap_in_pos].scriptWitness.stack = wit
if fail:
assert_raises_rpc_error(-26, fail, self.nodes[0].sendrawtransaction, tx.serialize().hex())
return
self.nodes[0].sendrawtransaction(hexstring = tx.serialize().hex())
self.nodes[0].generate(1)
last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash())
tx.rehash()
assert(tx.hash in last_blk['tx'])
def run_test(self):
self.nodes[0].generate(101)
self.wait_until(lambda: self.nodes[0].getblockcount() == 101, timeout=5)
# Test whether the above test framework is working
self.log.info("Test simple op_1")
self.tapscript_satisfy_test(CScript([OP_1]))
# Test streaming sha256
# All preimages upto len 80 can be filled in a single standard witness element
# Testing only random even len for speedup
self.log.info("Testing streaming SHA256")
for k in range(0, 81, 2):
preimage = os.urandom(k)
hash = sha256(preimage)
self.tapscript_satisfy_test(CScript([OP_SHA256INITIALIZE, OP_SWAP, OP_SHA256FINALIZE, hash, OP_EQUAL]), inputs = [bytes(0), preimage])
# All preimages greater than 80 require Cating the elements
# But this only works till 520 bytes, streaming opcodes can
# be when dealing with more than 520 bytes
# Also adds a test when the sha256 buffer is full
for pre_len in [64*i for i in range(2, 10)] + [100, 520, 521, 800, 1000, 10000]:
preimage = os.urandom(pre_len)
hash = sha256(preimage)
# Split into chunks of 80
wit = [preimage[i:min(i + 80, len(preimage))] for i in range(0, len(preimage), 80)]
script = CScript([OP_SHA256INITIALIZE] + [OP_SWAP, OP_SHA256UPDATE]*(len(wit)-2) + [OP_SWAP, OP_SHA256FINALIZE, hash, OP_EQUAL])
self.tapscript_satisfy_test(script, wit[::-1])
# Test introspection opcodes
# 1a. No Pegins/issuances
self.log.info("Instrospection tests: outpoint flag")
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]))
# 1b. Add a pegin (Test pegin input must be 0x40)
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x40', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin=True)
# 1c. Add pegin (The non-pegin input must be push 0x00)
self.tapscript_satisfy_test(CScript([OP_1, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin=True)
# 1d. Add an issuance
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x80', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_issuance=True)
# 1e. Both issuance and pegins together
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\xc0', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin = True, add_issuance=True)
# 1f. Failure test case
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_DROP, OP_DROP, OP_1]), add_pegin = True, add_issuance=True, fail="Script failed an OP_EQUALVERIFY operation")
# Test opcode for inspecting prev tx
self.log.info("Instrospection tests: inputs")
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTOUTPOINT, b'\x00', OP_EQUALVERIFY, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), add_prevout=True)
# Test taproot asset with blinding.
self.tapscript_satisfy_test(CScript([OP_1]), blind=True)
# 2 Test asset(explicit and conf)
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTASSET, 1, OP_EQUALVERIFY, BITCOIN_ASSET_BYTES, OP_EQUAL]))
for _ in range(5): # run multiple times for wallet to select conf utxos
self.tapscript_satisfy_test(CScript([OP_1, OP_INSPECTINPUTASSET, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=True, add_asset=True)
# 3 Test asset(explicit and conf)
amt = 12*10**7
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTVALUE, 1, OP_EQUALVERIFY, amt.to_bytes(8, 'little'), OP_EQUAL]))
for _ in range(5): # run multiple times for wallet to select conf utxos
self.tapscript_satisfy_test(CScript([OP_1, OP_INSPECTINPUTVALUE, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=True, add_value=True)
# 4 Test scriptpubkey
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTSCRIPTPUBKEY, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), add_spk = True)
# 5 Test nSequence
one, two = 1, 2
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTSEQUENCE, one.to_bytes(4, 'little'), OP_EQUAL]), seq = 1)
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTSEQUENCE, two.to_bytes(4, 'little'), OP_EQUAL]), seq = 1, fail="Script evaluated without error but finished with a false/empty top stack element")
# 6 Test issuances
# No issaunce
self.tapscript_satisfy_test(CScript([OP_0, OP_INSPECTINPUTISSUANCE, OP_FALSE, OP_EQUAL]))
# Issuance with 2 units
asset_issue_amount = 2*COIN
self.tapscript_satisfy_test(CScript([
OP_0, OP_INSPECTINPUTISSUANCE,
# Check blinding nonce is zero
bytes(32), OP_EQUALVERIFY,
# Check the entropy is 32 bytes
OP_SIZE, 32, OP_EQUALVERIFY, OP_DROP,
# Check the explicit issue amount
1, OP_EQUALVERIFY, asset_issue_amount.to_bytes(8, 'little'), OP_EQUALVERIFY,
# Last inflation keys is null, check it as explicit `0` LE 8
1, OP_EQUALVERIFY, int(0).to_bytes(8, 'little'), OP_EQUAL
]), add_issuance=True)
# Input index out of bounds
self.tapscript_satisfy_test(CScript([120, OP_1, OP_INSPECTINPUTASSET, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
self.tapscript_satisfy_test(CScript([-1, OP_1, OP_INSPECTINPUTVALUE, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
# Test current input
self.log.info("Instrospection tests: current input index")
self.tapscript_satisfy_test(CScript([OP_PUSHCURRENTINPUTINDEX, OP_0, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([OP_PUSHCURRENTINPUTINDEX, OP_1, OP_EQUAL]), fail="Script evaluated without error but finished with a false/empty top stack element")
# Test Outputs
self.log.info("Instrospection tests: outputs")
for blind in [True, False]:
for out_pos in [0, 1]:
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTASSET, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=blind, add_out_asset=out_pos)
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTVALUE, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUAL]), blind=blind, add_out_value=out_pos)
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTNONCE, OP_EQUAL]), blind=blind, add_out_nonce=out_pos)
self.tapscript_satisfy_test(CScript([out_pos, OP_INSPECTOUTPUTSCRIPTPUBKEY, OP_TOALTSTACK, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_EQUALVERIFY, OP_1]), blind=blind, add_out_spk=out_pos)
# Test that output index out of bounds fail
self.tapscript_satisfy_test(CScript([120, OP_INSPECTOUTPUTASSET, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
self.tapscript_satisfy_test(CScript([-1, OP_INSPECTOUTPUTVALUE, OP_FALSE, OP_EQUAL]), fail="Introspection index out of bounds")
# Finally, check the tx instrospection
self.log.info("Instrospection tests: tx")
# Test version equality
self.tapscript_satisfy_test(CScript([OP_INSPECTVERSION, int(2).to_bytes(4, 'little'), OP_EQUAL]), ver = 2)
self.tapscript_satisfy_test(CScript([OP_INSPECTVERSION, int(5).to_bytes(4, 'little'), OP_EQUAL]), ver = 2, fail="Script evaluated without error but finished with a false/empty top stack element")
# Test nlocktime
self.tapscript_satisfy_test(CScript([OP_INSPECTLOCKTIME, int(7).to_bytes(4, 'little'), OP_EQUAL]), locktime = 7)
self.tapscript_satisfy_test(CScript([OP_INSPECTLOCKTIME, int(7).to_bytes(4, 'little'), OP_EQUAL]), locktime = 10, fail="Script evaluated without error but finished with a false/empty top stack element")
# Test num_inputs
self.tapscript_satisfy_test(CScript([OP_INSPECTNUMINPUTS, OP_1, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([OP_INSPECTNUMINPUTS, OP_2, OP_EQUAL]), fail="Script evaluated without error but finished with a false/empty top stack element")
# Test num_outputs
self.tapscript_satisfy_test(CScript([OP_INSPECTNUMOUTPUTS, OP_EQUAL]), add_num_outputs= True)
# Test tx wieght
self.tapscript_satisfy_test(CScript([OP_TXWEIGHT, OP_EQUAL]), add_weight= True)
# short handle to convert int to 8 byte LE
def le8(x, signed=True):
return int(x).to_bytes(8, 'little', signed=signed)
def check_add(a, b, c, fail=None):
self.tapscript_satisfy_test(CScript([OP_ADD64, OP_VERIFY, le8(c), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_sub(a, b, c, fail=None):
self.tapscript_satisfy_test(CScript([OP_SUB64, OP_VERIFY, le8(c), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_mul(a, b, c, fail=None):
self.tapscript_satisfy_test(CScript([OP_MUL64, OP_VERIFY, le8(c), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_div(a, b, q, r, fail=None):
self.tapscript_satisfy_test(CScript([OP_DIV64, OP_VERIFY, le8(q), OP_EQUALVERIFY, le8(r), OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_le(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_LESSTHAN64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_leq(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_LESSTHANOREQUAL64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_ge(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_GREATERTHAN64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_geq(a, b, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_GREATERTHANOREQUAL64, res, OP_EQUAL]), inputs = [le8(a), le8(b)], fail=fail)
def check_neg(a, res, fail=None):
self.tapscript_satisfy_test(CScript([OP_NEG64, OP_VERIFY, le8(res), OP_EQUAL]), inputs = [le8(a)], fail=fail)
# Arithematic opcodes
self.log.info("Check Arithmetic opcodes")
check_add(5, 5, 10)
check_add(-5, -5, -10)
check_add(-5, 5, 0)
check_add(14231, -123213, 14231 - 123213)
check_add(2**63 - 1, 5, 4, fail="Script failed an OP_VERIFY operation")#overflow
check_add(5, 2**63 - 1, 4, fail="Script failed an OP_VERIFY operation")#overflow
check_add(-5, -2**63 + 1, -4, fail="Script failed an OP_VERIFY operation")#overflow
# Substraction
check_sub(5, 6, -1)
check_sub(-5, 6, -11)
check_sub(-5, -5, 0)
check_sub(14231, -123213, 14231 + 123213)
check_sub(2**63 - 1, 4, 2**63 - 5)
check_sub(-5, 2**63 - 1, -4, fail="Script failed an OP_VERIFY operation")#overflow
check_sub(2**63 - 1, -4, 5, fail="Script failed an OP_VERIFY operation")#overflow
# Multiplication
check_mul(5, 6, 30)
check_mul(-5, 6, -30)
check_mul(-5, 0, 0)
check_mul(-5, -6, 30)
check_mul(14231, -123213, -14231 * 123213)
check_mul(2**32, 2**31 - 1, 2**63 - 2**32)
check_mul(2**32, 2**31, 0, fail="Script failed an OP_VERIFY operation")#overflow
check_mul(-2**32, 2**31, -2**63)# no overflow
check_mul(-2**32, -2**32, 0, fail="Script failed an OP_VERIFY operation")#overflow
# Division
check_div(5, 6, 0, 5)
check_div(4, 2, 2, 0)
check_div(-5, 6, -1, 1) # r must be in 0<=r<a
check_div(5, -6, 0, 5) # r must be in 0<=r<a
check_div(-5, 0, 0, 0, fail="Script failed an OP_VERIFY operation") # only fails if b = 0
check_div(-2**63, -1, 0, 0, fail="Script failed an OP_VERIFY operation") # failure on -2**63/-1
check_div(6213123213513, 621, 6213123213513//621, 6213123213513 % 621)
check_div(2**62, 2**31, 2**31, 0)
# Less than test
# Comparison tests
# Less than
check_le(5, 6, 1)
check_le(5, 5, 0)
check_le(6, 5, 0)
# Less than equal
check_leq(5, 6, 1)
check_leq(5, 5, 1)
check_leq(6, 5, 0)
# Greater than
check_ge(5, 6, 0)
check_ge(5, 5, 0)
check_ge(6, 5, 1)
# Greater than equal
check_geq(5, 6, 0)
check_geq(5, 5, 1)
check_geq(6, 5, 1)
# equal
check_neg(5, -5)
check_neg(-5, 5)
check_neg(5, 4, fail="Script evaluated without error but finished with a false/empty top stack element")
check_neg(-2**63, 0, fail="Script failed an OP_VERIFY operation")
check_neg(2**63-1, -2**63 + 1)
# Test boolean operations
for _ in range(5):
a = randint(-2**63, 2**63 -1)
b = randint(-2**63, 2**63 -1)
self.tapscript_satisfy_test(CScript([OP_AND, le8(a & b), OP_EQUAL]), inputs = [le8(a), le8(b)])
self.tapscript_satisfy_test(CScript([OP_OR, le8(a | b), OP_EQUAL]), inputs = [le8(a), le8(b)])
self.tapscript_satisfy_test(CScript([OP_INVERT, le8(~a), OP_EQUAL]), inputs = [le8(a)])
self.tapscript_satisfy_test(CScript([OP_XOR, le8(a ^ b), OP_EQUAL]), inputs = [le8(a), le8(b)])
# Finally, test conversion opcodes
self.log.info("Check conversion opcodes")
for n in [-2**31 + 1, -1, 0, 1, 2**31 - 1]:
self.tapscript_satisfy_test(CScript([le8(n), OP_LE64TOSCRIPTNUM, n, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([n, OP_SCRIPTNUMTOLE64, le8(n), OP_EQUAL]))
self.tapscript_satisfy_test(CScript([n, OP_DUP, OP_SCRIPTNUMTOLE64, OP_LE64TOSCRIPTNUM, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([le8(n), OP_DUP, OP_LE64TOSCRIPTNUM, OP_SCRIPTNUMTOLE64, OP_EQUAL]))
self.tapscript_satisfy_test(CScript([n.to_bytes(4, 'little', signed=True), OP_LE32TOLE64, le8(n), OP_EQUAL]), fail= "Script evaluated without error but finished with a false/empty top stack element" if (n < 0) else None)
# Non 8 byte inputs
self.tapscript_satisfy_test(CScript([OP_ADD64, OP_VERIFY, le8(9), OP_EQUAL]), inputs = [le8(0), int(9).to_bytes(7, 'little')], fail="Arithmetic opcodes expect 8 bytes operands")
# Overflow inputs
self.tapscript_satisfy_test(CScript([le8(2**31), OP_LE64TOSCRIPTNUM, 2**31, OP_EQUAL]), fail = "Arithmetic opcode error")
self.log.info("Check Crypto opcodes")
res = bytes.fromhex("03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7")
scalar = bytes.fromhex("000000000000000000000000000000000000000000000000000000000000000a")
g = bytes.fromhex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [res, g, scalar])
res = bytes.fromhex("032c0158d0f6df4881e99e65fbea21f27321d817f79ad39e08eaf4f16f1419bb0c")
scalar = bytes.fromhex("e0f47c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6")
g = bytes.fromhex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [res, g, scalar])
# test that other random values fail correctly
res = bytes.fromhex("032c0158d0f6df4881e99e65fbea21f27321d817f79ad39e08eaf4f16f1419bb0c")
scalar = bytes.fromhex("e0f47c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6")
g = bytes.fromhex("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")# y co-ordinate mutated
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [res, g, scalar], fail="Public key is neither compressed or uncompressed")
invalid_full_pks_33 = [
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81700" # invalid point on curve
]
invalid_full_pks_non33 = [
"0979be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", # 33 byte without 0x02,0x03
"0462b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9a7a946ce481411e4034143e0acad4e79aecb2a8212ee5cca26a7cc5fe5b45881", # compressed
"62b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9", # x-only key
"0262b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9f9f9" # 34 byte key
]
invalid_tweaks = [
"e07c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6", # non 32 byte hash
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364143" # order + 2
]
invalid_xonly_keys = [
"0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", # 33 byte valid key
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81700", # invalid point on curve
"0462b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706f9a7a946ce481411e4034143e0acad4e79aecb2a8212ee5cca26a7cc5fe5b45881", # compressed
"62b31419b87d5e095e9d7532f92aea39c39735253a48fbdaff48441d5cf706" # 31 byte x-only key
]
valid_res = bytes.fromhex("032c0158d0f6df4881e99e65fbea21f27321d817f79ad39e08eaf4f16f1419bb0c")
valid_scalar = bytes.fromhex("e0f47c124f228b97bbdc0e4398aac9788869b9fbbc193d5323fdad9570609de6")
valid_g = bytes.fromhex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
valid_xonly_key = bytes.fromhex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
for invalid_tweak in invalid_tweaks:
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [valid_res, valid_g, bytes.fromhex(invalid_tweak)], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_tweaks[1]), valid_xonly_key], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_tweaks[0]), valid_xonly_key], fail="Public key is neither compressed or uncompressed")
for invalid_full_pk in invalid_full_pks_33:
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [bytes.fromhex(invalid_full_pk), valid_g, valid_scalar], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_full_pk), valid_scalar], fail="EC scalar mult verify fail")
for invalid_full_pk in invalid_full_pks_non33:
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [bytes.fromhex(invalid_full_pk), valid_g, valid_scalar], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_ECMULSCALARVERIFY, OP_1]), inputs = [valid_res, bytes.fromhex(invalid_full_pk), valid_scalar], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[0])], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[1])], fail="EC scalar mult verify fail")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[2])], fail="Public key is neither compressed or uncompressed")
self.tapscript_satisfy_test(CScript([OP_TWEAKVERIFY, OP_1]), inputs = [valid_res, valid_scalar, bytes.fromhex(invalid_xonly_keys[3])], fail="Public key is neither compressed or uncompressed")
# Checksigfromstack tests
# 32 byte msg
def csfs_test(msg, mutute_sig = False, fail=None):
sec = generate_privkey()
msg = bytes.fromhex(msg)
pub = compute_xonly_pubkey(sec)[0]
sig = sign_schnorr(sec, msg)
if mutute_sig:
new_sig = sig[:63]
new_sig += (sig[63] ^ 0xff).to_bytes(1, 'little') # flip the last bit
sig = new_sig
self.tapscript_satisfy_test(CScript([msg, pub, OP_CHECKSIGFROMSTACK]), inputs = [sig], fail=fail)
csfs_test("e168c349d0d2499caf3a6d71734c743d517f94f8571fa52c04285b68deec1936")
csfs_test("Hello World!".encode('utf-8').hex())
csfs_test("Hello World!".encode('utf-8').hex(), fail="Invalid Schnorr signature", mutute_sig=True)
msg = bytes.fromhex("e168c349d0d2499caf3a6d71734c743d517f94f8571fa52c04285b68deec1936")
pub = bytes.fromhex("3f67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b3")
self.tapscript_satisfy_test(CScript([OP_0, msg, pub, OP_CHECKSIGFROMSTACK, OP_NOT]), inputs = [])
short_sig = bytes.fromhex("0102")
self.tapscript_satisfy_test(CScript([short_sig, msg, pub, OP_CHECKSIGFROMSTACK, OP_NOT]), inputs = [], fail="Invalid Schnorr signature size")
short_pub = bytes.fromhex("67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b3")
long_pub = bytes.fromhex("67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b30908")
sig = bytes.fromhex("3f67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b33f67e97da0df6931189cfb0072447da22707897bd5de04936a277ed7e00b35b3")
self.tapscript_satisfy_test(CScript([msg, short_pub, OP_CHECKSIGFROMSTACK]), inputs = [sig], fail="Public key version reserved for soft-fork upgrades")
self.tapscript_satisfy_test(CScript([msg, long_pub, OP_CHECKSIGFROMSTACK]), inputs = [sig], fail="Public key version reserved for soft-fork upgrades")
if __name__ == '__main__':
TapHashPeginTest().main()

View file

@ -483,7 +483,7 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
aux = bytes(32)
assert len(key) == 32
assert len(msg) == 32
# Variable length signature message support is required for checksigfromstack
assert len(aux) == 32
sec = int.from_bytes(key, 'big')

View file

@ -35,7 +35,7 @@ ANNEX_TAG = 0x50
OPCODE_NAMES = {} # type: Dict[CScriptOp, str]
LEAF_VERSION_TAPSCRIPT = 0xc0
LEAF_VERSION_TAPSCRIPT = 0xc4
def hash160(s):
return hashlib.new('ripemd160', sha256(s)).digest()
@ -239,6 +239,8 @@ OP_CHECKSIG = CScriptOp(0xac)
OP_CHECKSIGVERIFY = CScriptOp(0xad)
OP_CHECKMULTISIG = CScriptOp(0xae)
OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
OP_CHECKSIGFROMSTACK = CScriptOp(0xc1)
OP_CHECKSIGFROMSTACKVERIFY = CScriptOp(0xc2)
# expansion
OP_NOP1 = CScriptOp(0xb0)
@ -255,6 +257,50 @@ OP_NOP10 = CScriptOp(0xb9)
# BIP 342 opcodes (Tapscript)
OP_CHECKSIGADD = CScriptOp(0xba)
# Elements Tapscript extension
OP_SHA256INITIALIZE = CScriptOp(0xc4)
OP_SHA256UPDATE = CScriptOp(0xc5)
OP_SHA256FINALIZE = CScriptOp(0xc6)
# inputs
OP_INSPECTINPUTOUTPOINT = CScriptOp(0xc7)
OP_INSPECTINPUTASSET = CScriptOp(0xc8)
OP_INSPECTINPUTVALUE = CScriptOp(0xc9)
OP_INSPECTINPUTSCRIPTPUBKEY = CScriptOp(0xca)
OP_INSPECTINPUTSEQUENCE = CScriptOp(0xcb)
OP_INSPECTINPUTISSUANCE = CScriptOp(0xcc)
# current index
OP_PUSHCURRENTINPUTINDEX = CScriptOp(0xcd)
# outputs
OP_INSPECTOUTPUTASSET = CScriptOp(0xce)
OP_INSPECTOUTPUTVALUE = CScriptOp(0xcf)
OP_INSPECTOUTPUTNONCE = CScriptOp(0xd0)
OP_INSPECTOUTPUTSCRIPTPUBKEY = CScriptOp(0xd1)
# transaction
OP_INSPECTVERSION = CScriptOp(0xd2)
OP_INSPECTLOCKTIME = CScriptOp(0xd3)
OP_INSPECTNUMINPUTS = CScriptOp(0xd4)
OP_INSPECTNUMOUTPUTS = CScriptOp(0xd5)
OP_TXWEIGHT = CScriptOp(0xd6)
# Arithmetic opcodes
OP_ADD64 = CScriptOp(0xd7)
OP_SUB64 = CScriptOp(0xd8)
OP_MUL64 = CScriptOp(0xd9)
OP_DIV64 = CScriptOp(0xda)
OP_NEG64 = CScriptOp(0xdb)
OP_LESSTHAN64 = CScriptOp(0xdc)
OP_LESSTHANOREQUAL64 = CScriptOp(0xdd)
OP_GREATERTHAN64 = CScriptOp(0xde)
OP_GREATERTHANOREQUAL64 = CScriptOp(0xdf)
# Conversion opcodes
OP_SCRIPTNUMTOLE64 = CScriptOp(0xe0)
OP_LE64TOSCRIPTNUM = CScriptOp(0xe1)
OP_LE32TOLE64 = CScriptOp(0xe2)
# Tapscript crypto opcodes
OP_ECMULSCALARVERIFY = CScriptOp(0xe3)
OP_TWEAKVERIFY = CScriptOp(0xe4)
OP_INVALIDOPCODE = CScriptOp(0xff)
OPCODE_NAMES.update({
@ -371,6 +417,42 @@ OPCODE_NAMES.update({
OP_NOP10: 'OP_NOP10',
OP_CHECKSIGADD: 'OP_CHECKSIGADD',
OP_INVALIDOPCODE: 'OP_INVALIDOPCODE',
OP_SHA256INITIALIZE : 'OP_SHA256INITIALIZE',
OP_SHA256UPDATE : 'OP_SHA256UPDATE',
OP_SHA256FINALIZE : 'OP_SHA256FINALIZE',
OP_INSPECTINPUTOUTPOINT: 'OP_INSPECTINPUTOUTPOINT',
OP_INSPECTINPUTASSET: 'OP_INSPECTINPUTASSET',
OP_INSPECTINPUTVALUE: 'OP_INSPECTINPUTVALUE',
OP_INSPECTINPUTSCRIPTPUBKEY: 'OP_INSPECTINPUTSCRIPTPUBKEY',
OP_INSPECTINPUTSEQUENCE: 'OP_INSPECTINPUTSEQUENCE',
OP_INSPECTINPUTISSUANCE: 'OP_INSPECTINPUTISSUANCE',
OP_PUSHCURRENTINPUTINDEX: 'OP_PUSHCURRENTINPUTINDEX',
OP_INSPECTOUTPUTASSET: 'OP_INSPECTOUTPUTASSET',
OP_INSPECTOUTPUTVALUE: 'OP_INSPECTOUTPUTVALUE',
OP_INSPECTOUTPUTNONCE: 'OP_INSPECTOUTPUTNONCE',
OP_INSPECTOUTPUTSCRIPTPUBKEY: 'OP_INSPECTOUTPUTSCRIPTPUBKEY',
OP_INSPECTVERSION: 'OP_INSPECTVERSION',
OP_INSPECTLOCKTIME: 'OP_INSPECTLOCKTIME',
OP_INSPECTNUMINPUTS: 'OP_INSPECTNUMINPUTS',
OP_INSPECTNUMOUTPUTS: 'OP_INSPECTNUMOUTPUTS',
OP_TXWEIGHT: 'OP_TXWEIGHT',
OP_INVALIDOPCODE: 'OP_INVALIDOPCODE',
OP_CHECKSIGFROMSTACK: 'OP_CHECKSIGFROMSTACK',
OP_TWEAKVERIFY: 'OP_TWEAKVERIFY',
OP_ADD64: 'OP_ADD64',
OP_SUB64: 'OP_SUB64',
OP_MUL64: 'OP_MUL64',
OP_DIV64: 'OP_DIV64',
OP_LESSTHAN64: 'OP_LESSTHAN64',
OP_LESSTHANOREQUAL64: 'OP_LESSTHANOREQUAL64',
OP_GREATERTHAN64: 'OP_GREATERTHAN64',
OP_GREATERTHANOREQUAL64: 'OP_GREATERTHANOREQUAL64',
OP_NEG64: 'OP_NEG64',
OP_SCRIPTNUMTOLE64: 'OP_SCRIPTNUMTOLE64',
OP_LE64TOSCRIPTNUM: 'OP_LE64TOSCRIPTNUM',
OP_LE32TOLE64: 'OP_LE32TOLE64',
OP_CHECKSIGFROMSTACKVERIFY: 'OP_CHECKSIGFROMSTACKVERIFY',
OP_ECMULSCALARVERIFY: 'OP_ECMULSCALARVERIFY',
})
class CScriptInvalidError(Exception):
@ -936,4 +1018,4 @@ def taproot_construct(pubkey, scripts=None):
return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves)
def is_op_success(o):
return o == 80 or o == 98 or (o >= 137 and o <= 138) or (o >= 141 and o <= 142) or (o >= 149 and o <= 151) or (o >= 187 and o <= 191) or (o >= 196 and o <= 254)
return o == 80 or o == 98 or (o >= 137 and o <= 138) or (o >= 141 and o <= 142) or (o >= 149 and o <= 151) or (o >= 187 and o <= 191) or (o >= 229 and o <= 254)

View file

@ -135,6 +135,7 @@ BASE_SCRIPTS = [
'wallet_listtransactions.py --descriptors',
'feature_taproot.py',
'feature_taphash_pegins_issuances.py',
'feature_tapscript_opcodes.py',
# vv Tests less than 60s vv
'p2p_sendheaders.py',
'wallet_importmulti.py --legacy-wallet',