mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Merge feee029d29 into merged_master (Elements PR ElementsProject/elements#1020)
Messy merge conflicts because this PR backported some ad-hoc stuff from upstream while keeping a few things that upstream deleted. Hopefully reviewing is easier than doing this in the first place, since ultimately all I did was delete code from one side or another of the conflicts. (Ok, I also changed some boost optional stuff to std::optional, and had to patch up a test file for test framework changes.) When reviewing the detailed crypto, bear in mind that taptweaks, like all hashes are the kind of crypto that cannot be subtly wrong -- it will either fail very hard or be correct. And we have independent implementations in C++ and Python that cross-check each other, si it's unlikely to be the former.
This commit is contained in:
commit
6387aaf765
17 changed files with 1795 additions and 48 deletions
|
|
@ -313,7 +313,8 @@ test_fuzz_fuzz_SOURCES = \
|
|||
test/fuzz/txrequest.cpp \
|
||||
test/fuzz/utxo_snapshot.cpp \
|
||||
test/fuzz/validation_load_mempool.cpp \
|
||||
test/fuzz/versionbits.cpp
|
||||
test/fuzz/versionbits.cpp \
|
||||
test/fuzz/witness_program.cpp
|
||||
endif # ENABLE_FUZZ_BINARY
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -186,12 +186,20 @@ bool XOnlyPubKey::IsFullyValid() const
|
|||
return secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data());
|
||||
}
|
||||
|
||||
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;
|
||||
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &base_point, base.data())) return false;
|
||||
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");
|
||||
|
|
@ -215,6 +223,18 @@ bool XOnlyPubKey::CheckTapTweak(const XOnlyPubKey& internal, const uint256& merk
|
|||
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);
|
||||
}
|
||||
|
||||
std::optional<std::pair<XOnlyPubKey, bool>> XOnlyPubKey::CreateTapTweak(const uint256* merkle_root) const
|
||||
{
|
||||
secp256k1_xonly_pubkey base_point;
|
||||
|
|
@ -226,7 +246,7 @@ std::optional<std::pair<XOnlyPubKey, bool>> XOnlyPubKey::CreateTapTweak(const ui
|
|||
std::pair<XOnlyPubKey, bool> ret;
|
||||
secp256k1_xonly_pubkey out_xonly;
|
||||
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_verify, &out_xonly, &parity, &out)) return std::nullopt;
|
||||
secp256k1_xonly_pubkey_serialize(secp256k1_context_verify, ret.first.begin(), &out_xonly);
|
||||
secp256k1_xonly_pubkey_serialize(secp256k1_context_verify, ret.first.data(), &out_xonly);
|
||||
assert(parity == 0 || parity == 1);
|
||||
ret.second = parity;
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -216,6 +216,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
|
||||
|
|
@ -249,7 +252,10 @@ 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:
|
||||
|
|
@ -275,6 +281,7 @@ public:
|
|||
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; }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <script/interpreter.h>
|
||||
|
||||
#include <consensus/consensus.h>
|
||||
#include <crypto/ripemd160.h>
|
||||
#include <crypto/sha1.h>
|
||||
#include <crypto/sha256.h>
|
||||
|
|
@ -14,6 +15,10 @@
|
|||
|
||||
typedef std::vector<unsigned char> valtype;
|
||||
|
||||
// These asserts are consensus critical for elements tapscript arithmetic opcodes
|
||||
static_assert(static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) == UINT64_C(0x7FFFFFFFFFFFFFFF));
|
||||
static_assert(static_cast<uint64_t>(std::numeric_limits<int64_t>::min()) == UINT64_C(0x8000000000000000));
|
||||
|
||||
namespace {
|
||||
|
||||
inline bool set_success(ScriptError* ret)
|
||||
|
|
@ -60,6 +65,78 @@ static inline void popstack(std::vector<valtype>& stack)
|
|||
stack.pop_back();
|
||||
}
|
||||
|
||||
static inline int64_t cast_signed64(uint64_t v)
|
||||
{
|
||||
uint64_t int64_min = static_cast<uint64_t>(std::numeric_limits<int64_t>::min());
|
||||
if (v >= int64_min)
|
||||
return static_cast<int64_t>(v - int64_min) + std::numeric_limits<int64_t>::min();
|
||||
return static_cast<int64_t>(v);
|
||||
}
|
||||
|
||||
static inline int64_t read_le8_signed(const unsigned char* ptr)
|
||||
{
|
||||
return cast_signed64(ReadLE64(ptr));
|
||||
}
|
||||
|
||||
static inline void push4_le(std::vector<valtype>& stack, uint32_t v)
|
||||
{
|
||||
uint32_t v_le = htole32(v);
|
||||
stack.emplace_back(reinterpret_cast<unsigned char*>(&v_le), reinterpret_cast<unsigned char*>(&v_le) + sizeof(v_le));
|
||||
}
|
||||
|
||||
static inline void push8_le(std::vector<valtype>& stack, uint64_t v)
|
||||
{
|
||||
uint64_t v_le = htole64(v);
|
||||
stack.emplace_back(reinterpret_cast<unsigned char*>(&v_le), reinterpret_cast<unsigned char*>(&v_le) + sizeof(v_le));
|
||||
}
|
||||
|
||||
static inline void pushasset(std::vector<valtype>& stack, const CConfidentialAsset& asset)
|
||||
{
|
||||
assert(!asset.IsNull());
|
||||
stack.emplace_back(asset.vchCommitment.begin() + 1, asset.vchCommitment.end()); // Push asset without prefix
|
||||
stack.emplace_back(asset.vchCommitment.begin(), asset.vchCommitment.begin() + 1); // Push prefix
|
||||
}
|
||||
|
||||
static inline void pushvalue(std::vector<valtype>& stack, const CConfidentialValue& value)
|
||||
{
|
||||
valtype vchinpValue, vchValuePref;
|
||||
if (value.IsNull()) {
|
||||
// If value is null, explicitly push the explicit prefix 0x01
|
||||
vchValuePref = {0x01};
|
||||
vchinpValue.assign(8, 0x00);
|
||||
} else if (value.IsExplicit()) {
|
||||
// Convert BE to LE by using reverse iterator
|
||||
vchValuePref.assign(value.vchCommitment.begin(), value.vchCommitment.begin() + 1);
|
||||
vchinpValue.assign(value.vchCommitment.rbegin(), value.vchCommitment.rbegin() + 8);
|
||||
} else { // (value.IsCommitment())
|
||||
vchValuePref.assign(value.vchCommitment.begin(), value.vchCommitment.begin() + 1);
|
||||
vchinpValue.assign(value.vchCommitment.begin() + 1, value.vchCommitment.end());
|
||||
}
|
||||
stack.push_back(std::move(vchinpValue)); // if value is null, 0(LE 8) is pushed
|
||||
stack.push_back(std::move(vchValuePref)); // always push prefix
|
||||
}
|
||||
|
||||
static inline void pushspk(std::vector<valtype>& stack, const CScript& scriptPubKey, const uint256& scriptPubKey_sha)
|
||||
{
|
||||
int witnessversion;
|
||||
valtype witnessprogram;
|
||||
if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
|
||||
stack.push_back(std::move(witnessprogram));
|
||||
stack.push_back(CScriptNum(witnessversion).getvch());
|
||||
} else {
|
||||
stack.emplace_back(scriptPubKey_sha.begin(), scriptPubKey_sha.end());
|
||||
stack.push_back(CScriptNum(-1).getvch());
|
||||
}
|
||||
}
|
||||
|
||||
/** Compute the outpoint flag(u8) for a given txin **/
|
||||
template <class T>
|
||||
inline unsigned char GetOutpointFlag(const T& txin)
|
||||
{
|
||||
return static_cast<unsigned char> ((!txin.assetIssuance.IsNull() ? (COutPoint::OUTPOINT_ISSUANCE_FLAG >> 24) : 0) |
|
||||
(txin.m_is_pegin ? (COutPoint::OUTPOINT_PEGIN_FLAG >> 24) : 0));
|
||||
}
|
||||
|
||||
bool static IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) {
|
||||
if (vchPubKey.size() < CPubKey::COMPRESSED_SIZE) {
|
||||
// Non-canonical public key: too short
|
||||
|
|
@ -356,6 +433,17 @@ public:
|
|||
};
|
||||
}
|
||||
|
||||
// Check the script has sufficient sigops budget for checksig(crypto) operation
|
||||
inline bool update_validation_weight(ScriptExecutionData& execdata, ScriptError* serror)
|
||||
{
|
||||
assert(execdata.m_validation_weight_left_init);
|
||||
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
|
||||
if (execdata.m_validation_weight_left < 0) {
|
||||
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess)
|
||||
{
|
||||
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0);
|
||||
|
|
@ -382,6 +470,47 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool EvalTapScriptCheckSigFromStack(const valtype& sig, const valtype& vchPubKey, ScriptExecutionData& execdata, unsigned int flags, const valtype& msg, SigVersion sigversion, ScriptError* serror, bool& success)
|
||||
{
|
||||
// This code follows the behaviour of EvalCheckSigTapscript
|
||||
assert(sigversion == SigVersion::TAPSCRIPT);
|
||||
|
||||
/*
|
||||
* The following validation sequence is consensus critical. Please note how --
|
||||
* upgradable public key versions precede other rules;
|
||||
* the script execution fails when using empty signature with invalid public key;
|
||||
* the script execution fails when using non-empty invalid signature.
|
||||
*/
|
||||
success = !sig.empty();
|
||||
if (success) {
|
||||
// Implement the sigops/witnesssize ratio test.
|
||||
// Passing with an upgradable public key version is also counted.
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
}
|
||||
if (vchPubKey.size() == 0) {
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
} else if (vchPubKey.size() == 32) {
|
||||
if (success) {
|
||||
if (sig.size() != 64)
|
||||
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE);
|
||||
const XOnlyPubKey pubkey{vchPubKey};
|
||||
if (!pubkey.VerifySchnorr(msg, sig))
|
||||
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* New public key version softforks should be defined before this `else` block.
|
||||
* Generally, the new code should not do anything but failing the script execution. To avoid
|
||||
* consensus bugs, it should not modify any existing values (including `success`).
|
||||
*/
|
||||
if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPSCRIPT);
|
||||
|
|
@ -396,11 +525,7 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr
|
|||
if (success) {
|
||||
// Implement the sigops/witnesssize ratio test.
|
||||
// Passing with an upgradable public key version is also counted.
|
||||
assert(execdata.m_validation_weight_left_init);
|
||||
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
|
||||
if (execdata.m_validation_weight_left < 0) {
|
||||
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
|
||||
}
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
}
|
||||
if (pubkey.size() == 0) {
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
|
|
@ -442,6 +567,10 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con
|
|||
assert(false);
|
||||
}
|
||||
|
||||
const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
|
||||
const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
|
||||
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
|
||||
{
|
||||
static const CScriptNum bnZero(0);
|
||||
|
|
@ -1587,29 +1716,521 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
|||
valtype& vchSig = stacktop(-3);
|
||||
valtype& vchData = stacktop(-2);
|
||||
valtype& vchPubKey = stacktop(-1);
|
||||
bool fSuccess;
|
||||
// Different semantics for CHECKSIGFROMSTACK for taproot and pre-taproot
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0)
|
||||
{
|
||||
// Sigs from stack have no hash byte ever
|
||||
if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
|
||||
//serror is set
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sigs from stack have no hash byte ever
|
||||
if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
|
||||
//serror is set
|
||||
return false;
|
||||
valtype vchHash(CSHA256::OUTPUT_SIZE);
|
||||
CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data());
|
||||
uint256 hash(vchHash);
|
||||
|
||||
CPubKey pubkey(vchPubKey);
|
||||
fSuccess = pubkey.Verify(hash, vchSig);
|
||||
// CHECKSIGFROMSTACK in pre-tapscript cannot be failed.
|
||||
if (!fSuccess)
|
||||
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
|
||||
} else {
|
||||
// New BIP 340 semantics for CHECKSIGFROMSTACK
|
||||
if (!EvalTapScriptCheckSigFromStack(vchSig, vchPubKey, execdata, flags, vchData, sigversion, serror, fSuccess)) return false;
|
||||
}
|
||||
|
||||
valtype vchHash(32);
|
||||
CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data());
|
||||
uint256 hash(vchHash);
|
||||
|
||||
CPubKey pubkey(vchPubKey);
|
||||
bool fSuccess = pubkey.Verify(hash, vchSig);
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
stack.push_back(fSuccess ? vchTrue : vchFalse);
|
||||
if (opcode == OP_CHECKSIGFROMSTACKVERIFY)
|
||||
popstack(stack);
|
||||
{
|
||||
if (fSuccess)
|
||||
popstack(stack);
|
||||
else
|
||||
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
if (!fSuccess)
|
||||
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
|
||||
case OP_SHA256INITIALIZE: // (in -- sha256_ctx)
|
||||
{
|
||||
// OP_SHA256INITIALIZE is only available in Tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
CSHA256 ctx;
|
||||
valtype& vch = stacktop(-1);
|
||||
if (!ctx.SafeWrite(vch.data(), vch.size()))
|
||||
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_WRITE);
|
||||
|
||||
popstack(stack);
|
||||
stack.push_back(ctx.Save());
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_SHA256UPDATE: // (sha256_ctx in -- sha256_ctx)
|
||||
{
|
||||
// OP_SHA256UPDATE is only available in Tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 2)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
CSHA256 ctx;
|
||||
valtype& vchCtx = stacktop(-2);
|
||||
if (!ctx.Load(vchCtx))
|
||||
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_LOAD);
|
||||
|
||||
valtype& vch = stacktop(-1);
|
||||
if (!ctx.SafeWrite(vch.data(), vch.size()))
|
||||
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_WRITE);
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
stack.push_back(ctx.Save());
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_SHA256FINALIZE: // (sha256_ctx in -- hash)
|
||||
{
|
||||
// OP_SHA256FINALIZE is only available in Tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 2)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
valtype& vchCtx = stacktop(-2);
|
||||
CSHA256 ctx;
|
||||
if (!ctx.Load(vchCtx))
|
||||
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_LOAD);
|
||||
|
||||
valtype& vch = stacktop(-1);
|
||||
if (!ctx.SafeWrite(vch.data(), vch.size()))
|
||||
return set_error(serror, SCRIPT_ERR_SHA2_CONTEXT_WRITE);
|
||||
|
||||
valtype vchHash(CHash256::OUTPUT_SIZE);
|
||||
ctx.Finalize(vchHash.data());
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
stack.push_back(std::move(vchHash));
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_INSPECTINPUTOUTPOINT:
|
||||
case OP_INSPECTINPUTASSET:
|
||||
case OP_INSPECTINPUTVALUE:
|
||||
case OP_INSPECTINPUTSCRIPTPUBKEY:
|
||||
case OP_INSPECTINPUTSEQUENCE:
|
||||
case OP_INSPECTINPUTISSUANCE:
|
||||
{
|
||||
// Input inspection opcodes only available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
int idx = CScriptNum(stacktop(-1), fRequireMinimal).getint();
|
||||
popstack(stack);
|
||||
|
||||
auto inps = checker.GetTxvIn();
|
||||
const PrecomputedTransactionData *cache = checker.GetPrecomputedTransactionData();
|
||||
// Return error if the evaluation context is unavailable
|
||||
// TODO: Handle accoding to MissingDataBehavior
|
||||
if (!inps || !cache || !cache->m_bip341_taproot_ready)
|
||||
return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE);
|
||||
const std::vector<CTxOut>& spent_outputs = cache->m_spent_outputs;
|
||||
// This condition is ensured when m_spent_outputs_ready is set
|
||||
// which is asserted when m_bip341_taproot_ready is set
|
||||
assert(spent_outputs.size() == inps->size());
|
||||
if (idx < 0 || static_cast<unsigned int>(idx) >= inps->size())
|
||||
return set_error(serror, SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS);
|
||||
const CTxIn& inp = inps->at(idx);
|
||||
const CTxOut& spent_utxo = spent_outputs[idx];
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case OP_INSPECTINPUTOUTPOINT:
|
||||
{
|
||||
// Push prev txid
|
||||
stack.emplace_back(inp.prevout.hash.begin(), inp.prevout.hash.end());
|
||||
push4_le(stack, inp.prevout.n);
|
||||
|
||||
// Push the outpoint flag
|
||||
stack.emplace_back(1, GetOutpointFlag(inp));
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTINPUTASSET:
|
||||
{
|
||||
pushasset(stack, spent_utxo.nAsset);
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTINPUTVALUE:
|
||||
{
|
||||
pushvalue(stack, spent_utxo.nValue);
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTINPUTSCRIPTPUBKEY:
|
||||
{
|
||||
pushspk(stack, spent_utxo.scriptPubKey, cache->m_spent_output_spk_single_hashes[idx]);
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTINPUTSEQUENCE:
|
||||
{
|
||||
push4_le(stack, inp.nSequence);
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTINPUTISSUANCE:
|
||||
{
|
||||
if (!inp.assetIssuance.IsNull()) {
|
||||
pushvalue(stack, inp.assetIssuance.nInflationKeys);
|
||||
pushvalue(stack, inp.assetIssuance.nAmount);
|
||||
// Next push Asset entropy
|
||||
stack.emplace_back(inp.assetIssuance.assetEntropy.begin(), inp.assetIssuance.assetEntropy.end());
|
||||
// Finally push blinding nonce
|
||||
// By pushing the this order, we make sure that the stack top is empty
|
||||
// iff there is no issuance.
|
||||
stack.emplace_back(inp.assetIssuance.assetBlindingNonce.begin(), inp.assetIssuance.assetBlindingNonce.end());
|
||||
} else { // No issuance
|
||||
stack.push_back(vchFalse);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: assert(!"invalid opcode"); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_PUSHCURRENTINPUTINDEX:
|
||||
{
|
||||
// OP_PUSHCURRENTINPUTINDEX is available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
// Even tough this value should never 2^25(MAX_SIZE), this can set to any value in exotic custom contexts
|
||||
// safe to check that this in 4 byte positive number before pushing it
|
||||
// TODO: Handle accoding to MissingDataBehavior
|
||||
if (checker.GetnIn() > MAX_SIZE)
|
||||
return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE);
|
||||
stack.push_back(CScriptNum(static_cast<int64_t>(checker.GetnIn())).getvch());
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_INSPECTOUTPUTASSET:
|
||||
case OP_INSPECTOUTPUTVALUE:
|
||||
case OP_INSPECTOUTPUTNONCE:
|
||||
case OP_INSPECTOUTPUTSCRIPTPUBKEY:
|
||||
{
|
||||
// Output instropsection codes only available post tapscript is available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
int idx = CScriptNum(stacktop(-1), fRequireMinimal).getint();
|
||||
popstack(stack);
|
||||
|
||||
auto outs = checker.GetTxvOut();
|
||||
const PrecomputedTransactionData *cache = checker.GetPrecomputedTransactionData();
|
||||
// Return error if the evaluation context is unavailable
|
||||
// TODO: Handle accoding to MissingDataBehavior
|
||||
if (!outs || !cache || !cache->m_bip341_taproot_ready)
|
||||
return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE);
|
||||
assert(cache->m_output_spk_single_hashes.size() == outs->size());
|
||||
|
||||
if (idx < 0 || static_cast<unsigned int>(idx) >= outs->size())
|
||||
return set_error(serror, SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS);
|
||||
const CTxOut& out = outs->at(idx);
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case OP_INSPECTOUTPUTASSET:
|
||||
{
|
||||
pushasset(stack, out.nAsset);
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTOUTPUTVALUE:
|
||||
{
|
||||
pushvalue(stack, out.nValue);
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTOUTPUTNONCE:
|
||||
{
|
||||
if (out.nNonce.IsNull()) {
|
||||
stack.push_back(vchFalse);
|
||||
} else {
|
||||
stack.emplace_back(out.nNonce.vchCommitment);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTOUTPUTSCRIPTPUBKEY:
|
||||
{
|
||||
pushspk(stack, out.scriptPubKey, cache->m_output_spk_single_hashes[idx]);
|
||||
break;
|
||||
}
|
||||
default: assert(!"invalid opcode"); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_INSPECTVERSION:
|
||||
case OP_INSPECTLOCKTIME:
|
||||
case OP_INSPECTNUMINPUTS:
|
||||
case OP_INSPECTNUMOUTPUTS:
|
||||
case OP_TXWEIGHT:
|
||||
{
|
||||
// Transaction introspection is available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case OP_INSPECTVERSION:
|
||||
{
|
||||
push4_le(stack, static_cast<uint32_t>(checker.GetTxVersion()));
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTLOCKTIME:
|
||||
{
|
||||
push4_le(stack, checker.GetLockTime());
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTNUMINPUTS:
|
||||
{
|
||||
auto inps = checker.GetTxvIn();
|
||||
// TODO: Handle according to MissingDataBehavior
|
||||
if (!inps)
|
||||
return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE);
|
||||
auto num_ins = inps->size();
|
||||
assert(num_ins <= MAX_SIZE);
|
||||
stack.push_back(CScriptNum(static_cast<int64_t>(num_ins)).getvch());
|
||||
break;
|
||||
}
|
||||
case OP_INSPECTNUMOUTPUTS:
|
||||
{
|
||||
auto outs = checker.GetTxvOut();
|
||||
// TODO: Handle according to MissingDataBehavior
|
||||
if (!outs)
|
||||
return set_error(serror, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE);
|
||||
auto num_outs = outs->size();
|
||||
assert(num_outs <= MAX_SIZE);
|
||||
stack.push_back(CScriptNum(static_cast<int64_t>(num_outs)).getvch());
|
||||
break;
|
||||
}
|
||||
case OP_TXWEIGHT:
|
||||
{
|
||||
push8_le(stack, checker.GetTxWeight());
|
||||
break;
|
||||
}
|
||||
default: assert(!"invalid opcode"); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_ADD64:
|
||||
case OP_SUB64:
|
||||
case OP_MUL64:
|
||||
case OP_DIV64:
|
||||
case OP_LESSTHAN64:
|
||||
case OP_LESSTHANOREQUAL64:
|
||||
case OP_GREATERTHAN64:
|
||||
case OP_GREATERTHANOREQUAL64:
|
||||
{
|
||||
// Opcodes only available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 2)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
valtype& vcha = stacktop(-2);
|
||||
valtype& vchb = stacktop(-1);
|
||||
if (vchb.size() != 8 || vcha.size() != 8)
|
||||
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
|
||||
|
||||
int64_t b = read_le8_signed(vchb.data());
|
||||
int64_t a = read_le8_signed(vcha.data());
|
||||
|
||||
switch(opcode)
|
||||
{
|
||||
case OP_ADD64:
|
||||
if ((a > 0 && b > std::numeric_limits<int64_t>::max() - a) ||
|
||||
(a < 0 && b < std::numeric_limits<int64_t>::min() - a))
|
||||
stack.push_back(vchFalse);
|
||||
else {
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
push8_le(stack, a + b);
|
||||
stack.push_back(vchTrue);
|
||||
}
|
||||
break;
|
||||
case OP_SUB64:
|
||||
if ((b > 0 && a < std::numeric_limits<int64_t>::min() + b) ||
|
||||
(b < 0 && a > std::numeric_limits<int64_t>::max() + b))
|
||||
stack.push_back(vchFalse);
|
||||
else {
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
push8_le(stack, a - b);
|
||||
stack.push_back(vchTrue);
|
||||
}
|
||||
break;
|
||||
case OP_MUL64:
|
||||
if ((a > 0 && b > 0 && a > std::numeric_limits<int64_t>::max() / b) ||
|
||||
(a > 0 && b < 0 && b < std::numeric_limits<int64_t>::min() / a) ||
|
||||
(a < 0 && b > 0 && a < std::numeric_limits<int64_t>::min() / b) ||
|
||||
(a < 0 && b < 0 && b < std::numeric_limits<int64_t>::max() / a))
|
||||
stack.push_back(vchFalse);
|
||||
else {
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
push8_le(stack, a * b);
|
||||
stack.push_back(vchTrue);
|
||||
}
|
||||
break;
|
||||
case OP_DIV64:
|
||||
{
|
||||
if (b == 0 || (b == -1 && a == std::numeric_limits<int64_t>::min())) { stack.push_back(vchFalse); break; }
|
||||
int64_t r = a % b;
|
||||
int64_t q = a / b;
|
||||
if (r < 0 && b > 0) { r += b; q-=1;} // ensures that 0<=r<|b|
|
||||
else if (r < 0 && b < 0) { r -= b; q+=1;} // ensures that 0<=r<|b|
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
push8_le(stack, r);
|
||||
push8_le(stack, q);
|
||||
stack.push_back(vchTrue);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case OP_LESSTHAN64: popstack(stack); popstack(stack); stack.push_back( (a < b) ? vchTrue : vchFalse ); break;
|
||||
case OP_LESSTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a <= b) ? vchTrue : vchFalse ); break;
|
||||
case OP_GREATERTHAN64: popstack(stack); popstack(stack); stack.push_back( (a > b) ? vchTrue : vchFalse ); break;
|
||||
case OP_GREATERTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a >= b) ? vchTrue : vchFalse ); break;
|
||||
default: assert(!"invalid opcode"); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OP_NEG64:
|
||||
{
|
||||
// Opcodes only available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
valtype& vcha = stacktop(-1);
|
||||
if (vcha.size() != 8)
|
||||
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
|
||||
|
||||
int64_t a = read_le8_signed(vcha.data());
|
||||
if (a == std::numeric_limits<int64_t>::min()) { stack.push_back(vchFalse); break; }
|
||||
|
||||
popstack(stack);
|
||||
push8_le(stack, -a);
|
||||
stack.push_back(vchTrue);
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_SCRIPTNUMTOLE64:
|
||||
{
|
||||
// Opcodes only available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
int64_t num = CScriptNum(stacktop(-1), fRequireMinimal).getint();
|
||||
popstack(stack);
|
||||
push8_le(stack, num);
|
||||
}
|
||||
break;
|
||||
case OP_LE64TOSCRIPTNUM:
|
||||
{
|
||||
// Opcodes only available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
valtype& vchnum = stacktop(-1);
|
||||
if (vchnum.size() != 8)
|
||||
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
|
||||
valtype vchscript_num = CScriptNum(read_le8_signed(vchnum.data())).getvch();
|
||||
if (vchscript_num.size() > CScriptNum::nDefaultMaxNumSize) {
|
||||
return set_error(serror, SCRIPT_ERR_ARITHMETIC64);
|
||||
} else {
|
||||
popstack(stack);
|
||||
stack.push_back(std::move(vchscript_num));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OP_LE32TOLE64:
|
||||
{
|
||||
// Opcodes only available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
if (stack.size() < 1)
|
||||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
|
||||
|
||||
valtype& vchnum = stacktop(-1);
|
||||
if (vchnum.size() != 4)
|
||||
return set_error(serror, SCRIPT_ERR_ARITHMETIC64);
|
||||
uint32_t num = ReadLE32(vchnum.data());
|
||||
popstack(stack);
|
||||
push8_le(stack, static_cast<int64_t>(num));
|
||||
}
|
||||
break;
|
||||
case OP_ECMULSCALARVERIFY:
|
||||
{
|
||||
// OP_ECMULSCALARVERIFY is available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
valtype& vchRes = stacktop(-3);
|
||||
valtype& vchGenerator = stacktop(-2);
|
||||
valtype& vchScalar = stacktop(-1);
|
||||
|
||||
CPubKey pk(vchGenerator);
|
||||
CPubKey res(vchRes);
|
||||
if (!pk.IsCompressed() || !res.IsCompressed())
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
|
||||
if (vchScalar.size() != 32 || !res.TweakMulVerify(pk, uint256(vchScalar)))
|
||||
return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL);
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
}
|
||||
break;
|
||||
|
||||
//crypto opcodes
|
||||
case OP_TWEAKVERIFY:
|
||||
{
|
||||
// OP_TWEAKVERIFY is available post tapscript
|
||||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
|
||||
|
||||
valtype& vchTweakedKey = stacktop(-3);
|
||||
valtype& vchTweak = stacktop(-2);
|
||||
valtype& vchInternalKey = stacktop(-1);
|
||||
|
||||
if (vchTweakedKey.size() != CPubKey::COMPRESSED_SIZE || (vchTweakedKey[0] != 0x02 && vchTweakedKey[0] != 0x03)
|
||||
|| vchInternalKey.size() != 32 || vchTweak.size() != 32)
|
||||
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
|
||||
|
||||
if (!update_validation_weight(execdata, serror)) return false; // serror is set
|
||||
|
||||
const XOnlyPubKey tweakedXOnlyKey{Span<const unsigned char>{vchTweakedKey.data() + 1, vchTweakedKey.data() + CPubKey::COMPRESSED_SIZE}};
|
||||
const uint256 tweak(vchTweak);
|
||||
const XOnlyPubKey internalKey{vchInternalKey};
|
||||
if (!tweakedXOnlyKey.CheckPayToContract(internalKey, tweak, vchTweakedKey[0] & 1))
|
||||
return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL);
|
||||
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
popstack(stack);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -1757,14 +2378,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/** Compute the outpoint flag(u8) for a given txin **/
|
||||
template <class T>
|
||||
inline unsigned char GetOutpointFlag(const T& txin)
|
||||
{
|
||||
return (unsigned char) ((!txin.assetIssuance.IsNull() ? (COutPoint::OUTPOINT_ISSUANCE_FLAG >> 24) : 0) |
|
||||
(txin.m_is_pegin ? (COutPoint::OUTPOINT_PEGIN_FLAG >> 24) : 0));
|
||||
}
|
||||
|
||||
/** Compute the (single) SHA256 of the concatenation of all outpoint flags of a tx. */
|
||||
template <class T>
|
||||
uint256 GetOutpointFlagsSHA256(const T& txTo)
|
||||
|
|
@ -1873,6 +2486,35 @@ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent)
|
|||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
/** Compute the vector where each element is SHA256 of scriptPubKeys spent by a tx. */
|
||||
std::vector<uint256> GetSpentScriptPubKeysSHA256(const std::vector<CTxOut>& outputs_spent)
|
||||
{
|
||||
std::vector<uint256> spent_spk_single_hashes;
|
||||
spent_spk_single_hashes.reserve(outputs_spent.size());
|
||||
for (const auto& txout : outputs_spent) {
|
||||
// Normal serialization using the << operater would also serialize the length, therefore we directly write using CSHA256
|
||||
uint256 spent_spk_single_hash;
|
||||
CSHA256().Write(txout.scriptPubKey.data(), txout.scriptPubKey.size()).Finalize(spent_spk_single_hash.data());
|
||||
spent_spk_single_hashes.push_back(std::move(spent_spk_single_hash));
|
||||
}
|
||||
return spent_spk_single_hashes;
|
||||
}
|
||||
|
||||
/** Compute the vector where each element is SHA256 of output scriptPubKey of a tx. */
|
||||
template <class T>
|
||||
std::vector<uint256> GetOutputScriptPubKeysSHA256(const T& txTo)
|
||||
{
|
||||
std::vector<uint256> out_spk_single_hashes;
|
||||
out_spk_single_hashes.reserve(txTo.vout.size());
|
||||
for (const auto& txout : txTo.vout) {
|
||||
// Normal serialization using the << operater would also serialize the length, therefore we directly write using CSHA256
|
||||
uint256 out_spk_single_hash;
|
||||
CSHA256().Write(txout.scriptPubKey.data(), txout.scriptPubKey.size()).Finalize(out_spk_single_hash.data());
|
||||
out_spk_single_hashes.push_back(std::move(out_spk_single_hash));
|
||||
}
|
||||
return out_spk_single_hashes;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
uint256 GetRangeproofsHash(const T& txTo) {
|
||||
CHashWriter ss(SER_GETHASH, 0);
|
||||
|
|
@ -1944,6 +2586,8 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
|
|||
m_issuance_rangeproofs_single_hash = GetIssuanceRangeproofsSHA256(txTo);
|
||||
m_output_witnesses_single_hash = GetOutputWitnessesSHA256(txTo);
|
||||
m_spent_scripts_single_hash = GetSpentScriptsSHA256(m_spent_outputs);
|
||||
m_spent_output_spk_single_hashes = GetSpentScriptPubKeysSHA256(m_spent_outputs);
|
||||
m_output_spk_single_hashes = GetOutputScriptPubKeysSHA256(txTo);
|
||||
m_bip341_taproot_ready = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1961,10 +2605,6 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo,
|
|||
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
|
||||
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
|
||||
|
||||
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
|
||||
const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
|
||||
const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
|
||||
|
||||
PrecomputedTransactionData::PrecomputedTransactionData(const uint256& hash_genesis_block)
|
||||
: m_tapsighash_hasher(CHashWriter(HASHER_TAPSIGHASH_ELEMENTS) << hash_genesis_block << hash_genesis_block) {}
|
||||
|
||||
|
|
@ -2346,6 +2986,49 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
|
|||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
uint32_t GenericTransactionSignatureChecker<T>::GetLockTime() const
|
||||
{
|
||||
return txTo->nLockTime;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
int32_t GenericTransactionSignatureChecker<T>::GetTxVersion() const
|
||||
{
|
||||
return txTo->nVersion;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const std::vector<CTxIn>* GenericTransactionSignatureChecker<T>::GetTxvIn() const
|
||||
{
|
||||
return &(txTo->vin);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const std::vector<CTxOut>* GenericTransactionSignatureChecker<T>::GetTxvOut() const
|
||||
{
|
||||
return &(txTo->vout);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
uint64_t GenericTransactionSignatureChecker<T>::GetTxWeight() const
|
||||
{
|
||||
// line copied from GetTransactionWeight() in src/consensus/validation.h
|
||||
return ::GetSerializeSize(*txTo, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(*txTo, PROTOCOL_VERSION);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const PrecomputedTransactionData* GenericTransactionSignatureChecker<T>::GetPrecomputedTransactionData() const
|
||||
{
|
||||
return txdata;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
uint32_t GenericTransactionSignatureChecker<T>::GetnIn() const
|
||||
{
|
||||
return nIn;
|
||||
}
|
||||
|
||||
// explicit instantiation
|
||||
template class GenericTransactionSignatureChecker<CTransaction>;
|
||||
template class GenericTransactionSignatureChecker<CMutableTransaction>;
|
||||
|
|
|
|||
|
|
@ -173,7 +173,9 @@ struct PrecomputedTransactionData
|
|||
uint256 m_issuances_single_hash;
|
||||
uint256 m_output_witnesses_single_hash;
|
||||
uint256 m_issuance_rangeproofs_single_hash;
|
||||
//! Whether the 10 fields above are initialized.
|
||||
//! transaction outputs scriptpubkey sha single hash
|
||||
std::vector<uint256> m_output_spk_single_hashes;
|
||||
//! Whether the 11 fields above are initialized.
|
||||
bool m_bip341_taproot_ready = false;
|
||||
|
||||
// BIP143 precomputed data (double-SHA256).
|
||||
|
|
@ -182,6 +184,8 @@ struct PrecomputedTransactionData
|
|||
bool m_bip143_segwit_ready = false;
|
||||
|
||||
std::vector<CTxOut> m_spent_outputs;
|
||||
//! ELEMENTS: spent outputs scriptpubkey sha single hash
|
||||
std::vector<uint256> m_spent_output_spk_single_hashes;
|
||||
//! Whether m_spent_outputs is initialized.
|
||||
bool m_spent_outputs_ready = false;
|
||||
|
||||
|
|
@ -237,7 +241,7 @@ static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
|
|||
static constexpr size_t WITNESS_V1_TAPROOT_SIZE = 32;
|
||||
|
||||
static constexpr uint8_t TAPROOT_LEAF_MASK = 0xfe;
|
||||
static constexpr uint8_t TAPROOT_LEAF_TAPSCRIPT = 0xc0;
|
||||
static constexpr uint8_t TAPROOT_LEAF_TAPSCRIPT = 0xc4;
|
||||
static constexpr size_t TAPROOT_CONTROL_BASE_SIZE = 33;
|
||||
static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
|
||||
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
|
||||
|
|
@ -272,6 +276,41 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
virtual const std::vector<CTxIn>* GetTxvIn() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual const std::vector<CTxOut>* GetTxvOut() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual uint32_t GetLockTime() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int32_t GetTxVersion() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual uint64_t GetTxWeight() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual const PrecomputedTransactionData* GetPrecomputedTransactionData() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual uint32_t GetnIn() const
|
||||
{
|
||||
return std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
|
||||
virtual ~BaseSignatureChecker() {}
|
||||
};
|
||||
|
||||
|
|
@ -308,6 +347,14 @@ public:
|
|||
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
|
||||
bool CheckLockTime(const CScriptNum& nLockTime) const override;
|
||||
bool CheckSequence(const CScriptNum& nSequence) const override;
|
||||
const std::vector<CTxIn>* GetTxvIn() const override;
|
||||
const std::vector<CTxOut>* GetTxvOut() const override;
|
||||
uint32_t GetLockTime() const override;
|
||||
int32_t GetTxVersion() const override;
|
||||
uint64_t GetTxWeight() const override;
|
||||
|
||||
const PrecomputedTransactionData* GetPrecomputedTransactionData() const override;
|
||||
uint32_t GetnIn() const override;
|
||||
};
|
||||
|
||||
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
145
src/test/fuzz/witness_program.cpp
Normal file
145
src/test/fuzz/witness_program.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// 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>
|
||||
|
||||
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, MissingDataBehavior mdb) : MutableTransactionSignatureChecker{txToIn, nInIn, amountIn, txdataIn, mdb} {}
|
||||
};
|
||||
|
||||
void initialize_witness_program()
|
||||
{
|
||||
static const ECCVerifyHandle verify_handle;
|
||||
}
|
||||
|
||||
FUZZ_TARGET_INIT(witness_program, initialize_witness_program)
|
||||
{
|
||||
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, MissingDataBehavior::ASSERT_FAIL};
|
||||
|
||||
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, MissingDataBehavior::ASSERT_FAIL};
|
||||
|
||||
VerifyScript(/* scriptSig */ CScript{}, scriptPubKey, &witness, flags, checker, /* serror */ NULL);
|
||||
}
|
||||
} catch (const std::ios_base::failure&) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1469,7 +1469,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());
|
||||
}
|
||||
|
||||
|
|
|
|||
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, ser_uint256, sha256, tx_from_hex, 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 = tx_from_hex(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 = tx_from_hex(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 = tx_from_hex(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 = tx_from_hex(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 = tx_from_hex(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 = tx_from_hex(issued_tx)
|
||||
# Sign inputs
|
||||
if add_pegin:
|
||||
signed = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
tx = tx_from_hex(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 = tx_from_hex(blinded_raw)
|
||||
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
tx = tx_from_hex(signed_raw_tx['hex'])
|
||||
|
||||
|
||||
suffix_annex = []
|
||||
control_block = bytes([tap.leaves["s0"].version + tap.negflag]) + tap.internal_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()
|
||||
|
|
@ -479,7 +479,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')
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ MAX_SCRIPT_ELEMENT_SIZE = 520
|
|||
LOCKTIME_THRESHOLD = 500000000
|
||||
ANNEX_TAG = 0x50
|
||||
|
||||
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):
|
||||
|
|
@ -948,4 +1030,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)
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ BASE_SCRIPTS = [
|
|||
'rpc_signer.py',
|
||||
'wallet_signer.py --descriptors',
|
||||
'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