mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-26 11:13:23 -03:00
Merge bitcoin/bitcoin#27255: MiniTapscript: port Miniscript to Tapscript
ec0fc14a22
miniscript: remove P2WSH-specific part of GetStackSize doc comment (Antoine Poinsot)128bc104ef
qa: bound testing for TapMiniscript (Antoine Poinsot)117927bd5f
miniscript: have a custom Node destructor (Antoine Poinsot)b917c715ac
qa: Tapscript Miniscript signing functional tests (Antoine Poinsot)5dc341dfe6
qa: list descriptors in Miniscript signing functional tests (Antoine Poinsot)4f473ea515
script/sign: Miniscript support in Tapscript (Antoine Poinsot)febe2abc0e
MOVEONLY: script/sign: move Satisfier declaration above Tapscript signing (Antoine Poinsot)bd4b11ee06
qa: functional test Miniscript inside Taproot descriptors (Antoine Poinsot)8571b89a7f
descriptor: parse Miniscript expressions within Taproot descriptors (Antoine Poinsot)8ff9489422
descriptor: Tapscript-specific Miniscript key serialization / parsing (Antoine Poinsot)5e76f3f0dd
fuzz: miniscript: higher sensitivity for max stack size limit under Tapscript (Antoine Poinsot)6f529cbaaf
qa: test Miniscript max stack size tracking (Antoine Poinsot)770ba5b519
miniscript: check maximum stack size during execution (Antoine Poinsot)574523dbe0
fuzz: adapt Miniscript targets to Tapscript (Antoine Poinsot)84623722ef
qa: Tapscript-Miniscript unit tests (Antoine Poinsot)fcb6f13f44
pubkey: introduce a GetEvenCorrespondingCPubKey helper (Antoine Poinsot)ce8845f5dd
miniscript: account for keys as being 32 bytes under Taproot context (Antoine Poinsot)f4f978d38e
miniscript: adapt resources checks depending on context (Antoine Poinsot)9cb4c68b89
serialize: make GetSizeOfCompactSize constexpr (Antoine Poinsot)892436c7d5
miniscript: sanity asserts context in ComputeType (Antoine Poinsot)e5aaa3d77a
miniscript: make 'd:' have the 'u' property under Tapscript context (Antoine Poinsot)687a0b0fa5
miniscript: introduce a multi_a fragment (Antoine Poinsot)9164c2eca1
miniscript: restrict multi() usage to P2WSH context (Antoine Poinsot)91b4db8590
miniscript: store the script context within the Node structure (Antoine Poinsot)c3738d0344
miniscript: introduce a MsContext() helper to contexts (Antoine Poinsot)bba9340a94
miniscript: don't anticipate signature presence in CalcStackSize() (Antoine Poinsot)a3793f2d1a
miniscript: add a missing dup key check bypass in Parse() (Antoine Poinsot) Pull request description: Miniscript was targeting P2WSH, and as such can currently only be used in `wsh()` descriptors. This pull request introduces support for Tapscript in Miniscript and makes Miniscript available inside `tr()` descriptors. It adds support for both watching *and* signing TapMiniscript descriptors. The main changes to Miniscript for Tapscript are the following: - A new `multi_a` fragment is introduced with the same semantics as `multi`. Like in other descriptors `multi` and `multi_a` can exclusively be used in respectively P2WSH and Tapscript. - The `d:` fragment has the `u` property under Tapscript, since the `MINIMALIF` rule is now consensus. See also https://github.com/bitcoin/bitcoin/pull/24906. - Keys are now serialized as 32 bytes. (Note this affects the key hashes.) - The resource consumption checks and calculation changed. Some limits were lifted in Tapscript, and signatures are now 64 bytes long. The largest amount of complexity probably lies in the last item. Scripts under Taproot can now run into the maximum stack size while executing a fragment. For instance if you've got a stack size of `999` due to the initial witness plus some execution that happened before and try to execute a `hash256` it would `DUP` (increasing the stack size `1000`), `HASH160` and then push the hash on the stack making the script fail. To make sure this does not happen on any of the spending paths of a sane Miniscript, we introduce a tracking of the maximum stack size during execution of a fragment. See the commits messages for details. Those commits were separated from the resource consumption change, and the fuzz target was tweaked to sometimes pad the witness so the script runs on the brink of the stack size limit to make sure the stack size was not underestimated. Existing Miniscript unit, functional and fuzz tests are extended with Tapscript logic and test cases. Care was taken for seed stability in the fuzz targets where we cared more about them. The design of Miniscript for Tapscript is the result of discussions between various people over the past year(s). To the extent of my knowledge at least Pieter Wuille, Sanket Kanjalkar, Andrew Poelstra and Andrew Chow contributed thoughts and ideas. ACKs for top commit: sipa: ACKec0fc14a22
achow101: ACKec0fc14a22
Tree-SHA512: f3cf98a3ec8e565650ccf51b7ee7e4b4c2b3949a1168bee16ec03d2942b4d9f20dedc2820457f67a3216161022263573d08419c8346d807a693169ad3a436e07
This commit is contained in:
commit
db283a6b6f
14 changed files with 1424 additions and 546 deletions
|
@ -132,6 +132,7 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
|
||||||
}
|
}
|
||||||
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
||||||
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
|
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
|
||||||
|
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey);
|
||||||
}
|
}
|
||||||
for (const auto& [hash, preimage] : ripemd160_preimages) {
|
for (const auto& [hash, preimage] : ripemd160_preimages) {
|
||||||
sigdata.ripemd160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
|
sigdata.ripemd160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
|
||||||
|
@ -246,6 +247,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
|
||||||
}
|
}
|
||||||
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
||||||
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
|
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;
|
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
|
bool XOnlyPubKey::IsFullyValid() const
|
||||||
{
|
{
|
||||||
secp256k1_xonly_pubkey pubkey;
|
secp256k1_xonly_pubkey pubkey;
|
||||||
|
|
|
@ -282,6 +282,8 @@ public:
|
||||||
*/
|
*/
|
||||||
std::vector<CKeyID> GetKeyIDs() const;
|
std::vector<CKeyID> GetKeyIDs() const;
|
||||||
|
|
||||||
|
CPubKey GetEvenCorrespondingCPubKey() const;
|
||||||
|
|
||||||
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
|
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
|
||||||
const unsigned char* data() const { return m_keydata.begin(); }
|
const unsigned char* data() const { return m_keydata.begin(); }
|
||||||
static constexpr size_t size() { return decltype(m_keydata)::size(); }
|
static constexpr size_t size() { return decltype(m_keydata)::size(); }
|
||||||
|
|
|
@ -1114,16 +1114,33 @@ public:
|
||||||
class ScriptMaker {
|
class ScriptMaker {
|
||||||
//! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args).
|
//! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args).
|
||||||
const std::vector<CPubKey>& m_keys;
|
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:
|
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 {
|
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 {
|
std::vector<unsigned char> ToPKHBytes(uint32_t key) const {
|
||||||
auto id = m_keys[key].GetID();
|
auto id = GetHash160(key);
|
||||||
return {id.begin(), id.end()};
|
return {id.begin(), id.end()};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1164,8 +1181,15 @@ protected:
|
||||||
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts,
|
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts,
|
||||||
FlatSigningProvider& provider) const override
|
FlatSigningProvider& provider) const override
|
||||||
{
|
{
|
||||||
for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key);
|
const auto script_ctx{m_node->GetMsCtx()};
|
||||||
return Vector(m_node->ToScript(ScriptMaker(keys)));
|
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:
|
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)
|
std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
|
||||||
{
|
{
|
||||||
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
|
CPubKey pubkey{xkey.GetEvenCorrespondingCPubKey()};
|
||||||
std::copy(xkey.begin(), xkey.end(), full_key + 1);
|
|
||||||
CPubKey pubkey(full_key);
|
|
||||||
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
|
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
|
||||||
KeyOriginInfo info;
|
KeyOriginInfo info;
|
||||||
if (provider.GetKeyOriginByXOnly(xkey, info)) {
|
if (provider.GetKeyOriginByXOnly(xkey, info)) {
|
||||||
|
@ -1426,18 +1448,32 @@ struct KeyParser {
|
||||||
mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys;
|
mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys;
|
||||||
//! Used to detect key parsing errors within a Miniscript.
|
//! Used to detect key parsing errors within a Miniscript.
|
||||||
mutable std::string m_key_parsing_error;
|
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 {
|
bool KeyCompare(const Key& a, const Key& b) const {
|
||||||
return *m_keys.at(a) < *m_keys.at(b);
|
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
|
template<typename I> std::optional<Key> FromString(I begin, I end) const
|
||||||
{
|
{
|
||||||
assert(m_out);
|
assert(m_out);
|
||||||
Key key = m_keys.size();
|
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 {};
|
if (!pk) return {};
|
||||||
m_keys.push_back(std::move(pk));
|
m_keys.push_back(std::move(pk));
|
||||||
return key;
|
return key;
|
||||||
|
@ -1451,11 +1487,18 @@ struct KeyParser {
|
||||||
template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const
|
template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const
|
||||||
{
|
{
|
||||||
assert(m_in);
|
assert(m_in);
|
||||||
CPubKey pubkey(begin, end);
|
Key key = m_keys.size();
|
||||||
if (pubkey.IsValidNonHybrid()) {
|
if (miniscript::IsTapscript(m_script_ctx) && end - begin == 32) {
|
||||||
Key key = m_keys.size();
|
XOnlyPubKey pubkey;
|
||||||
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
|
std::copy(begin, end, pubkey.begin());
|
||||||
|
m_keys.push_back(InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in));
|
||||||
return key;
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -1470,11 +1513,15 @@ struct KeyParser {
|
||||||
CPubKey pubkey;
|
CPubKey pubkey;
|
||||||
if (m_in->GetPubKey(keyid, pubkey)) {
|
if (m_in->GetPubKey(keyid, pubkey)) {
|
||||||
Key key = m_keys.size();
|
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 key;
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
miniscript::MiniscriptContext MsContext() const {
|
||||||
|
return m_script_ctx;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Parse a script in a particular context. */
|
/** 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;
|
++key_exp_index;
|
||||||
return std::make_unique<PKHDescriptor>(std::move(pubkey));
|
return std::make_unique<PKHDescriptor>(std::move(pubkey));
|
||||||
} else if (Func("pkh", expr)) {
|
} else if (ctx != ParseScriptContext::P2TR && Func("pkh", expr)) {
|
||||||
error = "Can only have pkh at top level, in sh(), or in wsh()";
|
// 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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
|
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.
|
// 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);
|
auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser);
|
||||||
if (node) {
|
if (node) {
|
||||||
if (ctx != ParseScriptContext::P2WSH) {
|
if (ctx != ParseScriptContext::P2WSH && ctx != ParseScriptContext::P2TR) {
|
||||||
error = "Miniscript expressions can only be used in wsh";
|
error = "Miniscript expressions can only be used in wsh or tr.";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (parser.m_key_parsing_error != "") {
|
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
|
// A signature check is required for a miniscript to be sane. Therefore no sane miniscript
|
||||||
// may have an empty list of public keys.
|
// may have an empty list of public keys.
|
||||||
CHECK_NONFATAL(!parser.m_keys.empty());
|
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));
|
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) {
|
if (ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR) {
|
||||||
KeyParser parser(nullptr, &provider);
|
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);
|
auto node = miniscript::FromScript(script, parser);
|
||||||
if (node && node->IsSane()) {
|
if (node && node->IsSane()) {
|
||||||
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
|
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <script/script.h>
|
#include <script/script.h>
|
||||||
#include <script/miniscript.h>
|
#include <script/miniscript.h>
|
||||||
|
#include <serialize.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
@ -32,7 +33,8 @@ Type SanitizeType(Type e) {
|
||||||
return 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
|
// Sanity check on data
|
||||||
if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) {
|
if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) {
|
||||||
assert(data_size == 32);
|
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
|
// Sanity check on k
|
||||||
if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) {
|
if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) {
|
||||||
assert(k >= 1 && k < 0x80000000UL);
|
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);
|
assert(k >= 1 && k <= n_keys);
|
||||||
} else if (fragment == Fragment::THRESH) {
|
} else if (fragment == Fragment::THRESH) {
|
||||||
assert(k >= 1 && k <= n_subs);
|
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) {
|
if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) {
|
||||||
assert(n_keys == 1);
|
assert(n_keys == 1);
|
||||||
} else if (fragment == Fragment::MULTI) {
|
} 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 {
|
} else {
|
||||||
assert(n_keys == 0);
|
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
|
"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 & "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
|
(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
|
"ndx"_mst; // n, d, x
|
||||||
case Fragment::WRAP_V: return
|
case Fragment::WRAP_V: return
|
||||||
"V"_mst.If(x << "B"_mst) | // V=B_x
|
"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 << "h"_mst) && (y << "g"_mst)) ||
|
||||||
((x << "i"_mst) && (y << "j"_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)
|
((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: {
|
case Fragment::THRESH: {
|
||||||
bool all_e = true;
|
bool all_e = true;
|
||||||
bool all_m = 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);
|
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) {
|
switch (fragment) {
|
||||||
case Fragment::JUST_1:
|
case Fragment::JUST_1:
|
||||||
case Fragment::JUST_0: return 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::PK_H: return 3 + 21;
|
||||||
case Fragment::OLDER:
|
case Fragment::OLDER:
|
||||||
case Fragment::AFTER: return 1 + BuildScript(k).size();
|
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::HASH160:
|
||||||
case Fragment::RIPEMD160: return 4 + 2 + 21;
|
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: 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::AND_V: return subsize;
|
||||||
case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst);
|
case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst);
|
||||||
case Fragment::WRAP_S:
|
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
|
// Decompose OP_EQUALVERIFY into OP_EQUAL OP_VERIFY
|
||||||
out.emplace_back(OP_EQUAL, std::vector<unsigned char>());
|
out.emplace_back(OP_EQUAL, std::vector<unsigned char>());
|
||||||
opcode = OP_VERIFY;
|
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)) {
|
} else if (IsPushdataOp(opcode)) {
|
||||||
if (!CheckMinimalPush(push_data, opcode)) return {};
|
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
|
// Rule out non minimal VERIFY sequences
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -114,12 +114,17 @@ static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigd
|
||||||
pubkey = it->second.first;
|
pubkey = it->second.first;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Look for pubkey in pubkey list
|
// Look for pubkey in pubkey lists
|
||||||
const auto& pk_it = sigdata.misc_pubkeys.find(address);
|
const auto& pk_it = sigdata.misc_pubkeys.find(address);
|
||||||
if (pk_it != sigdata.misc_pubkeys.end()) {
|
if (pk_it != sigdata.misc_pubkeys.end()) {
|
||||||
pubkey = pk_it->second.first;
|
pubkey = pk_it->second.first;
|
||||||
return true;
|
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
|
// Query the underlying provider
|
||||||
return provider.GetPubKey(address, pubkey);
|
return provider.GetPubKey(address, pubkey);
|
||||||
}
|
}
|
||||||
|
@ -171,49 +176,158 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur
|
||||||
return false;
|
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)) {
|
||||||
|
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)
|
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.
|
// Only BIP342 tapscript signing is supported for now.
|
||||||
if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false;
|
if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false;
|
||||||
SigVersion sigversion = SigVersion::TAPSCRIPT;
|
|
||||||
|
|
||||||
uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes);
|
uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes);
|
||||||
CScript script = CScript(script_bytes.begin(), script_bytes.end());
|
CScript script = CScript(script_bytes.begin(), script_bytes.end());
|
||||||
|
|
||||||
// <xonly pubkey> OP_CHECKSIG
|
TapSatisfier ms_satisfier{provider, sigdata, creator, script, leaf_hash};
|
||||||
if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) {
|
const auto ms = miniscript::FromScript(script, ms_satisfier);
|
||||||
XOnlyPubKey pubkey{Span{script}.subspan(1, 32)};
|
return ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result)
|
static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result)
|
||||||
|
@ -382,92 +496,6 @@ static CScript PushAll(const std::vector<valtype>& values)
|
||||||
return result;
|
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)) {
|
|
||||||
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)
|
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
|
||||||
{
|
{
|
||||||
if (sigdata.complete) return true;
|
if (sigdata.complete) return true;
|
||||||
|
@ -512,7 +540,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
|
||||||
// isn't fully solved. For instance the CHECKMULTISIG satisfaction in SignStep() pushes partial signatures
|
// isn't fully solved. For instance the CHECKMULTISIG satisfaction in SignStep() pushes partial signatures
|
||||||
// and the extractor relies on this behaviour to combine witnesses.
|
// and the extractor relies on this behaviour to combine witnesses.
|
||||||
if (!solved && result.empty()) {
|
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);
|
const auto ms = miniscript::FromScript(witnessscript, ms_satisfier);
|
||||||
solved = ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES;
|
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::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<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<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_pubkeys; ///< KeyIDs of pubkeys which could not be found
|
||||||
std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures 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)
|
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 -- 5 bytes (254 + 4 bytes)
|
||||||
* size > UINT_MAX -- 9 bytes (255 + 8 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);
|
if (nSize < 253) return sizeof(unsigned char);
|
||||||
else if (nSize <= std::numeric_limits<uint16_t>::max()) return sizeof(unsigned char) + sizeof(uint16_t);
|
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
|
// 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'");
|
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
|
// 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");
|
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("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");
|
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
|
// 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");
|
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
|
// 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_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")}});
|
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, {{"512033982eebe204dc66508e4b19cfc31b5ffc6e1bfcbf6e5597dfc2521a52270795"}}, 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, {{"51201e9875f690f5847404e4c5951e2f029887df0525691ee11a682afd37b608aad4"}}, 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, {{"5120d8ea39b29de2b550b68bd2ada8b075c888c2b2df3290c7a35856482747848934"}}, 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, {{"5120793185cd1a9a0bb710fa57df3845ac4ddf7df63b74beadce2573cbb0b508b3a4"}}, 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, {{"51209a3d79db56fbe3ba4d905d827b62e1ed31cd6df1198b8c759d589c0f4efc27bd"}}, 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, {{"51209a3d79db56fbe3ba4d905d827b62e1ed31cd6df1198b8c759d589c0f4efc27bd"}}, OutputType::BECH32M, /*op_desc_id=*/{}, {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/42, /*preimages=*/{{ParseHex("ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588"), ParseHex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")}});
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <key.h>
|
#include <key.h>
|
||||||
#include <script/miniscript.h>
|
#include <script/miniscript.h>
|
||||||
#include <script/script.h>
|
#include <script/script.h>
|
||||||
|
#include <script/signingprovider.h>
|
||||||
#include <test/fuzz/FuzzedDataProvider.h>
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
#include <test/fuzz/fuzz.h>
|
#include <test/fuzz/fuzz.h>
|
||||||
#include <test/fuzz/util.h>
|
#include <test/fuzz/util.h>
|
||||||
|
@ -14,6 +15,15 @@
|
||||||
|
|
||||||
namespace {
|
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.
|
//! Some pre-computed data for more efficient string roundtrips and to simulate challenges.
|
||||||
struct TestData {
|
struct TestData {
|
||||||
typedef CPubKey Key;
|
typedef CPubKey Key;
|
||||||
|
@ -23,6 +33,7 @@ struct TestData {
|
||||||
std::map<Key, int> dummy_key_idx_map;
|
std::map<Key, int> dummy_key_idx_map;
|
||||||
std::map<CKeyID, Key> dummy_keys_map;
|
std::map<CKeyID, Key> dummy_keys_map;
|
||||||
std::map<Key, std::pair<std::vector<unsigned char>, bool>> dummy_sigs;
|
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.
|
// Precomputed hashes of each kind.
|
||||||
std::vector<std::vector<unsigned char>> sha256;
|
std::vector<std::vector<unsigned char>> sha256;
|
||||||
|
@ -37,6 +48,11 @@ struct TestData {
|
||||||
//! Set the precomputed data.
|
//! Set the precomputed data.
|
||||||
void Init() {
|
void Init() {
|
||||||
unsigned char keydata[32] = {1};
|
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++) {
|
for (size_t i = 0; i < 256; i++) {
|
||||||
keydata[31] = i;
|
keydata[31] = i;
|
||||||
CKey privkey;
|
CKey privkey;
|
||||||
|
@ -46,11 +62,17 @@ struct TestData {
|
||||||
dummy_keys.push_back(pubkey);
|
dummy_keys.push_back(pubkey);
|
||||||
dummy_key_idx_map.emplace(pubkey, i);
|
dummy_key_idx_map.emplace(pubkey, i);
|
||||||
dummy_keys_map.insert({pubkey.GetID(), pubkey});
|
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;
|
std::vector<unsigned char> sig, schnorr_sig(64);
|
||||||
privkey.Sign(uint256S(""), sig);
|
privkey.Sign(MESSAGE_HASH, sig);
|
||||||
sig.push_back(1); // SIGHASH_ALL
|
sig.push_back(1); // SIGHASH_ALL
|
||||||
dummy_sigs.insert({pubkey, {sig, i & 1}});
|
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;
|
std::vector<unsigned char> hash;
|
||||||
hash.resize(32);
|
hash.resize(32);
|
||||||
|
@ -70,6 +92,19 @@ struct TestData {
|
||||||
if (i & 1) hash160_preimages[hash] = std::vector<unsigned char>(keydata, keydata + 32);
|
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;
|
} TEST_DATA;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,6 +115,8 @@ struct TestData {
|
||||||
struct ParserContext {
|
struct ParserContext {
|
||||||
typedef CPubKey Key;
|
typedef CPubKey Key;
|
||||||
|
|
||||||
|
MsCtx script_ctx{MsCtx::P2WSH};
|
||||||
|
|
||||||
bool KeyCompare(const Key& a, const Key& b) const {
|
bool KeyCompare(const Key& a, const Key& b) const {
|
||||||
return a < b;
|
return a < b;
|
||||||
}
|
}
|
||||||
|
@ -92,14 +129,20 @@ struct ParserContext {
|
||||||
return HexStr(Span{&idx, 1});
|
return HexStr(Span{&idx, 1});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> ToPKBytes(const Key& key) const
|
std::vector<unsigned char> ToPKBytes(const Key& key) const {
|
||||||
{
|
if (!miniscript::IsTapscript(script_ctx)) {
|
||||||
return {key.begin(), key.end()};
|
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
|
std::vector<unsigned char> ToPKHBytes(const Key& key) const {
|
||||||
{
|
if (!miniscript::IsTapscript(script_ctx)) {
|
||||||
const auto h = Hash160(key);
|
const auto h = Hash160(key);
|
||||||
|
return {h.begin(), h.end()};
|
||||||
|
}
|
||||||
|
const auto h = Hash160(XOnlyPubKey{key});
|
||||||
return {h.begin(), h.end()};
|
return {h.begin(), h.end()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,10 +156,15 @@ struct ParserContext {
|
||||||
|
|
||||||
template<typename I>
|
template<typename I>
|
||||||
std::optional<Key> FromPKBytes(I first, I last) const {
|
std::optional<Key> FromPKBytes(I first, I last) const {
|
||||||
CPubKey key;
|
if (!miniscript::IsTapscript(script_ctx)) {
|
||||||
key.Set(first, last);
|
Key key{first, last};
|
||||||
if (!key.IsValid()) return {};
|
if (key.IsValid()) return key;
|
||||||
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>
|
template<typename I>
|
||||||
|
@ -128,10 +176,16 @@ struct ParserContext {
|
||||||
if (it == TEST_DATA.dummy_keys_map.end()) return {};
|
if (it == TEST_DATA.dummy_keys_map.end()) return {};
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MsCtx MsContext() const {
|
||||||
|
return script_ctx;
|
||||||
|
}
|
||||||
} PARSER_CTX;
|
} PARSER_CTX;
|
||||||
|
|
||||||
//! Context that implements naive conversion from/to script only, for roundtrip testing.
|
//! Context that implements naive conversion from/to script only, for roundtrip testing.
|
||||||
struct ScriptParserContext {
|
struct ScriptParserContext {
|
||||||
|
MsCtx script_ctx{MsCtx::P2WSH};
|
||||||
|
|
||||||
//! For Script roundtrip we never need the key from a key hash.
|
//! For Script roundtrip we never need the key from a key hash.
|
||||||
struct Key {
|
struct Key {
|
||||||
bool is_hash;
|
bool is_hash;
|
||||||
|
@ -172,6 +226,10 @@ struct ScriptParserContext {
|
||||||
key.is_hash = true;
|
key.is_hash = true;
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MsCtx MsContext() const {
|
||||||
|
return script_ctx;
|
||||||
|
}
|
||||||
} SCRIPT_PARSER_CONTEXT;
|
} SCRIPT_PARSER_CONTEXT;
|
||||||
|
|
||||||
//! Context to produce a satisfaction for a Miniscript node using the pre-computed data.
|
//! 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.
|
// 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 {
|
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
|
||||||
const auto it = TEST_DATA.dummy_sigs.find(key);
|
bool sig_available{false};
|
||||||
if (it == TEST_DATA.dummy_sigs.end()) return miniscript::Availability::NO;
|
if (auto res = TEST_DATA.GetSig(script_ctx, key)) {
|
||||||
if (it->second.second) {
|
std::tie(sig, sig_available) = *res;
|
||||||
// Key is "available"
|
|
||||||
sig = it->second.first;
|
|
||||||
return miniscript::Availability::YES;
|
|
||||||
} else {
|
|
||||||
return miniscript::Availability::NO;
|
|
||||||
}
|
}
|
||||||
|
return sig_available ? miniscript::Availability::YES : miniscript::Availability::NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Lookup generalization for all the hash satisfactions below
|
//! Lookup generalization for all the hash satisfactions below
|
||||||
|
@ -230,6 +284,13 @@ struct CheckerContext: BaseSignatureChecker {
|
||||||
if (it == TEST_DATA.dummy_sigs.end()) return false;
|
if (it == TEST_DATA.dummy_sigs.end()) return false;
|
||||||
return it->second.first == sig;
|
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 CheckLockTime(const CScriptNum& nLockTime) const override { return nLockTime.GetInt64() & 1; }
|
||||||
bool CheckSequence(const CScriptNum& nSequence) const override { return nSequence.GetInt64() & 1; }
|
bool CheckSequence(const CScriptNum& nSequence) const override { return nSequence.GetInt64() & 1; }
|
||||||
} CHECKER_CTX;
|
} CHECKER_CTX;
|
||||||
|
@ -244,11 +305,8 @@ struct KeyComparator {
|
||||||
// A dummy scriptsig to pass to VerifyScript (we always use Segwit v0).
|
// A dummy scriptsig to pass to VerifyScript (we always use Segwit v0).
|
||||||
const CScript DUMMY_SCRIPTSIG;
|
const CScript DUMMY_SCRIPTSIG;
|
||||||
|
|
||||||
using Fragment = miniscript::Fragment;
|
//! Public key to be used as internal key for dummy Taproot spends.
|
||||||
using NodeRef = miniscript::NodeRef<CPubKey>;
|
const std::vector<unsigned char> NUMS_PK{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")};
|
||||||
using Node = miniscript::Node<CPubKey>;
|
|
||||||
using Type = miniscript::Type;
|
|
||||||
using miniscript::operator"" _mst;
|
|
||||||
|
|
||||||
//! Construct a miniscript node as a shared_ptr.
|
//! Construct a miniscript node as a shared_ptr.
|
||||||
template<typename... Args> NodeRef MakeNodeRef(Args&&... args) {
|
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 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
|
* - 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.
|
* 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.
|
* - 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_B = (type_needed == ""_mst) || (type_needed << "B"_mst);
|
||||||
bool allow_K = (type_needed == ""_mst) || (type_needed << "K"_mst);
|
bool allow_K = (type_needed == ""_mst) || (type_needed << "K"_mst);
|
||||||
bool allow_V = (type_needed == ""_mst) || (type_needed << "V"_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 {};
|
if (!allow_B) return {};
|
||||||
return {{Fragment::HASH160, ConsumeHash160(provider)}};
|
return {{Fragment::HASH160, ConsumeHash160(provider)}};
|
||||||
case 10: {
|
case 10: {
|
||||||
if (!allow_B) return {};
|
if (!allow_B || IsTapscript(script_ctx)) return {};
|
||||||
const auto k = provider.ConsumeIntegral<uint8_t>();
|
const auto k = provider.ConsumeIntegral<uint8_t>();
|
||||||
const auto n_keys = provider.ConsumeIntegral<uint8_t>();
|
const auto n_keys = provider.ConsumeIntegral<uint8_t>();
|
||||||
if (n_keys > 20 || k == 0 || k > n_keys) return {};
|
if (n_keys > 20 || k == 0 || k > n_keys) return {};
|
||||||
|
@ -428,6 +487,15 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ
|
||||||
case 26:
|
case 26:
|
||||||
if (!allow_B) return {};
|
if (!allow_B) return {};
|
||||||
return {{{"B"_mst}, Fragment::WRAP_N}};
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -444,9 +512,15 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ
|
||||||
struct SmartInfo
|
struct SmartInfo
|
||||||
{
|
{
|
||||||
using recipe = std::pair<Fragment, std::vector<Type>>;
|
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()
|
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). */
|
/* Construct a set of interesting type requirements to reason with (sections of BKVWzondu). */
|
||||||
std::vector<Type> types;
|
std::vector<Type> types;
|
||||||
|
@ -495,7 +569,7 @@ struct SmartInfo
|
||||||
std::sort(types.begin(), types.end());
|
std::sort(types.begin(), types.end());
|
||||||
|
|
||||||
// Iterate over all possible fragments.
|
// 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_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.
|
int sub_range = 1; //!< The maximum number of child nodes for this recipe is sub_count+sub_range-1.
|
||||||
size_t data_size = 0;
|
size_t data_size = 0;
|
||||||
|
@ -503,6 +577,12 @@ struct SmartInfo
|
||||||
uint32_t k = 0;
|
uint32_t k = 0;
|
||||||
Fragment frag{fragidx};
|
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. */
|
// Based on the fragment, determine #subs/data/k/keys to pass to ComputeType. */
|
||||||
switch (frag) {
|
switch (frag) {
|
||||||
case Fragment::PK_K:
|
case Fragment::PK_K:
|
||||||
|
@ -510,6 +590,7 @@ struct SmartInfo
|
||||||
n_keys = 1;
|
n_keys = 1;
|
||||||
break;
|
break;
|
||||||
case Fragment::MULTI:
|
case Fragment::MULTI:
|
||||||
|
case Fragment::MULTI_A:
|
||||||
n_keys = 1;
|
n_keys = 1;
|
||||||
k = 1;
|
k = 1;
|
||||||
break;
|
break;
|
||||||
|
@ -568,7 +649,7 @@ struct SmartInfo
|
||||||
if (subs > 0) subt.push_back(x);
|
if (subs > 0) subt.push_back(x);
|
||||||
if (subs > 1) subt.push_back(y);
|
if (subs > 1) subt.push_back(y);
|
||||||
if (subs > 2) subt.push_back(z);
|
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.
|
// Continue if the result is not a valid node.
|
||||||
if ((res << "K"_mst) + (res << "V"_mst) + (res << "B"_mst) + (res << "W"_mst) != 1) continue;
|
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
|
* (as improvements to the tables or changes to the typing rules could invalidate
|
||||||
* everything).
|
* 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. */
|
/** Table entry for the requested type. */
|
||||||
auto recipes_it = SMARTINFO.table.find(type_needed);
|
const auto& table{IsTapscript(script_ctx) ? SMARTINFO.tap_table : SMARTINFO.wsh_table};
|
||||||
assert(recipes_it != SMARTINFO.table.end());
|
auto recipes_it = table.find(type_needed);
|
||||||
|
assert(recipes_it != table.end());
|
||||||
/** Pick one recipe from the available ones for that type. */
|
/** Pick one recipe from the available ones for that type. */
|
||||||
const auto& [frag, subt] = PickValue(provider, recipes_it->second);
|
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);
|
for (auto& key: keys) key = ConsumePubKey(provider);
|
||||||
return {{frag, k, std::move(keys)}};
|
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::OLDER:
|
||||||
case Fragment::AFTER:
|
case Fragment::AFTER:
|
||||||
return {{frag, provider.ConsumeIntegralInRange<uint32_t>(1, 0x7FFFFFF)}};
|
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.
|
* a NodeRef whose Type() matches the type fed to ConsumeNode.
|
||||||
*/
|
*/
|
||||||
template<typename F>
|
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. */
|
/** A stack of miniscript Nodes being built up. */
|
||||||
std::vector<NodeRef> stack;
|
std::vector<NodeRef> stack;
|
||||||
/** The queue of instructions. */
|
/** 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
|
// 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
|
// 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.
|
// 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 {};
|
if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
|
||||||
switch (node_info->fragment) {
|
switch (node_info->fragment) {
|
||||||
case Fragment::JUST_0:
|
case Fragment::JUST_0:
|
||||||
|
@ -826,6 +916,9 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
||||||
case Fragment::MULTI:
|
case Fragment::MULTI:
|
||||||
ops += 1;
|
ops += 1;
|
||||||
break;
|
break;
|
||||||
|
case Fragment::MULTI_A:
|
||||||
|
ops += node_info->keys.size() + 1;
|
||||||
|
break;
|
||||||
case Fragment::WRAP_A:
|
case Fragment::WRAP_A:
|
||||||
ops += 2;
|
ops += 2;
|
||||||
break;
|
break;
|
||||||
|
@ -874,11 +967,11 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
||||||
// Construct new NodeRef.
|
// Construct new NodeRef.
|
||||||
NodeRef node;
|
NodeRef node;
|
||||||
if (info.keys.empty()) {
|
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 {
|
} else {
|
||||||
assert(sub.empty());
|
assert(sub.empty());
|
||||||
assert(info.hash.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.
|
// Verify acceptability.
|
||||||
if (!node || (node->GetType() & "KVWB"_mst) == ""_mst) {
|
if (!node || (node->GetType() & "KVWB"_mst) == ""_mst) {
|
||||||
|
@ -894,8 +987,10 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
||||||
ops += 1;
|
ops += 1;
|
||||||
scriptsize += 1;
|
scriptsize += 1;
|
||||||
}
|
}
|
||||||
if (ops > MAX_OPS_PER_SCRIPT) return {};
|
if (!miniscript::IsTapscript(script_ctx) && ops > MAX_OPS_PER_SCRIPT) return {};
|
||||||
if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
|
if (scriptsize > miniscript::internal::MaxScriptSize(script_ctx)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
// Move it to the stack.
|
// Move it to the stack.
|
||||||
stack.push_back(std::move(node));
|
stack.push_back(std::move(node));
|
||||||
todo.pop_back();
|
todo.pop_back();
|
||||||
|
@ -908,12 +1003,33 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
|
||||||
return std::move(stack[0]);
|
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. */
|
/** 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;
|
if (!node) return;
|
||||||
|
|
||||||
// Check that it roundtrips to text representation
|
// Check that it roundtrips to text representation
|
||||||
|
PARSER_CTX.script_ctx = script_ctx;
|
||||||
std::optional<std::string> str{node->ToString(PARSER_CTX)};
|
std::optional<std::string> str{node->ToString(PARSER_CTX)};
|
||||||
assert(str);
|
assert(str);
|
||||||
auto parsed = miniscript::FromString(*str, PARSER_CTX);
|
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).
|
// with a push of a key, which could match these opcodes).
|
||||||
if (!(node->GetType() << "K"_mst)) {
|
if (!(node->GetType() << "K"_mst)) {
|
||||||
bool ends_in_verify = !(node->GetType() << "x"_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.
|
// 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->ToScript(PARSER_CTX) == script);
|
||||||
assert(decoded->GetType() == node->GetType());
|
assert(decoded->GetType() == node->GetType());
|
||||||
|
|
||||||
const auto node_ops{node->GetOps()};
|
// Optionally pad the script or the witness in order to increase the sensitivity of the tests of
|
||||||
if (provider.ConsumeBool() && node_ops && *node_ops < MAX_OPS_PER_SCRIPT && node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
// the resources limits logic.
|
||||||
// Optionally pad the script with OP_NOPs to max op the ops limit of the constructed script.
|
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
|
// 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
|
// 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
|
// 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.
|
// maximal.
|
||||||
// Do not pad more than what would cause MAX_STANDARD_P2WSH_SCRIPT_SIZE to be reached, however,
|
// Do not pad more than what would cause MAX_STANDARD_P2WSH_SCRIPT_SIZE to be reached, however,
|
||||||
// as that also invalidates scripts.
|
// as that also invalidates scripts.
|
||||||
int add = std::min<int>(
|
const auto node_ops{node->GetOps()};
|
||||||
MAX_OPS_PER_SCRIPT - *node_ops,
|
if (!IsTapscript(script_ctx) && node_ops && *node_ops < MAX_OPS_PER_SCRIPT
|
||||||
MAX_STANDARD_P2WSH_SCRIPT_SIZE - node->ScriptSize());
|
&& node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
||||||
for (int i = 0; i < add; ++i) script.push_back(OP_NOP);
|
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.
|
// Run malleable satisfaction algorithm.
|
||||||
const CScript script_pubkey = CScript() << OP_0 << WitnessV0ScriptHash(script);
|
std::vector<std::vector<unsigned char>> stack_mal;
|
||||||
CScriptWitness witness_mal;
|
const bool mal_success = node->Satisfy(SATISFIER_CTX, stack_mal, false) == miniscript::Availability::YES;
|
||||||
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()));
|
|
||||||
|
|
||||||
// Run non-malleable satisfaction algorithm.
|
// Run non-malleable satisfaction algorithm.
|
||||||
CScriptWitness witness_nonmal;
|
std::vector<std::vector<unsigned char>> stack_nonmal;
|
||||||
const bool nonmal_success = node->Satisfy(SATISFIER_CTX, witness_nonmal.stack, true) == miniscript::Availability::YES;
|
const bool nonmal_success = node->Satisfy(SATISFIER_CTX, stack_nonmal, true) == miniscript::Availability::YES;
|
||||||
witness_nonmal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end()));
|
|
||||||
|
|
||||||
if (nonmal_success) {
|
if (nonmal_success) {
|
||||||
// Non-malleable satisfactions are bounded by GetStackSize().
|
// Non-malleable satisfactions are bounded by the satisfaction size plus:
|
||||||
assert(witness_nonmal.stack.size() <= *node->GetStackSize() + 1);
|
// - 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.
|
// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.
|
||||||
assert(mal_success);
|
assert(mal_success);
|
||||||
assert(witness_nonmal.stack == witness_mal.stack);
|
assert(stack_nonmal == stack_mal);
|
||||||
|
|
||||||
// Test non-malleable satisfaction.
|
// 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;
|
ScriptError serror;
|
||||||
bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_nonmal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &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().
|
// 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)) {
|
if (mal_success && (!nonmal_success || witness_mal.stack != witness_nonmal.stack)) {
|
||||||
// Test malleable satisfaction only if it's different from the non-malleable one.
|
// 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;
|
ScriptError serror;
|
||||||
bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_mal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &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
|
// 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
|
// algorithm succeeds. Given that under IsSane() both satisfactions
|
||||||
// are identical, this implies that for such nodes, the non-malleable
|
// are identical, this implies that for such nodes, the non-malleable
|
||||||
// satisfaction will also match the expected policy.
|
// 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) {
|
switch (node.fragment) {
|
||||||
case Fragment::PK_K:
|
case Fragment::PK_K:
|
||||||
case Fragment::PK_H: {
|
case Fragment::PK_H:
|
||||||
auto it = TEST_DATA.dummy_sigs.find(node.keys[0]);
|
return is_key_satisfiable(node.keys[0]);
|
||||||
assert(it != TEST_DATA.dummy_sigs.end());
|
case Fragment::MULTI:
|
||||||
return it->second.second;
|
case Fragment::MULTI_A: {
|
||||||
}
|
size_t sats = std::count_if(node.keys.begin(), node.keys.end(), [&](const auto& key) {
|
||||||
case Fragment::MULTI: {
|
return size_t(is_key_satisfiable(key));
|
||||||
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;
|
|
||||||
}
|
|
||||||
return sats >= node.k;
|
return sats >= node.k;
|
||||||
}
|
}
|
||||||
case Fragment::OLDER:
|
case Fragment::OLDER:
|
||||||
|
@ -1061,10 +1203,13 @@ void FuzzInitSmart()
|
||||||
/** Fuzz target that runs TestNode on nodes generated using ConsumeNodeStable. */
|
/** Fuzz target that runs TestNode on nodes generated using ConsumeNodeStable. */
|
||||||
FUZZ_TARGET(miniscript_stable, .init = FuzzInit)
|
FUZZ_TARGET(miniscript_stable, .init = FuzzInit)
|
||||||
{
|
{
|
||||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
// Run it under both P2WSH and Tapscript contexts.
|
||||||
TestNode(GenNode([&](Type needed_type) {
|
for (const auto script_ctx: {MsCtx::P2WSH, MsCtx::TAPSCRIPT}) {
|
||||||
return ConsumeNodeStable(provider, needed_type);
|
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||||
}, ""_mst), provider);
|
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. */
|
/** 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};
|
static constexpr std::array<Type, 4> BASE_TYPES{"B"_mst, "V"_mst, "K"_mst, "W"_mst};
|
||||||
|
|
||||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||||
TestNode(GenNode([&](Type needed_type) {
|
const auto script_ctx{(MsCtx)provider.ConsumeBool()};
|
||||||
return ConsumeNodeSmart(provider, needed_type);
|
TestNode(script_ctx, GenNode(script_ctx, [&](Type needed_type) {
|
||||||
|
return ConsumeNodeSmart(script_ctx, provider, needed_type);
|
||||||
}, PickValue(provider, BASE_TYPES), true), provider);
|
}, PickValue(provider, BASE_TYPES), true), provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fuzz tests that test parsing from a string, and roundtripping via string. */
|
/* Fuzz tests that test parsing from a string, and roundtripping via string. */
|
||||||
FUZZ_TARGET(miniscript_string, .init = FuzzInit)
|
FUZZ_TARGET(miniscript_string, .init = FuzzInit)
|
||||||
{
|
{
|
||||||
|
if (buffer.empty()) return;
|
||||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
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);
|
auto parsed = miniscript::FromString(str, PARSER_CTX);
|
||||||
if (!parsed) return;
|
if (!parsed) return;
|
||||||
|
|
||||||
|
@ -1101,6 +1249,7 @@ FUZZ_TARGET(miniscript_script)
|
||||||
const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
|
const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
|
||||||
if (!script) return;
|
if (!script) return;
|
||||||
|
|
||||||
|
SCRIPT_PARSER_CONTEXT.script_ctx = (MsCtx)fuzzed_data_provider.ConsumeBool();
|
||||||
const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT);
|
const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT);
|
||||||
if (!ms) return;
|
if (!ms) return;
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <script/interpreter.h>
|
#include <script/interpreter.h>
|
||||||
#include <script/miniscript.h>
|
#include <script/miniscript.h>
|
||||||
#include <script/script_error.h>
|
#include <script/script_error.h>
|
||||||
|
#include <script/signingprovider.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -30,7 +31,9 @@ struct TestData {
|
||||||
//! A map from the public keys to their CKeyIDs (faster than hashing every time).
|
//! A map from the public keys to their CKeyIDs (faster than hashing every time).
|
||||||
std::map<CPubKey, CKeyID> pkhashes;
|
std::map<CPubKey, CKeyID> pkhashes;
|
||||||
std::map<CKeyID, CPubKey> pkmap;
|
std::map<CKeyID, CPubKey> pkmap;
|
||||||
|
std::map<XOnlyPubKey, CKeyID> xonly_pkhashes;
|
||||||
std::map<CPubKey, std::vector<unsigned char>> signatures;
|
std::map<CPubKey, std::vector<unsigned char>> signatures;
|
||||||
|
std::map<XOnlyPubKey, std::vector<unsigned char>> schnorr_signatures;
|
||||||
|
|
||||||
// Various precomputed hashes
|
// Various precomputed hashes
|
||||||
std::vector<std::vector<unsigned char>> sha256;
|
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.
|
// All our signatures sign (and are required to sign) this constant message.
|
||||||
auto const MESSAGE_HASH = uint256S("f5cd94e18b6fe77dd7aca9e35c2b0c9cbd86356c80a71065");
|
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.
|
// We generate 255 public keys and 255 hashes of each type.
|
||||||
for (int i = 1; i <= 255; ++i) {
|
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).
|
// 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);
|
pubkeys.push_back(pubkey);
|
||||||
pkhashes.emplace(pubkey, keyid);
|
pkhashes.emplace(pubkey, keyid);
|
||||||
pkmap.emplace(keyid, pubkey);
|
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.
|
// 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));
|
BOOST_CHECK(key.Sign(MESSAGE_HASH, sig));
|
||||||
sig.push_back(1); // sighash byte
|
sig.push_back(1); // sighash byte
|
||||||
signatures.emplace(pubkey, sig);
|
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
|
// Compute various hashes
|
||||||
std::vector<unsigned char> hash;
|
std::vector<unsigned char> hash;
|
||||||
|
@ -114,19 +126,30 @@ typedef std::pair<ChallengeType, uint32_t> Challenge;
|
||||||
struct KeyConverter {
|
struct KeyConverter {
|
||||||
typedef CPubKey Key;
|
typedef CPubKey Key;
|
||||||
|
|
||||||
|
miniscript::MiniscriptContext m_script_ctx{miniscript::MiniscriptContext::P2WSH};
|
||||||
|
|
||||||
bool KeyCompare(const Key& a, const Key& b) const {
|
bool KeyCompare(const Key& a, const Key& b) const {
|
||||||
return a < b;
|
return a < b;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Convert a public key to bytes.
|
//! 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).
|
//! Convert a public key to its Hash160 bytes (precomputed).
|
||||||
std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const
|
std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const {
|
||||||
{
|
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||||
auto it = g_testdata->pkhashes.find(key);
|
auto hash = g_testdata->pkhashes.at(key);
|
||||||
assert(it != g_testdata->pkhashes.end());
|
return {hash.begin(), hash.end()};
|
||||||
return {it->second.begin(), it->second.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.
|
//! Parse a public key from a range of hex characters.
|
||||||
|
@ -140,9 +163,15 @@ struct KeyConverter {
|
||||||
|
|
||||||
template<typename I>
|
template<typename I>
|
||||||
std::optional<Key> FromPKBytes(I first, I last) const {
|
std::optional<Key> FromPKBytes(I first, I last) const {
|
||||||
Key key{first, last};
|
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||||
if (key.IsValid()) return key;
|
Key key{first, last};
|
||||||
return {};
|
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>
|
template<typename I>
|
||||||
|
@ -150,14 +179,20 @@ struct KeyConverter {
|
||||||
assert(last - first == 20);
|
assert(last - first == 20);
|
||||||
CKeyID keyid;
|
CKeyID keyid;
|
||||||
std::copy(first, last, keyid.begin());
|
std::copy(first, last, keyid.begin());
|
||||||
auto it = g_testdata->pkmap.find(keyid);
|
return g_testdata->pkmap.at(keyid);
|
||||||
assert(it != g_testdata->pkmap.end());
|
|
||||||
return it->second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> ToString(const Key& key) const {
|
std::optional<std::string> ToString(const Key& key) const {
|
||||||
return HexStr(ToPKBytes(key));
|
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. */
|
/** A class that encapsulates all signing/hash revealing operations. */
|
||||||
|
@ -178,9 +213,15 @@ struct Satisfier : public KeyConverter {
|
||||||
//! Produce a signature for the given key.
|
//! Produce a signature for the given key.
|
||||||
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
|
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
|
||||||
if (supported.count(Challenge(ChallengeType::PK, ChallengeNumber(key)))) {
|
if (supported.count(Challenge(ChallengeType::PK, ChallengeNumber(key)))) {
|
||||||
auto it = g_testdata->signatures.find(key);
|
if (!miniscript::IsTapscript(m_script_ctx)) {
|
||||||
if (it == g_testdata->signatures.end()) return miniscript::Availability::NO;
|
auto it = g_testdata->signatures.find(key);
|
||||||
sig = it->second;
|
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::YES;
|
||||||
}
|
}
|
||||||
return miniscript::Availability::NO;
|
return miniscript::Availability::NO;
|
||||||
|
@ -226,6 +267,14 @@ public:
|
||||||
return sig == it->second;
|
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 {
|
bool CheckLockTime(const CScriptNum& locktime) const override {
|
||||||
// Delegate to Satisfier.
|
// Delegate to Satisfier.
|
||||||
return ctx.CheckAfter(locktime.GetInt64());
|
return ctx.CheckAfter(locktime.GetInt64());
|
||||||
|
@ -238,7 +287,10 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Singleton instance of KeyConverter.
|
//! 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 Fragment = miniscript::Fragment;
|
||||||
using NodeRef = miniscript::NodeRef<CPubKey>;
|
using NodeRef = miniscript::NodeRef<CPubKey>;
|
||||||
|
@ -271,34 +323,61 @@ std::set<Challenge> FindChallenges(const NodeRef& ref) {
|
||||||
return chal;
|
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. */
|
/** Run random satisfaction tests. */
|
||||||
void TestSatisfy(const std::string& testcase, const NodeRef& node) {
|
void TestSatisfy(const KeyConverter& converter, const std::string& testcase, const NodeRef& node) {
|
||||||
auto script = node->ToScript(CONVERTER);
|
auto script = node->ToScript(converter);
|
||||||
auto challenges = FindChallenges(node); // Find all challenges in the generated miniscript.
|
auto challenges = FindChallenges(node); // Find all challenges in the generated miniscript.
|
||||||
std::vector<Challenge> challist(challenges.begin(), challenges.end());
|
std::vector<Challenge> challist(challenges.begin(), challenges.end());
|
||||||
for (int iter = 0; iter < 3; ++iter) {
|
for (int iter = 0; iter < 3; ++iter) {
|
||||||
Shuffle(challist.begin(), challist.end(), g_insecure_rand_ctx);
|
Shuffle(challist.begin(), challist.end(), g_insecure_rand_ctx);
|
||||||
Satisfier satisfier;
|
Satisfier satisfier;
|
||||||
|
satisfier.SetContext(converter.MsContext());
|
||||||
TestSignatureChecker checker(satisfier);
|
TestSignatureChecker checker(satisfier);
|
||||||
bool prev_mal_success = false, prev_nonmal_success = false;
|
bool prev_mal_success = false, prev_nonmal_success = false;
|
||||||
// Go over all challenges involved in this miniscript in random order.
|
// Go over all challenges involved in this miniscript in random order.
|
||||||
for (int add = -1; add < (int)challist.size(); ++add) {
|
for (int add = -1; add < (int)challist.size(); ++add) {
|
||||||
if (add >= 0) satisfier.supported.insert(challist[add]); // The first iteration does not add anything
|
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.
|
// Run malleable satisfaction algorithm.
|
||||||
const CScript script_pubkey = CScript() << OP_0 << WitnessV0ScriptHash(script);
|
|
||||||
CScriptWitness witness_mal;
|
CScriptWitness witness_mal;
|
||||||
const bool mal_success = node->Satisfy(satisfier, witness_mal.stack, false) == miniscript::Availability::YES;
|
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.
|
// Run non-malleable satisfaction algorithm.
|
||||||
CScriptWitness witness_nonmal;
|
CScriptWitness witness_nonmal;
|
||||||
const bool nonmal_success = node->Satisfy(satisfier, witness_nonmal.stack, true) == miniscript::Availability::YES;
|
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) {
|
if (nonmal_success) {
|
||||||
// Non-malleable satisfactions are bounded by GetStackSize().
|
// Non-malleable satisfactions are bounded by the satisfaction size plus:
|
||||||
BOOST_CHECK(witness_nonmal.stack.size() <= *node->GetStackSize() + 1);
|
// - 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.
|
// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.
|
||||||
BOOST_CHECK(mal_success);
|
BOOST_CHECK(mal_success);
|
||||||
BOOST_CHECK(witness_nonmal.stack == witness_mal.stack);
|
BOOST_CHECK(witness_nonmal.stack == witness_mal.stack);
|
||||||
|
@ -351,37 +430,57 @@ void TestSatisfy(const std::string& testcase, const NodeRef& node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TestMode : int {
|
enum TestMode : int {
|
||||||
|
//! Invalid under any context
|
||||||
TESTMODE_INVALID = 0,
|
TESTMODE_INVALID = 0,
|
||||||
|
//! Valid under any context unless overridden
|
||||||
TESTMODE_VALID = 1,
|
TESTMODE_VALID = 1,
|
||||||
TESTMODE_NONMAL = 2,
|
TESTMODE_NONMAL = 2,
|
||||||
TESTMODE_NEEDSIG = 4,
|
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);
|
auto node = miniscript::FromString(ms, converter);
|
||||||
if (mode == TESTMODE_INVALID) {
|
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);
|
BOOST_CHECK_MESSAGE(!node || !node->IsValid(), "Unexpectedly valid: " + ms);
|
||||||
} else {
|
} else {
|
||||||
BOOST_CHECK_MESSAGE(node, "Unparseable: " + ms);
|
BOOST_CHECK_MESSAGE(node, "Unparseable: " + ms);
|
||||||
BOOST_CHECK_MESSAGE(node->IsValid(), "Invalid: " + ms);
|
BOOST_CHECK_MESSAGE(node->IsValid(), "Invalid: " + ms);
|
||||||
BOOST_CHECK_MESSAGE(node->IsValidTopLevel(), "Invalid top level: " + 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);
|
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 + ")");
|
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->IsNonMalleable() == !!(mode & TESTMODE_NONMAL), "Malleability mismatch: " + ms);
|
||||||
BOOST_CHECK_MESSAGE(node->NeedsSignature() == !!(mode & TESTMODE_NEEDSIG), "Signature necessity 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);
|
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, "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 (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 (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 << ")");
|
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
|
} // namespace
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_SUITE(miniscript_tests, BasicTestingSetup)
|
BOOST_FIXTURE_TEST_SUITE(miniscript_tests, BasicTestingSetup)
|
||||||
|
@ -391,94 +490,146 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
|
||||||
g_testdata.reset(new TestData());
|
g_testdata.reset(new TestData());
|
||||||
|
|
||||||
// Validity rules
|
// Validity rules
|
||||||
Test("l:older(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(1): valid
|
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(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(2147483647)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(2147483647): valid
|
||||||
Test("l:older(2147483648)", "?", TESTMODE_INVALID); // older(2147483648): k must be below 2^31
|
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(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(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(2147483647)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(2147483647): valid
|
||||||
Test("u:after(2147483648)", "?", TESTMODE_INVALID); // after(2147483648): k must be below 2^31
|
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(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(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(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(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(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("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("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("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("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("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("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(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(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_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,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(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(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(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("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(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(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,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(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(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(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("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(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(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(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(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(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("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(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(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(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(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(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_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("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("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("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_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("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("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("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", 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("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
|
// 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("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, 14, 5, 2 + 2 + 1 + 2 * 73);
|
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, 14, 5, 2 + 1 + 2 * 73 + 2);
|
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);
|
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", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 2 + 33 + 33);
|
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, 13, 5, 1 + 3 * 73);
|
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, 15, 7, 2 + 1 + 3 * 73 + 1);
|
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", TESTMODE_VALID, 16, 1, 33);
|
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, 11, 5, 2 + 1 + 2 * 73 + 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 | 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, 14, 4, 1 + 2 * 73 + 2);
|
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);
|
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);
|
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);
|
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, 13, 6, 1 + 2 * 73 + 1 + 73 + 1);
|
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);
|
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);
|
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, 8, 2, 33 + 73);
|
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, 10, 5, 1 + 2 * 73 + 73);
|
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);
|
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);
|
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", TESTMODE_VALID | TESTMODE_NONMAL, 10, 2, 2 + 73);
|
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", TESTMODE_VALID, 18, 4, 1 + 34 + 33 + 33);
|
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", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 3, 33 + 2 + 73);
|
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", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 2, 73 + 1);
|
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", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 3, 2 + 34 + 73);
|
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", TESTMODE_VALID | TESTMODE_NONMAL, 13, 3, 1 + 34 + 73);
|
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", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 3, 33 + 34 + 73);
|
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", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 4, 2 + 33 + 34 + 73);
|
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", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 5, 2 + 34 + 73 + 34 + 73);
|
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", TESTMODE_VALID, 18, 3, 73 + 2 + 2);
|
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", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 4, 73 + 73 + 2 + 2);
|
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
|
// Misc unit tests
|
||||||
// A Script with a non minimal push is invalid
|
// 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());
|
const CScript nonminverify_script(nonminverify.begin(), nonminverify.end());
|
||||||
BOOST_CHECK(miniscript::FromScript(nonminverify_script, CONVERTER) == nullptr);
|
BOOST_CHECK(miniscript::FromScript(nonminverify_script, CONVERTER) == nullptr);
|
||||||
// A threshold as large as the number of subs is valid.
|
// 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.
|
// 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
|
// 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
|
// 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)
|
// 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);
|
const auto ms_multi = miniscript::FromString("multi(1,03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", CONVERTER);
|
||||||
BOOST_CHECK(ms_multi);
|
BOOST_CHECK(ms_multi);
|
||||||
BOOST_CHECK_EQUAL(*ms_multi->GetOps(), 4); // 3 pubkeys + CMS
|
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))");
|
BOOST_CHECK(insane_sub && *insane_sub->ToString(CONVERTER) == "and_b(after(1),a:after(1000000000))");
|
||||||
|
|
||||||
// Timelock tests
|
// Timelock tests
|
||||||
Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock
|
Test("after(100)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock
|
||||||
Test("after(1000000000)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only timelock
|
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("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("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
|
/* 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.
|
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.
|
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
|
// 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();
|
g_testdata.reset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ BASE_SCRIPTS = [
|
||||||
'feature_maxuploadtarget.py',
|
'feature_maxuploadtarget.py',
|
||||||
'mempool_updatefromblock.py',
|
'mempool_updatefromblock.py',
|
||||||
'mempool_persist.py --descriptors',
|
'mempool_persist.py --descriptors',
|
||||||
|
'wallet_miniscript.py --descriptors',
|
||||||
# vv Tests less than 60s vv
|
# vv Tests less than 60s vv
|
||||||
'rpc_psbt.py --legacy-wallet',
|
'rpc_psbt.py --legacy-wallet',
|
||||||
'rpc_psbt.py --descriptors',
|
'rpc_psbt.py --descriptors',
|
||||||
|
@ -242,7 +243,6 @@ BASE_SCRIPTS = [
|
||||||
'wallet_keypool.py --legacy-wallet',
|
'wallet_keypool.py --legacy-wallet',
|
||||||
'wallet_keypool.py --descriptors',
|
'wallet_keypool.py --descriptors',
|
||||||
'wallet_descriptor.py --descriptors',
|
'wallet_descriptor.py --descriptors',
|
||||||
'wallet_miniscript.py --descriptors',
|
|
||||||
'p2p_nobloomfilter_messages.py',
|
'p2p_nobloomfilter_messages.py',
|
||||||
'p2p_filter.py',
|
'p2p_filter.py',
|
||||||
'rpc_setban.py',
|
'rpc_setban.py',
|
||||||
|
|
|
@ -22,6 +22,7 @@ TPUBS = [
|
||||||
"tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a",
|
"tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a",
|
||||||
"tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy",
|
"tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy",
|
||||||
"tpubDEFLeBkKTm8aiYkySz8hXAXPVnPSfxMi7Fxhg9sejUrkwJuRWvPdLEiXjTDbhGbjLKCZUDUUibLxTnK5UP1q7qYrSnPqnNe7M8mvAW1STcc",
|
"tpubDEFLeBkKTm8aiYkySz8hXAXPVnPSfxMi7Fxhg9sejUrkwJuRWvPdLEiXjTDbhGbjLKCZUDUUibLxTnK5UP1q7qYrSnPqnNe7M8mvAW1STcc",
|
||||||
|
"tpubD6NzVbkrYhZ4WR99ygpiJvPMAJiwahjLgGywc5vJx2gUfKUfEPCrbKmQczDPJZmLcyZzRb5Ti6rfUb89S2WFyPH7FDtD6RFDA1hdgTEgEUL",
|
||||||
]
|
]
|
||||||
PUBKEYS = [
|
PUBKEYS = [
|
||||||
"02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068",
|
"02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068",
|
||||||
|
@ -33,7 +34,7 @@ PUBKEYS = [
|
||||||
"0211c7b2e18b6fd330f322de087da62da92ae2ae3d0b7cec7e616479cce175f183",
|
"0211c7b2e18b6fd330f322de087da62da92ae2ae3d0b7cec7e616479cce175f183",
|
||||||
]
|
]
|
||||||
|
|
||||||
MINISCRIPTS = [
|
P2WSH_MINISCRIPTS = [
|
||||||
# One of two keys
|
# One of two keys
|
||||||
f"or_b(pk({TPUBS[0]}/*),s:pk({TPUBS[1]}/*))",
|
f"or_b(pk({TPUBS[0]}/*),s:pk({TPUBS[1]}/*))",
|
||||||
# A script similar (same spending policy) to BOLT3's offered HTLC (with anchor outputs)
|
# 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)))",
|
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
|
# 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,
|
"sequence": None,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 1,
|
"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)
|
# 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,
|
"sequence": 2,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 3,
|
"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.
|
# 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,
|
"sequence": 2,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 3,
|
"sigs_count": 3,
|
||||||
|
@ -74,7 +87,7 @@ MINISCRIPTS_PRIV = [
|
||||||
},
|
},
|
||||||
# Signature with a relative timelock
|
# 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,
|
"sequence": 2,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 1,
|
"sigs_count": 1,
|
||||||
|
@ -82,7 +95,7 @@ MINISCRIPTS_PRIV = [
|
||||||
},
|
},
|
||||||
# Signature with an absolute timelock
|
# 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,
|
"sequence": None,
|
||||||
"locktime": 20,
|
"locktime": 20,
|
||||||
"sigs_count": 1,
|
"sigs_count": 1,
|
||||||
|
@ -90,7 +103,7 @@ MINISCRIPTS_PRIV = [
|
||||||
},
|
},
|
||||||
# Signature with both
|
# 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,
|
"sequence": 4,
|
||||||
"locktime": 30,
|
"locktime": 30,
|
||||||
"sigs_count": 1,
|
"sigs_count": 1,
|
||||||
|
@ -98,7 +111,7 @@ MINISCRIPTS_PRIV = [
|
||||||
},
|
},
|
||||||
# We have one key on each branch; Core signs both (can't finalize)
|
# 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,
|
"sequence": None,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 2,
|
"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
|
# 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,
|
"sequence": 10,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 3,
|
"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
|
# 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,
|
"sequence": None,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 3,
|
"sigs_count": 3,
|
||||||
|
@ -122,7 +135,7 @@ MINISCRIPTS_PRIV = [
|
||||||
},
|
},
|
||||||
# Finalizes to the smallest valid witness, regardless of sequence
|
# 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,
|
"sequence": 12,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 3,
|
"sigs_count": 3,
|
||||||
|
@ -130,7 +143,57 @@ MINISCRIPTS_PRIV = [
|
||||||
},
|
},
|
||||||
# Liquid-like federated pegin with emergency recovery privkeys
|
# 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,
|
"sequence": 42,
|
||||||
"locktime": None,
|
"locktime": None,
|
||||||
"sigs_count": 2,
|
"sigs_count": 2,
|
||||||
|
@ -142,6 +205,7 @@ MINISCRIPTS_PRIV = [
|
||||||
class WalletMiniscriptTest(BitcoinTestFramework):
|
class WalletMiniscriptTest(BitcoinTestFramework):
|
||||||
def add_options(self, parser):
|
def add_options(self, parser):
|
||||||
self.add_wallet_options(parser, legacy=False)
|
self.add_wallet_options(parser, legacy=False)
|
||||||
|
self.rpc_timeout = 480
|
||||||
|
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.num_nodes = 1
|
self.num_nodes = 1
|
||||||
|
@ -150,9 +214,9 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
||||||
self.skip_if_no_wallet()
|
self.skip_if_no_wallet()
|
||||||
self.skip_if_no_sqlite()
|
self.skip_if_no_sqlite()
|
||||||
|
|
||||||
def watchonly_test(self, ms):
|
def watchonly_test(self, desc):
|
||||||
self.log.info(f"Importing Miniscript '{ms}'")
|
self.log.info(f"Importing descriptor '{desc}'")
|
||||||
desc = descsum_create(f"wsh({ms})")
|
desc = descsum_create(f"{desc}")
|
||||||
assert self.ms_wo_wallet.importdescriptors(
|
assert self.ms_wo_wallet.importdescriptors(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -166,11 +230,14 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
||||||
)[0]["success"]
|
)[0]["success"]
|
||||||
|
|
||||||
self.log.info("Testing we derive new addresses for it")
|
self.log.info("Testing we derive new addresses for it")
|
||||||
|
addr_type = "bech32m" if desc.startswith("tr(") else "bech32"
|
||||||
assert_equal(
|
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(
|
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")
|
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"]
|
assert utxo["txid"] == txid and utxo["solvable"]
|
||||||
|
|
||||||
def signing_test(
|
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}'")
|
self.log.info(f"Importing private Miniscript descriptor '{desc}'")
|
||||||
desc = descsum_create(f"wsh({ms})")
|
is_taproot = desc.startswith("tr(")
|
||||||
|
desc = descsum_create(desc)
|
||||||
res = self.ms_sig_wallet.importdescriptors(
|
res = self.ms_sig_wallet.importdescriptors(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -201,7 +269,8 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
||||||
assert res[0]["success"], res
|
assert res[0]["success"], res
|
||||||
|
|
||||||
self.log.info("Generating an address for it and testing it detects funds")
|
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)
|
txid = self.funder.sendtoaddress(addr, 0.01)
|
||||||
self.wait_until(lambda: txid in self.funder.getrawmempool())
|
self.wait_until(lambda: txid in self.funder.getrawmempool())
|
||||||
self.funder.generatetoaddress(1, self.funder.getnewaddress())
|
self.funder.generatetoaddress(1, self.funder.getnewaddress())
|
||||||
|
@ -233,7 +302,8 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
||||||
psbt = psbt.to_base64()
|
psbt = psbt.to_base64()
|
||||||
res = self.ms_sig_wallet.walletprocesspsbt(psbt=psbt, finalize=False)
|
res = self.ms_sig_wallet.walletprocesspsbt(psbt=psbt, finalize=False)
|
||||||
psbtin = self.nodes[0].rpc.decodepsbt(res["psbt"])["inputs"][0]
|
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"])
|
res = self.ms_sig_wallet.finalizepsbt(res["psbt"])
|
||||||
assert res["complete"] == (stack_size is not None)
|
assert res["complete"] == (stack_size is not None)
|
||||||
|
|
||||||
|
@ -290,20 +360,45 @@ class WalletMiniscriptTest(BitcoinTestFramework):
|
||||||
assert not res["success"] and "is not satisfiable" in res["error"]["message"]
|
assert not res["success"] and "is not satisfiable" in res["error"]["message"]
|
||||||
|
|
||||||
# Test we can track any type of Miniscript
|
# Test we can track any type of Miniscript
|
||||||
for ms in MINISCRIPTS:
|
for desc in DESCS:
|
||||||
self.watchonly_test(ms)
|
self.watchonly_test(desc)
|
||||||
|
|
||||||
# Test we can sign for any Miniscript.
|
# Test we can sign for any Miniscript.
|
||||||
for ms in MINISCRIPTS_PRIV:
|
for desc in DESCS_PRIV:
|
||||||
self.signing_test(
|
self.signing_test(
|
||||||
ms["ms"],
|
desc["desc"],
|
||||||
ms["sequence"],
|
desc["sequence"],
|
||||||
ms["locktime"],
|
desc["locktime"],
|
||||||
ms["sigs_count"],
|
desc["sigs_count"],
|
||||||
ms["stack_size"],
|
desc["stack_size"],
|
||||||
ms.get("sha256_preimages"),
|
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__":
|
if __name__ == "__main__":
|
||||||
WalletMiniscriptTest().main()
|
WalletMiniscriptTest().main()
|
||||||
|
|
Loading…
Add table
Reference in a new issue