Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tapminiscript #155

Merged
merged 8 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions doc/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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 `/<M;N>` with `/M` for the receive descriptor, and `/N`
- replacing every `/<M;N>` with `/M` for the receive descriptor, and `/N`
for the change descriptor.

For example, the wallet descriptor `pkh(@0/**)` with key information
Expand Down Expand Up @@ -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)
- `<variable length>`: the wallet name (empty for standard wallets)
- `<variable length>`: the wallet name (empty for standard wallets)
- `<variable length>`: 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
- `<variable length>`: the number of keys in the list of keys, encoded as a Bitcoin-style variable-length integer
Expand Down Expand Up @@ -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.
166 changes: 114 additions & 52 deletions src/common/wallet.c

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion src/common/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
// (here depth 1 means only the root of the taptree)
#ifdef TARGET_NANOS
#define MAX_TAPTREE_POLICY_DEPTH 4
#else
#define MAX_TAPTREE_POLICY_DEPTH 9
#endif

typedef struct {
uint32_t master_key_derivation[MAX_BIP32_PATH_STEPS];
Expand Down Expand Up @@ -148,6 +153,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
Expand Down Expand Up @@ -407,13 +417,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

Expand Down
6 changes: 4 additions & 2 deletions src/handler/lib/check_merkle_tree_sorted.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *,
Expand Down
198 changes: 147 additions & 51 deletions src/handler/lib/policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
sgliner-ledger marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand All @@ -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
Expand Down
Loading
Loading