Skip to content

Commit 6bbe9ee

Browse files
committed
descriptor: support elements confidential wallet policies
These are the same as existing wallet policies except that the blinding key placeholder must be '@B`, which must be given first in the policy key map when parsing. This allows the blinding scheme and key to be presented separately from the rest of the policy which will then be identical (modulo el-prefixes) between elements and bitcoin for the same wallet structure. This also allows the distinction between blinding keys and policy keys to remain intact. Blinding keys are qualitatively different from derivation keys; they may for example be a real pubkey, raw entropy, and/or/in addition to an algorithm that operates on the result of the policy for given derivation indices (in the case of deterministic blinding). For these reasons blinding keys remain outside standard key iteration in a parsed descriptor.
1 parent 6d365ec commit 6bbe9ee

1 file changed

Lines changed: 90 additions & 33 deletions

File tree

src/descriptor.c

Lines changed: 90 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,14 @@ static const struct addr_ver_t *addr_ver_from_family(
301301
static const struct ms_builtin_t *builtin_get(const ms_node *node);
302302
static int generate_script(ms_ctx *ctx, ms_node *node,
303303
unsigned char *script, size_t script_len, size_t *written);
304-
static bool is_valid_policy_map(const struct wally_map *map_in);
304+
static int is_valid_policy_map(const struct wally_map *map_in, bool *is_elements);
305+
306+
static bool is_elements_policy_map(const struct wally_map *map_in)
307+
{
308+
/* Elements policy maps must have the blinding key @B first */
309+
return map_in->num_items && map_in->items[0].key_len == 2 &&
310+
map_in->items[0].key[0] == '@' && map_in->items[0].key[1] == 'B';
311+
}
305312

306313
/* Wrapper for strtoll */
307314
static bool strtoll_n(const char *str, size_t str_len, int64_t *v)
@@ -399,7 +406,8 @@ static bool is_identifer_char(char c)
399406
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
400407
}
401408
static bool is_policy_start_char(char c) { return c == '@'; }
402-
static bool is_policy_identifer_char(char c) { return c >= '0' && c <= '9'; }
409+
static bool is_policy_id_char(char c) { return c >= '0' && c <= '9'; }
410+
static bool is_elements_policy_id_char(char c) { return c == 'B' || is_policy_id_char(c); }
403411

404412
static int canonicalize_impl(const char *descriptor,
405413
const struct wally_map *vars_in, uint32_t flags,
@@ -411,18 +419,24 @@ static int canonicalize_impl(const char *descriptor,
411419
int key_index_hwm = -1;
412420
const char *p = descriptor, *start;
413421
char *out;
414-
bool found_policy_single = false, found_policy_multi = false;;
422+
bool found_policy_single = false, found_policy_multi = false;
423+
bool found_policy_elements = false;
415424

416425
*output = NULL;
417426
*num_substitutions = 0;
418427
if (!descriptor || (flags & ~MS_FLAGS_CANONICALIZE))
419428
return WALLY_EINVAL;
420429

421430
if (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE) {
422-
if (!is_valid_policy_map(vars_in))
423-
return WALLY_EINVAL; /* Invalid policy variables given */
431+
const int ret = is_valid_policy_map(vars_in, &found_policy_elements);
432+
if (ret != WALLY_OK)
433+
return ret; /* Invalid policy variables given */
434+
#ifndef BUILD_ELEMENTS
435+
if (found_policy_elements)
436+
return WALLY_EINVAL; /* No Elements support */
437+
#endif
424438
is_id_start = is_policy_start_char;
425-
is_id_char = is_policy_identifer_char;
439+
is_id_char = found_policy_elements ? is_elements_policy_id_char : is_policy_id_char;
426440
}
427441

428442
/* First, find the length of the canonicalized descriptor */
@@ -457,6 +471,17 @@ static int canonicalize_impl(const char *descriptor,
457471
return WALLY_EINVAL; /* Must be ordered with no gaps */
458472
if (key_index > key_index_hwm)
459473
key_index_hwm = key_index;
474+
if (found_policy_elements && key_index == 0) {
475+
/* The blinding key in a ct() policy */
476+
if (*p != ')' && *p != ',')
477+
return WALLY_EINVAL; /* Must be a single key */
478+
continue;
479+
}
480+
/* Check for a key path. Note that policies, unlike
481+
* raw descriptors, cannot be used to encode single
482+
* keys (as they are used to register wallet structures,
483+
* not to expose single addresses).
484+
*/
460485
if (*p++ != '/')
461486
return WALLY_EINVAL;
462487
++required_len;
@@ -2331,7 +2356,7 @@ static int analyze_key_hex(ms_ctx *ctx, ms_node *node,
23312356
}
23322357

23332358
static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags,
2334-
ms_node *node, ms_node *parent)
2359+
ms_node *node, ms_node *parent, bool force_ct)
23352360
{
23362361
unsigned char privkey[2 + EC_PRIVATE_KEY_LEN + BASE58_CHECKSUM_LEN];
23372362
struct ext_key extkey;
@@ -2340,10 +2365,11 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags,
23402365
bool is_hex;
23412366
#ifdef BUILD_ELEMENTS
23422367
/* Whether we are the blinding key child of a ct() expression */
2343-
const bool is_ct_key = parent && parent->kind == KIND_DESCRIPTOR_CT &&
2344-
!parent->child; /* If no child, we are the first child */
2368+
const bool is_ct_key = force_ct || (parent && parent->kind == KIND_DESCRIPTOR_CT &&
2369+
!parent->child); /* If no child, we are the first child */
23452370
#else
23462371
const bool is_ct_key = false;
2372+
(void)force_ct;
23472373
#endif
23482374

23492375
if (!node || (parent && !parent->builtin))
@@ -2546,7 +2572,8 @@ static int analyze_miniscript_value(ms_ctx *ctx, const char *str, size_t str_len
25462572
node->kind = KIND_NUMBER;
25472573
return WALLY_OK;
25482574
}
2549-
return analyze_miniscript_key(ctx, flags, node, parent);
2575+
const bool force_ct = false;
2576+
return analyze_miniscript_key(ctx, flags, node, parent, force_ct);
25502577
}
25512578

25522579
static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len,
@@ -2880,7 +2907,7 @@ static uint32_t get_max_depth(const char *miniscript, size_t miniscript_len)
28802907
return depth == 1 ? max_depth : 0xffffffff;
28812908
}
28822909

