Skip to content

Commit f419bb0

Browse files
committed
descriptor: allow fetching key origin info from keys
1 parent d705b70 commit f419bb0

File tree

11 files changed

+184
-22
lines changed

11 files changed

+184
-22
lines changed

include/wally.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,24 @@ inline int descriptor_get_key_features(const DESCRIPTOR& descriptor, size_t inde
528528
return ret;
529529
}
530530

531+
template <class DESCRIPTOR, class BYTES_OUT>
532+
inline int descriptor_get_key_origin_fingerprint(const DESCRIPTOR& descriptor, size_t index, BYTES_OUT& bytes_out) {
533+
int ret = ::wally_descriptor_get_key_origin_fingerprint(detail::get_p(descriptor), index, bytes_out.data(), bytes_out.size());
534+
return ret;
535+
}
536+
537+
template <class DESCRIPTOR>
538+
inline int descriptor_get_key_origin_path_str(const DESCRIPTOR& descriptor, size_t index, char** output) {
539+
int ret = ::wally_descriptor_get_key_origin_path_str(detail::get_p(descriptor), index, output);
540+
return ret;
541+
}
542+
543+
template <class DESCRIPTOR>
544+
inline int descriptor_get_key_origin_path_str_len(const DESCRIPTOR& descriptor, size_t index, size_t* written) {
545+
int ret = ::wally_descriptor_get_key_origin_path_str_len(detail::get_p(descriptor), index, written);
546+
return ret;
547+
}
548+
531549
template <class DESCRIPTOR>
532550
inline int descriptor_get_network(const DESCRIPTOR& descriptor, uint32_t* value_out) {
533551
int ret = ::wally_descriptor_get_network(detail::get_p(descriptor), value_out);

include/wally_descriptor.h

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ struct wally_descriptor;
2828
#define WALLY_MS_IS_RAW 0x10 /** Contains at least one raw key */
2929
#define WALLY_MS_IS_DESCRIPTOR 0x20 /** Contains only descriptor expressions (no miniscript) */
3030
#define WALLY_MS_IS_X_ONLY 0x40 /** Contains at least one x-only key */
31+
#define WALLY_MS_IS_PARENTED 0x80 /** Contains at least one key key with a parent key origin */
3132

3233
/*** ms-canonicalization-flags Miniscript/Descriptor canonicalization flags */
3334
#define WALLY_MS_CANONICAL_NO_CHECKSUM 0x01 /** Do not include a checksum */
@@ -230,7 +231,7 @@ WALLY_CORE_API int wally_descriptor_get_key_features(
230231
* :param descriptor: Parsed output descriptor or miniscript expression.
231232
* :param index: The zero-based index of the key whose child path to get.
232233
* :param written: Destination for the length of the keys child path string,
233-
*| excluding the NUL terminator.
234+
*| excluding the NUL terminator (zero if not present).
234235
*/
235236
WALLY_CORE_API int wally_descriptor_get_key_child_path_str_len(
236237
const struct wally_descriptor *descriptor,
@@ -242,14 +243,55 @@ WALLY_CORE_API int wally_descriptor_get_key_child_path_str_len(
242243
*
243244
* :param descriptor: Parsed output descriptor or miniscript expression.
244245
* :param index: The zero-based index of the key whose child path to get.
245-
* :param output: Destination for the resulting path string (may be empty).
246+
* :param output: Destination for the resulting path string (empty if not present).
246247
*| The string returned should be freed using `wally_free_string`.
247248
*/
248249
WALLY_CORE_API int wally_descriptor_get_key_child_path_str(
249250
const struct wally_descriptor *descriptor,
250251
size_t index,
251252
char **output);
252253

254+
/**
255+
* Get the keys parent BIP32 fingerprint in a parsed output descriptor or miniscript expression.
256+
*
257+
* :param descriptor: Parsed output descriptor or miniscript expression.
258+
* :param index: The zero-based index of the key whose parent fingerprint to get.
259+
* :param bytes_out: Destination for the fingerprint.
260+
* FIXED_SIZED_OUTPUT(len, bytes_out, BIP32_KEY_FINGERPRINT_LEN)
261+
*
262+
* If the key does not contain key origin information then `WALLY_EINVAL is returned.
263+
*/
264+
WALLY_CORE_API int wally_descriptor_get_key_origin_fingerprint(
265+
const struct wally_descriptor *descriptor,
266+
size_t index,
267+
unsigned char *bytes_out,
268+
size_t len);
269+
270+
/**
271+
* Get the length of a keys parent path string in a parsed output descriptor or miniscript expression.
272+
*
273+
* :param descriptor: Parsed output descriptor or miniscript expression.
274+
* :param index: The zero-based index of the key whose parent path to get.
275+
* :param written: Destination for the length of the keys parent path string,
276+
*| excluding the NUL terminator (zero if not present).
277+
*/
278+
WALLY_CORE_API int wally_descriptor_get_key_origin_path_str_len(
279+
const struct wally_descriptor *descriptor,
280+
size_t index,
281+
size_t *written);
282+
283+
/**
284+
* Get the keys parent path string in a parsed output descriptor or miniscript expression.
285+
*
286+
* :param descriptor: Parsed output descriptor or miniscript expression.
287+
* :param index: The zero-based index of the key whose parent path to get.
288+
* :param output: Destination for the resulting path string (empty if not present).
289+
*| The string returned should be freed using `wally_free_string`.
290+
*/
291+
WALLY_CORE_API int wally_descriptor_get_key_origin_path_str(
292+
const struct wally_descriptor *descriptor,
293+
size_t index,
294+
char **output);
253295

254296
/**
255297
* Get the maximum length of a script corresponding to an output descriptor.

src/descriptor.c

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2085,7 +2085,12 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags,
20852085
(node->data[9] != ']' && node->data[9] != '/'))
20862086
return WALLY_EINVAL; /* Invalid key origin fingerprint */
20872087
size = end - node->data + 1;
2088-
/* cut parent path */
2088+
/* Store offset and length of any origin info in the number field */
2089+
node->number = (size_t)(node->data - ctx->src) << 32u;
2090+
node->number |= size;
2091+
ctx->features |= WALLY_MS_IS_PARENTED;
2092+
node->flags |= WALLY_MS_IS_PARENTED;
2093+
/* Remove the key origin info from the key data */
20892094
node->data = end + 1;
20902095
node->data_len -= size;
20912096
}
@@ -2980,6 +2985,57 @@ int wally_descriptor_get_key_child_path_str(
29802985
return WALLY_OK;
29812986
}
29822987

2988+
int wally_descriptor_get_key_origin_fingerprint(
2989+
const struct wally_descriptor *descriptor, size_t index,
2990+
unsigned char *bytes_out, size_t len)
2991+
{
2992+
const ms_node *node = descriptor_get_key(descriptor, index);
2993+
const char *fingerprint;
2994+
size_t written;
2995+
int ret;
2996+
2997+
if (!node || !bytes_out || len != BIP32_KEY_FINGERPRINT_LEN ||
2998+
!(node->flags & WALLY_MS_IS_PARENTED))
2999+
return WALLY_EINVAL;
3000+
fingerprint = descriptor->src + (((size_t)node->number) >> 32u) + 1;
3001+
ret = wally_hex_n_to_bytes(fingerprint, BIP32_KEY_FINGERPRINT_LEN * 2,
3002+
bytes_out, len, &written);
3003+
return ret == WALLY_OK && written != BIP32_KEY_FINGERPRINT_LEN ? WALLY_EINVAL : ret;
3004+
}
3005+
3006+
int wally_descriptor_get_key_origin_path_str_len(
3007+
const struct wally_descriptor *descriptor, size_t index, size_t *written)
3008+
{
3009+
const ms_node *node = descriptor_get_key(descriptor, index);
3010+
3011+
if (written)
3012+
*written = 0;
3013+
if (!node || !written)
3014+
return WALLY_EINVAL;
3015+
*written = node->flags & WALLY_MS_IS_PARENTED ? node->number & 0xffffffff : 0;
3016+
*written = *written < 11u ? 0 : *written - 11u;
3017+
return WALLY_OK;
3018+
}
3019+
3020+
int wally_descriptor_get_key_origin_path_str(
3021+
const struct wally_descriptor *descriptor, size_t index, char **output)
3022+
{
3023+
const ms_node *node = descriptor_get_key(descriptor, index);
3024+
const char *path;
3025+
size_t path_len;
3026+
3027+
if (output)
3028+
*output = NULL;
3029+
if (!node || !output)
3030+
return WALLY_EINVAL;
3031+
path_len = node->flags & WALLY_MS_IS_PARENTED ? node->number & 0xffffffff : 0;
3032+
path_len = path_len < 11u ? 0 : path_len - 11u;
3033+
path = descriptor->src + (((size_t)node->number) >> 32u) + 10u;
3034+
if (!(*output = wally_strdup_n(path, path_len)))
3035+
return WALLY_ENOMEM;
3036+
return WALLY_OK;
3037+
}
3038+
29833039
static const char *get_multipath_child(const char* p, uint32_t *v)
29843040
{
29853041
*v = 0;

src/swig_java/swig.i

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,9 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
565565
%returns_string(wally_descriptor_get_key_child_path_str);
566566
%returns_size_t(wally_descriptor_get_key_child_path_str_len);
567567
%returns_size_t(wally_descriptor_get_key_features);
568+
%returns_array_(wally_descriptor_get_key_origin_fingerprint, 3, 4, BIP32_KEY_FINGERPRINT_LEN);
569+
%returns_string(wally_descriptor_get_key_origin_path_str);
570+
%returns_size_t(wally_descriptor_get_key_origin_path_str_len);
568571
%returns_size_t(wally_descriptor_get_num_keys);
569572
%returns_size_t(wally_descriptor_get_num_paths);
570573
%returns_size_t(wally_descriptor_get_num_variants);

src/swig_python/python_extra.py_in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ bip38_to_private_key = _wrap_bin(bip38_to_private_key, EC_PRIVATE_KEY_LEN)
150150
bip39_mnemonic_to_bytes = _wrap_bin(bip39_mnemonic_to_bytes, BIP39_ENTROPY_MAX_LEN, resize=True)
151151
bip39_mnemonic_to_seed512 = _wrap_bin(bip39_mnemonic_to_seed512, BIP39_SEED_LEN_512)
152152
bip85_get_bip39_entropy = _wrap_bin(bip85_get_bip39_entropy, HMAC_SHA512_LEN, resize=True)
153+
descriptor_get_key_origin_fingerprint = _wrap_bin(descriptor_get_key_origin_fingerprint, BIP32_KEY_FINGERPRINT_LEN)
153154
descriptor_to_script = _wrap_bin(descriptor_to_script, descriptor_to_script_get_maximum_length, resize=True)
154155
ec_private_key_bip341_tweak = _wrap_bin(ec_private_key_bip341_tweak, EC_PRIVATE_KEY_LEN)
155156
ec_public_key_bip341_tweak = _wrap_bin(ec_public_key_bip341_tweak, EC_PUBLIC_KEY_LEN)

src/test/test_descriptor.py

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
POLICY = 0x08 # WALLY_MINISCRIPT_POLICY_TEMPLATE
1717
UNIQUE_KEYPATHS = 0x10 # WALLY_MINISCRIPT_UNIQUE_KEYPATHS
1818

19-
MS_IS_RANGED = 0x1
20-
MS_IS_MULTIPATH = 0x2
21-
MS_IS_PRIVATE = 0x4
19+
MS_IS_RANGED = 0x1
20+
MS_IS_MULTIPATH = 0x2
21+
MS_IS_PRIVATE = 0x4
2222
MS_IS_UNCOMPRESSED = 0x08
23-
MS_IS_RAW = 0x010
24-
MS_IS_DESCRIPTOR = 0x20
23+
MS_IS_RAW = 0x010
24+
MS_IS_DESCRIPTOR = 0x20
25+
MS_IS_X_ONLY = 0x40
26+
MS_IS_PARENTED = 0x80
2527

2628
NO_CHECKSUM = 0x1 # WALLY_MS_CANONICAL_NO_CHECKSUM
2729

@@ -326,12 +328,16 @@ def make_keys(xpubs):
326328

327329
def test_key_iteration(self):
328330
"""Test iterating descriptor keys"""
331+
origin_fp = 'd34db33f'
332+
origin_path = "44'/0'/0'"
333+
origin = f'[{origin_fp}/{origin_path}]'
329334
k1 = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB'
330335
k2 = 'xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU'
331336
wif = 'L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM'
332337
pk = '03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284'
333338
pk_u = '0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf'
334339
policy_keys = wally_map_from_dict({f'@{i}': xpub for i,xpub in enumerate([k1])})
340+
policy_keys_with_origins = wally_map_from_dict({f'@{i}': f'{origin}{xpub}' for i,xpub in enumerate([k1])})
335341
P = POLICY
336342

337343
# Valid args
@@ -355,20 +361,43 @@ def test_key_iteration(self):
355361
(0, f'pk({pk_u})', pk_u, '', MS_IS_RAW|MS_IS_UNCOMPRESSED),
356362
]:
357363
d = c_void_p()
358-
keys = policy_keys if flags & P else None
359-
ret = wally_descriptor_parse(descriptor, keys, NETWORK_BTC_MAIN, flags, d)
360-
self.assertEqual(ret, WALLY_OK)
361-
ret, num_keys = wally_descriptor_get_num_keys(d)
362-
self.assertEqual((ret, num_keys), (WALLY_OK, 1))
363-
ret, key_str = wally_descriptor_get_key(d, 0)
364-
self.assertEqual((ret, key_str), (WALLY_OK, expected))
365-
ret, path_len = wally_descriptor_get_key_child_path_str_len(d, 0)
366-
self.assertEqual((ret, path_len), (WALLY_OK, len(child_path)))
367-
ret, path_str = wally_descriptor_get_key_child_path_str(d, 0)
368-
self.assertEqual((ret, path_str), (WALLY_OK, child_path))
369-
ret, features = wally_descriptor_get_key_features(d, 0)
370-
self.assertEqual((ret, features), (WALLY_OK, expected_features))
371-
wally_descriptor_free(d)
364+
buf, buf_len = make_cbuffer('0' * 8)
365+
366+
for with_origin in [False, True] if expected == k1 else [False]:
367+
keys = None
368+
if flags & P:
369+
keys = policy_keys_with_origins if with_origin else policy_keys
370+
elif with_origin:
371+
continue
372+
ret = wally_descriptor_parse(descriptor, keys, NETWORK_BTC_MAIN, flags, d)
373+
self.assertEqual(ret, WALLY_OK)
374+
ret, num_keys = wally_descriptor_get_num_keys(d)
375+
self.assertEqual((ret, num_keys), (WALLY_OK, 1))
376+
ret, key_str = wally_descriptor_get_key(d, 0)
377+
self.assertEqual((ret, key_str), (WALLY_OK, expected))
378+
ret, path_len = wally_descriptor_get_key_child_path_str_len(d, 0)
379+
self.assertEqual((ret, path_len), (WALLY_OK, len(child_path)))
380+
ret, path_str = wally_descriptor_get_key_child_path_str(d, 0)
381+
self.assertEqual((ret, path_str), (WALLY_OK, child_path))
382+
ret, features = wally_descriptor_get_key_features(d, 0)
383+
if with_origin:
384+
expected_features |= MS_IS_PARENTED
385+
self.assertEqual((ret, features), (WALLY_OK, expected_features))
386+
ret = wally_descriptor_get_key_origin_fingerprint(d, 0, buf, buf_len)
387+
# Ensure the key origin matches if present
388+
if with_origin:
389+
self.assertEqual(ret, WALLY_OK)
390+
ret, fp = wally_hex_from_bytes(buf, buf_len)
391+
self.assertEqual((ret, fp), (WALLY_OK, origin_fp))
392+
else:
393+
self.assertEqual(ret, WALLY_EINVAL)
394+
ret, path_len = wally_descriptor_get_key_origin_path_str_len(d, 0)
395+
expected_len = len(origin_path) if with_origin else 0
396+
self.assertEqual((ret, path_len), (WALLY_OK, expected_len))
397+
ret, path_str = wally_descriptor_get_key_origin_path_str(d, 0)
398+
expected_path = origin_path if with_origin else ''
399+
self.assertEqual((ret, path_str), (WALLY_OK, expected_path))
400+
wally_descriptor_free(d)
372401

373402

374403
if __name__ == '__main__':

src/test/util.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ class wally_psbt(Structure):
317317
('wally_descriptor_get_key_child_path_str', c_int, [c_void_p, c_size_t, c_char_p_p]),
318318
('wally_descriptor_get_key_child_path_str_len', c_int, [c_void_p, c_size_t, c_size_t_p]),
319319
('wally_descriptor_get_key_features', c_int, [c_void_p, c_size_t, c_uint32_p]),
320+
('wally_descriptor_get_key_origin_fingerprint', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]),
321+
('wally_descriptor_get_key_origin_path_str', c_int, [c_void_p, c_size_t, c_char_p_p]),
322+
('wally_descriptor_get_key_origin_path_str_len', c_int, [c_void_p, c_size_t, c_size_t_p]),
320323
('wally_descriptor_get_network', c_int, [c_void_p, c_uint32_p]),
321324
('wally_descriptor_get_num_keys', c_int, [c_void_p, c_uint32_p]),
322325
('wally_descriptor_get_num_paths', c_int, [c_void_p, c_uint32_p]),

src/wasm_package/src/const.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export const WALLY_MINISCRIPT_UNIQUE_KEYPATHS = 0x10; /** For policy templates,
128128
export const WALLY_MS_CANONICAL_NO_CHECKSUM = 0x01; /** Do not include a checksum */
129129
export const WALLY_MS_IS_DESCRIPTOR = 0x20; /** Contains only descriptor expressions (no miniscript) */
130130
export const WALLY_MS_IS_MULTIPATH = 0x02; /** Allows multiple paths via ``<a;b;c>`` */
131+
export const WALLY_MS_IS_PARENTED = 0x80; /** Contains at least one key key with a parent key origin */
131132
export const WALLY_MS_IS_PRIVATE = 0x04; /** Contains at least one private key */
132133
export const WALLY_MS_IS_RANGED = 0x01; /** Allows key ranges via ``*`` */
133134
export const WALLY_MS_IS_RAW = 0x10; /** Contains at least one raw key */

src/wasm_package/src/functions.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/wasm_package/src/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ export function descriptor_get_features(descriptor: Ref_wally_descriptor): numbe
126126
export function descriptor_get_key(descriptor: Ref_wally_descriptor, index: number): string;
127127
export function descriptor_get_key_child_path_str_len(descriptor: Ref_wally_descriptor, index: number): number;
128128
export function descriptor_get_key_features(descriptor: Ref_wally_descriptor, index: number): number;
129+
export function descriptor_get_key_origin_fingerprint(descriptor: Ref_wally_descriptor, index: number): Buffer;
130+
export function descriptor_get_key_origin_path_str_len(descriptor: Ref_wally_descriptor, index: number): number;
129131
export function descriptor_get_network(descriptor: Ref_wally_descriptor): number;
130132
export function descriptor_get_num_keys(descriptor: Ref_wally_descriptor): number;
131133
export function descriptor_get_num_paths(descriptor: Ref_wally_descriptor): number;
@@ -728,6 +730,7 @@ export function base58_n_to_bytes(str_in: string, str_len: number, flags: number
728730
export function base58_to_bytes(str_in: string, flags: number): Buffer;
729731
export function base64_to_bytes(str_in: string, flags: number): Buffer;
730732
export function descriptor_get_key_child_path_str(descriptor: Ref_wally_descriptor, index: number): string;
733+
export function descriptor_get_key_origin_path_str(descriptor: Ref_wally_descriptor, index: number): string;
731734
export function descriptor_to_script(descriptor: Ref_wally_descriptor, depth: number, index: number, variant: number, multi_index: number, child_num: number, flags: number): Buffer;
732735
export function ec_sig_from_bytes(priv_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): Buffer;
733736
export function ec_sig_from_bytes_aux(priv_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, aux_rand: Buffer|Uint8Array, flags: number): Buffer;

0 commit comments

Comments
 (0)