From f0d81214765374f2b0bfc29e922d254defa23cc3 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Fri, 8 Sep 2023 00:21:07 +1200 Subject: [PATCH] descriptor: allow iterating keys --- include/wally.hpp | 6 ++++ include/wally_descriptor.h | 37 ++++++++++++------- src/descriptor.c | 60 ++++++++++++++++++++++++------- src/swig_java/swig.i | 1 + src/test/test_descriptor.py | 30 ++++++++++++++++ src/test/util.py | 1 + src/wasm_package/src/functions.js | 1 + src/wasm_package/src/index.d.ts | 1 + tools/wasm_exports.sh | 1 + 9 files changed, 114 insertions(+), 24 deletions(-) diff --git a/include/wally.hpp b/include/wally.hpp index dd0746ea4..5e743e5bc 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -504,6 +504,12 @@ inline int descriptor_get_features(const DESCRIPTOR& descriptor, uint32_t* value return ret; } +template +inline int descriptor_get_key(const DESCRIPTOR& descriptor, size_t index, char** output) { + int ret = ::wally_descriptor_get_key(detail::get_p(descriptor), index, output); + return ret; +} + template inline int descriptor_get_network(const DESCRIPTOR& descriptor, uint32_t* value_out) { int ret = ::wally_descriptor_get_network(detail::get_p(descriptor), value_out); diff --git a/include/wally_descriptor.h b/include/wally_descriptor.h index 108107a60..afa9ef37f 100644 --- a/include/wally_descriptor.h +++ b/include/wally_descriptor.h @@ -131,18 +131,6 @@ WALLY_CORE_API int wally_descriptor_get_features( const struct wally_descriptor *descriptor, uint32_t *value_out); -/** - * Get the number of keys in a parsed output descriptor or miniscript expression. - * - * :param descriptor: Parsed output descriptor or miniscript expression. - * :param value_out: Destination for the number of keys. - * -* .. note:: Repeated keys are counted once for each time they appear. - */ -WALLY_CORE_API int wally_descriptor_get_num_keys( - const struct wally_descriptor *descriptor, - uint32_t *value_out); - /** * Get the number of variants in a parsed output descriptor or miniscript expression. * @@ -193,6 +181,31 @@ WALLY_CORE_API int wally_descriptor_get_depth( const struct wally_descriptor *descriptor, uint32_t *value_out); +/** + * Get the number of keys in a parsed output descriptor or miniscript expression. + * + * :param descriptor: Parsed output descriptor or miniscript expression. + * :param value_out: Destination for the number of keys. + * +* .. note:: Repeated keys are counted once for each time they appear. + */ +WALLY_CORE_API int wally_descriptor_get_num_keys( + const struct wally_descriptor *descriptor, + uint32_t *value_out); + +/** + * Get the string representation of a key in 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. + * :param output: Destination for the resulting string representation. + *| The string returned should be freed using `wally_free_string`. + */ +WALLY_CORE_API int wally_descriptor_get_key( + const struct wally_descriptor *descriptor, + size_t index, + char **output); + /** * Get the maximum length of a script corresponding to an output descriptor. * diff --git a/src/descriptor.c b/src/descriptor.c index d677c1442..90f8526ef 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -206,7 +206,7 @@ static int ctx_add_key_node(ms_ctx *ctx, ms_node *node) { const char *v = (char *)node; return map_add(&ctx->keys, NULL, ctx->keys.num_items, - (unsigned char *)v, 1, false, false); + (unsigned char *)v, 1, true, false); } /* Built-in miniscript expressions */ @@ -2840,17 +2840,6 @@ int wally_descriptor_get_features(const struct wally_descriptor *descriptor, offsetof(struct wally_descriptor, features)); } -int wally_descriptor_get_num_keys(const struct wally_descriptor *descriptor, - uint32_t *value_out) -{ - if (value_out) - *value_out = 0; - if (!descriptor || !value_out) - return WALLY_EINVAL; - *value_out = (uint32_t)descriptor->keys.num_items; - return WALLY_OK; -} - int wally_descriptor_get_num_variants(const struct wally_descriptor *descriptor, uint32_t *value_out) { @@ -2887,3 +2876,50 @@ int wally_descriptor_get_depth(const struct wally_descriptor *descriptor, *value_out = node_get_depth(descriptor->top_node) - 1; return WALLY_OK; } + +int wally_descriptor_get_num_keys(const struct wally_descriptor *descriptor, + uint32_t *value_out) +{ + if (value_out) + *value_out = 0; + if (!descriptor || !value_out) + return WALLY_EINVAL; + *value_out = (uint32_t)descriptor->keys.num_items; + return WALLY_OK; +} + +/* Ignore incorrect warnings from the ms_node cast below */ +#pragma GCC diagnostic ignored "-Wcast-align" +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wcast-align" +#endif + +int wally_descriptor_get_key(const struct wally_descriptor *descriptor, + size_t index, char **output) +{ + const ms_node *node; + + if (output) + *output = 0; + if (!descriptor || index >= descriptor->keys.num_items || !output) + return WALLY_EINVAL; + + node = (ms_node *)descriptor->keys.items[index].value; + if (node->kind == KIND_PUBLIC_KEY) { + return wally_hex_from_bytes((const unsigned char *)node->data, + node->data_len, output); + } + if (node->kind == KIND_PRIVATE_KEY) { + uint32_t flags = node->flags & NF_IS_UNCOMPRESSED ? WALLY_WIF_FLAG_UNCOMPRESSED : 0; + if (!descriptor->addr_ver) + return WALLY_EINVAL; /* Must have a network to fetch private keys */ + return wally_wif_from_bytes((const unsigned char *)node->data, node->data_len, + descriptor->addr_ver->version_wif, + flags, output); + } + if ((node->kind & KIND_BIP32) != KIND_BIP32) + return WALLY_ERROR; /* Unknown key type, should not happen */ + if (!(*output = wally_strdup_n(node->data, node->data_len))) + return WALLY_ENOMEM; + return WALLY_OK; +} diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index 04335f93f..2a526b5fc 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -561,6 +561,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_size_t(wally_descriptor_get_depth); %returns_size_t(wally_descriptor_get_features); %returns_size_t(wally_descriptor_get_network); +%returns_string(wally_descriptor_get_key); %returns_size_t(wally_descriptor_get_num_keys); %returns_size_t(wally_descriptor_get_num_paths); %returns_size_t(wally_descriptor_get_num_variants); diff --git a/src/test/test_descriptor.py b/src/test/test_descriptor.py index be2b1a029..3a4ee8edd 100644 --- a/src/test/test_descriptor.py +++ b/src/test/test_descriptor.py @@ -314,5 +314,35 @@ def make_keys(xpubs): self.assertEqual(ret, WALLY_EINVAL) wally_map_free(keys) + def test_key_iteration(self): + """Test iterating descriptor keys""" + k1 = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB' + k2 = 'xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU' + wif = 'L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM' + pk = '03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284' + pk_u = '0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf' + # Valid args + for descriptor, expected in [ + # Bip32 xpub + (f'pkh({k1})', k1), + # BIP32 xprv + (f'pkh({k2}/*)', k2), + # WIF + (f'pkh({wif})', wif), + # Hex pubkey, compressed + (f'pk({pk})', pk), + # Hex pubkey, uncompressed + (f'pk({pk_u})', pk_u), + ]: + d = c_void_p() + ret = wally_descriptor_parse(descriptor, None, NETWORK_BTC_MAIN, 0, d) + self.assertEqual(ret, WALLY_OK) + ret, num_keys = wally_descriptor_get_num_keys(d) + self.assertEqual((ret, num_keys), (WALLY_OK, 1)) + ret, key_str = wally_descriptor_get_key(d, 0) + self.assertEqual((ret, key_str), (WALLY_OK, expected)) + wally_descriptor_free(d) + + if __name__ == '__main__': unittest.main() diff --git a/src/test/util.py b/src/test/util.py index 22b354ee5..52bc41ab7 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -313,6 +313,7 @@ class wally_psbt(Structure): ('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]), ('wally_descriptor_get_features', c_int, [c_void_p, c_uint32_p]), + ('wally_descriptor_get_key', c_int, [c_void_p, c_size_t, c_char_p_p]), ('wally_descriptor_get_network', c_int, [c_void_p, c_uint32_p]), ('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]), diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index 82bc174cd..9a5f03ffb 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -171,6 +171,7 @@ 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)]); export const descriptor_get_features = wrap('wally_descriptor_get_features', [T.OpaqueRef, T.DestPtr(T.Int32)]); +export const descriptor_get_key = wrap('wally_descriptor_get_key', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); export const descriptor_get_network = wrap('wally_descriptor_get_network', [T.OpaqueRef, T.DestPtr(T.Int32)]); 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)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 69bddea94..67ec487c0 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -123,6 +123,7 @@ 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; export function descriptor_get_features(descriptor: Ref_wally_descriptor): number; +export function descriptor_get_key(descriptor: Ref_wally_descriptor, index: number): string; 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; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index 540fa7ea3..c65c05678 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -81,6 +81,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_descriptor_get_checksum' \ ,'_wally_descriptor_get_depth' \ ,'_wally_descriptor_get_features' \ +,'_wally_descriptor_get_key' \ ,'_wally_descriptor_get_network' \ ,'_wally_descriptor_get_num_keys' \ ,'_wally_descriptor_get_num_paths' \