diff --git a/include/wally.hpp b/include/wally.hpp index 1dd7adbc4..f14e966e7 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -534,6 +534,18 @@ inline int descriptor_canonicalize(const DESCRIPTOR& descriptor, uint32_t flags, return detail::check_ret(__FUNCTION__, ret); } +template +inline int descriptor_derive_bip32_key(const DESCRIPTOR& descriptor, size_t index, uint32_t variant, uint32_t multi_index, uint32_t child_num, uint32_t flags, struct ext_key* output) { + int ret = ::wally_descriptor_derive_bip32_key(detail::get_p(descriptor), index, variant, multi_index, child_num, flags, output); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_derive_bip32_key_alloc(const DESCRIPTOR& descriptor, size_t index, uint32_t variant, uint32_t multi_index, uint32_t child_num, uint32_t flags, struct ext_key** output) { + int ret = ::wally_descriptor_derive_bip32_key_alloc(detail::get_p(descriptor), index, variant, multi_index, child_num, flags, output); + return detail::check_ret(__FUNCTION__, ret); +} + inline int descriptor_free(struct wally_descriptor* descriptor) { int ret = ::wally_descriptor_free(descriptor); return detail::check_ret(__FUNCTION__, ret); @@ -623,6 +635,60 @@ inline int descriptor_get_num_variants(const DESCRIPTOR& descriptor, uint32_t* v return detail::check_ret(__FUNCTION__, ret); } +template +inline int descriptor_get_taproot_control_block(const DESCRIPTOR& descriptor, uint32_t leaf_index, uint32_t multi_index, uint32_t child_num, uint32_t flags, BYTES_OUT& bytes_out, size_t* written) { + int ret = ::wally_descriptor_get_taproot_control_block(detail::get_p(descriptor), leaf_index, multi_index, child_num, flags, bytes_out.data(), bytes_out.size(), written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_control_block_len(const DESCRIPTOR& descriptor, uint32_t leaf_index, uint32_t multi_index, uint32_t child_num, uint32_t flags, size_t* written) { + int ret = ::wally_descriptor_get_taproot_control_block_len(detail::get_p(descriptor), leaf_index, multi_index, child_num, flags, written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_leaf_hash(const DESCRIPTOR& descriptor, uint32_t leaf_index, uint32_t multi_index, uint32_t child_num, uint32_t flags, BYTES_OUT& bytes_out) { + int ret = ::wally_descriptor_get_taproot_leaf_hash(detail::get_p(descriptor), leaf_index, multi_index, child_num, flags, bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_leaf_key_index(const DESCRIPTOR& descriptor, uint32_t leaf_index, uint32_t key_index, uint32_t* value_out) { + int ret = ::wally_descriptor_get_taproot_leaf_key_index(detail::get_p(descriptor), leaf_index, key_index, value_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_leaf_num_keys(const DESCRIPTOR& descriptor, uint32_t leaf_index, uint32_t* value_out) { + int ret = ::wally_descriptor_get_taproot_leaf_num_keys(detail::get_p(descriptor), leaf_index, value_out); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_leaf_script(const DESCRIPTOR& descriptor, uint32_t leaf_index, uint32_t multi_index, uint32_t child_num, uint32_t flags, BYTES_OUT& bytes_out, size_t* written) { + int ret = ::wally_descriptor_get_taproot_leaf_script(detail::get_p(descriptor), leaf_index, multi_index, child_num, flags, bytes_out.data(), bytes_out.size(), written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_leaf_script_len(const DESCRIPTOR& descriptor, uint32_t leaf_index, uint32_t multi_index, uint32_t child_num, uint32_t flags, size_t* written) { + int ret = ::wally_descriptor_get_taproot_leaf_script_len(detail::get_p(descriptor), leaf_index, multi_index, child_num, flags, written); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_merkle_root(const DESCRIPTOR& descriptor, uint32_t multi_index, uint32_t child_num, uint32_t flags, BYTES_OUT& bytes_out) { + int ret = ::wally_descriptor_get_taproot_merkle_root(detail::get_p(descriptor), multi_index, child_num, flags, bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int descriptor_get_taproot_num_leaves(const DESCRIPTOR& descriptor, uint32_t* value_out) { + int ret = ::wally_descriptor_get_taproot_num_leaves(detail::get_p(descriptor), value_out); + return detail::check_ret(__FUNCTION__, 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); diff --git a/include/wally_descriptor.h b/include/wally_descriptor.h index 7ab484174..6ab85bb9c 100644 --- a/include/wally_descriptor.h +++ b/include/wally_descriptor.h @@ -7,6 +7,7 @@ extern "C" { #endif +struct ext_key; struct wally_map; /** An opaque type holding a parsed minscript/descriptor expression */ struct wally_descriptor; @@ -22,25 +23,29 @@ struct wally_descriptor; #define WALLY_MINISCRIPT_DEPTH_SHIFT 16 /** Shift to convert maximum depth to flags */ /*** miniscript-features Miniscript/Descriptor feature flags */ -#define WALLY_MS_IS_RANGED 0x001 /** Allows key ranges via ``*`` */ -#define WALLY_MS_IS_MULTIPATH 0x002 /** Allows multiple paths via ```` */ -#define WALLY_MS_IS_PRIVATE 0x004 /** Contains at least one private key */ -#define WALLY_MS_IS_UNCOMPRESSED 0x008 /** Contains at least one uncompressed key */ -#define WALLY_MS_IS_RAW 0x010 /** Contains at least one raw key */ -#define WALLY_MS_IS_DESCRIPTOR 0x020 /** Contains only descriptor expressions (no miniscript) */ -#define WALLY_MS_IS_X_ONLY 0x040 /** Contains at least one x-only key */ -#define WALLY_MS_IS_PARENTED 0x080 /** Contains at least one key key with a parent key origin */ -#define WALLY_MS_IS_ELEMENTS 0x100 /** Contains Elements expressions or was parsed as Elements */ -#define WALLY_MS_IS_SLIP77 0x200 /** A confidential ct() descriptor with SLIP-77 blinding */ -#define WALLY_MS_IS_ELIP150 0x400 /** A confidential ct() descriptor with ELIP-150 blinding */ -#define WALLY_MS_IS_ELIP151 0x800 /** A confidential ct() descriptor with ELIP-151 blinding */ -#define WALLY_MS_ANY_BLINDING_KEY 0xE00 /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ +#define WALLY_MS_IS_RANGED 0x0001 /** Allows key ranges via ``*`` */ +#define WALLY_MS_IS_MULTIPATH 0x0002 /** Allows multiple paths via ```` */ +#define WALLY_MS_IS_PRIVATE 0x0004 /** Contains at least one private key */ +#define WALLY_MS_IS_UNCOMPRESSED 0x0008 /** Contains at least one uncompressed key */ +#define WALLY_MS_IS_RAW 0x0010 /** Contains at least one raw key */ +#define WALLY_MS_IS_DESCRIPTOR 0x0020 /** Contains only descriptor expressions (no miniscript) */ +#define WALLY_MS_IS_X_ONLY 0x0040 /** Contains at least one x-only key */ +#define WALLY_MS_IS_PARENTED 0x0080 /** Contains at least one key with a parent key origin */ +#define WALLY_MS_IS_ELEMENTS 0x0100 /** Contains Elements expressions or was parsed as Elements */ +#define WALLY_MS_IS_SLIP77 0x0200 /** A confidential ct() descriptor with SLIP-77 blinding */ +#define WALLY_MS_IS_ELIP150 0x0400 /** A confidential ct() descriptor with ELIP-150 blinding */ +#define WALLY_MS_IS_ELIP151 0x0800 /** A confidential ct() descriptor with ELIP-151 blinding */ +#define WALLY_MS_IS_TAPROOT 0x1000 /** Contains a tr() taproot expression */ +#define WALLY_MS_IS_TAPSCRIPT 0x2000 /** Contains a tr(key,{...}) tapscript tree expression */ +#define WALLY_MS_ANY_BLINDING_KEY 0x0E00 /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ /*** ms-canonicalization-flags Miniscript/Descriptor canonicalization flags */ #define WALLY_MS_CANONICAL_NO_CHECKSUM 0x01 /** Do not include a checksum */ #define WALLY_MS_BLINDING_KEY_INDEX 0xffffffff /* Key index for confidential blinding key */ +#define WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH 128 /* BIP-341: maximum taptree depth */ + /** * Parse an output descriptor or miniscript expression. * @@ -306,6 +311,49 @@ WALLY_CORE_API int wally_descriptor_get_key_origin_path_str( size_t index, char **output); + +/** + * Derive a BIP32 extended key from a parsed output descriptor or miniscript expression. + * + * :param descriptor: Parsed output descriptor or miniscript expression. + * :param index: The zero-based index of the key to get, or `WALLY_MS_BLINDING_KEY_INDEX` + *| to fetch the descriptors blinding key representaton (if any). + * :param variant: The variant of descriptor to derive from. See `wally_descriptor_get_num_variants`. + * :param multi_index: The multi-path item to derive from. See `wally_descriptor_get_num_paths`. + * :param child_num: The BIP32 child number to derive, or 0 for static descriptors. + * :param flags: Use `BIP32_FLAG_KEY_PUBLIC` to return public keys from private keys, + *| and `BIP32_FLAG_SKIP_HASH` to avoid populating the derived key fingerprint. + * :param output: Destination for the resulting derived key. + * + * .. note:: The returned key may be bare (have only the public or private key populated). + *| x-only bare keys have a prefix byte of 0x00. The caller can use `wally_descriptor_get_key_features` to + *| determine the type of a given key before extracting data from it. + * + * .. note:: SLIP77 blinding keys are returned in the private key of the extended key. + */ +WALLY_CORE_API int wally_descriptor_derive_bip32_key( + const struct wally_descriptor *descriptor, + size_t index, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + struct ext_key *output); + +/** + * Derive a BIP32 extended key from a parsed output descriptor or miniscript expression. + * + * See `wally_descriptor_derive_bip32_key`. + */ +WALLY_CORE_API int wally_descriptor_derive_bip32_key_alloc( + const struct wally_descriptor *descriptor, + size_t index, + uint32_t variant, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + struct ext_key **output); + /** * Get the maximum length of a script corresponding to an output descriptor. * @@ -406,6 +454,170 @@ WALLY_CORE_API int wally_descriptor_to_addresses( char **output, size_t num_outputs); +/** + * Get the number of taptree leaves in a taproot output descriptor. + * + * Returns WALLY_EINVAL if the descriptor is not taproot. + * + * :param descriptor: Parsed tr() output descriptor. + * :param value_out: Destination for the number of taptree leaves. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_num_leaves( + const struct wally_descriptor *descriptor, + uint32_t *value_out); + +/** + * Get the script for a specific taptree leaf in a taproot output descriptor. + * + * Returns WALLY_EINVAL if the descriptor is not taproot or `leaf_index` + * is out of bounds. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the compiled tapscript. + * :param len: Length of ``bytes_out`` in bytes. + * :param written: Destination for the number of bytes written to ``bytes_out``. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_script( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + +/** + * Get the script length for a specific taptree leaf in a taproot output descriptor. + * + * See `wally_descriptor_get_taproot_leaf_script`. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_script_len( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + size_t *written); + +/** + * Get the tapleaf hash for a specific taptree leaf in a taproot output descriptor. + * + * Returns WALLY_EINVAL if the descriptor is not taproot or `leaf_index` + * is out of bounds. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the 32-byte tapleaf hash. + * FIXED_SIZED_OUTPUT(len, bytes_out, SHA256_LEN) + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_hash( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len); + +/** + * Get the BIP-341 control block for spending via a specific taptree leaf. + * + * Returns WALLY_EINVAL if the descriptor is not taproot or `leaf_index` + * is out of bounds. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the control block bytes. + * :param len: Length of ``bytes_out`` in bytes. + * :param written: Destination for the number of bytes written to ``bytes_out``. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_control_block( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + +/** + * Get the BIP-341 control block for spending via a specific taptree leaf. + * + * See `wally_descriptor_get_taproot_control_block`. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_control_block_len( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + size_t *written); + +/** + * Get the number of keys in a specific taptree leaf's miniscript. + * + * Returns WALLY_EINVAL if the descriptor is not taproot or `leaf_index` + * is out of bounds. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param value_out: Destination for the key count. + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_num_keys( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t *value_out); + +/** + * Get the descriptor-level key index for a key within a specific taptree leaf. + * + * Returns WALLY_EINVAL if the descriptor is not taproot or `leaf_index` + * is out of bounds. + * + * :param descriptor: Parsed tr() output descriptor. + * :param leaf_index: Zero-based leaf index (depth-first, left-to-right order). + * :param key_index: Zero-based index of the key within the leaf's miniscript. + * :param value_out: Destination for the descriptor-level key index + *| (suitable for use with `wally_descriptor_get_key`). + */ +WALLY_CORE_API int wally_descriptor_get_taproot_leaf_key_index( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t key_index, + uint32_t *value_out); + +/** + * Get the merkle root of the taptree in a tr() descriptor. + * + * Returns WALLY_EINVAL if the descriptor does not contain a taptree. + * + * :param descriptor: Parsed tr() output descriptor. + * :param multi_index: See `wally_descriptor_get_num_paths`. + * :param child_num: BIP32 child number, or 0 for static descriptors. + * :param flags: For future use. Must be 0. + * :param bytes_out: Destination for the 32-byte merkle root. + * FIXED_SIZED_OUTPUT(len, bytes_out, SHA256_LEN) + */ +WALLY_CORE_API int wally_descriptor_get_taproot_merkle_root( + const struct wally_descriptor *descriptor, + uint32_t multi_index, + uint32_t child_num, + uint32_t flags, + unsigned char *bytes_out, + size_t len); + #ifdef __cplusplus } #endif diff --git a/include/wally_script.h b/include/wally_script.h index d032ef36a..563ff97ce 100644 --- a/include/wally_script.h +++ b/include/wally_script.h @@ -27,6 +27,8 @@ extern "C" { #define WALLY_SCRIPTPUBKEY_P2WSH_LEN 34 /** OP_0 [SHA256] */ #define WALLY_SCRIPTPUBKEY_P2TR_LEN 34 /** OP_1 [X-ONLY-PUBKEY] */ +#define WALLY_LEAF_VERSION_TAPSCRIPT 0xc0 /** BIP-342 tapscript leaf version */ + #define WALLY_SCRIPTPUBKEY_OP_RETURN_MAX_LEN 83 /** OP_RETURN [80 bytes of data] */ #define WALLY_MAX_OP_RETURN_LEN 80 /* Maximum length of OP_RETURN data push */ @@ -171,6 +173,8 @@ extern "C" { #define OP_NOP9 0xb8 #define OP_NOP10 0xb9 +#define OP_CHECKSIGADD 0xba /* BIP-342 tapscript */ + #define OP_INVALIDOPCODE 0xff #endif /* WALLY_DISABLE_OP_CODE */ diff --git a/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c index 34457e3f1..6f480d223 100644 --- a/src/ctest/test_descriptor.c +++ b/src/ctest/test_descriptor.c @@ -47,7 +47,8 @@ static struct wally_map_item g_key_map_items[] = { { B("non_x_only"), B("03b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e") }, /* The taproot singlesig xpriv corresponding to Jades test_jade.py test script */ { B("jade_ss_tr_xpriv"), B("tprv8gTfWnFCND72oJZfZTokBBXcS1FzQhrtd5wNFu3FgBE76yErH49cev2Zn3Wws3o6ZwKZVZaQP1UWKVNotpPg8U6tCgGrjMfaRQJvV1Vdbi7") }, - { B("slip77_key"), B("b2396b3ee20509cdb64fe24180a14a72dbd671728eaa49bac69d2bdecb5f5a04") } + { B("slip77_key"), B("b2396b3ee20509cdb64fe24180a14a72dbd671728eaa49bac69d2bdecb5f5a04") }, + { B("bip341_vec1"), B("d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d") } }; static struct wally_map_item g_policy_map_items[] = { @@ -111,7 +112,7 @@ static bool check_varbuff(const char *function, const unsigned char *src, size_t #define DEPTH_TEST_DESCRIPTOR "sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))" -static const struct descriptor_test { +typedef struct { const char *name; const char *descriptor; const uint32_t network; @@ -123,7 +124,9 @@ static const struct descriptor_test { const char *script; const char *checksum; int policy_map_index; -} g_descriptor_cases[] = { +} descriptor_test; + +static const descriptor_test g_descriptor_cases[] = { /* * Output descriptors */ @@ -414,6 +417,42 @@ static const struct descriptor_test { WALLY_NETWORK_BITCOIN_REGTEST, 0, 0, 0, NULL, 0, "51205fb8e39dbbdc7c831af59e44a9b2997f9daaf72c3e965b30982f3c731539e1db", "tp2ky708", VARS_STD + },{ + "descriptor - tr - bip341 test vector 1", + "tr(bip341_vec1)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + "zd5eym6u", VARS_STD + },{ + "descriptor - tr - single leaf pk", + "tr(x_only,pk(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "5120951b6ab79b75bf3083163e8c4a3df1cba0928e07b3b2e3732503bb7fe6df804b", + "", VARS_STD + },{ + "descriptor - tr - balanced 2-leaf", + "tr(x_only,{pk(key_1),pk(key_2)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "512082a4c5d240cadcf568140691f751370be05e3da59df98c3b1e92a37f1bfd7dfe", + "", VARS_STD + },{ + "descriptor - tr - unbalanced 3-leaf", + "tr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "51201edef6eaf60517b880b7c721436840e45c487f4f7d4b544848a1fa8ecae1a146", + "", VARS_STD + },{ + "descriptor - tr - multi_a leaf", + "tr(x_only,multi_a(2,key_1,key_2,key_3))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "5120e96b74eb71c05f7362ab7977c829d78256685d87fc4e1e44545146466caedd19", + "", VARS_STD + },{ + "descriptor - tr - mixed multi_a and and_v", + "tr(x_only,{multi_a(2,key_1,key_2,key_3),and_v(v:pk(key_1),older(52560))})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "51207b56ea61956475f5751c4da934cd2ac20d3088f327c60ffe249bc7a66b9952b0", + "", VARS_STD }, #ifdef BUILD_ELEMENTS /* Elements/Confidential descriptors */ @@ -980,10 +1019,54 @@ static const struct descriptor_test { "5192", /* 1 OP_0NOTEQUAL */ "d959hk4q", VARS_STD }, + { + "miniscript - pk_k segwit v0", + "c:pk_k(key_1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "", VARS_STD + }, + { + "miniscript - pk_h segwit v0", + "c:pk_h(key_1)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "76a914d0721279e70d39fb4aa409b52839a0056454e3b588ac", + "", VARS_STD + }, { + "miniscript - sha256 segwit v0", + "sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed287", + "", VARS_STD + }, { + "miniscript - hash256 segwit v0", + "hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b87", + "", VARS_STD + }, { + "miniscript - ripemd160 segwit v0", + "ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87", + "", VARS_STD + }, { + "miniscript - hash160 segwit v0", + "hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY, + "82012088a91420195b5a3d650c17f0f29f91c33f8f6335193d0787", + "", VARS_STD + }, /* * Miniscript taproot cases */ { + "miniscript - pk_k tapscript x-only", + "c:pk_k(x_only)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, + "20b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0eac", + "", VARS_STD + }, { "miniscript - taproot raw pubkey", "c:pk_k(daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729)", WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, @@ -1223,7 +1306,7 @@ static const struct descriptor_test { "addr(ex1qwu7hp9vckakyuw6htsy244qxtztrlyez4l7qlrpg68v6drgvj39q06fgz7)", WALLY_NETWORK_LIQUID_REGTEST, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ - "descriptor - multisig too many keys", + "descriptor - multi() 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, "", VARS_STD @@ -1272,7 +1355,7 @@ static const struct descriptor_test { "wsh(pk(key_1)garbage)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ - "descriptor - sh - trailing garbage in nested expression", + "descriptor - sh(wsh) - trailing garbage in nested expression", "sh(wsh(pk(key_1)garbage))", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ @@ -1340,17 +1423,37 @@ static const struct descriptor_test { "multi(1)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ - "descriptor - multi - no number", + "descriptor - multi - no threshold", "multi(022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ - "descriptor - multi - negative number", + "descriptor - multi - zero threshold", + "multi(0,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - multi - negative threshold", "multi(-1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - multi - too-high threshold", + "multi(2,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ "descriptor - multi - non-key child", "multi(1,1)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - multi - invalid pubkey child", + "multi(1,020000000000000000000000000000000000000000000000000000000000000000)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - multi - mixed pubkey children", + "multi(1,key_1,uncompressed)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - multi - x-only pubkey child", + "multi(1,x_only)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ "descriptor - sortedmulti - no args", "sortedmulti", @@ -1432,8 +1535,8 @@ static const struct descriptor_test { "tr()", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ - "descriptor - tr - multi-child", - "tr(x_only,x_only)", /* FIXME: delete this case when script path is supported */ + "descriptor - tr - 3 children", + "tr(x_only,multi_a(2,key_1,key_2),multi_a(2,key_1,key_2))", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ "descriptor - tr - any parent", @@ -1451,6 +1554,70 @@ static const struct descriptor_test { "descriptor - tr - invalid public key", "tr(uncompresseduncompressed)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - multi() fragment not allowed in tapscript", + "tr(x_only,multi(2,key_1,key_2))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - K miniscript not allowed as tapleaf root", + "tr(x_only,pk_k(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - V miniscript not allowed as tapleaf root", + "tr(x_only,v:pk(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - W miniscript not allowed as tapleaf root", + "tr(x_only,a:pk(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - trailing garbage after tapleaf", + "tr(x_only,pk(key_1)garbage)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - trailing garbage after branched tapleaf", + "tr(x_only,{pk(key_1),pk(key_2)garbage})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - single element in braces not allowed", + "tr(x_only,{pk(key_1)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - three elements in braces not allowed", + "tr(x_only,{pk(key_1),pk(key_2),pk(key_3)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - empty braces not allowed", + "tr(x_only,{})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - wsh() inside tr not allowed", + "tr(x_only,wsh(pk(key_1)))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - combo() inside tr not allowed", + "tr(x_only,combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - tr() inside tr not allowed", + "tr(x_only,tr(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - wsh() inside taptree leaf not allowed", + "tr(x_only,{wsh(pk(key_1)),pk(key_2)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - tr - tr() inside taptree leaf not allowed", + "tr(x_only,{tr(key_1),pk(key_2)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - wsh - tr() inside wsh not allowed", + "wsh(and_v(v:pk(key_1),tr(key_2)))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD + },{ + "descriptor - multi_a not allowed outside tapscript context", + "wsh(multi_a(2,key_1,key_2))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "", VARS_STD },{ "descriptor - after - non number child", "wsh(after(key_1))", @@ -1781,6 +1948,195 @@ static const struct descriptor_test { #endif /* BUILD_ELEMENTS */ }; +#define MAX_LEAVES 5 + +typedef struct { + const char *items[MAX_LEAVES]; +} taproot_descriptor_test_keys; + +#define TR_K_NONE { "", "", "", "", "" } +#define TR_K1(k1) { k1, "", "", "", "" } +#define TR_K2(k1, k2) { k1, k2, "", "", "" } +#define TR_K3(k1, k2, k3) { k1, k2, k3, "", "" } +#define TR_K4(k1, k2, k3, k4) { k1, k2, k3, k4, "" } + +typedef struct { + descriptor_test test; + uint32_t depth; + const char *internal_key; + const char *merkle_root; + const char *leaf_scripts[MAX_LEAVES]; + const char *leaf_hashes[MAX_LEAVES]; + const char *control_blocks[MAX_LEAVES]; + taproot_descriptor_test_keys keys[MAX_LEAVES]; +} taproot_descriptor_test; + +static const taproot_descriptor_test g_taproot_cases[] = { + {{ + "taproot - keyspend only", + "tr(x_only)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "51205fb8e39dbbdc7c831af59e44a9b2997f9daaf72c3e965b30982f3c731539e1db", + "", VARS_STD + }, + 2, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "", /* No taptree, so no merkle root */ + { + /* scripts */ + "", "", "", "", "", + }, { + /* hashes */ + "", "", "", "", "", + }, { + /* control blocks */ + "", "", "", "", "", + }, { + /* leaf_keys */ + { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE } + }}, + {{ + "taproot - single leaf pk", + "tr(x_only,pk(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "5120951b6ab79b75bf3083163e8c4a3df1cba0928e07b3b2e3732503bb7fe6df804b", + "", VARS_STD + }, + 3, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1", + { + /* scripts */ + /* = [key_1_xonly] OP_CHECKSIG */ + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", + "", "", "", "", + }, { + /* hashes */ + "815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1", + "", "", "", "", + }, { + /* control blocks */ + /* /LHS: [internal_key] + 1 leaf = 65 bytes (no siblings for single leaf) */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "", "", "", "", + }, { + /* leaf_keys */ + /* leaf 0 key 0 = key_1 (stripped to x-only) */ + { TR_K1("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, + { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE } + }}, + {{ + "taproot - 3 leaf left balanced", + "tr(x_only,{{pk(key_1),pk(key_2)},pk(key_3)})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "5120e557ef32f751b835ac15bf75769802ec65e1df31559aecab23dae39aa4a5c77f", + "", VARS_STD + }, + 5, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "65fc067c1d21f9fa0449348ea63dde5e6a1bdad2da98f505b3b7478b949e211f", + { + /* scripts */ + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", /* [k1] CHECKSIG */ + "20a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac", /* [k2] CHECKSIG */ + "20b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac", /* [k3] CHECKSIG */ + "", "", + }, { + /* hashes */ + "815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1", /* k1 */ + "7c285d60b6e125d82ed715992dae12db8091bd9b9d92c48d768e6c043deca50d", /* k2 */ + "15ba0270b5e0006a16b832bd0f875873bb957516603e9a08ae3e968dbf4672f8", /* k3 */ + "", "", + }, { + /* control blocks */ + /* /LHS/LHS: [internal_key] + 2 leaves + parent = 97 bytes */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e7c285d60b6e125d82ed715992dae12db8091bd9b9d92c48d768e6c043deca50d15ba0270b5e0006a16b832bd0f875873bb957516603e9a08ae3e968dbf4672f8", + /* /RHS/RHS: [internal_key] + 2 leaves + parent = 97 bytes */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e115ba0270b5e0006a16b832bd0f875873bb957516603e9a08ae3e968dbf4672f8", + /* /RHS: [internal_key] + 2 leaves + = 65 bytes */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e7d35d60d6e53dac2f6cf4b638d50a67ea8963843eabfab3839ef7343893ce704", + "", "", + }, { + /* leaf_keys */ + { TR_K1("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, /* k1 xo */ + { TR_K1("a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, /* k2 xo */ + { TR_K1("b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284") }, /* k3 xo */ + { TR_K_NONE }, { TR_K_NONE } + }}, + /* This is the test case above, with the tree balanced to the right. + * Note that iteration of the keys happens in the same DFS order, so + * scripts, hashes and leaf keys are the same. + * Because the path to each key is different, the control blocks and + * merkle root differ. + */ + {{ + "taproot - 3 leaf right balanced", + "tr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "51201edef6eaf60517b880b7c721436840e45c487f4f7d4b544848a1fa8ecae1a146", + "", VARS_STD + }, + 5, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "e6229e969670aedf50e45d06fb764d38d92090fb8ddd45051dbf572ce4aaa126", + { + /* scripts */ + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac", /* [k1] CHECKSIG */ + "20a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ac", /* [k2] CHECKSIG */ + "20b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac", /* [k3] CHECKSIG */ + "", "", + }, { + /* hashes */ + "815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1", /* k1 */ + "7c285d60b6e125d82ed715992dae12db8091bd9b9d92c48d768e6c043deca50d", /* k2 */ + "15ba0270b5e0006a16b832bd0f875873bb957516603e9a08ae3e968dbf4672f8", /* k3 */ + "", "", + }, { + /* control blocks */ + /* /LHS: [internal_key] + 2 leaves = 65 bytes */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e211d67a9121e03064b2928aab2c7912fd759e0f51d8c0dcf071cebd4b1d56c3d", + /* /RHS/LHS: [internal_key] + 2 leaves + parent = 97 bytes */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e15ba0270b5e0006a16b832bd0f875873bb957516603e9a08ae3e968dbf4672f8815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1", + /* /RHS/RHS: [internal_key] + 2 leaves + parent = 97 bytes */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e7c285d60b6e125d82ed715992dae12db8091bd9b9d92c48d768e6c043deca50d815764544533858b85135d9ddf54e667a2e7bc0e3bfa4ab8fdcc8c22b7ba93e1", + "", "", + }, { + /* leaf_keys */ + { TR_K1("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, /* k1 xo */ + { TR_K1("a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, /* k2 xo */ + { TR_K1("b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284") }, /* k3 xo */ + { TR_K_NONE }, { TR_K_NONE } + }}, + {{ + "taproot - 2of3 multi_a single script path", + "tr(x_only,multi_a(2,key_1,key_2,key_3))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "5120e96b74eb71c05f7362ab7977c829d78256685d87fc4e1e44545146466caedd19", + "", VARS_STD + }, + 3, + "b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", + "d5b9b3b8c9611d90d8205d3334e4ec016c828bb175e39cbedb23a151d40b0de0", + { + /* scripts */ + "208bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac20a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ba20b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ba529c", + "", "", "", "", + }, { + /* hashes */ + "d5b9b3b8c9611d90d8205d3334e4ec016c828bb175e39cbedb23a151d40b0de0", "", "", "", "", + }, { + /* control blocks */ + "c1b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e", "", "", "", "", + }, { + /* leaf_keys */ + /* k1,k2,k3 inside multi_a() (as x-only) */ + { TR_K3("8bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048", "a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7", "b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284" ) }, + { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE }, { TR_K_NONE } + }} + /* FIXME: Elements test cases */ +}; + + #define ADDR(a) 1, { a, "", "", "", "", "", "", "", "", "", "", "", "", "", \ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" } @@ -2359,10 +2715,20 @@ static const struct address_test { "address errchk - Invalid multi-path index", "pkh(mainnet_xpub/<0;1>)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 2, 0, ADDR("") + },{ + "address - tr - single leaf pk(key_1)", + "tr(x_only,pk(key_1))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, + ADDR("bc1pj5dk4dumwklnpqck86xy5003ewsf9rs8kwewxue9qwahleklsp9sdyja0e") + },{ + "address - tr - unbalanced 3-leaf", + "tr(x_only,{pk(key_1),{pk(key_2),pk(key_3)}})", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, + ADDR("bc1prm00d6hkq5tm3q9hcus5x6zqu3wysl600494gjzg58agajhp59rqudfe9j") } }; -static bool check_descriptor_to_script(const struct descriptor_test* test) +static bool check_descriptor_to_script(const descriptor_test* test) { struct wally_descriptor *descriptor; size_t written, upper_limit, max_written; @@ -2500,7 +2866,7 @@ static bool check_descriptor_to_script(const struct descriptor_test* test) 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", + printf("descriptor_to_script: expected checksum [%s], got [%s]\n", test->checksum, checksum); wally_free_string(checksum); @@ -2551,6 +2917,223 @@ static bool check_descriptor_to_address(const struct address_test *test) return true; } +static bool check_taproot_descriptor(const taproot_descriptor_test* tr_test) +{ + unsigned char buf[1024]; + struct wally_descriptor *descriptor; + const descriptor_test* test = &tr_test->test; + const struct wally_map *keys = &g_vars[test->policy_map_index]; + size_t i, n, written; + uint32_t num_leaves, expected_num_leaves = 0; + int ret, expected_ret; + + if (!check_descriptor_to_script(test)) + return false; + + for (i = tr_test->depth - 1; i <= tr_test->depth; ++i) { + uint32_t flags = (i << WALLY_MINISCRIPT_DEPTH_SHIFT) | test->flags; + struct ext_key *internal_key; + uint32_t features; + + /* Ensure depth parsing limits include the taptree */ + expected_ret = i == tr_test->depth ? WALLY_OK : WALLY_EINVAL; + ret = wally_descriptor_parse(test->descriptor, keys, test->network, + flags, &descriptor); + + if (!check_ret("descriptor_parse", ret, expected_ret)) + return false; + if (expected_ret != WALLY_OK) + continue; + + /* The internal key is always key index 0 */ + ret = wally_descriptor_derive_bip32_key_alloc(descriptor, 0, + 0, 0, 0, 0, + &internal_key); + if (!check_ret("derive_bip32_key(internal)", ret, WALLY_OK)) + return false; + if (!check_varbuff("internal_key", internal_key->pub_key + 1, + EC_XONLY_PUBLIC_KEY_LEN, tr_test->internal_key)) + return false; + bip32_key_free(internal_key); + + ret = wally_descriptor_get_key_features(descriptor, 0, &features); + if (!check_ret("get_key_features(internal)", ret, WALLY_OK)) + return false; + if (features != (WALLY_MS_IS_X_ONLY|WALLY_MS_IS_RAW)) { + printf("features(internal): expected raw x-only, got %x\n", + features); + return false; + } + } + + for (i = 0; i < NUM_ELEMS(tr_test->leaf_scripts); ++i) + expected_num_leaves += tr_test->leaf_scripts[i][0] ? 1 : 0; + + ret = wally_descriptor_get_taproot_num_leaves(descriptor, &num_leaves); + if (!check_ret("get_taproot_num_leaves", ret, WALLY_OK)) + return false; + if (num_leaves != expected_num_leaves) { + printf("num_leaves: expected %u, got %u\n", expected_num_leaves, num_leaves); + return false; + } + + /* If there is no taptree, get_taproot_merkle_root returns WALLY_EINVAL */ + ret = wally_descriptor_get_taproot_merkle_root(descriptor, 0, 0, 0, + buf, SHA256_LEN); + if (!check_ret("get_taproot_merkle_root", ret, + num_leaves ? WALLY_OK : WALLY_EINVAL)) + return false; + if (num_leaves && + !check_varbuff("merkle_root", buf, SHA256_LEN, tr_test->merkle_root)) + return false; + + + /* Iterate checking each leaf and then the keys within each leaf */ + for (i = 0; i < num_leaves + 1; ++i) { + uint32_t key_index, num_keys, expected_num_keys = 0; + size_t cb_len; + int expected_sub_ret; + + expected_ret = i == num_leaves ? WALLY_EINVAL : WALLY_OK; + expected_sub_ret = expected_ret; + + ret = wally_descriptor_get_taproot_leaf_script(descriptor, i, 0, 0, 0, + buf, sizeof(buf), &written); + if (!check_ret("get_taproot_leaf_script", ret, expected_ret)) + return false; + if (expected_ret == WALLY_OK && + !check_varbuff("leaf_script", buf, written, tr_test->leaf_scripts[i])) + return false; + + ret = wally_descriptor_get_taproot_leaf_hash(descriptor, i, 0, 0, 0, + buf, SHA256_LEN); + if (!check_ret("get_taproot_leaf_hash", ret, expected_ret)) + return false; + if (expected_ret == WALLY_OK && + !check_varbuff("leaf_hash", buf, SHA256_LEN, tr_test->leaf_hashes[i])) + return false; + + ret = wally_descriptor_get_taproot_control_block(descriptor, i, 0, 0, 0, + buf, sizeof(buf), &written); + if (!check_ret("get_taproot_control_block", ret, expected_ret)) + return false; + + ret = wally_descriptor_get_taproot_control_block_len(descriptor, + i, 0, 0, 0, &cb_len); + if (!check_ret("get_taproot_control_block_len", ret, expected_ret)) + return false; + + if (expected_ret == WALLY_OK) { + const size_t expected_cb_len = (size_t)strlen(tr_test->control_blocks[i]) / 2; + if (written != expected_cb_len) { + printf("control_block len: expected %zu, got %zu\n", + expected_cb_len, written); + return false; + } + if (!check_varbuff("control_block", buf, written, tr_test->control_blocks[i])) + return false; + + if (cb_len != expected_cb_len) { + printf("control_block_len: expected %zu, got %zu\n", + expected_cb_len, cb_len); + return false; + } + + ret = wally_bip341_control_block_verify(buf, written); + if (!check_ret("bip341_control_block_verify", ret, WALLY_OK)) + return false; + } + + if (expected_ret == WALLY_OK) + for (n = 0; n < NUM_ELEMS(tr_test->keys[i].items); ++n) + expected_num_keys += tr_test->keys[i].items[n][0] ? 1 : 0; + + ret = wally_descriptor_get_taproot_leaf_num_keys(descriptor, i, &num_keys); + if (!check_ret("get_taproot_leaf_num_keys", ret, expected_ret)) + return false; + if (expected_ret == WALLY_OK) { + if (num_keys != expected_num_keys) { + printf("leaf_num_keys: expected %u, got %u\n", + expected_num_keys, num_keys); + return false; + } + } + + /* Check the keys in each tapleaf script */ + for (n = 0; n < expected_num_keys + 1; ++n) { + uint32_t features; + if (n == expected_num_keys) + expected_sub_ret = WALLY_EINVAL; /* Instead of expected_ret */ + ret = wally_descriptor_get_taproot_leaf_key_index(descriptor, i, n, + &key_index); + if (!check_ret("get_taproot_leaf_key_index", ret, expected_sub_ret)) + return false; + + if (expected_sub_ret == WALLY_OK) { + char *key_out; + ret = wally_descriptor_get_key(descriptor, key_index, &key_out); + if (!check_ret("get_key", ret, WALLY_OK)) + return false; + if (strcmp(key_out, tr_test->keys[i].items[n])) { + printf("get_key: expected %s got %s\n", + tr_test->keys[i].items[n], key_out); + return false; + } + ret = wally_descriptor_get_key_features(descriptor, key_index, &features); + if (!check_ret("get_key_features", ret, WALLY_OK)) + return false; + if (!(features & WALLY_MS_IS_X_ONLY)) { + printf("features: expected x-only got %x\n", features); + return false; + } + wally_free_string(key_out); + } + } + } + + wally_descriptor_free(descriptor); + return true; +} + +/* Verify the BIP-341 taptree depth limit: a leaf nested in 128 branches + * (merkle path of 128 hashes) is the deepest valid leaf, matching core. + */ +static bool check_taptree_depth_limit(void) +{ + const char *key = "a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"; + static char descriptor_str[16384]; + size_t depth, i; + bool tests_ok = true; + + for (depth = WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH; + depth <= WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH + 1; ++depth) { + /* Build tr(key,{...{pk(key),pk(key)}...}) with `depth` nested braces, + * placing the deepest leaves at a merkle path length of `depth` */ + const int expected_ret = + depth > WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH ? WALLY_EINVAL : WALLY_OK; + struct wally_descriptor *descriptor = NULL; + char *p = descriptor_str; + int ret; + + p += sprintf(p, "tr(%s,", key); + for (i = 0; i < depth; ++i) + *p++ = '{'; + p += sprintf(p, "pk(%s)", key); + for (i = 0; i < depth; ++i) + p += sprintf(p, ",pk(%s)}", key); + *p++ = ')'; + *p = '\0'; + + ret = wally_descriptor_parse(descriptor_str, NULL, + WALLY_NETWORK_BITCOIN_MAINNET, 0, + &descriptor); + if (!check_ret("taptree depth limit", ret, expected_ret)) + tests_ok = false; + wally_descriptor_free(descriptor); + } + return tests_ok; +} + int main(void) { bool tests_ok = true; @@ -2570,6 +3153,18 @@ int main(void) } } + for (i = 0; i < NUM_ELEMS(g_taproot_cases); ++i) { + if (!check_taproot_descriptor(&g_taproot_cases[i])) { + printf("[%s] taproot test failed!\n", g_taproot_cases[i].test.name); + tests_ok = false; + } + } + + if (!check_taptree_depth_limit()) { + printf("taptree depth limit test failed!\n"); + tests_ok = false; + } + wally_cleanup(0); return tests_ok ? 0 : 1; } diff --git a/src/descriptor.c b/src/descriptor.c index 7c8aaf1bc..331d767eb 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -2,6 +2,7 @@ #include "script.h" #include "script_int.h" +#include "tx_io.h" #include #include @@ -61,6 +62,7 @@ #define KIND_NUMBER 0x08 #define KIND_ADDRESS 0x10 #define KIND_KEY 0x20 +#define KIND_BRANCH 0x40 /* Taptree branch */ #define KIND_BASE58 (0x0100 | KIND_ADDRESS) #define KIND_BECH32 (0x0200 | KIND_ADDRESS) @@ -73,6 +75,7 @@ #define DESCRIPTOR_MIN_SIZE 20 #define MINISCRIPT_MULTI_MAX 20 +#define MULTI_A_NUM_KEYS_MAX 999 /* BIP-342: stack limited to 1000 elements, one used by the threshold */ #define REDEEM_SCRIPT_MAX_SIZE 520 #define WITNESS_SCRIPT_MAX_SIZE 10000 #define DESCRIPTOR_SEQUENCE_LOCKTIME_TYPE_FLAG 0x00400000 @@ -117,6 +120,8 @@ #define KIND_MINISCRIPT_OR_C (0x06000000 | KIND_MINISCRIPT) #define KIND_MINISCRIPT_OR_D (0x07000000 | KIND_MINISCRIPT) #define KIND_MINISCRIPT_OR_I (0x08000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_MULTI_A (0x09000000 | KIND_MINISCRIPT) +#define KIND_MINISCRIPT_MULTI_A_S (0x0A000000 | KIND_MINISCRIPT) struct addr_ver_t { const unsigned char network; @@ -227,6 +232,41 @@ static int ctx_add_key_node(ms_ctx *ctx, ms_node *node) (unsigned char *)v, 1, true, false); } +/* Copy a descriptor's evaluation context, setting the given indices. + * On success the caller must ctx_clear(ctx) when done with the context. + */ +static int ctx_clone(const struct wally_descriptor *descriptor, + uint32_t variant, uint32_t multi_index, + uint32_t child_num, ms_ctx *ctx) +{ + memcpy(ctx, descriptor, sizeof(*ctx)); + ctx->variant = variant; + ctx->child_num = child_num; + ctx->multi_index = multi_index; + ctx->path_buff = NULL; + if (ctx->max_path_elems && + !(ctx->path_buff = wally_malloc(ctx->max_path_elems * sizeof(uint32_t)))) + return WALLY_ENOMEM; + return WALLY_OK; +} + +static inline void ctx_clear(ms_ctx *ctx) +{ + if (ctx->path_buff) + wally_free(ctx->path_buff); +} + +/* Check the generation index arguments for a descriptor */ +static bool index_args_valid(const struct wally_descriptor *descriptor, + uint32_t variant, uint32_t multi_index, + uint32_t child_num) +{ + return descriptor && variant < descriptor->num_variants && + child_num < BIP32_INITIAL_HARDENED_CHILD && + (!child_num || (descriptor->features & WALLY_MS_IS_RANGED)) && + multi_index < descriptor->num_multipaths; +} + static int ensure_unique_policy_keys(const ms_ctx *ctx); /* Built-in miniscript expressions */ @@ -301,6 +341,7 @@ static const struct addr_ver_t *addr_ver_from_family( 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); +static int node_generation_size(const ms_node *node, size_t *total); static int is_valid_policy_map(const struct wally_map *map_in, bool *is_elements); static bool is_elements_policy_map(const struct wally_map *map_in) @@ -581,30 +622,31 @@ static bool node_has_uncompressed_key(const ms_ctx *ctx, const ms_node *node) return false; } -static int node_is_top(const ms_node *node) +static inline bool node_is_ct(const ms_node *node) { - /* True if this is the top node in the descriptor - * (disregarding any ct() parent for Elements). - */ #ifdef BUILD_ELEMENTS - return !node->parent || node->parent->kind == KIND_DESCRIPTOR_CT; + return !node->parent && node->kind == KIND_DESCRIPTOR_CT; #else - return !node->parent; + (void)node; + return false; #endif } -static bool node_is_root(const ms_node *node) +static int node_is_top(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; + /* True if this is the top node in the descriptor + * (ignoring ct() parent for Elements) */ + return !node->parent || node_is_ct(node->parent); } -#ifdef BUILD_ELEMENTS -static bool node_is_ct(const ms_node *node) +static bool node_is_root(const ms_node *node) { - return !node->parent && node->kind == KIND_DESCRIPTOR_CT; + /* True if this is a (possibly temporary) top level node, or an argument of a builtin, + * or a direct child of a taptree branch node (each taptree leaf is an independent + * miniscript expression that must be validated as its own root) */ + return !node->parent || node->parent->builtin || + node->parent->kind == KIND_BRANCH; } -#endif static void node_free(ms_node *node) { @@ -622,6 +664,73 @@ static void node_free(ms_node *node) } } +static int node_derive_key(ms_ctx* ctx, const ms_node* node, + uint32_t flags, struct ext_key* output) +{ + const unsigned char* data = (const unsigned char*)node->data; + + wally_clear(output, sizeof(*output)); + + if (node->kind == KIND_PUBLIC_KEY) { + int ret; + if (node->data_len == EC_XONLY_PUBLIC_KEY_LEN) { + memcpy(output->pub_key+1, data, node->data_len); + ret = WALLY_OK; + } else + ret = wally_ec_public_key_compress(data, node->data_len, + output->pub_key, + sizeof(output->pub_key)); + if (ret == WALLY_OK) + ret = bip32_key_strip_private_key(output); /* Mark as pubkey only */ + return ret; + } else if (node->kind == KIND_PRIVATE_KEY) { + int ret; + if (!(flags & BIP32_FLAG_KEY_PUBLIC)) { + memcpy(output->priv_key + 1, data, sizeof(output->priv_key) - 1); + return WALLY_OK; + } + ret = wally_ec_public_key_from_private_key(data, node->data_len, + output->pub_key, sizeof(output->pub_key)); + if (ret == WALLY_OK) + ret = bip32_key_strip_private_key(output); /* Mark as pubkey only */ + return ret; + } else if (node->kind == KIND_RAW && node->parent && + node->parent->kind == KIND_DESCRIPTOR_SLIP77) { + /* SLIP77 blinding key is returned as a private key */ + memcpy(output->priv_key + 1, data, sizeof(output->priv_key) - 1); + return WALLY_OK; + } else if ((node->kind & KIND_BIP32) == KIND_BIP32) { + int ret = bip32_key_from_base58_n(node->data, node->data_len, output); + if (ret == WALLY_OK && node->child_path_len) { + size_t path_len; + const uint32_t path_flags = BIP32_FLAG_STR_WILDCARD | + BIP32_FLAG_STR_BARE | + BIP32_FLAG_STR_MULTIPATH; + const uint32_t derive_flags = flags & (BIP32_FLAG_SKIP_HASH | + BIP32_FLAG_KEY_PUBLIC); + const bool is_ranged = node->flags & WALLY_MS_IS_RANGED; + const bool is_multi = node->flags & WALLY_MS_IS_MULTIPATH; + struct ext_key derived; + + ret = bip32_path_from_str_n(node->child_path, node->child_path_len, + is_ranged ? ctx->child_num : 0, + is_multi ? ctx->multi_index : 0, + path_flags, ctx->path_buff, ctx->max_path_elems, + &path_len); + if (ret == WALLY_OK) + ret = bip32_key_from_parent_path(output, ctx->path_buff, path_len, + derive_flags, &derived); + if (ret == WALLY_OK) + memcpy(output, &derived, sizeof(derived)); + wally_clear(&derived, sizeof(derived)); + } else if (ret == WALLY_OK && (flags & BIP32_FLAG_KEY_PUBLIC)) { + ret = bip32_key_strip_private_key(output); + } + return ret; + } + return WALLY_ERROR; /* Not a key node */ +} + static bool has_two_different_lock_states(uint32_t primary, uint32_t secondary) { return ((primary & PROP_G) && (secondary & PROP_H)) || @@ -733,9 +842,8 @@ static int verify_combo(ms_ctx *ctx, ms_node *node) return ret; } -static int verify_multi(ms_ctx *ctx, ms_node *node) +static int verify_multi_impl(ms_ctx *ctx, ms_node *node, uint32_t flags, int32_t max_keys) { - (void)ctx; const ms_node *top = node->child; ms_node *key = top ? top->next : NULL; int64_t key_count = 0; @@ -744,9 +852,11 @@ static int verify_multi(ms_ctx *ctx, ms_node *node) top->kind != KIND_NUMBER || top->number <= 0) return WALLY_EINVAL; + if ((ctx->features & WALLY_MS_IS_TAPSCRIPT) != (flags & WALLY_MS_IS_TAPSCRIPT)) + return WALLY_EINVAL; /* _a/non-_a tapscript/non-tapscript mismatch */ + while (key) { - if (key->builtin || !(key->kind & KIND_KEY) || - ++key_count > MINISCRIPT_MULTI_MAX) + if (key->builtin || !(key->kind & KIND_KEY) || ++key_count > max_keys) return WALLY_EINVAL; key = key->next; } @@ -757,6 +867,16 @@ static int verify_multi(ms_ctx *ctx, ms_node *node) return WALLY_OK; } +static int verify_multi(ms_ctx *ctx, ms_node *node) +{ + return verify_multi_impl(ctx, node, 0, MINISCRIPT_MULTI_MAX); +} + +static int verify_multi_a(ms_ctx *ctx, ms_node *node) +{ + return verify_multi_impl(ctx, node, WALLY_MS_IS_TAPSCRIPT, MULTI_A_NUM_KEYS_MAX); +} + static int verify_addr(ms_ctx *ctx, ms_node *node) { (void)ctx; @@ -788,8 +908,9 @@ static int verify_raw_tr(ms_ctx *ctx, ms_node *node) static int verify_tr(ms_ctx *ctx, ms_node *node) { const uint32_t child_count = node_get_child_count(node); - if (child_count != 1u) - return WALLY_EINVAL; /* FIXME: Support script paths */ + /* only tr(key) and tr(key, tree) is valid */ + if (child_count < 1u || child_count > 2u) + return WALLY_EINVAL; if (!node_is_top(node) || node->child->builtin || !(node->child->kind & KIND_KEY) || node_has_uncompressed_key(ctx, node)) return WALLY_EINVAL; @@ -1130,7 +1251,7 @@ static int verify_slip77(ms_ctx *ctx, ms_node *node) } #endif /* ifdef BUILD_ELEMENTS */ -static int node_verify_wrappers(ms_node *node) +static int node_verify_wrappers(ms_ctx *ctx, ms_node *node) { uint32_t *properties = &node->type_properties; size_t i; @@ -1181,6 +1302,11 @@ static int node_verify_wrappers(ms_node *node) *properties &= ~PROP_F; *properties |= PROP_E; } + if (ctx->features & WALLY_MS_IS_TAPSCRIPT) { + /* d: gains the u property under tapscript thanks to + * tapscript requiring MINIMALIF enforcement */ + *properties |= PROP_U; + } break; case 'v': PROP_REQUIRE(TYPE_B); @@ -1244,7 +1370,7 @@ static int node_verify_wrappers(ms_node *node) static int generate_number(int64_t number, ms_node *parent, unsigned char *script, size_t script_len, size_t *written) { - if ((parent && !parent->builtin)) + if (parent && !parent->builtin && parent->kind != KIND_BRANCH) return WALLY_EINVAL; if (number >= -1 && number <= 16) { @@ -1311,7 +1437,8 @@ static int generate_pk_h(ms_ctx *ctx, ms_node *node, if (script_len >= WALLY_SCRIPTPUBKEY_P2PKH_LEN - 1) { ret = generate_pk_k(ctx, node, buff+3, sizeof(buff)-3, written); if (ret == WALLY_OK) { - if (node->child->flags & WALLY_MS_IS_X_ONLY) + if ((node->child->flags & WALLY_MS_IS_X_ONLY) && + !(ctx->features & WALLY_MS_IS_TAPSCRIPT)) return WALLY_EINVAL; script[0] = OP_DUP; script[1] = OP_HASH160; @@ -1360,7 +1487,9 @@ static int generate_sh_wsh(ms_ctx *ctx, ms_node *node, static int generate_inplace_checksig(unsigned char *script, size_t script_len, size_t *written) { - if (!*written || (*written + 1 > WITNESS_SCRIPT_MAX_SIZE)) + /* Witness script size limit enforced in generate_inplace_wrappers() for + * segwit v0 only; tapscript has no script size restriction. */ + if (!*written) return WALLY_EINVAL; *written += 1; @@ -1453,44 +1582,62 @@ static int compare_multisig_node(const void *lhs, const void *rhs) return memcmp(l->pubkey, ((const struct multisig_sort_data_t *)rhs)->pubkey, l->pubkey_len); } +/* multisig: + * standard: M [ ...] N OP_CHECKMULTISIG + * tapscript: OP_CHECKSIG [ OP_CHECKSIGADD ...] OP_NUMEQUAL + */ static int generate_multi(ms_ctx *ctx, ms_node *node, - unsigned char *script, size_t script_len, size_t *written) + unsigned char *script, size_t script_len, + size_t *written) { - size_t offset; + size_t offset = 0; uint32_t count, i; ms_node *child = node->child; struct multisig_sort_data_t *sorted; - int ret; + int (*pk_verify_fn)(const unsigned char *, size_t); + const bool is_tapscript = (ctx->features & WALLY_MS_IS_TAPSCRIPT) != 0; + int ret = WALLY_OK; if (!child || !node_is_root(node) || !node->builtin) return WALLY_EINVAL; count = node_get_child_count(node) - 1; - /* FIXME: We should allow 20 keys in witness scriptss */ - if (count > CHECKMULTISIG_NUM_KEYS_MAX) + /* FIXME: We should allow 20 keys in witness scripts */ + if (count > (is_tapscript ? MULTI_A_NUM_KEYS_MAX : CHECKMULTISIG_NUM_KEYS_MAX)) return WALLY_EINVAL; /* Too many keys for multisig */ - if ((ret = generate_script(ctx, child, script, script_len, &offset)) != WALLY_OK) + /* standard: generate threshold */ + if (!is_tapscript && + (ret = generate_script(ctx, child, script, script_len, &offset)) != WALLY_OK) return ret; if (!(sorted = wally_malloc(count * sizeof(struct multisig_sort_data_t)))) return WALLY_ENOMEM; - child = child->next; + pk_verify_fn = is_tapscript ? wally_ec_xonly_public_key_verify : wally_ec_public_key_verify; + child = child->next; /* Skip threshold */ for (i = 0; ret == WALLY_OK && i < count; ++i) { struct multisig_sort_data_t *item = sorted + i; 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_ERROR; /* FIXME: check for valid pubkey lengths */ + if (ret == WALLY_OK) + ret = pk_verify_fn(item->pubkey, item->pubkey_len); + if (ret == WALLY_OK && i > 0) { + const struct multisig_sort_data_t *prev = sorted + i -1; + if (prev->pubkey_len != item->pubkey_len) + ret = WALLY_EINVAL; /* Cannot mix pubkey types */ + } child = child->next; } 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); + if (node->kind == KIND_DESCRIPTOR_MULTI_S || + node->kind == KIND_MINISCRIPT_MULTI_A_S) { + /* Sort keys, skipping if we are already beyond the output + * size, since sorting won't change the final size computed */ + if (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; @@ -1499,19 +1646,31 @@ static int generate_multi(ms_ctx *ctx, ms_node *node, memcpy(script + offset + 1, sorted[i].pubkey, pubkey_len); } offset += pubkey_len + 1; + if (is_tapscript) { + /* OP_CHECKSIG/OP_CHECKSIGADD follows the pubkey */ + if (offset + 1 <= script_len) + script[offset] = i == 0 ? OP_CHECKSIG : OP_CHECKSIGADD; + ++offset; + } } if (ret == WALLY_OK) { + /* standard: num_keys OP_CHECKMULTISIG + * tapscript: threshold OP_NUMEQUAL + */ size_t number_len; size_t remaining_len = offset > script_len ? 0 : script_len - offset; - ret = generate_number(count, node->parent, script + offset, + unsigned char *num_script = remaining_len ? script + offset : NULL; + if (is_tapscript) + count = node->child->number; /* Threshold */ + ret = generate_number(count, node->parent, num_script, remaining_len, &number_len); if (ret == WALLY_OK) { *written = offset + number_len + 1; - if (*written > REDEEM_SCRIPT_MAX_SIZE) + if (!is_tapscript && *written > REDEEM_SCRIPT_MAX_SIZE) return WALLY_EINVAL; if (*written <= script_len) - script[*written - 1] = OP_CHECKMULTISIG; + script[*written - 1] = is_tapscript ? OP_NUMEQUAL : OP_CHECKMULTISIG; } } } @@ -1551,37 +1710,275 @@ static int generate_raw_tr(ms_ctx *ctx, ms_node *node, return ret; } +static bool ms_ctx_is_elements(const ms_ctx *ctx) +{ +#ifdef BUILD_ELEMENTS + return (ctx->features & WALLY_MS_IS_ELEMENTS) != 0; +#else + (void)ctx; + return false; +#endif +} + +/* Return the tr() node for a taproot descriptor */ +static inline ms_node *tr_get_tr(const struct wally_descriptor *descriptor) +{ + ms_node *node = descriptor->top_node; + if (node_is_ct(node)) + node = node->child->next; /* tr() from ct(blinding_key,tr(...)) */ + return node; +} + +/* Return the taptree for a tr() descriptor, or NULL if non-tr() or no tree */ +static inline ms_node *tr_get_tree(const struct wally_descriptor *descriptor) +{ + if (descriptor->features & WALLY_MS_IS_TAPSCRIPT) + return tr_get_tr(descriptor)->child->next; + return NULL; +} + +/* Compute the BIP-341 tapleaf hash for a single miniscript leaf node. */ +static int leaf_tapleaf_hash(ms_ctx *ctx, ms_node *leaf, + unsigned char *hash_out, size_t hash_out_len) +{ + unsigned char *buf; + size_t buf_len = 0, written = 0; + int ret; + + ret = node_generation_size(leaf, &buf_len); + if (ret != WALLY_OK) + return ret; + if (!(buf = wally_malloc(buf_len))) + return WALLY_ENOMEM; + + ret = generate_script(ctx, leaf, buf, buf_len, &written); + if (ret == WALLY_OK) { + if (written > buf_len) + ret = WALLY_ERROR; /* Should not happen! */ + else + ret = bip341_tapleaf_hash(WALLY_LEAF_VERSION_TAPSCRIPT, + buf, written, + ms_ctx_is_elements(ctx), + hash_out, hash_out_len); + } + wally_free(buf); + return ret; +} + +static uint32_t count_taptree_leaves(const ms_node *node) +{ + if (!node) return 0; + if (node->kind == KIND_BRANCH) { + if (!node->child || !node->child->next) return 0; + return count_taptree_leaves(node->child) + + count_taptree_leaves(node->child->next); + } + return 1; +} + +/* Return the index-th leaf of a taptree (DFS left-first), NULL if not found. + * if depth_out is non-NULL, it is populated with the 0-based node depth. + */ +static ms_node *find_taptree_leaf(ms_node *taptree, uint32_t index, + uint32_t *depth_out) +{ + ms_node *p = taptree, *prev = NULL; + + if (depth_out) + *depth_out = 0; + + if (p->kind != KIND_BRANCH) + return index ? NULL : taptree; /* Single script tree */ + + while (p) { + if (prev == p->parent || (prev && prev->next == p)) { + /* Came from parent or lhs child while descending: process p */ + if (p->kind != KIND_BRANCH && index-- == 0) + return p; /* Leaf node at index: return it */ + } + + if (p->kind == KIND_BRANCH && p->child && + prev != p->child && prev != p->child->next) { + prev = p; /* Branch with unvisted children: descend */ + p = p->child; + if (depth_out) + ++*depth_out; + } else if (p->next) { + prev = p; /* Move to rhs sibling */ + p = p->next; + } else { + prev = p; /* Only child or rhs sibling: ascend */ + p = p->parent; + if (depth_out) + --*depth_out; + } + } + return NULL; /* leaf not found at the given index */ +} + +/* Recursively build a merkle proof for a target leaf in the taptree. + * + * Set *path_len=0, *index=0, and *found=false before calling. + * + * As recursion unwinds while ascending from leaf to root, each branch + * on the path appends its sibling hash to path_out. The result is a + * sequence of BIP-341 merkle hashes in leaf-to-root order: + * path_out[0] = the target leaf's immediate sibling + * path_out[1] = the next sibling closer to the root + * ... + * path_out[*path_len_out - 1] = the sibling closest to the root + * + * The top-level merkle root hash is placed in hash_out; it is not written + * to path_out. path_out may be NULL to compute only the merkle root. + */ +static int compute_merkle_info(ms_ctx *ctx, ms_node *taptree, + uint32_t target_index, uint32_t *index, + unsigned char *path_out, uint32_t *path_len, + unsigned char *hash_out, size_t hash_out_len, + bool *found) +{ + int ret; + if (taptree->kind == KIND_BRANCH) { + unsigned char left_hash[SHA256_LEN], right_hash[SHA256_LEN]; + bool left_found = false, right_found = false; + + ret = compute_merkle_info(ctx, taptree->child, target_index, index, + path_out, path_len, left_hash, sizeof(left_hash), &left_found); + if (ret != WALLY_OK) + return ret; + ret = compute_merkle_info(ctx, taptree->child->next, target_index, index, + path_out, path_len, right_hash, sizeof(right_hash), &right_found); + if (ret != WALLY_OK) + return ret; + + if (left_found) { + if (path_out) + memcpy(path_out + (*path_len) * SHA256_LEN, right_hash, SHA256_LEN); + (*path_len)++; + *found = true; + } else if (right_found) { + if (path_out) + memcpy(path_out + (*path_len) * SHA256_LEN, left_hash, SHA256_LEN); + (*path_len)++; + *found = true; + } + ret = bip341_tapbranch_hash(left_hash, sizeof(left_hash), + right_hash, sizeof(right_hash), + ms_ctx_is_elements(ctx), hash_out, hash_out_len); + } else { + ret = leaf_tapleaf_hash(ctx, taptree, hash_out, hash_out_len); + if (ret == WALLY_OK) { + if (*index == target_index) + *found = true; + (*index)++; + } + } + return ret; +} + +static uint32_t count_keys_in_subtree(const ms_node *node) +{ + uint32_t count = 0; + const ms_node *child; + if (!node) return 0; + if (node->kind & KIND_KEY) return 1; + if (node->builtin) { + for (child = node->child; child; child = child->next) + count += count_keys_in_subtree(child); + } + return count; +} + +/* Recursive helper for find_nth_key_in_subtree. Caller MUST initialise + * *current_index to 0 before the (top-level) call. */ +static ms_node *find_nth_key_in_subtree_impl(ms_node *node, uint32_t target_index, uint32_t *current_index) +{ + ms_node *child, *found; + if (!node) return NULL; + if (node->kind & KIND_KEY) { + if (*current_index == target_index) return node; + (*current_index)++; + return NULL; + } + if (node->builtin) { + for (child = node->child; child; child = child->next) { + found = find_nth_key_in_subtree_impl(child, target_index, current_index); + if (found) return found; + } + } + return NULL; +} + +/* Return the n-th key (DFS left-first) inside a miniscript subtree, or NULL + * if target_index is out of range. */ +static ms_node *find_nth_key_in_subtree(ms_node *subtree_root, uint32_t target_index) +{ + uint32_t current_index = 0; + return find_nth_key_in_subtree_impl(subtree_root, target_index, ¤t_index); +} + +/* Helper for generating a taproot script (i.e. the internal key), the + * merkle path, and merkle root for a tr() descriptor. + */ +static int tr_impl(ms_ctx *ctx, unsigned char *script, size_t script_len, + uint32_t target_index, + unsigned char *path_out, uint32_t *path_len, + unsigned char *merkle_root, size_t merkle_root_len, + unsigned char *tweaked, size_t tweaked_len, + size_t *written) +{ + unsigned char *merkle_p = NULL; + ms_node* taptree = tr_get_tree(ctx); + int ret; + + /* Generate an untweaked p2tr script OP_1 [x-only internal key] */ + ret = generate_raw_tr(ctx, tr_get_tr(ctx), script, script_len, written); + if (ret != WALLY_OK || script_len < *written) + return ret; + + if (taptree) { + /* taptree is present: use its merkle root for our tweak below, + * and store the merkle path if the caller asked us to. */ + uint32_t index = 0; + bool found = false; + if (path_len) + *path_len = 0; + ret = compute_merkle_info(ctx, taptree, target_index, + &index, path_out, path_len, + merkle_root, merkle_root_len, &found); + if (target_index != 0xffffffff && ret == WALLY_OK && !found) + ret = WALLY_EINVAL; /* target_index given and not found */ + if (ret != WALLY_OK) + return ret; + merkle_p = merkle_root; /* Use merkle_root for tweaking */ + } + + /* Tweak the p2tr script x-only pubkey into 'tweaked' */ + const uint32_t flags = ms_ctx_is_elements(ctx) ? EC_FLAG_ELEMENTS : 0; + return wally_ec_public_key_bip341_tweak(script + 2, EC_XONLY_PUBLIC_KEY_LEN, + merkle_p, merkle_p ? SHA256_LEN : 0, + flags, tweaked, tweaked_len); +} + static int generate_tr(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { + unsigned char merkle_root[SHA256_LEN]; unsigned char tweaked[EC_PUBLIC_KEY_LEN]; - unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN + 1]; - size_t pubkey_len = 0; - uint32_t tweak_flags = 0; + uint32_t path_len = 0; int ret; - /* Generate a push of the x-only public key of our child */ - const bool force_xonly = true; - ret = generate_pk_k_impl(ctx, node, pubkey, sizeof(pubkey), force_xonly, &pubkey_len); - if (ret != WALLY_OK || pubkey_len != EC_XONLY_PUBLIC_KEY_LEN + 1) - return WALLY_EINVAL; /* Should be PUSH_32 [x-only pubkey] */ - - /* Tweak it into a compressed pubkey */ -#ifdef BUILD_ELEMENTS - if (ctx->features & WALLY_MS_IS_ELEMENTS) - tweak_flags = EC_FLAG_ELEMENTS; -#endif - ret = wally_ec_public_key_bip341_tweak(pubkey + 1, pubkey_len - 1, - NULL, 0, /* FIXME: Support script path */ - tweak_flags, tweaked, sizeof(tweaked)); + (void)node; + /* Generate an untweaked p2tr script OP_1 [x-only internal key], + * and collect the merkle root if a taptree is present. */ + ret = tr_impl(ctx, script, script_len, 0xffffffff, + NULL, &path_len, merkle_root, sizeof(merkle_root), + tweaked, sizeof(tweaked), written); - if (ret == WALLY_OK && script_len >= WALLY_SCRIPTPUBKEY_P2TR_LEN) { - /* Generate the script using the x-only part of the tweaked key */ - script[0] = OP_1; - script[1] = sizeof(tweaked) - 1; - memcpy(script + 2, tweaked + 1, sizeof(tweaked) - 1); + if (ret == WALLY_OK && script_len >= *written) { + /* Replace the p2tr x-only pubkey with the tweaked key */ + memcpy(script + 2, tweaked + 1, EC_XONLY_PUBLIC_KEY_LEN); } - *written = WALLY_SCRIPTPUBKEY_P2TR_LEN; return ret; } @@ -1837,7 +2234,7 @@ static int generate_thresh(ms_ctx *ctx, ms_node *node, return ret; } -static int generate_inplace_wrappers(ms_node *node, +static int generate_inplace_wrappers(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { @@ -1938,7 +2335,8 @@ static int generate_inplace_wrappers(ms_node *node, default: return WALLY_ERROR; /* Wrapper type not found, should not happen */ } - if (*written + output_len > WITNESS_SCRIPT_MAX_SIZE) + if (!(ctx->features & WALLY_MS_IS_TAPSCRIPT) && + *written + output_len > WITNESS_SCRIPT_MAX_SIZE) return WALLY_EINVAL; *written += output_len; } @@ -2093,6 +2491,16 @@ static const struct ms_builtin_t g_builtins[] = { I_NAME("thresh"), KIND_MINISCRIPT_THRESH, TYPE_B | PROP_D | PROP_U, 0xffffffff, verify_thresh, generate_thresh + }, { + I_NAME("multi_a"), + KIND_MINISCRIPT_MULTI_A, + TYPE_B | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_K, + 0xffffffff, verify_multi_a, generate_multi + }, { + I_NAME("sortedmulti_a"), + KIND_MINISCRIPT_MULTI_A_S, + TYPE_B | PROP_N | PROP_D | PROP_U | PROP_E | PROP_M | PROP_S | PROP_K, + 0xffffffff, verify_multi_a, generate_multi } /* Elements confidential descriptors */ #ifdef BUILD_ELEMENTS @@ -2111,24 +2519,25 @@ static const struct ms_builtin_t g_builtins[] = { }; #undef I_NAME -#ifdef BUILD_ELEMENTS static inline bool builtin_is_elements(const char *name, size_t name_len) { +#ifdef BUILD_ELEMENTS /* Elements descriptor builtins are prefixed with "el" */ return name_len > 2 && name[0] == 'e' && name[1] == 'l'; -} +#else + (void)name; + (void)name_len; + return false; #endif /* ifdef BUILD_ELEMENTS */ +} static unsigned char builtin_lookup(const char *name, size_t name_len, uint32_t kind) { unsigned char i; -#ifdef BUILD_ELEMENTS if (builtin_is_elements(name, name_len)) { name += 2; /* Look up without matching the prefix */ name_len -= 2; } -#endif /* ifdef BUILD_ELEMENTS */ - for (i = 0; i < NUM_ELEMS(g_builtins); ++i) { if ((g_builtins[i].kind & kind) && g_builtins[i].name_len == name_len && @@ -2181,43 +2590,26 @@ static int generate_script(ms_ctx *ctx, ms_node *node, } } } + } else if (node->kind == KIND_BRANCH) { + /* Taptree branch nodes cannot be directly generated as a script */ + return WALLY_EINVAL; } else if ((node->kind & KIND_BIP32) == KIND_BIP32) { output_len = node->flags & WALLY_MS_IS_X_ONLY ? EC_XONLY_PUBLIC_KEY_LEN : EC_PUBLIC_KEY_LEN; if (output_len > script_len) { ret = WALLY_OK; /* Return required length without writing */ } else { struct ext_key master; - - ret = bip32_key_from_base58_n(node->data, node->data_len, &master); - if (ret == WALLY_OK && node->child_path_len) { - size_t path_len; - const uint32_t flags = BIP32_FLAG_STR_WILDCARD | - BIP32_FLAG_STR_BARE | - BIP32_FLAG_STR_MULTIPATH; - const uint32_t derive_flags = BIP32_FLAG_SKIP_HASH | - BIP32_FLAG_KEY_PUBLIC; - const bool is_ranged = node->flags & WALLY_MS_IS_RANGED; - const bool is_multi = node->flags & WALLY_MS_IS_MULTIPATH; - struct ext_key derived; - - ret = bip32_path_from_str_n(node->child_path, node->child_path_len, - is_ranged ? ctx->child_num : 0, - is_multi ? ctx->multi_index : 0, - flags, ctx->path_buff, ctx->max_path_elems, - &path_len); - if (ret == WALLY_OK) - ret = bip32_key_from_parent_path(&master, ctx->path_buff, path_len, - derive_flags, &derived); - if (ret == WALLY_OK) - memcpy(&master, &derived, sizeof(master)); - } + ret = node_derive_key(ctx, node, + BIP32_FLAG_SKIP_HASH|BIP32_FLAG_KEY_PUBLIC, + &master); if (ret == WALLY_OK) - memcpy(script, master.pub_key + ((node->flags & WALLY_MS_IS_X_ONLY) ? 1 : 0), output_len); + memcpy(script, master.pub_key + EC_PUBLIC_KEY_LEN - output_len, + output_len); wally_clear(&master, sizeof(master)); } } if (ret == WALLY_OK) { - ret = generate_inplace_wrappers(node, script, script_len, &output_len); + ret = generate_inplace_wrappers(ctx, node, script, script_len, &output_len); if (ret == WALLY_OK) *written = output_len; } @@ -2328,8 +2720,10 @@ static int analyze_key_hex(ms_ctx *ctx, ms_node *node, if (key_len == EC_XONLY_PUBLIC_KEY_LEN && !allow_xonly) return WALLY_OK; /* X-only not allowed here */ if (key_len != EC_XONLY_PUBLIC_KEY_LEN) { - if (flags & WALLY_MINISCRIPT_TAPSCRIPT) - return WALLY_OK; /* Only X-only pubkeys allowed under tapscript */ + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) { + /* In tapscript, compressed keys are accepted and stripped to x-only */ + make_xonly = true; + } if (make_xonly) { /* Convert to x-only */ --key_len; @@ -2565,6 +2959,7 @@ static int analyze_miniscript_value(ms_ctx *ctx, const char *str, size_t str_len node->data_len = written; node->kind = KIND_RAW; if (kind == KIND_DESCRIPTOR_SLIP77) { + node->flags = WALLY_MS_IS_RAW | WALLY_MS_IS_SLIP77; ctx->features |= (WALLY_MS_IS_ELEMENTS | WALLY_MS_IS_SLIP77); } } @@ -2586,12 +2981,126 @@ static int analyze_miniscript_value(ms_ctx *ctx, const char *str, size_t str_len return analyze_miniscript_key(ctx, flags, node, parent, force_ct); } +/* Forward declaration */ +static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, + uint32_t kind, uint32_t flags, ms_node *prev_node, + ms_node *parent, ms_node **output); + +/* + * Recursively parse a taptree. + * Expressions in the tree are either a leaf script or a {LEFT,RIGHT} branch. + */ +static int parse_taptree(ms_ctx *ctx, const char *str, size_t str_len, + uint32_t flags, uint32_t depth, + ms_node *parent, ms_node *prev_sibling, ms_node **output) +{ + int ret; + + /* A leaf at depth N has a merkle path of N hashes; BIP-341 allows up + * to WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH path elements, so a leaf at + * exactly that depth is valid (as per core, which allows 128 levels + * of tree nesting). + */ + if (!str_len || depth > WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH) + return WALLY_EINVAL; /* No text remaining or exceeded BIP-341 tree depth */ + + if (str[0] == '{') { + /* Branch node: {LEFT, RIGHT} */ + size_t j, brace_depth = 1, paren_depth = 0, comma_pos = 0; + ms_node *node, *left = NULL, *right = NULL; + + /* Minimum 3 chars: `{`, >=1 byte of content, `}`. The actual minimum + * valid branch is much larger (each leaf must be a typed miniscript + * expression); this is just a buffer-size sanity check before we + * start scanning. */ + if (str_len < 3 || str[str_len - 1] != '}') + return WALLY_EINVAL; + + /* Find the comma separating left and right subtrees at brace_depth=1, paren_depth=0 */ + for (j = 1; j < str_len - 1; ++j) { + if (str[j] == '{') ++brace_depth; + else if (str[j] == '}') { + if (!brace_depth) + return WALLY_EINVAL; + --brace_depth; + } else if (str[j] == '(') ++paren_depth; + else if (str[j] == ')') { + if (!paren_depth) + return WALLY_EINVAL; /* Unmatched ')' */ + --paren_depth; + } else if (str[j] == ',' && brace_depth == 1 && paren_depth == 0) { + if (comma_pos != 0) + return WALLY_EINVAL; /* Multiple commas at separator level */ + comma_pos = j; + } + } + /* comma_pos == 0: no separator found + * comma_pos == 1: empty left subtree ({,b}) + * comma_pos == str_len - 2: empty right subtree ({a,}) */ + if (comma_pos == 0 || comma_pos == 1 || comma_pos == str_len - 2) + return WALLY_EINVAL; + + /* Allocate branch node */ + if (!(node = wally_calloc(sizeof(*node)))) + return WALLY_ENOMEM; + node->kind = KIND_BRANCH; + node->parent = parent; + + /* Parse left subtree: str[1..comma_pos-1] */ + ret = parse_taptree(ctx, str + 1, comma_pos - 1, + flags, depth + 1, node, NULL, &left); + if (ret != WALLY_OK) { + node_free(node); /* node_free() will also free left */ + return ret; + } + + /* Parse right subtree: str[comma_pos+1..str_len-2] */ + ret = parse_taptree(ctx, str + comma_pos + 1, str_len - comma_pos - 2, + flags, depth + 1, node, left, &right); + if (ret != WALLY_OK) { + node_free(node); /* node_free() will free all children*/ + return ret; + } + (void)right; /* linked via left->next by the recursive call */ + + /* Link branch node to its parent and previous sibling */ + *output = node; + /* First child (left arm of {L,R}): link as parent's first child */ + if (parent && !parent->child) + parent->child = node; + /* Subsequent child (right arm of {L,R}, or the taptree of tr(KEY,T)): + * link via the previous sibling */ + else if (prev_sibling) + prev_sibling->next = node; + } else { + /* Leaf node: bare miniscript expression in tapscript context */ + ret = analyze_miniscript(ctx, str, str_len, KIND_MINISCRIPT, + flags | WALLY_MINISCRIPT_TAPSCRIPT, + prev_sibling, parent, output); + if (ret == WALLY_OK && *output) { + /* A top-level miniscript expression must be type B. In particular, + * K, V, and W expressions are only valid as subexpressions. */ + if (((*output)->type_properties & TYPE_MASK) != TYPE_B) { + if (prev_sibling) + prev_sibling->next = NULL; /* unlink from sibling chain */ + else if (parent) + parent->child = NULL; /* reset dangling pointer */ + node_free(*output); + *output = NULL; + ret = WALLY_EINVAL; + } + } + } + + return ret; +} + static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, uint32_t kind, 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; + uint32_t indent = 0, brace_depth = 0; bool seen_indent = false, collect_child = false, copy_child = false; ms_node *node, *child = NULL, *prev_child = NULL; int ret = WALLY_OK; @@ -2625,11 +3134,10 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, /* Not a pure descriptor */ ctx->features &= ~WALLY_MS_IS_DESCRIPTOR; } -#ifdef BUILD_ELEMENTS - if (builtin_is_elements(str + offset, i - offset)) { + if (builtin_is_elements(str + offset, i - offset)) ctx->features |= WALLY_MS_IS_ELEMENTS; - } -#endif /* ifdef BUILD_ELEMENTS */ + if (node->kind == KIND_DESCRIPTOR_TR) + ctx->features |= WALLY_MS_IS_TAPROOT; offset = i + 1; child_offset = offset; } @@ -2645,12 +3153,22 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, } } seen_indent = true; + } else if (str[i] == '{') { + ++brace_depth; + seen_indent = true; + } else if (str[i] == '}') { + if (!brace_depth) { + ret = WALLY_EINVAL; /* Unmatched '}' */ + break; + } + --brace_depth; + seen_indent = true; } else if (str[i] == ',') { if (!indent) { ret = WALLY_EINVAL; /* Comma outside of ()'s */ break; } - if (collect_child && (indent == 1)) { + if (collect_child && (indent == 1) && brace_depth == 0) { copy_child = true; } seen_indent = true; @@ -2671,11 +3189,21 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, } if (copy_child) { - if (i - child_offset && - (ret = analyze_miniscript(ctx, str + child_offset, i - child_offset, - kind, flags, prev_child, - node, &child)) != WALLY_OK) - break; + if (i - child_offset) { + if (node->kind == KIND_DESCRIPTOR_TR && prev_child) { + /* Second argument of tr() is the taptree: parse_taptree + * handles both {LEFT,RIGHT} branches and a single bare + * miniscript leaf. */ + ctx->features |= WALLY_MS_IS_TAPSCRIPT; + ret = parse_taptree(ctx, str + child_offset, i - child_offset, + flags, 0, node, prev_child, &child); + } else { + ret = analyze_miniscript(ctx, str + child_offset, i - child_offset, + kind, flags, prev_child, node, &child); + } + if (ret != WALLY_OK) + break; + } prev_child = child; child = NULL; @@ -2708,7 +3236,7 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, } if (ret == WALLY_OK) - ret = node_verify_wrappers(node); + ret = node_verify_wrappers(ctx, node); if (ret != WALLY_OK) node_free(node); @@ -2790,6 +3318,12 @@ static int node_generation_size(const ms_node *node, size_t *total) case KIND_DESCRIPTOR_TR: *total += WALLY_SCRIPTPUBKEY_P2TR_LEN; break; + case KIND_MINISCRIPT_MULTI_A: + case KIND_MINISCRIPT_MULTI_A_S: + /* Each key: 1 (push) + 32 (x-only key) + 1 (OP_CHECKSIG/OP_CHECKSIGADD) = 34. + * Plus threshold (up to 3 bytes) + OP_NUMEQUAL (1 byte) = 4. */ + *total += (node_get_child_count(node) - 1) * 34 + 4; + break; case KIND_MINISCRIPT_PK_K: *total += 1; break; @@ -2851,6 +3385,8 @@ static int node_generation_size(const ms_node *node, size_t *total) *total += EC_XONLY_PUBLIC_KEY_LEN; else *total += EC_PUBLIC_KEY_LEN; + } else if (node->kind == KIND_BRANCH) { + /* Taptree branch nodes don't contribute to scriptPubkey size */ } else return WALLY_ERROR; /* Should not happen */ @@ -2914,10 +3450,14 @@ static uint32_t get_max_depth(const char *miniscript, size_t miniscript_len) uint32_t depth = 1, max_depth = 1; for (i = 0; i < miniscript_len; ++i) { - if (miniscript[i] == '(' && ++depth > max_depth) - max_depth = depth; - else if (miniscript[i] == ')' && depth-- == 1) - return 0xffffffff; /* Mismatched */ + if (miniscript[i] == '(' || miniscript[i] == '{') { + if (++depth > max_depth) + max_depth = depth; + } else if (miniscript[i] == ')' || miniscript[i] == '}') { + if (depth == 1) + return 0xffffffff; /* Mismatched */ + --depth; + } } return depth == 1 ? max_depth : 0xffffffff; } @@ -3082,22 +3622,14 @@ int wally_descriptor_to_script(const struct wally_descriptor *descriptor, if (written) *written = 0; - if (!descriptor || variant >= descriptor->num_variants || - child_num >= BIP32_INITIAL_HARDENED_CHILD || - (child_num && !(descriptor->features & WALLY_MS_IS_RANGED)) || - multi_index >= descriptor->num_multipaths || + if (!index_args_valid(descriptor, variant, multi_index, child_num) || (flags & WALLY_MINISCRIPT_ONLY) || !bytes_out || !len || !written) return WALLY_EINVAL; - memcpy(&ctx, descriptor, sizeof(ctx)); - ctx.variant = variant; - ctx.child_num = child_num; - ctx.multi_index = multi_index; - if (ctx.max_path_elems && - !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) - return WALLY_ENOMEM; - ret = node_generate_script(&ctx, depth, index, bytes_out, len, written); - wally_free(ctx.path_buff); + ret = ctx_clone(descriptor, variant, multi_index, child_num, &ctx); + if (ret == WALLY_OK) + ret = node_generate_script(&ctx, depth, index, bytes_out, len, written); + ctx_clear(&ctx); return ret; } @@ -3199,40 +3731,31 @@ int wally_descriptor_to_addresses(const struct wally_descriptor *descriptor, char **addresses, size_t num_addresses) { ms_ctx ctx; - unsigned char *p; + unsigned char *p = NULL; size_t i, written; int ret = WALLY_OK; - if (!descriptor || !descriptor->addr_ver || !descriptor->script_len || - variant >= descriptor->num_variants || - child_num >= BIP32_INITIAL_HARDENED_CHILD || + if (!index_args_valid(descriptor, variant, multi_index, child_num) || + !descriptor->addr_ver || !descriptor->script_len || (uint64_t)child_num + num_addresses >= BIP32_INITIAL_HARDENED_CHILD || - (child_num && !(descriptor->features & WALLY_MS_IS_RANGED)) || - multi_index >= descriptor->num_multipaths || flags || !addresses || !num_addresses) return WALLY_EINVAL; - wally_clear(addresses, num_addresses * sizeof(*addresses)); - if (!(p = wally_malloc(descriptor->script_len))) - return WALLY_ENOMEM; - #ifdef BUILD_ELEMENTS if (descriptor->features & WALLY_MS_IS_ELEMENTS && !(descriptor->features & WALLY_MS_ANY_BLINDING_KEY)) { - // Elements requires a blinding key to generate addresses + /* Elements requires a blinding key to generate addresses */ return WALLY_ERROR; } #endif + wally_clear(addresses, num_addresses * sizeof(*addresses)); - memcpy(&ctx, descriptor, sizeof(ctx)); - ctx.variant = variant; - if (ctx.max_path_elems && - !(ctx.path_buff = wally_malloc(ctx.max_path_elems * sizeof(uint32_t)))) - return WALLY_ENOMEM; + ret = ctx_clone(descriptor, variant, multi_index, child_num, &ctx); + if (ret == WALLY_OK && !(p = wally_malloc(descriptor->script_len))) + ret = WALLY_ENOMEM; for (i = 0; ret == WALLY_OK && i < num_addresses; ++i) { ctx.child_num = child_num + i; - ctx.multi_index = multi_index; ret = node_generate_script(&ctx, 0, 0, p, ctx.script_len, &written); if (ret == WALLY_OK) { if (written > ctx.script_len) @@ -3251,7 +3774,7 @@ int wally_descriptor_to_addresses(const struct wally_descriptor *descriptor, addresses[i] = NULL; } } - wally_free(ctx.path_buff); + ctx_clear(&ctx); wally_free(p); return ret; } @@ -3402,25 +3925,29 @@ int wally_descriptor_get_num_keys(const struct wally_descriptor *descriptor, static const ms_node *descriptor_get_key(const struct wally_descriptor *descriptor, size_t index) { - if (!descriptor || index >= descriptor->keys.num_items) + if (!descriptor) return NULL; - return (ms_node *)descriptor->keys.items[index].value; -} -int wally_descriptor_get_key(const struct wally_descriptor *descriptor, - size_t index, char **output) -{ - const ms_node *node = NULL; -#ifdef BUILD_ELEMENTS if (index == WALLY_MS_BLINDING_KEY_INDEX) { - if (descriptor && node_is_ct(descriptor->top_node)) { + const ms_node *node = NULL; + if (node_is_ct(descriptor->top_node)) { node = descriptor->top_node->child; if (node && node->kind == KIND_DESCRIPTOR_SLIP77) node = node->child; + else if (node && node->kind == KIND_DESCRIPTOR_ELIP151) + node = NULL; /* FIXME: Support ELIP-151 derivation */ } - } else -#endif - node = descriptor_get_key(descriptor, index); + return node; + } + if (index >= descriptor->keys.num_items) + return NULL; + return (ms_node *)descriptor->keys.items[index].value; +} + +int wally_descriptor_get_key(const struct wally_descriptor *descriptor, + size_t index, char **output) +{ + const ms_node *node = descriptor_get_key(descriptor, index); if (output) *output = 0; @@ -3458,17 +3985,7 @@ int wally_descriptor_get_key(const struct wally_descriptor *descriptor, int wally_descriptor_get_key_features(const struct wally_descriptor *descriptor, size_t index, uint32_t *value_out) { - const ms_node *node = NULL; -#ifdef BUILD_ELEMENTS - if (index == WALLY_MS_BLINDING_KEY_INDEX) { - if (descriptor && node_is_ct(descriptor->top_node)) { - node = descriptor->top_node->child; - if (node && node->kind == KIND_DESCRIPTOR_SLIP77) - node = node->child; - } - } else -#endif - node = descriptor_get_key(descriptor, index); + const ms_node *node = descriptor_get_key(descriptor, index); if (value_out) *value_out = 0; @@ -3556,6 +4073,48 @@ int wally_descriptor_get_key_origin_path_str( return WALLY_OK; } +int wally_descriptor_derive_bip32_key( + const struct wally_descriptor *descriptor, size_t index, uint32_t variant, + uint32_t multi_index, uint32_t child_num, uint32_t flags, struct ext_key* output) +{ + ms_ctx ctx, *ctx_p = NULL; + const ms_node *node = descriptor_get_key(descriptor, index); + int ret; + + if (output) + wally_clear(output, sizeof(*output)); + if (!node || !index_args_valid(descriptor, variant, multi_index, child_num) || + flags & ~(BIP32_FLAG_KEY_PUBLIC|BIP32_FLAG_SKIP_HASH) || !output) + return WALLY_EINVAL; + if ((node->kind & KIND_BIP32) == KIND_BIP32 && node->child_path_len) { + /* Non-static key: create context required for deriving */ + ret = ctx_clone(descriptor, variant, multi_index, child_num, &ctx); + if (ret != WALLY_OK) + return ret; + ctx_p = &ctx; + } + ret = node_derive_key(ctx_p, node, flags, output); + if (ctx_p) + ctx_clear(ctx_p); + return ret; +} + +int wally_descriptor_derive_bip32_key_alloc( + const struct wally_descriptor *descriptor, size_t index, uint32_t variant, + uint32_t multi_index, uint32_t child_num, uint32_t flags, struct ext_key** output) +{ + int ret; + OUTPUT_CHECK; + OUTPUT_ALLOC(struct ext_key); + ret = wally_descriptor_derive_bip32_key(descriptor, index, variant, multi_index, + child_num, flags, *output); + if (ret != WALLY_OK) { + clear_and_free(*output, sizeof(struct ext_key)); + *output = NULL; + } + return ret; +} + static const char *get_multipath_child(const char* p, uint32_t *v) { *v = 0; @@ -3616,3 +4175,228 @@ static int ensure_unique_policy_keys(const ms_ctx *ctx) } return WALLY_OK; } + +/* Fetch the leaf_index'th leaf (DFS order) of a tr() descriptor's taptree */ +static int tr_get_leaf(const struct wally_descriptor *descriptor, + uint32_t leaf_index, ms_node **leaf) +{ + *leaf = NULL; + if (descriptor && descriptor->features & WALLY_MS_IS_TAPSCRIPT) { + ms_node *taptree = tr_get_tree(descriptor); + if (taptree) + *leaf = find_taptree_leaf(taptree, leaf_index, NULL); + } + return *leaf ? WALLY_OK : WALLY_EINVAL; +} + +int wally_descriptor_get_taproot_num_leaves( + const struct wally_descriptor *descriptor, + uint32_t *value_out) +{ + ms_node *taptree; + + if (value_out) + *value_out = 0; + if (!descriptor || !value_out || + !(descriptor->features & WALLY_MS_IS_TAPROOT)) + return WALLY_EINVAL; + /* Return the number of leaves, or zero for key-only tr() expressions */ + if ((taptree = tr_get_tree(descriptor)) != NULL) + *value_out = count_taptree_leaves(taptree); + return WALLY_OK; +} + +int wally_descriptor_get_taproot_leaf_script( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + ms_ctx ctx; + ms_node *leaf; + int ret; + + if (written) + *written = 0; + if (!index_args_valid(descriptor, 0, multi_index, child_num) || + flags || BYTES_INVALID(bytes_out, len) || !written) + return WALLY_EINVAL; + if ((ret = tr_get_leaf(descriptor, leaf_index, &leaf)) != WALLY_OK || + (ret = ctx_clone(descriptor, 0, multi_index, child_num, &ctx)) != WALLY_OK) + return ret; + + /* leaf->parent->kind == KIND_BRANCH => node_is_root() is true */ + ret = generate_script(&ctx, leaf, bytes_out, len, written); + ctx_clear(&ctx); + return ret; +} + +int wally_descriptor_get_taproot_leaf_script_len( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + size_t *written) +{ + return wally_descriptor_get_taproot_leaf_script(descriptor, leaf_index, + multi_index, child_num, + flags, NULL, 0, written); +} + +int wally_descriptor_get_taproot_leaf_hash( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len) +{ + ms_ctx ctx; + ms_node *leaf; + int ret; + + if (!index_args_valid(descriptor, 0, multi_index, child_num) || + flags || !bytes_out || len != SHA256_LEN) + return WALLY_EINVAL; + if ((ret = tr_get_leaf(descriptor, leaf_index, &leaf)) != WALLY_OK || + (ret = ctx_clone(descriptor, 0, multi_index, child_num, &ctx)) != WALLY_OK) + return ret; + + ret = leaf_tapleaf_hash(&ctx, leaf, bytes_out, len); + ctx_clear(&ctx); + return ret; +} + +int wally_descriptor_get_taproot_control_block( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + unsigned char p2tr[WALLY_SCRIPTPUBKEY_P2TR_LEN]; /* OP_1 [x-only internal key] */ + unsigned char merkle_root[SHA256_LEN]; + unsigned char tweaked[EC_PUBLIC_KEY_LEN]; + ms_ctx ctx; + uint32_t path_len = 0; + int ret; + + ret = wally_descriptor_get_taproot_control_block_len(descriptor, + leaf_index, multi_index, child_num, flags, written); + if (ret != WALLY_OK || BYTES_INVALID(bytes_out, len)) + return WALLY_EINVAL; + + if (!bytes_out || len < *written) + return WALLY_OK; /* Size query, or buffer too small to generate into */ + + if ((ret = ctx_clone(descriptor, 0, multi_index, child_num, &ctx)) != WALLY_OK) + return ret; + + /* Generate an untweaked p2tr script OP_1 [x-only internal key], + * collecting the path and merkle root if a taptree is present. */ + size_t unused; + ret = tr_impl(&ctx, p2tr, sizeof(p2tr), leaf_index, + bytes_out + 1 + EC_XONLY_PUBLIC_KEY_LEN, &path_len, + merkle_root, sizeof(merkle_root), + tweaked, sizeof(tweaked), &unused); + + if (ret == WALLY_OK) { + /* Leaf version is ORed with the output key parity, per BIP-341 */ + bytes_out[0] = WALLY_LEAF_VERSION_TAPSCRIPT | (tweaked[0] & 1); + /* Followed by the (untweaked) x-only internal key */ + memcpy(bytes_out + 1, p2tr + 2, sizeof(p2tr) - 2); + /* Followed by the path already written by tr_impl() above */ + } + ctx_clear(&ctx); + return ret; +} + +int wally_descriptor_get_taproot_control_block_len( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, uint32_t multi_index, + uint32_t child_num, uint32_t flags, + size_t *written) +{ + uint32_t leaf_depth; + + if (written) + *written = 0; + if (!index_args_valid(descriptor, 0, multi_index, child_num) || + flags || !written || !(descriptor->features & WALLY_MS_IS_TAPSCRIPT)) + return WALLY_EINVAL; + + if (!find_taptree_leaf(tr_get_tree(descriptor), leaf_index, &leaf_depth)) + return WALLY_EINVAL; /* leaf_index out of range */ + + /* Control block size is determined by the depth of the leaf */ + *written = 1u + EC_XONLY_PUBLIC_KEY_LEN + (size_t)leaf_depth * SHA256_LEN; + return WALLY_OK; +} + +static int leaf_num_keys_impl(const struct wally_descriptor *descriptor, + uint32_t leaf_index, uint32_t key_index, + uint32_t *value_out, bool get_num_keys) +{ + ms_node *leaf, *key_node; + int ret; + + if (value_out) + *value_out = 0; + else + return WALLY_EINVAL; + if ((ret = tr_get_leaf(descriptor, leaf_index, &leaf)) != WALLY_OK) + return ret; + + if (get_num_keys) { + *value_out = count_keys_in_subtree(leaf); + return WALLY_OK; + } + if ((key_node = find_nth_key_in_subtree(leaf, key_index)) != NULL) { + for (uint32_t i = 0; i < descriptor->keys.num_items; i++) { + if ((ms_node *)descriptor->keys.items[i].value == key_node) { + *value_out = i; /* Found: return descriptor-level key index */ + return WALLY_OK; + } + } + } + return WALLY_EINVAL; /* key not found in map (should not happen) */ +} + +int wally_descriptor_get_taproot_leaf_num_keys( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t *value_out) +{ + return leaf_num_keys_impl(descriptor, leaf_index, 0, value_out, true); +} + +int wally_descriptor_get_taproot_leaf_key_index( + const struct wally_descriptor *descriptor, + uint32_t leaf_index, + uint32_t key_index, + uint32_t *value_out) +{ + return leaf_num_keys_impl(descriptor, leaf_index, key_index, value_out, false); +} + +int wally_descriptor_get_taproot_merkle_root( + const struct wally_descriptor *descriptor, + uint32_t multi_index, uint32_t child_num, uint32_t flags, + unsigned char *bytes_out, size_t len) +{ + ms_ctx ctx; + int ret; + + if (!index_args_valid(descriptor, 0, multi_index, child_num) || + flags || !bytes_out || len != SHA256_LEN || + !(descriptor->features & WALLY_MS_IS_TAPSCRIPT)) + return WALLY_EINVAL; + + ret = ctx_clone(descriptor, 0, multi_index, child_num, &ctx); + if (ret == WALLY_OK) { + ms_node *taptree = tr_get_tree(&ctx); + uint32_t index = 0, path_len = 0; + bool found = false; + ret = compute_merkle_info(&ctx, taptree, 0xffffffff, + &index, NULL, &path_len, + bytes_out, len, &found); + } + ctx_clear(&ctx); + return ret; +} diff --git a/src/map.c b/src/map.c index 084d84572..43c658e79 100644 --- a/src/map.c +++ b/src/map.c @@ -2,6 +2,7 @@ #include #include +#include #include #include "psbt_io.h" @@ -669,7 +670,8 @@ int wally_merkle_path_xonly_public_key_verify(const unsigned char *key, size_t k keypath_key_verify(key, key_len, &extkey) != WALLY_OK || extkey.version || BYTES_INVALID(val, val_len)) return WALLY_EINVAL; - if (val_len && (val_len % SHA256_LEN || val_len % SHA256_LEN > 128u)) + if (val_len && (val_len % SHA256_LEN || + val_len % SHA256_LEN > WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH)) return WALLY_EINVAL; return WALLY_OK; } diff --git a/src/swig_java/jni_extra.java_in b/src/swig_java/jni_extra.java_in index 51c6ca2eb..0c6c680f1 100644 --- a/src/swig_java/jni_extra.java_in +++ b/src/swig_java/jni_extra.java_in @@ -516,6 +516,20 @@ return checkBuffer(buf, len); } + public final static byte[] descriptor_get_taproot_leaf_script(Object descriptor, long leaf_index, long multi_index, long child_num, long flags) { + final byte[] buf = new byte[descriptor_get_taproot_leaf_script_len(descriptor, leaf_index, multi_index, child_num, flags)]; + if (buf.length == 0) return buf; + final int len = descriptor_get_taproot_leaf_script(descriptor, leaf_index, multi_index, child_num, flags, buf); + return checkBuffer(buf, len); + } + + public final static byte[] descriptor_get_taproot_control_block(Object descriptor, long leaf_index, long multi_index, long child_num, long flags) { + final byte[] buf = new byte[descriptor_get_taproot_control_block_len(descriptor, leaf_index, multi_index, child_num, flags)]; + if (buf.length == 0) return buf; + final int len = descriptor_get_taproot_control_block(descriptor, leaf_index, multi_index, child_num, flags, buf); + return checkBuffer(buf, len); + } + public final static void cleanup() { _cleanup(0); } diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index 633eb0c30..7fad6f1f8 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -341,6 +341,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %ignore bip32_key_init; %ignore bip32_key_unserialize; %ignore bip32_key_with_tweak_from_parent_path; +%ignore wally_descriptor_derive_bip32_key; %ignore wally_map_init; %ignore wally_map_keypath_get_bip32_key_from; %ignore wally_psbt_blind; @@ -577,6 +578,8 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %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_struct(wally_descriptor_derive_bip32_key_alloc, ext_key); +%rename("descriptor_derive_bip32_key") wally_descriptor_derive_bip32_key_alloc; %returns_string(wally_descriptor_get_checksum); %returns_size_t(wally_descriptor_get_depth); %returns_size_t(wally_descriptor_get_features); @@ -591,6 +594,15 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_size_t(wally_descriptor_get_num_keys); %returns_size_t(wally_descriptor_get_num_paths); %returns_size_t(wally_descriptor_get_num_variants); +%returns_size_t(wally_descriptor_get_taproot_control_block); +%returns_size_t(wally_descriptor_get_taproot_control_block_len); +%returns_size_t(wally_descriptor_get_taproot_leaf_key_index); +%returns_size_t(wally_descriptor_get_taproot_leaf_num_keys); +%returns_size_t(wally_descriptor_get_taproot_leaf_script); +%returns_size_t(wally_descriptor_get_taproot_leaf_script_len); +%returns_array_(wally_descriptor_get_taproot_leaf_hash, 6, 7, SHA256_LEN); +%returns_array_(wally_descriptor_get_taproot_merkle_root, 5, 6, SHA256_LEN ); +%returns_size_t(wally_descriptor_get_taproot_num_leaves); %returns_void__(wally_descriptor_set_network); %returns_void__(wally_descriptor_free); %returns_struct(wally_descriptor_parse, wally_descriptor); diff --git a/src/swig_python/python_extra.py_in b/src/swig_python/python_extra.py_in index 3a05b8f95..733795e1e 100644 --- a/src/swig_python/python_extra.py_in +++ b/src/swig_python/python_extra.py_in @@ -99,13 +99,16 @@ if is_elements_build(): def elements_pegin_contract_script_from_bytes_len(rs, cs, flags): return len(rs) -# Work around SWIG wrapping unisgned constants +# Work around SWIG wrapping unsigned constants # See https://github.com/swig/swig/issues/1287 BIP32_INITIAL_HARDENED_CHILD = 0x80000000 +WALLY_MINISCRIPT_DEPTH_MASK = 0xffff0000 +WALLY_MS_BLINDING_KEY_INDEX = 0xffffffff +WALLY_NO_CODESEPARATOR = 0xffffffff WALLY_PSET_BLIND_ALL = 0xffffffff WALLY_SATOSHI_MAX = WALLY_BTC_MAX * WALLY_SATOSHI_PER_BTC +WALLY_TX_ISSUANCE_FLAG = 0x80000000 WALLY_TX_SEQUENCE_FINAL = 0xffffffff -WALLY_NO_CODESEPARATOR = 0xffffffff # BEGIN AUTOGENERATED addr_segwit_n_to_bytes = _wrap_bin(addr_segwit_n_to_bytes, WALLY_SEGWIT_ADDRESS_PUBKEY_MAX_LEN, resize=True) @@ -148,7 +151,12 @@ bip39_mnemonic_to_bytes = _wrap_bin(bip39_mnemonic_to_bytes, BIP39_ENTROPY_MAX_L bip39_mnemonic_to_seed512 = _wrap_bin(bip39_mnemonic_to_seed512, BIP39_SEED_LEN_512) bip85_get_bip39_entropy = _wrap_bin(bip85_get_bip39_entropy, HMAC_SHA512_LEN, resize=True) bip85_get_rsa_entropy = _wrap_bin(bip85_get_rsa_entropy, HMAC_SHA512_LEN, resize=True) +descriptor_derive_bip32_key = descriptor_derive_bip32_key_alloc descriptor_get_key_origin_fingerprint = _wrap_bin(descriptor_get_key_origin_fingerprint, BIP32_KEY_FINGERPRINT_LEN) +descriptor_get_taproot_control_block = _wrap_bin(descriptor_get_taproot_control_block, descriptor_get_taproot_control_block_len) +descriptor_get_taproot_leaf_hash = _wrap_bin(descriptor_get_taproot_leaf_hash, SHA256_LEN) +descriptor_get_taproot_leaf_script = _wrap_bin(descriptor_get_taproot_leaf_script, descriptor_get_taproot_leaf_script_len) +descriptor_get_taproot_merkle_root = _wrap_bin(descriptor_get_taproot_merkle_root, SHA256_LEN) descriptor_to_script = _wrap_bin(descriptor_to_script, descriptor_to_script_get_maximum_length, resize=True) ec_private_key_bip341_tweak = _wrap_bin(ec_private_key_bip341_tweak, EC_PRIVATE_KEY_LEN) ec_public_key_bip341_tweak = _wrap_bin(ec_public_key_bip341_tweak, EC_PUBLIC_KEY_LEN) diff --git a/src/swig_python/swig.i b/src/swig_python/swig.i index 262077a4f..039c66292 100644 --- a/src/swig_python/swig.i +++ b/src/swig_python/swig.i @@ -414,6 +414,7 @@ static void destroy_words(PyObject *obj) { (void)obj; } %ignore bip32_key_init; %ignore bip32_key_unserialize; %ignore bip32_key_with_tweak_from_parent_path; +%ignore wally_descriptor_derive_bip32_key; %ignore wally_map_init; %ignore wally_map_keypath_get_bip32_key_from; %ignore wally_psbt_blind; diff --git a/src/test/test_descriptor.py b/src/test/test_descriptor.py index 6cc5b541c..9d69657f0 100644 --- a/src/test/test_descriptor.py +++ b/src/test/test_descriptor.py @@ -21,7 +21,7 @@ MS_IS_MULTIPATH = 0x2 MS_IS_PRIVATE = 0x4 MS_IS_UNCOMPRESSED = 0x08 -MS_IS_RAW = 0x010 +MS_IS_RAW = 0x10 MS_IS_DESCRIPTOR = 0x20 MS_IS_X_ONLY = 0x40 MS_IS_PARENTED = 0x80 @@ -29,11 +29,16 @@ MS_IS_SLIP77 = 0x200 MS_IS_ELIP150 = 0x400 MS_IS_ELIP151 = 0x800 +MS_IS_TAPROOT = 0x1000 +MS_IS_TAPSCRIPT = 0x2000 NO_CHECKSUM = 0x1 # WALLY_MS_CANONICAL_NO_CHECKSUM BLINDING_KEY_INDEX = 0xffffffff +FLAG_KEY_PRIVATE = 0x0 +FLAG_KEY_PUBLIC = 0x1 + def wally_map_from_dict(d): m = pointer(wally_map()) assert(wally_map_init_alloc(len(d.keys()), None, m) == WALLY_OK) @@ -44,14 +49,16 @@ def wally_map_from_dict(d): class DescriptorTests(unittest.TestCase): + keys = wally_map_from_dict({ + 'key_local': '038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048', + 'key_remote': '03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7', + 'key_revocation': '03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284', + 'H': 'd0721279e70d39fb4aa409b52839a0056454e3b5', # HASH160(key_local) + 'x_only': 'b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e', + }) + def test_parse_and_to_script(self): """Test parsing and script generation""" - keys = wally_map_from_dict({ - 'key_local': '038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048', - 'key_remote': '03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7', - 'key_revocation': '03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284', - 'H': 'd0721279e70d39fb4aa409b52839a0056454e3b5', # HASH160(key_local) - }) script, script_len = make_cbuffer('00' * 256 * 2) # Valid args @@ -63,7 +70,7 @@ def test_parse_and_to_script(self): ] for miniscript, child_num, expected in args: d = c_void_p() - ret = wally_descriptor_parse(miniscript, keys, NETWORK_NONE, MS_ONLY, d) + ret = wally_descriptor_parse(miniscript, self.keys, NETWORK_NONE, MS_ONLY, d) self.assertEqual(ret, WALLY_OK) ret, written = wally_descriptor_to_script(d, 0, 0, 0, 0, child_num, 0, script, script_len) @@ -71,7 +78,46 @@ def test_parse_and_to_script(self): self.assertEqual(written, len(expected) / 2) self.assertEqual(script[:written], make_cbuffer(expected)[0]) wally_descriptor_free(d) - wally_map_free(keys) + + # pk_k and pk_h fragment tests: (miniscript, flags, expected_hex) + pk_args = [ + ('c:pk_k(key_local)', MS_ONLY, + '21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac'), + ('c:pk_h(key_local)', MS_ONLY, + '76a914d0721279e70d39fb4aa409b52839a0056454e3b588ac'), + ('c:pk_k(x_only)', MS_ONLY | MS_TAP, + '20b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0eac'), + ] + for miniscript, flags, expected in pk_args: + d = c_void_p() + ret = wally_descriptor_parse(miniscript, self.keys, NETWORK_NONE, flags, d) + self.assertEqual(ret, WALLY_OK) + ret, written = wally_descriptor_to_script(d, 0, 0, 0, 0, 0, 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) + + # hash fragment tests: (miniscript, flags, expected_hex) + hash_args = [ + ('sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2)', MS_ONLY, + '82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed287'), + ('hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b)', MS_ONLY, + '82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b87'), + ('ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba)', MS_ONLY, + '82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87'), + ('hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07)', MS_ONLY, + '82012088a91420195b5a3d650c17f0f29f91c33f8f6335193d0787'), + ] + for hash_ms, flags, expected in hash_args: + d = c_void_p() + ret = wally_descriptor_parse(hash_ms, self.keys, NETWORK_NONE, flags, d) + self.assertEqual(ret, WALLY_OK) + ret, written = wally_descriptor_to_script(d, 0, 0, 0, 0, 0, 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) # Invalid args M, U = NETWORK_BTC_MAIN, 0x33 # Unknown network @@ -268,16 +314,22 @@ def test_features_and_depth(self): 0, MS_IS_PRIVATE, 5, 2), (f'or_d(thresh(1,pk({k1})),and_v(v:thresh(1,pk({k2}/)),older(30)))', MS_ONLY, MS_IS_PRIVATE, 5, 2), + # tr() key-path only: MS_IS_TAPSCRIPT must NOT be set + (f'tr({k1})', + 0, MS_IS_DESCRIPTOR|MS_IS_TAPROOT, 2, 1), + # tr() with taptree: MS_IS_TAPSCRIPT must be set + (f'tr({k1},{{pk({k1}),pk({k1})}})', + 0, MS_IS_DESCRIPTOR|MS_IS_TAPROOT|MS_IS_TAPSCRIPT|MS_IS_X_ONLY, 4, 3), ] if is_elements_build: slip77 = 'ct(slip77(b2396b3ee20509cdb64fe24180a14a72dbd671728eaa49bac69d2bdecb5f5a04),elpkh(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))' cases.extend([ # Parsing a descriptor as elements returns elements in its features (f'tr({k1})', - AS_ELEMENTS, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS, 2, 1), + AS_ELEMENTS, MS_IS_DESCRIPTOR|MS_IS_TAPROOT|MS_IS_ELEMENTS, 2, 1), # el-prefixed builtins return elements in their features (f'eltr({k1})', - 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS, 2, 1), + 0, MS_IS_DESCRIPTOR|MS_IS_TAPROOT|MS_IS_ELEMENTS, 2, 1), # Note that ct() blinding keys aren't returned in the key count. # slip77 builtins return elements and slip77 in their features, # and the ct() parent wrapper is included in their depth. @@ -335,12 +387,14 @@ def test_features_and_depth(self): flags | (5 << 16), d) self.assertEqual(ret, WALLY_EINVAL) + def test_policy(self): """Test policy parsing""" # Substitution variables slip77 = 'b2396b3ee20509cdb64fe24180a14a72dbd671728eaa49bac69d2bdecb5f5a04' xpriv = 'xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU' xpub1 = 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL' + xpub1_pubkey = '02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0' xpub2 = 'xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU' def make_keys(xpubs): @@ -400,7 +454,7 @@ def make_keys(xpubs): [P, 'ct(@B,elpkh(@0/*))', {'@B': xpub1, '@0': xpub2}], ] d = c_void_p() - for flags, policy, key_items in cases: + for i, (flags, policy, key_items) in enumerate(cases): keys = wally_map_from_dict(key_items) ret = wally_descriptor_parse(policy, keys, NETWORK_LIQUID, flags, d) self.assertEqual(ret, WALLY_OK) @@ -410,6 +464,26 @@ def make_keys(xpubs): self.assertEqual((ret, key_str), (WALLY_OK, key_items['@0'])) ret, key_info = wally_descriptor_get_key(d, BLINDING_KEY_INDEX) self.assertEqual((ret, key_info), (WALLY_OK, key_items['@B'])) + key_out = ext_key() + ret, features = wally_descriptor_get_key_features(d, BLINDING_KEY_INDEX) + self.assertEqual(ret, WALLY_OK) + ret = wally_descriptor_derive_bip32_key(d, BLINDING_KEY_INDEX, + 0, 0, 0, 0, key_out) + self.assertEqual(ret, WALLY_OK) + if i == 0: + # SLIP-77 blinding keys are returned in the private key + self.assertEqual(key_out.priv_key[0], FLAG_KEY_PRIVATE) + self.assertEqual(bytes(key_out.priv_key[1:]).hex(), slip77) + self.assertEqual(features, MS_IS_RAW|MS_IS_SLIP77) + elif i == 1: + self.assertEqual(key_out.priv_key[0], FLAG_KEY_PRIVATE) + self.assertEqual(bytes(key_out.priv_key[1:]).hex(), slip77) + self.assertEqual(features, MS_IS_RAW|MS_IS_PRIVATE) + else: + self.assertEqual(key_out.priv_key[0], FLAG_KEY_PUBLIC) + self.assertEqual(bytes(key_out.pub_key).hex(), xpub1_pubkey) + self.assertEqual(features, 0) # Standard unranged xpub + wally_map_free(keys) wally_descriptor_free(d) @@ -495,6 +569,144 @@ def test_key_iteration(self): self.assertEqual((ret, path_str), (WALLY_OK, expected_path)) wally_descriptor_free(d) + def test_wrappers(self): + """Test miniscript wrapper expressions (a:, s:, c:, d:, v:, j:, n:, l:, u:, t:)""" + script, script_len = make_cbuffer('00' * 256 * 2) + + # pk_k push: 21 <33-byte compressed pubkey> + pk_push = '21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048' + + # (miniscript, expected_script_hex) + # In libwally, multiple wrappers use a single colon with all chars before it, + # e.g. "ac:pk_k" applies c: first then a: (wrappers applied in reverse order). + # c: [pk_k] CHECKSIG + c_pk = pk_push + 'ac' + # vc: pk_k CHECKSIGVERIFY (v: replaces trailing CHECKSIG with CHECKSIGVERIFY) + vc_pk = pk_push + 'ad' + + wrapper_cases = [ + # c: wrapper — pk_k(K) -> [K] CHECKSIG + ('c:pk_k(key_local)', c_pk), + # a: wrapper — TOALTSTACK [X] FROMALTSTACK (X = c:pk_k, type B) + ('ac:pk_k(key_local)', '6b' + c_pk + '6c'), + # s: wrapper — SWAP [X] (X = c:pk_k, type Bo) + ('sc:pk_k(key_local)', '7c' + c_pk), + # v: wrapper — replaces trailing CHECKSIG with CHECKSIGVERIFY + ('vc:pk_k(key_local)', vc_pk), + # d: wrapper — DUP IF [X] ENDIF (X = v:older(1), type Vz) + # older(1) = OP_1(51) OP_CSV(b2); v: appends OP_VERIFY(69) since CSV not replaceable + ('dv:older(1)', '7663' + '51b269' + '68'), + # j: wrapper — SIZE 0NOTEQUAL IF [X] ENDIF (X = c:pk_k, type Bn) + ('jc:pk_k(key_local)', '829263' + c_pk + '68'), + # n: wrapper — [X] 0NOTEQUAL (X = c:pk_k, type B) + ('nc:pk_k(key_local)', c_pk + '92'), + # l: wrapper — or_i(0, X): IF 0 ELSE [X] ENDIF (X = c:pk_k, type B) + ('lc:pk_k(key_local)', '630067' + c_pk + '68'), + # u: wrapper — or_i(X, 0): IF [X] ELSE 0 ENDIF (X = c:pk_k, type B) + ('uc:pk_k(key_local)', '63' + c_pk + '670068'), + # t: wrapper — and_v(X, 1): [X] OP_1 (X = vc:pk_k, type V) + ('tvc:pk_k(key_local)', vc_pk + '51'), + ] + + for miniscript, expected in wrapper_cases: + d = c_void_p() + ret = wally_descriptor_parse(miniscript, self.keys, NETWORK_NONE, MS_ONLY, d) + self.assertEqual(ret, WALLY_OK, f'parse failed for: {miniscript}') + ret, written = wally_descriptor_to_script(d, 0, 0, 0, 0, 0, 0, script, script_len) + self.assertEqual(ret, WALLY_OK, f'to_script failed for: {miniscript}') + self.assertEqual(written, len(expected) // 2, + f'wrong length for: {miniscript}') + self.assertEqual(script[:written], make_cbuffer(expected)[0], + f'wrong script for: {miniscript}') + wally_descriptor_free(d) + + def test_composite_descriptors(self): + """Test composite miniscript expressions (and_v, or_d, andor) including Liana-style templates""" + script, script_len = make_cbuffer('00' * 512 * 2) + + cases = [ + # Case A: Liana-like recovery leaf — key + timelock + # and_v(X,Y) -> [X][Y] + # vc:pk_k -> push(K) OP_CHECKSIGVERIFY; older(52560=0xCD50) -> 03 50 CD 00 OP_CSV + ('and_v(vc:pk_k(key_local),older(52560))', MS_ONLY, + '21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048' + 'ad0350cd00b2'), + # Case B: Primary key OR (recovery key + timelock) + # or_d(X,Y) -> [X] OP_IFDUP(73) OP_NOTIF(64) [Y] OP_ENDIF(68) + ('or_d(c:pk_k(key_local),and_v(vc:pk_k(key_remote),older(52560)))', MS_ONLY, + '21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac' + '73642103a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7ad' + '0350cd00b268'), + # Case C: andor — if primary key succeeds use timelock, else use revocation key + # andor(X,Y,Z) -> [X] OP_NOTIF(64) [Z] OP_ELSE(67) [Y] OP_ENDIF(68) + ('andor(c:pk_k(key_local),older(52560),c:pk_k(key_revocation))', MS_ONLY, + '21038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048ac' + '642103b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284ac' + '670350cd00b268'), + # Case D: Tapscript — x-only key uses 32-byte push (opcode 20) + ('and_v(vc:pk_k(x_only),older(52560))', MS_ONLY | MS_TAP, + '20b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e' + 'ad0350cd00b2'), + ] + + for miniscript, flags, expected in cases: + d = c_void_p() + ret = wally_descriptor_parse(miniscript, self.keys, NETWORK_NONE, flags, d) + self.assertEqual(ret, WALLY_OK, f'parse failed for: {miniscript}') + ret, written = wally_descriptor_to_script(d, 0, 0, 0, 0, 0, 0, script, script_len) + self.assertEqual(ret, WALLY_OK, f'to_script failed for: {miniscript}') + self.assertEqual(written, len(expected) // 2, f'wrong length for: {miniscript}') + self.assertEqual(script[:written], make_cbuffer(expected)[0], f'wrong script for: {miniscript}') + wally_descriptor_free(d) + + def test_taproot_bad_args(self): + buf, buf_len = make_cbuffer('00' * 1024) + d = c_void_p() + ret = wally_descriptor_parse('tr(x_only,pk(key_local))', + self.keys, NETWORK_BTC_MAIN, 0, d) + self.assertEqual(ret, WALLY_OK) + for args in [ + (None, 0, 0, 0, 0, buf, buf_len), # NULL descriptor + (d, 1, 0, 0, 0, buf, buf_len), # Invalid leaf_index + (d, 0, 1, 0, 0, buf, buf_len), # Invalid multi_index + (d, 0, 0, 1, 0, buf, buf_len), # Invalid child_num + (d, 0, 0, 0, 1, buf, buf_len), # Invalid flags + (None, 0, 0, 0, 0, None, buf_len), # NULL output buff + (None, 0, 0, 0, 0, buf, 0), # Empty output buff + ]: + ret = wally_descriptor_get_taproot_control_block(*args) + self.assertEqual(ret, (WALLY_EINVAL, 0)) + ret = wally_descriptor_get_taproot_control_block_len(*(args[:-2])) + self.assertEqual(ret, (WALLY_EINVAL, 0)) + ret = wally_descriptor_get_taproot_leaf_script(*args) + self.assertEqual(ret, (WALLY_EINVAL, 0)) + ret = wally_descriptor_get_taproot_leaf_script_len(*(args[:-2])) + self.assertEqual(ret, (WALLY_EINVAL, 0)) + ret = wally_descriptor_get_taproot_leaf_hash(*(args[:-1] + (32,))) + self.assertEqual(ret, WALLY_EINVAL) + if args[1] != 1: + # Not a leaf_index test case, test calls that don't take it + merkle_internal_args = (args[0],) + args[2:-1] + (32,) + ret = wally_descriptor_get_taproot_merkle_root(*merkle_internal_args) + self.assertEqual(ret, WALLY_EINVAL) + + ret = wally_descriptor_get_taproot_num_leaves(None) # NULL descriptor + self.assertEqual(ret, (WALLY_EINVAL, 0)) + + for args in [ + (None, 0), # NULL descriptor + (d, 1), # Invalid leaf_index + ]: + ret = wally_descriptor_get_taproot_leaf_num_keys(*args) + self.assertEqual(ret, (WALLY_EINVAL, 0)) + + for args in [ + (None, 0, 0), # NULL descriptor + (d, 1, 0), # Invalid leaf_index + (d, 0, 1), # Invalid key_index + ]: + ret = wally_descriptor_get_taproot_leaf_key_index(*args) + self.assertEqual(ret, (WALLY_EINVAL, 0)) if __name__ == '__main__': unittest.main() diff --git a/src/test/util.py b/src/test/util.py index 36dccfcf7..db9873d3a 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -321,6 +321,8 @@ class wally_psbt(Structure): ('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_derive_bip32_key', c_int, [c_void_p, c_size_t, c_uint32, c_uint32, c_uint32, c_uint32, POINTER(ext_key)]), + ('wally_descriptor_derive_bip32_key_alloc', c_int, [c_void_p, c_size_t, c_uint32, c_uint32, c_uint32, c_uint32, POINTER(POINTER(ext_key))]), ('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_depth', c_int, [c_void_p, c_uint32_p]), @@ -336,6 +338,15 @@ class wally_psbt(Structure): ('wally_descriptor_get_num_keys', c_int, [c_void_p, c_uint32_p]), ('wally_descriptor_get_num_paths', c_int, [c_void_p, c_uint32_p]), ('wally_descriptor_get_num_variants', c_int, [c_void_p, c_uint32_p]), + ('wally_descriptor_get_taproot_control_block', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, c_size_t, c_size_t_p]), + ('wally_descriptor_get_taproot_control_block_len', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_size_t_p]), + ('wally_descriptor_get_taproot_leaf_hash', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, c_size_t]), + ('wally_descriptor_get_taproot_leaf_key_index', c_int, [c_void_p, c_uint32, c_uint32, c_uint32_p]), + ('wally_descriptor_get_taproot_leaf_num_keys', c_int, [c_void_p, c_uint32, c_uint32_p]), + ('wally_descriptor_get_taproot_leaf_script', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_void_p, c_size_t, c_size_t_p]), + ('wally_descriptor_get_taproot_leaf_script_len', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_size_t_p]), + ('wally_descriptor_get_taproot_merkle_root', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_void_p, c_size_t]), + ('wally_descriptor_get_taproot_num_leaves', c_int, [c_void_p, c_uint32_p]), ('wally_descriptor_parse', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_uint32, POINTER(c_void_p)]), ('wally_descriptor_set_network', c_int, [c_void_p, c_uint32]), ('wally_descriptor_to_address', c_int, [c_void_p, c_uint32, c_uint32, c_uint32, c_uint32, c_char_p_p]), diff --git a/src/tx_io.c b/src/tx_io.c index 0256b71d1..d8ef2f6c3 100644 --- a/src/tx_io.c +++ b/src/tx_io.c @@ -1,4 +1,5 @@ #include "internal.h" +#include #include #include "pullpush.h" #include "script.h" @@ -62,6 +63,11 @@ static const unsigned char TAPLEAF_SHA256[SHA256_LEN] = { 0xae, 0xea, 0x8f, 0xdc, 0x42, 0x08, 0x98, 0x31, 0x05, 0x73, 0x4b, 0x58, 0x08, 0x1d, 0x1e, 0x26, 0x38, 0xd3, 0x5f, 0x1c, 0xb5, 0x40, 0x08, 0xd4, 0xd3, 0x57, 0xca, 0x03, 0xbe, 0x78, 0xe9, 0xee }; +/* SHA256(TapBranch) */ +static const unsigned char TAPBRANCH_SHA256[SHA256_LEN] = { + 0x19, 0x41, 0xa1, 0xf2, 0xe5, 0x6e, 0xb9, 0x5f, 0xa2, 0xa9, 0xf1, 0x94, 0xbe, 0x5c, 0x01, 0xf7, + 0x21, 0x6f, 0x33, 0xed, 0x82, 0xb0, 0x91, 0x46, 0x34, 0x90, 0xd0, 0x5b, 0xf5, 0x16, 0xa0, 0x15 +}; #ifdef BUILD_ELEMENTS /* SHA256(TapSighash/elements) */ @@ -74,11 +80,18 @@ static const unsigned char TAPLEAF_SHA256_ELEMENTS[SHA256_LEN] = { 0x69, 0xff, 0xb5, 0x5a, 0xb8, 0xc8, 0x1c, 0x21, 0xf5, 0x8b, 0x2a, 0xdc, 0xb0, 0x83, 0x5a, 0x08, 0x60, 0x8a, 0xf5, 0x9d, 0x04, 0x2f, 0x03, 0x37, 0x64, 0x33, 0x9c, 0xd8, 0xe6, 0xba, 0x33, 0xe7 }; +/* SHA256(TapBranch/elements) */ +static const unsigned char TAPBRANCH_SHA256_ELEMENTS[SHA256_LEN] = { + 0xa0, 0x37, 0x22, 0xab, 0xcb, 0x0d, 0x54, 0xaa, 0x9e, 0x6b, 0x38, 0x93, 0xe9, 0xf8, 0x13, 0x7d, + 0x90, 0x60, 0x21, 0x9c, 0x42, 0xcc, 0x13, 0x9c, 0x32, 0xc2, 0xeb, 0x67, 0x08, 0x0d, 0x73, 0xc6 +}; #define TAPSIGHASH_SHA256(is_elements) (is_elements ? TAPSIGHASH_SHA256_ELEMENTS : TAPSIGHASH_SHA256) #define TAPLEAF_SHA256(is_elements) (is_elements ? TAPLEAF_SHA256_ELEMENTS : TAPLEAF_SHA256) +#define TAPBRANCH_SHA256(is_elements) (is_elements ? TAPBRANCH_SHA256_ELEMENTS : TAPBRANCH_SHA256) #else #define TAPSIGHASH_SHA256(is_elements) TAPSIGHASH_SHA256 #define TAPLEAF_SHA256(is_elements) TAPLEAF_SHA256 +#define TAPBRANCH_SHA256(is_elements) TAPBRANCH_SHA256 #endif /* BUILD_ELEMENTS */ static bool script_len_ok(size_t len) { return len != 0; } @@ -559,7 +572,63 @@ static void txio_hash_annex(cursor_io *io, } } -static void txio_hash_tapleaf_hash(cursor_io *io, +int bip341_tapbranch_hash(const unsigned char *lhs, size_t lhs_len, + const unsigned char *rhs, size_t rhs_len, + bool is_elements, unsigned char *bytes_out, size_t len) +{ + struct sha256_ctx ctx; + struct sha256 hash; + + if (!lhs || lhs_len != SHA256_LEN || !rhs || rhs_len != SHA256_LEN || + !bytes_out || len != SHA256_LEN) + return WALLY_EINVAL; + +#ifndef BUILD_ELEMENTS + if (is_elements) + return WALLY_EINVAL; +#endif + + tagged_hash_init(&ctx, TAPBRANCH_SHA256(is_elements), SHA256_LEN); + /* BIP-341: child hashes are sorted lexicographically before hashing so + * the merkle path doesn't need to encode left/right direction. + * If k_j < e_j: k_{j+1} = hash_TapBranch(k_j || e_j) + * If k_j >= e_j: k_{j+1} = hash_TapBranch(e_j || k_j) */ + if (memcmp(lhs, rhs, lhs_len) > 0) { + const unsigned char *tmp = lhs; + lhs = rhs; + rhs = tmp; + } + hash_bytes(&ctx, lhs, lhs_len); + hash_bytes(&ctx, rhs, rhs_len); + sha256_done(&ctx, &hash); + memcpy(bytes_out, hash.u.u8, sizeof(hash)); + return WALLY_OK; +} + +int bip341_tapleaf_hash(unsigned char leaf_version, + const unsigned char *script, size_t script_len, + bool is_elements, unsigned char *bytes_out, size_t len) +{ + struct sha256_ctx ctx; + struct sha256 hash; + + if (!bytes_out || len != SHA256_LEN) + return WALLY_EINVAL; + +#ifndef BUILD_ELEMENTS + if (is_elements) + return WALLY_EINVAL; +#endif + + tagged_hash_init(&ctx, TAPLEAF_SHA256(is_elements), SHA256_LEN); + hash_u8(&ctx, leaf_version); + hash_varbuff(&ctx, script, script_len); + sha256_done(&ctx, &hash); + memcpy(bytes_out, hash.u.u8, sizeof(hash)); + return WALLY_OK; +} + +static void txio_hash_tapleaf_hash(cursor_io *io, unsigned char leaf_version, const unsigned char *tapleaf_script, size_t tapleaf_script_len, bool is_elements) { @@ -571,15 +640,12 @@ static void txio_hash_tapleaf_hash(cursor_io *io, if (item) { hash_bytes(&io->ctx, item->value, item->value_len); } else { - struct sha256_ctx ctx; - struct sha256 hash; - tagged_hash_init(&ctx, TAPLEAF_SHA256(is_elements), SHA256_LEN); - hash_u8(&ctx, 0xc0); /* leaf_version */ - hash_varbuff(&ctx, tapleaf_script, tapleaf_script_len); - sha256_done(&ctx, &hash); - hash_bytes(&io->ctx, hash.u.u8, sizeof(hash)); + unsigned char hash[SHA256_LEN]; + bip341_tapleaf_hash(leaf_version, tapleaf_script, tapleaf_script_len, + is_elements, hash, sizeof(hash)); + hash_bytes(&io->ctx, hash, sizeof(hash)); if (io->cache) - wally_map_add(io->cache, tapleaf_script, tapleaf_script_len, hash.u.u8, sizeof(hash)); + wally_map_add(io->cache, tapleaf_script, tapleaf_script_len, hash, sizeof(hash)); } } @@ -949,7 +1015,8 @@ static int bip341_signature_hash( /* Tapscript Extensions */ if (tapleaf_script) { if (!sh_anyprevout_anyscript) - txio_hash_tapleaf_hash(&io, tapleaf_script, tapleaf_script_len, is_elements); + txio_hash_tapleaf_hash(&io, WALLY_LEAF_VERSION_TAPSCRIPT, + tapleaf_script, tapleaf_script_len, is_elements); hash_u8(&io.ctx, key_version & 0xff); hash_le32(&io.ctx, codesep_position); } diff --git a/src/tx_io.h b/src/tx_io.h index 41e3b323c..af600bcc7 100644 --- a/src/tx_io.h +++ b/src/tx_io.h @@ -23,4 +23,12 @@ void tagged_hash_init(struct sha256_ctx *ctx, void hash_varbuff(struct sha256_ctx *ctx, const unsigned char *bytes, size_t bytes_len); +int bip341_tapbranch_hash(const unsigned char *lhs, size_t lhs_len, + const unsigned char *rhs, size_t rhs_len, + bool is_elements, unsigned char *bytes_out, size_t len); + +int bip341_tapleaf_hash(unsigned char leaf_version, + const unsigned char *script, size_t script_len, + bool is_elements, unsigned char *bytes_out, size_t len); + #endif /* LIBWALLY_CORE_TX_IO_H */ diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index eafc5171e..b12fec2c8 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -114,10 +114,12 @@ export const WALLY_CA_PREFIX_LIQUID = 0x0c; /** Liquid v1 confidential address p export const WALLY_CA_PREFIX_LIQUID_REGTEST = 0x04; /** Liquid v1 confidential address prefix for regtest */ export const WALLY_CA_PREFIX_LIQUID_TESTNET = 0x17; /** Liquid v1 confidential address prefix for testnet */ export const WALLY_CS_MAX_ASSETS = 256; +export const WALLY_DESCRIPTOR_TAPTREE_MAX_DEPTH = 128; /* BIP-341: maximum taptree depth */ export const WALLY_EINVAL = -2; /** Invalid argument */ export const WALLY_ENOMEM = -3; /** malloc() failed */ export const WALLY_ERROR = -1; /** General error */ export const WALLY_HOST_COMMITMENT_LEN = 32; +export const WALLY_LEAF_VERSION_TAPSCRIPT = 0xc0; /** BIP-342 tapscript leaf version */ export const WALLY_MAJOR_VER = 1; export const WALLY_MAX_OP_RETURN_LEN = 80; /* Maximum length of OP_RETURN data push */ export const WALLY_MINISCRIPT_AS_ELEMENTS = 0x20; /** Treat non-elements expressions as elements, e.g. tr() as eltr() */ @@ -129,21 +131,23 @@ export const WALLY_MINISCRIPT_REQUIRE_CHECKSUM = 0x04; /** Require a checksum to export const WALLY_MINISCRIPT_TAPSCRIPT = 0x01; /** Tapscript, use x-only pubkeys */ export const WALLY_MINISCRIPT_UNIQUE_KEYPATHS = 0x10; /** For policy templates, ensure BIP32 derivation paths differ for identical keys */ export const WALLY_MINOR_VER = 5; -export const WALLY_MS_ANY_BLINDING_KEY = 0xE00; /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ +export const WALLY_MS_ANY_BLINDING_KEY = 0x0E00; /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ export const WALLY_MS_BLINDING_KEY_INDEX = 0xffffffff; /* Key index for confidential blinding key */ export const WALLY_MS_CANONICAL_NO_CHECKSUM = 0x01; /** Do not include a checksum */ -export const WALLY_MS_IS_DESCRIPTOR = 0x020; /** Contains only descriptor expressions (no miniscript) */ -export const WALLY_MS_IS_ELEMENTS = 0x100; /** Contains Elements expressions or was parsed as Elements */ -export const WALLY_MS_IS_ELIP150 = 0x400; /** A confidential ct() descriptor with ELIP-150 blinding */ -export const WALLY_MS_IS_ELIP151 = 0x800; /** A confidential ct() descriptor with ELIP-151 blinding */ -export const WALLY_MS_IS_MULTIPATH = 0x002; /** Allows multiple paths via ```` */ -export const WALLY_MS_IS_PARENTED = 0x080; /** Contains at least one key key with a parent key origin */ -export const WALLY_MS_IS_PRIVATE = 0x004; /** Contains at least one private key */ -export const WALLY_MS_IS_RANGED = 0x001; /** Allows key ranges via ``*`` */ -export const WALLY_MS_IS_RAW = 0x010; /** Contains at least one raw key */ -export const WALLY_MS_IS_SLIP77 = 0x200; /** A confidential ct() descriptor with SLIP-77 blinding */ -export const WALLY_MS_IS_UNCOMPRESSED = 0x008; /** Contains at least one uncompressed key */ -export const WALLY_MS_IS_X_ONLY = 0x040; /** Contains at least one x-only key */ +export const WALLY_MS_IS_DESCRIPTOR = 0x0020; /** Contains only descriptor expressions (no miniscript) */ +export const WALLY_MS_IS_ELEMENTS = 0x0100; /** Contains Elements expressions or was parsed as Elements */ +export const WALLY_MS_IS_ELIP150 = 0x0400; /** A confidential ct() descriptor with ELIP-150 blinding */ +export const WALLY_MS_IS_ELIP151 = 0x0800; /** A confidential ct() descriptor with ELIP-151 blinding */ +export const WALLY_MS_IS_MULTIPATH = 0x0002; /** Allows multiple paths via ```` */ +export const WALLY_MS_IS_PARENTED = 0x0080; /** Contains at least one key with a parent key origin */ +export const WALLY_MS_IS_PRIVATE = 0x0004; /** Contains at least one private key */ +export const WALLY_MS_IS_RANGED = 0x0001; /** Allows key ranges via ``*`` */ +export const WALLY_MS_IS_RAW = 0x0010; /** Contains at least one raw key */ +export const WALLY_MS_IS_SLIP77 = 0x0200; /** A confidential ct() descriptor with SLIP-77 blinding */ +export const WALLY_MS_IS_TAPROOT = 0x1000; /** Contains a tr() taproot expression */ +export const WALLY_MS_IS_TAPSCRIPT = 0x2000; /** Contains a tr(key,{...}) tapscript tree expression */ +export const WALLY_MS_IS_UNCOMPRESSED = 0x0008; /** Contains at least one uncompressed key */ +export const WALLY_MS_IS_X_ONLY = 0x0040; /** Contains at least one x-only key */ 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 */ diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index e2f0ee340..5419d0a07 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -164,6 +164,8 @@ export const confidential_addr_to_addr = wrap('wally_confidential_addr_to_addr', 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_derive_bip32_key = wrap('wally_descriptor_derive_bip32_key_alloc', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrPtr(T.OpaqueRef)]); +export const descriptor_derive_bip32_key_noalloc = wrap('wally_descriptor_derive_bip32_key', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.Int32, T.OpaqueRef]); 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_depth = wrap('wally_descriptor_get_depth', [T.OpaqueRef, T.DestPtr(T.Int32)]); @@ -177,6 +179,13 @@ export const descriptor_get_network = wrap('wally_descriptor_get_network', [T.Op export const descriptor_get_num_keys = wrap('wally_descriptor_get_num_keys', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const descriptor_get_num_paths = wrap('wally_descriptor_get_num_paths', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const descriptor_get_num_variants = wrap('wally_descriptor_get_num_variants', [T.OpaqueRef, T.DestPtr(T.Int32)]); +export const descriptor_get_taproot_control_block_len = wrap('wally_descriptor_get_taproot_control_block_len', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtr(T.Int32)]); +export const descriptor_get_taproot_leaf_hash = wrap('wally_descriptor_get_taproot_leaf_hash', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrSized(T.Bytes, C.SHA256_LEN)]); +export const descriptor_get_taproot_leaf_key_index = wrap('wally_descriptor_get_taproot_leaf_key_index', [T.OpaqueRef, T.Int32, T.Int32, T.DestPtr(T.Int32)]); +export const descriptor_get_taproot_leaf_num_keys = wrap('wally_descriptor_get_taproot_leaf_num_keys', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); +export const descriptor_get_taproot_leaf_script_len = wrap('wally_descriptor_get_taproot_leaf_script_len', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtr(T.Int32)]); +export const descriptor_get_taproot_merkle_root = wrap('wally_descriptor_get_taproot_merkle_root', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.DestPtrSized(T.Bytes, C.SHA256_LEN)]); +export const descriptor_get_taproot_num_leaves = wrap('wally_descriptor_get_taproot_num_leaves', [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_set_network = wrap('wally_descriptor_set_network', [T.OpaqueRef, T.Int32]); export const descriptor_to_address = wrap('wally_descriptor_to_address', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrPtr(T.String)]); @@ -815,6 +824,8 @@ export const bip32_path_from_str = wrap('bip32_path_from_str', [T.String, T.Int3 export const bip32_path_from_str_n = wrap('bip32_path_from_str_n', [T.String, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Uint32Array, bip32_path_from_str_n_len, false)]); export const descriptor_get_key_child_path_str = wrap('wally_descriptor_get_key_child_path_str', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); export const descriptor_get_key_origin_path_str = wrap('wally_descriptor_get_key_origin_path_str', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); +export const descriptor_get_taproot_control_block = wrap('wally_descriptor_get_taproot_control_block', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, descriptor_get_taproot_control_block_len, false)]); +export const descriptor_get_taproot_leaf_script = wrap('wally_descriptor_get_taproot_leaf_script', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, descriptor_get_taproot_leaf_script_len, false)]); export const descriptor_to_script = wrap('wally_descriptor_to_script', [T.OpaqueRef, T.Int32, 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 ec_sig_from_bytes_aux = wrap('wally_ec_sig_from_bytes_aux', [T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, ec_sig_from_bytes_aux_len, false)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index c2d76c209..aa4db6289 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -124,6 +124,8 @@ export function confidential_addr_to_addr(address: string, prefix: number): stri 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_derive_bip32_key(descriptor: Ref_wally_descriptor, index: number, variant: number, multi_index: number, child_num: number, flags: number): Ref_ext_key; +export function descriptor_derive_bip32_key_noalloc(descriptor: Ref_wally_descriptor, index: number, variant: number, multi_index: number, child_num: number, flags: number, output: Ref_ext_key): void; 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_depth(descriptor: Ref_wally_descriptor): number; @@ -137,6 +139,13 @@ export function descriptor_get_network(descriptor: Ref_wally_descriptor): number export function descriptor_get_num_keys(descriptor: Ref_wally_descriptor): number; export function descriptor_get_num_paths(descriptor: Ref_wally_descriptor): number; export function descriptor_get_num_variants(descriptor: Ref_wally_descriptor): number; +export function descriptor_get_taproot_control_block_len(descriptor: Ref_wally_descriptor, leaf_index: number, multi_index: number, child_num: number, flags: number): number; +export function descriptor_get_taproot_leaf_hash(descriptor: Ref_wally_descriptor, leaf_index: number, multi_index: number, child_num: number, flags: number): Buffer; +export function descriptor_get_taproot_leaf_key_index(descriptor: Ref_wally_descriptor, leaf_index: number, key_index: number): number; +export function descriptor_get_taproot_leaf_num_keys(descriptor: Ref_wally_descriptor, leaf_index: number): number; +export function descriptor_get_taproot_leaf_script_len(descriptor: Ref_wally_descriptor, leaf_index: number, multi_index: number, child_num: number, flags: number): number; +export function descriptor_get_taproot_merkle_root(descriptor: Ref_wally_descriptor, multi_index: number, child_num: number, flags: number): Buffer; +export function descriptor_get_taproot_num_leaves(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_set_network(descriptor: Ref_wally_descriptor, network: number): void; export function descriptor_to_address(descriptor: Ref_wally_descriptor, variant: number, multi_index: number, child_num: number, flags: number): string; @@ -775,6 +784,8 @@ export function bip32_path_from_str(path_str: string, child_num: number, multi_i export function bip32_path_from_str_n(path_str: string, path_str_len: number, child_num: number, multi_index: number, flags: number): Uint32Array; export function descriptor_get_key_child_path_str(descriptor: Ref_wally_descriptor, index: number): string; export function descriptor_get_key_origin_path_str(descriptor: Ref_wally_descriptor, index: number): string; +export function descriptor_get_taproot_control_block(descriptor: Ref_wally_descriptor, leaf_index: number, multi_index: number, child_num: number, flags: number): Buffer; +export function descriptor_get_taproot_leaf_script(descriptor: Ref_wally_descriptor, leaf_index: number, multi_index: number, child_num: number, flags: number): Buffer; export function descriptor_to_script(descriptor: Ref_wally_descriptor, depth: number, index: number, variant: number, multi_index: number, child_num: number, flags: number): Buffer; export function ec_sig_from_bytes(priv_key: Buffer|Uint8Array|null, bytes: Buffer|Uint8Array|null, flags: number): Buffer; export function ec_sig_from_bytes_aux(priv_key: Buffer|Uint8Array|null, bytes: Buffer|Uint8Array|null, aux_rand: Buffer|Uint8Array|null, flags: number): Buffer; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index 8aa077686..ec7e4c05f 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -87,6 +87,8 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_bzero' \ ,'_wally_cleanup' \ ,'_wally_descriptor_canonicalize' \ +,'_wally_descriptor_derive_bip32_key' \ +,'_wally_descriptor_derive_bip32_key_alloc' \ ,'_wally_descriptor_free' \ ,'_wally_descriptor_get_checksum' \ ,'_wally_descriptor_get_depth' \ @@ -102,6 +104,15 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_descriptor_get_num_keys' \ ,'_wally_descriptor_get_num_paths' \ ,'_wally_descriptor_get_num_variants' \ +,'_wally_descriptor_get_taproot_control_block' \ +,'_wally_descriptor_get_taproot_control_block_len' \ +,'_wally_descriptor_get_taproot_leaf_hash' \ +,'_wally_descriptor_get_taproot_leaf_key_index' \ +,'_wally_descriptor_get_taproot_leaf_num_keys' \ +,'_wally_descriptor_get_taproot_leaf_script' \ +,'_wally_descriptor_get_taproot_leaf_script_len' \ +,'_wally_descriptor_get_taproot_merkle_root' \ +,'_wally_descriptor_get_taproot_num_leaves' \ ,'_wally_descriptor_parse' \ ,'_wally_descriptor_set_network' \ ,'_wally_descriptor_to_address' \