Skip to content

Commit 873942a

Browse files
committed
WIP: descriptor: Implement wallet-policies BIP
Policies are simply a restriction on the general variable substitution mechanism that wally already supports; (a) the variables can only be of the form @n where n is an integer, and (b) the substitution data must be an xpub. See bitcoin/bips#1389 WIP: Requires tests and documentation (and the BIP to be accepted).
1 parent b9e69df commit 873942a

File tree

2 files changed

+46
-10
lines changed

2 files changed

+46
-10
lines changed

include/wally_descriptor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct wally_descriptor;
1515
#define WALLY_MINISCRIPT_TAPSCRIPT 0x01 /** Tapscript, use x-only pubkeys */
1616
#define WALLY_MINISCRIPT_ONLY 0x02 /** Only allow miniscript (not descriptor) expressions */
1717
#define WALLY_MINISCRIPT_REQUIRE_CHECKSUM 0x04 /** Require a checksum to be present */
18+
#define WALLY_MINISCRIPT_POLICY 0x08 /** Only allow policy @n variable substitution */
1819

1920
#define WALLY_MS_IS_RANGED 0x01 /** Allows key ranges via '*' */
2021

src/descriptor.c

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#define MS_FLAGS_ALL (WALLY_MINISCRIPT_TAPSCRIPT | \
1919
WALLY_MINISCRIPT_ONLY | \
2020
WALLY_MINISCRIPT_REQUIRE_CHECKSUM)
21+
#define MS_FLAGS_CANONICALIZE (WALLY_MINISCRIPT_REQUIRE_CHECKSUM | \
22+
WALLY_MINISCRIPT_POLICY)
2123

2224
/* Properties and expressions definition */
2325
#define TYPE_NONE 0x00
@@ -262,6 +264,7 @@ static const struct addr_ver_t *addr_ver_from_family(
262264
static const struct ms_builtin_t *builtin_get(const ms_node *node);
263265
static int generate_script(ms_ctx *ctx, ms_node *node,
264266
unsigned char *script, size_t script_len, size_t *written);
267+
static bool is_valid_policy_map(const struct wally_map *map_in);
265268

266269
/* Wrapper for strtoll */
267270
static bool strtoll_n(const char *str, size_t str_len, int64_t *v)
@@ -347,8 +350,10 @@ static int generate_checksum(const char *str, size_t str_len, char *checksum_out
347350
return WALLY_OK;
348351
}
349352

350-
static inline bool is_identifer_char(char c)
353+
static inline bool is_identifer_char(char c, uint32_t flags)
351354
{
355+
if (flags & WALLY_MINISCRIPT_POLICY)
356+
return (c >= '0' && c <= '9') || c == '@';
352357
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
353358
}
354359

@@ -376,17 +381,20 @@ static int canonicalize(const char *descriptor,
376381
if (output)
377382
*output = NULL;
378383

379-
if (!descriptor || (flags & ~WALLY_MINISCRIPT_REQUIRE_CHECKSUM) || !output)
384+
if (!descriptor || (flags & ~MS_FLAGS_CANONICALIZE) || !output)
380385
return WALLY_EINVAL;
381386

387+
if ((flags & WALLY_MINISCRIPT_POLICY) && !is_valid_policy_map(vars_in))
388+
return WALLY_EINVAL; /* Invalid policy variables given */
389+
382390
/* First, find the length of the canonicalized descriptor */
383391
while (*p && *p != '#') {
384-
while (*p && *p != '#' && !is_identifer_char(*p)) {
392+
while (*p && *p != '#' && !is_identifer_char(*p, flags)) {
385393
++required_len;
386394
++p;
387395
}
388396
start = p;
389-
while (is_identifer_char(*p))
397+
while (is_identifer_char(*p, flags))
390398
++p;
391399
if (p != start) {
392400
const bool starts_with_digit = *start >= '0' && *start <= '9';
@@ -410,18 +418,18 @@ static int canonicalize(const char *descriptor,
410418
p = descriptor;
411419
out = *output;
412420
while (*p && *p != '#') {
413-
while (*p && *p != '#' && !is_identifer_char(*p)) {
421+
while (*p && *p != '#' && !is_identifer_char(*p, flags)) {
414422
*out++ = *p++;
415423
}
416424
start = p;
417-
while (is_identifer_char(*p))
425+
while (is_identifer_char(*p, flags))
418426
++p;
419427
if (p != start) {
420428
const bool is_number = *start >= '0' && *start <= '9';
421429
size_t lookup_len = p - start;
422-
if (!vars_in || lookup_len > VAR_MAX_NAME_LEN || is_number) {
430+
if (!vars_in || lookup_len > VAR_MAX_NAME_LEN || is_number)
423431
memcpy(out, start, lookup_len);
424-
} else {
432+
else {
425433
/* Lookup the potential identifier */
426434
const struct wally_map_item *item = lookup_identifier(vars_in, start, lookup_len);
427435
lookup_len = item ? item->value_len - 1 : lookup_len;
@@ -2370,6 +2378,34 @@ static int node_generate_script(ms_ctx *ctx, uint32_t depth, uint32_t index,
23702378
return ret;
23712379
}
23722380

2381+
static bool is_valid_policy_map(const struct wally_map *map_in)
2382+
{
2383+
ms_ctx ctx;
2384+
ms_node node;
2385+
int64_t v;
2386+
size_t i;
2387+
int ret = WALLY_OK;
2388+
2389+
memset(&ctx, 0, sizeof(ctx));
2390+
2391+
for (i = 0; ret == WALLY_OK && i < map_in->num_items; ++i) {
2392+
const struct wally_map_item *item = &map_in->items[i];
2393+
if (!item->key || item->key_len < 2 || item->key[0] != '@' ||
2394+
!strtoll_n((const char *)item->key + 1, item->key_len - 1, &v) || v < 0)
2395+
return false; /* Policy keys can only be @n */
2396+
if (!item->value || !item->value_len)
2397+
return false; /* No key value */
2398+
memset(&node, 0, sizeof(node));
2399+
node.data = (const char*)item->value;
2400+
node.data_len = item->value_len - 1;
2401+
if (analyze_miniscript_key(&ctx, 0, &node, NULL) != WALLY_OK ||
2402+
node.kind == KIND_PRIVATE_KEY)
2403+
ret = WALLY_EINVAL; /* Policy data must be an xpub */
2404+
node_free(&node);
2405+
}
2406+
return ret == WALLY_OK;
2407+
}
2408+
23732409
int wally_descriptor_parse(const char *miniscript,
23742410
const struct wally_map *vars_in,
23752411
uint32_t network, uint32_t flags,
@@ -2391,8 +2427,7 @@ int wally_descriptor_parse(const char *miniscript,
23912427
return WALLY_ENOMEM;
23922428
ctx = *output;
23932429
ctx->addr_ver = addr_ver;
2394-
ret = canonicalize(miniscript, vars_in,
2395-
flags & WALLY_MINISCRIPT_REQUIRE_CHECKSUM,
2430+
ret = canonicalize(miniscript, vars_in, flags & MS_FLAGS_CANONICALIZE,
23962431
&ctx->src);
23972432
if (ret == WALLY_OK) {
23982433
ctx->src_len = strlen(ctx->src);

0 commit comments

Comments
 (0)