diff --git a/.gitignore b/.gitignore index d8cd9346a..545ea3f56 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,7 @@ docs/source/bip39.rst docs/source/core.rst docs/source/crypto.rst docs/source/map.rst +docs/source/descriptor.rst docs/source/psbt.rst docs/source/psbt_members.rst docs/source/script.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 94a6a8794..4e895fecc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -82,7 +82,8 @@ def extract_docs(infile, outfile): else: for m in [ 'core', 'crypto', 'address', 'bip32', 'bip38', 'bip39', 'map', - 'script', 'psbt', 'symmetric', 'transaction', 'elements', 'anti_exfil' + 'script', 'psbt', 'descriptor', 'symmetric', 'transaction', + 'elements', 'anti_exfil' ]: extract_docs('../../include/wally_%s.h' % m, '%s.rst' % m) diff --git a/docs/source/index.rst b/docs/source/index.rst index a8196f88f..4afb4b08a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,6 +14,7 @@ libwally-core documentation map psbt script + descriptor symmetric transaction elements diff --git a/include/wally.hpp b/include/wally.hpp index 30458456d..a57079d27 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -421,6 +421,37 @@ inline int cleanup(uint32_t flags) { return ret; } +template +inline int descriptor_canonicalize(const DESCRIPTOR& descriptor, const VARS_IN& vars_in, uint32_t flags, char** output) { + int ret = ::wally_descriptor_canonicalize(detail::get_p(descriptor), detail::get_p(vars_in), flags, output); + return ret; +} + +template +inline int descriptor_get_checksum(const DESCRIPTOR& descriptor, const VARS_IN& vars_in, uint32_t flags, char** output) { + int ret = ::wally_descriptor_get_checksum(detail::get_p(descriptor), detail::get_p(vars_in), flags, output); + return ret; +} + +template +inline int descriptor_to_address(const DESCRIPTOR& descriptor, const VARS_IN& vars_in, uint32_t child_num, uint32_t network, uint32_t flags, char** output) { + int ret = ::wally_descriptor_to_address(detail::get_p(descriptor), detail::get_p(vars_in), child_num, network, flags, output); + return ret; +} + +template +inline int descriptor_to_addresses(const DESCRIPTOR& descriptor, const VARS_IN& vars_in, uint32_t child_num, uint32_t network, uint32_t flags, char** output, size_t num_outputs) { + int ret = ::wally_descriptor_to_addresses(detail::get_p(descriptor), detail::get_p(vars_in), child_num, network, flags, output, num_outputs); + return ret; +} + +template +inline int descriptor_to_scriptpubkey(const DESCRIPTOR& descriptor, const VARS_IN& vars_in, uint32_t child_num, uint32_t network, uint32_t depth, uint32_t index, uint32_t flags, BYTES_OUT& bytes_out, size_t* written = 0) { + size_t n; + int ret = ::wally_descriptor_to_scriptpubkey(detail::get_p(descriptor), detail::get_p(vars_in), child_num, network, depth, index, flags, bytes_out.data(), bytes_out.size(), written ? written : &n); + return written || ret != WALLY_OK ? ret : n == static_cast(bytes_out.size()) ? WALLY_OK : WALLY_EINVAL; +} + template inline int ec_private_key_verify(const PRIV_KEY& priv_key) { int ret = ::wally_ec_private_key_verify(priv_key.data(), priv_key.size()); @@ -787,6 +818,13 @@ inline int map_sort(const MAP_IN& map_in, uint32_t flags) { return ret; } +template +inline int miniscript_to_script(const MINISCRIPT& miniscript, const VARS_IN& vars_in, uint32_t child_num, uint32_t flags, BYTES_OUT& bytes_out, size_t* written = 0) { + size_t n; + int ret = ::wally_miniscript_to_script(detail::get_p(miniscript), detail::get_p(vars_in), child_num, flags, bytes_out.data(), bytes_out.size(), written ? written : &n); + return written || ret != WALLY_OK ? ret : n == static_cast(bytes_out.size()) ? WALLY_OK : WALLY_EINVAL; +} + template inline int pbkdf2_hmac_sha256(const PASS& pass, const SALT& salt, uint32_t flags, uint32_t cost, BYTES_OUT& bytes_out) { int ret = ::wally_pbkdf2_hmac_sha256(pass.data(), pass.size(), salt.data(), salt.size(), flags, cost, bytes_out.data(), bytes_out.size()); diff --git a/include/wally_address.h b/include/wally_address.h index 61ee30430..a43ee97cf 100644 --- a/include/wally_address.h +++ b/include/wally_address.h @@ -16,7 +16,9 @@ struct ext_key; #define WALLY_CA_PREFIX_LIQUID_REGTEST 0x04 /** Liquid v1 confidential address prefix for regtest */ #define WALLY_CA_PREFIX_LIQUID_TESTNET 0x17 /** Liquid v1 confidential address prefix for testnet */ +#define WALLY_NETWORK_NONE 0x00 /** Used for miniscript parsing only */ #define WALLY_NETWORK_BITCOIN_MAINNET 0x01 /** Bitcoin mainnet */ +#define WALLY_NETWORK_BITCOIN_REGTEST 0xff /** Bitcoin regtest: Behaves as testnet except for segwit */ #define WALLY_NETWORK_BITCOIN_TESTNET 0x02 /** Bitcoin testnet */ #define WALLY_NETWORK_LIQUID 0x03 /** Liquid v1 */ #define WALLY_NETWORK_LIQUID_REGTEST 0x04 /** Liquid v1 regtest */ diff --git a/include/wally_descriptor.h b/include/wally_descriptor.h new file mode 100644 index 000000000..e777c53ff --- /dev/null +++ b/include/wally_descriptor.h @@ -0,0 +1,145 @@ +#ifndef LIBWALLY_CORE_DESCRIPTOR_H +#define LIBWALLY_CORE_DESCRIPTOR_H + +#include "wally_core.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wally_map; + +/* Miniscript type flag */ +#define WALLY_MINISCRIPT_WITNESS_SCRIPT 0x00 /** Witness script */ +#define WALLY_MINISCRIPT_TAPSCRIPT 0x01 /** Tapscript */ + +/** + * Canonicalize a descriptor. + * + * :param descriptor: Output descriptor. + * :param vars_in: Map of variable names to values, or NULL. + * :param flags: For future use. Must be 0. + * :param output: Destination for the resulting canonical descriptor. + *| The string returned should be freed using `wally_free_string`. + * + * Replaces any variables from ``vars_in`` with their mapped values, + * and adds a checksum if required. Key names for ``vars_in`` must be 16 + * characters or less and start with a letter. + * + * .. note:: Other canonicalization (hardened derivation indicator + * mapping, and private to public key mapping) is not yet implemented. + */ +WALLY_CORE_API int wally_descriptor_canonicalize( + const char *descriptor, + const struct wally_map *vars_in, + uint32_t flags, + char **output); + +/** + * Create a script corresponding to a miniscript string. + * + * :param miniscript: Miniscript string. + * :param vars_in: Map of variable names to values, or NULL. + * :param child_num: The BIP32 child number to derive. + * :param flags: Flags controlling the type of script to create. Use one of + *| ``WALLY_MINISCRIPT_WITNESS_SCRIPT`` or ``WALLY_MINISCRIPT_TAPSCRIPT``. + * :param bytes_out: Destination for the resulting scriptPubkey. + * :param len: The length of ``bytes_out`` in bytes. + * :param written: Destination for the number of bytes written to ``bytes_out``. + */ +WALLY_CORE_API int wally_miniscript_to_script( + const char *miniscript, + const struct wally_map *vars_in, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + +/** + * Create a scriptPubKey corresponding to an output descriptor. + * + * :param descriptor: Output descriptor. + * :param vars_in: Map of variable names to values, or NULL. + * :param child_num: The BIP32 child number to derive. + * :param network: ``WALLY_NETWORK_`` constant descripting the network to generate for. + * :param depth: Number of the descriptor depth. Default is 0. + * :param index: Number of the descriptor index. Default is 0. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the resulting scriptPubkey. + * :param len: The length of ``bytes_out`` in bytes. + * :param written: Destination for the number of bytes written to ``bytes_out``. + */ +WALLY_CORE_API int wally_descriptor_to_scriptpubkey( + const char *descriptor, + const struct wally_map *vars_in, + uint32_t child_num, + uint32_t network, + uint32_t depth, + uint32_t index, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + +/** + * Create an address corresponding to an output descriptor. + * + * :param descriptor: Output descriptor. + * :param vars_in: Map of variable names to values, or NULL. + * :param child_num: The BIP32 child number to derive. + * :param network: ``WALLY_NETWORK_`` constant descripting the network to generate for. + * :param flags: For future use. Must be 0. + * :param output: Destination for the resulting addresss. + *| The string returned should be freed using `wally_free_string`. + */ +WALLY_CORE_API int wally_descriptor_to_address( + const char *descriptor, + const struct wally_map *vars_in, + uint32_t child_num, + uint32_t network, + uint32_t flags, + char **output); + +/** + * Create addresses that correspond to the derived range of an output descriptor. + * + * :param descriptor: Output descriptor. + * :param vars_in: Map of variable names to values, or NULL. + * :param child_num: The first BIP32 child number to derive. + * :param network: ``WALLY_NETWORK_`` constant descripting the network to generate for. + * :param flags: For future use. Must be 0. + * :param output: Destination for the resulting addresses. + * :param num_outputs: The number of items in ``output``. Addresses will be + *| generated into this array starting from child_num, incrementing by 1. + *| The addresses returned should be freed using `wally_free_string`. + */ +WALLY_CORE_API int wally_descriptor_to_addresses( + const char *descriptor, + const struct wally_map *vars_in, + uint32_t child_num, + uint32_t network, + uint32_t flags, + char **output, + size_t num_outputs); + +/** + * Create an output descriptor checksum. + * + * :param descriptor: Output descriptor. + * :param vars_in: Map of variable names to values, or NULL. + * :param flags: For future use. Must be 0. + * :param output: Destination for the resulting descriptor checksum. + *| The string returned should be freed using `wally_free_string`. + */ +WALLY_CORE_API int wally_descriptor_get_checksum( + const char *descriptor, + const struct wally_map *vars_in, + uint32_t flags, + char **output); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBWALLY_CORE_DESCRIPTOR_H */ diff --git a/src/Makefile.am b/src/Makefile.am index 73ed7c3b8..66763f631 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ include_HEADERS += $(top_srcdir)/include/wally_bip38.h include_HEADERS += $(top_srcdir)/include/wally_bip39.h include_HEADERS += $(top_srcdir)/include/wally_core.h include_HEADERS += $(top_srcdir)/include/wally_crypto.h +include_HEADERS += $(top_srcdir)/include/wally_descriptor.h include_HEADERS += $(top_srcdir)/include/wally_elements.h include_HEADERS += $(top_srcdir)/include/wally_map.h include_HEADERS += $(top_srcdir)/include/wally_psbt.h @@ -158,6 +159,7 @@ all: swig_java/wallycore.jar SWIG_JAVA_TEST_DEPS = \ $(sjs)/$(cbt)/test_bip32.class \ + $(sjs)/$(cbt)/test_descriptor.class \ $(sjs)/$(cbt)/test_tx.class \ $(sjs)/$(cbt)/test_scripts.class \ $(sjs)/$(cbt)/test_mnemonic.class @@ -190,6 +192,7 @@ libwallycore_la_SOURCES = \ bip38.c \ bip39.c \ bech32.c \ + descriptor.c \ ecdh.c \ elements.c \ blech32.c \ @@ -222,6 +225,7 @@ libwallycore_la_INCLUDES = \ include/wally_bip39.h \ include/wally_core.h \ include/wally_crypto.h \ + include/wally_descriptor.h \ include/wally_elements.h \ include/wally_map.h \ include/wally_psbt.h \ @@ -288,6 +292,14 @@ test_tx_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ if PYTHON_MANYLINUX test_tx_LDADD += $(PYTHON_LIBS) endif +TESTS += test_descriptor +noinst_PROGRAMS += test_descriptor +test_descriptor_SOURCES = ctest/test_descriptor.c +test_descriptor_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +test_descriptor_LDADD = $(lib_LTLIBRARIES) @CTEST_EXTRA_STATIC@ +if PYTHON_MANYLINUX +test_descriptor_LDADD += $(PYTHON_LIBS) +endif if BUILD_ELEMENTS TESTS += test_elements_tx noinst_PROGRAMS += test_elements_tx @@ -314,6 +326,7 @@ check-libwallycore: $(PYTHON_TEST_DEPS) $(AM_V_at)$(PYTHON_TEST) test/test_bip32.py $(AM_V_at)$(PYTHON_TEST) test/test_bip38.py $(AM_V_at)$(PYTHON_TEST) test/test_bip39.py + $(AM_V_at)$(PYTHON_TEST) test/test_descriptor.py $(AM_V_at)$(PYTHON_TEST) test/test_ecdh.py $(AM_V_at)$(PYTHON_TEST) test/test_hash.py $(AM_V_at)$(PYTHON_TEST) test/test_hex.py @@ -340,6 +353,7 @@ endif if USE_SWIG_PYTHON check-swig-python: $(SWIG_PYTHON_TEST_DEPS) $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/bip32.py + $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/descriptor.py $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/mnemonic.py $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/psbt.py $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/sha.py @@ -366,6 +380,7 @@ if BUILD_ELEMENTS $(AM_V_at)$(JAVA_TEST)test_pegs endif $(AM_V_at)$(JAVA_TEST)test_bip32 + $(AM_V_at)$(JAVA_TEST)test_descriptor $(AM_V_at)$(JAVA_TEST)test_mnemonic $(AM_V_at)$(JAVA_TEST)test_scripts $(AM_V_at)$(JAVA_TEST)test_tx diff --git a/src/address.c b/src/address.c index 94f73edc4..ffc8278b7 100644 --- a/src/address.c +++ b/src/address.c @@ -166,6 +166,7 @@ int wally_scriptpubkey_to_address(const unsigned char *scriptpubkey, size_t scri case WALLY_NETWORK_BITCOIN_MAINNET: bytes[0] = WALLY_ADDRESS_VERSION_P2PKH_MAINNET; break; + case WALLY_NETWORK_BITCOIN_REGTEST: case WALLY_NETWORK_BITCOIN_TESTNET: bytes[0] = WALLY_ADDRESS_VERSION_P2PKH_TESTNET; break; @@ -188,6 +189,7 @@ int wally_scriptpubkey_to_address(const unsigned char *scriptpubkey, size_t scri case WALLY_NETWORK_BITCOIN_MAINNET: bytes[0] = WALLY_ADDRESS_VERSION_P2SH_MAINNET; break; + case WALLY_NETWORK_BITCOIN_REGTEST: case WALLY_NETWORK_BITCOIN_TESTNET: bytes[0] = WALLY_ADDRESS_VERSION_P2SH_TESTNET; break; diff --git a/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c new file mode 100644 index 000000000..cf6cd6250 --- /dev/null +++ b/src/ctest/test_descriptor.c @@ -0,0 +1,1421 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +/* + { + pubkey: '038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048', + privkey: 'cNha6ams8o6qokphL3XfcUTRs7ggweD3SWn7YXLtB3Rrm3QDNxD4' + },{ + pubkey: '03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7', + privkey: 'cQbGCCA1P9aGWiyrGVXueofGJZmQAHBQhrrsX49rsExFKzeGTXT2' + },{ + pubkey: '03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284', + privkey: 'cQezKD6V8dtqkLz1Mh6JHYiz1TsZBXyizTtzY1xm3pqdMsxJ6wXT' + },{ + pubkey: '04a238b0cbea14c9b3f59d0a586a82985f69af3da50579ed5971eefa41e6758ee7f1d77e4d673c6e7aac39759bb762d22259e27bf93572e9d5e363d5a64b6c062b', + privkey: 'bc2f39635ef2e24b4689345fb68c615987b6b0388fdffb57f907bd44445603a4' + } + */ + +#define B(str) (unsigned char *)(str), sizeof(str) +#define NUM_ELEMS(a) (sizeof(a) / sizeof(a[0])) + +static struct wally_map_item g_key_map_items[] = { + { B("key_1"), B("038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, + { B("key_2"), B("03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, + { B("key_3"), B("03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284") }, + { B("key_likely"), B("038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, + { B("key_unlikely"), B("03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, + { B("key_user"), B("038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, + { B("key_service"), B("03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, + { B("key_local"), B("038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, + { B("key_remote"), B("03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, + { B("key_revocation"), B("03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284") }, + { B("H"), B("d0721279e70d39fb4aa409b52839a0056454e3b5") }, /* HASH160(key_local) */ + { B("mainnet_xpub"), B("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL") } +}; + +static const struct wally_map g_key_map = { + g_key_map_items, + NUM_ELEMS(g_key_map_items), + NUM_ELEMS(g_key_map_items), + NULL +}; + +static const uint32_t g_miniscript_index_0 = 0; +static const uint32_t g_miniscript_index_16 = 0x10; + +static bool check_ret(const char *function, int ret, int expected) +{ + if (ret != expected) + printf("%s: expected %d, got %d\n", function, expected, ret); + return ret == expected; +} + +static bool check_varbuff(const char *function, const unsigned char *src, size_t src_len, const char *expected) +{ + char *hex = NULL; + + if (!check_ret(function, wally_hex_from_bytes(src, src_len, &hex), WALLY_OK)) + return false; + + if (strcmp(hex, expected)) { + printf("%s: mismatch [%s] != [%s]\n", function, hex, expected); + return false; + } + wally_free_string(hex); + return true; +} + +static const struct miniscript_ref_test { + const char *miniscript; + const char *scriptpubkey; +} g_miniscript_ref_cases[] = { + /* Randomly generated test set that covers the majority of type and node type combinations */ + {"lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868"}, + {"uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068"}, + {"or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b"}, + {"j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268"}, + {"t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851"}, + {"t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851"}, + {"or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68"}, + {"or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868"}, + {"and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887"}, + {"j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68"}, + {"and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a"}, + {"j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868"}, + {"and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a"}, + {"thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287"}, + {"and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168"}, + {"or_d(d:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b26968736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768"}, + {"c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac"}, + {"c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac"}, + {"and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1"}, + {"andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868"}, + {"or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768"}, + {"thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287"}, + {"and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868"}, + {"and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68"}, + {"c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac"}, + {"or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868"}, + {"c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac"}, + {"c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac"}, + {"c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac"}, + /* Error cases */ + {"lltvlnlltvln:after(1231488000)", NULL}, /* too many wrappers */ + {"older(-9223372036854775808)", NULL}, /* Number too small to parse */ + {"older(9223372036854775807)", NULL}, /* Number too large to parse */ +}; + +static const struct miniscript_test { + const char *name; + const char *descriptor; + const char *scriptpubkey; + const char *miniscript; + const char *p2wsh_scriptpubkey; +} g_miniscript_cases[] = { + { + "miniscript - A single key", + "c:pk_k(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048)", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "c:pk_k(key_1)", + "0020fa5bf4aae3ee617c6cce1976f6d7d285c359613ffeed481f1067f62bc0f54852" + }, { + "miniscript - One of two keys (equally likely)", + "or_b(c:pk_k(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),sc:pk_k(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac7c2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac9b", + "or_b(c:pk_k(key_1),sc:pk_k(key_2))", + "002018a9df986ba10bcd8f503f495cab5fd00c9fb23c05143e65dbba49ef4d8a825f" + }, { + "miniscript - A user and a 2FA service need to sign off, but after 90 days the user alone is enough", + "and_v(vc:pk_k(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),or_d(c:pk_k(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),older(12960)))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ad2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac736402a032b268", + "and_v(vc:pk_k(key_user),or_d(c:pk_k(key_service),older(12960)))", + "00201264946c666958d9522f63dcdcfc85941bdd5b9308b1e6c68696857506f6cced" + }, { + "miniscript - A 3-of-3 that turns into a 2-of-3 after 90 days", + "thresh(3,c:pk_k(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),sc:pk_k(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),sc:pk_k(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284),sdv:older(12960))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac7c2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac937c2103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac937c766302a032b26968935387", + "thresh(3,c:pk_k(key_1),sc:pk_k(key_2),sc:pk_k(key_3),sdv:older(12960))", + "0020ab3551bec623130218a9ca5da0e3adb82b8569f91355a483653a49f2a2dd6e70" + }, { + "miniscript - The BOLT #3 to_local policy", + "andor(c:pk_k(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),older(1008),c:pk_k(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac6702f003b268", + "andor(c:pk_k(key_local),older(1008),c:pk_k(key_revocation))", + "0020052cf1e9c90e9a2883d890467a6a01837e21b3b755a743c9d96a2b6f8285d7c0" + }, { + "miniscript - The BOLT #3 offered HTLC policy ", + "t:or_c(c:pk_k(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284),and_v(vc:pk_k(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),or_c(c:pk_k(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),v:hash160(d0721279e70d39fb4aa409b52839a0056454e3b5))))", + "2103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac642103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ad21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac6482012088a914d0721279e70d39fb4aa409b52839a0056454e3b588686851", + "t:or_c(c:pk_k(key_revocation),and_v(vc:pk_k(key_remote),or_c(c:pk_k(key_local),v:hash160(H))))", + "0020f9259363db0facc7b97ab2c0294c4f21a0cd56b01bb54ecaaa5899012aae1bc2" + }, { + "miniscript - The BOLT #3 received HTLC policy ", + "andor(c:pk_k(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),or_i(and_v(vc:pk_h(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),hash160(d0721279e70d39fb4aa409b52839a0056454e3b5)),older(1008)),c:pk_k(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284))", + "2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac676376a914d0721279e70d39fb4aa409b52839a0056454e3b588ad82012088a914d0721279e70d39fb4aa409b52839a0056454e3b5876702f003b26868", + "andor(c:pk_k(key_remote),or_i(and_v(vc:pk_h(key_local),hash160(H)),older(1008)),c:pk_k(key_revocation))", + "002087515e0059c345eaa5cccbaa9cd16ad1266e7a69e350db82d8e1f33c86285303" + }, { + "miniscript(2) - A single key", + "pk(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048)", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "pk(key_1)", + "0020fa5bf4aae3ee617c6cce1976f6d7d285c359613ffeed481f1067f62bc0f54852" + }, { + "miniscript(2) - One of two keys (equally likely)", + "or_b(pk(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),s:pk(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac7c2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac9b", + "or_b(pk(key_1),s:pk(key_2))", + "002018a9df986ba10bcd8f503f495cab5fd00c9fb23c05143e65dbba49ef4d8a825f" + }, { + "miniscript(2) - A user and a 2FA service need to sign off, but after 90 days the user alone is enough", + "and_v(v:pk(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),or_d(pk(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),older(12960)))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ad2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac736402a032b268", + "and_v(v:pk(key_user),or_d(pk(key_service),older(12960)))", + "00201264946c666958d9522f63dcdcfc85941bdd5b9308b1e6c68696857506f6cced" + }, { + "miniscript(2) - A 3-of-3 that turns into a 2-of-3 after 90 days", + "thresh(3,pk(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),s:pk(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),s:pk(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284),sdv:older(12960))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac7c2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac937c2103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac937c766302a032b26968935387", + "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sdv:older(12960))", + "0020ab3551bec623130218a9ca5da0e3adb82b8569f91355a483653a49f2a2dd6e70" + }, { + "miniscript(2) - The BOLT #3 to_local policy", + "andor(pk(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),older(1008),pk(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284))", + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac6702f003b268", + "andor(pk(key_local),older(1008),pk(key_revocation))", + "0020052cf1e9c90e9a2883d890467a6a01837e21b3b755a743c9d96a2b6f8285d7c0" + }, { + "miniscript(2) - The BOLT #3 offered HTLC policy ", + "t:or_c(pk(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284),and_v(v:pk(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),or_c(pk(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),v:hash160(d0721279e70d39fb4aa409b52839a0056454e3b5))))", + "2103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac642103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ad21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac6482012088a914d0721279e70d39fb4aa409b52839a0056454e3b588686851", + "t:or_c(pk(key_revocation),and_v(v:pk(key_remote),or_c(pk(key_local),v:hash160(H))))", + "0020f9259363db0facc7b97ab2c0294c4f21a0cd56b01bb54ecaaa5899012aae1bc2" + }, { + "miniscript(2) - The BOLT #3 received HTLC policy ", + "andor(pk(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),or_i(and_v(v:pkh(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),hash160(d0721279e70d39fb4aa409b52839a0056454e3b5)),older(1008)),pk(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284))", + "2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac676376a914d0721279e70d39fb4aa409b52839a0056454e3b588ad82012088a914d0721279e70d39fb4aa409b52839a0056454e3b5876702f003b26868", + "andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation))", + "002087515e0059c345eaa5cccbaa9cd16ad1266e7a69e350db82d8e1f33c86285303" + }, { + "miniscript(2) - The BOLT #3 received HTLC policy ", + "andor(pk(03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7),or_i(and_v(v:pkh(038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048),hash160(d0721279e70d39fb4aa409b52839a0056454e3b5)),older(1008)),pk(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284))", + "2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac676376a914d0721279e70d39fb4aa409b52839a0056454e3b588ad82012088a914d0721279e70d39fb4aa409b52839a0056454e3b5876702f003b26868", + "andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation))", + "002087515e0059c345eaa5cccbaa9cd16ad1266e7a69e350db82d8e1f33c86285303" + } +}; + +static const struct miniscript_taproot_test { + const char *miniscript; + const char *scriptpubkey; + uint32_t flags; +} g_miniscript_taproot_cases[] = { + { + "c:pk_k(daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729)", + "20daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac", + WALLY_MINISCRIPT_TAPSCRIPT + }, { + "c:pk_k([bd16bee5/0]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/1)", + "208c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816ac", + WALLY_MINISCRIPT_TAPSCRIPT + }, { + "c:pk_k(L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM)", + "20ff7e7b1d3c4ba385cb1f2e6423bf30c96fb5007e7917b09ec1b6c965ef644d13ac", + WALLY_MINISCRIPT_TAPSCRIPT + } +}; + +static const struct descriptor_test { + const char *name; + const char *descriptor; + const char *scriptpubkey; + const uint32_t *bip32_index; + const char *checksum; +} g_descriptor_cases[] = { + { + "descriptor - p2pk", + "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)#gn28ywm7", + "210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac", + NULL, + "gn28ywm7" + },{ + "descriptor - p2pkh", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + "76a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac", + NULL, + "8fhd9pwu" + },{ + "descriptor - p2wpkh", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc", + NULL, + "8zl0zxma" + },{ + "descriptor - p2sh-p2wpkh", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + "a914cc6ffbc0bf31af759451068f90ba7a0272b6b33287", + NULL, + "qkrrc7je" + },{ + "descriptor - combo(p2wpkh)", + "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + "0014751e76e8199196d454941c45d1b3a323f1433bd6", + NULL, + "lq9sf04s" + },{ + "descriptor - combo(p2pkh)", + "combo(04a238b0cbea14c9b3f59d0a586a82985f69af3da50579ed5971eefa41e6758ee7f1d77e4d673c6e7aac39759bb762d22259e27bf93572e9d5e363d5a64b6c062b)", + "76a91448cb866ee3edb295e4cfeb3da65b4003ab9fa6a288ac", + NULL, + "r3wj6k68" + },{ + "descriptor - p2sh-p2wsh", + "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))", + "a91455e8d5e8ee4f3604aba23c71c2684fa0a56a3a1287", + NULL, + "2wtr0ej5" + },{ + "descriptor - multisig", + "multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)", + "5121022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe421025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52ae", + NULL, + "hzhjw406" + },{ + "descriptor - p2sh-multi", + "sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", + "a914a6a8b030a38762f4c1f5cbe387b61a3c5da5cd2687", + NULL, + "y9zthqta" + },{ + "descriptor - p2sh-sortedmulti 1", + "sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", + "a914a6a8b030a38762f4c1f5cbe387b61a3c5da5cd2687", + NULL, + "qwx6n9lh" + },{ + "descriptor - p2sh-sortedmulti 2", + "sh(sortedmulti(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", + "a914a6a8b030a38762f4c1f5cbe387b61a3c5da5cd2687", + NULL, + "fjpjdnvk" /* Note different checksum from p2sh-sortedmulti 1 */ + },{ + "descriptor - p2wsh-multi", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + "0020773d709598b76c4e3b575c08aad40658963f9322affc0f8c28d1d9a68d0c944a", + NULL, + "en3tu306" + },{ + "descriptor - p2sh-p2wsh-multi", + "sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))", + "a914aec509e284f909f769bb7dda299a717c87cc97ac87", + NULL, + "ks05yr6p" + },{ + "descriptor - p2sh multisig 15", + /* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */ + "sh(multi(1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1))", + "a914276b4ebc33265436a9c9b46ca23d6781aef98fe087", + NULL, + "pckwejvm" + },{ + "descriptor - p2pk-xpub", + "pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)", + "210339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2ac", + NULL, + "axav5m0j" + },{ + "descriptor - p2pkh-xpub-derive", + "pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1/2)", + "76a914f833c08f02389c451ae35ec797fccf7f396616bf88ac", + NULL, + "kczqajcv" + },{ + "descriptor - p2pkh-empty-path", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/)", + "76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac", + NULL, + "ee44hjhg" + },{ + "descriptor - p2pkh-parent-derive", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*)", + "76a914d234825a563de8b4fd31d2b30f60b1e60fe57ee788ac", + &g_miniscript_index_16, + "ml40v0wf" + },{ + "descriptor - p2wsh-multi-xpub", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "00204616bb4e66d0b540b480c5b26c619385c4c2b83ed79f4f3eab09b01745443a55", + &g_miniscript_index_16, + "t2zpj2eu" + },{ + "descriptor - p2wsh-sortedmulti-xpub", + "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "002002aeee9c3773dfecfe6215f2eea2908776b1232513a700e1ee516b634883ecb0", + &g_miniscript_index_16, + "v66cvalc" + },{ + "descriptor - addr-btc-legacy-testnet", + "addr(moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW)", + "76a91457526b1a1534d4bde788253281649fc2e91dc70b88ac", + NULL, + "9amhxcar" + },{ + "descriptor - addr-btc-segwit-mainnet", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + NULL, + "8kzm8txf" + },{ + "descriptor - raw-checksum", + "raw(6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e)#zf2avljj", + "6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e", + NULL, + "zf2avljj" + },{ + "descriptor - p2pkh-xpriv", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1h/2)", + "76a914b28d12ab72a51b10114b17ce76b536265194e1fb88ac", + NULL, + "wghlxksl" + },{ + "descriptor - p2pkh-xpriv hardened last child", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1h/2h)", + "76a9148ab3d0acbde6766fb0a24e0e4286168c2a24a7a088ac", + NULL, + "cj20v7ag" + },{ + "descriptor - p2pkh-privkey-wif mainnet", + "pkh(L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM)", + "76a91492ed3283cfb01caec1163aefba29caf1182f478e88ac", + NULL, + "qm00tjwh" + },{ + "descriptor - p2pkh-privkey-wif testnet uncompressed", + "pkh(936Xapr4wpeuiKToGeXtEcsVJAfE6ze8KUEb2UQu72rzBQsMZdX)", + "76a91477b6f27ac523d8b9aa8abcfc94fd536493202ae088ac", + NULL, + "9gv5p2gj" + },{ + "descriptor - A single key", + "wsh(c:pk_k(key_1))", + "0020fa5bf4aae3ee617c6cce1976f6d7d285c359613ffeed481f1067f62bc0f54852", + NULL, + "9u0h8j4t" + },{ + "descriptor - One of two keys (equally likely)", + "wsh(or_b(c:pk_k(key_1),sc:pk_k(key_2)))", + "002018a9df986ba10bcd8f503f495cab5fd00c9fb23c05143e65dbba49ef4d8a825f", + NULL, + "hyh0kcqw" + },{ + "descriptor - A user and a 2FA service need to sign off, but after 90 days the user alone is enough", + "wsh(and_v(vc:pk_k(key_user),or_d(c:pk_k(key_service),older(12960))))", + "00201264946c666958d9522f63dcdcfc85941bdd5b9308b1e6c68696857506f6cced", + NULL, + "nwlxsraz" + },{ + "descriptor - A 3-of-3 that turns into a 2-of-3 after 90 days", + "wsh(thresh(3,c:pk_k(key_1),sc:pk_k(key_2),sc:pk_k(key_3),sdv:older(12960)))", + "0020ab3551bec623130218a9ca5da0e3adb82b8569f91355a483653a49f2a2dd6e70", + NULL, + "yxfzvp79" + },{ + "descriptor - The BOLT #3 to_local policy", + "wsh(andor(c:pk_k(key_local),older(1008),c:pk_k(key_revocation)))", + "0020052cf1e9c90e9a2883d890467a6a01837e21b3b755a743c9d96a2b6f8285d7c0", + NULL, + "hthd6qg9" + },{ + "descriptor - The BOLT #3 offered HTLC policy", + "wsh(t:or_c(c:pk_k(key_revocation),and_v(vc:pk_k(key_remote),or_c(c:pk_k(key_local),v:hash160(H)))))", + "0020f9259363db0facc7b97ab2c0294c4f21a0cd56b01bb54ecaaa5899012aae1bc2", + NULL, + "0hmjukva" + },{ + "descriptor - The BOLT #3 received HTLC policy", + "wsh(andor(c:pk_k(key_remote),or_i(and_v(vc:pk_h(key_local),hash160(H)),older(1008)),c:pk_k(key_revocation)))", + "002087515e0059c345eaa5cccbaa9cd16ad1266e7a69e350db82d8e1f33c86285303", + NULL, + "8re62ejc" + },{ + "descriptor - derive key index 0", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "002064969d8cdca2aa0bb72cfe88427612878db98a5f07f9a7ec6ec87b85e9f9208b", + &g_miniscript_index_0, + "t2zpj2eu" + }, + /* https://github.com/rust-bitcoin/rust-miniscript/blob/master/src/descriptor/checksum.rs */ + { + "descriptor - rust-bitcoin checksum", + "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", + "0014e2d19350c9d8722e2994c81791f4a0ba115bc479", + NULL, + "tqz0nc62" + }, { + /* https://github.com/bitcoin/bitcoin/blob/7ae86b3c6845873ca96650fc69beb4ae5285c801/src/test/descriptor_tests.cpp#L352-L354 */ + "descriptor - core checksum", + "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", + "a91445a9a622a8b0a1269944be477640eedc447bbd8487", + NULL, + "ggrsrxfy" + } +}; + +static const char *const DEPTH_TEST_DESCRIPTOR = "sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))"; + +static const struct descriptor_depth_test { + const char *name; + const char *descriptor; + const uint32_t depth; + const uint32_t index; + const char *scriptpubkey; +} g_descriptor_depth_cases[] = { + { + "descriptor depth - p2sh-p2wsh-multi (p2sh-p2wsh)", + DEPTH_TEST_DESCRIPTOR, + 0, + 0, + "a914aec509e284f909f769bb7dda299a717c87cc97ac87" + }, { + "descriptor depth - p2sh-p2wsh-multi (p2wsh)", + DEPTH_TEST_DESCRIPTOR, + 1, + 0, + "0020ef8110fa7ddefb3e2d02b2c1b1480389b4bc93f606281570cfc20dba18066aee" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi)", + DEPTH_TEST_DESCRIPTOR, + 2, + 0, + "512103f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa82103499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e42102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e53ae" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[0])", + DEPTH_TEST_DESCRIPTOR, + 3, + 0, + "51" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[1])", + DEPTH_TEST_DESCRIPTOR, + 3, + 1, + "03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[2])", + DEPTH_TEST_DESCRIPTOR, + 3, + 2, + "03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[3])", + DEPTH_TEST_DESCRIPTOR, + 3, + 3, + "02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e" + } +}; + +static const struct descriptor_address_test { + const char *name; + const char *descriptor; + const uint32_t bip32_index; + const uint32_t network; + const char *address; +} g_descriptor_address_cases[] = { + { + "address - p2pkh - mainnet", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP" + },{ + "address - p2pkh - testnet", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + "mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H" + },{ + "address - p2pkh - regtest", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + "mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H" + },{ + "address - p2wpkh - mainnet", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1q0ht9tyks4vh7p5p904t340cr9nvahy7u3re7zg" + },{ + "address - p2wpkh - testnet", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + "tb1q0ht9tyks4vh7p5p904t340cr9nvahy7um9zdem" + },{ + "address - p2wpkh - regtest", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + "bcrt1q0ht9tyks4vh7p5p904t340cr9nvahy7uevmqwj" + },{ + "address - p2sh-p2wpkh - mainnet", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "3LKyvRN6SmYXGBNn8fcQvYxW9MGKtwcinN" + },{ + "address - p2sh-p2wpkh - liquidv1", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + 0, + WALLY_NETWORK_LIQUID, + "H1pVQ7VtauJK4v7ixvwFQpDFYW2Q6eiPVx" + },{ + "address - p2sh-p2wpkh - liquidregtest", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + 0, + WALLY_NETWORK_LIQUID_REGTEST, + "XVzCr2EG9PyrWX8qr2visL1aCfJMhGTZyS" + },{ + "address - p2sh-p2wsh - mainnet", + "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "39XGHYpYmJV9sGFoGHZeU2rLkY6r1MJ6C1" + },{ + "address - p2sh-p2wsh - liquidv1", + "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))", + 0, + WALLY_NETWORK_LIQUID, + "Gq1mmExLuSEwfzzk6YtUxJ769grv6T5Tak" + },{ + "address - p2wsh-multi - mainnet", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qn4zazc" + },{ + "address - p2wsh-multi - testnet", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + "tb1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qya5jch" + },{ + "address - p2wsh-multi - regtest", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + "bcrt1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qfy75dd" + },{ + "address - p2wsh-multi - liquidv1", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_LIQUID, + "ex1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39q06fgz7" + },{ + "address - p2wsh-multi - liquidregtest", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_LIQUID_REGTEST, + "ert1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qchk2yf" + },{ + "address - p2pkh-xpub-derive", + "pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1/2)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "1PdNaNxbyQvHW5QHuAZenMGVHrrRaJuZDJ" + },{ + "descriptor - p2pkh-empty-path", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R" + },{ + "address - p2pkh-parent-derive", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "14qCH92HCyDDBFFZdhDt1WMfrMDYnBFYMF" + },{ + "address - p2wsh-multi-xpub", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c" + },{ + "address - p2wsh-sortedmulti-xpub", + "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c" + },{ + "address - addr-btc-legacy-testnet", + "addr(moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW)", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + "moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW" + },{ + "address - addr-btc-legacy-testnet/regtest", + "addr(moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW)", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + "moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW" + },{ + "address - addr-btc-segwit-mainnet", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3" + },{ + "address - p2pkh-xpriv", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1h/2)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "1HH6H4km128m4NsJMNVN2qqCHukbEhgU3V" + },{ + "address - A single key", + "wsh(c:pk_k(key_1))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qlfdlf2hraeshcmxwr9m0d47jshp4jcfllmk5s8csvlmzhs84fpfqa6ufv5" + },{ + "address - One of two keys (equally likely)", + "wsh(or_b(c:pk_k(key_1),sc:pk_k(key_2)))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qrz5alxrt5y9umr6s8ay4e26l6qxflv3uq52ruewmhfy77nv2sf0spz2em3" + },{ + "address - A user and a 2FA service need to sign off, but after 90 days the user alone is enough", + "wsh(and_v(vc:pk_k(key_user),or_d(c:pk_k(key_service),older(12960))))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qzfjfgmrxd9vdj530v0wdely9jsda6kunpzc7d35xj6zh2phkenkstn6ur7" + },{ + "address - A 3-of-3 that turns into a 2-of-3 after 90 days", + "wsh(thresh(3,c:pk_k(key_1),sc:pk_k(key_2),sc:pk_k(key_3),sdv:older(12960)))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1q4v64r0kxyvfsyx9fefw6pcadhq4c260ezd26fqm98fyl9gkadecqy9uufs" + },{ + "address - The BOLT #3 to_local policy", + "wsh(andor(c:pk_k(key_local),older(1008),c:pk_k(key_revocation)))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qq5k0r6wfp6dz3q7cjpr856spsdlzrvah2kn58jwedg4klq596lqq90rr7h" + },{ + "address - The BOLT #3 offered HTLC policy", + "wsh(t:or_c(c:pk_k(key_revocation),and_v(vc:pk_k(key_remote),or_c(c:pk_k(key_local),v:hash160(H)))))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qlyjexc7mp7kv0wt6ktqzjnz0yxsv644srw65aj42tzvsz24wr0pqc6enkg" + },{ + "address - The BOLT #3 received HTLC policy", + "wsh(andor(c:pk_k(key_remote),or_i(and_v(vc:pk_h(key_local),hash160(H)),older(1008)),c:pk_k(key_revocation)))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + "bc1qsag4uqzecdz74fwvew4fe5t26ynxu7nfudgdhqkcu8enep3g2vpsvp0wl0" + } +}; + +static struct descriptor_address_list_test { + const char *name; + const char *descriptor; + const uint32_t child_num; + const uint32_t network; + const size_t num_addresses; + const char *address_list[30]; +} g_descriptor_addresses_cases[] = { + { + "address list - p2wsh multisig (0-29)", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))#t2zpj2eu", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + 30, + { + "bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c", + "bc1qp6rfclasvmwys7w7j4svgc2mrujq9m73s5shpw4e799hwkdcqlcsj464fw", + "bc1qsflxzyj2f2evshspl9n5n745swcvs5k7p5t8qdww5unxpjwdvw5qx53ms4", + "bc1qmhmj2mswyvyj4az32mzujccvd4dgr8s0lfzaum4n4uazeqc7xxvsr7e28n", + "bc1qjeu2wa5jwvs90tv9t9xz99njnv3we3ux04fn7glw3vqsk4ewuaaq9kdc9t", + "bc1qc6626sa08a4ktk3nqjrr65qytt9k273u24mfy2ld004g76jzxmdqjgpm2c", + "bc1qwlq7jjqcklrcqypvdndjx0fyrudgrymm67gcx3e09sekgs28u47smq0lx5", + "bc1qx8qq9k2mtqarugg3ctcsm2um22ahmq5uttrecy5ufku0ukfgpwrs7epn38", + "bc1qgrs4qzvw4aat2k38fvmrqf3ucaanqz2wxe5yy5cewwmqn06evxgq02wv43", + "bc1qnkpr4y7fp7jwad3gfngczwsv9069rq96cl7lpq4h9j3eng9mwjzsssr520", + "bc1q7yzadku3kxs855wgjxnyr2nk3e44ed75p07lzhnj53ynpczg78nq0leae5", + "bc1qpg9ag0ugqeucujyagca0n3httpgrgcsxftfgpymvmdeuyyejq9ks79c99t", + "bc1qt2sv92tuklq28hptplvq7v75mmc8h6a0ynd7vd7y0h07mr8uzf5seh30gh", + "bc1qdyfk0c5ksrxg6klz93acchg0xvavduzv3g4zj02fa3tm2yfy445q27zmar", + "bc1qrpfz6zpargqu9s2qy0ef9uk82x6fcg6jfwjhxdaewgj880nxj2rqt0hwcm", + "bc1qz6l0ar69xhk209nfdna68fkkg9tqp7pz7eq8mmu6hf5lvpltfx9slc9y6y", + "bc1qgcttknnx6z65pdyqckexccvnshzv9wp76705704tpxcpw32y8f2suf5fx8", + "bc1q0pauhlw2y4nyc2hud7dsmtc97k6kc30nz5u05dt6stahrfwy68tsnvl7l6", + "bc1qhgv6v7jgxxpf0cpzxd9zga52mx3c5xrnkvchk35ypavesumh8yqscvxrjh", + "bc1qrshvtv8ldqpdtv4z9z8fsah3plkl57drk7d8xgasgwj6puxpcxessp57hv", + "bc1qma56gu8mxywqjpeh56cwltmaddrtvyxec4ppdx4j733j8wtva09qnldwgs", + "bc1qj25wzn56y79x6tm67hpwr9d8vew87nk8asgwcc8mp53g4wh6hr9s2lh8nn", + "bc1q2ct0r07txjd32gh5c0cwg59ml0ahrzg07q3cm5naykdzdstmxhmqe8rtdu", + "bc1qn3n488yufhn2zfxtu4c7cqrmasqslrkmdyh7jen3yx8lj9z4cdfq03v349", + "bc1q89u4zs3vxyyznzzp99w8n8w7rh6hr4z3nvvtvkhyzkkqsgppvv8sgq0hqh", + "bc1q588dgge2vx0azcslfktlpeehqlh6y34hg6ur3rluxmkkm28f69dswj664f", + "bc1qv9eul0xtc8pg0sheuxp5ve9z7kl95j00efdxts8ae7ls7utcl4mq67jgqp", + "bc1qygswuelpc3rcuvzmempn0ku9h35fcpnc6sjd6h6exq4zx9zvxmrqz2eacm", + "bc1q2t74yd2ec7qx4j5xe9pj2y522whj3lz4lmhsxeasu8z00ggapgnqjxvlnk", + "bc1qp0rlvd76cmsv9ls5jv9az8kmra44rzgm5mz8008ypyfjayk70r8qwrg6c6", + } + }, { + "address list - p2wsh multisig (30-40)", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))#t2zpj2eu", + 30, + WALLY_NETWORK_BITCOIN_MAINNET, + 11, + { + "bc1qz7ymgzfyx5x0qk04e0je54zwlh8mcshzwdmyd72jpgp33zkl3ekqxl6xuc", + "bc1qt07wnht6j90aczg7e7wsvnpzxveueyud34a90d99phm7apesvp0sw63ceh", + "bc1qwwl8fkywdhpn2xh8k95qglhkrjlt7xp60nahvc6yderj53wg79rs8kdfrv", + "bc1qxu7g60rcjlfulna079ccmta7ytazck82vwth3hktqeey2e5vh4lqp4s0a3", + "bc1q8v89njuqn66w7elpxjy79j2fpksnafje2xs0l268typfm553hwcqsw9wza", + "bc1qn66uht0ndvdw6nna8pm8nhjhulrp8yq84rcarkfr3u5nprdzyq0sx3k9g6", + "bc1q3em7pyxvyte20n5mx4yeswkfq7vkj77xty06vu5gk47z7tews48q39g324", + "bc1qytjx24vzm7q5munv9yn3j7ltg23q86sqxnzunhhvsrx5hrnu47rsplzqux", + "bc1q283cq3dknnypqzjdtkhx3mjq7ncex5snfjpcl0vuq5k8v9nmcr8sxfdfr2", + "bc1qqdte9nnnam9zpgg5zfttyw7hmgh0secxnj6ukrq20c60fcjx7lhqv6am95", + "bc1qd6ffgpayzpywa6hps0c65xuur5letl9hdy3pv5y40t8p9nrjpdtqqkan7a" + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" + } + } +}; + +static const struct descriptor_err_test { + const char *name; + const char *descriptor; + const uint32_t network; +} g_descriptor_err_cases[] = { + { + "descriptor errchk - invalid checksum", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)#8rap84p2", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor errchk - upper case hardened indicator", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1H/2)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor errchk - privkey - unmatch network1", + "wpkh(cSMSHUGbEiZQUXVw9zA33yT3m8fgC27rn2XEGZJupwCpsRS3rAYa)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor errchk - privkey - unmatch network2", + "wpkh(cSMSHUGbEiZQUXVw9zA33yT3m8fgC27rn2XEGZJupwCpsRS3rAYa)", + WALLY_NETWORK_LIQUID + },{ + "descriptor errchk - privkey - unmatch network3", + "wpkh(L4gLCkRn5VfJsDSWsrrC57kqVgq6Z2sjeRELmim5zzAgJisysh17)", + WALLY_NETWORK_BITCOIN_TESTNET + },{ + "descriptor errchk - privkey - unmatch network4", + "wpkh(L4gLCkRn5VfJsDSWsrrC57kqVgq6Z2sjeRELmim5zzAgJisysh17)", + WALLY_NETWORK_BITCOIN_REGTEST + },{ + "descriptor errchk - privkey - unmatch network5", + "wpkh(L4gLCkRn5VfJsDSWsrrC57kqVgq6Z2sjeRELmim5zzAgJisysh17)", + WALLY_NETWORK_LIQUID_REGTEST + },{ + "descriptor errchk - xpubkey - unmatch network1", + "wpkh(tpubD6NzVbkrYhZ4XJDrzRvuxHEyQaPd1mwwdDofEJwekX18tAdsqeKfxss79AJzg1431FybXg5rfpTrJF4iAhyR7RubberdzEQXiRmXGADH2eA)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor errchk - xpubkey - unmatch network2", + "wpkh(tpubD6NzVbkrYhZ4XJDrzRvuxHEyQaPd1mwwdDofEJwekX18tAdsqeKfxss79AJzg1431FybXg5rfpTrJF4iAhyR7RubberdzEQXiRmXGADH2eA)", + WALLY_NETWORK_LIQUID + },{ + "descriptor errchk - xpubkey - unmatch network3", + "wpkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB)", + WALLY_NETWORK_BITCOIN_TESTNET + },{ + "descriptor errchk - xpubkey - unmatch network4", + "wpkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB)", + WALLY_NETWORK_BITCOIN_REGTEST + },{ + "descriptor errchk - xpubkey - unmatch network5", + "wpkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB)", + WALLY_NETWORK_LIQUID_REGTEST + },{ + "descriptor errchk - xprivkey - unmatch network1", + "wpkh(tprv8jDG3g2yc8vh71x9ejCDSfMz4AuQRx7MMNBXXvpD4jh7CkDuB3ZmnLVcEM99jgg5MaSp7gYNpnKS5dvkGqq7ad8X63tE7yFaMGTfp6gD54p)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor errchk - xprivkey - unmatch network2", + "wpkh(tprv8jDG3g2yc8vh71x9ejCDSfMz4AuQRx7MMNBXXvpD4jh7CkDuB3ZmnLVcEM99jgg5MaSp7gYNpnKS5dvkGqq7ad8X63tE7yFaMGTfp6gD54p)", + WALLY_NETWORK_LIQUID + },{ + "descriptor errchk - xprivkey - unmatch network3", + "wpkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU)", + WALLY_NETWORK_BITCOIN_TESTNET + },{ + "descriptor errchk - xprivkey - unmatch network4", + "wpkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU)", + WALLY_NETWORK_BITCOIN_REGTEST + },{ + "descriptor errchk - xprivkey - unmatch network5", + "wpkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU)", + WALLY_NETWORK_LIQUID_REGTEST + },{ + "descriptor errchk - addr - unmatch network1", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + WALLY_NETWORK_BITCOIN_TESTNET + },{ + "descriptor errchk - addr - unmatch network2", + "addr(ex1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39q06fgz7)", + WALLY_NETWORK_LIQUID_REGTEST + },{ + "descriptor - multisig too many keys", + /* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 */ + "sh(multi(1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1,key_1))", + WALLY_NETWORK_LIQUID_REGTEST + },{ + "descriptor - sh - non-root", + "sh(sh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - sh - multi-child", + "sh(sh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - wsh - non-sh parent", + "wsh(wsh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - wsh uncompressed", + "wsh(936Xapr4wpeuiKToGeXtEcsVJAfE6ze8KUEb2UQu72rzBQsMZdX)", + WALLY_NETWORK_BITCOIN_TESTNET + },{ + "descriptor - wsh - multi-child", + "wsh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - pk - non-key child", + "pk(1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - pk - multi-child", + "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - wpkh - multi-child", + "wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - wpkh - non-key child", + "wpkh(1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - wpkh - wsh parent", + "wsh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - wpkh - descriptor type parent", + "pk(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - combo - any parent", + "pk(combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - combo - multi-child", + "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - multi - not enough children", + "multi(1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - multi - no number", + "multi(022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - multi - negative number", + "multi(-1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - multi - non-key child", + "multi(1,1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - addr - multi-child", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3,bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - addr - non-address child", + /* Note: The actual check in verify_addr is unreachable as children + * of addr() nodes are only analysed as addresses. */ + "addr(1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - addr - any parent", + "pk(addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - raw - multi-child", + "raw(000102030405060708090a0b0c0d0e0f,000102030405060708090a0b0c0d0e0f)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - raw - non-raw child", + /* Note: The actual check in verify_raw is unreachable as children + * of raw() nodes are only analysed as raw hex. */ + "raw(1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - raw - any parent", + "pk(raw(000102030405060708090a0b0c0d0e0f))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - after - non number child", + "wsh(after(key_1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - after - zero delay", + "wsh(after(0)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - after - negative delay", + "wsh(after(-1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - after - delay too large", + "wsh(after(2147483648)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - older - non number child", + "wsh(older(key_1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - older - zero delay", + "wsh(older(0)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - older - negative delay", + "wsh(older(-1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "descriptor - older - delay too large", + "wsh(older(2147483648)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "miniscript - thresh - zero required", + "wsh(thresh(0,c:pk_k(key_1),sc:pk_k(key_2),sc:pk_k(key_3)))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "miniscript - thresh - not enough children", + "wsh(thresh(2,c:pk_k(key_1),sc:pk_k(key_2)))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "miniscript - thresh - require more than available children", + "wsh(thresh(4,c:pk_k(key_1),sc:pk_k(key_2),sc:pk_k(key_3)))", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "miniscript - core recursion limit", + "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::", + WALLY_NETWORK_BITCOIN_MAINNET + }, + /* https://github.com/rust-bitcoin/rust-miniscript/blob/master/src/descriptor/key.rs + * (Adapted) + */ + { + "miniscript - invalid xpub", + "pk([78412e3a]xpub1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaLcgJvLJuZZvRcEL/1/1)", + WALLY_NETWORK_BITCOIN_MAINNET + }, { + "miniscript - invalid raw key", + "pk([78412e3a]0208a117f3897c3a13c9384b8695eed98dc31bc2500feb19a1af424cd47a5d83/1/1)", + WALLY_NETWORK_BITCOIN_MAINNET + }, { + "miniscript - invalid fingerprint separator", + "pk([78412e3a]]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798/1/1)", + WALLY_NETWORK_BITCOIN_MAINNET + }, { + "miniscript - fuzzer error (1)", + "pk([11111f11]033333333333333333333333333333323333333333333333333333333433333333]]333]]3]]101333333333333433333]]]10]333333mmmm)", + WALLY_NETWORK_BITCOIN_MAINNET + }, { + "miniscript - fuzzer error (2)", + "pk(0777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777)", + WALLY_NETWORK_BITCOIN_MAINNET + }, { + "miniscript - Non-hex fingerprint", + "pk([NonHexor]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_BITCOIN_MAINNET + }, { + "miniscript - Short fingerprint", + "pk([1122334]", + WALLY_NETWORK_BITCOIN_MAINNET + }, { + "miniscript - Long fingerprint", + "pk([112233445]", + WALLY_NETWORK_BITCOIN_MAINNET + } + /* TODO: Add more tests for verify_x cases */ +}; + +struct descriptor_err_test g_address_err_cases[] = { + { + "address errchk - invalid network", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0xf0 + },{ + "address errchk - addr - unmatch network1", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + WALLY_NETWORK_BITCOIN_TESTNET + },{ + "address errchk - addr - no HRP", + "addr(bcqrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + WALLY_NETWORK_BITCOIN_TESTNET + },{ + "address errchk - addr - unmatch network2", + "addr(ex1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39q06fgz7)", + WALLY_NETWORK_LIQUID_REGTEST + },{ + "address errchk - unsupport address - p2pk", + "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - unsupport address - raw", + "raw(6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e)#zf2avljj", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - unterminated key origin", + "pkh([d34db33f/44'/0'/0'mainnet_xpub/1/*)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - double slash", + "pkh([d34db33f/44'/0'/0']mainnet_xpub//)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - middle double slash", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1//2)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - end slash", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/2/)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - duplicate wildcard (1)", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/**)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - duplicate wildcard (2)", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*/*)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - non-final wildcard", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*/1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - hardened from xpub", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1h)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - hardened wildcard from xpub", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*h)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - index too large", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/2147483648/1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - invalid path character", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/3c/1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + /* Note: mainnet_xpub depth is 4, so this valid path takes it over the depth limit */ + "address errchk - depth exceeded", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + /* Paths over 255 elements in length are up-front invalid */ + "address errchk - path too long", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - invalid descriptor character", + "pkh(\b)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - unknown function", + "foo(mainnet_xpub)", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - missing leading brace", + ")", + WALLY_NETWORK_BITCOIN_MAINNET + },{ + "address errchk - trailing value", + "pkh(mainnet_xpub),1", + WALLY_NETWORK_BITCOIN_MAINNET + } +}; + +static bool check_parse_miniscript(const char *function, const char *descriptor, + const char *expected, + const struct wally_map *map_in, + uint32_t flags) +{ + size_t written = 0; + unsigned char script[520]; + uint32_t index = 0; + + int ret = wally_miniscript_to_script(descriptor, map_in, index, flags, + script, sizeof(script), &written); + if (!expected) + return check_ret(function, ret, WALLY_EINVAL); + + return check_ret(function, ret, WALLY_OK) && + check_varbuff(function, script, written, expected); +} + +static bool check_descriptor_to_scriptpubkey(const char *function, + const char *descriptor, + const char *expected, + const uint32_t *bip32_index, + const char *expected_checksum) +{ + size_t written = 0; + unsigned char script[520]; + char *checksum, *canonical, *canonical_checksum; + int ret; + uint32_t network = 0, desc_depth = 0, desc_index = 0, flags = 0; + uint32_t index = bip32_index ? *bip32_index : 0; + + ret = wally_descriptor_to_scriptpubkey(descriptor, &g_key_map, index, network, + desc_depth, desc_index, flags, + script, sizeof(script), &written); + if (!check_ret(function, ret, WALLY_OK)) + return false; + + ret = wally_descriptor_get_checksum(descriptor, &g_key_map, flags, &checksum); + if (!check_ret(function, ret, WALLY_OK)) + return false; + + ret = wally_descriptor_canonicalize(descriptor, &g_key_map, flags, &canonical); + if (!check_ret(function, ret, WALLY_OK)) + return false; + + ret = wally_descriptor_get_checksum(canonical, &g_key_map, flags, &canonical_checksum); + if (!check_ret(function, ret, WALLY_OK)) + return false; + + if (check_varbuff(function, script, written, expected)) { + if (strcmp(checksum, expected_checksum) == 0 && + strcmp(canonical_checksum, expected_checksum) == 0) { + wally_free_string(canonical); + wally_free_string(canonical_checksum); + wally_free_string(checksum); + return true; + } + printf("%s: expected [%s], got [%s / %s]\n", function, expected_checksum, + checksum, canonical_checksum); + } + return false; +} + +static bool check_descriptor_to_scriptpubkey_depth(const char *function, + const char *descriptor, + const uint32_t depth, + const uint32_t index, + const char *expected_scriptpubkey) +{ + size_t written = 0; + unsigned char script[520]; + uint32_t network = 0, flags = 0; + + int ret = wally_descriptor_to_scriptpubkey(descriptor, &g_key_map, 0, network, + depth, index, flags, + script, sizeof(script), &written); + return check_ret(function, ret, WALLY_OK) && + check_varbuff(function, script, written, expected_scriptpubkey); +} + +static bool check_descriptor_to_address(const char *function, + const char *descriptor, + const uint32_t bip32_index, + const uint32_t network, + const char *expected_address) +{ + char *address = NULL; + uint32_t flags = 0; + + int ret = wally_descriptor_to_address(descriptor, &g_key_map, bip32_index, + network, flags, &address); + if (check_ret(function, ret, WALLY_OK) && strcmp(address, expected_address) == 0) { + wally_free_string(address); + return true; + } + printf("%s: expected [%s], got [%s]\n", function, expected_address, address); + return false; +} + +static bool check_descriptor_to_addresses(const char *function, + const char *descriptor, + const uint32_t child_num, + const uint32_t network, + const char **expected_address_list, + size_t num_addresses) +{ + char *addresses[64]; + uint32_t flags = 0; + size_t i; + + int ret = wally_descriptor_to_addresses(descriptor, &g_key_map, child_num, + network, flags, addresses, num_addresses); + if (!check_ret(function, ret, WALLY_OK)) + return false; + + for (i = 0; i < num_addresses; ++i) { + if (strcmp(expected_address_list[i], addresses[i]) != 0) { + printf("%s: expected address: %s, got%s\n", function, expected_address_list[i], addresses[i]); + return false; + } + wally_free_string(addresses[i]); + } + return true; +} + +static bool check_descriptor_scriptpubkey_error(const char *function, + const char *descriptor, + const uint32_t network) +{ + size_t written = 0; + unsigned char script[520]; + uint32_t flags = 0, desc_depth = 0, desc_index = 0; + + int ret = wally_descriptor_to_scriptpubkey(descriptor, &g_key_map, 0, network, + desc_depth, desc_index, flags, + script, sizeof(script), &written); + return check_ret(function, ret, WALLY_EINVAL); +} + +static bool check_descriptor_address_error(const char *function, + const char *descriptor, + const uint32_t network) +{ + char *address = NULL; + uint32_t flags = 0; + + int ret = wally_descriptor_to_address(descriptor, &g_key_map, 0, network, flags, &address); + return check_ret(function, ret, WALLY_EINVAL); +} + +int main(void) +{ + bool tests_ok = true; + size_t i; + + for (i = 0; i < NUM_ELEMS(g_miniscript_ref_cases); ++i) { + if (!check_parse_miniscript( + g_miniscript_ref_cases[i].miniscript, + g_miniscript_ref_cases[i].miniscript, + g_miniscript_ref_cases[i].scriptpubkey, NULL, 0)) { + printf("[%s] test failed!\n", g_miniscript_ref_cases[i].miniscript); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_miniscript_cases); ++i) { + if (!check_parse_miniscript( + g_miniscript_cases[i].name, + g_miniscript_cases[i].descriptor, + g_miniscript_cases[i].scriptpubkey, NULL, 0)) { + printf("[%s] test failed!\n", g_miniscript_cases[i].name); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_miniscript_taproot_cases); ++i) { + if (!check_parse_miniscript( + g_miniscript_taproot_cases[i].miniscript, + g_miniscript_taproot_cases[i].miniscript, + g_miniscript_taproot_cases[i].scriptpubkey, + NULL, g_miniscript_taproot_cases[i].flags)) { + printf("[%s] test failed!\n", g_miniscript_taproot_cases[i].miniscript); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_descriptor_cases); ++i) { + if (!check_descriptor_to_scriptpubkey( + g_descriptor_cases[i].name, + g_descriptor_cases[i].descriptor, + g_descriptor_cases[i].scriptpubkey, + g_descriptor_cases[i].bip32_index, + g_descriptor_cases[i].checksum)) { + printf("[%s] test failed!\n", g_descriptor_cases[i].name); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_descriptor_depth_cases); ++i) { + if (!check_descriptor_to_scriptpubkey_depth( + g_descriptor_depth_cases[i].name, + g_descriptor_depth_cases[i].descriptor, + g_descriptor_depth_cases[i].depth, + g_descriptor_depth_cases[i].index, + g_descriptor_depth_cases[i].scriptpubkey)) { + printf("[%s] keylist test failed!\n", g_descriptor_depth_cases[i].name); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_descriptor_address_cases); ++i) { + if (!check_descriptor_to_address( + g_descriptor_address_cases[i].name, + g_descriptor_address_cases[i].descriptor, + g_descriptor_address_cases[i].bip32_index, + g_descriptor_address_cases[i].network, + g_descriptor_address_cases[i].address)) { + printf("[%s] test failed!\n", g_descriptor_address_cases[i].name); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_descriptor_addresses_cases); ++i) { + if (!check_descriptor_to_addresses( + g_descriptor_addresses_cases[i].name, + g_descriptor_addresses_cases[i].descriptor, + g_descriptor_addresses_cases[i].child_num, + g_descriptor_addresses_cases[i].network, + g_descriptor_addresses_cases[i].address_list, + g_descriptor_addresses_cases[i].num_addresses)) { + printf("[%s] test failed!\n", g_descriptor_addresses_cases[i].name); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_descriptor_err_cases); ++i) { + if (!check_descriptor_scriptpubkey_error( + g_descriptor_err_cases[i].name, + g_descriptor_err_cases[i].descriptor, + g_descriptor_err_cases[i].network)) { + printf("[%s] test failed!\n", g_descriptor_err_cases[i].name); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_address_err_cases); ++i) { + if (!check_descriptor_address_error( + g_address_err_cases[i].name, + g_address_err_cases[i].descriptor, + g_address_err_cases[i].network)) { + tests_ok = false; + } + } + + wally_cleanup(0); + return tests_ok ? 0 : 1; +} diff --git a/src/descriptor.c b/src/descriptor.c new file mode 100644 index 000000000..a3539fed0 --- /dev/null +++ b/src/descriptor.c @@ -0,0 +1,2308 @@ +#include "internal.h" + +#include "script.h" +#include "script_int.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define NUM_ELEMS(a) (sizeof(a) / sizeof(a[0])) + +/* Properties and expressions definition */ +#define TYPE_NONE 0x00 +#define TYPE_B 0x01 /* Base expressions */ +#define TYPE_V 0x02 /* Verify expressions */ +#define TYPE_K 0x04 /* Key expressions */ +#define TYPE_W 0x08 /* Wrapped expressions */ +#define TYPE_MASK 0x0F /* expressions mask */ + +#define PROP_Z 0x00000100 /* Zero-arg property */ +#define PROP_O 0x00000200 /* One-arg property */ +#define PROP_N 0x00000400 /* Nonzero arg property */ +#define PROP_D 0x00000800 /* Dissatisfiable property */ +#define PROP_U 0x00001000 /* Unit property */ +#define PROP_E 0x00002000 /* Expression property */ +#define PROP_F 0x00004000 /* Forced property */ +#define PROP_S 0x00008000 /* Safe property */ +#define PROP_M 0x00010000 /* Nonmalleable property */ +#define PROP_X 0x00020000 /* Expensive verify */ + +/* OP_0 properties: Bzudemsx */ +#define PROP_OP_0 (TYPE_B | PROP_Z | PROP_U | PROP_D | PROP_E | PROP_M | PROP_S | PROP_X) +/* OP_1 properties: Bzufmx */ +#define PROP_OP_1 (TYPE_B | PROP_Z | PROP_U | PROP_F | PROP_M | PROP_X) + +#define KIND_MINISCRIPT 0x01 +#define KIND_DESCRIPTOR 0x02 /* Output Descriptor */ +#define KIND_RAW 0x04 +#define KIND_NUMBER 0x08 +#define KIND_ADDRESS 0x10 +#define KIND_KEY 0x20 + +#define KIND_BASE58 (0x0100 | KIND_ADDRESS) +#define KIND_BECH32 (0x0200 | KIND_ADDRESS) + +#define KIND_PUBLIC_KEY (0x001000 | KIND_KEY) +#define KIND_PRIVATE_KEY (0x002000 | KIND_KEY) +#define KIND_BIP32 (0x004000 | KIND_KEY) +#define KIND_BIP32_PRIVATE_KEY (0x010000 | KIND_BIP32) +#define KIND_BIP32_PUBLIC_KEY (0x020000 | KIND_BIP32) + +/* FIXME: Calculate the script length instead of using this maximal size */ +#define DESCRIPTOR_MAX_SIZE 1000000 +#define DESCRIPTOR_MIN_SIZE 20 +#define MINISCRIPT_MULTI_MAX 20 +#define REDEEM_SCRIPT_MAX_SIZE 520 +#define WITNESS_SCRIPT_MAX_SIZE 10000 + +#define DESCRIPTOR_CHECKSUM_LENGTH 8 + +/* output descriptor */ +#define KIND_DESCRIPTOR_PK (0x00000100 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_PKH (0x00000200 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_MULTI (0x00000300 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_MULTI_S (0x00000400 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_SH (0x00000500 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_WPKH (0x00010000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_WSH (0x00020000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_COMBO (0x00030000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_ADDR (0x00040000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_RAW (0x00050000 | KIND_DESCRIPTOR) + +/* miniscript */ +#define KIND_MINISCRIPT_PK (0x00000100 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_PKH (0x00000200 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_MULTI (0x00000300 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_PK_K (0x00001000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_PK_H (0x00002000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OLDER (0x00010000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AFTER (0x00020000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_SHA256 (0x00030000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_HASH256 (0x00040000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_RIPEMD160 (0x00050000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_HASH160 (0x00060000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_THRESH (0x00070000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_ANDOR (0x01000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AND_V (0x02000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AND_B (0x03000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_AND_N (0x04000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_B (0x05000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_C (0x06000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_D (0x07000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_OR_I (0x08000000 | KIND_MINISCRIPT) + +/* A node in a parsed miniscript expression */ +typedef struct ms_node_t { + struct ms_node_t *next; + struct ms_node_t *child; + struct ms_node_t *parent; + uint32_t kind; + uint32_t type_properties; + int64_t number; + const char *child_path; + const char *data; + uint32_t data_len; + uint32_t child_path_len; + char wrapper_str[12]; + unsigned char builtin; + bool is_uncompressed_key; + bool is_xonly_key; +} ms_node; + +/* Built-in miniscript expressions */ +typedef int (*node_verify_fn_t)(ms_node *node); +typedef int (*node_gen_fn_t)(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written); + +struct ms_builtin_t { + const char *name; + const uint32_t name_len; + const uint32_t kind; + const uint32_t type_properties; + const uint32_t child_count; /* Number of expected children */ + const node_verify_fn_t verify_fn; + const node_gen_fn_t generate_fn; +}; + +struct ms_context { + unsigned char *script; + size_t script_len; + uint32_t child_num; /* Start child number for derivation */ + size_t num_derivations; /* How many incrementing children to derive */ +}; + +struct multisig_sort_data_t { + size_t pubkey_len; + unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN]; +}; + +struct addr_ver_t { + const unsigned char network; + const unsigned char version_p2pkh; + const unsigned char version_p2sh; + const unsigned char version_wif; + const char family[8]; +}; + +static const struct addr_ver_t g_address_versions[] = { + { + WALLY_NETWORK_BITCOIN_MAINNET, + WALLY_ADDRESS_VERSION_P2PKH_MAINNET, + WALLY_ADDRESS_VERSION_P2SH_MAINNET, + WALLY_ADDRESS_VERSION_WIF_MAINNET, + { 'b', 'c', '\0', '\0', '\0', '\0', '\0', '\0' } + }, + { + WALLY_NETWORK_BITCOIN_TESTNET, + WALLY_ADDRESS_VERSION_P2PKH_TESTNET, + WALLY_ADDRESS_VERSION_P2SH_TESTNET, + WALLY_ADDRESS_VERSION_WIF_TESTNET, + { 't', 'b', '\0', '\0', '\0', '\0', '\0', '\0' } + }, + { /* Bitcoin regtest. This must remain immediately after WALLY_NETWORK_BITCOIN_TESTNET */ + WALLY_NETWORK_BITCOIN_REGTEST, + WALLY_ADDRESS_VERSION_P2PKH_TESTNET, + WALLY_ADDRESS_VERSION_P2SH_TESTNET, + WALLY_ADDRESS_VERSION_WIF_TESTNET, + { 'b', 'c', 'r', 't', '\0', '\0', '\0', '\0' } + }, + { + WALLY_NETWORK_LIQUID, + WALLY_ADDRESS_VERSION_P2PKH_LIQUID, + WALLY_ADDRESS_VERSION_P2SH_LIQUID, + WALLY_ADDRESS_VERSION_WIF_MAINNET, + { 'e', 'x', '\0', '\0', '\0', '\0', '\0', '\0' } + }, + { + WALLY_NETWORK_LIQUID_TESTNET, + WALLY_ADDRESS_VERSION_P2PKH_LIQUID_TESTNET, + WALLY_ADDRESS_VERSION_P2SH_LIQUID_TESTNET, + WALLY_ADDRESS_VERSION_WIF_TESTNET, + { 't', 'e', 'x', '\0', '\0', '\0', '\0', '\0' } + }, + { + WALLY_NETWORK_LIQUID_REGTEST, + WALLY_ADDRESS_VERSION_P2PKH_LIQUID_REGTEST, + WALLY_ADDRESS_VERSION_P2SH_LIQUID_REGTEST, + WALLY_ADDRESS_VERSION_WIF_TESTNET, + { 'e', 'r', 't', '\0', '\0', '\0', '\0', '\0' } + }, +}; + +static const struct addr_ver_t *addr_ver_from_network(uint32_t network) +{ + size_t i; + for (i = 0; i < NUM_ELEMS(g_address_versions); ++i) { + if (network == g_address_versions[i].network) + return g_address_versions + i; + } + return NULL; /* Not found */ +} + +static const struct addr_ver_t *addr_ver_from_version( + uint32_t version, const struct addr_ver_t *expected, bool *is_p2sh) +{ + size_t i; + + for (i = 0; i < NUM_ELEMS(g_address_versions); ++i) { + const struct addr_ver_t *addr_ver = g_address_versions + i; + if (version == addr_ver->version_p2pkh || version == addr_ver->version_p2sh) { + /* Found a matching network based on base58 address version */ + if (expected && addr_ver->network != expected->network) { + /* Mismatch on caller provided network */ + if (addr_ver->network == WALLY_NETWORK_BITCOIN_TESTNET && + expected->network == WALLY_NETWORK_BITCOIN_REGTEST) + ++addr_ver; /* testnet/regtest use the same versions; use regtest */ + else + return NULL; /* Mismatch on provided network: Not found */ + } + *is_p2sh = version == addr_ver->version_p2sh; + return addr_ver; /* Found */ + } + } + return NULL; /* Not found */ +} + +static const struct addr_ver_t *addr_ver_from_family( + const char *family, size_t family_len, uint32_t network) +{ + const struct addr_ver_t *addr_ver = addr_ver_from_network(network); + if (!addr_ver || !family || strlen(addr_ver->family) != family_len || + memcmp(family, addr_ver->family, family_len)) + return NULL; /* Not found or mismatched address version */ + return addr_ver; /* Found */ +} + +/* Function prototype */ +static const struct ms_builtin_t *builtin_get(const ms_node *node); +static int analyze_address(const char *str, size_t str_len, + ms_node *node, ms_node *parent, + const struct addr_ver_t *addr_ver, + unsigned char *script, size_t script_len, size_t *written); +static int generate_script(ms_node *node, uint32_t child_num, + unsigned char *script, size_t script_len, size_t *written); + +/* Wrapper for strtoll */ +static bool strtoll_n(const char *str, size_t str_len, int64_t *v) +{ + char buf[21]; /* from -9223372036854775808 to 9223372036854775807 */ + char *end = NULL; + + if (!str_len || str_len > sizeof(buf) - 1u || + (str[0] != '-' && (str[0] < '0' || str[0] > '9'))) + return false; /* Too short/long, or invalid format */ + + memcpy(buf, str, str_len); + buf[str_len] = '\0'; + *v = strtoll(buf, &end, 10); + return end == buf + str_len && *v != LLONG_MIN && *v != LLONG_MAX; +} + +static uint32_t node_get_child_count(const ms_node *node) +{ + int32_t ret = 0; + const ms_node *child; + for (child = node->child; child; child = child->next) + ++ret; + return ret; +} + +static bool node_has_uncompressed_key(const ms_node *node) +{ + const ms_node *child; + for (child = node->child; child; child = child->next) + if (child->is_uncompressed_key || node_has_uncompressed_key(child)) + return true; + return false; +} + +static bool node_is_root(const ms_node *node) +{ + /* True if this is a (possibly temporary) top level node, or an argument of a builtin */ + return !node->parent || node->parent->builtin; +} + +static void node_free(ms_node *node) +{ + if (node) { + ms_node *child = node->child; + while (child) { + ms_node *next = child->next; + node_free(child); + child = next; + } + clear_and_free(node, sizeof(*node)); + } +} + +static int verify_sh(ms_node *node) +{ + if (node->parent || !node->child->builtin) + return WALLY_EINVAL; + + node->type_properties = node->child->type_properties; + return WALLY_OK; +} + +static int verify_wsh(ms_node *node) +{ + if (node->parent && node->parent->kind != KIND_DESCRIPTOR_SH) + return WALLY_EINVAL; + if (!node->child->builtin || node_has_uncompressed_key(node)) + return WALLY_EINVAL; + + node->type_properties = node->child->type_properties; + return WALLY_OK; +} + +static int verify_pk(ms_node *node) +{ + if (node->child->builtin || !(node->child->kind & KIND_KEY)) + return WALLY_EINVAL; + + node->type_properties = builtin_get(node)->type_properties; + return WALLY_OK; +} + +static int verify_wpkh(ms_node *node) +{ + ms_node *parent = node->parent; + if (parent && (!parent->builtin || parent->kind & KIND_MINISCRIPT)) + return WALLY_EINVAL; + if (node->child->builtin || !(node->child->kind & KIND_KEY)) + return WALLY_EINVAL; + + for (/* no-op */; parent; parent = parent->parent) + if (parent->kind == KIND_DESCRIPTOR_WSH) + return WALLY_EINVAL; + + return node_has_uncompressed_key(node) ? WALLY_EINVAL : WALLY_OK; +} + +static int verify_combo(ms_node *node) +{ + if (node->parent) + return WALLY_EINVAL; + + /* Since the combo is of multiple return types, the return value is wpkh or pkh. */ + return node_has_uncompressed_key(node) ? verify_pk(node) : verify_wpkh(node); +} + +static int verify_multi(ms_node *node) +{ + const int64_t count = node_get_child_count(node); + ms_node *top, *key; + + if (count < 2 || count - 1 > MINISCRIPT_MULTI_MAX) + return WALLY_EINVAL; + + top = node->child; + if (!top->next || top->builtin || top->kind != KIND_NUMBER || + top->number <= 0 || count < top->number) + return WALLY_EINVAL; + + key = top->next; + while (key) { + if (key->builtin || !(key->kind & KIND_KEY)) + return WALLY_EINVAL; + key = key->next; + } + + node->type_properties = builtin_get(node)->type_properties; + return WALLY_OK; +} + +static int verify_addr(ms_node *node) +{ + if (node->parent || node->child->builtin || !(node->child->kind & KIND_ADDRESS)) + return WALLY_EINVAL; + return WALLY_OK; +} + +static int verify_raw(ms_node *node) +{ + if (node->parent || node->child->builtin || !(node->child->kind & KIND_RAW)) + return WALLY_EINVAL; + return WALLY_OK; +} + +static int verify_delay(ms_node *node) +{ + if (node->child->builtin || node->child->kind != KIND_NUMBER || + node->child->number <= 0 || node->child->number > 0x7fffffff) + return WALLY_EINVAL; + + node->type_properties = builtin_get(node)->type_properties; + return WALLY_OK; +} + +static int verify_hash_type(ms_node *node) +{ + if (node->child->builtin || !(node->child->kind & KIND_RAW)) + return WALLY_EINVAL; + + node->type_properties = builtin_get(node)->type_properties; + return WALLY_OK; +} + +static uint32_t verify_andor_property(uint32_t x_property, uint32_t y_property, uint32_t z_property) +{ + /* Y and Z are both B, K, or V */ + uint32_t prop = PROP_X; + uint32_t need_x = TYPE_B | PROP_D | PROP_U; + uint32_t need_yz = TYPE_B | TYPE_K | TYPE_V; + if (!(x_property & TYPE_B) || !(x_property & need_x)) + return 0; + if (!(y_property & z_property & need_yz)) + return 0; + + prop |= y_property & z_property & need_yz; + prop |= x_property & y_property & z_property & PROP_Z; + prop |= (x_property | (y_property & z_property)) & PROP_O; + prop |= y_property & z_property & PROP_U; + prop |= z_property & PROP_D; + if (x_property & PROP_S || y_property & PROP_F) { + prop |= z_property & PROP_F; + prop |= x_property & z_property & PROP_E; + } + if (x_property & PROP_E && + (x_property | y_property | z_property) & PROP_S) { + prop |= x_property & y_property & z_property & PROP_M; + } + prop |= z_property & (x_property | y_property) & PROP_S; + return prop; +} + +static int verify_andor(ms_node *node) +{ + node->type_properties = verify_andor_property(node->child->type_properties, + node->child->next->type_properties, + node->child->next->next->type_properties); + return node->type_properties ? WALLY_OK : WALLY_EINVAL; +} + +static uint32_t verify_and_v_property(uint32_t x_property, uint32_t y_property) +{ + uint32_t prop = 0; + prop |= x_property & PROP_N; + prop |= y_property & (PROP_U | PROP_X); + prop |= x_property & y_property & (PROP_D | PROP_M | PROP_Z); + prop |= (x_property | y_property) & PROP_S; + if (x_property & TYPE_V) + prop |= y_property & (TYPE_K | TYPE_V | TYPE_B); + if (x_property & PROP_Z) + prop |= y_property & PROP_N; + if ((x_property | y_property) & PROP_Z) + prop |= (x_property | y_property) & PROP_O; + if (y_property & PROP_F || x_property & PROP_S) + prop |= PROP_F; + + return prop & TYPE_MASK ? prop : 0; +} + +static int verify_and_v(ms_node *node) +{ + node->type_properties = verify_and_v_property( + node->child->type_properties, + node->child->next->type_properties); + return node->type_properties ? WALLY_OK : WALLY_EINVAL; +} + +static int verify_and_b(ms_node *node) +{ + const uint32_t x_prop = node->child->type_properties; + const uint32_t y_prop = node->child->next->type_properties; + node->type_properties = PROP_U | PROP_X; + node->type_properties |= x_prop & y_prop & (PROP_D | PROP_Z | PROP_M); + node->type_properties |= (x_prop | y_prop) & PROP_S; + node->type_properties |= x_prop & PROP_N; + if (y_prop & TYPE_W) + node->type_properties |= x_prop & TYPE_B; + if ((x_prop | y_prop) & PROP_Z) + node->type_properties |= (x_prop | y_prop) & PROP_O; + if (x_prop & PROP_Z) + node->type_properties |= y_prop & PROP_N; + if ((x_prop & y_prop) & PROP_S) + node->type_properties |= x_prop & y_prop & PROP_E; + if (((x_prop & y_prop) & PROP_F) || + !(~x_prop & (PROP_S | PROP_F)) || + !(~y_prop & (PROP_S | PROP_F))) + node->type_properties |= PROP_F; + + return WALLY_OK; +} + +static int verify_and_n(ms_node *node) +{ + node->type_properties = verify_andor_property(node->child->type_properties, + node->child->next->type_properties, + PROP_OP_0); + return node->type_properties ? WALLY_OK : WALLY_EINVAL; +} + +static int verify_or_b(ms_node *node) +{ + const uint32_t x_prop = node->child->type_properties; + const uint32_t y_prop = node->child->next->type_properties; + node->type_properties = PROP_D | PROP_U | PROP_X; + node->type_properties |= x_prop & y_prop & (PROP_Z | PROP_S | PROP_E); + if (!(~x_prop & (TYPE_B | PROP_D)) && + !(~y_prop & (TYPE_W | PROP_D))) + node->type_properties |= TYPE_B; + if ((x_prop | y_prop) & PROP_Z) + node->type_properties |= (x_prop | y_prop) & PROP_O; + if (((x_prop | y_prop) & PROP_S) && + ((x_prop & y_prop) & PROP_E)) + node->type_properties |= x_prop & y_prop & PROP_M; + + return WALLY_OK; +} + +static int verify_or_c(ms_node *node) +{ + const uint32_t x_prop = node->child->type_properties; + const uint32_t y_prop = node->child->next->type_properties; + node->type_properties = PROP_F | PROP_X; + node->type_properties |= x_prop & y_prop & (PROP_Z | PROP_S); + if (!(~x_prop & (TYPE_B | PROP_D | PROP_U))) + node->type_properties |= y_prop & TYPE_V; + if (y_prop & PROP_Z) + node->type_properties |= x_prop & PROP_O; + if (x_prop & PROP_E && ((x_prop | y_prop) & PROP_S)) + node->type_properties |= x_prop & y_prop & PROP_M; + + return WALLY_OK; +} + +static int verify_or_d(ms_node *node) +{ + const uint32_t x_prop = node->child->type_properties; + const uint32_t y_prop = node->child->next->type_properties; + node->type_properties = PROP_X; + node->type_properties |= x_prop & y_prop & (PROP_Z | PROP_E | PROP_S); + node->type_properties |= y_prop & (PROP_U | PROP_F | PROP_D); + if (!(~x_prop & (TYPE_B | PROP_D | PROP_U))) + node->type_properties |= y_prop & TYPE_B; + if (y_prop & PROP_Z) + node->type_properties |= x_prop & PROP_O; + if (x_prop & PROP_E && ((x_prop | y_prop) & PROP_S)) + node->type_properties |= x_prop & y_prop & PROP_M; + + return WALLY_OK; +} + +static uint32_t verify_or_i_property(uint32_t x_property, uint32_t y_property) +{ + uint32_t prop = PROP_X; + prop |= x_property & y_property & (TYPE_V | TYPE_B | TYPE_K | PROP_U | PROP_F | PROP_S); + if (!(prop & TYPE_MASK)) + return 0; + + prop |= (x_property | y_property) & PROP_D; + if ((x_property & y_property) & PROP_Z) + prop |= PROP_O; + if ((x_property | y_property) & PROP_F) + prop |= (x_property | y_property) & PROP_E; + if ((x_property | y_property) & PROP_S) + prop |= x_property & y_property & PROP_M; + + return prop; +} + +static int verify_or_i(ms_node *node) +{ + node->type_properties = verify_or_i_property(node->child->type_properties, + node->child->next->type_properties); + return node->type_properties ? WALLY_OK : WALLY_EINVAL; +} + +static int verify_thresh(ms_node *node) +{ + ms_node *top = top = node->child, *child; + int64_t count = 0, num_s = 0, args = 0; + bool all_e = true, all_m = true; + + if (!top || top->builtin || top->kind != KIND_NUMBER) + return WALLY_EINVAL; + + for (child = top->next; child; child = child->next) { + const uint32_t expected_type = count ? TYPE_W : TYPE_B; + + if (!child->builtin || (~child->type_properties & (expected_type | PROP_D | PROP_U))) + return WALLY_EINVAL; + + if (~child->type_properties & PROP_E) + all_e = false; + if (~child->type_properties & PROP_M) + all_m = false; + if (child->type_properties & PROP_S) + ++num_s; + if (child->type_properties & PROP_Z) + args += (~child->type_properties & PROP_O) ? 2 : 1; + + ++count; + } + if (count < 3 || top->number < 1 || top->number >= count) + return WALLY_EINVAL; + + node->type_properties = TYPE_B | PROP_D | PROP_U; + if (args == 0) + node->type_properties |= PROP_Z; + else if (args == 1) + node->type_properties |= PROP_O; + if (all_e && num_s == count) + node->type_properties |= PROP_E; + if (all_e && all_m && num_s >= count - top->number) + node->type_properties |= PROP_M; + if (num_s >= count - top->number + 1) + node->type_properties |= PROP_S; + + return WALLY_OK; +} + +static int node_verify_wrappers(ms_node *node) +{ + uint32_t *properties = &node->type_properties; + size_t i; + + if (node->wrapper_str[0] == '\0') + return WALLY_OK; /* No wrappers */ + + /* Validate the nodes wrappers in reserve order */ + for (i = strlen(node->wrapper_str); i != 0; --i) { + const uint32_t x_prop = *properties; +#define PROP_REQUIRE(props) if ((x_prop & (props)) != (props)) return WALLY_EINVAL +#define PROP_CHANGE_TYPE(clr, set) *properties &= ~(clr); *properties |= set +#define PROP_CHANGE(keep, set) *properties &= (TYPE_MASK | keep); *properties |= set + + switch(node->wrapper_str[i - 1]) { + case 'a': + PROP_REQUIRE(TYPE_B); + PROP_CHANGE_TYPE(TYPE_B, TYPE_W); + PROP_CHANGE(PROP_U | PROP_D | PROP_F | PROP_E | PROP_M | PROP_S, PROP_X); + break; + case 's': + PROP_REQUIRE(TYPE_B | PROP_O); + PROP_CHANGE_TYPE(TYPE_B | PROP_O, TYPE_W); + PROP_CHANGE(PROP_U | PROP_D | PROP_F | PROP_E | PROP_M | PROP_S | PROP_X, 0); + break; + case 'c': + PROP_REQUIRE(TYPE_K); + PROP_CHANGE_TYPE(TYPE_K, TYPE_B); + PROP_CHANGE(PROP_O | PROP_N | PROP_D | PROP_F | PROP_E | PROP_M, PROP_U | PROP_S); + break; + case 't': + *properties = verify_and_v_property(x_prop, PROP_OP_1); + if (!(*properties & TYPE_MASK)) + return WALLY_EINVAL; + /* prop >= PROP_F */ + break; + case 'd': + PROP_REQUIRE(TYPE_V | PROP_Z); + PROP_CHANGE_TYPE(TYPE_V | PROP_Z, TYPE_B); + PROP_CHANGE(PROP_M | PROP_S, PROP_N | PROP_U | PROP_D | PROP_X); + if (x_prop & PROP_Z) + *properties |= PROP_O; + if (x_prop & PROP_F) { + *properties &= ~PROP_F; + *properties |= PROP_E; + } + break; + case 'v': + PROP_REQUIRE(TYPE_B); + PROP_CHANGE_TYPE(TYPE_B, TYPE_V); + PROP_CHANGE(PROP_Z | PROP_O | PROP_N | PROP_M | PROP_S, PROP_F | PROP_X); + break; + case 'j': + PROP_REQUIRE(TYPE_B | PROP_N); + *properties &= TYPE_MASK | PROP_O | PROP_U | PROP_M | PROP_S; + *properties |= PROP_N | PROP_D | PROP_X; + if (x_prop & PROP_F) { + PROP_CHANGE(~PROP_F, PROP_E); + } + break; + case 'n': + PROP_REQUIRE(TYPE_B); + PROP_CHANGE(PROP_Z | PROP_O | PROP_N | PROP_D | PROP_F | PROP_E | PROP_M | PROP_S, PROP_X); + break; + case 'l': + *properties = verify_or_i_property(PROP_OP_0, x_prop); + break; + case 'u': + *properties = verify_or_i_property(x_prop, PROP_OP_0); + break; + default: + return WALLY_EINVAL; /* Wrapper type not found */ + break; + } + } + + switch (*properties & TYPE_MASK) { + case TYPE_B: + case TYPE_V: + case TYPE_K: + case TYPE_W: + break; + default: + return WALLY_EINVAL; /* K, V, B, W all conflict with each other */ + } + + if (((*properties & PROP_Z) && (*properties & PROP_O)) || + ((*properties & PROP_N) && (*properties & PROP_Z)) || + ((*properties & TYPE_V) && (*properties & PROP_D)) || + ((*properties & TYPE_K) && !(*properties & PROP_U)) || + ((*properties & TYPE_V) && (*properties & PROP_U)) || + ((*properties & PROP_E) && (*properties & PROP_F)) || + ((*properties & PROP_E) && !(*properties & PROP_D)) || + ((*properties & TYPE_V) && (*properties & PROP_E)) || + ((*properties & PROP_D) && (*properties & PROP_F)) || + ((*properties & TYPE_V) && !(*properties & PROP_F)) || + ((*properties & TYPE_K) && !(*properties & PROP_S)) || + ((*properties & PROP_Z) && !(*properties & PROP_M))) + return WALLY_EINVAL; + + return WALLY_OK; +} + +static int generate_script_from_number(int64_t number, ms_node *parent, + unsigned char *script, size_t script_len, size_t *written) +{ + if ((parent && !parent->builtin) || !script_len) + return WALLY_EINVAL; + + if (number == -1) { + script[0] = OP_1NEGATE; + *written = 1; + } else if (number >= 0 && number <= 16) { + script[0] = value_to_op_n(number); + *written = 1; + } else { + /* PUSH */ + script[0] = scriptint_get_length(number); + if (script_len < script[0] + 1u) + return WALLY_EINVAL; + scriptint_to_bytes(number, script + 1); + *written = script[0] + 1; + } + return WALLY_OK; +} + +static int generate_pk_k(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + + if (!node->child || script_len < EC_PUBLIC_KEY_LEN * 2 || !node_is_root(node)) + return WALLY_EINVAL; + + ret = generate_script(node->child, child_num, &script[1], script_len - 1, written); + if (ret != WALLY_OK) + return ret; + + if (*written + 1 > REDEEM_SCRIPT_MAX_SIZE) + return WALLY_EINVAL; + + script[0] = (unsigned char)*written; + ++(*written); + return ret; +} + +static int generate_pk_h(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + size_t output_len = *written; + unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN]; + + if (!node->child || script_len < WALLY_SCRIPTPUBKEY_P2PKH_LEN - 1 || !node_is_root(node)) + return WALLY_EINVAL; + if (node->child->is_xonly_key) + return WALLY_EINVAL; + + ret = generate_script(node->child, child_num, pubkey, sizeof(pubkey), &output_len); + if (ret != WALLY_OK) + return ret; + + ret = wally_hash160(pubkey, output_len, &script[3], HASH160_LEN); + if (ret != WALLY_OK) + return ret; + + script[0] = OP_DUP; + script[1] = OP_HASH160; + script[2] = HASH160_LEN; + script[HASH160_LEN + 3] = OP_EQUALVERIFY; + *written = HASH160_LEN + 4; + return ret; +} + +static int generate_sh_wsh(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const bool is_sh = node->kind == KIND_DESCRIPTOR_SH; + const size_t required_len = is_sh ? WALLY_SCRIPTPUBKEY_P2SH_LEN : WALLY_SCRIPTPUBKEY_P2WSH_LEN; + const uint32_t flags = is_sh ? WALLY_SCRIPT_HASH160 : WALLY_SCRIPT_SHA256; + size_t output_len = *written; + unsigned char output[WALLY_SCRIPTPUBKEY_P2WSH_LEN]; + int ret; + + if (!node->child || script_len < required_len || !node_is_root(node)) + return WALLY_EINVAL; + + ret = generate_script(node->child, child_num, script, script_len, &output_len); + if (ret != WALLY_OK) + return ret; + + if (output_len > REDEEM_SCRIPT_MAX_SIZE) + ret = WALLY_EINVAL; + + ret = (is_sh ? wally_scriptpubkey_p2sh_from_bytes : wally_witness_program_from_bytes)( + script, output_len, flags, output, required_len, written); + if (ret == WALLY_OK) + memcpy(script, output, *written); + + return ret; +} + +static int generate_checksig(unsigned char *script, size_t script_len, size_t *written) +{ + if (!*written || (*written + 1 > script_len) || (*written + 1 > WITNESS_SCRIPT_MAX_SIZE)) + return WALLY_EINVAL; + + script[*written] = OP_CHECKSIG; + *written += 1; + return WALLY_OK; +} + +static int generate_pk(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret = generate_pk_k(node, child_num, script, script_len, written); + return ret == WALLY_OK ? generate_checksig(script, script_len, written) : ret; +} + +static int generate_pkh(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret = generate_pk_h(node, child_num, script, script_len, written); + return ret == WALLY_OK ? generate_checksig(script, script_len, written) : ret; +} + +static int generate_wpkh(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + size_t output_len = *written; + unsigned char output[WALLY_SCRIPTPUBKEY_P2WPKH_LEN]; + + if (!node->child || script_len < sizeof(output) || !node_is_root(node)) + return WALLY_EINVAL; + + ret = generate_script(node->child, child_num, script, script_len, &output_len); + if (ret == WALLY_OK) { + if (output_len > REDEEM_SCRIPT_MAX_SIZE) + return WALLY_EINVAL; + + ret = wally_witness_program_from_bytes(script, output_len, WALLY_SCRIPT_HASH160, + output, WALLY_SCRIPTPUBKEY_P2WPKH_LEN, written); + if (ret == WALLY_OK) + memcpy(script, output, *written); + } + return ret; +} + +static int generate_combo(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + if (node_has_uncompressed_key(node)) + return generate_pkh(node, child_num, script, script_len, written); + return generate_wpkh(node, child_num, script, script_len, written); +} + +static int compare_multisig_node(const void *lhs, const void *rhs) +{ + const struct multisig_sort_data_t *l = lhs; + /* Note: if pubkeys are different sizes, the head byte will differ and so this + * memcmp will not read beyond either */ + return memcmp(l->pubkey, ((const struct multisig_sort_data_t *)rhs)->pubkey, l->pubkey_len); +} + +static int generate_multi(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + size_t offset; + uint32_t count, i; + ms_node *child = node->child; + struct multisig_sort_data_t sorted[15]; /* 15 = Max number of pubkeys for OP_CHECKMULTISIG */ + size_t check_len = script_len <= REDEEM_SCRIPT_MAX_SIZE ? script_len : REDEEM_SCRIPT_MAX_SIZE; + int ret; + + if (!child || !node_is_root(node) || !node->builtin) + return WALLY_EINVAL; + + if ((ret = generate_script(child, child_num, script, script_len, &offset)) != WALLY_OK) + return ret; + + child = child->next; + for (count = 0; ret == WALLY_OK && child && count < NUM_ELEMS(sorted); ++count) { + struct multisig_sort_data_t *item = sorted + count; + ret = generate_script(child, child_num, + item->pubkey, sizeof(item->pubkey), &item->pubkey_len); + if (ret == WALLY_OK && item->pubkey_len > sizeof(item->pubkey)) + ret = WALLY_EINVAL; + child = child->next; + } + + if (ret == WALLY_OK && (!count || child)) + ret = WALLY_EINVAL; /* Not enough, or too many keys for multisig */ + + if (ret == WALLY_OK) { + if (node->kind == KIND_DESCRIPTOR_MULTI_S) + qsort(sorted, count, sizeof(sorted[0]), compare_multisig_node); + + for (i = 0; ret == WALLY_OK && i < count; ++i) { + const size_t pubkey_len = sorted[i].pubkey_len; + if (offset + pubkey_len + 1 > check_len) + return WALLY_EINVAL; + script[offset] = pubkey_len; + memcpy(&script[offset + 1], sorted[i].pubkey, pubkey_len); + offset += pubkey_len + 1; + } + + if (ret == WALLY_OK) { + size_t number_len = 0; + ret = generate_script_from_number(count, node->parent, &script[offset], + check_len - offset, &number_len); + if (ret == WALLY_OK) { + offset += number_len; + if (offset + 1 > check_len) + return WALLY_EINVAL; + script[offset] = OP_CHECKMULTISIG; + *written = offset + 1; + } + } + } + return ret; +} + +static int generate_raw(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + if (!node->child || !script_len || !node_is_root(node)) + return WALLY_EINVAL; + + ret = generate_script(node->child, child_num, script, script_len, written); + return *written > REDEEM_SCRIPT_MAX_SIZE ? WALLY_EINVAL : ret; +} + +static int generate_delay(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + size_t output_len = *written; + if (!node->child || script_len < DESCRIPTOR_MIN_SIZE || !node_is_root(node) || !node->builtin) + return WALLY_EINVAL; + + ret = generate_script(node->child, child_num, script, script_len, &output_len); + if (ret != WALLY_OK) + return ret; + + if (output_len + 1 > REDEEM_SCRIPT_MAX_SIZE) + return WALLY_EINVAL; + + if (node->kind == KIND_MINISCRIPT_OLDER) + script[output_len] = OP_CHECKSEQUENCEVERIFY; + else if (node->kind == KIND_MINISCRIPT_AFTER) + script[output_len] = OP_CHECKLOCKTIMEVERIFY; + else + return WALLY_ERROR; /* Shouldn't happen */ + *written = output_len + 1; + return ret; +} + +static int generate_hash_type(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + unsigned char op_code; + size_t hash_size; + size_t output_len = *written; + size_t check_len = script_len <= REDEEM_SCRIPT_MAX_SIZE ? script_len : REDEEM_SCRIPT_MAX_SIZE; + + if (!node->child || !node_is_root(node) || !node->builtin) + return WALLY_EINVAL; + + if (node->kind == KIND_MINISCRIPT_SHA256) { + op_code = OP_SHA256; + hash_size = SHA256_LEN; + } else if (node->kind == KIND_MINISCRIPT_HASH256) { + op_code = OP_HASH256; + hash_size = SHA256_LEN; + } else if (node->kind == KIND_MINISCRIPT_RIPEMD160) { + op_code = OP_RIPEMD160; + hash_size = RIPEMD160_LEN; + } else if (node->kind == KIND_MINISCRIPT_HASH160) { + op_code = OP_HASH160; + hash_size = HASH160_LEN; + } else + return WALLY_ERROR; /* Shouldn't happen */ + + if (script_len < hash_size + 8) + return WALLY_EINVAL; + + ret = generate_script(node->child, child_num, &script[6], script_len - 8, &output_len); + if (ret == WALLY_OK) { + if (output_len + 7 > check_len) + return WALLY_EINVAL; + + script[0] = OP_SIZE; + script[1] = 0x01; + script[2] = 0x20; + script[3] = OP_EQUALVERIFY; + script[4] = op_code; + script[5] = hash_size; + script[output_len + 6] = OP_EQUAL; + *written = output_len + 7; + } + return ret; +} + +static int generate_concat(ms_node *node, int32_t child_num, size_t target_num, + const size_t *reference_indices, + const unsigned char *prev_insert, size_t prev_insert_num, + const unsigned char *first_insert, size_t first_insert_num, + const unsigned char *second_insert, size_t second_insert_num, + const unsigned char *last_append, size_t last_append_num, + unsigned char *script, size_t script_len, size_t *written) +{ + size_t output_len; + size_t total = prev_insert_num + first_insert_num + second_insert_num; + size_t i = 0, offset = 0; + ms_node *child[3] = { NULL, NULL, NULL }; + size_t default_indices[] = { 0, 1, 2 }; + const size_t *indices = reference_indices; + size_t check_len = script_len <= REDEEM_SCRIPT_MAX_SIZE ? script_len : REDEEM_SCRIPT_MAX_SIZE; + int ret = WALLY_OK; + + if (!node->child || !node_is_root(node)) + return WALLY_EINVAL; + + if (!reference_indices) + indices = default_indices; + + for (i = 0; i < target_num; ++i) { + child[i] = (i == 0) ? node->child : child[i - 1]->next; + if (!child[i]) + return WALLY_EINVAL; + } + + for (i = 0; i < target_num; ++i) { + if (i == 0 && prev_insert_num) { + memcpy(script + offset, prev_insert, prev_insert_num); + offset += prev_insert_num; + } + if (i == 1 && first_insert_num) { + memcpy(script + offset, first_insert, first_insert_num); + offset += first_insert_num; + } + if (i == 2 && second_insert_num) { + memcpy(script + offset, second_insert, second_insert_num); + offset += second_insert_num; + } + + output_len = 0; + ret = generate_script(child[indices[i]], child_num, + &script[offset], script_len - offset - 1, &output_len); + if (ret != WALLY_OK) + return ret; + + offset += output_len; + total += output_len; + if (total > check_len) + return WALLY_EINVAL; + } + + if (total + last_append_num > check_len) + return WALLY_EINVAL; + if (last_append_num) { + memcpy(script + offset, last_append, last_append_num); + offset += last_append_num; + } + + if (ret == WALLY_OK) + *written = offset; + + return ret; +} + +static int generate_andor(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const unsigned char first_op[1] = { OP_NOTIF }; + const unsigned char second_op[1] = { OP_ELSE }; + const unsigned char last_op[1] = { OP_ENDIF }; + const size_t indices[3] = { 0, 2, 1 }; + /* [X] NOTIF 0 ELSE [Y] ENDIF */ + return generate_concat(node, child_num, 3, indices, + NULL, 0, + first_op, NUM_ELEMS(first_op), + second_op, NUM_ELEMS(second_op), + last_op, NUM_ELEMS(last_op), + script, script_len, written); +} + +static int generate_and_v(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] [Y] */ + const size_t indices[2] = { 0, 1 }; + return generate_concat(node, child_num, 2, indices, + NULL, 0, + NULL, 0, + NULL, 0, + NULL, 0, + script, script_len, written); +} + +static int generate_and_b(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const unsigned char append[1] = { OP_BOOLAND }; + const size_t indices[2] = { 0, 1 }; + /* [X] [Y] BOOLAND */ + return generate_concat(node, child_num, 2, indices, + NULL, 0, + NULL, 0, + NULL, 0, + append, NUM_ELEMS(append), + script, script_len, written); +} + +static int generate_and_n(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const unsigned char middle_op[3] = { OP_NOTIF, OP_0, OP_ELSE }; + const unsigned char last_op[1] = { OP_ENDIF }; + const size_t indices[2] = { 0, 1 }; + /* [X] NOTIF 0 ELSE [Y] ENDIF */ + return generate_concat(node, child_num, 2, indices, + NULL, 0, + middle_op, NUM_ELEMS(middle_op), + NULL, 0, + last_op, NUM_ELEMS(last_op), + script, script_len, written); +} + +static int generate_or_b(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const unsigned char append[1] = { OP_BOOLOR }; + const size_t indices[2] = { 0, 1 }; + /* [X] [Y] OP_BOOLOR */ + return generate_concat(node, child_num, 2, indices, + NULL, 0, + NULL, 0, + NULL, 0, + append, NUM_ELEMS(append), + script, script_len, written); +} + +static int generate_or_c(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const unsigned char middle_op[1] = { OP_NOTIF }; + const unsigned char last_op[1] = { OP_ENDIF }; + const size_t indices[2] = { 0, 1 }; + /* [X] NOTIF [Z] ENDIF */ + return generate_concat(node, child_num, 2, indices, + NULL, 0, + middle_op, NUM_ELEMS(middle_op), + NULL, 0, + last_op, NUM_ELEMS(last_op), + script, script_len, written); +} + +static int generate_or_d(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const unsigned char middle_op[2] = { OP_IFDUP, OP_NOTIF }; + const unsigned char last_op[1] = { OP_ENDIF }; + const size_t indices[2] = { 0, 1 }; + /* [X] IFDUP NOTIF [Z] ENDIF */ + return generate_concat(node, child_num, 2, indices, + NULL, 0, + middle_op, NUM_ELEMS(middle_op), + NULL, 0, + last_op, NUM_ELEMS(last_op), + script, script_len, written); +} + +static int generate_or_i(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + const unsigned char top_op[1] = { OP_IF }; + const unsigned char middle_op[1] = { OP_ELSE }; + const unsigned char last_op[1] = { OP_ENDIF }; + const size_t indices[2] = { 0, 1 }; + /* IF [X] ELSE [Z] ENDIF */ + return generate_concat(node, child_num, 2, indices, + top_op, NUM_ELEMS(top_op), + middle_op, NUM_ELEMS(middle_op), + NULL, 0, + last_op, NUM_ELEMS(last_op), + script, script_len, written); +} + +static int generate_thresh(ms_node *node, int32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X1] [X2] ADD ... [Xn] ADD EQUAL */ + int ret; + size_t output_len, offset = 0, count = 0; + ms_node *child = node->child; + size_t check_len = script_len <= REDEEM_SCRIPT_MAX_SIZE ? script_len : REDEEM_SCRIPT_MAX_SIZE; + + if (!child || !node_is_root(node)) + return WALLY_EINVAL; + + child = child->next; + while (child) { + output_len = 0; + ret = generate_script(child, child_num, + &script[offset], script_len - offset - 1, &output_len); + if (ret != WALLY_OK) + return ret; + + ++count; + offset += output_len; + if (offset >= check_len) + return WALLY_EINVAL; + + if (count != 1) { + if (offset + 1 >= check_len) + return WALLY_EINVAL; + + script[offset] = OP_ADD; + ++offset; + } + + child = child->next; + } + + ret = generate_script(node->child, child_num, + &script[offset], script_len - offset - 1, &output_len); + if (ret != WALLY_OK) + return ret; + + offset += output_len; + if (offset + 1 >= check_len) + return WALLY_EINVAL; + + script[offset] = OP_EQUAL; + *written = offset + 1; + return WALLY_OK; +} + +static int generate_wrappers(ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + size_t i; + + if (node->wrapper_str[0] == '\0') + return WALLY_OK; /* No wrappers */ + + if (!*written) + return WALLY_EINVAL; /* Nothing to wrap */ + +#define WRAP_REQUIRE(req, move_by) output_len = (req); \ + if (*written + output_len > script_len || *written + output_len > WITNESS_SCRIPT_MAX_SIZE) \ + return WALLY_EINVAL; \ + if (move_by) memmove(script + (move_by), script, *written) + + /* Generate the nodes wrappers in reserve order */ + for (i = strlen(node->wrapper_str); i != 0; --i) { + size_t output_len = 0; + switch(node->wrapper_str[i - 1]) { + case 'a': + WRAP_REQUIRE(2, 1); + script[0] = OP_TOALTSTACK; + script[*written + 1] = OP_FROMALTSTACK; + break; + case 's': + WRAP_REQUIRE(1, 1); + script[0] = OP_SWAP; + break; + case 'c': + WRAP_REQUIRE(1, 0); + script[*written] = OP_CHECKSIG; + break; + case 't': + WRAP_REQUIRE(1, 0); + script[*written] = OP_1; + break; + case 'd': + WRAP_REQUIRE(3, 2); + script[0] = OP_DUP; + script[1] = OP_IF; + script[*written + 2] = OP_ENDIF; + break; + case 'v': { + unsigned char *last = script + *written - 1; + if (*last == OP_EQUAL) + *last = OP_EQUALVERIFY; + else if (*last == OP_NUMEQUAL) + *last = OP_NUMEQUALVERIFY; + else if (*last == OP_CHECKSIG) + *last = OP_CHECKSIGVERIFY; + else if (*last == OP_CHECKMULTISIG) + *last = OP_CHECKMULTISIGVERIFY; + else if (*last == OP_CHECKMULTISIG) + *last = OP_CHECKMULTISIGVERIFY; + else { + WRAP_REQUIRE(1, 0); + script[*written] = OP_VERIFY; + } + break; + } + case 'j': + WRAP_REQUIRE(4, 3); + script[0] = OP_SIZE; + script[1] = OP_0NOTEQUAL; + script[2] = OP_IF; + script[*written + 3] = OP_ENDIF; + break; + case 'n': + WRAP_REQUIRE(1, 0); + script[*written] = OP_0NOTEQUAL; + break; + case 'l': + WRAP_REQUIRE(4, 3); + script[0] = OP_IF; + script[1] = OP_0; + script[2] = OP_ELSE; + script[*written + 3] = OP_ENDIF; + break; + case 'u': + WRAP_REQUIRE(4, 1); + script[0] = OP_IF; + script[*written + 1] = OP_ELSE; + script[*written + 2] = OP_0; + script[*written + 3] = OP_ENDIF; + break; + default: + return WALLY_ERROR; /* Wrapper type not found, should not happen */ + } + *written += output_len; + } + return WALLY_OK; +} + +#define I_NAME(name) name, sizeof(name) - 1 +static const struct ms_builtin_t g_builtins[] = { + /* output descriptor */ + { + I_NAME("sh"), + KIND_DESCRIPTOR_SH, + TYPE_NONE, + 1, verify_sh, generate_sh_wsh + }, { + I_NAME("wsh"), + KIND_DESCRIPTOR_WSH, + TYPE_NONE, + 1, verify_wsh, generate_sh_wsh + }, { /* c:pk_k */ + I_NAME("pk"), + KIND_DESCRIPTOR_PK | KIND_MINISCRIPT_PK, + TYPE_B | PROP_O | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_X, + 1, verify_pk, generate_pk + }, { /* c:pk_h */ + I_NAME("pkh"), + KIND_DESCRIPTOR_PKH | KIND_MINISCRIPT_PKH, + TYPE_B | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_X, + 1, verify_pk, generate_pkh + }, { + I_NAME("wpkh"), + KIND_DESCRIPTOR_WPKH, + TYPE_NONE, + 1, verify_wpkh, generate_wpkh + }, { + I_NAME("combo"), + KIND_DESCRIPTOR_COMBO, + TYPE_NONE, + 1, verify_combo, generate_combo + }, { + I_NAME("multi"), + KIND_DESCRIPTOR_MULTI | KIND_MINISCRIPT_MULTI, + TYPE_B | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S, + 0xffffffff, verify_multi, generate_multi + }, { + I_NAME("sortedmulti"), + KIND_DESCRIPTOR_MULTI_S, + TYPE_NONE, + 0xffffffff, verify_multi, generate_multi + }, { + I_NAME("addr"), + KIND_DESCRIPTOR_ADDR, + TYPE_NONE, + 1, verify_addr, generate_raw + }, { + I_NAME("raw"), + KIND_DESCRIPTOR_RAW, + TYPE_NONE, + 1, verify_raw, generate_raw + }, + /* miniscript */ + { + I_NAME("pk_k"), + KIND_MINISCRIPT_PK_K, + TYPE_K | PROP_O | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_X, + 1, verify_pk, generate_pk_k + }, { + I_NAME("pk_h"), + KIND_MINISCRIPT_PK_H, + TYPE_K | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_X, + 1, verify_pk, generate_pk_h + }, { + I_NAME("older"), + KIND_MINISCRIPT_OLDER, + TYPE_B | PROP_Z | PROP_F | PROP_M | PROP_X, + 1, verify_delay, generate_delay + }, { + I_NAME("after"), + KIND_MINISCRIPT_AFTER, + TYPE_B | PROP_Z | PROP_F | PROP_M | PROP_X, + 1, verify_delay, generate_delay + }, { + I_NAME("sha256"), + KIND_MINISCRIPT_SHA256, + TYPE_B | PROP_O | PROP_N | PROP_D | PROP_U | PROP_M, + 1, verify_hash_type, generate_hash_type + }, { + I_NAME("hash256"), + KIND_MINISCRIPT_HASH256, + TYPE_B | PROP_O | PROP_N | PROP_D | PROP_U | PROP_M, + 1, verify_hash_type, generate_hash_type + }, { + I_NAME("ripemd160"), + KIND_MINISCRIPT_RIPEMD160, + TYPE_B | PROP_O | PROP_N | PROP_D | PROP_U | PROP_M, + 1, verify_hash_type, generate_hash_type + }, { + I_NAME("hash160"), + KIND_MINISCRIPT_HASH160, + TYPE_B | PROP_O | PROP_N | PROP_D | PROP_U | PROP_M, + 1, verify_hash_type, generate_hash_type + }, { + I_NAME("andor"), + KIND_MINISCRIPT_ANDOR, + TYPE_NONE, + 3, verify_andor, generate_andor + }, { + I_NAME("and_v"), + KIND_MINISCRIPT_AND_V, + TYPE_NONE, 2, verify_and_v, generate_and_v + }, { + I_NAME("and_b"), + KIND_MINISCRIPT_AND_B, + TYPE_B | PROP_U, + 2, verify_and_b, generate_and_b + }, { + I_NAME("and_n"), + KIND_MINISCRIPT_AND_N, + TYPE_NONE, + 2, verify_and_n, generate_and_n + }, { + I_NAME("or_b"), + KIND_MINISCRIPT_OR_B, + TYPE_B | PROP_D | PROP_U, + 2, verify_or_b, generate_or_b + }, { + I_NAME("or_c"), + KIND_MINISCRIPT_OR_C, + TYPE_V, + 2, verify_or_c, generate_or_c + }, { + I_NAME("or_d"), + KIND_MINISCRIPT_OR_D, + TYPE_B, + 2, verify_or_d, generate_or_d + }, { + I_NAME("or_i"), + KIND_MINISCRIPT_OR_I, + TYPE_NONE, + 2, verify_or_i, generate_or_i + }, { + I_NAME("thresh"), + KIND_MINISCRIPT_THRESH, TYPE_B | PROP_D | PROP_U, + 0xffffffff, verify_thresh, generate_thresh + } +}; +#undef I_NAME + +static unsigned char builtin_lookup(const char *name, size_t name_len, uint32_t kind) +{ + unsigned char i; + + for (i = 0; i < NUM_ELEMS(g_builtins); ++i) { + if ((g_builtins[i].kind & kind) && + g_builtins[i].name_len == name_len && + !memcmp(g_builtins[i].name, name, name_len)) + return i + 1; + } + return 0; +} + +static const struct ms_builtin_t *builtin_get(const ms_node *node) +{ + return node->builtin ? &g_builtins[node->builtin - 1] : NULL; +} + +static int generate_script(ms_node *node, uint32_t child_num, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret = WALLY_EINVAL; + size_t output_len; + + if (node->builtin) { + output_len = *written; + ret = builtin_get(node)->generate_fn(node, child_num, script, script_len, &output_len); + if (ret == WALLY_OK) { + ret = generate_wrappers(node, script, script_len, &output_len); + if (ret == WALLY_OK) + *written = output_len; + } + return ret; + } + + /* value data */ + if (node->kind & KIND_RAW || node->kind == KIND_PUBLIC_KEY) { + ret = wally_hex_n_to_bytes(node->data, node->data_len, script, script_len, written); + } else if (node->kind == KIND_NUMBER) { + ret = generate_script_from_number(node->number, node->parent, script, script_len, written); + } else if (node->kind == KIND_BASE58 || node->kind == KIND_BECH32) { + ret = analyze_address(node->data, node->data_len, + NULL, NULL, NULL, + script, script_len, written); + } else if (node->kind == KIND_PRIVATE_KEY) { + unsigned char privkey[2 + EC_PRIVATE_KEY_LEN + BASE58_CHECKSUM_LEN]; + unsigned char pubkey[EC_PUBLIC_KEY_LEN]; + if (script_len < EC_PUBLIC_KEY_UNCOMPRESSED_LEN) + return WALLY_EINVAL; + + ret = wally_base58_n_to_bytes(node->data, node->data_len, BASE58_FLAG_CHECKSUM, + privkey, sizeof(privkey), &output_len); + if (ret == WALLY_OK && output_len < EC_PRIVATE_KEY_LEN + 1) + return WALLY_EINVAL; + + ret = wally_ec_public_key_from_private_key(&privkey[1], EC_PRIVATE_KEY_LEN, + pubkey, sizeof(pubkey)); + if (ret == WALLY_OK) { + if (output_len == EC_PRIVATE_KEY_LEN + 2 && privkey[EC_PRIVATE_KEY_LEN + 1] == 1) { + if (node->is_xonly_key) { + memcpy(script, &pubkey[1], EC_XONLY_PUBLIC_KEY_LEN); + *written = EC_XONLY_PUBLIC_KEY_LEN; + } else { + memcpy(script, pubkey, EC_PUBLIC_KEY_LEN); + *written = EC_PUBLIC_KEY_LEN; + } + } else { + ret = wally_ec_public_key_decompress(pubkey, sizeof(pubkey), script, + EC_PUBLIC_KEY_UNCOMPRESSED_LEN); + if (ret == WALLY_OK) + *written = EC_PUBLIC_KEY_UNCOMPRESSED_LEN; + } + } + } else if ((node->kind & KIND_BIP32) == KIND_BIP32) { + struct ext_key master; + + if ((ret = bip32_key_from_base58_n(node->data, node->data_len, &master)) != WALLY_OK) + return ret; + + if (node->child_path_len) { + const uint32_t flags = BIP32_FLAG_STR_WILDCARD | BIP32_FLAG_STR_BARE | \ + BIP32_FLAG_SKIP_HASH | BIP32_FLAG_KEY_PUBLIC; + struct ext_key derived; + + ret = bip32_key_from_parent_path_str_n(&master, node->child_path, node->child_path_len, + child_num, flags, &derived); + if (ret != WALLY_OK) + return ret; + + memcpy(&master, &derived, sizeof(master)); + } + if (node->is_xonly_key) { + memcpy(script, &master.pub_key[1], EC_XONLY_PUBLIC_KEY_LEN); + *written = EC_XONLY_PUBLIC_KEY_LEN; + } else { + memcpy(script, master.pub_key, EC_PUBLIC_KEY_LEN); + *written = EC_PUBLIC_KEY_LEN; + } + } + return ret; +} + +static int analyze_address(const char *str, size_t str_len, + ms_node *node, ms_node *parent, + const struct addr_ver_t *addr_ver, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + unsigned char buf[SHA256_LEN + 2]; + unsigned char decoded[1 + HASH160_LEN + BASE58_CHECKSUM_LEN]; + char *hrp_end; + size_t hrp_len, output_len; + + if (parent && !node) + return WALLY_EINVAL; + + if (script && (script_len < sizeof(buf) || !written)) + return WALLY_EINVAL; + + if (node) { + node->data = str; + node->data_len = str_len; + } + + ret = wally_base58_n_to_bytes(str, str_len, BASE58_FLAG_CHECKSUM, + decoded, sizeof(decoded), &output_len); + if (ret == WALLY_OK) { + /* base58 address: Check for P2PKH/P2SH */ + bool is_p2sh; + + if (output_len != HASH160_LEN + 1) + return WALLY_EINVAL; /* Unexpected address length */ + + if (!addr_ver_from_version(decoded[0], addr_ver, &is_p2sh)) + return WALLY_EINVAL; /* Network not found */ + + if (node) + node->kind = KIND_BASE58; + + if (script) { + /* Create the scriptpubkey */ + ret = (is_p2sh ? wally_scriptpubkey_p2sh_from_bytes : wally_scriptpubkey_p2pkh_from_bytes)( + decoded + 1, HASH160_LEN, 0, script, script_len, written); + } + return ret; + } + + /* segwit */ + hrp_end = memchr(str, '1', str_len); + if (!hrp_end) + return WALLY_EINVAL; /* Address family missing */ + hrp_len = hrp_end - str; + + if (addr_ver && !addr_ver_from_family(str, hrp_len, addr_ver->network)) + return WALLY_EINVAL; /* Unknown network or address family mismatch */ + + ret = wally_addr_segwit_n_to_bytes(str, str_len, str, hrp_len, 0, buf, sizeof(buf), &output_len); + if (ret == WALLY_OK && output_len != HASH160_LEN + 2 && output_len != SHA256_LEN + 2) + return WALLY_EINVAL; + + if (ret == WALLY_OK) { + if (node) + node->kind = KIND_BECH32; + if (script) { + memcpy(script, buf, output_len); + *written = output_len; + } + } + return ret; +} + +static bool analyze_pubkey_hex(const char *str, size_t str_len, uint32_t flags, ms_node *node) +{ + unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN + 1]; + size_t offset = flags & WALLY_MINISCRIPT_TAPSCRIPT ? 1 : 0; + size_t written; + + if (offset) { + if (str_len != EC_XONLY_PUBLIC_KEY_LEN * 2) + return false; /* Only X-only pubkeys allowed under tapscript */ + pubkey[0] = 2; /* Non-X-only pubkey prefix, for validation below */ + } else { + if (str_len != EC_PUBLIC_KEY_LEN * 2 && str_len != EC_PUBLIC_KEY_UNCOMPRESSED_LEN * 2) + return false; /* Unknown public key size */ + } + + if (wally_hex_n_to_bytes(str, str_len, pubkey + offset, sizeof(pubkey) - offset, &written) != WALLY_OK || + wally_ec_public_key_verify(pubkey, written + offset) != WALLY_OK) + return false; + + node->kind = KIND_PUBLIC_KEY; + node->is_uncompressed_key = str_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN * 2; + node->is_xonly_key = str_len == EC_XONLY_PUBLIC_KEY_LEN * 2; + return true; +} + +static int analyze_miniscript_key(const struct addr_ver_t *addr_ver, uint32_t flags, + ms_node *node, ms_node *parent) +{ + int ret; + size_t buf_len, size; + unsigned char privkey[2 + EC_PRIVATE_KEY_LEN + BASE58_CHECKSUM_LEN]; + struct ext_key extkey; + + if (!node || (parent && !parent->builtin)) + return WALLY_EINVAL; + + /* + * key origin identification + * https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md#key-origin-identification + */ + if (node->data[0] == '[') { + const char *end = memchr(node->data, ']', node->data_len); + if (!end || end < node->data + 10 || + wally_hex_n_verify(node->data + 1, 8u) != WALLY_OK || + (node->data[9] != ']' && node->data[9] != '/')) + return WALLY_EINVAL; /* Invalid key origin fingerprint */ + size = end - node->data + 1; + /* cut parent path */ + node->data = end + 1; + node->data_len -= size; + } + + /* check key (public key) */ + if (analyze_pubkey_hex(node->data, node->data_len, flags, node)) + return WALLY_OK; + + /* check key (private key(wif)) */ + ret = wally_base58_n_to_bytes(node->data, node->data_len, BASE58_FLAG_CHECKSUM, + privkey, sizeof(privkey), &buf_len); + if (ret == WALLY_OK && buf_len <= EC_PRIVATE_KEY_LEN + 2) { + if (addr_ver && (addr_ver->version_wif != privkey[0])) + ret = WALLY_EINVAL; + else if (buf_len == EC_PRIVATE_KEY_LEN + 1 || + (buf_len == EC_PRIVATE_KEY_LEN + 2 && privkey[EC_PRIVATE_KEY_LEN + 1] == 0x01)) { + node->kind = KIND_PRIVATE_KEY; + if (buf_len == EC_PRIVATE_KEY_LEN + 1) { + node->is_uncompressed_key = true; + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) + ret = WALLY_EINVAL; + } + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) + node->is_xonly_key = true; + if (ret == WALLY_OK) + ret = wally_ec_private_key_verify(&privkey[1], EC_PRIVATE_KEY_LEN); + } else + ret = WALLY_EINVAL; + wally_clear(privkey, sizeof(privkey)); + return ret; + } + + /* check bip32 key */ + if ((node->child_path = memchr(node->data, '/', node->data_len))) { + node->child_path_len = node->data_len - (node->child_path - node->data); + node->data_len = node->child_path - node->data; /* Trim node data to just the bip32 key */ + if (node->child_path_len) { + if (node->child_path[1] == '/') + return WALLY_EINVAL; /* Double slash, invalid */ + ++node->child_path; /* Skip leading '/' */ + --node->child_path_len; + if (memchr(node->child_path, '*', node->child_path_len)) { + if (node->child_path[node->child_path_len - 1] != '*' && + node->child_path[node->child_path_len - 2] != '*') + return WALLY_EINVAL; /* Wildcard must be the last element */ + } + } + } + + if ((ret = bip32_key_from_base58_n(node->data, node->data_len, &extkey)) != WALLY_OK) + return ret; + + if (extkey.priv_key[0] == BIP32_FLAG_KEY_PRIVATE) + node->kind = KIND_BIP32_PRIVATE_KEY; + else + node->kind = KIND_BIP32_PUBLIC_KEY; + + if (addr_ver) { + const bool main_key = extkey.version == BIP32_VER_MAIN_PUBLIC || + extkey.version == BIP32_VER_MAIN_PRIVATE; + const bool main_net = addr_ver->network == WALLY_NETWORK_BITCOIN_MAINNET || + addr_ver->network == WALLY_NETWORK_LIQUID; + if (main_key != main_net) + ret = WALLY_EINVAL; /* Mismatched main/test network */ + } + + if (ret == WALLY_OK && (flags & WALLY_MINISCRIPT_TAPSCRIPT)) + node->is_xonly_key = true; + wally_clear(&extkey, sizeof(extkey)); + return ret; +} + +static int analyze_miniscript_value(const char *str, size_t str_len, + const struct addr_ver_t *addr_ver, uint32_t flags, + ms_node *node, ms_node *parent) +{ + + if (!node || (parent && !parent->builtin) || !str || !str_len) + return WALLY_EINVAL; + + if (parent && parent->kind == KIND_DESCRIPTOR_ADDR) + return analyze_address(str, str_len, node, parent, addr_ver, NULL, 0, NULL); + + if (!node->data) { + node->data = str; + node->data_len = str_len; + } + + if (parent) { + const uint32_t kind = parent->kind; + if (kind == KIND_DESCRIPTOR_RAW || kind == KIND_MINISCRIPT_SHA256 || + kind == KIND_MINISCRIPT_HASH256 || kind == KIND_MINISCRIPT_RIPEMD160 || + kind == KIND_MINISCRIPT_HASH160) { + node->kind = KIND_RAW; + return wally_hex_n_verify(node->data, node->data_len); + } + } + + if (strtoll_n(node->data, node->data_len, &node->number)) { + node->kind = KIND_NUMBER; + node->type_properties = TYPE_B | PROP_Z | PROP_U | PROP_M | PROP_X; + node->type_properties |= (node->number ? PROP_F : (PROP_D | PROP_E | PROP_S)); + return WALLY_OK; + } + + return analyze_miniscript_key(addr_ver, flags, node, parent); +} + +static int analyze_miniscript(const char *str, size_t str_len, uint32_t kind, + const struct addr_ver_t *addr_ver, uint32_t flags, + ms_node *prev_node, ms_node *parent, ms_node **output) +{ + size_t i, offset = 0, child_offset = 0; + uint32_t indent = 0; + bool seen_indent = false, collect_child = false, copy_child = false; + ms_node *node, *child = NULL, *prev_child = NULL; + int ret = WALLY_OK; + + if (!(node = wally_calloc(sizeof(*node)))) + return WALLY_ENOMEM; + + if (parent) + node->parent = parent; + + for (i = 0; i < str_len; ++i) { + if (!node->builtin && str[i] == ':') { + if (i - offset > sizeof(node->wrapper_str)) { + ret = WALLY_EINVAL; + break; + } + memcpy(node->wrapper_str, &str[offset], i - offset); + offset = i + 1; + } else if (str[i] == '(') { + if (!node->builtin && indent == 0) { + collect_child = true; + node->builtin = builtin_lookup(str + offset, i - offset, kind); + if (!node->builtin || + (node->wrapper_str[0] != '\0' && !(builtin_get(node)->kind & KIND_MINISCRIPT))) { + ret = WALLY_EINVAL; + break; + } + node->kind = builtin_get(node)->kind; + offset = i + 1; + child_offset = offset; + } + ++indent; + seen_indent = true; + } else if (str[i] == ')') { + if (indent) { + --indent; + if (collect_child && (indent == 0)) { + collect_child = false; + offset = i + 1; + copy_child = true; + } + } + seen_indent = true; + } else if (str[i] == ',') { + if (!indent) { + ret = WALLY_EINVAL; /* Comma outside of ()'s */ + break; + } + if (collect_child && (indent == 1)) { + copy_child = true; + } + seen_indent = true; + } else if (str[i] == '#') { + if (!parent && node->builtin && !collect_child && indent == 0) { + break; /* end */ + } + } + + if (copy_child) { + if ((ret = analyze_miniscript(str + child_offset, i - child_offset, kind, + addr_ver, flags, prev_child, node, &child)) != WALLY_OK) + break; + + prev_child = child; + child = NULL; + copy_child = false; + if (str[i] == ',') { + offset = i + 1; + child_offset = offset; + } + } + } + + if (ret == WALLY_OK && !seen_indent) + ret = analyze_miniscript_value(str, str_len, addr_ver, flags, node, parent); + + if (ret == WALLY_OK && node->builtin) { + const uint32_t expected_children = builtin_get(node)->child_count; + if (expected_children != 0xffffffff && node_get_child_count(node) != expected_children) + ret = WALLY_EINVAL; /* Too many or too few children */ + else + ret = builtin_get(node)->verify_fn(node); + } + + if (ret == WALLY_OK) + ret = node_verify_wrappers(node); + + if (ret != WALLY_OK) + node_free(node); + else { + *output = node; + if (parent && !parent->child) + parent->child = node; + if (prev_node) + prev_node->next = node; + } + + return ret; +} + +static int node_generate_script(ms_node *node, + uint32_t child_num, uint32_t depth, uint32_t index, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + unsigned char *buf; + size_t output_len = 0; + ms_node *p = node, *parent; + uint32_t count; + + *written = 0; + + for (count = 0; count < depth; ++count) { + if (!p->child) + return WALLY_EINVAL; + p = p->child; + } + for (count = 0; count < index; ++count) { + if (!p->next) + return WALLY_EINVAL; + p = p->next; + } + + if (!(buf = wally_malloc(DESCRIPTOR_MAX_SIZE))) + return WALLY_ENOMEM; + + parent = p->parent; + p->parent = NULL; + ret = generate_script(p, child_num, buf, DESCRIPTOR_MAX_SIZE, &output_len); + p->parent = parent; + + if (ret == WALLY_OK) { + *written = output_len; + if (output_len > script_len) { + /* return WALLY_OK, but data is not written. */ + } else { + memcpy(script, buf, output_len); + } + } + + clear_and_free(buf, DESCRIPTOR_MAX_SIZE); + return ret; +} + +/* Parse miniscript/output descriptor into script(s) or address(es). + * Called with: + * - addresses == NULL: Generate a single script + * - addresses != NULL: Generate a range of scripts and then their addresses + */ +static int parse_miniscript(const char *str, size_t str_len, + uint32_t flags, uint32_t kind, + const struct addr_ver_t *addr_ver, + uint32_t descriptor_depth, uint32_t descriptor_index, + struct ms_context *ctx, char **addresses) +{ + int ret; + size_t i; + ms_node *top_node = NULL; + + if (!str || !str_len || flags & ~WALLY_MINISCRIPT_TAPSCRIPT || (addresses && !addr_ver)) + return WALLY_EINVAL; + + if (ctx->child_num >= BIP32_INITIAL_HARDENED_CHILD || + (uint64_t)ctx->child_num + ctx->num_derivations >= BIP32_INITIAL_HARDENED_CHILD) + return WALLY_EINVAL; /* Don't allow private derivation via child_num */ + + ret = analyze_miniscript(str, str_len, kind, addr_ver, flags, NULL, NULL, &top_node); + if (ret == WALLY_OK && (kind & KIND_DESCRIPTOR) && + (!top_node->builtin || !(top_node->kind & KIND_DESCRIPTOR))) + ret = WALLY_EINVAL; + + for (i = 0; ret == WALLY_OK && i < ctx->num_derivations; ++i) { + size_t written = 0; + ret = node_generate_script(top_node, ctx->child_num + i, + descriptor_depth, descriptor_index, + ctx->script, ctx->script_len, + &written); + if (ret == WALLY_OK && !addresses) { + ctx[i].script_len = written; /* Tell the caller how much was written/needed */ + } else if (ret == WALLY_OK) { + /* Generate the address corresponding to this script */ + ret = wally_scriptpubkey_to_address(ctx->script, written, + addr_ver->network, &addresses[i]); + if (ret == WALLY_EINVAL) + ret = wally_addr_segwit_from_bytes(ctx->script, written, + addr_ver->family, 0, &addresses[i]); + } + } + + if (addresses && ret != WALLY_OK) { + for (i = 0; i < ctx->num_derivations; ++i) { + wally_free_string(addresses[i]); + addresses[i] = NULL; + } + } + + node_free(top_node); + return ret; +} + +int wally_miniscript_to_script(const char *miniscript, const struct wally_map *vars_in, + uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + struct ms_context ctx = { bytes_out, len, child_num, 1 }; + char *str; + int ret; + + if (written) + *written = 0; + + if (!miniscript || !bytes_out || !len || !written) + return WALLY_EINVAL; + + if ((ret = wally_descriptor_canonicalize(miniscript, vars_in, 0, &str)) == WALLY_OK) + ret = parse_miniscript(str, strlen(str), flags, KIND_MINISCRIPT, NULL, 0, 0, &ctx, NULL); + if (ret == WALLY_OK) + *written = ctx.script_len; + + wally_free_string(str); + return ret; +} + +int wally_descriptor_to_scriptpubkey(const char *descriptor, const struct wally_map *vars_in, + uint32_t child_num, uint32_t network, + uint32_t depth, uint32_t index, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + const struct addr_ver_t *addr_ver = addr_ver_from_network(network); + struct ms_context ctx = { bytes_out, len, child_num, 1 }; + char *str; + int ret; + + if (written) + *written = 0; + + if (!descriptor || (network && !addr_ver) || !bytes_out || !len || !written) + return WALLY_EINVAL; + + if ((ret = wally_descriptor_canonicalize(descriptor, vars_in, 0, &str)) == WALLY_OK) + ret = parse_miniscript(str, strlen(str), flags, KIND_MINISCRIPT | KIND_DESCRIPTOR, + addr_ver, depth, index, &ctx, NULL); + if (ret == WALLY_OK) + *written = ctx.script_len; + + wally_free_string(str); + return ret; +} + +int wally_descriptor_to_addresses(const char *descriptor, const struct wally_map *vars_in, + uint32_t child_num, uint32_t network, uint32_t flags, + char **addresses, size_t num_addresses) +{ + char *str; + const struct addr_ver_t *addr_ver = addr_ver_from_network(network); + struct ms_context ctx = { NULL, DESCRIPTOR_MAX_SIZE, child_num, num_addresses }; + int ret; + + if (addresses && num_addresses) + wally_clear(addresses, num_addresses * sizeof(*addresses)); + + if (!descriptor || !addr_ver || !addresses || !num_addresses) + return WALLY_EINVAL; + + if ((ret = wally_descriptor_canonicalize(descriptor, vars_in, 0, &str)) == WALLY_OK) { + if (!(ctx.script = wally_malloc(DESCRIPTOR_MAX_SIZE))) { + wally_free_string(str); + return WALLY_ENOMEM; + } + ret = parse_miniscript(str, strlen(str), flags, KIND_MINISCRIPT | KIND_DESCRIPTOR, + addr_ver, 0, 0, &ctx, addresses); + } + + clear_and_free(ctx.script, DESCRIPTOR_MAX_SIZE); + wally_free_string(str); + return ret; +} + +int wally_descriptor_to_address(const char *descriptor, const struct wally_map *vars_in, + uint32_t child_num, uint32_t network, uint32_t flags, + char **output) +{ + return wally_descriptor_to_addresses(descriptor, vars_in, child_num, network, flags, + output, 1); +} + +int wally_descriptor_get_checksum(const char *descriptor, + const struct wally_map *vars_in, uint32_t flags, + char **output) +{ + char *str; + int ret; + + if (output) + *output = NULL; + + if (!descriptor || flags || !output) + return WALLY_EINVAL; + + if (((ret = wally_descriptor_canonicalize(descriptor, vars_in, 0, &str)) == WALLY_OK) && + !(*output = wally_strdup(str + strlen(str) - DESCRIPTOR_CHECKSUM_LENGTH))) + ret = WALLY_ENOMEM; + + wally_free_string(str); + return ret; +} + +/* + * Checksum code adapted from bitcoin core: bitcoin/src/script/descriptor.cpp DescriptorChecksum() + */ +/* The character set for the checksum itself (same as bech32). */ +static const char *checksum_charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +static const unsigned char checksum_positions[] = { + 0x5f, 0x3c, 0x5d, 0x5c, 0x1d, 0x1e, 0x33, 0x10, 0x0b, 0x0c, 0x12, 0x34, 0x0f, 0x35, 0x36, 0x11, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x1c, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x1b, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x0d, 0x5e, 0x0e, 0x3d, 0x3e, + 0x5b, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x1f, 0x3f, 0x20, 0x40 +}; + +static inline size_t checksum_get_position(char c) +{ + return c < ' ' || c > '~' ? 0 : checksum_positions[(unsigned char)(c - ' ')]; +} + +static uint64_t poly_mod_descriptor_checksum(uint64_t c, int val) +{ + uint8_t c0 = c >> 35; + c = ((c & 0x7ffffffff) << 5) ^ val; + if (c0 & 1) c ^= 0xf5dee51989; + if (c0 & 2) c ^= 0xa9fdca3312; + if (c0 & 4) c ^= 0x1bab10e32d; + if (c0 & 8) c ^= 0x3706b1677a; + if (c0 & 16) c ^= 0x644d626ffd; + return c; +} + +static int generate_checksum(const char *str, size_t str_len, char *checksum_out) +{ + uint64_t c = 1; + int cls = 0; + int clscount = 0; + size_t pos; + size_t i; + + for (i = 0; i < str_len; ++i) { + if ((pos = checksum_get_position(str[i])) == 0) + return WALLY_EINVAL; /* Invalid character */ + --pos; + /* Emit a symbol for the position inside the group, for every character. */ + c = poly_mod_descriptor_checksum(c, pos & 31); + /* Accumulate the group numbers */ + cls = cls * 3 + (int)(pos >> 5); + if (++clscount == 3) { + /* Emit an extra symbol representing the group numbers, for every 3 characters. */ + c = poly_mod_descriptor_checksum(c, cls); + cls = 0; + clscount = 0; + } + } + if (clscount > 0) + c = poly_mod_descriptor_checksum(c, cls); + for (i = 0; i < DESCRIPTOR_CHECKSUM_LENGTH; ++i) + c = poly_mod_descriptor_checksum(c, 0); + c ^= 1; + + for (i = 0; i < DESCRIPTOR_CHECKSUM_LENGTH; ++i) + checksum_out[i] = checksum_charset[(c >> (5 * (7 - i))) & 31]; + checksum_out[DESCRIPTOR_CHECKSUM_LENGTH] = '\0'; + + return WALLY_OK; +} + +static inline bool is_identifer_char(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +} + +static const struct wally_map_item *lookup_identifier(const struct wally_map *map_in, + const char *key, size_t key_len) +{ + size_t i; + for (i = 0; i < map_in->num_items; ++i) { + const struct wally_map_item *item = &map_in->items[i]; + if (key_len == item->key_len - 1 && memcmp(key, item->key, key_len) == 0) + return item; + } + return NULL; +} + +int wally_descriptor_canonicalize(const char *descriptor, + const struct wally_map *vars_in, uint32_t flags, + char **output) +{ + const size_t VAR_MAX_NAME_LEN = 16; + size_t required_len = 0; + const char *p = descriptor, *start; + char *out; + + if (output) + *output = NULL; + + if (!descriptor || flags || !output) + return WALLY_EINVAL; + + /* First, find the length of the canonicalized descriptor */ + while (*p && *p != '#') { + while (*p && *p != '#' && !is_identifer_char(*p)) { + ++required_len; + ++p; + } + start = p; + while (is_identifer_char(*p)) + ++p; + if (p != start) { + const bool starts_with_digit = *start >= '0' && *start <= '9'; + const size_t lookup_len = p - start; + if (!vars_in || lookup_len > VAR_MAX_NAME_LEN || starts_with_digit) { + required_len += lookup_len; /* Too long/wrong format for an identifier */ + } else { + /* Lookup the potential identifier */ + const struct wally_map_item *item = lookup_identifier(vars_in, start, lookup_len); + required_len += item ? item->value_len - 1 : lookup_len; + } + } + } + + if (!(*output = wally_malloc(required_len + 1 + DESCRIPTOR_CHECKSUM_LENGTH + 1))) + return WALLY_ENOMEM; + + p = descriptor; + out = *output; + while (*p && *p != '#') { + while (*p && *p != '#' && !is_identifer_char(*p)) { + *out++ = *p++; + } + start = p; + while (is_identifer_char(*p)) + ++p; + if (p != start) { + const bool is_number = *start >= '0' && *start <= '9'; + size_t lookup_len = p - start; + if (!vars_in || lookup_len > VAR_MAX_NAME_LEN || is_number) { + memcpy(out, start, lookup_len); + } else { + /* Lookup the potential identifier */ + const struct wally_map_item *item = lookup_identifier(vars_in, start, lookup_len); + lookup_len = item ? item->value_len - 1 : lookup_len; + memcpy(out, item ? (char *)item->value : start, lookup_len); + } + out += lookup_len; + } + } + *out++ = '#'; + out[DESCRIPTOR_CHECKSUM_LENGTH] = '\0'; + if (generate_checksum(*output, required_len, out) != WALLY_OK || + (*p == '#' && strcmp(p + 1, out))) { + /* Invalid character in input or failed to match passed in checksum */ + clear_and_free(*output, required_len + 1 + DESCRIPTOR_CHECKSUM_LENGTH + 1); + *output = NULL; + return WALLY_EINVAL; + } + return WALLY_OK; +} diff --git a/src/script.c b/src/script.c index 86ec745d0..1dea5da59 100644 --- a/src/script.c +++ b/src/script.c @@ -154,7 +154,7 @@ size_t varint_length_from_bytes(const unsigned char *bytes) /* Get the length of a script integer in bytes. signed_v should not be * larger than int32_t (i.e. +/- 31 bits) */ -static size_t scriptint_get_length(int64_t signed_v) +size_t scriptint_get_length(int64_t signed_v) { uint64_t v = signed_v < 0 ? -signed_v : signed_v; size_t len = 0; @@ -168,7 +168,7 @@ static size_t scriptint_get_length(int64_t signed_v) return len + (last & 0x80 ? 1 : 0); } -static size_t scriptint_to_bytes(int64_t signed_v, unsigned char *bytes_out) +size_t scriptint_to_bytes(int64_t signed_v, unsigned char *bytes_out) { uint64_t v = signed_v < 0 ? -signed_v : signed_v; size_t len = 0; diff --git a/src/script_int.h b/src/script_int.h index 86a3641f4..d90576c72 100644 --- a/src/script_int.h +++ b/src/script_int.h @@ -83,6 +83,10 @@ size_t varint_to_bytes(uint64_t v, unsigned char *bytes_out); /* Read a variant from bytes */ size_t varint_from_bytes(const unsigned char *bytes, uint64_t *v); +size_t scriptint_get_length(int64_t signed_v); + +size_t scriptint_to_bytes(int64_t signed_v, unsigned char *bytes_out); + size_t varint_length_from_bytes(const unsigned char *bytes); size_t confidential_asset_length_from_bytes(const unsigned char *bytes); diff --git a/src/swig_java/src/com/blockstream/test/test_descriptor.java b/src/swig_java/src/com/blockstream/test/test_descriptor.java new file mode 100644 index 000000000..b01e2bf76 --- /dev/null +++ b/src/swig_java/src/com/blockstream/test/test_descriptor.java @@ -0,0 +1,41 @@ +package com.blockstream.test; + +import com.blockstream.libwally.Wally; + +public class test_descriptor { + + static final String descriptor = "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))#t2zpj2eu"; + final String expected_addrs[] = { "bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c", + "bc1qp6rfclasvmwys7w7j4svgc2mrujq9m73s5shpw4e799hwkdcqlcsj464fw", + "bc1qsflxzyj2f2evshspl9n5n745swcvs5k7p5t8qdww5unxpjwdvw5qx53ms4" }; + + public test_descriptor() { } + + public static void assert_eq(final Object expected, final Object actual, final String message) { + if(!expected.equals(actual)) { + System.out.println(expected); + System.out.println(actual); + throw new RuntimeException(message); + } + } + + public void test_descriptor_to_addresses() { + final int child_num = 0; + final int flags = 0; + final String[] addrs = Wally.descriptor_to_addresses(descriptor, Wally.map_init(0), child_num, + Wally.WALLY_NETWORK_BITCOIN_MAINNET, flags, + expected_addrs.length); + + assert_eq(expected_addrs.length, addrs.length, "Addresses size mismatch"); + for (int i = 0; i < expected_addrs.length; i++) { + assert_eq(expected_addrs[i], addrs[i], "Addresses mismatch"); + } + + Wally.cleanup(); + } + + public static void main(final String[] args) { + final test_descriptor t = new test_descriptor(); + t.test_descriptor_to_addresses(); + } +} diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index c57f09340..457dae099 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -8,6 +8,7 @@ #include "../include/wally_bip38.h" #include "../include/wally_bip39.h" #include "../include/wally_crypto.h" +#include "../include/wally_descriptor.h" #include "../include/wally_map.h" #include "../include/wally_psbt.h" #include "../include/wally_psbt_members.h" @@ -50,6 +51,12 @@ static uint32_t uint32_cast(JNIEnv *jenv, jlong value) { return (uint32_t)value; } +static size_t size_t_cast(JNIEnv *jenv, jlong value) { + if (value < 0) + SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "Invalid size_t"); + return (size_t)value; +} + /* Use a static class to hold our opaque pointers */ #define OBJ_CLASS "com/blockstream/libwally/Wally$Obj" @@ -98,13 +105,26 @@ static unsigned char* malloc_or_throw(JNIEnv *jenv, size_t len) { return p; } -static jbyteArray create_array(JNIEnv *jenv, const unsigned char* p, size_t len) { +static jbyteArray create_array(JNIEnv *jenv, const unsigned char *p, size_t len) { jbyteArray ret = (*jenv)->NewByteArray(jenv, len); if (ret) (*jenv)->SetByteArrayRegion(jenv, ret, 0, len, (const jbyte*)p); return ret; } +static jobjectArray create_string_array(JNIEnv *jenv, char **p, size_t len) { + size_t i; + jclass clazz = (*jenv)->FindClass(jenv, "java/lang/String"); + jobjectArray ret = (*jenv)->NewObjectArray(jenv, len, clazz, NULL); + if (ret) { + for (i = 0; i < len && !(*jenv)->ExceptionOccurred(jenv); ++i) { + jstring s = (*jenv)->NewStringUTF(jenv, p[i]); + (*jenv)->SetObjectArrayElement(jenv, ret, i, s); + } + } + return ret; +} + #define member_size(struct_, member) sizeof(((struct struct_ *)0)->member) %} @@ -174,6 +194,23 @@ static jbyteArray create_array(JNIEnv *jenv, const unsigned char* p, size_t len) } } +/* Output string arrays are converted to native Java string arrays and returned */ +%typemap(in) (char** output, size_t num_outputs) { + $2 = size_t_cast(jenv, $input); + if (!(*jenv)->ExceptionOccurred(jenv)) { + $1 = (void *) wally_malloc($2 * sizeof(char*)); + } +} +%typemap(argout) (char** output, size_t num_outputs) { + if ($1 != NULL) { + size_t i; + $result = create_string_array(jenv, $1, $2); + for (i = 0; i < $2; ++i) + wally_free_string($1[i]); + wally_free($1); + } +} + /* uint32_t input arguments are taken as longs and cast with range checking */ %typemap(in) uint32_t { $1 = uint32_cast(jenv, $input); @@ -341,6 +378,9 @@ static jbyteArray create_array(JNIEnv *jenv, const unsigned char* p, size_t len) %define %returns_string(FUNC) %return_decls(FUNC, String, jstring) %enddef +%define %returns_sarray(FUNC) +%return_decls(FUNC, String[], jobject) +%enddef %define %returns_struct(FUNC, STRUCT) %return_decls(FUNC, Object, jobject) %enddef @@ -464,6 +504,12 @@ static jbyteArray create_array(JNIEnv *jenv, const unsigned char* p, size_t len) %returns_string(wally_confidential_addr_to_addr_segwit); %returns_array_(wally_confidential_addr_segwit_to_ec_public_key, 3, 4, EC_PUBLIC_KEY_LEN); %returns_string(wally_confidential_addr_from_addr_segwit); +%returns_string(wally_descriptor_canonicalize); +%returns_string(wally_descriptor_get_checksum); +%returns_size_t(wally_miniscript_to_script); +%returns_string(wally_descriptor_to_address); +%returns_sarray(wally_descriptor_to_addresses); +%returns_size_t(wally_descriptor_to_scriptpubkey); %returns_void__(wally_ec_private_key_verify); %returns_void__(wally_ec_public_key_verify); %returns_array_(wally_ec_public_key_decompress, 3, 4, EC_PUBLIC_KEY_UNCOMPRESSED_LEN); @@ -1029,6 +1075,7 @@ static jbyteArray create_array(JNIEnv *jenv, const unsigned char* p, size_t len) %include "../include/wally_bip38.h" %include "../include/wally_bip39.h" %include "../include/wally_crypto.h" +%include "../include/wally_descriptor.h" %include "../include/wally_map.h" %include "../include/wally_psbt.h" %include "../include/wally_psbt_members.h" diff --git a/src/swig_python/contrib/descriptor.py b/src/swig_python/contrib/descriptor.py new file mode 100644 index 000000000..b14f58be5 --- /dev/null +++ b/src/swig_python/contrib/descriptor.py @@ -0,0 +1,23 @@ +"""Tests for output descriptors""" +import unittest +from wallycore import * + +class DescriptorTests(unittest.TestCase): + + def test_descriptor_to_addresses(self): + """Test the SWIG string array mapping works for descriptor_to_addresses""" + descriptor = "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))#t2zpj2eu" + child_num = 0 + flags = 0 + expected = [ + 'bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c', + 'bc1qp6rfclasvmwys7w7j4svgc2mrujq9m73s5shpw4e799hwkdcqlcsj464fw', + 'bc1qsflxzyj2f2evshspl9n5n745swcvs5k7p5t8qdww5unxpjwdvw5qx53ms4' + ] + addrs = descriptor_to_addresses(descriptor, None, child_num, WALLY_NETWORK_BITCOIN_MAINNET, flags, + len(expected)) + self.assertEqual(addrs, expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/swig_python/swig.i b/src/swig_python/swig.i index b7ca02c4c..81089633d 100644 --- a/src/swig_python/swig.i +++ b/src/swig_python/swig.i @@ -29,6 +29,7 @@ del swig_import_helper #include "../include/wally_bip38.h" #include "../include/wally_bip39.h" #include "../include/wally_crypto.h" +#include "../include/wally_descriptor.h" #include "../include/wally_map.h" #include "../include/wally_psbt.h" #include "../include/wally_psbt_members.h" @@ -62,17 +63,19 @@ static int check_result(int result) return result; } -static bool ulonglong_cast(PyObject *item, unsigned long long *val) +static bool size_t_cast(PyObject *item, size_t *val) { -#if PY_MAJOR_VERSION < 3 - if (PyInt_Check(item)) { - *val = PyInt_AsUnsignedLongLongMask(item); + if (PyLong_Check(item)) { + *val = PyLong_AsSize_t(item); if (!PyErr_Occurred()) return true; PyErr_Clear(); - return false; } -#endif + return false; +} + +static bool ulonglong_cast(PyObject *item, unsigned long long *val) +{ if (PyLong_Check(item)) { *val = PyLong_AsUnsignedLongLong(item); if (!PyErr_Occurred()) @@ -208,6 +211,28 @@ static void destroy_words(PyObject *obj) { (void)obj; } } } +/* Output string arrays are converted to a native python string list and returned */ +%typemap(in) (char** output, size_t num_outputs) { + if (!size_t_cast($input, &$2)) { + PyErr_SetString(PyExc_OverflowError, "Invalid output size"); + SWIG_fail; + } + $1 = (void *) wally_malloc($2 * sizeof(char*)); +} +%typemap(argout) (char** output, size_t num_outputs) { + if ($1 != NULL) { + size_t i; + Py_DecRef($result); + $result = PyList_New($2); + for (i = 0; i < $2; i++) { + PyObject *s = PyString_FromString($1[i]); + PyList_SetItem($result, i, s); + wally_free_string($1[i]); + } + wally_free($1); + } +} + /* Opaque types are passed along as capsules */ %define %py_opaque_struct(NAME) %typemap(in, numinputs=0) struct NAME **output (struct NAME * w) { @@ -383,10 +408,11 @@ static void destroy_words(PyObject *obj) { (void)obj; } %include "../include/wally_bip38.h" %include "../include/wally_bip39.h" %include "../include/wally_crypto.h" -%include "../include/wally_script.h" +%include "../include/wally_descriptor.h" %include "../include/wally_map.h" %include "../include/wally_psbt.h" %include "../include/wally_psbt_members.h" +%include "../include/wally_script.h" %include "../include/wally_symmetric.h" %include "../include/wally_transaction.h" %include "transaction_int.h" diff --git a/src/test/test_descriptor.py b/src/test/test_descriptor.py new file mode 100644 index 000000000..0a68114e5 --- /dev/null +++ b/src/test/test_descriptor.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +import unittest +from util import * + + +NETWORK_BTC_MAIN = 0x01 +NETWORK_BTC_TEST = 0x02 +NETWORK_BTC_REG = 0xff +NETWORK_LIQUID = 0x03 +NETWORK_LIQUID_REG = 0x04 + + +def wally_map_from_dict(d): + m = pointer(wally_map()) + assert(wally_map_init_alloc(len(d.keys()), None, m) == WALLY_OK) + for k,v in d.items(): + assert(wally_map_add(m, k, len(k) + 1, v, len(v) + 1) == WALLY_OK) + return m + + +class DescriptorTests(unittest.TestCase): + + def test_parse_miniscript(self): + keys = wally_map_from_dict({ + utf8('key_local'): utf8('038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048'), + utf8('key_remote'): utf8('03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7'), + utf8('key_revocation'): utf8('03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284'), + utf8('H'): utf8('d0721279e70d39fb4aa409b52839a0056454e3b5'), # HASH160(key_local) + }) + script, script_len = make_cbuffer('00' * 256 * 2) + + # Valid args + args = [ + ('t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))', 0, + '532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851'), + ('andor(c:pk_k(key_remote),or_i(and_v(vc:pk_h(key_local),hash160(H)),older(1008)),c:pk_k(key_revocation))', 0, + '2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac676376a914d0721279e70d39fb4aa409b52839a0056454e3b588ad82012088a914d0721279e70d39fb4aa409b52839a0056454e3b5876702f003b26868'), + ] + for miniscript, child_num, expected in args: + ret, written = wally_miniscript_to_script(miniscript, keys, child_num, + 0, script, script_len) + self.assertEqual(ret, WALLY_OK) + self.assertEqual(written, len(expected) / 2) + self.assertEqual(script[:written], make_cbuffer(expected)[0]) + wally_map_free(keys) + + # Invalid args + bad_args = [ + (None, 0, 0, script, script_len), # NULL miniscript + (args[0][0], 0x80000000, 0, script, script_len), # Hardened child + (args[0][0], 0, 0, None, script_len), # NULL output + (args[0][0], 0, 0, script, 0), # Empty output + ] + for miniscript, child_num, flags, bytes_out, bytes_len in bad_args: + ret, written = wally_miniscript_to_script(miniscript, None, child_num, + flags, bytes_out, bytes_len) + self.assertEqual((ret, written), (WALLY_EINVAL, 0)) + + def test_descriptor_to_scriptpubkey(self): + script, script_len = make_cbuffer('00' * 64 * 2) + + # Valid args + args = [ + ('wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)', + 0, NETWORK_BTC_MAIN, '00147dd65592d0ab2fe0d0257d571abf032cd9db93dc'), + ] + for descriptor, child_num, network, expected in args: + ret, written = wally_descriptor_to_scriptpubkey(descriptor, None, child_num, + network, 0, 0, 0, script, script_len) + self.assertEqual((ret, written), (WALLY_OK, len(expected) // 2)) + self.assertEqual(script[:written], make_cbuffer(expected)[0]) + + # Invalid args + M, U = NETWORK_BTC_MAIN, 0x33 # Unknown network + bad_args = [ + (None, 0, M, 0, 0, 0, script, script_len), # NULL miniscript + (args[0][0], 0x80000000, M, 0, 0, 0, script, script_len), # Hardened child + (args[0][0], 0, U, 0, 0, 0, script, script_len), # Unknown network + (args[0][0], 0, M, 2, 0, 0, script, script_len), # Invalid depth + (args[0][0], 0, M, 1, 1, 0, script, script_len), # Invalid child index + (args[0][0], 0, M, 1, 0, 1, script, script_len), # Invalid flags + (args[0][0], 0, M, 0, 0, 0, None, script_len), # NULL output + (args[0][0], 0, M, 0, 0, 0, script, 0), # Empty output + ] + for descriptor, child_num, network, depth, idx, flags, bytes_out, bytes_len in bad_args: + ret, written = wally_descriptor_to_scriptpubkey(descriptor, None, child_num, + network, depth, idx, flags, + bytes_out, bytes_len) + self.assertEqual((ret, written), (WALLY_EINVAL, 0)) + + def test_descriptor_to_address(self): + # Valid args + args = [ + ('wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)', 0, NETWORK_BTC_TEST, 'tb1q0ht9tyks4vh7p5p904t340cr9nvahy7um9zdem'), + ] + for descriptor, child_num, network, expected in args: + ret, addr = wally_descriptor_to_address(descriptor, None, child_num, network, 0) + self.assertEqual((ret, addr), (WALLY_OK, expected)) + + def test_descriptor_to_addresses(self): + addrs_len = 64 + addrs = (c_char_p * addrs_len)() + + # Valid args + args = [ + ('wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))', + 0, NETWORK_BTC_MAIN, [ + 'bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c', + 'bc1qp6rfclasvmwys7w7j4svgc2mrujq9m73s5shpw4e799hwkdcqlcsj464fw', + 'bc1qsflxzyj2f2evshspl9n5n745swcvs5k7p5t8qdww5unxpjwdvw5qx53ms4', + 'bc1qmhmj2mswyvyj4az32mzujccvd4dgr8s0lfzaum4n4uazeqc7xxvsr7e28n', + 'bc1qjeu2wa5jwvs90tv9t9xz99njnv3we3ux04fn7glw3vqsk4ewuaaq9kdc9t', + ]), + ] + for descriptor, child_num, network, expected in args: + ret = wally_descriptor_to_addresses(descriptor, None, child_num, network, + 0, addrs, len(expected)) + self.assertEqual(ret, WALLY_OK) + for i in range(len(expected)): + self.assertEqual(expected[i].encode('utf-8'), addrs[i]) + + # Invalid args + M, U = NETWORK_BTC_MAIN, 0x33 # Unknown network + bad_args = [ + (None, 0, M, 0, addrs, addrs_len), # NULL miniscript + (args[0][0], 0x80000000, M, 0, addrs, addrs_len), # Hardened child + (args[0][0], 0, U, 0, addrs, addrs_len), # Unknown network + (args[0][0], 0, M, 2, addrs, addrs_len), # Invalid flags + (args[0][0], 0, M, 0, None, addrs_len), # NULL output + (args[0][0], 0, M, 0, addrs, 0), # Empty output + ] + for descriptor, child_num, network, flags, out, out_len in bad_args: + ret = wally_descriptor_to_addresses(descriptor, None, child_num, network, + flags, out, out_len) + self.assertEqual(ret, WALLY_EINVAL) + + def test_create_descriptor_checksum(self): + # Valid args + for descriptor, expected in [ + ('wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))', 't2zpj2eu'), + ]: + ret, checksum = wally_descriptor_get_checksum(descriptor, None, 0) + self.assertEqual((ret, checksum), (WALLY_OK, expected)) + + def test_canonicalize_checksum_bad_args(self): + """Test bad arguments to canonicalize and checksum functions""" + descriptor = 'sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))' + bad_args = [ + (None, 0), # NULL descriptor + (descriptor, 1), # Bad flags + ] + + for fn in (wally_descriptor_canonicalize, wally_descriptor_get_checksum): + for descriptor, flags in bad_args: + ret, out = fn(descriptor, None, flags) + self.assertEqual((ret, out), (WALLY_EINVAL, None)) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/util.py b/src/test/util.py index 98f86fa78..bd010d865 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -291,6 +291,11 @@ class wally_psbt(Structure): ('wally_confidential_addr_to_addr', c_int, [c_char_p, c_uint32, c_char_p_p]), ('wally_confidential_addr_to_addr_segwit', c_int, [c_char_p, c_char_p, c_char_p, c_char_p_p]), ('wally_confidential_addr_to_ec_public_key', c_int, [c_char_p, c_uint32, c_void_p, c_size_t]), + ('wally_descriptor_canonicalize', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_char_p_p]), + ('wally_descriptor_get_checksum', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_char_p_p]), + ('wally_descriptor_to_address', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_uint32, c_uint32, c_char_p_p]), + ('wally_descriptor_to_addresses', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_uint32, c_uint32, POINTER(c_char_p), c_size_t]), + ('wally_descriptor_to_scriptpubkey', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, c_size_t, c_size_t_p]), ('wally_ec_private_key_verify', c_int, [c_void_p, c_size_t]), ('wally_ec_public_key_decompress', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_ec_public_key_from_private_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), @@ -361,6 +366,7 @@ class wally_psbt(Structure): ('wally_map_replace', c_int, [POINTER(wally_map), c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_map_replace_integer', c_int, [POINTER(wally_map), c_uint32, c_void_p, c_size_t]), ('wally_map_sort', c_int, [POINTER(wally_map), c_uint32]), + ('wally_miniscript_to_script', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_uint32, c_void_p, c_size_t, c_size_t_p]), ('wally_pbkdf2_hmac_sha256', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_uint32, c_void_p, c_size_t]), ('wally_pbkdf2_hmac_sha512', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_uint32, c_void_p, c_size_t]), ('wally_psbt_add_global_scalar', c_int, [POINTER(wally_psbt), c_void_p, c_size_t]), diff --git a/src/wrap_js/src/combined.c b/src/wrap_js/src/combined.c index 6a7d90b88..b5877f8c1 100644 --- a/src/wrap_js/src/combined.c +++ b/src/wrap_js/src/combined.c @@ -10,6 +10,7 @@ #include "bip32.c" #include "bip38.c" #include "bip39.c" +#include "descriptor.c" #include "ecdh.c" #include "elements.c" #include "hex.c" diff --git a/tools/build_wrappers.py b/tools/build_wrappers.py index b1cd2cc5f..cb4bcb439 100755 --- a/tools/build_wrappers.py +++ b/tools/build_wrappers.py @@ -92,6 +92,8 @@ def map_arg(arg, n, num_args): argtype = arg.type[6:] if arg.is_const else arg.type # Strip const if argtype == u'uint64_t*' and n != num_args - 1: return u'POINTER(c_uint64)' + if argtype == u'char**' and n != num_args - 1: + return u'POINTER(c_char_p)' if argtype in typemap: return typemap[argtype] if arg.is_struct: diff --git a/tools/cleanup.sh b/tools/cleanup.sh index a2f6fc9fb..81e2f6e29 100755 --- a/tools/cleanup.sh +++ b/tools/cleanup.sh @@ -23,9 +23,7 @@ rm -f src/*pyc rm -f src/test/*pyc rm -f src/config.h.in rm -rf src/lcov* -rm -f src/test_bech32* -rm -f src/test_clear* -rm -f src/test_tx* +rm -f src/test_* rm -f src/test-suite.log rm -f src/swig_java/swig_java_wrap.c rm -f src/swig_java/*java diff --git a/tools/msvc/build.bat b/tools/msvc/build.bat index cdd828592..0ad9b7b31 100644 --- a/tools/msvc/build.bat +++ b/tools/msvc/build.bat @@ -21,4 +21,4 @@ if "%ELEMENTS_BUILD%" == "elements" ( REM Compile everything (wally, ccan, libsecp256k) in one lump. REM Define USE_ECMULT_STATIC_PRECOMPUTATION to pick up the REM ecmult_static_context.h file generated previously -cl /utf-8 /DUSE_ECMULT_STATIC_PRECOMPUTATION /DECMULT_WINDOW_SIZE=15 /DWALLY_CORE_BUILD %ELEMENTS_OPT% /DHAVE_CONFIG_H /DSECP256K1_BUILD /I%LIBWALLY_DIR%\src\wrap_js\windows_config /I%LIBWALLY_DIR% /I%LIBWALLY_DIR%\src /I%LIBWALLY_DIR%\include /I%LIBWALLY_DIR%\src\ccan /I%LIBWALLY_DIR%\src\ccan\base64 /I%LIBWALLY_DIR%\src\secp256k1 /Zi /LD src/aes.c src/anti_exfil.c src/base58.c src/base64.c src/bech32.c src/bip32.c src/bip38.c src/bip39.c src/blech32.c src/ecdh.c src/elements.c src/hex.c src/hmac.c src/internal.c src/mnemonic.c src/pbkdf2.c src/map.c src/psbt.c src/script.c src/scrypt.c src/sign.c src/symmetric.c src/transaction.c src/wif.c src/wordlist.c src/ccan/ccan/crypto/ripemd160/ripemd160.c src/ccan/ccan/crypto/sha256/sha256.c src/ccan/ccan/crypto/sha512/sha512.c src/ccan/ccan/base64/base64_.c src\ccan\ccan\str\hex\hex_.c src/secp256k1/src/secp256k1.c src/secp256k1/src/precomputed_ecmult_gen.c src/secp256k1/src/precomputed_ecmult.c /Fewally.dll +cl /utf-8 /DUSE_ECMULT_STATIC_PRECOMPUTATION /DECMULT_WINDOW_SIZE=15 /DWALLY_CORE_BUILD %ELEMENTS_OPT% /DHAVE_CONFIG_H /DSECP256K1_BUILD /I%LIBWALLY_DIR%\src\wrap_js\windows_config /I%LIBWALLY_DIR% /I%LIBWALLY_DIR%\src /I%LIBWALLY_DIR%\include /I%LIBWALLY_DIR%\src\ccan /I%LIBWALLY_DIR%\src\ccan\base64 /I%LIBWALLY_DIR%\src\secp256k1 /Zi /LD src/aes.c src/anti_exfil.c src/base58.c src/base64.c src/bech32.c src/bip32.c src/bip38.c src/bip39.c src/blech32.c src/descriptor.c src/ecdh.c src/elements.c src/hex.c src/hmac.c src/internal.c src/mnemonic.c src/pbkdf2.c src/map.c src/psbt.c src/script.c src/scrypt.c src/sign.c src/symmetric.c src/transaction.c src/wif.c src/wordlist.c src/ccan/ccan/crypto/ripemd160/ripemd160.c src/ccan/ccan/crypto/sha256/sha256.c src/ccan/ccan/crypto/sha512/sha512.c src/ccan/ccan/base64/base64_.c src\ccan\ccan\str\hex\hex_.c src/secp256k1/src/secp256k1.c src/secp256k1/src/precomputed_ecmult_gen.c src/secp256k1/src/precomputed_ecmult.c /Fewally.dll diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index 3ea880110..c436ebdf9 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -65,6 +65,11 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_bip32_key_to_address' \ ,'_wally_bzero' \ ,'_wally_cleanup' \ +,'_wally_descriptor_canonicalize' \ +,'_wally_descriptor_get_checksum' \ +,'_wally_descriptor_to_address' \ +,'_wally_descriptor_to_addresses' \ +,'_wally_descriptor_to_scriptpubkey' \ ,'_wally_ec_private_key_verify' \ ,'_wally_ec_public_key_decompress' \ ,'_wally_ec_public_key_from_private_key' \ @@ -127,6 +132,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_map_replace' \ ,'_wally_map_replace_integer' \ ,'_wally_map_sort' \ +,'_wally_miniscript_to_script' \ ,'_wally_pbkdf2_hmac_sha256' \ ,'_wally_pbkdf2_hmac_sha512' \ ,'_wally_psbt_add_tx_input_at' \