From 20bca76d25a49bb8678c833648678de70bca440e 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 1/8] Support for tapminiscript --- src/common/wallet.c | 163 +++++++++++++++++++++----------- src/common/wallet.h | 10 +- src/handler/lib/policy.c | 198 +++++++++++++++++++++++++++++---------- unit-tests/test_wallet.c | 4 +- 4 files changed, 269 insertions(+), 106 deletions(-) diff --git a/src/common/wallet.c b/src/common/wallet.c index 8590cb29..0ff44121 100644 --- a/src/common/wallet.c +++ b/src/common/wallet.c @@ -549,8 +549,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; @@ -625,19 +629,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) { @@ -1548,6 +1539,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; @@ -1609,9 +1614,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; @@ -1619,6 +1624,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; @@ -1736,7 +1749,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) { @@ -1948,7 +1961,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(); @@ -1970,7 +1985,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; @@ -1994,7 +2010,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(); @@ -2014,7 +2032,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++) { @@ -2034,11 +2053,16 @@ static int compute_thresh_stacksize(const policy_node_thresh_t *node, miniscript } 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) @@ -2070,7 +2094,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}; @@ -2092,7 +2116,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}; @@ -2126,6 +2150,21 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, return 0; } + case TOKEN_MULTI_A: { + const policy_node_multisig_t *node = (const policy_node_multisig_t *) policy_node; + + out->s = 1; + out->e = 1; + + out->x = 0; + + out->script_size = (uint16_t) (1 + get_push_script_size(node->k) + 34 * node->n); + + out->ops = (miniscript_ops_t){node->n + 1, 0, 0}; + out->ss = (miniscript_stacksize_t){node->n, node->n}; + + return 0; + } case TOKEN_OLDER: { const policy_node_with_uint32_t *node = (const policy_node_with_uint32_t *) policy_node; @@ -2189,11 +2228,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); @@ -2229,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; @@ -2263,9 +2307,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; @@ -2299,9 +2345,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; @@ -2332,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; @@ -2366,9 +2416,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; @@ -2398,9 +2450,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; @@ -2431,9 +2485,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; @@ -2474,7 +2530,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) { @@ -2515,8 +2571,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; } @@ -2524,7 +2580,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; @@ -2551,7 +2607,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; @@ -2579,7 +2635,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; @@ -2609,7 +2665,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; @@ -2634,7 +2690,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; @@ -2660,7 +2716,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; @@ -2685,7 +2741,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; @@ -2711,7 +2767,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; @@ -2735,6 +2791,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, return 0; } case TOKEN_SORTEDMULTI: + case TOKEN_SORTEDMULTI_A: case TOKEN_WPKH: case TOKEN_SH: case TOKEN_WSH: diff --git a/src/common/wallet.h b/src/common/wallet.h index b96e1781..b440eac5 100644 --- a/src/common/wallet.h +++ b/src/common/wallet.h @@ -148,6 +148,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 @@ -407,13 +412,16 @@ int get_policy_segwit_version(const policy_node_t *policy); /** * Computes additional properties of the given miniscript, to detect malleability and other security * properties to assess if the miniscript is sane. + * The stack size limits are only valid for miniscript within wsh. * * @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 7a5a3667..c6534af4 100644 --- a/src/handler/lib/policy.c +++ b/src/handler/lib/policy.c @@ -91,7 +91,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, @@ -126,9 +126,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}}; @@ -559,7 +592,12 @@ __attribute__((warn_unused_result)) static int process_generic_node(policy_parse compressed_pubkey)) { return -1; } - crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory + if (!state->is_taproot) { + crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory + } else { + // x-only pubkey if within taproot + crypto_hash160(compressed_pubkey + 1, 32, compressed_pubkey); // reuse memory + } update_output_u8(state, 20); // PUSH 20 bytes update_output(state, compressed_pubkey, 20); @@ -649,12 +687,22 @@ __attribute__((warn_unused_result)) static int process_pkh_wpkh_node(policy_pars update_output_u8(state, 20); // PUSH 20 bytes - crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory + if (!state->is_taproot) { + crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory + } else { + // x-only pubkey if within taproot + crypto_hash160(compressed_pubkey + 1, 32, compressed_pubkey); // reuse memory + } update_output(state, compressed_pubkey, 20); update_output_u8(state, OP_EQUALVERIFY); update_output_op_v(state, OP_CHECKSIG); } else { // policy->base.type == TOKEN_WPKH + if (state->is_taproot) { + PRINTF("wpkh is invalid within taproot context"); + return -1; + } + update_output_u8(state, OP_0); update_output_u8(state, 20); // PUSH 20 bytes @@ -1689,6 +1737,91 @@ static int get_pubkey_from_merkle_tree(dispatcher_context_t *dispatcher_context, return 0; } +static int is_miniscript_sane(const policy_node_t *script, MiniscriptContext context) { + if (context != MINISCRIPT_CONTEXT_P2WSH && context != MINISCRIPT_CONTEXT_TAPSCRIPT) { + return WITH_ERROR(-1, "Unknown miniscript context"); + } + if (!script->flags.is_miniscript) { + return WITH_ERROR(-1, "This function can only be called for miniscript"); + } + + // Top level node in miniscript must be type B + if (script->flags.miniscript_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "Top level miniscript node must be of type B"); + } + + // check miniscript sanity conditions + policy_node_ext_info_t ext_info; + if (0 > compute_miniscript_policy_ext_info(script, &ext_info, context)) { + return WITH_ERROR(-1, "Error analyzing miniscript policy"); + } + + // Check that non-malleability can be guaranteed + if (!ext_info.m) { + return WITH_ERROR(-1, "Miniscript cannot always be satisfied non-malleably"); + } + + // Check that a signature is always required to satisfy the miniscript + if (!ext_info.s) { + return WITH_ERROR(-1, "Miniscript does not always require a signature"); + } + + // Check that there is no time-lock mix + if (!ext_info.k) { + return WITH_ERROR(-1, "Miniscript with time-lock mix"); + } + + // Note: the following limits could be relaxed for taproot miniscript; however, that + // would mean that tapscripts could run into the maximum stack size limits during + // execution, which we didn't implement explicit checks against. + // Therefore, we rather apply the conservative limit for segwit even to tapscripts. + // We don't expect these limits to be reached in real-world policies. + + // Check the maximum stack size to satisfy the policy + if (ext_info.ss.sat == -1 || (uint32_t) ext_info.ss.sat > MAX_STANDARD_P2WSH_STACK_ITEMS) { + return WITH_ERROR(-1, "Miniscript exceeds maximum standard stack size"); + } + + if (ext_info.ops.sat == -1) { + // Should never happen for non-malleable scripts + return WITH_ERROR(-1, "Invalid maximum ops computations"); + } + + // Check ops limit + if ((uint32_t) ext_info.ops.count + (uint32_t) ext_info.ops.sat > MAX_OPS_PER_SCRIPT) { + return WITH_ERROR(-1, "Miniscript exceeds maximum ops"); + } + + // Check the script size + if (ext_info.script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) { + return WITH_ERROR(-1, "Miniscript exceeds maximum script size"); + } + return 0; +} + +static int is_taptree_miniscript_sane(const policy_node_tree_t *taptree) { + // Recurse until leaves are found, then check sanity if they contain miniscript. + // No check is performed on leaves not containing miniscript. + if (taptree->is_leaf) { + const policy_node_t *script = resolve_node_ptr(&taptree->script); + if (script->flags.is_miniscript && // only check for miniscript leaves + 0 > is_miniscript_sane(script, MINISCRIPT_CONTEXT_TAPSCRIPT)) { + return -1; + } + } else { + if (0 > is_taptree_miniscript_sane( + (const policy_node_tree_t *) resolve_node_ptr(&taptree->left_tree))) { + return -1; + } + if (0 > is_taptree_miniscript_sane( + (const policy_node_tree_t *) resolve_node_ptr(&taptree->right_tree))) { + return -1; + } + } + + return 0; +} + int is_policy_sane(dispatcher_context_t *dispatcher_context, const policy_node_t *policy, int wallet_version, @@ -1698,53 +1831,16 @@ int is_policy_sane(dispatcher_context_t *dispatcher_context, const policy_node_t *inner = resolve_node_ptr(&((const policy_node_with_script_t *) policy)->script); if (inner->flags.is_miniscript) { - // Top level node in miniscript must be type B - if (inner->flags.miniscript_type != MINISCRIPT_TYPE_B) { - return WITH_ERROR(-1, "Top level miniscript node must be of type B"); - } - - // check miniscript sanity conditions - policy_node_ext_info_t ext_info; - if (0 > compute_miniscript_policy_ext_info(inner, &ext_info)) { - return WITH_ERROR(-1, "Error analyzing miniscript policy"); - } - - // Check that non-malleability can be guaranteed - if (!ext_info.m) { - return WITH_ERROR(-1, "Miniscript cannot always be satisfied non-malleably"); - } - - // Check that a signature is always required to satisfy the miniscript - if (!ext_info.s) { - return WITH_ERROR(-1, "Miniscript does not always require a signature"); - } - - // Check that there is no time-lock mix - if (!ext_info.k) { - return WITH_ERROR(-1, "Miniscript with time-lock mix"); - } - - // Check the maximum stack size to satisfy the policy - if (ext_info.ss.sat == -1 || - (uint32_t) ext_info.ss.sat > MAX_STANDARD_P2WSH_STACK_ITEMS) { - return WITH_ERROR(-1, "Miniscript exceeds maximum standard stack size"); - } - - if (ext_info.ops.sat == -1) { - // Should never happen for non-malleable scripts - return WITH_ERROR(-1, "Invalid maximum ops computations"); - } - - // Check ops limit - if ((uint32_t) ext_info.ops.count + (uint32_t) ext_info.ops.sat > MAX_OPS_PER_SCRIPT) { - return WITH_ERROR(-1, "Miniscript exceeds maximum ops"); - } - - // Check the script size - if (ext_info.script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) { - return WITH_ERROR(-1, "Miniscript exceeds maximum script size"); + if (0 > is_miniscript_sane(inner, MINISCRIPT_CONTEXT_P2WSH)) { + return -1; } } + } else if (policy->type == TOKEN_TR) { + // if there is a taptree, we check the sanity of every miniscript leaf + const policy_node_tree_t *taptree = ((const policy_node_tr_t *) policy)->tree; + if (taptree != NULL && 0 > is_taptree_miniscript_sane(taptree)) { + return -1; + } } // check that all the xpubs are different 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); From 61077317e9af12e50982fd29ecfaef05bfa55604 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:06:51 +0000 Subject: [PATCH 2/8] Check immediately for possible error result of parse_token --- src/common/wallet.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/wallet.c b/src/common/wallet.c index 0ff44121..2a83ef4c 100644 --- a/src/common/wallet.c +++ b/src/common/wallet.c @@ -628,6 +628,10 @@ 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 (token == TOKEN_INVALID) { + PRINTF("Failed to parse token"); + return -1; + } if (context_flags & CONTEXT_WITHIN_SH) { // whitelist of allowed tokens within sh; in particular, no miniscript @@ -2799,6 +2803,7 @@ int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, PRINTF("Not miniscript: %d\n", policy_node->type); return -1; default: + PRINTF("%s, %d\n", __FILE__, __LINE__); PRINTF("Unknown token: %d\n", policy_node->type); return -1; } From 47e4f5ed9d639429739e685fbb94b6036c57ae47 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Tue, 25 Apr 2023 17:36:37 +0200 Subject: [PATCH 3/8] Add tapminiscript tests --- tests/test_e2e_tapscripts.py | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tests/test_e2e_tapscripts.py b/tests/test_e2e_tapscripts.py index debaf4da..d558e652 100644 --- a/tests/test_e2e_tapscripts.py +++ b/tests/test_e2e_tapscripts.py @@ -286,3 +286,100 @@ def test_e2e_tapscript_large(rpc, rpc_test_wallet, client: Client, speculos_glob keys_info=keys_info) run_test_e2e(wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, client, speculos_globals, comm) + + +def test_e2e_tapminiscript_keypath_or_decaying_3of3(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): + # The key path is external + # The only script path is a decaying 3-of-3 that becomes a 2-of-3 after the timelock. + # Only one internal key in the script path. + + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + core_name_2, core_xpub_orig_2 = create_new_wallet() + core_name_3, core_xpub_orig_3 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Internal or decaying 3-of-3", + descriptor_template="tr(@0/**,thresh(3,pk(@1/**),s:pk(@2/**),s:pk(@3/**),sln:older(12960)))", + keys_info=[ + f"{core_xpub_orig_1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_2}", + f"{core_xpub_orig_3}", + ]) + + run_test_e2e(wallet_policy, [core_name_2, core_name_3], + rpc, rpc_test_wallet, client, speculos_globals, comm) + + +def test_e2e_tapminiscript_with_hash256(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): + # a taptree containing a hash challenge in a script path (but we're signing for the other script path) + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + _, core_xpub_orig_2 = create_new_wallet() + core_name_3, core_xpub_orig_3 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Hash challenge", + descriptor_template="tr(@0/**,{and_v(v:pk(@1/**),hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588)),multi_a(2,@2/**,@3/**)})", + keys_info=[ + f"{core_xpub_orig_1}", + f"{core_xpub_orig_2}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_3}", + ]) + + run_test_e2e(wallet_policy, [core_name_3], rpc, rpc_test_wallet, client, speculos_globals, comm) + + +def test_e2e_tapminiscript_mixed_leaves(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): + # A leaf has miniscript, a leaf has sortedmulti_a (which is not miniscript) + + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + _, core_xpub_orig_2 = create_new_wallet() + _, core_xpub_orig_3 = create_new_wallet() + _, core_xpub_orig_4 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Mixed tapminiscript and not", + descriptor_template="tr(@0/**,{sortedmulti_a(1,@1/**,@2/**),or_b(pk(@3/**),s:pk(@4/**))})", + keys_info=[ + f"{core_xpub_orig_1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_2}", + f"{core_xpub_orig_3}", + f"{core_xpub_orig_4}", + ]) + + run_test_e2e(wallet_policy, [], + rpc, rpc_test_wallet, client, speculos_globals, comm) + + +def test_invalid_tapminiscript(client: Client, speculos_globals: SpeculosGlobals): + path = "48'/1'/0'/2'" + _, core_xpub_orig1 = create_new_wallet() + _, core_xpub_orig2 = create_new_wallet() + _, core_xpub_orig3 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + internal_xpub_orig = f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}" + + # can't have scripts in the key path + run_test_invalid(client, "tr(pk(@0/**))", [internal_xpub_orig]) + run_test_invalid(client, "tr(pk(@0/**),pk(@1/**))", [internal_xpub_orig, core_xpub_orig1]) + + # test scripts that are invalid inside taproot trees + run_test_invalid(client, "tr(@0,sh(pk(@1/**)))", [internal_xpub_orig, core_xpub_orig1]) + run_test_invalid(client, "tr(@0,wsh(pk(@1/**)))", [internal_xpub_orig, core_xpub_orig1]) + run_test_invalid(client, "tr(@0,multi(1,@1/**,@2/**))", [internal_xpub_orig, core_xpub_orig1, core_xpub_orig2]) + run_test_invalid(client, "tr(@0,sortedmulti(1,@1/**,@2/**))", + [internal_xpub_orig, core_xpub_orig1, core_xpub_orig2]) + + # sortedmulti_a is not valid tapminiscript (but it's valid as a tapscript) + run_test_invalid(client, "tr(@0,or_d(pk(@1/**),sortedmulti_a(2,@2/**,@3/**)))", + [ + internal_xpub_orig, + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + f"{core_xpub_orig3}", + ]) From 4cb7dd70e7b1014d0b83ce83042e306a94c04cf5 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Tue, 18 Apr 2023 11:49:39 +0200 Subject: [PATCH 4/8] Add set of tests on generated address for miniscripts covering all fragments --- tests/test_get_wallet_address.py | 115 +++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tests/test_get_wallet_address.py b/tests/test_get_wallet_address.py index 9206db58..91513008 100644 --- a/tests/test_get_wallet_address.py +++ b/tests/test_get_wallet_address.py @@ -1,9 +1,16 @@ +from hashlib import sha256 +import hmac +import re + from bitcoin_client.ledger_bitcoin import Client, AddressType, MultisigWallet, WalletPolicy from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError +from .conftest import testnet_to_regtest_addr as T import pytest +from test_utils import SpeculosGlobals + # TODO: add tests with UI @@ -294,3 +301,111 @@ def test_get_wallet_address_large_addr_index(client: Client): # too large address_index, not allowed for an unhardened step with pytest.raises(IncorrectDataError): client.get_wallet_address(wallet, wallet_hmac, 0, 2**31, False) + + +def test_get_wallet_address_miniscript_all_fragments(client: Client, speculos_globals: SpeculosGlobals, rpc): + # Create some miniscripts to exercise all possible fragments at least once, + # by comparing with the addresses generated by bitcoin-core. + + # arbitrary 20-bytes and 32-bytes hex strings + H20 = bytes(list(range(20))).hex() + H32 = bytes(list(range(32))).hex() + fragments_common = [ + "or_d(pk(@0/**),0)", # 0, or_d and pk + "1", # 1 + "c:pk_k(@0/**)", # pk_k and c: + "c:pk_h(@0/**)", # pk_h + "pkh(@0/**)", # pkh + "older(42)", # older + "after(42)", # after + f"sha256({H32})", # sha256 + f"ripemd160({H20})", # ripemd160 + f"hash256({H32})", # hash256 + f"hash160({H20})", # hash160 + "andor(pk(@0/**),older(42),pk(@1/**))", # andor + "and_v(v:pk(@0/**),pk(@1/**))", # and_v and v: + "and_b(pk(@0/**),a:pk(@1/**))", # and_b and a: + "or_b(pk(@0/**),a:pk(@1/**))", # or_b + "t:or_c(pk(@0/**),v:pk(@1/**))", # or_c and t: + # or_d is covered + "or_i(pk(@0/**),pk(@1/**))", # or_i + "thresh(1,pk(@0/**),a:pk(@1/**))", # thresh + "thresh(1,pk(@0/**))", # thresh(1,X) + + # WRAPPERS not covered above + # a: is covered + "and_b(1,s:pk(@0/**))", # s: + # c: is covered + "dv:older(42)", # d: + # t: is covered + # v: is covered + "j:pk(@0/**)", # j: + "n:pk(@0/**)", # n: + "l:pk(@0/**)", # l: + "u:pk(@0/**)", # u: + ] + + fragments_wsh = [ + *fragments_common, + "multi(2,@0/**,@1/**,@2/**)", # multi + "multi(1,@0/**)", # multi(1,X) + ] + + fragments_tr = [ + *fragments_common, + "multi_a(2,@0/**,@1/**,@2/**)", # multi_a + "multi_a(1,@0/**)", # multi_a(1,X) + ] + + def prepend_a(frag): + # prepends the a: wrapper (taking into account that `frag` could already start with wrappers) + if re.match("^[a-z]+:", frag): + return "a" + frag + else: + return "a:" + frag + + test_keys = [ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + 'tpubDDcmHJ6bsQqSRDzXrF1cgyPfXpFTHmqBUcq5cevfszh83XJtjqXZXDYwP3N82bA51dBVhbe3uaaWwAxW2tEsjgZPXmupQpNwdmULXq1WXDU', + 'tpubDCXK744twow5CX8HdAvV4Vez413R4xrM3hgD85mA3EpbnwgvtBmhh18eLAGsL5R9E2mwThPTz9fs4x4ZYgCC6GuuKmzSitH9FgWyqaDEKta', + 'tpubDCLxCbopTq5qisZzRcf5ZJ8dHR3PXEexc1vDUR61eGDnSVcXjvEwC9CFXqRPzCi9vmrMd6xfJtFrZY8yrPo5886K1AjJACAviLuEXMNfvbS', + 'tpubDB7v3qHJSGF9r3c1VRnQxwVi7gaTWWF7rt1zohdU4RRahfcvcYXjQ63PWzHSGBzY3ZCu61A1t9SENM147kwdWimtmo5Lm5HgPNgzk83Q45x' + ] + + is_change = False + addr_index = 3 + + def generate_address_and_compare_with_core(desc_tmpl: str): + n_keys_total = desc_tmpl.count("@") + wallet_policy = WalletPolicy("A policy", desc_tmpl, test_keys[:n_keys_total]) + + assert n_keys_total <= len(test_keys), "add more tpubs to the test_keys" + + wallet_hmac = hmac.new(speculos_globals.wallet_registration_key, wallet_policy.id, sha256).digest() + addr_hww = client.get_wallet_address(wallet_policy, wallet_hmac, is_change, addr_index, False) + + desc = wallet_policy.get_descriptor(is_change) + # compute descriptor checksum and derive the address + desc_chk = rpc.getdescriptorinfo(desc)["descriptor"] + addr_core = rpc.deriveaddresses(desc_chk, [3, 3])[0] + + assert T(addr_hww) == addr_core + + for fr in fragments_wsh: + # We use "and_b(pk(@/**),a:})" as a generic gadget to compute + # a valid descriptor that can be registered, as long as the if valid and safe. + + n_keys = fr.count("@") + desc_tmpl = f"wsh(and_b(pk(@{n_keys}/**),{prepend_a(fr)}))" + + generate_address_and_compare_with_core(desc_tmpl) + + for fr in fragments_tr: + # For taproot miniscript, we use "tr(@, and_b(pk(@/**),a:})" + # as the generic gadget. + + n_keys = fr.count("@") + desc_tmpl = f"tr(@{n_keys}/**,and_b(pk(@{n_keys+1}/**),{prepend_a(fr)}))" + + generate_address_and_compare_with_core(desc_tmpl) + From 1015fc317ae2e4323531f231beb2677e1a95bc06 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Tue, 27 Jun 2023 13:14:43 +0200 Subject: [PATCH 5/8] Adapt miniscript unit tests to tapscript --- unit-tests/test_wallet.c | 117 ++++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 27 deletions(-) diff --git a/unit-tests/test_wallet.c b/unit-tests/test_wallet.c index 18437036..54b56361 100644 --- a/unit-tests/test_wallet.c +++ b/unit-tests/test_wallet.c @@ -272,7 +272,7 @@ static void test_get_policy_segwit_version(void **state) { (void) state; uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; - policy_node_t *policy = out; + policy_node_t *policy = (policy_node_t *) out; // legacy policies (returning -1) parse_policy("pkh(@0/**)", out, sizeof(out)); @@ -361,41 +361,98 @@ enum TestMode { TESTMODE_VALID = 1, TESTMODE_NONMAL = 2, TESTMODE_NEEDSIG = 4, - TESTMODE_TIMELOCKMIX = 8 // ignored in our tests + TESTMODE_TIMELOCKMIX = 8, // ignored in our tests + //! Invalid only under P2WSH context + TESTMODE_P2WSH_INVALID = 16, + //! Invalid only under Tapscript context + TESTMODE_TAPSCRIPT_INVALID = 32 }; static void Test(const char *ms, const char *hexscript, int mode, int opslimit, int stacklimit) { - char descriptor[1024]; + char descriptor_wsh[1024]; + char descriptor_tr[1024]; - if (strlen(ms) + sizeof("wsh()") > sizeof(descriptor)) { + if (strlen(ms) + sizeof("wsh(") + sizeof(")") > sizeof(descriptor_wsh)) { + assert(false); + } + + if (strlen(ms) + sizeof("tr(@0/<100;101>/*,") + sizeof(")") > sizeof(descriptor_tr)) { assert(false); } // Wrap the miniscript inside "wsh" - strcpy(descriptor, "wsh("); - strcat(descriptor, ms); - strcat(descriptor, ")"); + strcpy(descriptor_wsh, "wsh("); + strcat(descriptor_wsh, ms); + strcat(descriptor_wsh, ")"); + + // Wrap the miniscript inside a "tr" script. + // We make sure to use a key placeholder with derivations that + // are not used elsewhere in the descriptor template. + strcpy(descriptor_tr, "tr(@0/<100;101>/*,"); + strcat(descriptor_tr, ms); + strcat(descriptor_tr, ")"); + uint8_t out_wsh[MAX_WALLET_POLICY_MEMORY_SIZE]; + uint8_t out_tr[MAX_WALLET_POLICY_MEMORY_SIZE]; + + uint8_t *out; + + int res = 0; + if ((mode & TESTMODE_P2WSH_INVALID) != 0 && (mode & TESTMODE_TAPSCRIPT_INVALID) != 0) { + assert(false); // at most one of TESTMODE_P2WSH_INVALID and TESTMODE_TAPSCRIPT_INVALID + } - uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; + buffer_t descriptor_template_wsh_buf = + buffer_create((void *) descriptor_wsh, strlen(descriptor_wsh)); - int res; - buffer_t descriptor_template_buf = buffer_create((void *) descriptor, strlen(descriptor)); + int res_wsh = parse_descriptor_template(&descriptor_template_wsh_buf, + out_wsh, + sizeof(out_wsh), + WALLET_POLICY_VERSION_V2); + + buffer_t descriptor_template_tr_buf = + buffer_create((void *) descriptor_tr, strlen(descriptor_tr)); + + int res_tr = parse_descriptor_template(&descriptor_template_tr_buf, + out_tr, + sizeof(out_tr), + WALLET_POLICY_VERSION_V2); + + res = res_wsh; + out = out_wsh; - res = parse_descriptor_template(&descriptor_template_buf, - out, - sizeof(out), - WALLET_POLICY_VERSION_V2); + if (mode & TESTMODE_P2WSH_INVALID) { + assert_true(res_wsh < 0); + + // we use the result of parsing as wsh script in the remaining + // tests below, unless that's invalid + res = res_tr; + out = out_tr; + } + + if (mode & TESTMODE_TAPSCRIPT_INVALID) { + assert_true(res_tr < 0); + } if (mode == TESTMODE_INVALID) { assert_true(res < 0); } else { assert_true(res == 0); - policy_node_with_script_t *policy = (policy_node_with_script_t *) out; + const policy_node_t *miniscript; + int context; + if (((policy_node_t *) out)->type == TOKEN_WSH) { + miniscript = resolve_ptr(&((policy_node_with_script_t *) out)->script); + context = MINISCRIPT_CONTEXT_P2WSH; + } else { + assert_true(((policy_node_t *) out)->type == TOKEN_TR); + assert_true(((policy_node_tr_t *) out)->tree->is_leaf); + + miniscript = resolve_ptr(&((policy_node_tr_t *) out)->tree->script); + context = MINISCRIPT_CONTEXT_TAPSCRIPT; + } + policy_node_ext_info_t ext_info; - res = compute_miniscript_policy_ext_info(resolve_ptr(&policy->script), - &ext_info, - MINISCRIPT_CONTEXT_P2WSH); + res = compute_miniscript_policy_ext_info(miniscript, &ext_info, context); assert_true(res == 0); @@ -499,23 +556,23 @@ static void test_miniscript_types(void **state) { // Randomly generated test set that covers the majority of type and node type combinations Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4); - Test("uuj:and_v(v:multi(2,@0/**,@1/**),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 14, 6); - Test("or_b(un:multi(2,@0/**,@1/**),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", TESTMODE_VALID, 14, 6); + Test("uuj:and_v(v:multi(2,@0/**,@1/**),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 6); + Test("or_b(un:multi(2,@0/**,@1/**),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", TESTMODE_VALID | TESTMODE_TAPSCRIPT_INVALID, 14, 6); Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", TESTMODE_VALID | TESTMODE_NONMAL, 11, 2); Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4); - Test("t:andor(multi(3,@0/**,@1/**,@2/**),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", TESTMODE_VALID | TESTMODE_NONMAL, 13, 6); - Test("or_d(multi(1,@0/**),or_b(multi(3,@1/**,@2/**,@3/**),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", TESTMODE_VALID | TESTMODE_NONMAL, 15, 8); + Test("t:andor(multi(3,@0/**,@1/**,@2/**),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 13, 6); + Test("or_d(multi(1,@0/**),or_b(multi(3,@1/**,@2/**,@3/**),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 15, 8); Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 2); - Test("and_v(or_i(v:multi(2,@0/**,@1/**),v:multi(2,@2/**,@3/**)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 11, 6); - Test("j:and_b(multi(2,@0/**,@1/**),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", TESTMODE_VALID | TESTMODE_NEEDSIG, 14, 5); + Test("and_v(or_i(v:multi(2,@0/**,@1/**),v:multi(2,@2/**,@3/**)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 11, 6); + Test("j:and_b(multi(2,@0/**,@1/**),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 5); Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", TESTMODE_VALID, 12, 2); Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", TESTMODE_VALID, 16, 3); Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", TESTMODE_VALID | TESTMODE_NONMAL, 15, 3); - Test("thresh(2,multi(2,@0/**,@1/**),a:multi(1,@2/**),ac:pk_k(@3/**))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 13, 7); + Test("thresh(2,multi(2,@0/**,@1/**),a:multi(1,@2/**),ac:pk_k(@3/**))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 13, 7); Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", TESTMODE_VALID, 14, 3); Test("or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b2696892736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", TESTMODE_VALID, 15, 3); - Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,@0/**)),pk_k(@1/**))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", TESTMODE_VALID | TESTMODE_NEEDSIG, 8, 3); - Test("c:and_v(or_c(multi(2,@0/**,@1/**),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(@2/**))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 10, 6); + Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,@0/**)),pk_k(@1/**))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 8, 3); + Test("c:and_v(or_c(multi(2,@0/**,@1/**),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(@2/**))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 10, 6); Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", TESTMODE_VALID, 14, 3); Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", TESTMODE_VALID, 20, 3); Test("or_i(c:and_v(v:after(500000),pk_k(@0/**)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 3); @@ -530,6 +587,12 @@ static void test_miniscript_types(void **state) { Test("thresh(1,c:pk_k(@0/**),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 4); Test("thresh(2,c:pk_k(@0/**),ac:pk_k(@1/**),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 5); + // Additional Tapscript-related tests + Test("and_v(v:multi_a(2,@0/**,@1/**),after(1231488000))", "20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aac205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba529d0400046749b1", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4, 3); + + // Since 'd:' is 'u' we can use it directly inside a thresh. But we can't under P2WSH. + Test("thresh(2,dv:older(42),s:pk(@0/**),s:pk(@1/**))", "7663012ab269687c205cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bcac937c20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 12, 4); + // clang-format on } From a8002f0587f4f64801fb17710631894093c8bf0a Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:38:03 +0100 Subject: [PATCH 6/8] Increase maximum depth of the supported taptrees on non-NanoS devices; generalize corresponding test so that it adapts to the different limits --- src/common/wallet.h | 7 ++++++- src/handler/lib/check_merkle_tree_sorted.h | 6 ++++-- tests/test_e2e_tapscripts.py | 13 ++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/common/wallet.h b/src/common/wallet.h index b440eac5..c43b57d0 100644 --- a/src/common/wallet.h +++ b/src/common/wallet.h @@ -73,7 +73,12 @@ MAX(MAX_WALLET_POLICY_SERIALIZED_LENGTH_V1, MAX_WALLET_POLICY_SERIALIZED_LENGTH_V2) // maximum depth of a taproot tree that we support -#define MAX_TAPTREE_POLICY_DEPTH 4 +// (here depth 1 means only the root of the taptree) +#ifdef TARGET_NANOS +#define MAX_TAPTREE_POLICY_DEPTH 5 +#else +#define MAX_TAPTREE_POLICY_DEPTH 9 +#endif typedef struct { uint32_t master_key_derivation[MAX_BIP32_PATH_STEPS]; diff --git a/src/handler/lib/check_merkle_tree_sorted.h b/src/handler/lib/check_merkle_tree_sorted.h index 2cb8993e..23f28d51 100644 --- a/src/handler/lib/check_merkle_tree_sorted.h +++ b/src/handler/lib/check_merkle_tree_sorted.h @@ -2,11 +2,13 @@ #include "../../boilerplate/dispatcher.h" #include "../../common/merkle.h" +#include "../../common/wallet.h" // this flow aborts if any element is larger than this size -// This is enough for a PSBT with the control block of a taptree of depth 5 tapbranches // TODO: we might remove this limitation altogether with a more careful implementation. -#define MAX_CHECK_MERKLE_TREE_SORTED_PREIMAGE_SIZE 162 +// Here we make sure that we have enough space for control block of a taptree of the maximum +// supported depth +#define MAX_CHECK_MERKLE_TREE_SORTED_PREIMAGE_SIZE (34 + 32 * (MAX_TAPTREE_POLICY_DEPTH - 1)) typedef void (*merkle_tree_elements_callback_t)(struct dispatcher_context_s *, void *, diff --git a/tests/test_e2e_tapscripts.py b/tests/test_e2e_tapscripts.py index d558e652..9731ffba 100644 --- a/tests/test_e2e_tapscripts.py +++ b/tests/test_e2e_tapscripts.py @@ -236,21 +236,28 @@ def test_e2e_tapscript_multi_a_2of2(rpc, rpc_test_wallet, client: Client, specul rpc, rpc_test_wallet, client, speculos_globals, comm) -def test_e2e_tapscript_depth4(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient]): +def test_e2e_tapscript_maxdepth(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, comm: Union[TransportClient, SpeculosClient], model: str): # A taproot tree with maximum supported depth, where the internal key is in the deepest script + MAX_TAPTREE_POLICY_DEPTH = 4 if model == "nanos" else 9 + + # Make the most unbalanced tree where each script is a simple pk() + parts = [f"pk(@{i}/**)" for i in range(1, MAX_TAPTREE_POLICY_DEPTH)] + descriptor_template = "tr(@0/**,{" + ',{'.join(parts) + f",pk(@{MAX_TAPTREE_POLICY_DEPTH}/**)" + "}" * (MAX_TAPTREE_POLICY_DEPTH - 1) + ")" + keys_info = [] - for _ in range(4): + for _ in range(MAX_TAPTREE_POLICY_DEPTH): _, core_xpub_orig = create_new_wallet() keys_info.append(core_xpub_orig) + # the last (deepest) script is the only one we sign with the ledger key path = "499'/1'/0'" internal_xpub = get_internal_xpub(speculos_globals.seed, path) keys_info.append(f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}") wallet_policy = WalletPolicy( name="Tapscriptception", - descriptor_template="tr(@0/**,{pk(@1/**),{pk(@2/**),{pk(@3/**),pk(@4/**)}}})", + descriptor_template=descriptor_template, keys_info=keys_info) run_test_e2e(wallet_policy, [], rpc, rpc_test_wallet, client, speculos_globals, comm) From 84daf9b1f33840277ce1339ef0d7d6aa6d0e4459 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:47:57 +0000 Subject: [PATCH 7/8] Typos and formatting --- doc/wallet.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/wallet.md b/doc/wallet.md index 044266cd..c5f22aa6 100644 --- a/doc/wallet.md +++ b/doc/wallet.md @@ -3,7 +3,7 @@ A _wallet policy_ is a structured representation of an account secured by a policy expressed with output script descriptors. It is composed by two parts: a wallet descriptor template and the vector of key placeholder expressions. -A _wallet descriptor template_ follows language very similar to output descriptor, with a few differences; the biggest one is that each `KEY` expression with a key placeholder `KP` expression, that refers to to one of the keys in the _keys information vector_, plus the additional derivation steps to use for that key. Contextually, the keys information vector contains all the relevant _xpubs_, and possibly their key origin information. +A _wallet descriptor template_ follows language very similar to output descriptor, with a few differences; the biggest one is that each `KEY` expression with a key placeholder `KP` expression, that refers to one of the keys in the _keys information vector_, plus the additional derivation steps to use for that key. Contextually, the keys information vector contains all the relevant _xpubs_, and possibly their key origin information. Each entry in the key information vector contains an _xpub_ (other types of keys supported in output script descriptors are not allowed), possible preceeded by the key origin information. The key origin information is compulsory for internal keys. @@ -89,7 +89,7 @@ derivation steps is used when the corresponding key information is not an xpub. The key information vector *should* be ordered so that placeholder `@i` -never appear for the first time before an occurrence of `@j` for some `j < i`; for example, the first placeholder is always `@0`, the next one is +never appear for the first time before an occurrence of `@j` for some `j < i`; for example, the first placeholder is always `@0`, the next one is `@1`, etc. ### Implementation-specific restrictions @@ -106,9 +106,9 @@ receive and change addresses by: - replacing each key placeholder with the corresponding key origin information; -- replacing every `/**` with `/0/*` for the receive descriptor, and `/1/*` +- replacing every `/**` with `/0/*` for the receive descriptor, and `/1/*` for the change descriptor; -- replacing every `/` with `/M` for the receive descriptor, and `/N` +- replacing every `/` with `/M` for the receive descriptor, and `/N` for the change descriptor. For example, the wallet descriptor `pkh(@0/**)` with key information @@ -144,7 +144,7 @@ The wallet policy is serialized as the concatenation of: - `1 byte`: a byte equal to `0x02`, the version of the wallet policy language - `1 byte`: the length of the wallet name (0 for standard wallet) -- ``: the wallet name (empty for standard wallets) +- ``: the wallet name (empty for standard wallets) - ``: the length of the wallet descriptor template, encoded as a Bitcoin-style variable-length integer - `32 bytes`: the sha256 hash of the wallet descriptor template - ``: the number of keys in the list of keys, encoded as a Bitcoin-style variable-length integer @@ -188,4 +188,4 @@ A few policies that correspond to standardized single-key wallets can be used wi - ``sh(wpkh(@0/**))`` - nested segwit addresses as per [BIP-49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) - ``tr(@0/**)`` - single Key P2TR as per [BIP-86](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki) -Note that the wallet policy is considered standard (and therefore usable for signing without prior registration) only if the signing paths (defined in the key origin information) adhere to the corresponding BIP. Moreover, the BIP-44 `account` level must be at most `100`, and the `address index` at most `50000`. Larger values can still be used by registering the policy. +Note that the wallet policy is considered standard (and therefore usable for signing without prior registration) only if the signing paths (defined in the key origin information) adhere to the corresponding BIP. Moreover, the BIP-44 `account` level must be at most `100`, and the `address index` at most `50000`. Larger values can still be used by registering the policy. From eaf496b4c655a70d37867a7d7a21daf042516fbd Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Fri, 10 Nov 2023 13:59:59 +0000 Subject: [PATCH 8/8] Nits from code review --- src/common/wallet.c | 2 +- src/common/wallet.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/wallet.c b/src/common/wallet.c index 2a83ef4c..e238a264 100644 --- a/src/common/wallet.c +++ b/src/common/wallet.c @@ -1547,7 +1547,7 @@ static int parse_script(buffer_t *in_buf, if (token != TOKEN_MULTI_A && token != TOKEN_SORTEDMULTI_A) { return WITH_ERROR( -1, - "multi and sortedmulti can only be used legacy or segwit scripts"); + "multi and sortedmulti can only be used in legacy or segwit scripts"); } } else { // legacy or segwit scripts if (token != TOKEN_MULTI && token != TOKEN_SORTEDMULTI) { diff --git a/src/common/wallet.h b/src/common/wallet.h index c43b57d0..6ff81920 100644 --- a/src/common/wallet.h +++ b/src/common/wallet.h @@ -75,7 +75,7 @@ // maximum depth of a taproot tree that we support // (here depth 1 means only the root of the taptree) #ifdef TARGET_NANOS -#define MAX_TAPTREE_POLICY_DEPTH 5 +#define MAX_TAPTREE_POLICY_DEPTH 4 #else #define MAX_TAPTREE_POLICY_DEPTH 9 #endif