2883-
static bool is_valid_policy_map(const struct wally_map *map_in)
2910+
static int is_valid_policy_map(const struct wally_map *map_in, bool *is_elements)
28842911
{
28852912
struct wally_map keys;
28862913
ms_ctx ctx;
@@ -2889,6 +2916,8 @@ static bool is_valid_policy_map(const struct wally_map *map_in)
28892916
size_t i;
28902917
int ret = WALLY_OK;
28912918

2919+
*is_elements = false;
2920+
28922921
if (!map_in || !map_in->num_items)
28932922
return WALLY_EINVAL; /* Must contain at least one key expression */
28942923

@@ -2898,37 +2927,56 @@ static bool is_valid_policy_map(const struct wally_map *map_in)
28982927
for (i = 0; ret == WALLY_OK && i < map_in->num_items; ++i) {
28992928
const struct wally_map_item *item = &map_in->items[i];
29002929
if (!item->key || item->key_len < 2 || item->key[0] != '@' ||
2901-
!strtoll_n((const char *)item->key + 1, item->key_len - 1, &v) || v < 0) {
2902-
/* Policy keys can only be @n: positive integers */
2903-
ret = WALLY_EINVAL;
2904-
} else if ((size_t)v != i)
2905-
ret = WALLY_EINVAL; /* Must be sorted in order from 0-n */
2906-
else if (!item->value || !item->value_len)
2907-
ret = WALLY_EINVAL; /* No key value */
2908-
else if (!(node = wally_calloc(sizeof(*node))))
2930+
!item->value || !item->value_len)
2931+
goto fail; /* No valid key/value */
2932+
2933+
if (i == 0 && item->key_len == 2 && item->key[1] == 'B') {
2934+
/* @B can be used as the first (blinding) key for ct() policies */
2935+
*is_elements = true;
2936+
} else {
2937+
/* Policy keys can only be @n: positive integers,
2938+
and must be sorted in order from 0-n */
2939+
if (!strtoll_n((const char *)item->key + 1, item->key_len - 1, &v) ||
2940+
v < 0 || (size_t)v + (*is_elements ? 1 : 0) != i)
2941+
goto fail;
2942+
}
2943+
2944+
if (!(node = wally_calloc(sizeof(*node)))) {
29092945
ret = WALLY_ENOMEM;
2910-
else {
2911-
node->data = (const char*)item->value;
2912-
node->data_len = item->value_len;
2913-
if (analyze_miniscript_key(&ctx, 0, node, NULL) != WALLY_OK ||
2914-
node->kind != KIND_BIP32_PUBLIC_KEY ||
2915-
node->child_path_len) {
2946+
goto fail_nomem;
2947+
}
2948+
2949+
/* Parse the key data */
2950+
node->data = (const char*)item->value;
2951+
node->data_len = item->value_len;
2952+
const bool force_ct = *is_elements && i == 0;
2953+
ret = analyze_miniscript_key(&ctx, 0, node, NULL, force_ct);
2954+
if (ret == WALLY_OK) {
2955+
if (force_ct && node->kind == KIND_PRIVATE_KEY) {
2956+
/* Valid 64 byte hex blinding key: */
2957+
/* no-op */;
2958+
} else if (node->kind != KIND_BIP32_PUBLIC_KEY || node->child_path_len) {
29162959
ret = WALLY_EINVAL; /* Only BIP32 xpubs are allowed */
2917-
} else if (ctx.features & (WALLY_MS_IS_MULTIPATH | WALLY_MS_IS_RANGED)) {
2960+
}
2961+
if (ctx.features & (WALLY_MS_IS_MULTIPATH | WALLY_MS_IS_RANGED)) {
29182962
/* Range or multipath must be part of the expression, not the key */
29192963
ret = WALLY_EINVAL;
2920-
} else {
2964+
} else if (ret == WALLY_OK) {
29212965
ret = wally_map_add(&keys, item->value, item->value_len, NULL, 0);
29222966
}
2923-
node_free(node);
29242967
}
2968+
node_free(node);
29252969
}
2926-
if (ret == WALLY_OK && keys.num_items != map_in->num_items)
2927-
ret = WALLY_EINVAL; /* One of more keys is not unique */
2970+
if (ret == WALLY_OK && keys.num_items != map_in->num_items) {
2971+
/* One of more keys is not unique */
2972+
fail:
2973+
ret = WALLY_EINVAL;
2974+
}
2975+
fail_nomem:
29282976
clear_and_free(ctx.keys.items,
29292977
ctx.keys.num_items * sizeof(*ctx.keys.items));
29302978
wally_map_clear(&keys);
2931-
return ret == WALLY_OK;
2979+
return ret;
29322980
}
29332981

29342982
int wally_descriptor_parse(const char *miniscript,
@@ -2966,6 +3014,10 @@ int wally_descriptor_parse(const char *miniscript,
29663014
if (ret == WALLY_OK)
29673015
ret = canonicalize_impl(miniscript, vars_in, flags & MS_FLAGS_CANONICALIZE,
29683016
&ctx->src, &num_substitutions);
3017+
if (ret == WALLY_OK && (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE)) {
3018+
if (!num_substitutions)
3019+
ret = WALLY_EINVAL; /* Policy with no keys substituted */
3020+
}
29693021
if (ret == WALLY_OK) {
29703022
ctx->src_len = strlen(ctx->src);
29713023
ctx->features = WALLY_MS_IS_DESCRIPTOR; /* Un-set if miniscript found */
@@ -2981,12 +3033,17 @@ int wally_descriptor_parse(const char *miniscript,
29813033
if (ret == WALLY_OK)
29823034
ret = node_generation_size(ctx->top_node, &ctx->script_len);
29833035
if (ret == WALLY_OK && (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE)) {
2984-
if (ctx->keys.num_items != num_substitutions)
3036+
const bool have_blinding_key = is_elements_policy_map(vars_in);
3037+
const size_t num_skipped_keys = have_blinding_key ? 1 : 0;
3038+
if (ctx->keys.num_items != num_substitutions - num_skipped_keys)
29853039
ret = WALLY_EINVAL; /* non-substituted key in the expression */
2986-
else if (vars_in && ctx->keys.num_items < vars_in->num_items)
3040+
else if (vars_in && ctx->keys.num_items + num_skipped_keys < vars_in->num_items)
29873041
ret = WALLY_EINVAL; /* non-substituted key in substitutions */
29883042
else if (ctx->num_variants > 1 || ctx->num_multipaths > 2)
29893043
ret = WALLY_EINVAL; /* Solved cardinality must be 1 or 2 */
3044+
else if ((ctx->features & (WALLY_MS_IS_SLIP77 | WALLY_MS_IS_ELIP150)) &&
3045+
!have_blinding_key)
3046+
ret = WALLY_EINVAL; /* this ct policy requires a blinding key var */
29903047
else if (flags & WALLY_MINISCRIPT_UNIQUE_KEYPATHS)
29913048
ret = ensure_unique_policy_keys(ctx);
29923049
}

0 commit comments

Comments
 (0)