diff --git a/.gitignore b/.gitignore index ac92b852d..a351a02dc 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,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 cd372dae2..3d5284f42 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -103,7 +103,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 000e9ccc5..f474e771f 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -433,6 +433,60 @@ inline int cleanup(uint32_t flags) { return ret; } +template +inline int descriptor_canonicalize(const DESCRIPTOR& descriptor, uint32_t flags, char** output) { + int ret = ::wally_descriptor_canonicalize(detail::get_p(descriptor), flags, output); + return ret; +} + +inline int descriptor_free(struct wally_descriptor* descriptor) { + int ret = ::wally_descriptor_free(descriptor); + return ret; +} + +template +inline int descriptor_get_checksum(const DESCRIPTOR& descriptor, uint32_t flags, char** output) { + int ret = ::wally_descriptor_get_checksum(detail::get_p(descriptor), flags, output); + return ret; +} + +template +inline int descriptor_get_features(const DESCRIPTOR& descriptor, uint32_t* value_out) { + int ret = ::wally_descriptor_get_features(detail::get_p(descriptor), value_out); + return ret; +} + +template +inline int descriptor_parse(const DESCRIPTOR& descriptor, const VARS_IN& vars_in, uint32_t network, uint32_t flags, struct wally_descriptor** output) { + int ret = ::wally_descriptor_parse(detail::get_p(descriptor), detail::get_p(vars_in), network, flags, output); + return ret; +} + +template +inline int descriptor_to_address(const DESCRIPTOR& descriptor, uint32_t variant, uint32_t child_num, uint32_t flags, char** output) { + int ret = ::wally_descriptor_to_address(detail::get_p(descriptor), variant, child_num, flags, output); + return ret; +} + +template +inline int descriptor_to_addresses(const DESCRIPTOR& descriptor, uint32_t variant, uint32_t child_num, uint32_t flags, char** output, size_t num_outputs) { + int ret = ::wally_descriptor_to_addresses(detail::get_p(descriptor), variant, child_num, flags, output, num_outputs); + return ret; +} + +template +inline int descriptor_to_script(const DESCRIPTOR& descriptor, uint32_t depth, uint32_t index, uint32_t variant, uint32_t child_num, uint32_t flags, BYTES_OUT& bytes_out, size_t* written = 0) { + size_t n; + int ret = ::wally_descriptor_to_script(detail::get_p(descriptor), depth, index, variant, 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 descriptor_to_script_get_maximum_length(const DESCRIPTOR& descriptor, uint32_t flags, size_t* written) { + int ret = ::wally_descriptor_to_script_get_maximum_length(detail::get_p(descriptor), flags, written); + return ret; +} + 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()); diff --git a/include/wally_address.h b/include/wally_address.h index 82de21c0f..cc17c1021 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..ef0f4cbd6 --- /dev/null +++ b/include/wally_descriptor.h @@ -0,0 +1,172 @@ +#ifndef LIBWALLY_CORE_DESCRIPTOR_H +#define LIBWALLY_CORE_DESCRIPTOR_H + +#include "wally_core.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wally_map; +/** An opaque type holding a parsed minscript/descriptor expression */ +struct wally_descriptor; + +/* Miniscript type flag */ +#define WALLY_MINISCRIPT_WITNESS_SCRIPT 0x00 /** Witness script */ +#define WALLY_MINISCRIPT_TAPSCRIPT 0x01 /** Tapscript */ +#define WALLY_MINISCRIPT_ONLY 0x02 /** Only allow miniscript (not descriptor) expressions */ + +#define WALLY_MS_IS_RANGED 0x01 /** Allows key ranges via '*' */ + + +/** + * Parse an output descriptor or miniscript expression. + * + * :param descriptor: Output descriptor or miniscript expression to parse. + * :param vars_in: Map of variable names to values, or NULL. + * :param network: ``WALLY_NETWORK_`` constant descripting the network the + *| descriptor belongs to, or WALLY_NETWORK_NONE for miniscript-only expressions. + * :param flags: Include ``WALLY_MINISCRIPT_ONLY`` to disallow descriptor + *| expressions, ``WALLY_MINISCRIPT_TAPSCRIPT`` to use x-only pubkeys, or 0. + * :param output: Destination for the resulting parsed descriptor. + *| The descriptor returned should be freed using `wally_descriptor_free`. + * + * Variable names can be used in the descriptor string and will be substituted + * with the contents of ``vars_in`` during parsing. Key names for ``vars_in`` + * must be 16 characters or less in length and start with a letter. + */ +WALLY_CORE_API int wally_descriptor_parse( + const char *descriptor, + const struct wally_map *vars_in, + uint32_t network, + uint32_t flags, + struct wally_descriptor **output); + +/** + * Free a parsed output descriptor or miniscript expression. + * + * :param descriptor: Parsed output descriptor or miniscript expression to free. + */ +WALLY_CORE_API int wally_descriptor_free( + struct wally_descriptor *descriptor); + +/** + * Canonicalize a descriptor. + * + * :param descriptor: Parsed output descriptor or miniscript expression. + * :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`. + * + * .. 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 struct wally_descriptor *descriptor, + uint32_t flags, + char **output); + +/** + * Create an output descriptor checksum. + * + * :param descriptor: Parsed output descriptor or miniscript expression. + * :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 struct wally_descriptor *descriptor, + uint32_t flags, + char **output); + +/** + * Get the features used in a parsed output descriptor or miniscript expression. + * + * :param descriptor: Parsed output descriptor or miniscript expression. + * :param value_out: Destination for the resulting ``WALLY_MS_`` feature flags. + */ +WALLY_CORE_API int wally_descriptor_get_features( + const struct wally_descriptor *descriptor, + uint32_t *value_out); + +/** + * Get the maximum length of a script corresponding to an output descriptor. + * + * :param descriptor: Parsed output descriptor or miniscript expression. + * :param flags: For future use. Must be 0. + * :param written: Destination for the resulting maximum script length. + * + * .. note:: This function overestimates the script size required, but is + *| cheap to compute (does not require script generation). + */ +WALLY_CORE_API int wally_descriptor_to_script_get_maximum_length( + const struct wally_descriptor *descriptor, + uint32_t flags, + size_t *written); + +/** + * Create a script corresponding to an output descriptor or miniscript expression. + * + * :param descriptor: Parsed output descriptor or miniscript expression. + * :param depth: Depth of the expression tree to generate from. Pass 0 to generate from the root. + * :param index: The zero-based index of the child at depth ``depth`` to generate from. + * :param variant: The variant of descriptor to generate. Pass 0 for the default. + * :param child_num: The BIP32 child number to derive, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the resulting scriptPubKey or script. + * :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_script( + struct wally_descriptor *descriptor, + uint32_t depth, + uint32_t index, + uint32_t variant, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + +/** + * Create an address corresponding to an output descriptor. + * + * :param descriptor: Parsed output descriptor. + * :param variant: The variant of descriptor to generate. Pass 0 for the default. + * :param child_num: The BIP32 child number to derive, or zero for static descriptors. + * :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( + struct wally_descriptor *descriptor, + uint32_t variant, + uint32_t child_num, + uint32_t flags, + char **output); + +/** + * Create addresses that correspond to the derived range of an output descriptor. + * + * :param descriptor: Parsed output descriptor. + * :param variant: The variant of descriptor to generate. Pass 0 for the default. + * :param child_num: The BIP32 child number to derive, or zero for static descriptors. + * :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( + struct wally_descriptor *descriptor, + uint32_t variant, + uint32_t child_num, + uint32_t flags, + char **output, + size_t num_outputs); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBWALLY_CORE_DESCRIPTOR_H */ diff --git a/src/Makefile.am b/src/Makefile.am index b24182296..9c54e95d3 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 @@ -109,6 +110,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 @@ -141,6 +143,7 @@ libwallycore_la_SOURCES = \ bip38.c \ bip39.c \ bech32.c \ + descriptor.c \ ecdh.c \ elements.c \ blech32.c \ @@ -173,6 +176,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 \ @@ -239,6 +243,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 @@ -265,6 +277,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 @@ -291,6 +304,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 @@ -317,6 +331,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/amalgamation/combined.c b/src/amalgamation/combined.c index 6a7d90b88..b5877f8c1 100644 --- a/src/amalgamation/combined.c +++ b/src/amalgamation/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/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c new file mode 100644 index 000000000..517c7c784 --- /dev/null +++ b/src/ctest/test_descriptor.c @@ -0,0 +1,1734 @@ +#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") }, + { B("uncompressed"), B("0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf") }, +}; + +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; +} + +#define DEPTH_TEST_DESCRIPTOR "sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))" + +static const struct descriptor_test { + const char *name; + const char *descriptor; + const uint32_t network; + const uint32_t depth; + const uint32_t index; + const uint32_t variant; + const uint32_t *bip32_index; + const uint32_t flags; + const char *script; + const char *checksum; +} g_descriptor_cases[] = { + /* + * Output descriptors + */ + { + "descriptor - p2pk with checksum", + "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)#gn28ywm7", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac", + "gn28ywm7" + },{ + "descriptor - p2pkh", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac", + "8fhd9pwu" + },{ + "descriptor - p2wpkh", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc", + "8zl0zxma" + },{ + "descriptor - p2wpkh (bip143 test vector)", + "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + /* From script "76a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac" */ + "00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1", + "pw3pfgx0" + },{ + "descriptor - p2sh-p2wpkh", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a914cc6ffbc0bf31af759451068f90ba7a0272b6b33287", + "qkrrc7je" + },{ + "descriptor - p2sh-p2wpkh (bip143 test vector)", + "sh(wpkh(03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + /* From script "76a91479091972186c449eb1ded22b78e40d009bdf008988ac" */ + "a9144733f37cf4db86fbc2efed2500b4f4e49f31202387", + "946zr4e5" + },{ + "descriptor - combo(p2wpkh)", + "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "0014751e76e8199196d454941c45d1b3a323f1433bd6", + "lq9sf04s" + },{ + "descriptor - combo(p2pkh)", + "combo(04a238b0cbea14c9b3f59d0a586a82985f69af3da50579ed5971eefa41e6758ee7f1d77e4d673c6e7aac39759bb762d22259e27bf93572e9d5e363d5a64b6c062b)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a91448cb866ee3edb295e4cfeb3da65b4003ab9fa6a288ac", + "r3wj6k68" + },{ + "descriptor - p2sh-p2wsh", + "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a91455e8d5e8ee4f3604aba23c71c2684fa0a56a3a1287", + "2wtr0ej5" + },{ + "descriptor - multisig", + "multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "5121022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe421025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52ae", + "hzhjw406" + },{ + "descriptor - p2sh-multi", + "sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a914a6a8b030a38762f4c1f5cbe387b61a3c5da5cd2687", + "y9zthqta" + },{ + "descriptor - p2sh-sortedmulti 1", + "sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a914a6a8b030a38762f4c1f5cbe387b61a3c5da5cd2687", + "qwx6n9lh" + },{ + "descriptor - p2sh-sortedmulti 2", + "sh(sortedmulti(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a914a6a8b030a38762f4c1f5cbe387b61a3c5da5cd2687", + "fjpjdnvk" /* Note different checksum from p2sh-sortedmulti 1 */ + },{ + "descriptor - p2wsh-multi", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "0020773d709598b76c4e3b575c08aad40658963f9322affc0f8c28d1d9a68d0c944a", + "en3tu306" + },{ + "descriptor - p2wsh-multi (from bitcoind's `createmultisig`)", + "wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + /* From script "522103789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd2103dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a6162652ae" */ + "00207ca68449d39a95da91c6c283871f587b74b45c1645a37f8c8337fd3d9ac4fee6", + "5wacx8g6" + },{ + "descriptor - p2sh-p2wsh-multi", + "sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a914aec509e284f909f769bb7dda299a717c87cc97ac87", + "ks05yr6p" + },{ + "descriptor - p2sh-p2wsh-multi (from bitcoind's `createmultisig`)", + "sh(wsh(multi(2,03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd,03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + /* From script "522103789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd2103dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a6162652ae" */ + "a91411aca2b63fbee2cdda856217a8863135b070978b87", + "du4tngj2" + },{ + "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))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a914276b4ebc33265436a9c9b46ca23d6781aef98fe087", + "pckwejvm" + },{ + "descriptor - p2pk-xpub", + "pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "210339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2ac", + "axav5m0j" + },{ + "descriptor - p2pkh-xpub-derive", + "pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1/2)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a914f833c08f02389c451ae35ec797fccf7f396616bf88ac", + "kczqajcv" + },{ + "descriptor - p2pkh-empty-path", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac", + "ee44hjhg" + },{ + "descriptor - p2pkh-empty-path h-hardened", + "pkh([d34db33f/44h/0h/0h]mainnet_xpub/)#ltv22yxk", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac", + "ltv22yxk" /* Note different checksum despite being the same expression as above */ + },{ + "descriptor - p2pkh-parent-derive", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*)", + WALLY_NETWORK_NONE, 0, 0, 0, &g_miniscript_index_16, 0, + "76a914d234825a563de8b4fd31d2b30f60b1e60fe57ee788ac", + "ml40v0wf" + },{ + "descriptor - p2wsh-multi-xpub", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + WALLY_NETWORK_NONE, 0, 0, 0, &g_miniscript_index_16, 0, + "00204616bb4e66d0b540b480c5b26c619385c4c2b83ed79f4f3eab09b01745443a55", + "t2zpj2eu" + },{ + "descriptor - p2wsh-sortedmulti-xpub", + "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + WALLY_NETWORK_NONE, 0, 0, 0, &g_miniscript_index_16, 0, + "002002aeee9c3773dfecfe6215f2eea2908776b1232513a700e1ee516b634883ecb0", + "v66cvalc" + },{ + "descriptor - addr-btc-legacy-testnet", + "addr(moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a91457526b1a1534d4bde788253281649fc2e91dc70b88ac", + "9amhxcar" + },{ + "descriptor - addr-btc-segwit-mainnet", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + "8kzm8txf" + },{ + "descriptor - raw-checksum", + "raw(6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e)#zf2avljj", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e", + "zf2avljj" + },{ + "descriptor - p2pkh-xpriv", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1h/2)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a914b28d12ab72a51b10114b17ce76b536265194e1fb88ac", + "wghlxksl" + },{ + "descriptor - p2pkh-xpriv hardened last child", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1h/2h)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a9148ab3d0acbde6766fb0a24e0e4286168c2a24a7a088ac", + "cj20v7ag" + },{ + "descriptor - p2pkh-privkey-wif mainnet", + "pkh(L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a91492ed3283cfb01caec1163aefba29caf1182f478e88ac", + "qm00tjwh" + },{ + "descriptor - p2pkh-privkey-wif testnet uncompressed", + "pkh(936Xapr4wpeuiKToGeXtEcsVJAfE6ze8KUEb2UQu72rzBQsMZdX)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a91477b6f27ac523d8b9aa8abcfc94fd536493202ae088ac", + "9gv5p2gj" + },{ + "descriptor - A single key", + "wsh(c:pk_k(key_1))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "0020fa5bf4aae3ee617c6cce1976f6d7d285c359613ffeed481f1067f62bc0f54852", + "9u0h8j4t" + },{ + "descriptor - One of two keys (equally likely)", + "wsh(or_b(c:pk_k(key_1),sc:pk_k(key_2)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "002018a9df986ba10bcd8f503f495cab5fd00c9fb23c05143e65dbba49ef4d8a825f", + "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))))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "00201264946c666958d9522f63dcdcfc85941bdd5b9308b1e6c68696857506f6cced", + "nwlxsraz" + },{ + "descriptor - The BOLT #3 to_local policy", + "wsh(andor(c:pk_k(key_local),older(1008),c:pk_k(key_revocation)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "0020052cf1e9c90e9a2883d890467a6a01837e21b3b755a743c9d96a2b6f8285d7c0", + "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)))))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "0020f9259363db0facc7b97ab2c0294c4f21a0cd56b01bb54ecaaa5899012aae1bc2", + "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)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "002087515e0059c345eaa5cccbaa9cd16ad1266e7a69e350db82d8e1f33c86285303", + "8re62ejc" + },{ + "descriptor - derive key index 0", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + WALLY_NETWORK_NONE, 0, 0, 0, &g_miniscript_index_0, 0, + "002064969d8cdca2aa0bb72cfe88427612878db98a5f07f9a7ec6ec87b85e9f9208b", + "t2zpj2eu" + }, + /* https://github.com/rust-bitcoin/rust-miniscript/blob/master/src/descriptor/checksum.rs */ + { + "descriptor - rust-bitcoin checksum", + "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "0014e2d19350c9d8722e2994c81791f4a0ba115bc479", + "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))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a91445a9a622a8b0a1269944be477640eedc447bbd8487", + "ggrsrxfy" + }, + /* + * Depth/index test cases (for generating sub-scripts) + */ + { + "descriptor depth - p2sh-p2wsh-multi (p2sh-p2wsh)", + DEPTH_TEST_DESCRIPTOR, + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "a914aec509e284f909f769bb7dda299a717c87cc97ac87", + "ks05yr6p" + }, { + "descriptor depth - p2sh-p2wsh-multi (p2wsh)", + DEPTH_TEST_DESCRIPTOR, + WALLY_NETWORK_NONE, 1, 0, 0, NULL, 0, + "0020ef8110fa7ddefb3e2d02b2c1b1480389b4bc93f606281570cfc20dba18066aee", + "ks05yr6p" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi)", + DEPTH_TEST_DESCRIPTOR, + WALLY_NETWORK_NONE, 2, 0, 0, NULL, 0, + "512103f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa82103499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e42102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e53ae", + "ks05yr6p" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[0])", + DEPTH_TEST_DESCRIPTOR, + WALLY_NETWORK_NONE, 3, 0, 0, NULL, 0, + "51", + "ks05yr6p" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[1])", + DEPTH_TEST_DESCRIPTOR, + WALLY_NETWORK_NONE, 3, 1, 0, NULL, 0, + "03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8", + "ks05yr6p" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[2])", + DEPTH_TEST_DESCRIPTOR, + WALLY_NETWORK_NONE, 3, 2, 0, NULL, 0, + "03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4", + "ks05yr6p" + }, { + "descriptor depth - p2sh-p2wsh-multi (multi[3])", + DEPTH_TEST_DESCRIPTOR, + WALLY_NETWORK_NONE, 3, 3, 0, NULL, 0, + "02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e", + "ks05yr6p" + }, + /* + * Miniscript: Randomly generated test set that covers the majority of type and node type combinations + */ + { + "miniscript - random 1", + "lltvln:after(1231488000)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6300676300676300670400046749b1926869516868", + "" + }, { + "miniscript - random 2", + "uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", + "" + }, { + "miniscript - random 3", + "or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", + "" + }, { + "miniscript - random 4", + "j:and_v(vdv:after(1567547623),older(2016))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "829263766304e7e06e5db169686902e007b268", + "" + }, { + "miniscript - random 5", + "t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", + "" + }, { + "miniscript - random 6", + "t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", + "" + }, { + "miniscript - random 7", + "or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", + "" + }, { + "miniscript - random 8", + "or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", + "" + }, { + "miniscript - random 9", + "and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", + "" + }, { + "miniscript - random 10", + "j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", + "" + }, { + "miniscript - random 11", + "and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", + "" + }, { + "miniscript - random 12", + "j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", + "" + }, { + "miniscript - random 13", + "and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", + "" + }, { + "miniscript - random 14", + "thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", + "" + }, { + "miniscript - random 15", + "and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", + "" + }, { + "miniscript - random 16", + "or_d(d:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "766303e2e440b26903e2e440b26968736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", + "" + }, { + "miniscript - random 17", + "c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", + "" + }, { + "miniscript - random 18", + "c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", + "" + }, { + "miniscript - random 19", + "and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", + "" + }, { + "miniscript - random 20", + "andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", + "" + }, { + "miniscript - random 21", + "or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", + "" + }, { + "miniscript - random 22", + "thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", + "" + }, { + "miniscript - random 23", + "and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", + "" + }, { + "miniscript - random 24", + "and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", + "" + }, { + "miniscript - random 25", + "c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", + "" + }, { + "miniscript - random 26", + "or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", + "" + }, { + "miniscript - random 27", + "c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", + "" + }, { + "miniscript - random 28", + "c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", + "" + }, { + "miniscript - random 29", + "c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", + "" + }, { + "miniscript - random 30", + "thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", + "" + }, { + "miniscript - random 31", + "thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),altv:after(1000000000),altv:after(100))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", + "" + }, { + "miniscript - random 32", + "thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", + "" + }, { + "miniscript - random 33", + "thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", + "" + }, { + "miniscript - random 34", + "after(100)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "0164b1", + "" + }, { + "miniscript - random 35", + "after(1000000000)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "0400ca9a3bb1", + "" + }, { + "miniscript - random 36", + "or_b(l:after(100),al:after(1000000000))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6300670164b1686b6300670400ca9a3bb1686c9b", + "" + }, { + "miniscript - random 37", + "and_b(after(100),a:after(1000000000))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "0164b16b0400ca9a3bb16c9a", + "" + }, { + "miniscript - random 38", + "thresh(2,ltv:after(1000000000),altv:after(100),a:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "6300670400ca9a3bb16951686b6300670164b16951686c936b2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6c935287", + "" + }, + /* + * Miniscript: Error cases + */ + { + "miniscript - Too many wrappers", + "lltvlnlltvln:after(1231488000)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + NULL, + "" + }, { + "miniscript - Number too small to parse", + "older(-9223372036854775808)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + NULL, + "" + }, { + "miniscript - Number too large to parse", + "older(9223372036854775807)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + NULL, + "" + }, { + "miniscript - Rust-miniscript issue 63", + "nl:0", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + NULL, + "" + }, { + "miniscript - Rust-miniscript context test", + "or_i(pk(uncompressed),pk(uncompressed))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + NULL, + "" + }, { + "miniscript - Threshold greater than the number of policies", + "thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + NULL, + "" + }, { + "miniscript - Threshold of 0 is not allowed", + "thresh(0,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + NULL, + "" + }, + /* + * Miniscript: BOLT examples + */ + { + "miniscript - A single key", + "c:pk_k(key_1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "" + }, { + "miniscript - A single key (2)", + "pk(key_1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "" + }, { + "miniscript - One of two keys (equally likely)", + "or_b(c:pk_k(key_1),sc:pk_k(key_2))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac7c2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac9b", + "" + }, { + "miniscript - One of two keys (equally likely) (2)", + "or_b(pk(key_1),s:pk(key_2))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac7c2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac9b", + "" + }, { + "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(key_user),or_d(c:pk_k(key_service),older(12960)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ad2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac736402a032b268", + "" + }, { + "miniscript - A user and a 2FA service need to sign off, but after 90 days the user alone is enough (2)", + "and_v(v:pk(key_user),or_d(pk(key_service),older(12960)))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ad2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac736402a032b268", + "" + }, { + "miniscript - The BOLT #3 to_local policy", + "andor(c:pk_k(key_local),older(1008),c:pk_k(key_revocation))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac6702f003b268", + "" + }, { + "miniscript - The BOLT #3 to_local policy (2)", + "andor(pk(key_local),older(1008),pk(key_revocation))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac6702f003b268", + "" + }, { + "miniscript - The BOLT #3 offered HTLC policy", + "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))))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac642103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ad21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac6482012088a914d0721279e70d39fb4aa409b52839a0056454e3b588686851", + "" + }, { + "miniscript - The BOLT #3 offered HTLC policy (2)", + "t:or_c(pk(key_revocation),and_v(v:pk(key_remote),or_c(pk(key_local),v:hash160(H))))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac642103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ad21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac6482012088a914d0721279e70d39fb4aa409b52839a0056454e3b588686851", + "" + }, { + "miniscript - The BOLT #3 received HTLC policy", + "andor(c:pk_k(key_remote),or_i(and_v(vc:pk_h(key_local),hash160(H)),older(1008)),c:pk_k(key_revocation))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac676376a914d0721279e70d39fb4aa409b52839a0056454e3b588ad82012088a914d0721279e70d39fb4aa409b52839a0056454e3b5876702f003b26868", + "" + }, { + "miniscript - The BOLT #3 received HTLC policy (2)", + "andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation))", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "2103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac676376a914d0721279e70d39fb4aa409b52839a0056454e3b588ad82012088a914d0721279e70d39fb4aa409b52839a0056454e3b5876702f003b26868", + "" + }, + /* + * Taproot cases + */ + { + "miniscript - taproot raw pubkey", + "c:pk_k(daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, + "20daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac", + "" + }, { + "miniscript - taproot bip32 key", + "c:pk_k([bd16bee5/0]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, + "208c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816ac", + "" + }, { + "miniscript - taproot WIF", + "c:pk_k(L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, + "20ff7e7b1d3c4ba385cb1f2e6423bf30c96fb5007e7917b09ec1b6c965ef644d13ac", + "" + }, + /* + * Misc error cases (code coverage) + */ + { + "descriptor errchk - invalid checksum", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)#8rap84p2", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - upper case hardened indicator", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1H/2)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - privkey - unmatch network1", + "wpkh(cSMSHUGbEiZQUXVw9zA33yT3m8fgC27rn2XEGZJupwCpsRS3rAYa)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - privkey - unmatch network2", + "wpkh(cSMSHUGbEiZQUXVw9zA33yT3m8fgC27rn2XEGZJupwCpsRS3rAYa)", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - privkey - unmatch network3", + "wpkh(L4gLCkRn5VfJsDSWsrrC57kqVgq6Z2sjeRELmim5zzAgJisysh17)", + WALLY_NETWORK_BITCOIN_TESTNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - privkey - unmatch network4", + "wpkh(L4gLCkRn5VfJsDSWsrrC57kqVgq6Z2sjeRELmim5zzAgJisysh17)", + WALLY_NETWORK_BITCOIN_REGTEST, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - privkey - unmatch network5", + "wpkh(L4gLCkRn5VfJsDSWsrrC57kqVgq6Z2sjeRELmim5zzAgJisysh17)", + WALLY_NETWORK_LIQUID_REGTEST, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xpubkey - unmatch network1", + "wpkh(tpubD6NzVbkrYhZ4XJDrzRvuxHEyQaPd1mwwdDofEJwekX18tAdsqeKfxss79AJzg1431FybXg5rfpTrJF4iAhyR7RubberdzEQXiRmXGADH2eA)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xpubkey - unmatch network2", + "wpkh(tpubD6NzVbkrYhZ4XJDrzRvuxHEyQaPd1mwwdDofEJwekX18tAdsqeKfxss79AJzg1431FybXg5rfpTrJF4iAhyR7RubberdzEQXiRmXGADH2eA)", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xpubkey - unmatch network3", + "wpkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB)", + WALLY_NETWORK_BITCOIN_TESTNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xpubkey - unmatch network4", + "wpkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB)", + WALLY_NETWORK_BITCOIN_REGTEST, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xpubkey - unmatch network5", + "wpkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB)", + WALLY_NETWORK_LIQUID_REGTEST, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xprivkey - unmatch network1", + "wpkh(tprv8jDG3g2yc8vh71x9ejCDSfMz4AuQRx7MMNBXXvpD4jh7CkDuB3ZmnLVcEM99jgg5MaSp7gYNpnKS5dvkGqq7ad8X63tE7yFaMGTfp6gD54p)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xprivkey - unmatch network2", + "wpkh(tprv8jDG3g2yc8vh71x9ejCDSfMz4AuQRx7MMNBXXvpD4jh7CkDuB3ZmnLVcEM99jgg5MaSp7gYNpnKS5dvkGqq7ad8X63tE7yFaMGTfp6gD54p)", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xprivkey - unmatch network3", + "wpkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU)", + WALLY_NETWORK_BITCOIN_TESTNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xprivkey - unmatch network4", + "wpkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU)", + WALLY_NETWORK_BITCOIN_REGTEST, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - xprivkey - unmatch network5", + "wpkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU)", + WALLY_NETWORK_LIQUID_REGTEST, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - addr - unmatch network1", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + WALLY_NETWORK_BITCOIN_TESTNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor errchk - addr - unmatch network2", + "addr(ex1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39q06fgz7)", + WALLY_NETWORK_LIQUID_REGTEST, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "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, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sh - non-root", + "sh(sh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sh - multi-child", + "sh(sh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wsh - non-sh parent", + "wsh(wsh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wsh uncompressed", + "wsh(936Xapr4wpeuiKToGeXtEcsVJAfE6ze8KUEb2UQu72rzBQsMZdX)", + WALLY_NETWORK_BITCOIN_TESTNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wsh - multi-child", + "wsh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - pk - non-key child", + "pk(1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - pk - multi-child", + "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wpkh - multi-child", + "wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wpkh - non-key child", + "wpkh(1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wpkh - wsh parent", + "wsh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wpkh - descriptor type parent", + "pk(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wpkh uncompressed", + "wpkh(uncompressed)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sh(wpkh) uncompressed", + "sh(wpkh(uncompressed))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - combo - any parent", + "pk(combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - combo - multi-child", + "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - multi - no args", + "multi", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - multi - not enough children", + "multi(1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - multi - no number", + "multi(022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - multi - negative number", + "multi(-1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - multi - non-key child", + "multi(1,1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sortedmulti - no args", + "sortedmulti", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sortedmulti - not enough children", + "sortedmulti(1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sortedmulti - no number", + "sortedmulti(022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sortedmulti - negative number", + "sortedmulti(-1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sortedmulti - non-key child", + "sortedmulti(1,1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - addr - multi-child", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3,bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "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, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - addr - any parent", + "pk(addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - raw - multi-child", + "raw(000102030405060708090a0b0c0d0e0f,000102030405060708090a0b0c0d0e0f)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "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, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - raw - any parent", + "pk(raw(000102030405060708090a0b0c0d0e0f))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - after - non number child", + "wsh(after(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - after - zero delay", + "wsh(after(0))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - after - negative delay", + "wsh(after(-1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - after - delay too large", + "wsh(after(2147483648))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - older - non number child", + "wsh(older(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - older - zero delay", + "wsh(older(0))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - older - negative delay", + "wsh(older(-1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - older - delay too large", + "wsh(older(2147483648))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "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, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "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, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - wsh-pk uncompressed", + "wsh(pk(uncompressed))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "descriptor - sh(wsh-pk) uncompressed", + "sh(wsh(pk(uncompressed)))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + },{ + "miniscript - core recursion limit", + "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, + /* 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, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "miniscript - invalid raw key", + "pk([78412e3a]0208a117f3897c3a13c9384b8695eed98dc31bc2500feb19a1af424cd47a5d83/1/1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "miniscript - invalid fingerprint separator", + "pk([78412e3a]]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798/1/1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "miniscript - fuzzer error (1)", + "pk([11111f11]033333333333333333333333333333323333333333333333333333333433333333]]333]]3]]101333333333333433333]]]10]333333mmmm)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "miniscript - fuzzer error (2)", + "pk(0777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "miniscript - Non-hex fingerprint", + "pk([NonHexor]0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "miniscript - Short fingerprint", + "pk([1122334]", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "miniscript - Long fingerprint", + "pk([112233445]", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, + /* https://github.com/rust-bitcoin/rust-miniscript/blob/master/src/descriptor/mod.rs */ + { + "descriptor - unclosed brace", + "(", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "descriptor - unclosed brace (nested)", + "(x()", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "descriptor - invalid char", + "(\x7f()3", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + }, { + "descriptor - empty pk", + "pk(]", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + NULL, + "" + } + /* TODO: Add more tests for verify_x cases */ +}; + +#define ADDR(a) 1, { a, "", "", "", "", "", "", "", "", "", "", "", "", "", \ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" } + +static const struct address_test { + const char *name; + const char *descriptor; + const uint32_t bip32_index; + const uint32_t network; + const size_t num_addresses; + const char *addresses[30]; +} g_address_cases[] = { + /* + * Single address cases + */ + { + "address - p2pkh - mainnet", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP") + },{ + "address - p2pkh - testnet", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + ADDR("mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H") + },{ + "address - p2pkh - regtest", + "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + ADDR("mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H") + },{ + "address - p2wpkh - mainnet", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("bc1q0ht9tyks4vh7p5p904t340cr9nvahy7u3re7zg") + },{ + "address - p2wpkh - testnet", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + ADDR("tb1q0ht9tyks4vh7p5p904t340cr9nvahy7um9zdem") + },{ + "address - p2wpkh - regtest", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + ADDR("bcrt1q0ht9tyks4vh7p5p904t340cr9nvahy7uevmqwj") + },{ + "address - p2sh-p2wpkh - mainnet", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("3LKyvRN6SmYXGBNn8fcQvYxW9MGKtwcinN") + },{ + "address - p2sh-p2wpkh - liquidv1", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + 0, + WALLY_NETWORK_LIQUID, + ADDR("H1pVQ7VtauJK4v7ixvwFQpDFYW2Q6eiPVx") + },{ + "address - p2sh-p2wpkh - liquidregtest", + "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", + 0, + WALLY_NETWORK_LIQUID_REGTEST, + ADDR("XVzCr2EG9PyrWX8qr2visL1aCfJMhGTZyS") + },{ + "address - p2sh-p2wsh - mainnet", + "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("39XGHYpYmJV9sGFoGHZeU2rLkY6r1MJ6C1") + },{ + "address - p2sh-p2wsh - liquidv1", + "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))", + 0, + WALLY_NETWORK_LIQUID, + ADDR("Gq1mmExLuSEwfzzk6YtUxJ769grv6T5Tak") + },{ + "address - p2wsh-multi - mainnet", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("bc1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qn4zazc") + },{ + "address - p2wsh-multi - testnet", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + ADDR("tb1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qya5jch") + },{ + "address - p2wsh-multi - regtest", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + ADDR("bcrt1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qfy75dd") + },{ + "address - p2wsh-multi - liquidv1", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_LIQUID, + ADDR("ex1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39q06fgz7") + },{ + "address - p2wsh-multi - liquidregtest", + "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))", + 0, + WALLY_NETWORK_LIQUID_REGTEST, + ADDR("ert1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39qchk2yf") + },{ + "address - p2pkh-xpub-derive", + "pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1/2)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("1PdNaNxbyQvHW5QHuAZenMGVHrrRaJuZDJ") + },{ + "descriptor - p2pkh-empty-path", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R") + },{ + "address - p2pkh-parent-derive", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("14qCH92HCyDDBFFZdhDt1WMfrMDYnBFYMF") + },{ + "address - p2wsh-multi-xpub", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c") + },{ + "address - p2wsh-sortedmulti-xpub", + "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c") + },{ + "address - addr-btc-legacy-testnet", + "addr(moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW)", + 0, + WALLY_NETWORK_BITCOIN_TESTNET, + ADDR("moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW") + },{ + "address - addr-btc-legacy-testnet/regtest", + "addr(moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW)", + 0, + WALLY_NETWORK_BITCOIN_REGTEST, + ADDR("moUfpGiXWcFd5ueRn3988VDqRSkB5NrEmW") + },{ + "address - addr-btc-segwit-mainnet", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3") + },{ + "address - p2pkh-xpriv", + "pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1h/2)", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("1HH6H4km128m4NsJMNVN2qqCHukbEhgU3V") + },{ + "address - A single key", + "wsh(c:pk_k(key_1))", + 0, + WALLY_NETWORK_BITCOIN_MAINNET, + ADDR("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, + ADDR("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, + ADDR("bc1qzfjfgmrxd9vdj530v0wdely9jsda6kunpzc7d35xj6zh2phkenkstn6ur7") + },{ + "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, + ADDR("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, + ADDR("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, + ADDR("bc1qsag4uqzecdz74fwvew4fe5t26ynxu7nfudgdhqkcu8enep3g2vpsvp0wl0") + }, + /* + * Multiple address 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" + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" + } + }, + /* + * Address error cases + */ + { + "address errchk - invalid network", + "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)", + 0, 0xf0, ADDR("") + },{ + "address errchk - addr - unmatch network1", + "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + 0, WALLY_NETWORK_BITCOIN_TESTNET, ADDR("") + },{ + "address errchk - addr - no HRP", + "addr(bcqrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", + 0, WALLY_NETWORK_BITCOIN_TESTNET, ADDR("") + },{ + "address errchk - addr - unmatch network2", + "addr(ex1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39q06fgz7)", + 0, WALLY_NETWORK_LIQUID_REGTEST, ADDR("") + },{ + "address errchk - unsupport address - p2pk", + "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - unsupport address - raw", + "raw(6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e)#zf2avljj", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - unterminated key origin", + "pkh([d34db33f/44'/0'/0'mainnet_xpub/1/*)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - double slash", + "pkh([d34db33f/44'/0'/0']mainnet_xpub//)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - middle double slash", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1//2)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - end slash", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/2/)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - duplicate wildcard (1)", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/**)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - duplicate wildcard (2)", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*/*)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - non-final wildcard", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*/1)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - hardened from xpub", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1h)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - hardened wildcard from xpub", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/1/*h)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - index too large", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/2147483648/1)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - invalid path character", + "pkh([d34db33f/44'/0'/0']mainnet_xpub/3c/1)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + /* 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)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + /* 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)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - invalid descriptor character", + "pkh(\b)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - unknown function", + "foo(mainnet_xpub)", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - missing leading brace", + ")", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + },{ + "address errchk - trailing value", + "pkh(mainnet_xpub),1", + 0, WALLY_NETWORK_BITCOIN_MAINNET, ADDR("") + } +}; + +static bool check_descriptor_to_script(const struct descriptor_test* test) +{ + struct wally_descriptor *descriptor; + size_t written, max_written; + unsigned char script[520]; + char *checksum, *canonical; + int expected_ret, ret, len_ret; + uint32_t child_num = test->bip32_index ? *test->bip32_index : 0, features; + + expected_ret = test->script ? WALLY_OK : WALLY_EINVAL; + + ret = wally_descriptor_parse(test->descriptor, &g_key_map, test->network, + test->flags, &descriptor); + if (expected_ret == WALLY_OK || ret == expected_ret) { + /* For failure cases, we may fail when generating instead of parsing, + * we catch those cases below */ + if (!check_ret("descriptor_parse", ret, expected_ret)) + return false; + + if (expected_ret != WALLY_OK) + return true; + } + + ret = wally_descriptor_to_script(descriptor, + test->depth, test->index, + test->variant, child_num, + 0, script, sizeof(script), + &written); + if (!check_ret("descriptor_to_script", ret, expected_ret)) + return false; + if (expected_ret != WALLY_OK) { + wally_descriptor_free(descriptor); + return true; + } + + ret = wally_descriptor_get_features(descriptor, &features); + if (!check_ret("descriptor_get_features", ret, WALLY_OK)) + return false; + + len_ret = wally_descriptor_to_script_get_maximum_length(descriptor, 0, + &max_written); + if (!check_ret("descriptor_to_script_get_maximum_length", len_ret, WALLY_OK) || + max_written < written) + return false; + + ret = wally_descriptor_get_checksum(descriptor, 0, &checksum); + if (!check_ret("descriptor_get_checksum", ret, WALLY_OK)) + return false; + + ret = wally_descriptor_canonicalize(descriptor, 0, &canonical); + wally_free_string(canonical); + if (!check_ret("descriptor_canonicalize", ret, WALLY_OK)) + return false; + + ret = check_varbuff("descriptor_to_script", script, written, test->script) && + (!*test->checksum || !strcmp(checksum, test->checksum)); + if (!ret) + printf("%s: expected [%s], got [%s]\n", "descriptor_to_script", + test->checksum, checksum); + + wally_free_string(checksum); + wally_descriptor_free(descriptor); + return !!ret; +} + +static bool check_descriptor_to_address(const struct address_test *test) +{ + struct wally_descriptor *descriptor; + char *addresses[64]; + uint32_t variant = 0, flags = 0; + size_t i; + int ret, expected_ret = *test->addresses[0] ? WALLY_OK : WALLY_EINVAL; + + ret = wally_descriptor_parse(test->descriptor, &g_key_map, test->network, + flags, &descriptor); + + if (expected_ret == WALLY_OK || ret == expected_ret) { + /* For failure cases, we may fail when generating instead of parsing, + * we catch those cases below */ + if (!check_ret("descriptor_parse", ret, expected_ret)) + return false; + + if (expected_ret != WALLY_OK) + return true; + } + + ret = wally_descriptor_to_addresses(descriptor, variant, test->bip32_index, + flags, addresses, test->num_addresses); + if (!check_ret("descriptor_to_addresses", ret, expected_ret)) + return false; + + if (expected_ret == WALLY_OK) { + for (i = 0; i < test->num_addresses; ++i) { + if (strcmp(test->addresses[i], addresses[i]) != 0) { + printf("%s: expected address: %s, got%s\n", "descriptor_to_addresses", + test->addresses[i], addresses[i]); + return false; + } + wally_free_string(addresses[i]); + } + } + wally_descriptor_free(descriptor); + return true; +} + +int main(void) +{ + bool tests_ok = true; + size_t i; + + for (i = 0; i < NUM_ELEMS(g_descriptor_cases); ++i) { + if (!check_descriptor_to_script(&g_descriptor_cases[i])) { + printf("[%s] descriptor test failed!\n", g_descriptor_cases[i].name); + tests_ok = false; + } + } + + for (i = 0; i < NUM_ELEMS(g_address_cases); ++i) { + if (!check_descriptor_to_address(&g_address_cases[i])) { + printf("[%s] descriptor_address test failed!\n", g_address_cases[i].name); + 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..24389a91c --- /dev/null +++ b/src/descriptor.c @@ -0,0 +1,2480 @@ +#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])) +#define MS_FLAGS_ALL (WALLY_MINISCRIPT_WITNESS_SCRIPT | \ + WALLY_MINISCRIPT_TAPSCRIPT | WALLY_MINISCRIPT_ONLY) + +/* 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 */ +#define PROP_G 0x00040000 /* Relative time timelock */ +#define PROP_H 0x00080000 /* Relative height timelock */ +#define PROP_I 0x00100000 /* Absolute time timelock */ +#define PROP_J 0x00200000 /* Absolute time heightlock */ +#define PROP_K 0x00400000 /* No timelock mixing allowed */ + +/* OP_0 properties: Bzudemsxk */ +#define PROP_OP_0 (TYPE_B | PROP_Z | PROP_U | PROP_D | PROP_E | PROP_M | PROP_S | PROP_X | PROP_K) +/* OP_1 properties: Bzufmxk */ +#define PROP_OP_1 (TYPE_B | PROP_Z | PROP_U | PROP_F | PROP_M | PROP_X | PROP_K) + +#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) + +#define DESCRIPTOR_MIN_SIZE 20 +#define MINISCRIPT_MULTI_MAX 20 +#define REDEEM_SCRIPT_MAX_SIZE 520 +#define WITNESS_SCRIPT_MAX_SIZE 10000 +#define DESCRIPTOR_SEQUENCE_LOCKTIME_TYPE_FLAG 0x00400000 +#define DESCRIPTOR_LOCKTIME_THRESHOLD 500000000 +#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) + +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' } + }, +}; + +/* 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; + +typedef struct wally_descriptor { + char *src; /* The canonical source script */ + size_t src_len; /* Length of src */ + ms_node *top_node; /* The first node of the parse tree */ + const struct addr_ver_t *addr_ver; + uint32_t features; /* Features present in the parsed tree */ + size_t script_len; /* Max script length generatable from this expression */ + uint32_t child_num; /* BIP32 child number for derivation */ +} ms_ctx; + +/* Built-in miniscript expressions */ +typedef int (*node_verify_fn_t)(ms_node *node); +typedef int (*node_gen_fn_t)(ms_ctx *ctx, ms_node *node, + 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; +}; + +/* FIXME: the max is actually 20 in a witness script */ +#define CHECKMULTISIG_NUM_KEYS_MAX 15 +struct multisig_sort_data_t { + size_t pubkey_len; + unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN]; +}; + +static const struct addr_ver_t *addr_ver_from_network(uint32_t network) +{ + size_t i; + if (network != WALLY_NETWORK_NONE) { + 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 generate_script(ms_ctx *ctx, ms_node *node, + 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; +} + +/* + * 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; +} + +static int 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; +} + +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; + } + if (node->kind & (KIND_RAW | KIND_ADDRESS) || node->kind == KIND_PUBLIC_KEY || node->kind == KIND_PRIVATE_KEY) + clear_and_free((void*)node->data, node->data_len); + clear_and_free(node, sizeof(*node)); + } +} + +static bool has_two_different_lock_states(uint32_t primary, uint32_t secondary) +{ + return ((primary & PROP_G) && (secondary & PROP_H)) || + ((primary & PROP_H) && (secondary & PROP_G)) || + ((primary & PROP_I) && (secondary & PROP_J)) || + ((primary & PROP_J) && (secondary & PROP_I)); +} + +int wally_descriptor_free(ms_ctx *ctx) +{ + if (ctx) { + wally_free_string(ctx->src); + node_free(ctx->top_node); + clear_and_free(ctx, sizeof(*ctx)); + } + return WALLY_OK; +} + +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; + if (node->parent && node_has_uncompressed_key(node) && + node->parent->kind != KIND_DESCRIPTOR_SH && + node->parent->kind != KIND_DESCRIPTOR_WSH) + 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; + if (builtin_get(node)->kind == KIND_MINISCRIPT_OLDER) { + if (node->child->number & DESCRIPTOR_SEQUENCE_LOCKTIME_TYPE_FLAG) + node->type_properties |= PROP_G; + else + node->type_properties |= PROP_H; + } else { + /* KIND_MINISCRIPT_AFTER */ + if (node->child->number >= DESCRIPTOR_LOCKTIME_THRESHOLD) + node->type_properties |= PROP_I; + else + node->type_properties |= PROP_J; + } + 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_prop, uint32_t y_prop, uint32_t z_prop) +{ + /* 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_prop & TYPE_B) || !(x_prop & need_x)) + return 0; + if (!(y_prop & z_prop & need_yz)) + return 0; + + prop |= y_prop & z_prop & need_yz; + prop |= x_prop & y_prop & z_prop & PROP_Z; + prop |= (x_prop | (y_prop & z_prop)) & PROP_O; + prop |= y_prop & z_prop & PROP_U; + prop |= z_prop & PROP_D; + prop |= (x_prop | y_prop | z_prop) & (PROP_G | PROP_H | PROP_I | PROP_J); + if (x_prop & PROP_S || y_prop & PROP_F) { + prop |= z_prop & PROP_F; + prop |= x_prop & z_prop & PROP_E; + } + if (x_prop & PROP_E && + (x_prop | y_prop | z_prop) & PROP_S) { + prop |= x_prop & y_prop & z_prop & PROP_M; + } + prop |= z_prop & (x_prop | y_prop) & PROP_S; + if ((x_prop & y_prop & z_prop & PROP_K) && + !has_two_different_lock_states(x_prop, y_prop)) + prop |= PROP_K; + 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_prop, uint32_t y_prop) +{ + uint32_t prop = 0; + prop |= x_prop & PROP_N; + prop |= y_prop & (PROP_U | PROP_X); + prop |= x_prop & y_prop & (PROP_D | PROP_M | PROP_Z); + prop |= (x_prop | y_prop) & PROP_S; + prop |= (x_prop | y_prop) & (PROP_G | PROP_H | PROP_I | PROP_J); + if (x_prop & TYPE_V) + prop |= y_prop & (TYPE_K | TYPE_V | TYPE_B); + if (x_prop & PROP_Z) + prop |= y_prop & PROP_N; + if ((x_prop | y_prop) & PROP_Z) + prop |= (x_prop | y_prop) & PROP_O; + if (y_prop & PROP_F || x_prop & PROP_S) + prop |= PROP_F; + if ((x_prop & y_prop & PROP_K) && + !has_two_different_lock_states(x_prop, y_prop)) + prop |= PROP_K; + + 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 | y_prop) & (PROP_G | PROP_H | PROP_I | PROP_J); + 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; + if ((x_prop & y_prop & PROP_K) && + !has_two_different_lock_states(x_prop, y_prop)) + node->type_properties |= PROP_K; + + 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); + node->type_properties |= (x_prop | y_prop) & (PROP_G | PROP_H | PROP_I | PROP_J); + node->type_properties |= (x_prop & y_prop) & PROP_K; + 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); + node->type_properties |= (x_prop | y_prop) & (PROP_G | PROP_H | PROP_I | PROP_J); + node->type_properties |= (x_prop & y_prop) & PROP_K; + 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); + node->type_properties |= (x_prop | y_prop) & (PROP_G | PROP_H | PROP_I | PROP_J); + node->type_properties |= (x_prop & y_prop) & PROP_K; + 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_prop, uint32_t y_prop) +{ + uint32_t prop = PROP_X; + prop |= x_prop & y_prop & (TYPE_V | TYPE_B | TYPE_K | PROP_U | PROP_F | PROP_S); + prop |= (x_prop | y_prop) & (PROP_G | PROP_H | PROP_I | PROP_J); + prop |= (x_prop & y_prop) & PROP_K; + if (!(prop & TYPE_MASK)) + return 0; + + prop |= (x_prop | y_prop) & PROP_D; + if ((x_prop & y_prop) & PROP_Z) + prop |= PROP_O; + if ((x_prop | y_prop) & PROP_F) + prop |= (x_prop | y_prop) & PROP_E; + if ((x_prop | y_prop) & PROP_S) + prop |= x_prop & y_prop & 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; + uint32_t acc_tl = PROP_K, tmp_acc_tl; + 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; + + + tmp_acc_tl = ((acc_tl | child->type_properties) & (PROP_G | PROP_H | PROP_I | PROP_J)); + if ((acc_tl & child->type_properties) & PROP_K) { + if (top->number <= 1 || (top->number > 1 && + !has_two_different_lock_states(acc_tl, child->type_properties))) + tmp_acc_tl |= PROP_K; + } + acc_tl = tmp_acc_tl; + ++count; + } + if (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; + node->type_properties |= acc_tl; + + 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 reverse 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_G | PROP_H | PROP_I | PROP_J | PROP_K, 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 | PROP_G | PROP_H | PROP_I | PROP_J | PROP_K, 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_G | PROP_H | PROP_I | PROP_J | PROP_K, 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_D | PROP_X | + PROP_G | PROP_H | PROP_I | PROP_J | PROP_K); + 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_G | + PROP_H | PROP_I | PROP_J | PROP_K, PROP_F | PROP_X); + break; + case 'j': + PROP_REQUIRE(TYPE_B | PROP_N); + PROP_CHANGE(PROP_O | PROP_U | PROP_M | PROP_S | PROP_N | PROP_D | + PROP_X, 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_N | PROP_D | PROP_X, 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_W) && (*properties & PROP_N)) || + ((*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_number(int64_t number, ms_node *parent, + unsigned char *script, size_t script_len, size_t *written) +{ + if ((parent && !parent->builtin)) + return WALLY_EINVAL; + + if (number >= -1 && number <= 16) { + *written = 1; + if (*written <= script_len) + script[0] = number == -1 ? OP_1NEGATE : value_to_op_n(number); + } else { + /* PUSH */ + *written = 1 + scriptint_get_length(number); + if (*written <= script_len) { + script[0] = *written - 1; + scriptint_to_bytes(number, script + 1); + } + } + return WALLY_OK; +} + +static int generate_pk_k(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + unsigned char buff[EC_PUBLIC_KEY_UNCOMPRESSED_LEN]; + int ret; + + if (!node->child || !node_is_root(node)) + return WALLY_EINVAL; + + ret = generate_script(ctx, node->child, buff, sizeof(buff), written); + if (ret == WALLY_OK) { + if (*written != EC_PUBLIC_KEY_LEN && *written != EC_XONLY_PUBLIC_KEY_LEN && + *written != EC_PUBLIC_KEY_UNCOMPRESSED_LEN) + return WALLY_EINVAL; /* Invalid pubkey length */ + if (*written <= script_len) { + script[0] = *written & 0xff; /* push opcode */ + memcpy(script + 1, buff, *written); + } + *written += 1; + } + return ret; +} + +static int generate_pk_h(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + unsigned char buff[1 + EC_PUBLIC_KEY_UNCOMPRESSED_LEN]; + int ret = WALLY_OK; + + if (script_len >= WALLY_SCRIPTPUBKEY_P2PKH_LEN - 1) { + ret = generate_pk_k(ctx, node, buff, sizeof(buff), written); + if (ret == WALLY_OK) { + if (node->child->is_xonly_key) + return WALLY_EINVAL; + script[0] = OP_DUP; + script[1] = OP_HASH160; + script[2] = HASH160_LEN; + ret = wally_hash160(&buff[1], *written - 1, script + 3, HASH160_LEN); + script[3 + HASH160_LEN] = OP_EQUALVERIFY; + } + } + *written = WALLY_SCRIPTPUBKEY_P2PKH_LEN - 1; + return ret; +} + +static int generate_sh_wsh(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + const bool is_sh = node->kind == KIND_DESCRIPTOR_SH; + const size_t final_len = is_sh ? WALLY_SCRIPTPUBKEY_P2SH_LEN : WALLY_SCRIPTPUBKEY_P2WSH_LEN; + const uint32_t flags = is_sh ? WALLY_SCRIPT_HASH160 : WALLY_SCRIPT_SHA256; + unsigned char output[WALLY_SCRIPTPUBKEY_P2WSH_LEN]; + size_t output_len; + int ret; + + if (!node->child || !node_is_root(node)) + return WALLY_EINVAL; + + ret = generate_script(ctx, node->child, script, script_len, &output_len); + if (ret == WALLY_OK) { + if (output_len > REDEEM_SCRIPT_MAX_SIZE) + ret = WALLY_EINVAL; + else { + const size_t required = output_len > final_len ? output_len : final_len; + if (script_len < required) { + *written = required; /* To generate, not for the final script */ + } else { + ret = (is_sh ? wally_scriptpubkey_p2sh_from_bytes : + wally_witness_program_from_bytes)( + script, output_len, flags, output, sizeof(output), written); + if (ret == WALLY_OK && *written <= script_len) + memcpy(script, output, *written); + } + } + } + return ret; +} + +static int generate_checksig(unsigned char *script, size_t script_len, size_t *written) +{ + if (!*written || (*written + 1 > WITNESS_SCRIPT_MAX_SIZE)) + return WALLY_EINVAL; + + *written += 1; + if (*written <= script_len) + script[*written - 1] = OP_CHECKSIG; + return WALLY_OK; +} + +static int generate_pk(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret = generate_pk_k(ctx, node, script, script_len, written); + return ret == WALLY_OK ? generate_checksig(script, script_len, written) : ret; +} + +static int generate_pkh(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret = generate_pk_h(ctx, node, script, script_len, written); + return ret == WALLY_OK ? generate_checksig(script, script_len, written) : ret; +} + +static int generate_wpkh(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + unsigned char output[WALLY_SCRIPTPUBKEY_P2WPKH_LEN]; + size_t output_len; + int ret; + + if (!node->child || !node_is_root(node)) + return WALLY_EINVAL; + + ret = generate_script(ctx, node->child, script, script_len, &output_len); + if (ret == WALLY_OK) { + if (output_len > REDEEM_SCRIPT_MAX_SIZE) + ret = WALLY_EINVAL; + else { + const size_t final_len = sizeof(output); + const size_t required = output_len > final_len ? output_len : final_len; + if (script_len < required) { + *written = required; /* To generate, not for the final script */ + } else { + ret = wally_witness_program_from_bytes(script, output_len, WALLY_SCRIPT_HASH160, + output, final_len, written); + if (ret == WALLY_OK && *written <= script_len) + memcpy(script, output, *written); + } + } + } + return ret; +} + +static int generate_combo(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + if (node_has_uncompressed_key(node)) + return generate_pkh(ctx, node, script, script_len, written); + return generate_wpkh(ctx, node, 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_ctx *ctx, ms_node *node, + 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[CHECKMULTISIG_NUM_KEYS_MAX]; + int ret; + + if (!child || !node_is_root(node) || !node->builtin) + return WALLY_EINVAL; + + if ((ret = generate_script(ctx, child, 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(ctx, child, + item->pubkey, sizeof(item->pubkey), &item->pubkey_len); + if (ret == WALLY_OK && item->pubkey_len > sizeof(item->pubkey)) + ret = WALLY_EINVAL; /* FIXME: check for valid pubkey lengths */ + child = child->next; + } + + if (ret == WALLY_OK && (!count || child)) + ret = WALLY_EINVAL; /* Not enough, or too many keys for multisig */ + + if (ret == WALLY_OK) { + /* Note we don't bother sorting if we are already beyond the output + * size, since sorting won't change the final size computed */ + if (node->kind == KIND_DESCRIPTOR_MULTI_S && offset <= script_len) + 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 <= script_len) { + 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; + size_t remaining_len = offset > script_len ? 0 : script_len - offset; + ret = generate_number(count, node->parent, script + offset, + remaining_len, &number_len); + if (ret == WALLY_OK) { + *written = offset + number_len + 1; + if (*written > REDEEM_SCRIPT_MAX_SIZE) + return WALLY_EINVAL; + if (*written <= script_len) + script[*written - 1] = OP_CHECKMULTISIG; + } + } + } + return ret; +} + +static int generate_raw(ms_ctx *ctx, ms_node *node, + 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(ctx, node->child, script, script_len, written); + return *written > REDEEM_SCRIPT_MAX_SIZE ? WALLY_EINVAL : ret; +} + +static int generate_delay(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + size_t output_len = *written; + if (!node->child || !node_is_root(node) || !node->builtin) + return WALLY_EINVAL; + + ret = generate_script(ctx, node->child, script, script_len, &output_len); + if (ret != WALLY_OK) + return ret; + + *written = output_len + 1; + if (*written <= script_len) { + if (node->kind == KIND_MINISCRIPT_OLDER) + script[output_len] = OP_CHECKSEQUENCEVERIFY; + else if (node->kind == KIND_MINISCRIPT_AFTER) + script[output_len] = OP_CHECKLOCKTIMEVERIFY; + else + ret = WALLY_ERROR; /* Shouldn't happen */ + } + return ret; +} + +static int generate_hash_type(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + int ret; + size_t hash_size, output_len = *written, remaining_len = 0; + unsigned char op_code; + + 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 >= 7) + remaining_len = script_len - 7; + ret = generate_script(ctx, node->child, script + 6, remaining_len, &output_len); + if (ret == WALLY_OK) { + *written = output_len + 7; + if (*written <= script_len) { + script[0] = OP_SIZE; + script[1] = 0x01; + script[2] = 0x20; + script[3] = OP_EQUALVERIFY; + script[4] = op_code; + script[5] = hash_size; + script[6 + output_len] = OP_EQUAL; + } + } + return ret; +} + +static int generate_concat(ms_ctx *ctx, ms_node *node, size_t target_num, + const size_t *reference_indices, + const unsigned char **insert, const uint8_t *insert_len, + unsigned char *script, size_t script_len, size_t *written) +{ + size_t i = 0, offset = 0; + ms_node *children[3] = { NULL, NULL, NULL }; + const size_t default_indices[] = { 0, 1, 2 }, *indices; + int ret = WALLY_OK; + + if (!node->child || !node_is_root(node)) + return WALLY_EINVAL; + + indices = reference_indices ? reference_indices : default_indices; + + for (i = 0; i < target_num; ++i) { + children[i] = i == 0 ? node->child : children[i - 1]->next; + if (!children[i]) + return WALLY_EINVAL; + } + + for (i = 0; i < target_num; ++i) { + size_t output_len = 0, remaining_len = 0; + + if (insert_len[i] && offset + insert_len[i] <= script_len) + memcpy(script + offset, insert[i], insert_len[i]); + offset += insert_len[i]; + if (offset < script_len) + remaining_len = script_len - offset - 1; + ret = generate_script(ctx, children[indices[i]], + script + offset, remaining_len, &output_len); + if (ret != WALLY_OK) + return ret; + offset += output_len; + } + + if (insert_len[3] && offset + insert_len[3] <= script_len) + memcpy(script + offset, insert[3], insert_len[3]); + *written = offset + insert_len[3]; + if (*written > REDEEM_SCRIPT_MAX_SIZE) + return WALLY_EINVAL; + return ret; +} + +static int generate_andor(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] NOTIF 0 ELSE [Y] ENDIF */ + 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 }; + const unsigned char *insert[4] = { NULL, first_op, second_op, last_op }; + const uint8_t insert_len[4] = { 0, NUM_ELEMS(first_op), NUM_ELEMS(second_op), NUM_ELEMS(last_op) }; + return generate_concat(ctx, node, 3, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_and_v(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] [Y] */ + const size_t indices[2] = { 0, 1 }; + const unsigned char *insert[4] = { NULL, NULL, NULL, NULL }; + const uint8_t insert_len[4] = { 0, 0, 0, 0 }; + return generate_concat(ctx, node, 2, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_and_b(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] [Y] BOOLAND */ + const unsigned char append[1] = { OP_BOOLAND }; + const size_t indices[2] = { 0, 1 }; + const unsigned char *insert[4] = { NULL, NULL, NULL, append }; + const uint8_t insert_len[4] = { 0, 0, 0, NUM_ELEMS(append) }; + return generate_concat(ctx, node, 2, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_and_n(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] NOTIF 0 ELSE [Y] ENDIF */ + 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 }; + const unsigned char *insert[4] = { NULL, middle_op, NULL, last_op }; + const uint8_t insert_len[4] = { 0, NUM_ELEMS(middle_op), 0, NUM_ELEMS(last_op) }; + return generate_concat(ctx, node, 2, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_or_b(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] [Y] OP_BOOLOR */ + const unsigned char append[1] = { OP_BOOLOR }; + const size_t indices[2] = { 0, 1 }; + const unsigned char *insert[4] = { NULL, NULL, NULL, append }; + const uint8_t insert_len[4] = { 0, 0, 0, NUM_ELEMS(append) }; + return generate_concat(ctx, node, 2, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_or_c(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] NOTIF [Z] ENDIF */ + const unsigned char middle_op[1] = { OP_NOTIF }; + const unsigned char last_op[1] = { OP_ENDIF }; + const size_t indices[2] = { 0, 1 }; + const unsigned char *insert[4] = { NULL, middle_op, NULL, last_op }; + const uint8_t insert_len[4] = { 0, NUM_ELEMS(middle_op), 0, NUM_ELEMS(last_op) }; + return generate_concat(ctx, node, 2, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_or_d(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X] IFDUP NOTIF [Z] ENDIF */ + 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 }; + const unsigned char *insert[4] = { NULL, middle_op, NULL, last_op }; + const uint8_t insert_len[4] = { 0, NUM_ELEMS(middle_op), 0, NUM_ELEMS(last_op) }; + return generate_concat(ctx, node, 2, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_or_i(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* IF [X] ELSE [Z] ENDIF */ + 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 }; + const unsigned char *insert[4] = { top_op, middle_op, NULL, last_op }; + const uint8_t insert_len[4] = { NUM_ELEMS(top_op), NUM_ELEMS(middle_op), 0, NUM_ELEMS(last_op) }; + return generate_concat(ctx, node, 2, indices, insert, insert_len, + script, script_len, written); +} + +static int generate_thresh(ms_ctx *ctx, ms_node *node, + unsigned char *script, size_t script_len, size_t *written) +{ + /* [X1] [X2] ADD ... [Xn] ADD EQUAL */ + ms_node *child = node->child; + size_t output_len, remaining_len, offset = 0, count = 0; + int ret = WALLY_OK; + + if (!child || !node_is_root(node)) + return WALLY_EINVAL; + + for (child = child->next; child && ret == WALLY_OK; child = child->next) { + remaining_len = offset >= script_len ? 0 : script_len - offset - 1; + ret = generate_script(ctx, child, + script + offset, remaining_len, &output_len); + if (ret == WALLY_OK) { + offset += output_len; + if (count++) { + if (++offset < script_len) + script[offset - 1] = OP_ADD; + } + } + } + if (ret == WALLY_OK) { + remaining_len = offset >= script_len ? 0 : script_len - offset - 1; + ret = generate_script(ctx, node->child, + script + offset, remaining_len, &output_len); + } + if (ret == WALLY_OK) { + *written = offset + output_len + 1; + if (*written > REDEEM_SCRIPT_MAX_SIZE) + return WALLY_EINVAL; + if (*written <= script_len) + script[*written - 1] = OP_EQUAL; + } + return ret; +} + +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) { \ + if (move_by) memmove(script + (move_by), script, *written) +#define WRAP_REQUIRE_END } break + + /* 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; + WRAP_REQUIRE_END; + case 's': + WRAP_REQUIRE(1, 1); + script[0] = OP_SWAP; + WRAP_REQUIRE_END; + case 'c': + WRAP_REQUIRE(1, 0); + script[*written] = OP_CHECKSIG; + WRAP_REQUIRE_END; + case 't': + WRAP_REQUIRE(1, 0); + script[*written] = OP_1; + WRAP_REQUIRE_END; + case 'd': + WRAP_REQUIRE(3, 2); + script[0] = OP_DUP; + script[1] = OP_IF; + script[*written + 2] = OP_ENDIF; + WRAP_REQUIRE_END; + case 'v': + if (*written >= script_len) { + /* If we aren't actually generating output because the script + * output is too small, we have to assume the worst case, i.e. + * that this wrapper will require an extra opcode rather than + * modifying in place. + */ + output_len = 1; + } else { + 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 { + WRAP_REQUIRE(1, 0); + script[*written] = OP_VERIFY; + WRAP_REQUIRE_END; + } + } + break; + case 'j': + WRAP_REQUIRE(4, 3); + script[0] = OP_SIZE; + script[1] = OP_0NOTEQUAL; + script[2] = OP_IF; + script[*written + 3] = OP_ENDIF; + WRAP_REQUIRE_END; + case 'n': + WRAP_REQUIRE(1, 0); + script[*written] = OP_0NOTEQUAL; + WRAP_REQUIRE_END; + case 'l': + WRAP_REQUIRE(4, 3); + script[0] = OP_IF; + script[1] = OP_0; + script[2] = OP_ELSE; + script[*written + 3] = OP_ENDIF; + WRAP_REQUIRE_END; + 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; + WRAP_REQUIRE_END; + default: + return WALLY_ERROR; /* Wrapper type not found, should not happen */ + } + if (*written + output_len > WITNESS_SCRIPT_MAX_SIZE) + return WALLY_EINVAL; + *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 | PROP_K, + 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 | PROP_K, + 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 | PROP_K, + 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 | PROP_K, + 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 | PROP_K, + 1, verify_pk, generate_pk_h + }, { + I_NAME("older"), + KIND_MINISCRIPT_OLDER, + TYPE_B | PROP_Z | PROP_F | PROP_M | PROP_X | PROP_K, + 1, verify_delay, generate_delay + }, { + I_NAME("after"), + KIND_MINISCRIPT_AFTER, + TYPE_B | PROP_Z | PROP_F | PROP_M | PROP_X | PROP_K, + 1, verify_delay, generate_delay + }, { + I_NAME("sha256"), + KIND_MINISCRIPT_SHA256, + TYPE_B | PROP_O | PROP_N | PROP_D | PROP_U | PROP_M | PROP_K, + 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 | PROP_K, + 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 | PROP_K, + 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 | PROP_K, + 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_ctx *ctx, ms_node *node, + 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(ctx, node, 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_NUMBER) { + ret = generate_number(node->number, node->parent, script, script_len, written); + } else if (node->kind & (KIND_RAW | KIND_ADDRESS) || node->kind == KIND_PUBLIC_KEY) { + if (node->data_len <= script_len) + memcpy(script, node->data, node->data_len); + *written = node->data_len; + ret = WALLY_OK; + } else if (node->kind == KIND_PRIVATE_KEY) { + unsigned char pubkey[EC_PUBLIC_KEY_LEN]; + ret = wally_ec_public_key_from_private_key((const unsigned char*)node->data, node->data_len, + pubkey, sizeof(pubkey)); + if (ret == WALLY_OK) { + if (node->is_uncompressed_key) { + *written = EC_PUBLIC_KEY_UNCOMPRESSED_LEN; + if (*written <= script_len) + ret = wally_ec_public_key_decompress(pubkey, sizeof(pubkey), script, + EC_PUBLIC_KEY_UNCOMPRESSED_LEN); + } else { + if (node->is_xonly_key) { + *written = EC_XONLY_PUBLIC_KEY_LEN; + if (*written <= script_len) + memcpy(script, &pubkey[1], EC_XONLY_PUBLIC_KEY_LEN); + } else { + *written = EC_PUBLIC_KEY_LEN; + if (*written <= script_len) + memcpy(script, pubkey, EC_PUBLIC_KEY_LEN); + } + } + } + } else if ((node->kind & KIND_BIP32) == KIND_BIP32) { + struct ext_key master; + + *written = node->is_xonly_key ? EC_XONLY_PUBLIC_KEY_LEN : EC_PUBLIC_KEY_LEN; + if (*written > script_len) + return WALLY_OK; + + 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, + ctx->child_num, flags, &derived); + if (ret == WALLY_OK) + memcpy(&master, &derived, sizeof(master)); + } + if (ret == WALLY_OK) + memcpy(script, master.pub_key + (node->is_xonly_key ? 1 : 0), *written); + wally_clear(&master, sizeof(master)); + } + return ret; +} + +static int analyze_address(ms_ctx *ctx, const char *str, size_t str_len, + ms_node *node, const struct addr_ver_t *addr_ver) +{ + /* Generated script buffer, big enough for ADDRESS_PUBKEY_MAX_LEN too */ + unsigned char buff[WALLY_SEGWIT_ADDRESS_PUBKEY_MAX_LEN]; + unsigned char decoded[1 + HASH160_LEN + BASE58_CHECKSUM_LEN]; + size_t decoded_len, written; + int ret; + (void)ctx; + + ret = wally_base58_n_to_bytes(str, str_len, BASE58_FLAG_CHECKSUM, + decoded, sizeof(decoded), &decoded_len); + if (ret == WALLY_OK) { + /* P2PKH/P2SH base58 address */ + bool is_p2sh; + + if (decoded_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 */ + + /* Create the scriptpubkey and copy it into the node */ + ret = (is_p2sh ? wally_scriptpubkey_p2sh_from_bytes : wally_scriptpubkey_p2pkh_from_bytes)( + decoded + 1, HASH160_LEN, 0, buff, sizeof(buff), &written); + if (ret == WALLY_OK) { + if (written != (is_p2sh ? WALLY_SCRIPTPUBKEY_P2SH_LEN : WALLY_SCRIPTPUBKEY_P2PKH_LEN)) + ret = WALLY_ERROR; /* Should not happen! */ + else if (!clone_bytes((unsigned char **)&node->data, buff, written)) + ret = WALLY_ENOMEM; + else { + node->data_len = written; + node->kind = KIND_BASE58; + } + } + } else { + /* Segwit bech32 address */ + char *hrp_end = memchr(str, '1', str_len); + size_t hrp_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, + buff, sizeof(buff), &written); + if (ret == WALLY_OK) { + if (written != HASH160_LEN + 2 && written != SHA256_LEN + 2) + ret = WALLY_EINVAL; /* Unknown address format */ + else if (!clone_bytes((unsigned char **)&node->data, buff, written)) + ret = WALLY_ENOMEM; + else { + node->data_len = written; + node->kind = KIND_BECH32; + } + } + } + return ret; +} + +static bool analyze_pubkey_hex(ms_ctx *ctx, 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; + (void)ctx; + + 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; + + if (!clone_bytes((unsigned char **)&node->data, pubkey + offset, written)) + return false; /* FIXME: This needs to return ENOMEM, not continue checking */ + node->data_len = str_len / 2; + node->is_uncompressed_key = str_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN * 2; + node->is_xonly_key = str_len == EC_XONLY_PUBLIC_KEY_LEN * 2; + node->kind = KIND_PUBLIC_KEY; + return true; +} + +static int analyze_miniscript_key(ms_ctx *ctx, const struct addr_ver_t *addr_ver, + uint32_t flags, ms_node *node, ms_node *parent) +{ + unsigned char privkey[2 + EC_PRIVATE_KEY_LEN + BASE58_CHECKSUM_LEN]; + struct ext_key extkey; + size_t privkey_len, size; + int ret; + + 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(ctx, 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), &privkey_len); + if (ret == WALLY_OK && privkey_len <= EC_PRIVATE_KEY_LEN + 2) { + if (addr_ver && (addr_ver->version_wif != privkey[0])) + return WALLY_EINVAL; + if (privkey_len == EC_PRIVATE_KEY_LEN + 1) { + node->is_uncompressed_key = true; + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) + return WALLY_EINVAL; /* Tapscript only allows x-only keys */ + } else if (privkey_len != EC_PRIVATE_KEY_LEN + 2 || + privkey[EC_PRIVATE_KEY_LEN + 1] != 1) + return WALLY_EINVAL; /* Unknown WIF format */ + + node->is_xonly_key = (flags & WALLY_MINISCRIPT_TAPSCRIPT) != 0; + ret = wally_ec_private_key_verify(&privkey[1], EC_PRIVATE_KEY_LEN); + if (ret == WALLY_OK && !clone_bytes((unsigned char **)&node->data, &privkey[1], EC_PRIVATE_KEY_LEN)) + ret = WALLY_EINVAL; + else { + node->data_len = EC_PRIVATE_KEY_LEN; + node->kind = KIND_PRIVATE_KEY; + } + 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 */ + ctx->features |= WALLY_MS_IS_RANGED; + } + } + } + + 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(ms_ctx *ctx, 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(ctx, str, str_len, node, addr_ver); + + 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) { + int ret = wally_hex_n_verify(str, str_len); + if (ret == WALLY_OK) { + if (!(node->data = wally_malloc(str_len / 2))) + ret = WALLY_ENOMEM; + else { + size_t written; + wally_hex_n_to_bytes(str, str_len, + (unsigned char*)node->data, str_len / 2, + &written); + node->data_len = written; + node->kind = KIND_RAW; + } + } + return ret; + } + } + + node->data = str; + node->data_len = str_len; + + if (strtoll_n(node->data, node->data_len, &node->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)); + node->kind = KIND_NUMBER; + return WALLY_OK; + } + return analyze_miniscript_key(ctx, addr_ver, flags, node, parent); +} + +static int analyze_miniscript(ms_ctx *ctx, 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; + + node->parent = parent; + + for (i = 0; i < str_len; ++i) { + if (!node->builtin && str[i] == ':') { + if (i - offset > sizeof(node->wrapper_str) - 1) { + 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(ctx, 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(ctx, 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; +} + +/* Compute the maximum size of the buffer we need to generate a script. + * Although our final script may be small, we need a larger scratch + * buffer to generate its sub-components to assemble the final script. + * For example, we may need to generate a sub-script then hash it to + * produce a top level p2pkh/p2sh etc. Note that the exact size of a + * script cannot be known up-front without generating it; the 'v' wrapper + * for example modifies or appends depending on its sub-scripts final + * opcode, which isn't known until its generated. + * This can result in over-estimating the size required substantially in + * some cases, in order to be safe without requiring a huge up-front + * allocation. The alternative is repeated sub-allocations as we generate, + * which is undesirable as its slow, and leads to memory fragmentation. + */ +static int node_generation_size(const ms_node *node, size_t *total) +{ + const struct ms_builtin_t *builtin = builtin_get(node); + const ms_node *child; + size_t i; + int ret = WALLY_OK; + + if (builtin) { + /* TODO: we could collect this into a subtotal and use + * max(subtotal, final) to minimise the allocation size + * slightly, e.g. for sh-wrapped scripts. + */ + for (child = node->child; ret == WALLY_OK && child; child = child->next) + ret = node_generation_size(child, total); + if (ret != WALLY_OK) + return ret; + + switch (builtin->kind) { + case KIND_DESCRIPTOR_PK | KIND_MINISCRIPT_PK: + *total += 2; + break; + case KIND_DESCRIPTOR_PKH | KIND_MINISCRIPT_PKH: + *total += WALLY_SCRIPTPUBKEY_P2PKH_LEN; + break; + case KIND_DESCRIPTOR_MULTI | KIND_MINISCRIPT_MULTI: + case KIND_DESCRIPTOR_MULTI_S: + *total += CHECKMULTISIG_NUM_KEYS_MAX * EC_PUBLIC_KEY_UNCOMPRESSED_LEN; + *total += 5; /* worst case OP_PUSHDATA1 * 2 + OP_CHECKMULTISIG */ + break; + case KIND_DESCRIPTOR_SH: + *total += WALLY_SCRIPTPUBKEY_P2SH_LEN; + break; + case KIND_DESCRIPTOR_WSH: + *total += WALLY_SCRIPTPUBKEY_P2WSH_LEN; + break; + case KIND_DESCRIPTOR_WPKH: + *total += WALLY_SCRIPTPUBKEY_P2WPKH_LEN; + break; + case KIND_DESCRIPTOR_COMBO: + /* max of p2pk, p2pkh, p2wpkh, or p2sh-p2wpkh */ + *total += WALLY_SCRIPTPUBKEY_P2PKH_LEN; + break; + case KIND_DESCRIPTOR_ADDR: + case KIND_DESCRIPTOR_RAW: + /* No-op */ + break; + case KIND_MINISCRIPT_PK_K: + *total += 1; + break; + case KIND_MINISCRIPT_PK_H: + *total += WALLY_SCRIPTPUBKEY_P2PKH_LEN - 1; + break; + case KIND_MINISCRIPT_OLDER: + case KIND_MINISCRIPT_AFTER: + *total += 1; + break; + case KIND_MINISCRIPT_SHA256: + case KIND_MINISCRIPT_HASH256: + case KIND_MINISCRIPT_RIPEMD160: + case KIND_MINISCRIPT_HASH160: + *total += 7; + break; + case KIND_MINISCRIPT_THRESH: + *total += node_get_child_count(node) - 1 + 1; + break; + case KIND_MINISCRIPT_AND_B: + case KIND_MINISCRIPT_OR_B: + *total += 1; + break; + case KIND_MINISCRIPT_OR_C: + *total += 2; + break; + case KIND_MINISCRIPT_OR_D: + case KIND_MINISCRIPT_OR_I: + *total += 3; + break; + case KIND_MINISCRIPT_AND_N: + case KIND_MINISCRIPT_ANDOR: + *total += 4; + break; + case KIND_MINISCRIPT_AND_V: + /* no-op */ + break; + default: + return WALLY_ERROR; /* Should not happen! */ + } + + for (i = strlen(node->wrapper_str); i != 0; --i) { + switch(node->wrapper_str[i - 1]) { + case 's': case 'c': case 't': case 'n': case 'v': + *total += 1; /* max: 'v' can can be 0 or 1 */ + break; + case 'a': + *total += 2; + break; + case 'd': + *total += 3; + break; + case 'j': case 'l': case 'u': + *total += 4; + break; + } + } + return WALLY_OK; + } + if (node->kind == KIND_NUMBER) { + if (node->number >= -1 && node->number <= 16) + *total += 1; + else + *total += 1 + scriptint_get_length(node->number); + } else if (node->kind & (KIND_RAW | KIND_ADDRESS) || node->kind == KIND_PUBLIC_KEY) { + *total += node->data_len; + } else if (node->kind == KIND_PRIVATE_KEY || (node->kind & KIND_BIP32) == KIND_BIP32) { + if (node->is_uncompressed_key) + *total += EC_PUBLIC_KEY_UNCOMPRESSED_LEN; + else if (node->is_xonly_key) + *total += EC_XONLY_PUBLIC_KEY_LEN; + else + *total += EC_PUBLIC_KEY_LEN; + } else + return WALLY_ERROR; /* Should not happen */ + return WALLY_OK; +} + +static int node_generate_script(ms_ctx *ctx, uint32_t depth, uint32_t index, + unsigned char *bytes_out, size_t len, + size_t *written) +{ + ms_node *node = ctx->top_node, *parent; + size_t i; + int ret; + + *written = 0; + + for (i = 0; i < depth; ++i) { + if (!node->child) + return WALLY_EINVAL; + node = node->child; + } + for (i = 0; i < index; ++i) { + if (!node->next) + return WALLY_EINVAL; + node = node->next; + } + + parent = node->parent; + node->parent = NULL; + ret = generate_script(ctx, node, bytes_out, len, written); + node->parent = parent; + return ret; +} + +int wally_descriptor_parse(const char *miniscript, + const struct wally_map *vars_in, + uint32_t network, uint32_t flags, + ms_ctx **output) +{ + const struct addr_ver_t *addr_ver = addr_ver_from_network(network); + uint32_t kind = KIND_MINISCRIPT | (flags & WALLY_MINISCRIPT_ONLY ? 0 : KIND_DESCRIPTOR); + ms_ctx *ctx; + int ret; + + *output = NULL; + + if (!miniscript || flags & ~MS_FLAGS_ALL || + (network != WALLY_NETWORK_NONE && !addr_ver)) + return WALLY_EINVAL; + + /* Allocate a context to hold the canonicalized/parsed expression */ + if (!(*output = wally_calloc(sizeof(ms_ctx)))) + return WALLY_ENOMEM; + ctx = *output; + ctx->addr_ver = addr_ver; + ret = canonicalize(miniscript, vars_in, 0, &ctx->src); + if (ret == WALLY_OK) { + ctx->src_len = strlen(ctx->src); + ret = analyze_miniscript(ctx, ctx->src, ctx->src_len, kind, + ctx->addr_ver, flags, NULL, NULL, + &ctx->top_node); + if (ret == WALLY_OK && (kind & KIND_DESCRIPTOR) && + (!ctx->top_node->builtin || !(ctx->top_node->kind & KIND_DESCRIPTOR))) + ret = WALLY_EINVAL; + else if (ret == WALLY_OK) { + ret = node_generation_size(ctx->top_node, &ctx->script_len); + } + } + if (ret != WALLY_OK) { + wally_descriptor_free(ctx); + *output = NULL; + } + return ret; +} + +int wally_descriptor_to_script(struct wally_descriptor *descriptor, + uint32_t depth, uint32_t index, + uint32_t variant, uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + (void)variant; /* TODO: support variants */ + + if (written) + *written = 0; + + if (!descriptor || child_num >= BIP32_INITIAL_HARDENED_CHILD || + (flags & WALLY_MINISCRIPT_ONLY) || !bytes_out || !len || !written) + return WALLY_EINVAL; + + descriptor->child_num = child_num; + return node_generate_script(descriptor, depth, index, + bytes_out, len, written); +} + +int wally_descriptor_to_script_get_maximum_length( + const struct wally_descriptor *descriptor, uint32_t flags, size_t *written) +{ + if (written) + *written = 0; + if (!descriptor || (flags & ~MS_FLAGS_ALL) || !written) + return WALLY_EINVAL; + *written = descriptor->script_len; + return WALLY_OK; +} + +int wally_descriptor_to_addresses(struct wally_descriptor *descriptor, + uint32_t variant, uint32_t child_num, + uint32_t flags, + char **addresses, size_t num_addresses) +{ + unsigned char buff[REDEEM_SCRIPT_MAX_SIZE], *p = buff; + ms_ctx *ctx = descriptor; + size_t i, written; + int ret = WALLY_OK; + (void)variant; + + if (!ctx || child_num >= BIP32_INITIAL_HARDENED_CHILD || + (uint64_t)child_num + num_addresses >= BIP32_INITIAL_HARDENED_CHILD || + flags || !addresses || !num_addresses) + return WALLY_EINVAL; + + wally_clear(addresses, num_addresses * sizeof(*addresses)); + if (ctx->script_len > sizeof(buff) &&(!(p = wally_malloc(ctx->script_len)))) + return WALLY_ENOMEM; + + for (i = 0; ret == WALLY_OK && i < num_addresses; ++i) { + ctx->child_num = child_num + i; + ret = node_generate_script(ctx, 0, 0, p, ctx->script_len, &written); + if (ret == WALLY_OK) { + if (written > ctx->script_len) + ret = WALLY_ERROR; /* Not enough room - should not happen! */ + else { + /* Generate the address corresponding to this script */ + ret = wally_scriptpubkey_to_address(p, written, + ctx->addr_ver->network, + &addresses[i]); + if (ret == WALLY_EINVAL) + ret = wally_addr_segwit_from_bytes(p, written, + ctx->addr_ver->family, + 0, &addresses[i]); + } + } + } + + if (ret != WALLY_OK) { + /* Free any partial results */ + for (i = 0; i < num_addresses; ++i) { + wally_free_string(addresses[i]); + addresses[i] = NULL; + } + } + if (p != buff) + wally_free(p); + return ret; +} + +int wally_descriptor_to_address(struct wally_descriptor *descriptor, + uint32_t variant, uint32_t child_num, + uint32_t flags, char **output) +{ + return wally_descriptor_to_addresses(descriptor, variant, child_num, flags, + output, 1); +} + +int wally_descriptor_get_checksum(const struct wally_descriptor *descriptor, + uint32_t flags, char **output) +{ + if (output) + *output = NULL; + + if (!descriptor || flags || !output) + return WALLY_EINVAL; + + if (!(*output = wally_strdup(descriptor->src + descriptor->src_len - DESCRIPTOR_CHECKSUM_LENGTH))) + return WALLY_ENOMEM; + return WALLY_OK; +} + +int wally_descriptor_canonicalize(const struct wally_descriptor *descriptor, + uint32_t flags, char **output) +{ + if (output) + *output = NULL; + + if (!descriptor || flags || !output) + return WALLY_EINVAL; + + if (!(*output = wally_strdup(descriptor->src))) + return WALLY_ENOMEM; + return WALLY_OK; +} + +int wally_descriptor_get_features(const struct wally_descriptor *descriptor, + uint32_t *value_out) +{ + if (value_out) + *value_out = 0; + if (!descriptor || !value_out) + return WALLY_EINVAL; + *value_out = descriptor->features; + return WALLY_OK; +} diff --git a/src/script.c b/src/script.c index dec2ca83a..48a53219c 100644 --- a/src/script.c +++ b/src/script.c @@ -157,7 +157,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; @@ -171,7 +171,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..a16444f79 --- /dev/null +++ b/src/swig_java/src/com/blockstream/test/test_descriptor.java @@ -0,0 +1,44 @@ +package com.blockstream.test; + +import com.blockstream.libwally.Wally; + +public class test_descriptor { + + static final String descriptor_str = "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 int variant = 0; + final Object descriptor = Wally.descriptor_parse(descriptor_str, Wally.map_init(0), + Wally.WALLY_NETWORK_BITCOIN_MAINNET, + flags); + final String[] addrs = Wally.descriptor_to_addresses(descriptor, variant, child_num, + 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.descriptor_free(descriptor); + 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 5fa9e763e..b40a8bd39 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" @@ -114,6 +121,19 @@ static jbyteArray create_jintArray(JNIEnv *jenv, const uint32_t* p, size_t len) } #endif +static jobjectArray create_jstringArray(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) %} @@ -183,6 +203,23 @@ static jbyteArray create_jintArray(JNIEnv *jenv, const uint32_t* 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_jstringArray(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); @@ -369,11 +406,14 @@ static jbyteArray create_jintArray(JNIEnv *jenv, const uint32_t* 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 -%define %returns_arrayt(FUNC, ARRAYARG, LENARG, LEN, ARRAYTYPE, CTYPE) -%return_decls(FUNC, byte[], ARRAYTYPE) +%define %returns_arrayt(FUNC, ARRAYARG, LENARG, LEN, RETTYPE, ARRAYTYPE, CTYPE) +%return_decls(FUNC, RETTYPE[], ARRAYTYPE) %exception FUNC { int skip = 0; jresult = NULL; @@ -393,7 +433,7 @@ static jbyteArray create_jintArray(JNIEnv *jenv, const uint32_t* p, size_t len) } %enddef %define %returns_array_(FUNC, ARRAYARG, LENARG, LEN) -%returns_arrayt(FUNC, ARRAYARG, LENARG, LEN, jbyteArray, unsigned char) +%returns_arrayt(FUNC, ARRAYARG, LENARG, LEN, byte, jbyteArray, unsigned char) %enddef %define %returns_array_check_flag(FUNC, ARRAYARG, LENARG, FLAGSARG, FLAG, LEN_SET, LEN_UNSET) %returns_array_(FUNC, ARRAYARG, LENARG, (FLAGSARG & FLAG) ? LEN_SET : LEN_UNSET) @@ -408,6 +448,7 @@ static jbyteArray create_jintArray(JNIEnv *jenv, const uint32_t* p, size_t len) %java_opaque_struct(wally_tx, 6); %java_opaque_struct(wally_map, 7); %java_opaque_struct(wally_psbt, 8); +%java_opaque_struct(wally_descriptor, 9); /* Our wrapped functions return types */ %returns_void__(bip32_key_free); @@ -497,6 +538,15 @@ static jbyteArray create_jintArray(JNIEnv *jenv, const uint32_t* 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_descriptor_get_features); +%returns_void__(wally_descriptor_free); +%returns_struct(wally_descriptor_parse, wally_descriptor); +%returns_string(wally_descriptor_to_address); +%returns_sarray(wally_descriptor_to_addresses); +%returns_size_t(wally_descriptor_to_script); +%returns_size_t(wally_descriptor_to_script_get_maximum_length); %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); @@ -1234,6 +1284,7 @@ static jbyteArray create_jintArray(JNIEnv *jenv, const uint32_t* 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..ecad0fb80 --- /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_str = "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))#t2zpj2eu" + child_num = 0 + flags = 0 + expected = [ + 'bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c', + 'bc1qp6rfclasvmwys7w7j4svgc2mrujq9m73s5shpw4e799hwkdcqlcsj464fw', + 'bc1qsflxzyj2f2evshspl9n5n745swcvs5k7p5t8qdww5unxpjwdvw5qx53ms4' + ] + descriptor = descriptor_parse(descriptor_str, None, WALLY_NETWORK_BITCOIN_MAINNET, flags) + addrs = descriptor_to_addresses(descriptor, 0, child_num, 0, len(expected)) + self.assertEqual(addrs, expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/swig_python/python_extra.py_in b/src/swig_python/python_extra.py_in index 1647aa4ae..3d79f4cad 100644 --- a/src/swig_python/python_extra.py_in +++ b/src/swig_python/python_extra.py_in @@ -202,6 +202,7 @@ psbt_get_input_signature_hash = _wrap_bin(psbt_get_input_signature_hash, SHA256_ psbt_to_bytes = _wrap_bin(psbt_to_bytes, psbt_get_length) psbt_clone = psbt_clone_alloc psbt_blind = psbt_blind_alloc +descriptor_to_script = _wrap_bin(descriptor_to_script, descriptor_to_script_get_maximum_length, resize=True) symmetric_key_from_seed = _wrap_bin(symmetric_key_from_seed, HMAC_SHA512_LEN) symmetric_key_from_parent = _wrap_bin(symmetric_key_from_parent, HMAC_SHA512_LEN) tx_witness_stack_init = tx_witness_stack_init_alloc diff --git a/src/swig_python/swig.i b/src/swig_python/swig.i index df681b0ac..9187b056b 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()) @@ -87,6 +90,7 @@ static bool ulonglong_cast(PyObject *item, unsigned long long *val) if (p) fn(p); } capsule_dtor(ext_key, bip32_key_free) +capsule_dtor(wally_descriptor, wally_descriptor_free) capsule_dtor(wally_psbt, wally_psbt_free) capsule_dtor(wally_tx, wally_tx_free) capsule_dtor(wally_tx_input, wally_tx_input_free) @@ -208,6 +212,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) { @@ -390,6 +416,7 @@ static void destroy_words(PyObject *obj) { (void)obj; } %py_int_array_out(uint32_t, 0xffffffffull, child_path_out, child_path_out_len) %py_opaque_struct(ext_key); +%py_opaque_struct(wally_descriptor); %py_opaque_struct(wally_psbt); %py_opaque_struct(wally_tx); %py_opaque_struct(wally_tx_input); @@ -408,10 +435,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..232049db8 --- /dev/null +++ b/src/test/test_descriptor.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +import unittest +from util import * + + +NETWORK_NONE = 0x00 +NETWORK_BTC_MAIN = 0x01 +NETWORK_BTC_TEST = 0x02 +NETWORK_BTC_REG = 0xff +NETWORK_LIQUID = 0x03 +NETWORK_LIQUID_REG = 0x04 + +MS_TAP = 0x1 # WALLY_MINISCRIPT_TAPSCRIPT +MS_ONLY = 0x2 # WALLY_MINISCRIPT_ONLY + +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: + d = c_void_p() + ret = wally_descriptor_parse(miniscript, keys, NETWORK_NONE, MS_ONLY, d) + self.assertEqual(ret, WALLY_OK) + ret, written = wally_descriptor_to_script(d, 0, 0, 0, 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_descriptor_free(d) + wally_map_free(keys) + + # Invalid args + bad_args = [ + (None, 0, MS_ONLY, script, script_len), # NULL miniscript + (args[0][0], 0x80000000, MS_ONLY, script, script_len), # Hardened child + (args[0][0], 0, MS_ONLY, None, script_len), # NULL output + (args[0][0], 0, MS_ONLY, script, 0), # Empty output + ] + for miniscript, child_num, flags, bytes_out, bytes_len in bad_args: + d = c_void_p() + ret = wally_descriptor_parse(miniscript, None, NETWORK_NONE, flags, d) + if ret == WALLY_OK: + ret, written = wally_descriptor_to_script(d, 0, 0, 0, child_num, + flags, bytes_out, bytes_len) + self.assertEqual(written, 0) + wally_descriptor_free(d) + self.assertEqual(ret, WALLY_EINVAL) + + def test_descriptor_to_script(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: + d = c_void_p() + ret = wally_descriptor_parse(descriptor, None, network, 0, d) + self.assertEqual(ret, WALLY_OK) + ret, written = wally_descriptor_to_script(d, 0, 0, 0, child_num, + 0, script, script_len) + self.assertEqual((ret, written), (WALLY_OK, len(expected) // 2)) + self.assertEqual(script[:written], make_cbuffer(expected)[0]) + wally_descriptor_free(d) + + # 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: + d = c_void_p() + ret = wally_descriptor_parse(descriptor, None, network, flags, d) + if ret == WALLY_OK: + ret, written = wally_descriptor_to_script(d, depth, idx, 0, child_num, + flags, bytes_out, bytes_len) + self.assertEqual(written, 0) + wally_descriptor_free(d) + self.assertEqual(ret, WALLY_EINVAL) + + def test_descriptor_to_addresses(self): + addrs_len = 64 + addrs = (c_char_p * addrs_len)() + + # Valid args + args = [ + ('wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)', + 0, NETWORK_BTC_TEST, [ + 'tb1q0ht9tyks4vh7p5p904t340cr9nvahy7um9zdem' + ]), + ('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: + d = c_void_p() + ret = wally_descriptor_parse(descriptor, None, network, 0, d) + self.assertEqual(ret, WALLY_OK) + ret = wally_descriptor_to_addresses(d, 0, child_num, + 0, addrs, len(expected)) + self.assertEqual(ret, WALLY_OK) + for i in range(len(expected)): + self.assertEqual(expected[i].encode('utf-8'), addrs[i]) + wally_descriptor_free(d) + + # 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: + d = c_void_p() + ret = wally_descriptor_parse(descriptor, None, network, flags, d) + if ret == WALLY_OK: + ret = wally_descriptor_to_addresses(d, 0, child_num, + 0, out, out_len) + wally_descriptor_free(d) + 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'), + ]: + d = c_void_p() + ret = wally_descriptor_parse(descriptor, None, NETWORK_NONE, 0, d) + ret, checksum = wally_descriptor_get_checksum(d, 0) + self.assertEqual((ret, checksum), (WALLY_OK, expected)) + wally_descriptor_free(d) + + def test_canonicalize_checksum_bad_args(self): + """Test bad arguments to canonicalize and checksum functions""" + descriptor = 'sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))' + d = c_void_p() + ret = wally_descriptor_parse(descriptor, None, NETWORK_NONE, 0, d) + bad_args = [ + (None, 0), # NULL descriptor + (d, 1), # Bad flags + ] + + for fn in (wally_descriptor_canonicalize, wally_descriptor_get_checksum): + for descriptor, flags in bad_args: + ret, out = fn(descriptor, 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 5e79ab937..fc42f27c4 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -295,6 +295,15 @@ 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_void_p, c_uint32, c_char_p_p]), + ('wally_descriptor_free', c_int, [c_void_p]), + ('wally_descriptor_get_checksum', c_int, [c_void_p, c_uint32, c_char_p_p]), + ('wally_descriptor_get_features', c_int, [c_void_p, c_uint_p]), + ('wally_descriptor_parse', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_uint32, POINTER(c_void_p)]), + ('wally_descriptor_to_address', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_char_p_p]), + ('wally_descriptor_to_addresses', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, POINTER(c_char_p), c_size_t]), + ('wally_descriptor_to_script', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, c_size_t, c_size_t_p]), + ('wally_descriptor_to_script_get_maximum_length', c_int, [c_void_p, c_uint32, 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]), diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index 2159c4c92..bd0efd6b0 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -104,11 +104,17 @@ export const WALLY_ENOMEM = -3; /** malloc() failed */ export const WALLY_ERROR = -1; /** General error */ export const WALLY_HOST_COMMITMENT_LEN = 32; export const WALLY_MAX_OP_RETURN_LEN = 80; /* Maximum length of OP_RETURN data push */ +export const WALLY_MINISCRIPT_ONLY = 0x02; /** Only allow miniscript (not descriptor) expressions */ +export const WALLY_MINISCRIPT_TAPSCRIPT = 0x01; /** Tapscript */ +export const WALLY_MINISCRIPT_WITNESS_SCRIPT = 0x00; /** Witness script */ +export const WALLY_MS_IS_RANGED = 0x01; /** Allows key ranges via '*' */ export const WALLY_NETWORK_BITCOIN_MAINNET = 0x01; /** Bitcoin mainnet */ +export const WALLY_NETWORK_BITCOIN_REGTEST = 0xff ; /** Bitcoin regtest: Behaves as testnet except for segwit */ export const WALLY_NETWORK_BITCOIN_TESTNET = 0x02; /** Bitcoin testnet */ export const WALLY_NETWORK_LIQUID = 0x03; /** Liquid v1 */ export const WALLY_NETWORK_LIQUID_REGTEST = 0x04; /** Liquid v1 regtest */ export const WALLY_NETWORK_LIQUID_TESTNET = 0x05; /** Liquid v1 testnet */ +export const WALLY_NETWORK_NONE = 0x00; /** Used for miniscript parsing only */ export const WALLY_NO_CODESEPARATOR = 0xffffffff; /* No BIP342 code separator position */ export const WALLY_OK = 0; /** Success */ export const WALLY_PSBT_EXTRACT_NON_FINAL = 0x1; /* Extract without final scriptsig and witness */ diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index c43ec7141..bddbb86c7 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -157,6 +157,14 @@ export const confidential_addr_segwit_to_ec_public_key = wrap('wally_confidentia export const confidential_addr_to_addr = wrap('wally_confidential_addr_to_addr', [T.String, T.Int32, T.DestPtrPtr(T.String)]); export const confidential_addr_to_addr_segwit = wrap('wally_confidential_addr_to_addr_segwit', [T.String, T.String, T.String, T.DestPtrPtr(T.String)]); export const confidential_addr_to_ec_public_key = wrap('wally_confidential_addr_to_ec_public_key', [T.String, T.Int32, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); +export const descriptor_canonicalize = wrap('wally_descriptor_canonicalize', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); +export const descriptor_free = wrap('wally_descriptor_free', [T.OpaqueRef]); +export const descriptor_get_checksum = wrap('wally_descriptor_get_checksum', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); +export const descriptor_get_features = wrap('wally_descriptor_get_features', [T.OpaqueRef, T.DestPtr(T.Int32)]); +export const descriptor_parse = wrap('wally_descriptor_parse', [T.String, T.OpaqueRef, T.Int32, T.Int32, T.DestPtrPtr(T.OpaqueRef)]); +export const descriptor_to_address = wrap('wally_descriptor_to_address', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.DestPtrPtr(T.String)]); +export const descriptor_to_addresses = wrap('wally_descriptor_to_addresses', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.DestPtrSized(T.String, T.USER_PROVIDED_LEN)]); +export const descriptor_to_script_get_maximum_length = wrap('wally_descriptor_to_script_get_maximum_length', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const ec_private_key_verify = wrap('wally_ec_private_key_verify', [T.Bytes]); export const ec_public_key_decompress = wrap('wally_ec_public_key_decompress', [T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_UNCOMPRESSED_LEN)]); export const ec_public_key_from_private_key = wrap('wally_ec_public_key_from_private_key', [T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); @@ -727,6 +735,7 @@ export const asset_surjectionproof = wrap('wally_asset_surjectionproof', [T.Byte export const base58_n_to_bytes = wrap('wally_base58_n_to_bytes', [T.String, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, base58_n_to_bytes_len, true)]); export const base58_to_bytes = wrap('wally_base58_to_bytes', [T.String, T.Int32, T.DestPtrVarLen(T.Bytes, base58_to_bytes_len, true)]); export const base64_to_bytes = wrap('wally_base64_to_bytes', [T.String, T.Int32, T.DestPtrVarLen(T.Bytes, base64_get_maximum_length, true)]); +export const descriptor_to_script = wrap('wally_descriptor_to_script', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, descriptor_to_script_get_maximum_length, true)]); export const ec_sig_from_bytes = wrap('wally_ec_sig_from_bytes', [T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, ec_sig_from_bytes_len, false)]); export const keypath_get_path = wrap('wally_keypath_get_path', [T.Bytes, T.DestPtrVarLen(T.Uint32Array, keypath_get_path_len, false)]); export const map_get_item = wrap('wally_map_get_item', [T.OpaqueRef, T.Int32, T.DestPtrVarLen(T.Bytes, map_get_item_length, false)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index a8bf3df98..8794026fc 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -107,6 +107,14 @@ export function confidential_addr_segwit_to_ec_public_key(address: string, confi export function confidential_addr_to_addr(address: string, prefix: number): string; export function confidential_addr_to_addr_segwit(address: string, confidential_addr_family: string, addr_family: string): string; export function confidential_addr_to_ec_public_key(address: string, prefix: number): Buffer; +export function descriptor_canonicalize(descriptor: Ref_wally_descriptor, flags: number): string; +export function descriptor_free(descriptor: Ref_wally_descriptor): void; +export function descriptor_get_checksum(descriptor: Ref_wally_descriptor, flags: number): string; +export function descriptor_get_features(descriptor: Ref_wally_descriptor): number; +export function descriptor_parse(descriptor: string, vars_in: Ref_wally_map, network: number, flags: number): Ref_wally_descriptor; +export function descriptor_to_address(descriptor: Ref_wally_descriptor, variant: number, child_num: number, flags: number): string; +export function descriptor_to_addresses(descriptor: Ref_wally_descriptor, variant: number, child_num: number, flags: number, out_len: number): string; +export function descriptor_to_script_get_maximum_length(descriptor: Ref_wally_descriptor, flags: number): number; export function ec_private_key_verify(priv_key: Buffer|Uint8Array): void; export function ec_public_key_decompress(pub_key: Buffer|Uint8Array): Buffer; export function ec_public_key_from_private_key(priv_key: Buffer|Uint8Array): Buffer; @@ -677,6 +685,7 @@ export function asset_surjectionproof(output_asset: Buffer|Uint8Array, output_ab export function base58_n_to_bytes(str_in: string, str_len: number, flags: number): Buffer; export function base58_to_bytes(str_in: string, flags: number): Buffer; export function base64_to_bytes(str_in: string, flags: number): Buffer; +export function descriptor_to_script(descriptor: Ref_wally_descriptor, depth: number, index: number, variant: number, child_num: number, flags: number): Buffer; export function ec_sig_from_bytes(priv_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): Buffer; export function keypath_get_path(val: Buffer|Uint8Array): Uint32Array; export function map_get_item(map_in: Ref_wally_map, index: number): Buffer; diff --git a/tools/build_wrappers.py b/tools/build_wrappers.py index 59b2d73fa..f7e134fe1 100755 --- a/tools/build_wrappers.py +++ b/tools/build_wrappers.py @@ -4,7 +4,7 @@ import sys # Structs with no definition in the public header files -OPAQUE_STRUCTS = [u'words'] +OPAQUE_STRUCTS = [u'words', u'wally_descriptor'] EXCLUDED_FUNCS = { # Callers should use the fixed length bip39_mnemonic_to_seed512 @@ -180,6 +180,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: @@ -402,6 +404,7 @@ def gen_wasm_package(funcs, all_funcs): # Output arrays # map of C type -> (JS type, TypeScript return type) typemap_output_arrays = { + 'char**': ('T.String', 'string'), 'unsigned char*': ('T.Bytes', 'Buffer'), 'uint32_t*': ('T.Uint32Array', 'Uint32Array'), } @@ -438,7 +441,7 @@ def map_args(func): # Output pointer to an array if is_array(func, arg, curr_index, num_args, typemap_output_arrays.keys()): # Sanity check to make sure we don't misidentify unrelated arguments - assert arg.name.endswith("_out") or arg.name == 'scalar' + assert arg.name.endswith("_out") or arg.name in ('scalar', 'output') (array_type, ts_return_type) = typemap_output_arrays[arg.type] @@ -454,9 +457,8 @@ def map_args(func): elif func.buffer_len_fn: len_fn = get_export_name(func.buffer_len_fn, all_funcs) output_buffer_size = f"{len_fn}, {'true' if func.buffer_len_is_upper_bound else 'false'}" - elif func.name == 'wally_scrypt': - # Special-case for wally_scrypt(), the only function with an output buffer where - # the user provides the intended output length as an argument to the JS API. + elif func.name in ('wally_scrypt', 'wally_descriptor_to_addresses'): + # User provides the intended output length as an argument to the JS API. output_buffer_size = 'T.USER_PROVIDED_LEN' else: assert False, f'ERROR: Unknown output array size for {func.name}:{arg.type}' @@ -555,15 +557,18 @@ def get_function_defs(non_elements, internal_only): add_meta = False # Auto-detect output buffer length function based on the following naming conventions: - # - funcname -> funcname_len / funcname_length + # - funcname -> funcname_len / funcname_length / funcname_get_maximum_length # - funcname_to_bytes -> funcname_get_length - # - funcname_to_bytes -> funcname_get_maximum_length (implies is_upper_bound=true) - buffer_len_fns = set([f.name for f in funcs if f.name.endswith('_len') or f.name.endswith('_length')]) + # - funcname_to_bytes -> funcname_get_maximum_length + # - _get_maximum_length implies is_upper_bound=true + def is_len_fn(f): + return f.endswith('_len') or f.endswith('_length') + buffer_len_fns = set([f.name for f in funcs if is_len_fn(f.name)]) for f in funcs: if f.name.endswith('_to_bytes'): possible_names = [ f.name[0:-9]+'_get_length', f.name[0:-9]+'_get_maximum_length' ] else: - possible_names = [ f.name + '_len', f.name + '_length' ] + possible_names = [ f.name + '_len', f.name + '_length', f.name + '_get_maximum_length' ] for name in possible_names: if name in buffer_len_fns: diff --git a/tools/cleanup.sh b/tools/cleanup.sh index 2d6f52bee..5048de591 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 a0f2171d0..28c80472f 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\amalgamation\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\amalgamation\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 955e77b10..79652531e 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -70,6 +70,15 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_bip340_tagged_hash' \ ,'_wally_bzero' \ ,'_wally_cleanup' \ +,'_wally_descriptor_canonicalize' \ +,'_wally_descriptor_free' \ +,'_wally_descriptor_get_checksum' \ +,'_wally_descriptor_get_features' \ +,'_wally_descriptor_parse' \ +,'_wally_descriptor_to_address' \ +,'_wally_descriptor_to_addresses' \ +,'_wally_descriptor_to_script' \ +,'_wally_descriptor_to_script_get_maximum_length' \ ,'_wally_ec_private_key_verify' \ ,'_wally_ec_public_key_decompress' \ ,'_wally_ec_public_key_from_private_key' \