From d07f91a08c5ac329030438e837399a0e75e51f0c Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Tue, 25 Apr 2023 17:25:50 +0200 Subject: [PATCH] [WIP] Support for tapminiscript --- src/common/wallet.c | 148 +++++++++++++++++++++++++-------------- src/common/wallet.h | 9 ++- src/handler/lib/policy.c | 46 ++++++++++-- unit-tests/test_wallet.c | 4 +- 4 files changed, 147 insertions(+), 60 deletions(-) diff --git a/src/common/wallet.c b/src/common/wallet.c index 96ac0f4b..c3f1ea69 100644 --- a/src/common/wallet.c +++ b/src/common/wallet.c @@ -527,8 +527,12 @@ static int parse_script(buffer_t *in_buf, policy_node_t *outermost_node = (policy_node_t *) buffer_get_cur(out_buf); policy_node_with_script_t *inner_wrapper = NULL; // pointer to the inner wrapper, if any - // miniscript-related parsing only within top-level WSH - if ((context_flags & CONTEXT_WITHIN_WSH) != 0 && (context_flags & CONTEXT_WITHIN_SH) == 0) { + // miniscript-related parsing only within top-level WSH, or within tr + bool parse_as_miniscript = + ((context_flags & CONTEXT_WITHIN_WSH) != 0 && (context_flags & CONTEXT_WITHIN_SH) == 0) || + (context_flags & CONTEXT_WITHIN_TR) != 0; + + if (parse_as_miniscript) { // look ahead to finds out if the buffer starts with alphanumeric digits that could be // wrappers, followed by a colon char c; @@ -603,19 +607,6 @@ static int parse_script(buffer_t *in_buf, // We read the token, we'll do different parsing based on what token we find PolicyNodeType token = parse_token(in_buf); - if (context_flags & CONTEXT_WITHIN_TR) { - // whitelist of allowed tokens within tr scripts - // more will be added with taproot miniscript support - switch (token) { - case TOKEN_PK: - case TOKEN_MULTI_A: - case TOKEN_SORTEDMULTI_A: - break; - default: - return WITH_ERROR(-1, "Token not allowed within tr"); - } - } - if (context_flags & CONTEXT_WITHIN_SH) { // whitelist of allowed tokens within sh; in particular, no miniscript switch (token) { @@ -1526,6 +1517,20 @@ static int parse_script(buffer_t *in_buf, return WITH_ERROR(-1, "Out of memory"); } + if ((context_flags & CONTEXT_WITHIN_TR) != 0) { + if (token != TOKEN_MULTI_A && token != TOKEN_SORTEDMULTI_A) { + return WITH_ERROR( + -1, + "multi and sortedmulti can only be used legacy or segwit scripts"); + } + } else { // legacy or segwit scripts + if (token != TOKEN_MULTI && token != TOKEN_SORTEDMULTI) { + return WITH_ERROR( + -1, + "multi_a and sortedmulti_a can only be used in taproot scripts"); + } + } + if (token == TOKEN_SORTEDMULTI) { size_t n_sh_wrappers = 0; if (context_flags & CONTEXT_WITHIN_SH) ++n_sh_wrappers; @@ -1587,9 +1592,9 @@ static int parse_script(buffer_t *in_buf, return WITH_ERROR(-1, "Invalid k and/or n"); } - if (token == TOKEN_SORTEDMULTI) { + if (token == TOKEN_SORTEDMULTI || token == TOKEN_SORTEDMULTI_A) { node->base.flags.is_miniscript = 0; - } else { + } else if (token == TOKEN_MULTI) { node->base.flags.is_miniscript = 1; node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; node->base.flags.miniscript_mod_z = 0; @@ -1597,6 +1602,14 @@ static int parse_script(buffer_t *in_buf, node->base.flags.miniscript_mod_n = 1; node->base.flags.miniscript_mod_d = 1; node->base.flags.miniscript_mod_u = 1; + } else if (token == TOKEN_MULTI_A) { + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; } break; @@ -1714,7 +1727,7 @@ static int parse_script(buffer_t *in_buf, node->base.flags.miniscript_mod_o = 1; node->base.flags.miniscript_mod_n = 1; node->base.flags.miniscript_mod_d = 1; - node->base.flags.miniscript_mod_u = 0; + node->base.flags.miniscript_mod_u = (context_flags & CONTEXT_WITHIN_TR) ? 1 : 0; break; case TOKEN_V: if (X_type != MINISCRIPT_TYPE_B) { @@ -1926,7 +1939,9 @@ static int16_t maxcheck(int16_t a, int16_t b) { // Separated from the main function as it is stack-intensive, therefore we allocate large buffers // into the CXRAM section. There is some repeated work () -static int compute_thresh_ops(const policy_node_thresh_t *node, miniscript_ops_t *out) { +static int compute_thresh_ops(const policy_node_thresh_t *node, + miniscript_ops_t *out, + MiniscriptContext ctx) { #ifdef USE_CXRAM_SECTION // allocate buffers inside the cxram section; safe as there are no syscalls here uint16_t *sats = (uint16_t *) get_cxram_buffer(); @@ -1948,7 +1963,8 @@ static int compute_thresh_ops(const policy_node_thresh_t *node, miniscript_ops_t while (cur != NULL) { policy_node_ext_info_t t; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&cur->script), &t)) return -1; + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&cur->script), &t, ctx)) + return -1; out->count += t.ops.count + 1; @@ -1972,7 +1988,9 @@ static int compute_thresh_ops(const policy_node_thresh_t *node, miniscript_ops_t // Separated from the main function as it is stack-intensive, therefore we allocate large buffers // into the CXRAM section. There is some repeated work () -static int compute_thresh_stacksize(const policy_node_thresh_t *node, miniscript_stacksize_t *out) { +static int compute_thresh_stacksize(const policy_node_thresh_t *node, + miniscript_stacksize_t *out, + MiniscriptContext ctx) { #ifdef USE_CXRAM_SECTION // allocate buffers inside the cxram section; safe as there are no syscalls here uint16_t *sats = (uint16_t *) get_cxram_buffer(); @@ -1992,7 +2010,8 @@ static int compute_thresh_stacksize(const policy_node_thresh_t *node, miniscript while (cur != NULL) { policy_node_ext_info_t t; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&cur->script), &t)) return -1; + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&cur->script), &t, ctx)) + return -1; next_sats[0] = sumcheck(sats[0], t.ss.dsat); for (int j = 1; j < sats_size; j++) { @@ -2011,12 +2030,18 @@ static int compute_thresh_stacksize(const policy_node_thresh_t *node, miniscript return 0; } +// TODO: generalize stack size computation for tapminiscript int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, - policy_node_ext_info_t *out) { + policy_node_ext_info_t *out, + MiniscriptContext ctx) { if (!policy_node->flags.is_miniscript) { return WITH_ERROR(-1, "Not miniscript"); } + if (ctx != MINISCRIPT_CONTEXT_P2WSH && ctx != MINISCRIPT_CONTEXT_TAPSCRIPT) { + return WITH_ERROR(-1, "Unknown miniscript context"); + } + memset(out, 0, sizeof(policy_node_ext_info_t)); // set flags that are 1 in most cases (they will be zeroed when appropriate) @@ -2048,7 +2073,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, out->s = 1; out->e = 1; - out->script_size = 34; + out->script_size = (ctx == MINISCRIPT_CONTEXT_TAPSCRIPT ? 33 : 34); out->ops = (miniscript_ops_t){0, 0, 0}; out->ss = (miniscript_stacksize_t){1, 1}; @@ -2070,7 +2095,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, out->x = 0; - out->script_size = 34 + 1; + out->script_size = (ctx == MINISCRIPT_CONTEXT_TAPSCRIPT ? 34 : 35); out->ops = (miniscript_ops_t){1, 0, 0}; out->ss = (miniscript_stacksize_t){1, 1}; @@ -2167,11 +2192,14 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t y; policy_node_ext_info_t z; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[2]), &z)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[2]), &z, ctx)) return -1; out->s = z.s & (x.s | y.s); @@ -2207,9 +2235,11 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t x; policy_node_ext_info_t y; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y, ctx)) return -1; out->s = x.s | y.s; @@ -2241,9 +2271,11 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t x; policy_node_ext_info_t y; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y, ctx)) return -1; out->s = x.s | y.s; @@ -2277,9 +2309,11 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t x; policy_node_ext_info_t y; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &y, ctx)) return -1; out->s = x.s | y.s; @@ -2310,9 +2344,11 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t x; policy_node_ext_info_t z; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z, ctx)) return -1; out->s = x.s & z.s; @@ -2344,9 +2380,11 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t x; policy_node_ext_info_t z; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z, ctx)) return -1; out->s = x.s & z.s; @@ -2376,9 +2414,11 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t x; policy_node_ext_info_t z; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z, ctx)) return -1; out->s = x.s & z.s; @@ -2409,9 +2449,11 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, policy_node_ext_info_t x; policy_node_ext_info_t z; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[0]), &x, ctx)) return -1; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z)) + if (0 > + compute_miniscript_policy_ext_info(resolve_node_ptr(&node->scripts[1]), &z, ctx)) return -1; out->s = x.s & z.s; @@ -2452,7 +2494,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, ++n_children; policy_node_ext_info_t t; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&cur->script), &t)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&cur->script), &t, ctx)) return -1; if (t.e) { @@ -2493,8 +2535,8 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, out->script_size = children_scriptsize + n_children + get_push_script_size(node->k); - if (0 > compute_thresh_ops(node, &out->ops)) return -1; - if (0 > compute_thresh_stacksize(node, &out->ss)) return -1; + if (0 > compute_thresh_ops(node, &out->ops, ctx)) return -1; + if (0 > compute_thresh_stacksize(node, &out->ss, ctx)) return -1; return 0; } @@ -2502,7 +2544,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = x.s; @@ -2529,7 +2571,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = x.s; @@ -2557,7 +2599,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = 1; @@ -2587,7 +2629,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = x.s; @@ -2612,7 +2654,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = x.s; @@ -2638,7 +2680,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = x.s; @@ -2663,7 +2705,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = x.s; @@ -2689,7 +2731,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; policy_node_ext_info_t x; - if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x)) + if (0 > compute_miniscript_policy_ext_info(resolve_node_ptr(&node->script), &x, ctx)) return -1; out->s = x.s; diff --git a/src/common/wallet.h b/src/common/wallet.h index 06713184..8ef4773a 100644 --- a/src/common/wallet.h +++ b/src/common/wallet.h @@ -147,6 +147,11 @@ typedef enum { TOKEN_INVALID = -1 // used to mark invalid tokens } PolicyNodeType; +typedef enum { + MINISCRIPT_CONTEXT_P2WSH, + MINISCRIPT_CONTEXT_TAPSCRIPT, +} MiniscriptContext; + // miniscript basic types #define MINISCRIPT_TYPE_B 0 #define MINISCRIPT_TYPE_V 1 @@ -409,10 +414,12 @@ int get_policy_segwit_version(const policy_node_t *policy); * * @param policy_node a pointer to a miniscript policy node * @param out pointer to the output policy_node_ext_info_t + * @param ctx either MINISCRIPT_CONTEXT_P2WSH or MINISCRIPT_CONTEXT_TAPSCRIPT * @return a negative number on error; 0 on success. */ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, - policy_node_ext_info_t *out); + policy_node_ext_info_t *out, + MiniscriptContext ctx); #ifndef SKIP_FOR_CMOCKA diff --git a/src/handler/lib/policy.c b/src/handler/lib/policy.c index 053ada47..10e93666 100644 --- a/src/handler/lib/policy.c +++ b/src/handler/lib/policy.c @@ -90,7 +90,7 @@ typedef struct { static const uint8_t fragment_whitelist_sh[] = {TOKEN_WPKH, TOKEN_MULTI, TOKEN_SORTEDMULTI}; static const uint8_t fragment_whitelist_sh_wsh[] = {TOKEN_MULTI, TOKEN_SORTEDMULTI}; static const uint8_t fragment_whitelist_wsh[] = { - /* tokens for miniscript on segwit */ + /* tokens for scripts on segwit */ TOKEN_0, TOKEN_1, TOKEN_PK, @@ -125,9 +125,42 @@ static const uint8_t fragment_whitelist_wsh[] = { TOKEN_N, TOKEN_L, TOKEN_U}; -static const uint8_t fragment_whitelist_tapscript[] = {TOKEN_PK, - TOKEN_MULTI_A, - TOKEN_SORTEDMULTI_A}; +static const uint8_t fragment_whitelist_tapscript[] = { + /* tokens for scripts in taptrees */ + TOKEN_0, + TOKEN_1, + TOKEN_PK, + TOKEN_PKH, + TOKEN_PK_K, + TOKEN_PK_H, + TOKEN_OLDER, + TOKEN_AFTER, + TOKEN_SHA256, + TOKEN_HASH256, + TOKEN_RIPEMD160, + TOKEN_HASH160, + TOKEN_ANDOR, + TOKEN_AND_V, + TOKEN_AND_B, + TOKEN_AND_N, + TOKEN_MULTI_A, + TOKEN_OR_B, + TOKEN_OR_C, + TOKEN_OR_D, + TOKEN_OR_I, + TOKEN_SORTEDMULTI_A, + TOKEN_THRESH, + // wrappers + TOKEN_A, + TOKEN_S, + TOKEN_C, + TOKEN_T, + TOKEN_D, + TOKEN_V, + TOKEN_J, + TOKEN_N, + TOKEN_L, + TOKEN_U}; static const generic_processor_command_t commands_0[] = {{CMD_CODE_OP_V, OP_0}, {CMD_CODE_END, 0}}; static const generic_processor_command_t commands_1[] = {{CMD_CODE_OP_V, OP_1}, {CMD_CODE_END, 0}}; @@ -1583,7 +1616,8 @@ int is_policy_sane(dispatcher_context_t *dispatcher_context, // check miniscript sanity conditions policy_node_ext_info_t ext_info; - if (0 > compute_miniscript_policy_ext_info(inner, &ext_info)) { + if (0 > + compute_miniscript_policy_ext_info(inner, &ext_info, MINISCRIPT_CONTEXT_P2WSH)) { return WITH_ERROR(-1, "Error analyzing miniscript policy"); } @@ -1625,6 +1659,8 @@ int is_policy_sane(dispatcher_context_t *dispatcher_context, } } + // TODO: check sanity rules for all miniscript leaves the policy is a taproot tree + // check that all the xpubs are different for (unsigned int i = 0; i < n_keys - 1; i++) { // no point in running this for the last key char xpub_i[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; diff --git a/unit-tests/test_wallet.c b/unit-tests/test_wallet.c index eb95ae15..18437036 100644 --- a/unit-tests/test_wallet.c +++ b/unit-tests/test_wallet.c @@ -393,7 +393,9 @@ static void Test(const char *ms, const char *hexscript, int mode, int opslimit, policy_node_with_script_t *policy = (policy_node_with_script_t *) out; policy_node_ext_info_t ext_info; - res = compute_miniscript_policy_ext_info(resolve_ptr(&policy->script), &ext_info); + res = compute_miniscript_policy_ext_info(resolve_ptr(&policy->script), + &ext_info, + MINISCRIPT_CONTEXT_P2WSH); assert_true(res == 0);