mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-16 13:01:19 +02:00
Merge db283a6b6f into merged_master (Bitcoin PR bitcoin/bitcoin#27255)
This commit is contained in:
commit
d24651b7c4
14 changed files with 1424 additions and 546 deletions
|
|
@ -405,6 +405,7 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
|
|||
}
|
||||
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
||||
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
|
||||
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey);
|
||||
}
|
||||
for (const auto& [hash, preimage] : ripemd160_preimages) {
|
||||
sigdata.ripemd160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
|
||||
|
|
@ -554,6 +555,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
|
|||
}
|
||||
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
||||
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
|
||||
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,6 +204,13 @@ std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const
|
|||
return out;
|
||||
}
|
||||
|
||||
CPubKey XOnlyPubKey::GetEvenCorrespondingCPubKey() const
|
||||
{
|
||||
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
|
||||
std::copy(begin(), end(), full_key + 1);
|
||||
return CPubKey{full_key};
|
||||
}
|
||||
|
||||
bool XOnlyPubKey::IsFullyValid() const
|
||||
{
|
||||
secp256k1_xonly_pubkey pubkey;
|
||||
|
|
|
|||
|
|
@ -288,6 +288,8 @@ public:
|
|||
*/
|
||||
std::vector<CKeyID> GetKeyIDs() const;
|
||||
|
||||
CPubKey GetEvenCorrespondingCPubKey() const;
|
||||
|
||||
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
|
||||
const unsigned char* data() const { return m_keydata.begin(); }
|
||||
static constexpr size_t size() { return decltype(m_keydata)::size(); }
|
||||
|
|
|
|||
|
|
@ -1114,16 +1114,33 @@ public:
|
|||
class ScriptMaker {
|
||||
//! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args).
|
||||
const std::vector<CPubKey>& m_keys;
|
||||
//! The script context we're operating within (Tapscript or P2WSH).
|
||||
const miniscript::MiniscriptContext m_script_ctx;
|
||||
|
||||
//! Get the ripemd160(sha256()) hash of this key.
|
||||
//! Any key that is valid in a descriptor serializes as 32 bytes within a Tapscript context. So we
|
||||
//! must not hash the sign-bit byte in this case.
|
||||
uint160 GetHash160(uint32_t key) const {
|
||||
if (miniscript::IsTapscript(m_script_ctx)) {
|
||||
return Hash160(XOnlyPubKey{m_keys[key]});
|
||||
}
|
||||
return m_keys[key].GetID();
|
||||
}
|
||||
|
||||
public:
|
||||
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND) : m_keys(keys) {}
|
||||
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND, const miniscript::MiniscriptContext script_ctx) : m_keys(keys), m_script_ctx{script_ctx} {}
|
||||
|
||||
std::vector<unsigned char> ToPKBytes(uint32_t key) const {
|
||||
return {m_keys[key].begin(), m_keys[key].end()};
|
||||
// In Tapscript keys always serialize as x-only, whether an x-only key was used in the descriptor or not.
|
||||
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||
return {m_keys[key].begin(), m_keys[key].end()};
|
||||
}
|
||||
const XOnlyPubKey xonly_pubkey{m_keys[key]};
|
||||
return {xonly_pubkey.begin(), xonly_pubkey.end()};
|
||||
}
|
||||
|
||||
std::vector<unsigned char> ToPKHBytes(uint32_t key) const {
|
||||
auto id = m_keys[key].GetID();
|
||||
auto id = GetHash160(key);
|
||||
return {id.begin(), id.end()};
|
||||
}
|
||||
};
|
||||
|
|
@ -1164,8 +1181,15 @@ protected:
|
|||
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts,
|
||||
FlatSigningProvider& provider) const override
|
||||
{
|
||||
for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key);
|
||||
return Vector(m_node->ToScript(ScriptMaker(keys)));
|
||||
const auto script_ctx{m_node->GetMsCtx()};
|
||||
for (const auto& key : keys) {
|
||||
if (miniscript::IsTapscript(script_ctx)) {
|
||||
provider.pubkeys.emplace(Hash160(XOnlyPubKey{key}), key);
|
||||
} else {
|
||||
provider.pubkeys.emplace(key.GetID(), key);
|
||||
}
|
||||
}
|
||||
return Vector(m_node->ToScript(ScriptMaker(keys, script_ctx)));
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -1401,9 +1425,7 @@ std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptCo
|
|||
|
||||
std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
|
||||
{
|
||||
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
|
||||
std::copy(xkey.begin(), xkey.end(), full_key + 1);
|
||||
CPubKey pubkey(full_key);
|
||||
CPubKey pubkey{xkey.GetEvenCorrespondingCPubKey()};
|
||||
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOriginByXOnly(xkey, info)) {
|
||||
|
|
@ -1426,18 +1448,32 @@ struct KeyParser {
|
|||
mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys;
|
||||
//! Used to detect key parsing errors within a Miniscript.
|
||||
mutable std::string m_key_parsing_error;
|
||||
//! The script context we're operating within (Tapscript or P2WSH).
|
||||
const miniscript::MiniscriptContext m_script_ctx;
|
||||
//! The number of keys that were parsed before starting to parse this Miniscript descriptor.
|
||||
uint32_t m_offset;
|
||||
|
||||
KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND) : m_out(out), m_in(in) {}
|
||||
KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND,
|
||||
miniscript::MiniscriptContext ctx, uint32_t offset = 0)
|
||||
: m_out(out), m_in(in), m_script_ctx(ctx), m_offset(offset) {}
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return *m_keys.at(a) < *m_keys.at(b);
|
||||
}
|
||||
|
||||
ParseScriptContext ParseContext() const {
|
||||
switch (m_script_ctx) {
|
||||
case miniscript::MiniscriptContext::P2WSH: return ParseScriptContext::P2WSH;
|
||||
case miniscript::MiniscriptContext::TAPSCRIPT: return ParseScriptContext::P2TR;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
template<typename I> std::optional<Key> FromString(I begin, I end) const
|
||||
{
|
||||
assert(m_out);
|
||||
Key key = m_keys.size();
|
||||
auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error);
|
||||
auto pk = ParsePubkey(m_offset + key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error);
|
||||
if (!pk) return {};
|
||||
m_keys.push_back(std::move(pk));
|
||||
return key;
|
||||
|
|
@ -1451,11 +1487,18 @@ struct KeyParser {
|
|||
template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const
|
||||
{
|
||||
assert(m_in);
|
||||
CPubKey pubkey(begin, end);
|
||||
if (pubkey.IsValidNonHybrid()) {
|
||||
Key key = m_keys.size();
|
||||
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
|
||||
Key key = m_keys.size();
|
||||
if (miniscript::IsTapscript(m_script_ctx) && end - begin == 32) {
|
||||
XOnlyPubKey pubkey;
|
||||
std::copy(begin, end, pubkey.begin());
|
||||
m_keys.push_back(InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in));
|
||||
return key;
|
||||
} else if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||
CPubKey pubkey{begin, end};
|
||||
if (pubkey.IsValidNonHybrid()) {
|
||||
m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in));
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
@ -1470,11 +1513,15 @@ struct KeyParser {
|
|||
CPubKey pubkey;
|
||||
if (m_in->GetPubKey(keyid, pubkey)) {
|
||||
Key key = m_keys.size();
|
||||
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
|
||||
m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in));
|
||||
return key;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
miniscript::MiniscriptContext MsContext() const {
|
||||
return m_script_ctx;
|
||||
}
|
||||
};
|
||||
|
||||
/** Parse a script in a particular context. */
|
||||
|
|
@ -1500,8 +1547,9 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
|
|||
}
|
||||
++key_exp_index;
|
||||
return std::make_unique<PKHDescriptor>(std::move(pubkey));
|
||||
} else if (Func("pkh", expr)) {
|
||||
error = "Can only have pkh at top level, in sh(), or in wsh()";
|
||||
} else if (ctx != ParseScriptContext::P2TR && Func("pkh", expr)) {
|
||||
// Under Taproot, always the Miniscript parser deal with it.
|
||||
error = "Can only have pkh at top level, in sh(), wsh(), or in tr()";
|
||||
return nullptr;
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
|
||||
|
|
@ -1714,11 +1762,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
|
|||
}
|
||||
// Process miniscript expressions.
|
||||
{
|
||||
KeyParser parser(&out, nullptr);
|
||||
const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT};
|
||||
KeyParser parser(/*out = */&out, /* in = */nullptr, /* ctx = */script_ctx, key_exp_index);
|
||||
auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser);
|
||||
if (node) {
|
||||
if (ctx != ParseScriptContext::P2WSH) {
|
||||
error = "Miniscript expressions can only be used in wsh";
|
||||
if (ctx != ParseScriptContext::P2WSH && ctx != ParseScriptContext::P2TR) {
|
||||
error = "Miniscript expressions can only be used in wsh or tr.";
|
||||
return nullptr;
|
||||
}
|
||||
if (parser.m_key_parsing_error != "") {
|
||||
|
|
@ -1753,6 +1802,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
|
|||
// A signature check is required for a miniscript to be sane. Therefore no sane miniscript
|
||||
// may have an empty list of public keys.
|
||||
CHECK_NONFATAL(!parser.m_keys.empty());
|
||||
key_exp_index += parser.m_keys.size();
|
||||
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
|
||||
}
|
||||
}
|
||||
|
|
@ -1886,8 +1936,9 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
|
|||
}
|
||||
}
|
||||
|
||||
if (ctx == ParseScriptContext::P2WSH) {
|
||||
KeyParser parser(nullptr, &provider);
|
||||
if (ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR) {
|
||||
const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT};
|
||||
KeyParser parser(/* out = */nullptr, /* in = */&provider, /* ctx = */script_ctx);
|
||||
auto node = miniscript::FromScript(script, parser);
|
||||
if (node && node->IsSane()) {
|
||||
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <vector>
|
||||
#include <script/script.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <serialize.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
|
@ -32,7 +33,8 @@ Type SanitizeType(Type e) {
|
|||
return e;
|
||||
}
|
||||
|
||||
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) {
|
||||
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k,
|
||||
size_t data_size, size_t n_subs, size_t n_keys, MiniscriptContext ms_ctx) {
|
||||
// Sanity check on data
|
||||
if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) {
|
||||
assert(data_size == 32);
|
||||
|
|
@ -44,7 +46,7 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
|
|||
// Sanity check on k
|
||||
if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) {
|
||||
assert(k >= 1 && k < 0x80000000UL);
|
||||
} else if (fragment == Fragment::MULTI) {
|
||||
} else if (fragment == Fragment::MULTI || fragment == Fragment::MULTI_A) {
|
||||
assert(k >= 1 && k <= n_keys);
|
||||
} else if (fragment == Fragment::THRESH) {
|
||||
assert(k >= 1 && k <= n_subs);
|
||||
|
|
@ -68,7 +70,11 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
|
|||
if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) {
|
||||
assert(n_keys == 1);
|
||||
} else if (fragment == Fragment::MULTI) {
|
||||
assert(n_keys >= 1 && n_keys <= 20);
|
||||
assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTISIG);
|
||||
assert(!IsTapscript(ms_ctx));
|
||||
} else if (fragment == Fragment::MULTI_A) {
|
||||
assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTI_A);
|
||||
assert(IsTapscript(ms_ctx));
|
||||
} else {
|
||||
assert(n_keys == 0);
|
||||
}
|
||||
|
|
@ -113,7 +119,8 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
|
|||
"e"_mst.If(x << "f"_mst) | // e=f_x
|
||||
(x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
|
||||
(x & "ms"_mst) | // m=m_x, s=s_x
|
||||
// NOTE: 'd:' is not 'u' under P2WSH as MINIMALIF is only a policy rule there.
|
||||
// NOTE: 'd:' is 'u' under Tapscript but not P2WSH as MINIMALIF is only a policy rule there.
|
||||
"u"_mst.If(IsTapscript(ms_ctx)) |
|
||||
"ndx"_mst; // n, d, x
|
||||
case Fragment::WRAP_V: return
|
||||
"V"_mst.If(x << "B"_mst) | // V=B_x
|
||||
|
|
@ -210,7 +217,12 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
|
|||
((x << "h"_mst) && (y << "g"_mst)) ||
|
||||
((x << "i"_mst) && (y << "j"_mst)) ||
|
||||
((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*k_z* !(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y)
|
||||
case Fragment::MULTI: return "Bnudemsk"_mst;
|
||||
case Fragment::MULTI: {
|
||||
return "Bnudemsk"_mst;
|
||||
}
|
||||
case Fragment::MULTI_A: {
|
||||
return "Budemsk"_mst;
|
||||
}
|
||||
case Fragment::THRESH: {
|
||||
bool all_e = true;
|
||||
bool all_m = true;
|
||||
|
|
@ -246,11 +258,12 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
|
|||
assert(false);
|
||||
}
|
||||
|
||||
size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) {
|
||||
size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs,
|
||||
size_t n_keys, MiniscriptContext ms_ctx) {
|
||||
switch (fragment) {
|
||||
case Fragment::JUST_1:
|
||||
case Fragment::JUST_0: return 1;
|
||||
case Fragment::PK_K: return 34;
|
||||
case Fragment::PK_K: return IsTapscript(ms_ctx) ? 33 : 34;
|
||||
case Fragment::PK_H: return 3 + 21;
|
||||
case Fragment::OLDER:
|
||||
case Fragment::AFTER: return 1 + BuildScript(k).size();
|
||||
|
|
@ -259,6 +272,7 @@ size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_
|
|||
case Fragment::HASH160:
|
||||
case Fragment::RIPEMD160: return 4 + 2 + 21;
|
||||
case Fragment::MULTI: return 1 + BuildScript(n_keys).size() + BuildScript(k).size() + 34 * n_keys;
|
||||
case Fragment::MULTI_A: return (1 + 32 + 1) * n_keys + BuildScript(k).size() + 1;
|
||||
case Fragment::AND_V: return subsize;
|
||||
case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst);
|
||||
case Fragment::WRAP_S:
|
||||
|
|
@ -372,9 +386,13 @@ std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script)
|
|||
// Decompose OP_EQUALVERIFY into OP_EQUAL OP_VERIFY
|
||||
out.emplace_back(OP_EQUAL, std::vector<unsigned char>());
|
||||
opcode = OP_VERIFY;
|
||||
} else if (opcode == OP_NUMEQUALVERIFY) {
|
||||
// Decompose OP_NUMEQUALVERIFY into OP_NUMEQUAL OP_VERIFY
|
||||
out.emplace_back(OP_NUMEQUAL, std::vector<unsigned char>());
|
||||
opcode = OP_VERIFY;
|
||||
} else if (IsPushdataOp(opcode)) {
|
||||
if (!CheckMinimalPush(push_data, opcode)) return {};
|
||||
} else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) {
|
||||
} else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL || opcode == OP_NUMEQUAL) && (*it == OP_VERIFY)) {
|
||||
// Rule out non minimal VERIFY sequences
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -118,12 +118,17 @@ static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigd
|
|||
pubkey = it->second.first;
|
||||
return true;
|
||||
}
|
||||
// Look for pubkey in pubkey list
|
||||
// Look for pubkey in pubkey lists
|
||||
const auto& pk_it = sigdata.misc_pubkeys.find(address);
|
||||
if (pk_it != sigdata.misc_pubkeys.end()) {
|
||||
pubkey = pk_it->second.first;
|
||||
return true;
|
||||
}
|
||||
const auto& tap_pk_it = sigdata.tap_pubkeys.find(address);
|
||||
if (tap_pk_it != sigdata.tap_pubkeys.end()) {
|
||||
pubkey = tap_pk_it->second.GetEvenCorrespondingCPubKey();
|
||||
return true;
|
||||
}
|
||||
// Query the underlying provider
|
||||
return provider.GetPubKey(address, pubkey);
|
||||
}
|
||||
|
|
@ -175,49 +180,158 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur
|
|||
return false;
|
||||
}
|
||||
|
||||
template<typename M, typename K, typename V>
|
||||
miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value)
|
||||
{
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) {
|
||||
value = it->second;
|
||||
return miniscript::Availability::YES;
|
||||
}
|
||||
return miniscript::Availability::NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context for solving a Miniscript.
|
||||
* If enough material (access to keys, hash preimages, ..) is given, produces a valid satisfaction.
|
||||
*/
|
||||
template<typename Pk>
|
||||
struct Satisfier {
|
||||
using Key = Pk;
|
||||
|
||||
const SigningProvider& m_provider;
|
||||
SignatureData& m_sig_data;
|
||||
const BaseSignatureCreator& m_creator;
|
||||
const CScript& m_witness_script;
|
||||
//! The context of the script we are satisfying (either P2WSH or Tapscript).
|
||||
const miniscript::MiniscriptContext m_script_ctx;
|
||||
|
||||
explicit Satisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
|
||||
const BaseSignatureCreator& creator LIFETIMEBOUND,
|
||||
const CScript& witscript LIFETIMEBOUND,
|
||||
miniscript::MiniscriptContext script_ctx) : m_provider(provider),
|
||||
m_sig_data(sig_data),
|
||||
m_creator(creator),
|
||||
m_witness_script(witscript),
|
||||
m_script_ctx(script_ctx) {}
|
||||
|
||||
static bool KeyCompare(const Key& a, const Key& b) {
|
||||
return a < b;
|
||||
}
|
||||
|
||||
//! Get a CPubKey from a key hash. Note the key hash may be of an xonly pubkey.
|
||||
template<typename I>
|
||||
std::optional<CPubKey> CPubFromPKHBytes(I first, I last) const {
|
||||
assert(last - first == 20);
|
||||
CPubKey pubkey;
|
||||
CKeyID key_id;
|
||||
std::copy(first, last, key_id.begin());
|
||||
if (GetPubKey(m_provider, m_sig_data, key_id, pubkey)) return pubkey;
|
||||
m_sig_data.missing_pubkeys.push_back(key_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
//! Conversion to raw public key.
|
||||
std::vector<unsigned char> ToPKBytes(const Key& key) const { return {key.begin(), key.end()}; }
|
||||
|
||||
//! Time lock satisfactions.
|
||||
bool CheckAfter(uint32_t value) const { return m_creator.Checker().CheckLockTime(CScriptNum(value)); }
|
||||
bool CheckOlder(uint32_t value) const { return m_creator.Checker().CheckSequence(CScriptNum(value)); }
|
||||
|
||||
//! Hash preimage satisfactions.
|
||||
miniscript::Availability SatSHA256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.sha256_preimages, hash, preimage);
|
||||
}
|
||||
miniscript::Availability SatRIPEMD160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.ripemd160_preimages, hash, preimage);
|
||||
}
|
||||
miniscript::Availability SatHASH256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.hash256_preimages, hash, preimage);
|
||||
}
|
||||
miniscript::Availability SatHASH160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.hash160_preimages, hash, preimage);
|
||||
}
|
||||
|
||||
miniscript::MiniscriptContext MsContext() const {
|
||||
return m_script_ctx;
|
||||
}
|
||||
};
|
||||
|
||||
/** Miniscript satisfier specific to P2WSH context. */
|
||||
struct WshSatisfier: Satisfier<CPubKey> {
|
||||
explicit WshSatisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
|
||||
const BaseSignatureCreator& creator LIFETIMEBOUND, const CScript& witscript LIFETIMEBOUND)
|
||||
: Satisfier(provider, sig_data, creator, witscript, miniscript::MiniscriptContext::P2WSH) {}
|
||||
|
||||
//! Conversion from a raw compressed public key.
|
||||
template <typename I>
|
||||
std::optional<CPubKey> FromPKBytes(I first, I last) const {
|
||||
CPubKey pubkey{first, last};
|
||||
if (pubkey.IsValid()) return pubkey;
|
||||
return {};
|
||||
}
|
||||
|
||||
//! Conversion from a raw compressed public key hash.
|
||||
template<typename I>
|
||||
std::optional<CPubKey> FromPKHBytes(I first, I last) const {
|
||||
return Satisfier::CPubFromPKHBytes(first, last);
|
||||
}
|
||||
|
||||
//! Satisfy an ECDSA signature check.
|
||||
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
|
||||
if (CreateSig(m_creator, m_sig_data, m_provider, sig, key, m_witness_script, SigVersion::WITNESS_V0, SIGHASH_RANGEPROOF)) { // ELEMENTS FIXME: check flags
|
||||
return miniscript::Availability::YES;
|
||||
}
|
||||
return miniscript::Availability::NO;
|
||||
}
|
||||
};
|
||||
|
||||
/** Miniscript satisfier specific to Tapscript context. */
|
||||
struct TapSatisfier: Satisfier<XOnlyPubKey> {
|
||||
const uint256& m_leaf_hash;
|
||||
|
||||
explicit TapSatisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
|
||||
const BaseSignatureCreator& creator LIFETIMEBOUND, const CScript& script LIFETIMEBOUND,
|
||||
const uint256& leaf_hash LIFETIMEBOUND)
|
||||
: Satisfier(provider, sig_data, creator, script, miniscript::MiniscriptContext::TAPSCRIPT),
|
||||
m_leaf_hash(leaf_hash) {}
|
||||
|
||||
//! Conversion from a raw xonly public key.
|
||||
template <typename I>
|
||||
std::optional<XOnlyPubKey> FromPKBytes(I first, I last) const {
|
||||
CHECK_NONFATAL(last - first == 32);
|
||||
XOnlyPubKey pubkey;
|
||||
std::copy(first, last, pubkey.begin());
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
//! Conversion from a raw xonly public key hash.
|
||||
template<typename I>
|
||||
std::optional<XOnlyPubKey> FromPKHBytes(I first, I last) const {
|
||||
if (auto pubkey = Satisfier::CPubFromPKHBytes(first, last)) return XOnlyPubKey{*pubkey};
|
||||
return {};
|
||||
}
|
||||
|
||||
//! Satisfy a BIP340 signature check.
|
||||
miniscript::Availability Sign(const XOnlyPubKey& key, std::vector<unsigned char>& sig) const {
|
||||
if (CreateTaprootScriptSig(m_creator, m_sig_data, m_provider, sig, key, m_leaf_hash, SigVersion::TAPSCRIPT)) {
|
||||
return miniscript::Availability::YES;
|
||||
}
|
||||
return miniscript::Availability::NO;
|
||||
}
|
||||
};
|
||||
|
||||
static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, Span<const unsigned char> script_bytes, std::vector<valtype>& result)
|
||||
{
|
||||
// Only BIP342 tapscript signing is supported for now.
|
||||
if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false;
|
||||
SigVersion sigversion = SigVersion::TAPSCRIPT;
|
||||
|
||||
uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes);
|
||||
CScript script = CScript(script_bytes.begin(), script_bytes.end());
|
||||
|
||||
// <xonly pubkey> OP_CHECKSIG
|
||||
if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) {
|
||||
XOnlyPubKey pubkey{Span{script}.subspan(1, 32)};
|
||||
std::vector<unsigned char> sig;
|
||||
if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) {
|
||||
result = Vector(std::move(sig));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// multi_a scripts (<key> OP_CHECKSIG <key> OP_CHECKSIGADD <key> OP_CHECKSIGADD <k> OP_NUMEQUAL)
|
||||
if (auto match = MatchMultiA(script)) {
|
||||
std::vector<std::vector<unsigned char>> sigs;
|
||||
int good_sigs = 0;
|
||||
for (size_t i = 0; i < match->second.size(); ++i) {
|
||||
XOnlyPubKey pubkey{*(match->second.rbegin() + i)};
|
||||
std::vector<unsigned char> sig;
|
||||
bool good_sig = CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion);
|
||||
if (good_sig && good_sigs < match->first) {
|
||||
++good_sigs;
|
||||
sigs.push_back(std::move(sig));
|
||||
} else {
|
||||
sigs.emplace_back();
|
||||
}
|
||||
}
|
||||
if (good_sigs == match->first) {
|
||||
result = std::move(sigs);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
TapSatisfier ms_satisfier{provider, sigdata, creator, script, leaf_hash};
|
||||
const auto ms = miniscript::FromScript(script, ms_satisfier);
|
||||
return ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES;
|
||||
}
|
||||
|
||||
static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result)
|
||||
|
|
@ -395,92 +509,6 @@ static CScript PushAll(const std::vector<valtype>& values)
|
|||
return result;
|
||||
}
|
||||
|
||||
template<typename M, typename K, typename V>
|
||||
miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value)
|
||||
{
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) {
|
||||
value = it->second;
|
||||
return miniscript::Availability::YES;
|
||||
}
|
||||
return miniscript::Availability::NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context for solving a Miniscript.
|
||||
* If enough material (access to keys, hash preimages, ..) is given, produces a valid satisfaction.
|
||||
*/
|
||||
struct Satisfier {
|
||||
typedef CPubKey Key;
|
||||
|
||||
const SigningProvider& m_provider;
|
||||
SignatureData& m_sig_data;
|
||||
const BaseSignatureCreator& m_creator;
|
||||
const CScript& m_witness_script;
|
||||
|
||||
explicit Satisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
|
||||
const BaseSignatureCreator& creator LIFETIMEBOUND,
|
||||
const CScript& witscript LIFETIMEBOUND) : m_provider(provider),
|
||||
m_sig_data(sig_data),
|
||||
m_creator(creator),
|
||||
m_witness_script(witscript) {}
|
||||
|
||||
static bool KeyCompare(const Key& a, const Key& b) {
|
||||
return a < b;
|
||||
}
|
||||
|
||||
//! Conversion from a raw public key.
|
||||
template <typename I>
|
||||
std::optional<Key> FromPKBytes(I first, I last) const
|
||||
{
|
||||
Key pubkey{first, last};
|
||||
if (pubkey.IsValid()) return pubkey;
|
||||
return {};
|
||||
}
|
||||
|
||||
//! Conversion from a raw public key hash.
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKHBytes(I first, I last) const {
|
||||
assert(last - first == 20);
|
||||
Key pubkey;
|
||||
CKeyID key_id;
|
||||
std::copy(first, last, key_id.begin());
|
||||
if (GetPubKey(m_provider, m_sig_data, key_id, pubkey)) return pubkey;
|
||||
m_sig_data.missing_pubkeys.push_back(key_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
//! Conversion to raw public key.
|
||||
std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; }
|
||||
|
||||
//! Satisfy a signature check.
|
||||
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
|
||||
if (CreateSig(m_creator, m_sig_data, m_provider, sig, key, m_witness_script, SigVersion::WITNESS_V0, SIGHASH_RANGEPROOF)) { // ELEMENTS FIXME: check flags
|
||||
return miniscript::Availability::YES;
|
||||
}
|
||||
return miniscript::Availability::NO;
|
||||
}
|
||||
|
||||
//! Time lock satisfactions.
|
||||
bool CheckAfter(uint32_t value) const { return m_creator.Checker().CheckLockTime(CScriptNum(value)); }
|
||||
bool CheckOlder(uint32_t value) const { return m_creator.Checker().CheckSequence(CScriptNum(value)); }
|
||||
|
||||
|
||||
//! Hash preimage satisfactions.
|
||||
miniscript::Availability SatSHA256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.sha256_preimages, hash, preimage);
|
||||
}
|
||||
miniscript::Availability SatRIPEMD160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.ripemd160_preimages, hash, preimage);
|
||||
}
|
||||
miniscript::Availability SatHASH256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.hash256_preimages, hash, preimage);
|
||||
}
|
||||
miniscript::Availability SatHASH160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
|
||||
return MsLookupHelper(m_sig_data.hash160_preimages, hash, preimage);
|
||||
}
|
||||
};
|
||||
|
||||
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata, unsigned int additional_flags)
|
||||
{
|
||||
if (sigdata.complete) return true;
|
||||
|
|
@ -530,7 +558,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
|
|||
// isn't fully solved. For instance the CHECKMULTISIG satisfaction in SignStep() pushes partial signatures
|
||||
// and the extractor relies on this behaviour to combine witnesses.
|
||||
if (!solved && result.empty()) {
|
||||
Satisfier ms_satisfier{provider, sigdata, creator, witnessscript};
|
||||
WshSatisfier ms_satisfier{provider, sigdata, creator, witnessscript};
|
||||
const auto ms = miniscript::FromScript(witnessscript, ms_satisfier);
|
||||
solved = ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ struct SignatureData {
|
|||
std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending
|
||||
std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash.
|
||||
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes).
|
||||
std::map<CKeyID, XOnlyPubKey> tap_pubkeys; ///< Misc Taproot pubkeys involved in this input, by hash. (Equivalent of misc_pubkeys but for Taproot.)
|
||||
std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found
|
||||
std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found
|
||||
uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any)
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ template <typename Stream> inline void Unserialize(Stream& s, bool& a) { uint8_t
|
|||
* size <= UINT_MAX -- 5 bytes (254 + 4 bytes)
|
||||
* size > UINT_MAX -- 9 bytes (255 + 8 bytes)
|
||||
*/
|
||||
inline unsigned int GetSizeOfCompactSize(uint64_t nSize)
|
||||
constexpr inline unsigned int GetSizeOfCompactSize(uint64_t nSize)
|
||||
{
|
||||
if (nSize < 253) return sizeof(unsigned char);
|
||||
else if (nSize <= std::numeric_limits<uint16_t>::max()) return sizeof(unsigned char) + sizeof(uint16_t);
|
||||
|
|
|
|||
|
|
@ -538,10 +538,10 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
|
|||
|
||||
// Invalid checksum
|
||||
CheckUnparsable("wsh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))#abcdef12", "wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))#abcdef12", "Provided checksum 'abcdef12' does not match computed checksum 'tyzp6a7p'");
|
||||
// Only p2wsh context is valid
|
||||
CheckUnparsable("sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh");
|
||||
// Only p2wsh or tr contexts are valid
|
||||
CheckUnparsable("sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh or tr.");
|
||||
CheckUnparsable("tr(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "tr(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "tr(): key 'and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10))' is not valid");
|
||||
CheckUnparsable("raw(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh");
|
||||
CheckUnparsable("raw(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh or tr.");
|
||||
CheckUnparsable("", "tr(034D2224bbbbbbbbbbcbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb40,{{{{{{{{{{{{{{{{{{{{{{multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008')", "'multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008'' is not a valid descriptor function");
|
||||
// No uncompressed keys allowed
|
||||
CheckUnparsable("", "wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(049228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4))),after(10)))", "A function is needed within P2WSH");
|
||||
|
|
@ -582,6 +582,18 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
|
|||
// Same for hash256
|
||||
Check("wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", SIGNABLE_FAILS, {{"0020cf62bf97baf977aec69cbc290c372899f913337a9093e8f066ab59b8657a365c"}}, OutputType::BECH32, /*op_desc_id=*/uint256S("8412ba3ac20ba3a30f81442d10d32e0468fa52814960d04e959bf84a9b813b88"), {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/CTxIn::SEQUENCE_FINAL, {});
|
||||
Check("wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", SIGNABLE, {{"0020cf62bf97baf977aec69cbc290c372899f913337a9093e8f066ab59b8657a365c"}}, OutputType::BECH32, /*op_desc_id=*/uint256S("8412ba3ac20ba3a30f81442d10d32e0468fa52814960d04e959bf84a9b813b88"), {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/CTxIn::SEQUENCE_FINAL, {{ParseHex("ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588"), ParseHex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")}});
|
||||
// Can have a Miniscript expression under tr() if it's alone.
|
||||
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,thresh(2,pk(L1NKM8dVA1h52mwDrmk1YreTWkAZZTu2vmKLpmLEbFRqGQYjHeEV),s:pk(Kz3iCBy3HNGP5CZWDsAMmnCMFNwqdDohudVN9fvkrN7tAkzKNtM7),adv:older(42)))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,thresh(2,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766),adv:older(42)))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,thresh(2,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766),adv:older(42)))", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE, {{"5120bb6f3772737f30df6cc77048015081dfe0e9cc695184415026b6bea5bbf6299d"}}, OutputType::BECH32M);
|
||||
// Can have a pkh() expression alone as tr() script path (because pkh() is valid Miniscript).
|
||||
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pkh(L1NKM8dVA1h52mwDrmk1YreTWkAZZTu2vmKLpmLEbFRqGQYjHeEV))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pkh(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pkh(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529))", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE, {{"51203bd141cbf41022c0867f00ac4d776c2db17e02cb9a3fdd429f80ed560b3d5e67"}}, OutputType::BECH32M);
|
||||
// Can have a Miniscript expression under tr() if it's part of a tree.
|
||||
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{{pkh(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL),pk(L3Enys1jFgTq4E24b8Uom1kAz6cNkz3Z82XZpBKCE2ztErq9fqvJ)},thresh(1,pk(L1NKM8dVA1h52mwDrmk1YreTWkAZZTu2vmKLpmLEbFRqGQYjHeEV),s:pk(Kz3iCBy3HNGP5CZWDsAMmnCMFNwqdDohudVN9fvkrN7tAkzKNtM7))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{{pkh(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5),pk(0dd6b52b192ab195558d22dd8437a9ec4519ee5ded496c0d55bc9b1a8b0e8c2b)},thresh(1,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{{pkh(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5),pk(0dd6b52b192ab195558d22dd8437a9ec4519ee5ded496c0d55bc9b1a8b0e8c2b)},thresh(1,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766))})", MISSING_PRIVKEYS | XONLY_KEYS, {{"51203e4815a2505e23e5ffc4881eda238466f80534953035c0b60124366fa7867ef1"}}, OutputType::BECH32M);
|
||||
// Can have two Miniscripts in a Taproot with mixed private and public keys, and mixed ranged extended keys and raw keys.
|
||||
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(v:pk(xpub6AGbgdKcAGeUWaGNKH2o3sRvjtvJCGZ1NwrHqMJDwD4bN1QuwPQSsdeAYkPZGPt2FTAyu6nWGsC3fN2nsBELrLPcRNuwwr5k1X7yW5WV4aX/*),pk(02daf6e3477fc3906a1997820ed2940c8f5fa0942946d0368f981b001fdd85afcb)),and_v(v:pk(xprv9wCN7tTqN5ATsmBGEijuNeUgQjma9tv3GmdWLmbYiuArPsAMj6tD1uASiBfm47kdoi7bDBAVxUZNLM2MkeouPK5menDTyCNZtExQrKhVu7C/*),pk(03272c0c1ae2c07528283b91ca57b45d2cc84e7960e1f17f58815372285f35e99a))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(v:pk(xpub6AGbgdKcAGeUWaGNKH2o3sRvjtvJCGZ1NwrHqMJDwD4bN1QuwPQSsdeAYkPZGPt2FTAyu6nWGsC3fN2nsBELrLPcRNuwwr5k1X7yW5WV4aX/*),pk(02daf6e3477fc3906a1997820ed2940c8f5fa0942946d0368f981b001fdd85afcb)),and_v(v:pk(xpub6ABiXPzjCSim6FFjLkGujnRQxmc4ZMdtdzZ79A1AHEhqGfVWGeCTZhUvZTSf1mNnGUtyNqgfE9eWaYdYReDKbPYqgqi9LLVZSmWnLQRx477/*),pk(03272c0c1ae2c07528283b91ca57b45d2cc84e7960e1f17f58815372285f35e99a))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(v:pk(xpub6AGbgdKcAGeUWaGNKH2o3sRvjtvJCGZ1NwrHqMJDwD4bN1QuwPQSsdeAYkPZGPt2FTAyu6nWGsC3fN2nsBELrLPcRNuwwr5k1X7yW5WV4aX/*),pk(02daf6e3477fc3906a1997820ed2940c8f5fa0942946d0368f981b001fdd85afcb)),and_v(v:pk(xpub6ABiXPzjCSim6FFjLkGujnRQxmc4ZMdtdzZ79A1AHEhqGfVWGeCTZhUvZTSf1mNnGUtyNqgfE9eWaYdYReDKbPYqgqi9LLVZSmWnLQRx477/*),pk(03272c0c1ae2c07528283b91ca57b45d2cc84e7960e1f17f58815372285f35e99a))})", MISSING_PRIVKEYS | XONLY_KEYS | RANGE | MIXED_PUBKEYS, {{"512007ba1c46c3972eb10d843f25fe2548fa9abd3db043117641f427013d4af23932"}}, OutputType::BECH32M, /*op_desc_id=*/{}, {{}, {0}});
|
||||
// Can sign for a Miniscript expression containing a hash challenge inside a Taproot tree. (Fails without the
|
||||
// preimages and the sequence, passes with.)
|
||||
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,KztMyyi1pXUtuZfJSB7JzVdmJMAz7wfGVFoSRUR5CVZxXxULXuGR)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE | SIGNABLE_FAILS, {{"512010b7eddc849e4ee5616d2dc16ec8a782c21b3b5b41c44b90044ffb1519e40816"}}, OutputType::BECH32M);
|
||||
Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,KztMyyi1pXUtuZfJSB7JzVdmJMAz7wfGVFoSRUR5CVZxXxULXuGR)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE, {{"512010b7eddc849e4ee5616d2dc16ec8a782c21b3b5b41c44b90044ffb1519e40816"}}, OutputType::BECH32M, /*op_desc_id=*/{}, {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/42, /*preimages=*/{{ParseHex("ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588"), ParseHex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")}});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <key.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script.h>
|
||||
#include <script/signingprovider.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
|
|
@ -14,6 +15,15 @@
|
|||
|
||||
namespace {
|
||||
|
||||
using Fragment = miniscript::Fragment;
|
||||
using NodeRef = miniscript::NodeRef<CPubKey>;
|
||||
using Node = miniscript::Node<CPubKey>;
|
||||
using Type = miniscript::Type;
|
||||
using MsCtx = miniscript::MiniscriptContext;
|
||||
// https://github.com/llvm/llvm-project/issues/53444
|
||||
// NOLINTNEXTLINE(misc-unused-using-decls)
|
||||
using miniscript::operator"" _mst;
|
||||
|
||||
//! Some pre-computed data for more efficient string roundtrips and to simulate challenges.
|
||||
struct TestData {
|
||||
typedef CPubKey Key;
|
||||
|
|
@ -23,6 +33,7 @@ struct TestData {
|
|||
std::map<Key, int> dummy_key_idx_map;
|
||||
std::map<CKeyID, Key> dummy_keys_map;
|
||||
std::map<Key, std::pair<std::vector<unsigned char>, bool>> dummy_sigs;
|
||||
std::map<XOnlyPubKey, std::pair<std::vector<unsigned char>, bool>> schnorr_sigs;
|
||||
|
||||
// Precomputed hashes of each kind.
|
||||
std::vector<std::vector<unsigned char>> sha256;
|
||||
|
|
@ -37,6 +48,11 @@ struct TestData {
|
|||
//! Set the precomputed data.
|
||||
void Init() {
|
||||
unsigned char keydata[32] = {1};
|
||||
// All our signatures sign (and are required to sign) this constant message.
|
||||
auto const MESSAGE_HASH{uint256S("f5cd94e18b6fe77dd7aca9e35c2b0c9cbd86356c80a71065")};
|
||||
// We don't pass additional randomness when creating a schnorr signature.
|
||||
auto const EMPTY_AUX{uint256S("")};
|
||||
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
keydata[31] = i;
|
||||
CKey privkey;
|
||||
|
|
@ -46,11 +62,17 @@ struct TestData {
|
|||
dummy_keys.push_back(pubkey);
|
||||
dummy_key_idx_map.emplace(pubkey, i);
|
||||
dummy_keys_map.insert({pubkey.GetID(), pubkey});
|
||||
XOnlyPubKey xonly_pubkey{pubkey};
|
||||
dummy_key_idx_map.emplace(xonly_pubkey, i);
|
||||
uint160 xonly_hash{Hash160(xonly_pubkey)};
|
||||
dummy_keys_map.emplace(xonly_hash, pubkey);
|
||||
|
||||
std::vector<unsigned char> sig;
|
||||
privkey.Sign(uint256S(""), sig);
|
||||
std::vector<unsigned char> sig, schnorr_sig(64);
|
||||
privkey.Sign(MESSAGE_HASH, sig);
|
||||
sig.push_back(1); // SIGHASH_ALL
|
||||
dummy_sigs.insert({pubkey, {sig, i & 1}});
|
||||
assert(privkey.SignSchnorr(MESSAGE_HASH, schnorr_sig, nullptr, EMPTY_AUX));
|
||||
schnorr_sigs.emplace(XOnlyPubKey{pubkey}, std::make_pair(std::move(schnorr_sig), i & 1));
|
||||
|
||||
std::vector<unsigned char> hash;
|
||||
hash.resize(32);
|
||||
|
|
@ -70,6 +92,19 @@ struct TestData {
|
|||
if (i & 1) hash160_preimages[hash] = std::vector<unsigned char>(keydata, keydata + 32);
|
||||
}
|
||||
}
|
||||
|
||||
//! Get the (Schnorr or ECDSA, depending on context) signature for this pubkey.
|
||||
const std::pair<std::vector<unsigned char>, bool>* GetSig(const MsCtx script_ctx, const Key& key) const {
|
||||
if (!miniscript::IsTapscript(script_ctx)) {
|
||||
const auto it = dummy_sigs.find(key);
|
||||
if (it == dummy_sigs.end()) return nullptr;
|
||||
return &it->second;
|
||||
} else {
|
||||
const auto it = schnorr_sigs.find(XOnlyPubKey{key});
|
||||
if (it == schnorr_sigs.end()) return nullptr;
|
||||
return &it->second;
|
||||
}
|
||||
}
|
||||
} TEST_DATA;
|
||||
|
||||
/**
|
||||
|
|
@ -80,6 +115,8 @@ struct TestData {
|
|||
struct ParserContext {
|
||||
typedef CPubKey Key;
|
||||
|
||||
MsCtx script_ctx{MsCtx::P2WSH};
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return a < b;
|
||||
}
|
||||
|
|
@ -92,14 +129,20 @@ struct ParserContext {
|
|||
return HexStr(Span{&idx, 1});
|
||||
}
|
||||
|
||||
std::vector<unsigned char> ToPKBytes(const Key& key) const
|
||||
{
|
||||
return {key.begin(), key.end()};
|
||||
std::vector<unsigned char> ToPKBytes(const Key& key) const {
|
||||
if (!miniscript::IsTapscript(script_ctx)) {
|
||||
return {key.begin(), key.end()};
|
||||
}
|
||||
const XOnlyPubKey xonly_pubkey{key};
|
||||
return {xonly_pubkey.begin(), xonly_pubkey.end()};
|
||||
}
|
||||
|
||||
std::vector<unsigned char> ToPKHBytes(const Key& key) const
|
||||
{
|
||||
const auto h = Hash160(key);
|
||||
std::vector<unsigned char> ToPKHBytes(const Key& key) const {
|
||||
if (!miniscript::IsTapscript(script_ctx)) {
|
||||
const auto h = Hash160(key);
|
||||
return {h.begin(), h.end()};
|
||||
}
|
||||
const auto h = Hash160(XOnlyPubKey{key});
|
||||
return {h.begin(), h.end()};
|
||||
}
|
||||
|
||||
|
|
@ -113,10 +156,15 @@ struct ParserContext {
|
|||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKBytes(I first, I last) const {
|
||||
CPubKey key;
|
||||
key.Set(first, last);
|
||||
if (!key.IsValid()) return {};
|
||||
return key;
|
||||
if (!miniscript::IsTapscript(script_ctx)) {
|
||||
Key key{first, last};
|
||||
if (key.IsValid()) return key;
|
||||
return {};
|
||||
}
|
||||
if (last - first != 32) return {};
|
||||
XOnlyPubKey xonly_pubkey;
|
||||
std::copy(first, last, xonly_pubkey.begin());
|
||||
return xonly_pubkey.GetEvenCorrespondingCPubKey();
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
|
|
@ -128,10 +176,16 @@ struct ParserContext {
|
|||
if (it == TEST_DATA.dummy_keys_map.end()) return {};
|
||||
return it->second;
|
||||
}
|
||||
|
||||
MsCtx MsContext() const {
|
||||
return script_ctx;
|
||||
}
|
||||
} PARSER_CTX;
|
||||
|
||||
//! Context that implements naive conversion from/to script only, for roundtrip testing.
|
||||
struct ScriptParserContext {
|
||||
MsCtx script_ctx{MsCtx::P2WSH};
|
||||
|
||||
//! For Script roundtrip we never need the key from a key hash.
|
||||
struct Key {
|
||||
bool is_hash;
|
||||
|
|
@ -172,6 +226,10 @@ struct ScriptParserContext {
|
|||
key.is_hash = true;
|
||||
return key;
|
||||
}
|
||||
|
||||
MsCtx MsContext() const {
|
||||
return script_ctx;
|
||||
}
|
||||
} SCRIPT_PARSER_CONTEXT;
|
||||
|
||||
//! Context to produce a satisfaction for a Miniscript node using the pre-computed data.
|
||||
|
|
@ -183,15 +241,11 @@ struct SatisfierContext: ParserContext {
|
|||
|
||||
// Signature challenges fulfilled with a dummy signature, if it was one of our dummy keys.
|
||||
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
|
||||
const auto it = TEST_DATA.dummy_sigs.find(key);
|
||||
if (it == TEST_DATA.dummy_sigs.end()) return miniscript::Availability::NO;
|
||||
if (it->second.second) {
|
||||
// Key is "available"
|
||||
sig = it->second.first;
|
||||
return miniscript::Availability::YES;
|
||||
} else {
|
||||
return miniscript::Availability::NO;
|
||||
bool sig_available{false};
|
||||
if (auto res = TEST_DATA.GetSig(script_ctx, key)) {
|
||||
std::tie(sig, sig_available) = *res;
|
||||
}
|
||||
return sig_available ? miniscript::Availability::YES : miniscript::Availability::NO;
|
||||
}
|
||||
|
||||
//! Lookup generalization for all the hash satisfactions below
|
||||
|
|
@ -230,6 +284,13 @@ struct CheckerContext: BaseSignatureChecker {
|
|||
if (it == TEST_DATA.dummy_sigs.end()) return false;
|
||||
return it->second.first == sig;
|
||||
}
|
||||
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion,
|
||||
ScriptExecutionData&, ScriptError*) const override {
|
||||
XOnlyPubKey pk{pubkey};
|
||||
auto it = TEST_DATA.schnorr_sigs.find(pk);
|
||||
if (it == TEST_DATA.schnorr_sigs.end()) return false;
|
||||
return it->second.first == sig;
|
||||
}
|
||||
bool CheckLockTime(const CScriptNum& nLockTime) const override { return nLockTime.GetInt64() & 1; }
|
||||
bool CheckSequence(const CScriptNum& nSequence) const override { return nSequence.GetInt64() & 1; }
|
||||
} CHECKER_CTX;
|
||||
|
|
@ -244,11 +305,8 @@ struct KeyComparator {
|
|||
// A dummy scriptsig to pass to VerifyScript (we always use Segwit v0).
|
||||
const CScript DUMMY_SCRIPTSIG;
|
||||
|
||||
using Fragment = miniscript::Fragment;
|
||||
using NodeRef = miniscript::NodeRef<CPubKey>;
|
||||
using Node = miniscript::Node<CPubKey>;
|
||||
using Type = miniscript::Type;
|
||||
using miniscript::operator"" _mst;
|
||||
//! Public key to be used as internal key for dummy Taproot spends.
|
||||
const std::vector<unsigned char> NUMS_PK{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")};
|
||||
|
||||
//! Construct a miniscript node as a shared_ptr.
|
||||
template<typename... Args> NodeRef MakeNodeRef(Args&&... args) {
|
||||
|
|
@ -321,9 +379,10 @@ std::optional<uint32_t> ConsumeTimeLock(FuzzedDataProvider& provider) {
|
|||
* - For pk_k(), pk_h(), and all hashes, the next byte defines the index of the value in the test data.
|
||||
* - For multi(), the next 2 bytes define respectively the threshold and the number of keys. Then as many
|
||||
* bytes as the number of keys define the index of each key in the test data.
|
||||
* - For multi_a(), same as for multi() but the threshold and the keys count are encoded on two bytes.
|
||||
* - For thresh(), the next byte defines the threshold value and the following one the number of subs.
|
||||
*/
|
||||
std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type type_needed) {
|
||||
std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider& provider, Type type_needed) {
|
||||
bool allow_B = (type_needed == ""_mst) || (type_needed << "B"_mst);
|
||||
bool allow_K = (type_needed == ""_mst) || (type_needed << "K"_mst);
|
||||
bool allow_V = (type_needed == ""_mst) || (type_needed << "V"_mst);
|
||||
|
|
@ -367,7 +426,7 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ
|
|||
if (!allow_B) return {};
|
||||
return {{Fragment::HASH160, ConsumeHash160(provider)}};
|
||||
case 10: {
|
||||
if (!allow_B) return {};
|
||||
if (!allow_B || IsTapscript(script_ctx)) return {};
|
||||
const auto k = provider.ConsumeIntegral<uint8_t>();
|
||||
const auto n_keys = provider.ConsumeIntegral<uint8_t>();
|
||||
if (n_keys > 20 || k == 0 || k > n_keys) return {};
|
||||
|
|
@ -428,6 +487,15 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ
|
|||
case 26:
|
||||
if (!allow_B) return {};
|
||||
return {{{"B"_mst}, Fragment::WRAP_N}};
|
||||
case 27: {
|
||||
if (!allow_B || !IsTapscript(script_ctx)) return {};
|
||||
const auto k = provider.ConsumeIntegral<uint16_t>();
|
||||
const auto n_keys = provider.ConsumeIntegral<uint16_t>();
|
||||
if (n_keys > 999 || k == 0 || k > n_keys) return {};
|
||||
std::vector<CPubKey> keys{n_keys};
|
||||
for (auto& key: keys) key = ConsumePubKey(provider);
|
||||
return {{Fragment::MULTI_A, k, std::move(keys)}};
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -444,9 +512,15 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ
|
|||
struct SmartInfo
|
||||
{
|
||||
using recipe = std::pair<Fragment, std::vector<Type>>;
|
||||
std::map<Type, std::vector<recipe>> table;
|
||||
std::map<Type, std::vector<recipe>> wsh_table, tap_table;
|
||||
|
||||
void Init()
|
||||
{
|
||||
Init(wsh_table, MsCtx::P2WSH);
|
||||
Init(tap_table, MsCtx::TAPSCRIPT);
|
||||
}
|
||||
|
||||
void Init(std::map<Type, std::vector<recipe>>& table, MsCtx script_ctx)
|
||||
{
|
||||
/* Construct a set of interesting type requirements to reason with (sections of BKVWzondu). */
|
||||
std::vector<Type> types;
|
||||
|
|
@ -495,7 +569,7 @@ struct SmartInfo
|
|||
std::sort(types.begin(), types.end());
|
||||
|
||||
// Iterate over all possible fragments.
|
||||
for (int fragidx = 0; fragidx <= int(Fragment::MULTI); ++fragidx) {
|
||||
for (int fragidx = 0; fragidx <= int(Fragment::MULTI_A); ++fragidx) {
|
||||
int sub_count = 0; //!< The minimum number of child nodes this recipe has.
|
||||
int sub_range = 1; //!< The maximum number of child nodes for this recipe is sub_count+sub_range-1.
|
||||
size_t data_size = 0;
|
||||
|
|
@ -503,6 +577,12 @@ struct SmartInfo
|
|||
uint32_t k = 0;
|
||||
Fragment frag{fragidx};
|
||||
|
||||
// Only produce recipes valid in the given context.
|
||||
if ((!miniscript::IsTapscript(script_ctx) && frag == Fragment::MULTI_A)
|
||||
|| (miniscript::IsTapscript(script_ctx) && frag == Fragment::MULTI)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Based on the fragment, determine #subs/data/k/keys to pass to ComputeType. */
|
||||
switch (frag) {
|
||||
case Fragment::PK_K:
|
||||
|
|
@ -510,6 +590,7 @@ struct SmartInfo
|
|||
n_keys = 1;
|
||||
break;
|
||||
case Fragment::MULTI:
|
||||
case Fragment::MULTI_A:
|
||||
n_keys = 1;
|
||||
k = 1;
|
||||
break;
|
||||
|
|
@ -568,7 +649,7 @@ struct SmartInfo
|
|||
if (subs > 0) subt.push_back(x);
|
||||
if (subs > 1) subt.push_back(y);
|
||||
if (subs > 2) subt.push_back(z);
|
||||
Type res = miniscript::internal::ComputeType(frag, x, y, z, subt, k, data_size, subs, n_keys);
|
||||
Type res = miniscript::internal::ComputeType(frag, x, y, z, subt, k, data_size, subs, n_keys, script_ctx);
|
||||
// Continue if the result is not a valid node.
|
||||
if ((res << "K"_mst) + (res << "V"_mst) + (res << "B"_mst) + (res << "W"_mst) != 1) continue;
|
||||
|
||||
|
|
@ -685,10 +766,11 @@ struct SmartInfo
|
|||
* (as improvements to the tables or changes to the typing rules could invalidate
|
||||
* everything).
|
||||
*/
|
||||
std::optional<NodeInfo> ConsumeNodeSmart(FuzzedDataProvider& provider, Type type_needed) {
|
||||
std::optional<NodeInfo> ConsumeNodeSmart(MsCtx script_ctx, FuzzedDataProvider& provider, Type type_needed) {
|
||||
/** Table entry for the requested type. */
|
||||
auto recipes_it = SMARTINFO.table.find(type_needed);
|
||||
assert(recipes_it != SMARTINFO.table.end());
|
||||
const auto& table{IsTapscript(script_ctx) ? SMARTINFO.tap_table : SMARTINFO.wsh_table};
|
||||
auto recipes_it = table.find(type_needed);
|
||||
assert(recipes_it != table.end());
|
||||
/** Pick one recipe from the available ones for that type. */
|
||||
const auto& [frag, subt] = PickValue(provider, recipes_it->second);
|
||||
|
||||
|
|
@ -704,6 +786,13 @@ std::optional<NodeInfo> ConsumeNodeSmart(FuzzedDataProvider& provider, Type type
|
|||
for (auto& key: keys) key = ConsumePubKey(provider);
|
||||
return {{frag, k, std::move(keys)}};
|
||||
}
|
||||
case Fragment::MULTI_A: {
|
||||
const auto n_keys = provider.ConsumeIntegralInRange<uint16_t>(1, 999);
|
||||
const auto k = provider.ConsumeIntegralInRange<uint16_t>(1, n_keys);
|
||||
std::vector<CPubKey> keys{n_keys};
|
||||
for (auto& key: keys) key = ConsumePubKey(provider);
|
||||
return {{frag, k, std::move(keys)}};
|
||||
}
|
||||
case Fragment::OLDER:
|
||||
case Fragment::AFTER:
|
||||
return {{frag, provider.ConsumeIntegralInRange<uint32_t>(1, 0x7FFFFFF)}};
|
||||
|
|
@ -760,7 +849,7 @@ std::optional<NodeInfo> ConsumeNodeSmart(FuzzedDataProvider& provider, Type type
|
|||
* a NodeRef whose Type() matches the type fed to ConsumeNode.
|
||||
*/
|
||||
template<typename F>
|
||||
NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
||||
NodeRef GenNode(MsCtx script_ctx, F ConsumeNode, Type root_type, bool strict_valid = false) {
|
||||
/** A stack of miniscript Nodes being built up. */
|
||||
std::vector<NodeRef> stack;
|
||||
/** The queue of instructions. */
|
||||
|
|
@ -781,7 +870,8 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
|||
// Update predicted resource limits. Since every leaf Miniscript node is at least one
|
||||
// byte long, we move one byte from each child to their parent. A similar technique is
|
||||
// used in the miniscript::internal::Parse function to prevent runaway string parsing.
|
||||
scriptsize += miniscript::internal::ComputeScriptLen(node_info->fragment, ""_mst, node_info->subtypes.size(), node_info->k, node_info->subtypes.size(), node_info->keys.size()) - 1;
|
||||
scriptsize += miniscript::internal::ComputeScriptLen(node_info->fragment, ""_mst, node_info->subtypes.size(), node_info->k, node_info->subtypes.size(),
|
||||
node_info->keys.size(), script_ctx) - 1;
|
||||
if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
|
||||
switch (node_info->fragment) {
|
||||
case Fragment::JUST_0:
|
||||
|
|
@ -826,6 +916,9 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
|||
case Fragment::MULTI:
|
||||
ops += 1;
|
||||
break;
|
||||
case Fragment::MULTI_A:
|
||||
ops += node_info->keys.size() + 1;
|
||||
break;
|
||||
case Fragment::WRAP_A:
|
||||
ops += 2;
|
||||
break;
|
||||
|
|
@ -874,11 +967,11 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
|||
// Construct new NodeRef.
|
||||
NodeRef node;
|
||||
if (info.keys.empty()) {
|
||||
node = MakeNodeRef(info.fragment, std::move(sub), std::move(info.hash), info.k);
|
||||
node = MakeNodeRef(script_ctx, info.fragment, std::move(sub), std::move(info.hash), info.k);
|
||||
} else {
|
||||
assert(sub.empty());
|
||||
assert(info.hash.empty());
|
||||
node = MakeNodeRef(info.fragment, std::move(info.keys), info.k);
|
||||
node = MakeNodeRef(script_ctx, info.fragment, std::move(info.keys), info.k);
|
||||
}
|
||||
// Verify acceptability.
|
||||
if (!node || (node->GetType() & "KVWB"_mst) == ""_mst) {
|
||||
|
|
@ -894,8 +987,10 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
|||
ops += 1;
|
||||
scriptsize += 1;
|
||||
}
|
||||
if (ops > MAX_OPS_PER_SCRIPT) return {};
|
||||
if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
|
||||
if (!miniscript::IsTapscript(script_ctx) && ops > MAX_OPS_PER_SCRIPT) return {};
|
||||
if (scriptsize > miniscript::internal::MaxScriptSize(script_ctx)) {
|
||||
return {};
|
||||
}
|
||||
// Move it to the stack.
|
||||
stack.push_back(std::move(node));
|
||||
todo.pop_back();
|
||||
|
|
@ -908,12 +1003,33 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
|||
return std::move(stack[0]);
|
||||
}
|
||||
|
||||
//! The spk for this script under the given context. If it's a Taproot output also record the spend data.
|
||||
CScript ScriptPubKey(MsCtx ctx, const CScript& script, TaprootBuilder& builder)
|
||||
{
|
||||
if (!miniscript::IsTapscript(ctx)) return CScript() << OP_0 << WitnessV0ScriptHash(script);
|
||||
|
||||
// For Taproot outputs we always use a tree with a single script and a dummy internal key.
|
||||
builder.Add(0, script, TAPROOT_LEAF_TAPSCRIPT);
|
||||
builder.Finalize(XOnlyPubKey{NUMS_PK});
|
||||
return GetScriptForDestination(builder.GetOutput());
|
||||
}
|
||||
|
||||
//! Fill the witness with the data additional to the script satisfaction.
|
||||
void SatisfactionToWitness(MsCtx ctx, CScriptWitness& witness, const CScript& script, TaprootBuilder& builder) {
|
||||
// For P2WSH, it's only the witness script.
|
||||
witness.stack.push_back(std::vector<unsigned char>(script.begin(), script.end()));
|
||||
if (!miniscript::IsTapscript(ctx)) return;
|
||||
// For Tapscript we also need the control block.
|
||||
witness.stack.push_back(*builder.GetSpendData().scripts.begin()->second.begin());
|
||||
}
|
||||
|
||||
/** Perform various applicable tests on a miniscript Node. */
|
||||
void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
|
||||
void TestNode(const MsCtx script_ctx, const NodeRef& node, FuzzedDataProvider& provider)
|
||||
{
|
||||
if (!node) return;
|
||||
|
||||
// Check that it roundtrips to text representation
|
||||
PARSER_CTX.script_ctx = script_ctx;
|
||||
std::optional<std::string> str{node->ToString(PARSER_CTX)};
|
||||
assert(str);
|
||||
auto parsed = miniscript::FromString(*str, PARSER_CTX);
|
||||
|
|
@ -928,7 +1044,7 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
|
|||
// with a push of a key, which could match these opcodes).
|
||||
if (!(node->GetType() << "K"_mst)) {
|
||||
bool ends_in_verify = !(node->GetType() << "x"_mst);
|
||||
assert(ends_in_verify == (script.back() == OP_CHECKSIG || script.back() == OP_CHECKMULTISIG || script.back() == OP_EQUAL));
|
||||
assert(ends_in_verify == (script.back() == OP_CHECKSIG || script.back() == OP_CHECKMULTISIG || script.back() == OP_EQUAL || script.back() == OP_NUMEQUAL));
|
||||
}
|
||||
|
||||
// The rest of the checks only apply when testing a valid top-level script.
|
||||
|
|
@ -943,9 +1059,11 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
|
|||
assert(decoded->ToScript(PARSER_CTX) == script);
|
||||
assert(decoded->GetType() == node->GetType());
|
||||
|
||||
const auto node_ops{node->GetOps()};
|
||||
if (provider.ConsumeBool() && node_ops && *node_ops < MAX_OPS_PER_SCRIPT && node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
||||
// Optionally pad the script with OP_NOPs to max op the ops limit of the constructed script.
|
||||
// Optionally pad the script or the witness in order to increase the sensitivity of the tests of
|
||||
// the resources limits logic.
|
||||
CScriptWitness witness_mal, witness_nonmal;
|
||||
if (provider.ConsumeBool()) {
|
||||
// Under P2WSH, optionally pad the script with OP_NOPs to max op the ops limit of the constructed script.
|
||||
// This makes the script obviously not actually miniscript-compatible anymore, but the
|
||||
// signatures constructed in this test don't commit to the script anyway, so the same
|
||||
// miniscript satisfier will work. This increases the sensitivity of the test to the ops
|
||||
|
|
@ -954,31 +1072,54 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
|
|||
// maximal.
|
||||
// Do not pad more than what would cause MAX_STANDARD_P2WSH_SCRIPT_SIZE to be reached, however,
|
||||
// as that also invalidates scripts.
|
||||
int add = std::min<int>(
|
||||
MAX_OPS_PER_SCRIPT - *node_ops,
|
||||
MAX_STANDARD_P2WSH_SCRIPT_SIZE - node->ScriptSize());
|
||||
for (int i = 0; i < add; ++i) script.push_back(OP_NOP);
|
||||
const auto node_ops{node->GetOps()};
|
||||
if (!IsTapscript(script_ctx) && node_ops && *node_ops < MAX_OPS_PER_SCRIPT
|
||||
&& node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
||||
int add = std::min<int>(
|
||||
MAX_OPS_PER_SCRIPT - *node_ops,
|
||||
MAX_STANDARD_P2WSH_SCRIPT_SIZE - node->ScriptSize());
|
||||
for (int i = 0; i < add; ++i) script.push_back(OP_NOP);
|
||||
}
|
||||
|
||||
// Under Tapscript, optionally pad the stack up to the limit minus the calculated maximum execution stack
|
||||
// size to assert a Miniscript would never add more elements to the stack during execution than anticipated.
|
||||
const auto node_exec_ss{node->GetExecStackSize()};
|
||||
if (miniscript::IsTapscript(script_ctx) && node_exec_ss && *node_exec_ss < MAX_STACK_SIZE) {
|
||||
unsigned add{(unsigned)MAX_STACK_SIZE - *node_exec_ss};
|
||||
witness_mal.stack.resize(add);
|
||||
witness_nonmal.stack.resize(add);
|
||||
script.reserve(add);
|
||||
for (unsigned i = 0; i < add; ++i) script.push_back(OP_NIP);
|
||||
}
|
||||
}
|
||||
|
||||
SATISFIER_CTX.script_ctx = script_ctx;
|
||||
|
||||
// Get the ScriptPubKey for this script, filling spend data if it's Taproot.
|
||||
TaprootBuilder builder;
|
||||
const CScript script_pubkey{ScriptPubKey(script_ctx, script, builder)};
|
||||
|
||||
// Run malleable satisfaction algorithm.
|
||||
const CScript script_pubkey = CScript() << OP_0 << WitnessV0ScriptHash(script);
|
||||
CScriptWitness witness_mal;
|
||||
const bool mal_success = node->Satisfy(SATISFIER_CTX, witness_mal.stack, false) == miniscript::Availability::YES;
|
||||
witness_mal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end()));
|
||||
std::vector<std::vector<unsigned char>> stack_mal;
|
||||
const bool mal_success = node->Satisfy(SATISFIER_CTX, stack_mal, false) == miniscript::Availability::YES;
|
||||
|
||||
// Run non-malleable satisfaction algorithm.
|
||||
CScriptWitness witness_nonmal;
|
||||
const bool nonmal_success = node->Satisfy(SATISFIER_CTX, witness_nonmal.stack, true) == miniscript::Availability::YES;
|
||||
witness_nonmal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end()));
|
||||
std::vector<std::vector<unsigned char>> stack_nonmal;
|
||||
const bool nonmal_success = node->Satisfy(SATISFIER_CTX, stack_nonmal, true) == miniscript::Availability::YES;
|
||||
|
||||
if (nonmal_success) {
|
||||
// Non-malleable satisfactions are bounded by GetStackSize().
|
||||
assert(witness_nonmal.stack.size() <= *node->GetStackSize() + 1);
|
||||
// Non-malleable satisfactions are bounded by the satisfaction size plus:
|
||||
// - For P2WSH spends, the witness script
|
||||
// - For Tapscript spends, both the witness script and the control block
|
||||
const size_t max_stack_size{*node->GetStackSize() + 1 + miniscript::IsTapscript(script_ctx)};
|
||||
assert(stack_nonmal.size() <= max_stack_size);
|
||||
// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.
|
||||
assert(mal_success);
|
||||
assert(witness_nonmal.stack == witness_mal.stack);
|
||||
assert(stack_nonmal == stack_mal);
|
||||
|
||||
// Test non-malleable satisfaction.
|
||||
witness_nonmal.stack.insert(witness_nonmal.stack.end(), std::make_move_iterator(stack_nonmal.begin()), std::make_move_iterator(stack_nonmal.end()));
|
||||
SatisfactionToWitness(script_ctx, witness_nonmal, script, builder);
|
||||
ScriptError serror;
|
||||
bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_nonmal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &serror);
|
||||
// Non-malleable satisfactions are guaranteed to be valid if ValidSatisfactions().
|
||||
|
|
@ -992,6 +1133,8 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
|
|||
|
||||
if (mal_success && (!nonmal_success || witness_mal.stack != witness_nonmal.stack)) {
|
||||
// Test malleable satisfaction only if it's different from the non-malleable one.
|
||||
witness_mal.stack.insert(witness_mal.stack.end(), std::make_move_iterator(stack_mal.begin()), std::make_move_iterator(stack_mal.end()));
|
||||
SatisfactionToWitness(script_ctx, witness_mal, script, builder);
|
||||
ScriptError serror;
|
||||
bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_mal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &serror);
|
||||
// Malleable satisfactions are not guaranteed to be valid under any conditions, but they can only
|
||||
|
|
@ -1008,21 +1151,20 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
|
|||
// algorithm succeeds. Given that under IsSane() both satisfactions
|
||||
// are identical, this implies that for such nodes, the non-malleable
|
||||
// satisfaction will also match the expected policy.
|
||||
bool satisfiable = node->IsSatisfiable([](const Node& node) -> bool {
|
||||
const auto is_key_satisfiable = [script_ctx](const CPubKey& pubkey) -> bool {
|
||||
auto sig_ptr{TEST_DATA.GetSig(script_ctx, pubkey)};
|
||||
return sig_ptr != nullptr && sig_ptr->second;
|
||||
};
|
||||
bool satisfiable = node->IsSatisfiable([&](const Node& node) -> bool {
|
||||
switch (node.fragment) {
|
||||
case Fragment::PK_K:
|
||||
case Fragment::PK_H: {
|
||||
auto it = TEST_DATA.dummy_sigs.find(node.keys[0]);
|
||||
assert(it != TEST_DATA.dummy_sigs.end());
|
||||
return it->second.second;
|
||||
}
|
||||
case Fragment::MULTI: {
|
||||
size_t sats = 0;
|
||||
for (const auto& key : node.keys) {
|
||||
auto it = TEST_DATA.dummy_sigs.find(key);
|
||||
assert(it != TEST_DATA.dummy_sigs.end());
|
||||
sats += it->second.second;
|
||||
}
|
||||
case Fragment::PK_H:
|
||||
return is_key_satisfiable(node.keys[0]);
|
||||
case Fragment::MULTI:
|
||||
case Fragment::MULTI_A: {
|
||||
size_t sats = std::count_if(node.keys.begin(), node.keys.end(), [&](const auto& key) {
|
||||
return size_t(is_key_satisfiable(key));
|
||||
});
|
||||
return sats >= node.k;
|
||||
}
|
||||
case Fragment::OLDER:
|
||||
|
|
@ -1061,10 +1203,13 @@ void FuzzInitSmart()
|
|||
/** Fuzz target that runs TestNode on nodes generated using ConsumeNodeStable. */
|
||||
FUZZ_TARGET(miniscript_stable, .init = FuzzInit)
|
||||
{
|
||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||
TestNode(GenNode([&](Type needed_type) {
|
||||
return ConsumeNodeStable(provider, needed_type);
|
||||
}, ""_mst), provider);
|
||||
// Run it under both P2WSH and Tapscript contexts.
|
||||
for (const auto script_ctx: {MsCtx::P2WSH, MsCtx::TAPSCRIPT}) {
|
||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||
TestNode(script_ctx, GenNode(script_ctx, [&](Type needed_type) {
|
||||
return ConsumeNodeStable(script_ctx, provider, needed_type);
|
||||
}, ""_mst), provider);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fuzz target that runs TestNode on nodes generated using ConsumeNodeSmart. */
|
||||
|
|
@ -1074,16 +1219,19 @@ FUZZ_TARGET(miniscript_smart, .init = FuzzInitSmart)
|
|||
static constexpr std::array<Type, 4> BASE_TYPES{"B"_mst, "V"_mst, "K"_mst, "W"_mst};
|
||||
|
||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||
TestNode(GenNode([&](Type needed_type) {
|
||||
return ConsumeNodeSmart(provider, needed_type);
|
||||
const auto script_ctx{(MsCtx)provider.ConsumeBool()};
|
||||
TestNode(script_ctx, GenNode(script_ctx, [&](Type needed_type) {
|
||||
return ConsumeNodeSmart(script_ctx, provider, needed_type);
|
||||
}, PickValue(provider, BASE_TYPES), true), provider);
|
||||
}
|
||||
|
||||
/* Fuzz tests that test parsing from a string, and roundtripping via string. */
|
||||
FUZZ_TARGET(miniscript_string, .init = FuzzInit)
|
||||
{
|
||||
if (buffer.empty()) return;
|
||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||
auto str = provider.ConsumeRemainingBytesAsString();
|
||||
auto str = provider.ConsumeBytesAsString(provider.remaining_bytes() - 1);
|
||||
PARSER_CTX.script_ctx = (MsCtx)provider.ConsumeBool();
|
||||
auto parsed = miniscript::FromString(str, PARSER_CTX);
|
||||
if (!parsed) return;
|
||||
|
||||
|
|
@ -1101,6 +1249,7 @@ FUZZ_TARGET(miniscript_script)
|
|||
const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
|
||||
if (!script) return;
|
||||
|
||||
SCRIPT_PARSER_CONTEXT.script_ctx = (MsCtx)fuzzed_data_provider.ConsumeBool();
|
||||
const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT);
|
||||
if (!ms) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <script/interpreter.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script_error.h>
|
||||
#include <script/signingprovider.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -30,7 +31,9 @@ struct TestData {
|
|||
//! A map from the public keys to their CKeyIDs (faster than hashing every time).
|
||||
std::map<CPubKey, CKeyID> pkhashes;
|
||||
std::map<CKeyID, CPubKey> pkmap;
|
||||
std::map<XOnlyPubKey, CKeyID> xonly_pkhashes;
|
||||
std::map<CPubKey, std::vector<unsigned char>> signatures;
|
||||
std::map<XOnlyPubKey, std::vector<unsigned char>> schnorr_signatures;
|
||||
|
||||
// Various precomputed hashes
|
||||
std::vector<std::vector<unsigned char>> sha256;
|
||||
|
|
@ -46,6 +49,9 @@ struct TestData {
|
|||
{
|
||||
// All our signatures sign (and are required to sign) this constant message.
|
||||
auto const MESSAGE_HASH = uint256S("f5cd94e18b6fe77dd7aca9e35c2b0c9cbd86356c80a71065");
|
||||
// We don't pass additional randomness when creating a schnorr signature.
|
||||
auto const EMPTY_AUX{uint256S("")};
|
||||
|
||||
// We generate 255 public keys and 255 hashes of each type.
|
||||
for (int i = 1; i <= 255; ++i) {
|
||||
// This 32-byte array functions as both private key data and hash preimage (31 zero bytes plus any nonzero byte).
|
||||
|
|
@ -60,12 +66,18 @@ struct TestData {
|
|||
pubkeys.push_back(pubkey);
|
||||
pkhashes.emplace(pubkey, keyid);
|
||||
pkmap.emplace(keyid, pubkey);
|
||||
XOnlyPubKey xonly_pubkey{pubkey};
|
||||
uint160 xonly_hash{Hash160(xonly_pubkey)};
|
||||
xonly_pkhashes.emplace(xonly_pubkey, xonly_hash);
|
||||
pkmap.emplace(xonly_hash, pubkey);
|
||||
|
||||
// Compute ECDSA signatures on MESSAGE_HASH with the private keys.
|
||||
std::vector<unsigned char> sig;
|
||||
std::vector<unsigned char> sig, schnorr_sig(64);
|
||||
BOOST_CHECK(key.Sign(MESSAGE_HASH, sig));
|
||||
sig.push_back(1); // sighash byte
|
||||
signatures.emplace(pubkey, sig);
|
||||
BOOST_CHECK(key.SignSchnorr(MESSAGE_HASH, schnorr_sig, nullptr, EMPTY_AUX));
|
||||
schnorr_signatures.emplace(XOnlyPubKey{pubkey}, schnorr_sig);
|
||||
|
||||
// Compute various hashes
|
||||
std::vector<unsigned char> hash;
|
||||
|
|
@ -114,19 +126,30 @@ typedef std::pair<ChallengeType, uint32_t> Challenge;
|
|||
struct KeyConverter {
|
||||
typedef CPubKey Key;
|
||||
|
||||
miniscript::MiniscriptContext m_script_ctx{miniscript::MiniscriptContext::P2WSH};
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return a < b;
|
||||
}
|
||||
|
||||
//! Convert a public key to bytes.
|
||||
std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; }
|
||||
std::vector<unsigned char> ToPKBytes(const CPubKey& key) const {
|
||||
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||
return {key.begin(), key.end()};
|
||||
}
|
||||
const XOnlyPubKey xonly_pubkey{key};
|
||||
return {xonly_pubkey.begin(), xonly_pubkey.end()};
|
||||
}
|
||||
|
||||
//! Convert a public key to its Hash160 bytes (precomputed).
|
||||
std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const
|
||||
{
|
||||
auto it = g_testdata->pkhashes.find(key);
|
||||
assert(it != g_testdata->pkhashes.end());
|
||||
return {it->second.begin(), it->second.end()};
|
||||
std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const {
|
||||
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||
auto hash = g_testdata->pkhashes.at(key);
|
||||
return {hash.begin(), hash.end()};
|
||||
}
|
||||
const XOnlyPubKey xonly_key{key};
|
||||
auto hash = g_testdata->xonly_pkhashes.at(xonly_key);
|
||||
return {hash.begin(), hash.end()};
|
||||
}
|
||||
|
||||
//! Parse a public key from a range of hex characters.
|
||||
|
|
@ -140,9 +163,15 @@ struct KeyConverter {
|
|||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKBytes(I first, I last) const {
|
||||
Key key{first, last};
|
||||
if (key.IsValid()) return key;
|
||||
return {};
|
||||
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||
Key key{first, last};
|
||||
if (key.IsValid()) return key;
|
||||
return {};
|
||||
}
|
||||
if (last - first != 32) return {};
|
||||
XOnlyPubKey xonly_pubkey;
|
||||
std::copy(first, last, xonly_pubkey.begin());
|
||||
return xonly_pubkey.GetEvenCorrespondingCPubKey();
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
|
|
@ -150,14 +179,20 @@ struct KeyConverter {
|
|||
assert(last - first == 20);
|
||||
CKeyID keyid;
|
||||
std::copy(first, last, keyid.begin());
|
||||
auto it = g_testdata->pkmap.find(keyid);
|
||||
assert(it != g_testdata->pkmap.end());
|
||||
return it->second;
|
||||
return g_testdata->pkmap.at(keyid);
|
||||
}
|
||||
|
||||
std::optional<std::string> ToString(const Key& key) const {
|
||||
return HexStr(ToPKBytes(key));
|
||||
}
|
||||
|
||||
miniscript::MiniscriptContext MsContext() const {
|
||||
return m_script_ctx;
|
||||
}
|
||||
|
||||
void SetContext(miniscript::MiniscriptContext ctx) {
|
||||
m_script_ctx = ctx;
|
||||
}
|
||||
};
|
||||
|
||||
/** A class that encapsulates all signing/hash revealing operations. */
|
||||
|
|
@ -178,9 +213,15 @@ struct Satisfier : public KeyConverter {
|
|||
//! Produce a signature for the given key.
|
||||
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
|
||||
if (supported.count(Challenge(ChallengeType::PK, ChallengeNumber(key)))) {
|
||||
auto it = g_testdata->signatures.find(key);
|
||||
if (it == g_testdata->signatures.end()) return miniscript::Availability::NO;
|
||||
sig = it->second;
|
||||
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||
auto it = g_testdata->signatures.find(key);
|
||||
if (it == g_testdata->signatures.end()) return miniscript::Availability::NO;
|
||||
sig = it->second;
|
||||
} else {
|
||||
auto it = g_testdata->schnorr_signatures.find(XOnlyPubKey{key});
|
||||
if (it == g_testdata->schnorr_signatures.end()) return miniscript::Availability::NO;
|
||||
sig = it->second;
|
||||
}
|
||||
return miniscript::Availability::YES;
|
||||
}
|
||||
return miniscript::Availability::NO;
|
||||
|
|
@ -226,6 +267,14 @@ public:
|
|||
return sig == it->second;
|
||||
}
|
||||
|
||||
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion,
|
||||
ScriptExecutionData&, ScriptError*) const override {
|
||||
XOnlyPubKey pk{pubkey};
|
||||
auto it = g_testdata->schnorr_signatures.find(pk);
|
||||
if (it == g_testdata->schnorr_signatures.end()) return false;
|
||||
return sig == it->second;
|
||||
}
|
||||
|
||||
bool CheckLockTime(const CScriptNum& locktime) const override {
|
||||
// Delegate to Satisfier.
|
||||
return ctx.CheckAfter(locktime.GetInt64());
|
||||
|
|
@ -238,7 +287,10 @@ public:
|
|||
};
|
||||
|
||||
//! Singleton instance of KeyConverter.
|
||||
const KeyConverter CONVERTER{};
|
||||
KeyConverter CONVERTER;
|
||||
|
||||
//! Public key to be used as internal key for dummy Taproot spends.
|
||||
const std::vector<unsigned char> NUMS_PK{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")};
|
||||
|
||||
using Fragment = miniscript::Fragment;
|
||||
using NodeRef = miniscript::NodeRef<CPubKey>;
|
||||
|
|
@ -271,34 +323,61 @@ std::set<Challenge> FindChallenges(const NodeRef& ref) {
|
|||
return chal;
|
||||
}
|
||||
|
||||
//! The spk for this script under the given context. If it's a Taproot output also record the spend data.
|
||||
CScript ScriptPubKey(miniscript::MiniscriptContext ctx, const CScript& script, TaprootBuilder& builder)
|
||||
{
|
||||
if (!miniscript::IsTapscript(ctx)) return CScript() << OP_0 << WitnessV0ScriptHash(script);
|
||||
|
||||
// For Taproot outputs we always use a tree with a single script and a dummy internal key.
|
||||
builder.Add(0, script, TAPROOT_LEAF_TAPSCRIPT);
|
||||
builder.Finalize(XOnlyPubKey{NUMS_PK});
|
||||
return GetScriptForDestination(builder.GetOutput());
|
||||
}
|
||||
|
||||
//! Fill the witness with the data additional to the script satisfaction.
|
||||
void SatisfactionToWitness(miniscript::MiniscriptContext ctx, CScriptWitness& witness, const CScript& script, TaprootBuilder& builder) {
|
||||
// For P2WSH, it's only the witness script.
|
||||
witness.stack.push_back(std::vector<unsigned char>(script.begin(), script.end()));
|
||||
if (!miniscript::IsTapscript(ctx)) return;
|
||||
// For Tapscript we also need the control block.
|
||||
witness.stack.push_back(*builder.GetSpendData().scripts.begin()->second.begin());
|
||||
}
|
||||
|
||||
/** Run random satisfaction tests. */
|
||||
void TestSatisfy(const std::string& testcase, const NodeRef& node) {
|
||||
auto script = node->ToScript(CONVERTER);
|
||||
void TestSatisfy(const KeyConverter& converter, const std::string& testcase, const NodeRef& node) {
|
||||
auto script = node->ToScript(converter);
|
||||
auto challenges = FindChallenges(node); // Find all challenges in the generated miniscript.
|
||||
std::vector<Challenge> challist(challenges.begin(), challenges.end());
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
Shuffle(challist.begin(), challist.end(), g_insecure_rand_ctx);
|
||||
Satisfier satisfier;
|
||||
satisfier.SetContext(converter.MsContext());
|
||||
TestSignatureChecker checker(satisfier);
|
||||
bool prev_mal_success = false, prev_nonmal_success = false;
|
||||
// Go over all challenges involved in this miniscript in random order.
|
||||
for (int add = -1; add < (int)challist.size(); ++add) {
|
||||
if (add >= 0) satisfier.supported.insert(challist[add]); // The first iteration does not add anything
|
||||
|
||||
// Get the ScriptPubKey for this script, filling spend data if it's Taproot.
|
||||
TaprootBuilder builder;
|
||||
const CScript script_pubkey{ScriptPubKey(converter.MsContext(), script, builder)};
|
||||
|
||||
// Run malleable satisfaction algorithm.
|
||||
const CScript script_pubkey = CScript() << OP_0 << WitnessV0ScriptHash(script);
|
||||
CScriptWitness witness_mal;
|
||||
const bool mal_success = node->Satisfy(satisfier, witness_mal.stack, false) == miniscript::Availability::YES;
|
||||
witness_mal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end()));
|
||||
SatisfactionToWitness(converter.MsContext(), witness_mal, script, builder);
|
||||
|
||||
// Run non-malleable satisfaction algorithm.
|
||||
CScriptWitness witness_nonmal;
|
||||
const bool nonmal_success = node->Satisfy(satisfier, witness_nonmal.stack, true) == miniscript::Availability::YES;
|
||||
witness_nonmal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end()));
|
||||
SatisfactionToWitness(converter.MsContext(), witness_nonmal, script, builder);
|
||||
|
||||
if (nonmal_success) {
|
||||
// Non-malleable satisfactions are bounded by GetStackSize().
|
||||
BOOST_CHECK(witness_nonmal.stack.size() <= *node->GetStackSize() + 1);
|
||||
// Non-malleable satisfactions are bounded by the satisfaction size plus:
|
||||
// - For P2WSH spends, the witness script
|
||||
// - For Tapscript spends, both the witness script and the control block
|
||||
const size_t max_stack_size{*node->GetStackSize() + 1 + miniscript::IsTapscript(converter.MsContext())};
|
||||
BOOST_CHECK(witness_nonmal.stack.size() <= max_stack_size);
|
||||
// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.
|
||||
BOOST_CHECK(mal_success);
|
||||
BOOST_CHECK(witness_nonmal.stack == witness_mal.stack);
|
||||
|
|
@ -351,37 +430,57 @@ void TestSatisfy(const std::string& testcase, const NodeRef& node) {
|
|||
}
|
||||
|
||||
enum TestMode : int {
|
||||
//! Invalid under any context
|
||||
TESTMODE_INVALID = 0,
|
||||
//! Valid under any context unless overridden
|
||||
TESTMODE_VALID = 1,
|
||||
TESTMODE_NONMAL = 2,
|
||||
TESTMODE_NEEDSIG = 4,
|
||||
TESTMODE_TIMELOCKMIX = 8
|
||||
TESTMODE_TIMELOCKMIX = 8,
|
||||
//! Invalid only under P2WSH context
|
||||
TESTMODE_P2WSH_INVALID = 16,
|
||||
//! Invalid only under Tapscript context
|
||||
TESTMODE_TAPSCRIPT_INVALID = 32,
|
||||
};
|
||||
|
||||
void Test(const std::string& ms, const std::string& hexscript, int mode, int opslimit = -1, int stacklimit = -1, std::optional<uint32_t> max_wit_size = std::nullopt)
|
||||
void Test(const std::string& ms, const std::string& hexscript, int mode, const KeyConverter& converter,
|
||||
int opslimit = -1, int stacklimit = -1, std::optional<uint32_t> max_wit_size = std::nullopt,
|
||||
std::optional<uint32_t> stack_exec = {})
|
||||
{
|
||||
auto node = miniscript::FromString(ms, CONVERTER);
|
||||
if (mode == TESTMODE_INVALID) {
|
||||
auto node = miniscript::FromString(ms, converter);
|
||||
const bool is_tapscript{miniscript::IsTapscript(converter.MsContext())};
|
||||
if (mode == TESTMODE_INVALID || ((mode & TESTMODE_P2WSH_INVALID) && !is_tapscript) || ((mode & TESTMODE_TAPSCRIPT_INVALID) && is_tapscript)) {
|
||||
BOOST_CHECK_MESSAGE(!node || !node->IsValid(), "Unexpectedly valid: " + ms);
|
||||
} else {
|
||||
BOOST_CHECK_MESSAGE(node, "Unparseable: " + ms);
|
||||
BOOST_CHECK_MESSAGE(node->IsValid(), "Invalid: " + ms);
|
||||
BOOST_CHECK_MESSAGE(node->IsValidTopLevel(), "Invalid top level: " + ms);
|
||||
auto computed_script = node->ToScript(CONVERTER);
|
||||
auto computed_script = node->ToScript(converter);
|
||||
BOOST_CHECK_MESSAGE(node->ScriptSize() == computed_script.size(), "Script size mismatch: " + ms);
|
||||
if (hexscript != "?") BOOST_CHECK_MESSAGE(HexStr(computed_script) == hexscript, "Script mismatch: " + ms + " (" + HexStr(computed_script) + " vs " + hexscript + ")");
|
||||
BOOST_CHECK_MESSAGE(node->IsNonMalleable() == !!(mode & TESTMODE_NONMAL), "Malleability mismatch: " + ms);
|
||||
BOOST_CHECK_MESSAGE(node->NeedsSignature() == !!(mode & TESTMODE_NEEDSIG), "Signature necessity mismatch: " + ms);
|
||||
BOOST_CHECK_MESSAGE((node->GetType() << "k"_mst) == !(mode & TESTMODE_TIMELOCKMIX), "Timelock mix mismatch: " + ms);
|
||||
auto inferred_miniscript = miniscript::FromScript(computed_script, CONVERTER);
|
||||
auto inferred_miniscript = miniscript::FromScript(computed_script, converter);
|
||||
BOOST_CHECK_MESSAGE(inferred_miniscript, "Cannot infer miniscript from script: " + ms);
|
||||
BOOST_CHECK_MESSAGE(inferred_miniscript->ToScript(CONVERTER) == computed_script, "Roundtrip failure: miniscript->script != miniscript->script->miniscript->script: " + ms);
|
||||
BOOST_CHECK_MESSAGE(inferred_miniscript->ToScript(converter) == computed_script, "Roundtrip failure: miniscript->script != miniscript->script->miniscript->script: " + ms);
|
||||
if (opslimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetOps() == opslimit, "Ops limit mismatch: " << ms << " (" << *node->GetOps() << " vs " << opslimit << ")");
|
||||
if (stacklimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetStackSize() == stacklimit, "Stack limit mismatch: " << ms << " (" << *node->GetStackSize() << " vs " << stacklimit << ")");
|
||||
if (max_wit_size) BOOST_CHECK_MESSAGE(*node->GetWitnessSize() == *max_wit_size, "Witness size limit mismatch: " << ms << " (" << *node->GetWitnessSize() << " vs " << *max_wit_size << ")");
|
||||
TestSatisfy(ms, node);
|
||||
if (stack_exec) BOOST_CHECK_MESSAGE(*node->GetExecStackSize() == *stack_exec, "Stack execution limit mismatch: " << ms << " (" << *node->GetExecStackSize() << " vs " << *stack_exec << ")");
|
||||
TestSatisfy(converter, ms, node);
|
||||
}
|
||||
}
|
||||
|
||||
void Test(const std::string& ms, const std::string& hexscript, const std::string& hextapscript, int mode,
|
||||
int opslimit = -1, int stacklimit = -1, std::optional<uint32_t> max_wit_size = std::nullopt,
|
||||
std::optional<uint32_t> stack_exec = {})
|
||||
{
|
||||
CONVERTER.SetContext(miniscript::MiniscriptContext::P2WSH);
|
||||
Test(ms, hexscript, mode, CONVERTER, opslimit, stacklimit, max_wit_size, stack_exec);
|
||||
CONVERTER.SetContext(miniscript::MiniscriptContext::TAPSCRIPT);
|
||||
Test(ms, hextapscript == "=" ? hexscript : hextapscript, mode, CONVERTER, opslimit, stacklimit, max_wit_size, stack_exec);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(miniscript_tests, BasicTestingSetup)
|
||||
|
|
@ -391,94 +490,146 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
|
|||
g_testdata.reset(new TestData());
|
||||
|
||||
// Validity rules
|
||||
Test("l:older(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(1): valid
|
||||
Test("l:older(0)", "?", TESTMODE_INVALID); // older(0): k must be at least 1
|
||||
Test("l:older(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(2147483647): valid
|
||||
Test("l:older(2147483648)", "?", TESTMODE_INVALID); // older(2147483648): k must be below 2^31
|
||||
Test("u:after(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(1): valid
|
||||
Test("u:after(0)", "?", TESTMODE_INVALID); // after(0): k must be at least 1
|
||||
Test("u:after(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(2147483647): valid
|
||||
Test("u:after(2147483648)", "?", TESTMODE_INVALID); // after(2147483648): k must be below 2^31
|
||||
Test("andor(0,1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,B,B): valid
|
||||
Test("andor(a:0,1,1)", "?", TESTMODE_INVALID); // andor(Wdu,B,B): X must be B
|
||||
Test("andor(0,a:1,a:1)", "?", TESTMODE_INVALID); // andor(Bdu,W,W): Y and Z must be B/V/K
|
||||
Test("andor(1,1,1)", "?", TESTMODE_INVALID); // andor(Bu,B,B): X must be d
|
||||
Test("andor(n:or_i(0,after(1)),1,1)", "?", TESTMODE_VALID); // andor(Bdu,B,B): valid
|
||||
Test("andor(or_i(0,after(1)),1,1)", "?", TESTMODE_INVALID); // andor(Bd,B,B): X must be u
|
||||
Test("c:andor(0,pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // andor(Bdu,K,K): valid
|
||||
Test("t:andor(0,v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,V,V): valid
|
||||
Test("and_v(v:1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,B): valid
|
||||
Test("t:and_v(v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,V): valid
|
||||
Test("c:and_v(v:1,pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // and_v(V,K): valid
|
||||
Test("and_v(1,1)", "?", TESTMODE_INVALID); // and_v(B,B): X must be V
|
||||
Test("and_v(pk_k(02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),1)", "?", TESTMODE_INVALID); // and_v(K,B): X must be V
|
||||
Test("and_v(v:1,a:1)", "?", TESTMODE_INVALID); // and_v(K,W): Y must be B/V/K
|
||||
Test("and_b(1,a:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_b(B,W): valid
|
||||
Test("and_b(1,1)", "?", TESTMODE_INVALID); // and_b(B,B): Y must W
|
||||
Test("and_b(v:1,a:1)", "?", TESTMODE_INVALID); // and_b(V,W): X must be B
|
||||
Test("and_b(a:1,a:1)", "?", TESTMODE_INVALID); // and_b(W,W): X must be B
|
||||
Test("and_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:1)", "?", TESTMODE_INVALID); // and_b(K,W): X must be B
|
||||
Test("or_b(0,a:0)", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_b(Bd,Wd): valid
|
||||
Test("or_b(1,a:0)", "?", TESTMODE_INVALID); // or_b(B,Wd): X must be d
|
||||
Test("or_b(0,a:1)", "?", TESTMODE_INVALID); // or_b(Bd,W): Y must be d
|
||||
Test("or_b(0,0)", "?", TESTMODE_INVALID); // or_b(Bd,Bd): Y must W
|
||||
Test("or_b(v:0,a:0)", "?", TESTMODE_INVALID); // or_b(V,Wd): X must be B
|
||||
Test("or_b(a:0,a:0)", "?", TESTMODE_INVALID); // or_b(Wd,Wd): X must be B
|
||||
Test("or_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:0)", "?", TESTMODE_INVALID); // or_b(Kd,Wd): X must be B
|
||||
Test("t:or_c(0,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_c(Bdu,V): valid
|
||||
Test("t:or_c(a:0,v:1)", "?", TESTMODE_INVALID); // or_c(Wdu,V): X must be B
|
||||
Test("t:or_c(1,v:1)", "?", TESTMODE_INVALID); // or_c(Bu,V): X must be d
|
||||
Test("t:or_c(n:or_i(0,after(1)),v:1)", "?", TESTMODE_VALID); // or_c(Bdu,V): valid
|
||||
Test("t:or_c(or_i(0,after(1)),v:1)", "?", TESTMODE_INVALID); // or_c(Bd,V): X must be u
|
||||
Test("t:or_c(0,1)", "?", TESTMODE_INVALID); // or_c(Bdu,B): Y must be V
|
||||
Test("or_d(0,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_d(Bdu,B): valid
|
||||
Test("or_d(a:0,1)", "?", TESTMODE_INVALID); // or_d(Wdu,B): X must be B
|
||||
Test("or_d(1,1)", "?", TESTMODE_INVALID); // or_d(Bu,B): X must be d
|
||||
Test("or_d(n:or_i(0,after(1)),1)", "?", TESTMODE_VALID); // or_d(Bdu,B): valid
|
||||
Test("or_d(or_i(0,after(1)),1)", "?", TESTMODE_INVALID); // or_d(Bd,B): X must be u
|
||||
Test("or_d(0,v:1)", "?", TESTMODE_INVALID); // or_d(Bdu,V): Y must be B
|
||||
Test("or_i(1,1)", "?", TESTMODE_VALID); // or_i(B,B): valid
|
||||
Test("t:or_i(v:1,v:1)", "?", TESTMODE_VALID); // or_i(V,V): valid
|
||||
Test("c:or_i(pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_i(K,K): valid
|
||||
Test("or_i(a:1,a:1)", "?", TESTMODE_INVALID); // or_i(W,W): X and Y must be B/V/K
|
||||
Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid
|
||||
Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid
|
||||
Test("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_k
|
||||
Test("pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "76a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_h
|
||||
Test("l:older(1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(1): valid
|
||||
Test("l:older(0)", "?", "?", TESTMODE_INVALID); // older(0): k must be at least 1
|
||||
Test("l:older(2147483647)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(2147483647): valid
|
||||
Test("l:older(2147483648)", "?", "?", TESTMODE_INVALID); // older(2147483648): k must be below 2^31
|
||||
Test("u:after(1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(1): valid
|
||||
Test("u:after(0)", "?", "?", TESTMODE_INVALID); // after(0): k must be at least 1
|
||||
Test("u:after(2147483647)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(2147483647): valid
|
||||
Test("u:after(2147483648)", "?", "?", TESTMODE_INVALID); // after(2147483648): k must be below 2^31
|
||||
Test("andor(0,1,1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,B,B): valid
|
||||
Test("andor(a:0,1,1)", "?", "?", TESTMODE_INVALID); // andor(Wdu,B,B): X must be B
|
||||
Test("andor(0,a:1,a:1)", "?", "?", TESTMODE_INVALID); // andor(Bdu,W,W): Y and Z must be B/V/K
|
||||
Test("andor(1,1,1)", "?", "?", TESTMODE_INVALID); // andor(Bu,B,B): X must be d
|
||||
Test("andor(n:or_i(0,after(1)),1,1)", "?", "?", TESTMODE_VALID); // andor(Bdu,B,B): valid
|
||||
Test("andor(or_i(0,after(1)),1,1)", "?", "?", TESTMODE_INVALID); // andor(Bd,B,B): X must be u
|
||||
Test("c:andor(0,pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // andor(Bdu,K,K): valid
|
||||
Test("t:andor(0,v:1,v:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,V,V): valid
|
||||
Test("and_v(v:1,1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,B): valid
|
||||
Test("t:and_v(v:1,v:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,V): valid
|
||||
Test("c:and_v(v:1,pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // and_v(V,K): valid
|
||||
Test("and_v(1,1)", "?", "?", TESTMODE_INVALID); // and_v(B,B): X must be V
|
||||
Test("and_v(pk_k(02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),1)", "?", "?", TESTMODE_INVALID); // and_v(K,B): X must be V
|
||||
Test("and_v(v:1,a:1)", "?", "?", TESTMODE_INVALID); // and_v(K,W): Y must be B/V/K
|
||||
Test("and_b(1,a:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_b(B,W): valid
|
||||
Test("and_b(1,1)", "?", "?", TESTMODE_INVALID); // and_b(B,B): Y must W
|
||||
Test("and_b(v:1,a:1)", "?", "?", TESTMODE_INVALID); // and_b(V,W): X must be B
|
||||
Test("and_b(a:1,a:1)", "?", "?", TESTMODE_INVALID); // and_b(W,W): X must be B
|
||||
Test("and_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:1)", "?", "?", TESTMODE_INVALID); // and_b(K,W): X must be B
|
||||
Test("or_b(0,a:0)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_b(Bd,Wd): valid
|
||||
Test("or_b(1,a:0)", "?", "?", TESTMODE_INVALID); // or_b(B,Wd): X must be d
|
||||
Test("or_b(0,a:1)", "?", "?", TESTMODE_INVALID); // or_b(Bd,W): Y must be d
|
||||
Test("or_b(0,0)", "?", "?", TESTMODE_INVALID); // or_b(Bd,Bd): Y must W
|
||||
Test("or_b(v:0,a:0)", "?", "?", TESTMODE_INVALID); // or_b(V,Wd): X must be B
|
||||
Test("or_b(a:0,a:0)", "?", "?", TESTMODE_INVALID); // or_b(Wd,Wd): X must be B
|
||||
Test("or_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:0)", "?", "?", TESTMODE_INVALID); // or_b(Kd,Wd): X must be B
|
||||
Test("t:or_c(0,v:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_c(Bdu,V): valid
|
||||
Test("t:or_c(a:0,v:1)", "?", "?", TESTMODE_INVALID); // or_c(Wdu,V): X must be B
|
||||
Test("t:or_c(1,v:1)", "?", "?", TESTMODE_INVALID); // or_c(Bu,V): X must be d
|
||||
Test("t:or_c(n:or_i(0,after(1)),v:1)", "?", "?", TESTMODE_VALID); // or_c(Bdu,V): valid
|
||||
Test("t:or_c(or_i(0,after(1)),v:1)", "?", "?", TESTMODE_INVALID); // or_c(Bd,V): X must be u
|
||||
Test("t:or_c(0,1)", "?", "?", TESTMODE_INVALID); // or_c(Bdu,B): Y must be V
|
||||
Test("or_d(0,1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_d(Bdu,B): valid
|
||||
Test("or_d(a:0,1)", "?", "?", TESTMODE_INVALID); // or_d(Wdu,B): X must be B
|
||||
Test("or_d(1,1)", "?", "?", TESTMODE_INVALID); // or_d(Bu,B): X must be d
|
||||
Test("or_d(n:or_i(0,after(1)),1)", "?", "?", TESTMODE_VALID); // or_d(Bdu,B): valid
|
||||
Test("or_d(or_i(0,after(1)),1)", "?", "?", TESTMODE_INVALID); // or_d(Bd,B): X must be u
|
||||
Test("or_d(0,v:1)", "?", "?", TESTMODE_INVALID); // or_d(Bdu,V): Y must be B
|
||||
Test("or_i(1,1)", "?", "?", TESTMODE_VALID); // or_i(B,B): valid
|
||||
Test("t:or_i(v:1,v:1)", "?", "?", TESTMODE_VALID); // or_i(V,V): valid
|
||||
Test("c:or_i(pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_i(K,K): valid
|
||||
Test("or_i(a:1,a:1)", "?", "?", TESTMODE_INVALID); // or_i(W,W): X and Y must be B/V/K
|
||||
Test("or_b(l:after(100),al:after(1000000000))", "?", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid
|
||||
Test("and_b(after(100),a:after(1000000000))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid
|
||||
Test("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_k
|
||||
Test("pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "76a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac", "76a914fd1690c37fa3b0f04395ddc9415b220ab1ccc59588ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_h
|
||||
|
||||
|
||||
// Randomly generated test set that covers the majority of type and node type combinations
|
||||
Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 3);
|
||||
Test("uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 14, 5, 2 + 2 + 1 + 2 * 73);
|
||||
Test("or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", TESTMODE_VALID, 14, 5, 2 + 1 + 2 * 73 + 2);
|
||||
Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", TESTMODE_VALID | TESTMODE_NONMAL, 11, 1, 2);
|
||||
Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 2 + 33 + 33);
|
||||
Test("t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", TESTMODE_VALID | TESTMODE_NONMAL, 13, 5, 1 + 3 * 73);
|
||||
Test("or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", TESTMODE_VALID | TESTMODE_NONMAL, 15, 7, 2 + 1 + 3 * 73 + 1);
|
||||
Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 1, 33);
|
||||
Test("and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 11, 5, 2 + 1 + 2 * 73 + 33);
|
||||
Test("j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", TESTMODE_VALID | TESTMODE_NEEDSIG, 14, 4, 1 + 2 * 73 + 2);
|
||||
Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", TESTMODE_VALID, 12, 1, 33);
|
||||
Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", TESTMODE_VALID, 16, 2, 33 + 33);
|
||||
Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", TESTMODE_VALID | TESTMODE_NONMAL, 15, 2, 33 + 33);
|
||||
Test("thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 13, 6, 1 + 2 * 73 + 1 + 73 + 1);
|
||||
Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", TESTMODE_VALID, 14, 2, 33 + 2);
|
||||
Test("or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b2696892736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", TESTMODE_VALID, 15, 2, 1 + 33);
|
||||
Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", TESTMODE_VALID | TESTMODE_NEEDSIG, 8, 2, 33 + 73);
|
||||
Test("c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 10, 5, 1 + 2 * 73 + 73);
|
||||
Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", TESTMODE_VALID, 14, 2, 33 + 33);
|
||||
Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", TESTMODE_VALID, 20, 2, 33 + 33);
|
||||
Test("or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 2, 2 + 73);
|
||||
Test("thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", TESTMODE_VALID, 18, 4, 1 + 34 + 33 + 33);
|
||||
Test("and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 3, 33 + 2 + 73);
|
||||
Test("and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 2, 73 + 1);
|
||||
Test("c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 3, 2 + 34 + 73);
|
||||
Test("or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", TESTMODE_VALID | TESTMODE_NONMAL, 13, 3, 1 + 34 + 73);
|
||||
Test("c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 3, 33 + 34 + 73);
|
||||
Test("c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 4, 2 + 33 + 34 + 73);
|
||||
Test("c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 5, 2 + 34 + 73 + 34 + 73);
|
||||
Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 3, 73 + 2 + 2);
|
||||
Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 4, 73 + 73 + 2 + 2);
|
||||
Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", "=", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 3, 3);
|
||||
Test("uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 5, 2 + 2 + 1 + 2 * 73, 7);
|
||||
Test("or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", "?", TESTMODE_VALID | TESTMODE_TAPSCRIPT_INVALID, 14, 5, 2 + 1 + 2 * 73 + 2, 8);
|
||||
Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", "=", TESTMODE_VALID | TESTMODE_NONMAL, 11, 1, 2, 2);
|
||||
Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 2 + 33 + 33, 4);
|
||||
Test("t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 13, 5, 1 + 3 * 73, 10);
|
||||
Test("or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 15, 7, 2 + 1 + 3 * 73 + 1, 10);
|
||||
Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 1, 33, 3);
|
||||
Test("and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 11, 5, 2 + 1 + 2 * 73 + 33, 8);
|
||||
Test("j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", "?", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 4, 1 + 2 * 73 + 2, 8);
|
||||
Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", "=", TESTMODE_VALID, 12, 1, 33, 4);
|
||||
Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", "=", TESTMODE_VALID, 16, 2, 33 + 33, 4);
|
||||
Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", "=", TESTMODE_VALID | TESTMODE_NONMAL, 15, 2, 33 + 33, 4);
|
||||
Test("thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 13, 6, 1 + 2 * 73 + 1 + 73 + 1, 10);
|
||||
Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", "=", TESTMODE_VALID, 14, 2, 33 + 2, 4);
|
||||
Test("or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b2696892736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", "=", TESTMODE_VALID, 15, 2, 1 + 33, 3);
|
||||
Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", "?", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 8, 2, 33 + 73, 4);
|
||||
Test("c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 10, 5, 1 + 2 * 73 + 73, 9);
|
||||
Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", "=", TESTMODE_VALID, 14, 2, 33 + 33, 4);
|
||||
Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", "=", TESTMODE_VALID, 20, 2, 33 + 33, 4);
|
||||
Test("or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", "630320a107b16920c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 2, 2 + 73, 3);
|
||||
Test("thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", "76a9141a7ac36cfa8431ab2395d701b0050045ae4a37d188ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", TESTMODE_VALID, 18, 4, 1 + 34 + 33 + 33, 6);
|
||||
Test("and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b26920fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 3, 33 + 2 + 73, 5);
|
||||
Test("and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", "20daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 2, 73 + 1, 3);
|
||||
Test("c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", "6360b26976a9144d4421361c3289bdad06441ffaee8be8e786f1ad886776a91460d4a7bcbd08f58e58bd208d1069837d7adb16ae8868ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 3, 2 + 34 + 73, 4);
|
||||
Test("or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", "76a91421ab1a140d0d305b8ff62bdb887d9fef82c9899e88ac7364204ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", TESTMODE_VALID | TESTMODE_NONMAL, 13, 3, 1 + 34 + 73, 5);
|
||||
Test("c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914a63d1e4d2ed109246c600ec8c19cce546b65b1cc886776a9144d4421361c3289bdad06441ffaee8be8e786f1ad8868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 3, 33 + 34 + 73, 5);
|
||||
Test("c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a914ceedcb44b38bdbcb614d872223964fd3dca8a434886776a914f678d9b79045452c8c64e9309d0f0046056e26c588686776a914a2a75e1819afa208f6c89ae0da43021116dfcb0c8868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 4, 2 + 33 + 34 + 73, 5);
|
||||
Test("c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", "6376a914fd1690c37fa3b0f04395ddc9415b220ab1ccc59588ac6476a9149b652a14674a506079f574d20ca7daef6f9a66bb886776a914ceedcb44b38bdbcb614d872223964fd3dca8a43488686720d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 5, 2 + 34 + 73 + 34 + 73, 6);
|
||||
Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 3, 73 + 2 + 2, 4);
|
||||
Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b20fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 4, 73 + 73 + 2 + 2, 5);
|
||||
|
||||
// Additional Tapscript-related tests
|
||||
// Edge cases when parsing multi_a from script:
|
||||
// - no pubkey at all
|
||||
// - no pubkey before a CHECKSIGADD
|
||||
// - no pubkey before the CHECKSIG
|
||||
const auto no_pubkey{ParseHex("ac519c")};
|
||||
BOOST_CHECK(miniscript::FromScript({no_pubkey.begin(), no_pubkey.end()}, CONVERTER) == nullptr);
|
||||
const auto incomplete_multi_a{ParseHex("ba20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c")};
|
||||
BOOST_CHECK(miniscript::FromScript({incomplete_multi_a.begin(), incomplete_multi_a.end()}, CONVERTER) == nullptr);
|
||||
const auto incomplete_multi_a_2{ParseHex("ac2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c")};
|
||||
BOOST_CHECK(miniscript::FromScript({incomplete_multi_a_2.begin(), incomplete_multi_a_2.end()}, CONVERTER) == nullptr);
|
||||
// Can use multi_a under Tapscript but not P2WSH.
|
||||
Test("and_v(v:multi_a(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "?", "20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aac205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba529d0400046749b1", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4, 2, {}, 3);
|
||||
// Can use more than 20 keys in a multi_a.
|
||||
std::string ms_str_multi_a{"multi_a(1,"};
|
||||
for (size_t i = 0; i < 21; ++i) {
|
||||
ms_str_multi_a += HexStr(g_testdata->pubkeys[i]);
|
||||
if (i < 20) ms_str_multi_a += ",";
|
||||
}
|
||||
ms_str_multi_a += ")";
|
||||
Test(ms_str_multi_a, "?", "2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba20f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9ba20e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13ba202f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4ba20fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ba205cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bcba202f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ba20acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeba20a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7ba20774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cbba20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aba20f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8ba20499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4ba20d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080eba20e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0aba20defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34ba205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba202b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6cba204ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ba20352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5ba519c", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 22, 21, {}, 22);
|
||||
// Since 'd:' is 'u' we can use it directly inside a thresh. But we can't under P2WSH.
|
||||
Test("thresh(2,dv:older(42),s:pk(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", "7663012ab269687c205cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bcac937c20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 12, 3, {}, 4);
|
||||
// We can have a script that has more than 201 ops (n = 99), that needs a stack size > 100 (n = 110), or has a
|
||||
// script that is larger than 3600 bytes (n = 200). All that can't be under P2WSH.
|
||||
for (const auto pk_count: {99, 110, 200}) {
|
||||
std::string ms_str_large;
|
||||
for (auto i = 0; i < pk_count - 1; ++i) {
|
||||
ms_str_large += "and_b(pk(" + HexStr(g_testdata->pubkeys[i]) + "),a:";
|
||||
}
|
||||
ms_str_large += "pk(" + HexStr(g_testdata->pubkeys[pk_count - 1]) + ")";
|
||||
ms_str_large.insert(ms_str_large.end(), pk_count - 1, ')');
|
||||
Test(ms_str_large, "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, pk_count + (pk_count - 1) * 3, pk_count, {}, pk_count + 1);
|
||||
}
|
||||
// We can have a script that reaches a stack size of 1000 during execution.
|
||||
std::string ms_stack_limit;
|
||||
auto count{998};
|
||||
for (auto i = 0; i < count; ++i) {
|
||||
ms_stack_limit += "and_b(older(1),a:";
|
||||
}
|
||||
ms_stack_limit += "pk(" + HexStr(g_testdata->pubkeys[0]) + ")";
|
||||
ms_stack_limit.insert(ms_stack_limit.end(), count, ')');
|
||||
const auto ms_stack_ok{miniscript::FromString(ms_stack_limit, CONVERTER)};
|
||||
BOOST_CHECK(ms_stack_ok && ms_stack_ok->CheckStackSize());
|
||||
Test(ms_stack_limit, "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4 * count + 1, 1, {}, 1 + count + 1);
|
||||
// But one more element on the stack during execution will make it fail. And we'd detect that.
|
||||
count++;
|
||||
ms_stack_limit = "and_b(older(1),a:" + ms_stack_limit + ")";
|
||||
const auto ms_stack_nok{miniscript::FromString(ms_stack_limit, CONVERTER)};
|
||||
BOOST_CHECK(ms_stack_nok && !ms_stack_nok->CheckStackSize());
|
||||
Test(ms_stack_limit, "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4 * count + 1, 1, {}, 1 + count + 1);
|
||||
|
||||
// Misc unit tests
|
||||
// A Script with a non minimal push is invalid
|
||||
|
|
@ -490,14 +641,15 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
|
|||
const CScript nonminverify_script(nonminverify.begin(), nonminverify.end());
|
||||
BOOST_CHECK(miniscript::FromScript(nonminverify_script, CONVERTER) == nullptr);
|
||||
// A threshold as large as the number of subs is valid.
|
||||
Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL);
|
||||
Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL);
|
||||
// A threshold of 1 is valid.
|
||||
Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL);
|
||||
Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c20fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL);
|
||||
// A threshold with a k larger than the number of subs is invalid
|
||||
Test("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID);
|
||||
Test("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", "=", TESTMODE_INVALID);
|
||||
// A threshold with a k null is invalid
|
||||
Test("thresh(0,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID);
|
||||
Test("thresh(0,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", "=", TESTMODE_INVALID);
|
||||
// For CHECKMULTISIG the OP cost is the number of keys, but the stack size is the number of sigs (+1)
|
||||
CONVERTER.SetContext(miniscript::MiniscriptContext::P2WSH);
|
||||
const auto ms_multi = miniscript::FromString("multi(1,03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", CONVERTER);
|
||||
BOOST_CHECK(ms_multi);
|
||||
BOOST_CHECK_EQUAL(*ms_multi->GetOps(), 4); // 3 pubkeys + CMS
|
||||
|
|
@ -538,18 +690,17 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
|
|||
BOOST_CHECK(insane_sub && *insane_sub->ToString(CONVERTER) == "and_b(after(1),a:after(1000000000))");
|
||||
|
||||
// Timelock tests
|
||||
Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock
|
||||
Test("after(1000000000)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only timelock
|
||||
Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid
|
||||
Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid
|
||||
Test("after(100)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock
|
||||
Test("after(1000000000)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only timelock
|
||||
Test("or_b(l:after(100),al:after(1000000000))", "?", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid
|
||||
Test("and_b(after(100),a:after(1000000000))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid
|
||||
/* This is correctly detected as non-malleable but for the wrong reason. The type system assumes that branches 1 and 2
|
||||
can be spent together to create a non-malleble witness, but because of mixing of timelocks they cannot be spent together.
|
||||
But since exactly one of the two after's can be satisfied, the witness involving the key cannot be malleated.
|
||||
*/
|
||||
Test("thresh(2,ltv:after(1000000000),altv:after(100),a:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", TESTMODE_VALID | TESTMODE_TIMELOCKMIX | TESTMODE_NONMAL); // thresh with k = 2
|
||||
Test("thresh(2,ltv:after(1000000000),altv:after(100),a:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", "?", TESTMODE_VALID | TESTMODE_TIMELOCKMIX | TESTMODE_NONMAL); // thresh with k = 2
|
||||
// This is actually non-malleable in practice, but we cannot detect it in type system. See above rationale
|
||||
Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "?", TESTMODE_VALID); // thresh with k = 1
|
||||
|
||||
Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "?", "?", TESTMODE_VALID); // thresh with k = 1
|
||||
|
||||
g_testdata.reset();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ BASE_SCRIPTS = [
|
|||
'feature_maxuploadtarget.py',
|
||||
'mempool_updatefromblock.py',
|
||||
'mempool_persist.py --descriptors',
|
||||
'wallet_miniscript.py --descriptors',
|
||||
# vv Tests less than 60s vv
|
||||
'rpc_psbt.py --legacy-wallet',
|
||||
'rpc_psbt.py --descriptors',
|
||||
|
|
@ -282,7 +283,6 @@ BASE_SCRIPTS = [
|
|||
'wallet_keypool.py --legacy-wallet',
|
||||
'wallet_keypool.py --descriptors',
|
||||
'wallet_descriptor.py --descriptors',
|
||||
'wallet_miniscript.py --descriptors',
|
||||
'p2p_nobloomfilter_messages.py',
|
||||
'p2p_filter.py',
|
||||
'rpc_setban.py',
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ TPUBS = [
|
|||
"tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a",
|
||||
"tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy",
|
||||
"tpubDEFLeBkKTm8aiYkySz8hXAXPVnPSfxMi7Fxhg9sejUrkwJuRWvPdLEiXjTDbhGbjLKCZUDUUibLxTnK5UP1q7qYrSnPqnNe7M8mvAW1STcc",
|
||||
"tpubD6NzVbkrYhZ4WR99ygpiJvPMAJiwahjLgGywc5vJx2gUfKUfEPCrbKmQczDPJZmLcyZzRb5Ti6rfUb89S2WFyPH7FDtD6RFDA1hdgTEgEUL",
|
||||
]
|
||||
PUBKEYS = [
|
||||
"02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068",
|
||||
|
|
@ -33,7 +34,7 @@ PUBKEYS = [
|
|||
"0211c7b2e18b6fd330f322de087da62da92ae2ae3d0b7cec7e616479cce175f183",
|
||||
]
|
||||
|
||||
MINISCRIPTS = [
|
||||
P2WSH_MINISCRIPTS = [
|
||||
# One of two keys
|
||||
f"or_b(pk({TPUBS[0]}/*),s:pk({TPUBS[1]}/*))",
|
||||
# A script similar (same spending policy) to BOLT3's offered HTLC (with anchor outputs)
|
||||
|
|
@ -44,10 +45,22 @@ MINISCRIPTS = [
|
|||
f"or_i(and_b(pk({PUBKEYS[0]}),a:and_b(pk({PUBKEYS[1]}),a:and_b(pk({PUBKEYS[2]}),a:and_b(pk({PUBKEYS[3]}),s:pk({PUBKEYS[4]}))))),and_v(v:thresh(2,pkh({TPUBS[0]}/*),a:pkh({PUBKEYS[5]}),a:pkh({PUBKEYS[6]})),older(4209713)))",
|
||||
]
|
||||
|
||||
MINISCRIPTS_PRIV = [
|
||||
DESCS = [
|
||||
*[f"wsh({ms})" for ms in P2WSH_MINISCRIPTS],
|
||||
# A Taproot with one of the above scripts as the single script path.
|
||||
f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{P2WSH_MINISCRIPTS[0]})",
|
||||
# A Taproot with two script paths among the above scripts.
|
||||
f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}})",
|
||||
# A Taproot with three script paths among the above scripts.
|
||||
f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}},{P2WSH_MINISCRIPTS[2].replace('multi', 'multi_a')}}})",
|
||||
# A Taproot with all above scripts in its tree.
|
||||
f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}},{{{P2WSH_MINISCRIPTS[2].replace('multi', 'multi_a')},{P2WSH_MINISCRIPTS[3]}}}}})",
|
||||
]
|
||||
|
||||
DESCS_PRIV = [
|
||||
# One of two keys, of which one private key is known
|
||||
{
|
||||
"ms": f"or_i(pk({TPRVS[0]}/*),pk({TPUBS[0]}/*))",
|
||||
"desc": f"wsh(or_i(pk({TPRVS[0]}/*),pk({TPUBS[0]}/*)))",
|
||||
"sequence": None,
|
||||
"locktime": None,
|
||||
"sigs_count": 1,
|
||||
|
|
@ -55,7 +68,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# A more complex policy, that can't be satisfied through the first branch (need for a preimage)
|
||||
{
|
||||
"ms": f"andor(ndv:older(2),and_v(v:pk({TPRVS[0]}),sha256(2a8ce30189b2ec3200b47aeb4feaac8fcad7c0ba170389729f4898b0b7933bcb)),and_v(v:pkh({TPRVS[1]}),pk({TPRVS[2]}/*)))",
|
||||
"desc": f"wsh(andor(ndv:older(2),and_v(v:pk({TPRVS[0]}),sha256(2a8ce30189b2ec3200b47aeb4feaac8fcad7c0ba170389729f4898b0b7933bcb)),and_v(v:pkh({TPRVS[1]}),pk({TPRVS[2]}/*))))",
|
||||
"sequence": 2,
|
||||
"locktime": None,
|
||||
"sigs_count": 3,
|
||||
|
|
@ -63,7 +76,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# The same policy but we provide the preimage. This path will be chosen as it's a smaller witness.
|
||||
{
|
||||
"ms": f"andor(ndv:older(2),and_v(v:pk({TPRVS[0]}),sha256(61e33e9dbfefc45f6a194187684d278f789fd4d5e207a357e79971b6519a8b12)),and_v(v:pkh({TPRVS[1]}),pk({TPRVS[2]}/*)))",
|
||||
"desc": f"wsh(andor(ndv:older(2),and_v(v:pk({TPRVS[0]}),sha256(61e33e9dbfefc45f6a194187684d278f789fd4d5e207a357e79971b6519a8b12)),and_v(v:pkh({TPRVS[1]}),pk({TPRVS[2]}/*))))",
|
||||
"sequence": 2,
|
||||
"locktime": None,
|
||||
"sigs_count": 3,
|
||||
|
|
@ -74,7 +87,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# Signature with a relative timelock
|
||||
{
|
||||
"ms": f"and_v(v:older(2),pk({TPRVS[0]}/*))",
|
||||
"desc": f"wsh(and_v(v:older(2),pk({TPRVS[0]}/*)))",
|
||||
"sequence": 2,
|
||||
"locktime": None,
|
||||
"sigs_count": 1,
|
||||
|
|
@ -82,7 +95,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# Signature with an absolute timelock
|
||||
{
|
||||
"ms": f"and_v(v:after(20),pk({TPRVS[0]}/*))",
|
||||
"desc": f"wsh(and_v(v:after(20),pk({TPRVS[0]}/*)))",
|
||||
"sequence": None,
|
||||
"locktime": 20,
|
||||
"sigs_count": 1,
|
||||
|
|
@ -90,7 +103,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# Signature with both
|
||||
{
|
||||
"ms": f"and_v(v:older(4),and_v(v:after(30),pk({TPRVS[0]}/*)))",
|
||||
"desc": f"wsh(and_v(v:older(4),and_v(v:after(30),pk({TPRVS[0]}/*))))",
|
||||
"sequence": 4,
|
||||
"locktime": 30,
|
||||
"sigs_count": 1,
|
||||
|
|
@ -98,7 +111,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# We have one key on each branch; Core signs both (can't finalize)
|
||||
{
|
||||
"ms": f"c:andor(pk({TPRVS[0]}/*),pk_k({TPUBS[0]}),and_v(v:pk({TPRVS[1]}),pk_k({TPUBS[1]})))",
|
||||
"desc": f"wsh(c:andor(pk({TPRVS[0]}/*),pk_k({TPUBS[0]}),and_v(v:pk({TPRVS[1]}),pk_k({TPUBS[1]}))))",
|
||||
"sequence": None,
|
||||
"locktime": None,
|
||||
"sigs_count": 2,
|
||||
|
|
@ -106,7 +119,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# We have all the keys, wallet selects the timeout path to sign since it's smaller and sequence is set
|
||||
{
|
||||
"ms": f"andor(pk({TPRVS[0]}/*),pk({TPRVS[2]}),and_v(v:pk({TPRVS[1]}),older(10)))",
|
||||
"desc": f"wsh(andor(pk({TPRVS[0]}/*),pk({TPRVS[2]}),and_v(v:pk({TPRVS[1]}),older(10))))",
|
||||
"sequence": 10,
|
||||
"locktime": None,
|
||||
"sigs_count": 3,
|
||||
|
|
@ -114,7 +127,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# We have all the keys, wallet selects the primary path to sign unconditionally since nsequence wasn't set to be valid for timeout path
|
||||
{
|
||||
"ms": f"andor(pk({TPRVS[0]}/*),pk({TPRVS[2]}),and_v(v:pkh({TPRVS[1]}),older(10)))",
|
||||
"desc": f"wsh(andor(pk({TPRVS[0]}/*),pk({TPRVS[2]}),and_v(v:pkh({TPRVS[1]}),older(10))))",
|
||||
"sequence": None,
|
||||
"locktime": None,
|
||||
"sigs_count": 3,
|
||||
|
|
@ -122,7 +135,7 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# Finalizes to the smallest valid witness, regardless of sequence
|
||||
{
|
||||
"ms": f"or_d(pk({TPRVS[0]}/*),and_v(v:pk({TPRVS[1]}),and_v(v:pk({TPRVS[2]}),older(10))))",
|
||||
"desc": f"wsh(or_d(pk({TPRVS[0]}/*),and_v(v:pk({TPRVS[1]}),and_v(v:pk({TPRVS[2]}),older(10)))))",
|
||||
"sequence": 12,
|
||||
"locktime": None,
|
||||
"sigs_count": 3,
|
||||
|
|
@ -130,7 +143,57 @@ MINISCRIPTS_PRIV = [
|
|||
},
|
||||
# Liquid-like federated pegin with emergency recovery privkeys
|
||||
{
|
||||
"ms": f"or_i(and_b(pk({TPUBS[0]}/*),a:and_b(pk({TPUBS[1]}),a:and_b(pk({TPUBS[2]}),a:and_b(pk({TPUBS[3]}),s:pk({PUBKEYS[0]}))))),and_v(v:thresh(2,pkh({TPRVS[0]}),a:pkh({TPRVS[1]}),a:pkh({TPUBS[4]})),older(42)))",
|
||||
"desc": f"wsh(or_i(and_b(pk({TPUBS[0]}/*),a:and_b(pk({TPUBS[1]}),a:and_b(pk({TPUBS[2]}),a:and_b(pk({TPUBS[3]}),s:pk({PUBKEYS[0]}))))),and_v(v:thresh(2,pkh({TPRVS[0]}),a:pkh({TPRVS[1]}),a:pkh({TPUBS[4]})),older(42))))",
|
||||
"sequence": 42,
|
||||
"locktime": None,
|
||||
"sigs_count": 2,
|
||||
"stack_size": 8,
|
||||
},
|
||||
# Each leaf needs two sigs. We've got one key on each. Will sign both but can't finalize.
|
||||
{
|
||||
"desc": f"tr({TPUBS[0]}/*,{{and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]})),and_v(v:pk({TPRVS[1]}/*),pk({TPUBS[2]}))}})",
|
||||
"sequence": None,
|
||||
"locktime": None,
|
||||
"sigs_count": 2,
|
||||
"stack_size": None,
|
||||
},
|
||||
# The same but now the two leaves are identical. Will add a single sig that is valid for both. Can't finalize.
|
||||
{
|
||||
"desc": f"tr({TPUBS[0]}/*,{{and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]})),and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]}))}})",
|
||||
"sequence": None,
|
||||
"locktime": None,
|
||||
"sigs_count": 1,
|
||||
"stack_size": None,
|
||||
},
|
||||
# The same but we have the two necessary privkeys on one of the leaves. Also it uses a pubkey hash.
|
||||
{
|
||||
"desc": f"tr({TPUBS[0]}/*,{{and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]})),and_v(v:pkh({TPRVS[1]}/*),pk({TPRVS[2]}))}})",
|
||||
"sequence": None,
|
||||
"locktime": None,
|
||||
"sigs_count": 3,
|
||||
"stack_size": 5,
|
||||
},
|
||||
# A key immediately or one of two keys after a timelock. If both paths are available it'll use the
|
||||
# non-timelocked path because it's a smaller witness.
|
||||
{
|
||||
"desc": f"tr({TPUBS[0]}/*,{{pk({TPRVS[0]}/*),and_v(v:older(42),multi_a(1,{TPRVS[1]},{TPRVS[2]}))}})",
|
||||
"sequence": 42,
|
||||
"locktime": None,
|
||||
"sigs_count": 3,
|
||||
"stack_size": 3,
|
||||
},
|
||||
# A key immediately or one of two keys after a timelock. If the "primary" key isn't available though it'll
|
||||
# use the timelocked path. Same remark for multi_a.
|
||||
{
|
||||
"desc": f"tr({TPUBS[0]}/*,{{pk({TPUBS[1]}/*),and_v(v:older(42),multi_a(1,{TPRVS[0]},{TPRVS[1]}))}})",
|
||||
"sequence": 42,
|
||||
"locktime": None,
|
||||
"sigs_count": 2,
|
||||
"stack_size": 4,
|
||||
},
|
||||
# Liquid-like federated pegin with emergency recovery privkeys, but in a Taproot.
|
||||
{
|
||||
"desc": f"tr({TPUBS[1]}/*,{{and_b(pk({TPUBS[2]}/*),a:and_b(pk({TPUBS[3]}),a:and_b(pk({TPUBS[4]}),a:and_b(pk({TPUBS[5]}),s:pk({PUBKEYS[0]}))))),and_v(v:thresh(2,pkh({TPRVS[0]}),a:pkh({TPRVS[1]}),a:pkh({TPUBS[6]})),older(42))}})",
|
||||
"sequence": 42,
|
||||
"locktime": None,
|
||||
"sigs_count": 2,
|
||||
|
|
@ -142,6 +205,7 @@ MINISCRIPTS_PRIV = [
|
|||
class WalletMiniscriptTest(BitcoinTestFramework):
|
||||
def add_options(self, parser):
|
||||
self.add_wallet_options(parser, legacy=False)
|
||||
self.rpc_timeout = 480
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
|
@ -150,9 +214,9 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
|||
self.skip_if_no_wallet()
|
||||
self.skip_if_no_sqlite()
|
||||
|
||||
def watchonly_test(self, ms):
|
||||
self.log.info(f"Importing Miniscript '{ms}'")
|
||||
desc = descsum_create(f"wsh({ms})")
|
||||
def watchonly_test(self, desc):
|
||||
self.log.info(f"Importing descriptor '{desc}'")
|
||||
desc = descsum_create(f"{desc}")
|
||||
assert self.ms_wo_wallet.importdescriptors(
|
||||
[
|
||||
{
|
||||
|
|
@ -166,11 +230,14 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
|||
)[0]["success"]
|
||||
|
||||
self.log.info("Testing we derive new addresses for it")
|
||||
addr_type = "bech32m" if desc.startswith("tr(") else "bech32"
|
||||
assert_equal(
|
||||
self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 0)[0]
|
||||
self.ms_wo_wallet.getnewaddress(address_type=addr_type),
|
||||
self.funder.deriveaddresses(desc, 0)[0],
|
||||
)
|
||||
assert_equal(
|
||||
self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 1)[1]
|
||||
self.ms_wo_wallet.getnewaddress(address_type=addr_type),
|
||||
self.funder.deriveaddresses(desc, 1)[1],
|
||||
)
|
||||
|
||||
self.log.info("Testing we detect funds sent to one of them")
|
||||
|
|
@ -183,10 +250,11 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
|||
assert utxo["txid"] == txid and utxo["solvable"]
|
||||
|
||||
def signing_test(
|
||||
self, ms, sequence, locktime, sigs_count, stack_size, sha256_preimages
|
||||
self, desc, sequence, locktime, sigs_count, stack_size, sha256_preimages
|
||||
):
|
||||
self.log.info(f"Importing private Miniscript '{ms}'")
|
||||
desc = descsum_create(f"wsh({ms})")
|
||||
self.log.info(f"Importing private Miniscript descriptor '{desc}'")
|
||||
is_taproot = desc.startswith("tr(")
|
||||
desc = descsum_create(desc)
|
||||
res = self.ms_sig_wallet.importdescriptors(
|
||||
[
|
||||
{
|
||||
|
|
@ -201,7 +269,8 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
|||
assert res[0]["success"], res
|
||||
|
||||
self.log.info("Generating an address for it and testing it detects funds")
|
||||
addr = self.ms_sig_wallet.getnewaddress()
|
||||
addr_type = "bech32m" if is_taproot else "bech32"
|
||||
addr = self.ms_sig_wallet.getnewaddress(address_type=addr_type)
|
||||
txid = self.funder.sendtoaddress(addr, 0.01)
|
||||
self.wait_until(lambda: txid in self.funder.getrawmempool())
|
||||
self.funder.generatetoaddress(1, self.funder.getnewaddress())
|
||||
|
|
@ -236,7 +305,8 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
|||
# psbt = psbt.to_base64()
|
||||
# res = self.ms_sig_wallet.walletprocesspsbt(psbt=psbt, finalize=False)
|
||||
# psbtin = self.nodes[0].rpc.decodepsbt(res["psbt"])["inputs"][0]
|
||||
# assert len(psbtin["partial_signatures"]) == sigs_count
|
||||
# sigs_field_name = "taproot_script_path_sigs" if is_taproot else "partial_signatures"
|
||||
# assert len(psbtin[sigs_field_name]) == sigs_count
|
||||
# res = self.ms_sig_wallet.finalizepsbt(res["psbt"])
|
||||
# assert res["complete"] == (stack_size is not None)
|
||||
|
||||
|
|
@ -293,20 +363,45 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
|||
assert not res["success"] and "is not satisfiable" in res["error"]["message"]
|
||||
|
||||
# Test we can track any type of Miniscript
|
||||
for ms in MINISCRIPTS:
|
||||
self.watchonly_test(ms)
|
||||
for desc in DESCS:
|
||||
self.watchonly_test(desc)
|
||||
|
||||
# Test we can sign for any Miniscript.
|
||||
for ms in MINISCRIPTS_PRIV:
|
||||
for desc in DESCS_PRIV:
|
||||
self.signing_test(
|
||||
ms["ms"],
|
||||
ms["sequence"],
|
||||
ms["locktime"],
|
||||
ms["sigs_count"],
|
||||
ms["stack_size"],
|
||||
ms.get("sha256_preimages"),
|
||||
desc["desc"],
|
||||
desc["sequence"],
|
||||
desc["locktime"],
|
||||
desc["sigs_count"],
|
||||
desc["stack_size"],
|
||||
desc.get("sha256_preimages"),
|
||||
)
|
||||
|
||||
# Test we can sign for a max-size TapMiniscript. Recompute the maximum accepted size
|
||||
# for a TapMiniscript (see cpp file for details). Then pad a simple pubkey check up
|
||||
# to the maximum size. Make sure we can import and spend this script.
|
||||
leeway_weight = (4 + 4 + 1 + 36 + 4 + 1 + 1 + 8 + 1 + 1 + 33) * 4 + 2
|
||||
max_tapmini_size = 400_000 - 3 - (1 + 65) * 1_000 - 3 - (33 + 32 * 128) - leeway_weight - 5
|
||||
padding = max_tapmini_size - 33 - 1
|
||||
ms = f"pk({TPRVS[0]}/*)"
|
||||
ms = "n" * padding + ":" + ms
|
||||
desc = f"tr({PUBKEYS[0]},{ms})"
|
||||
self.signing_test(desc, None, None, 1, 3, None)
|
||||
# This was really the maximum size, one more byte and we can't import it.
|
||||
ms = "n" + ms
|
||||
desc = f"tr({PUBKEYS[0]},{ms})"
|
||||
res = self.ms_wo_wallet.importdescriptors(
|
||||
[
|
||||
{
|
||||
"desc": descsum_create(desc),
|
||||
"active": False,
|
||||
"timestamp": "now",
|
||||
}
|
||||
]
|
||||
)[0]
|
||||
assert not res["success"]
|
||||
assert "is not a valid descriptor function" in res["error"]["message"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
WalletMiniscriptTest().main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue