mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Merge ElementsProject/elements#1020: Tapscript new opcodes
1577ed4e90fuzz: add fuzz test for segwitv0 and tapscript script interpreters (Andrew Poelstra)8a20fbe8a6Add XOnlyPubKey::CreateTapTweak (Andrew Poelstra)68961ca60eMake consensus checking of tweaks in pubkey.* Taproot-specific (Andrew Poelstra)c3945cb39fMake XOnlyPubKey act like byte container (Pieter Wuille)2c5c7eb87bUpdate tapscript leaf version (sanket1729)937343c75cAdd tests for crypto opcodes (sanket1729)7193636d09Add crypto opcodes (sanket1729)3e7b8c6aedTest arithmetic opcodes (sanket1729)4b9032209aImplement arithmetic opcodes (sanket1729)6092d94c1dAdd tests for introspection (sanket1729)9346a8495eImplement transaction introspection opcodes (sanket1729)97b53e0b89Cache Input/Output ScriptPubkeys sha256 (sanket1729)61b5152d44Expose transaction data via BaseSignatureChecker (sanket1729)007912eea4Implement tests for sha256 streaming opcodes (sanket1729)2df8e99813Implement 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: ACK1577ed4e90Tree-SHA512: 297cfe8699982d9575a566eda2f5f4a253968552dbc9ff000440da4d6f5b175a1619d7a5074e1ddf6818e2c51f54a33142a89a63494f26ad946ca09e0cff94c5
This commit is contained in:
commit
feee029d29
18 changed files with 1867 additions and 53 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
40
src/pubkey.h
40
src/pubkey.h
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
147
src/test/fuzz/witness_program.cpp
Normal file
147
src/test/fuzz/witness_program.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public:
|
|||
return &m_data[WIDTH];
|
||||
}
|
||||
|
||||
unsigned int size() const
|
||||
static constexpr unsigned int size()
|
||||
{
|
||||
return sizeof(m_data);
|
||||
}
|
||||
|
|
|
|||
590
test/functional/feature_tapscript_opcodes.py
Executable file
590
test/functional/feature_tapscript_opcodes.py
Executable 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()
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue