Disable musig in key expressions of musig_a/sortedmulti_a
What changed, and why it matters
This commit temporarily blocks a specific advanced Bitcoin wallet feature (MuSig inside multi_a/sortedmulti_a) because the code that reads those wallet descriptions doesn't handle it correctly yet. It's a defensive change to prevent malformed or unexpected wallet policies from being accepted, rather than a fix for a known active attack.
Treat as a hardening patch. Review whether the disabled parsing path could previously have led to memory corruption, incorrect key derivation, or unintended transaction signing. Monitor for a follow-up commit that generalizes the parser and re-enables musig inside multi_a/sortedmulti_a with proper validation.
Security signals we found
Parser hardening for unsupported nested MuSig expressions
Addition of negative unit tests for malformed/unsupported policies
Commentary in code explicitly notes incompatibility and temporary disablement
No CVE, advisory, or vendor security disclosure referenced in commit
Evidence from the diff
The change modifies parse_keyexpr in src/common/wallet.c so that when parsing key expressions inside musig_a and sortedmulti_a fragments, the allow_musig flag is passed as false. Previously, the parser used is_taproot, which would allow musig key expressions anywhere inside a taproot policy. The commit adds unit tests confirming that policies like tr(@0/,multi_a(1,musig(@1,@2)/)) now fail to parse. The commit message states this is because parsing of such fragments is not correctly handled in the current state.
Changed components
src/common/wallet.c policy parsermulti_a fragment parsingsortedmulti_a fragment parsingMuSig2 aggregate key expression handlingInspect captured patch +24 / −13
diff --git a/src/common/wallet.c b/src/common/wallet.c
index fdbff9b..df5e467 100644
--- a/src/common/wallet.c
+++ b/src/common/wallet.c
@@ -437,7 +437,7 @@ int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out, int
* - Single key index:
* - @IDX/**
* - @IDX/<M;N>/*
- * - MuSig2 aggregate key (only if is_taproot is true):
+ * - MuSig2 aggregate key (only if allow_musig is true):
* - musig(@IDX,@IDX,...,@IDX)/**
* - musig(@IDX,@IDX,...,@IDX)/<M;N>/*
* where IDX is a key index.
@@ -446,7 +446,7 @@ int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out, int
static int parse_keyexpr(buffer_t *in_buf,
int version,
policy_node_keyexpr_t *out,
- bool is_taproot,
+ bool allow_musig,
buffer_t *out_buf,
uint16_t *keyexpr_index) {
char c;
@@ -469,7 +469,7 @@ static int parse_keyexpr(buffer_t *in_buf,
return WITH_ERROR(-1, "Expected musig key expression");
}
- if (!is_taproot) {
+ if (!allow_musig) {
return WITH_ERROR(-1, "musig is only allowed in taproot");
}
@@ -1525,7 +1525,7 @@ static int parse_script(buffer_t *in_buf,
if (0 > parse_keyexpr(in_buf,
version,
key_expr,
- is_taproot,
+ is_taproot, // musig is only allowed in taproot
out_buf,
&key_expression_count)) {
return WITH_ERROR(-1, "Couldn't parse key expression");
@@ -1597,6 +1597,7 @@ static int parse_script(buffer_t *in_buf,
}
i_policy_node_keyexpr(&node->key, key_expr);
+ // the taproot internal key can be a musig
if (0 >
parse_keyexpr(in_buf, version, key_expr, true, out_buf, &key_expression_count)) {
return WITH_ERROR(-1, "Couldn't parse key expression");
@@ -1712,7 +1713,9 @@ static int parse_script(buffer_t *in_buf,
node->k = (int16_t) k;
// We allocate the array of key indices at the current position in the output buffer
- // (on success)
+ // (on success).
+ // Note: this is incompatible with musig keys, therefore we don't currently support
+ // musig nested inside multi_a or sortedmulti_a.
buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer
i_policy_node_keyexpr(&node->keys, buffer_get_cur(out_buf));
@@ -1738,12 +1741,14 @@ static int parse_script(buffer_t *in_buf,
return WITH_ERROR(-1, "Out of memory");
}
- if (0 > parse_keyexpr(in_buf,
- version,
- key_expr,
- is_taproot,
- out_buf,
- &key_expression_count)) {
+ if (0 >
+ parse_keyexpr(
+ in_buf,
+ version,
+ key_expr,
+ false, // musig is not currently supported in keys of multisig fragments
+ out_buf,
+ &key_expression_count)) {
return WITH_ERROR(-1, "Error parsing key expression");
}
diff --git a/unit-tests/test_wallet.c b/unit-tests/test_wallet.c
index 7da6878..9628c98 100644
--- a/unit-tests/test_wallet.c
+++ b/unit-tests/test_wallet.c
@@ -300,7 +300,7 @@ static void test_parse_policy_tr_musig_keypath(void **state) {
assert_int_equal(root->base.type, TOKEN_TR);
assert_true(isnull_policy_node_tree(&root->tree));
- check_key_expr_musig(r_policy_node_keyexpr(&root->key), 3, (uint16_t[]){2, 0, 1}, 3, 13);
+ check_key_expr_musig(r_policy_node_keyexpr(&root->key), 3, (uint16_t[]) {2, 0, 1}, 3, 13);
}
static void test_parse_policy_tr_musig_scriptpath(void **state) {
@@ -324,7 +324,7 @@ static void test_parse_policy_tr_musig_scriptpath(void **state) {
policy_node_with_key_t *script_pk = (policy_node_with_key_t *) r_policy_node(&tree->script);
assert_int_equal(script_pk->base.type, TOKEN_PK);
- check_key_expr_musig(r_policy_node_keyexpr(&script_pk->key), 3, (uint16_t[]){2, 0, 3}, 0, 1);
+ check_key_expr_musig(r_policy_node_keyexpr(&script_pk->key), 3, (uint16_t[]) {2, 0, 3}, 0, 1);
}
static void test_get_policy_segwit_version(void **state) {
@@ -438,6 +438,12 @@ static void test_failures(void **state) {
assert_true(0 > parse_policy("wpkh(musig(@0,@1)/**)", out, sizeof(out))); // not taproot
assert_true(
0 > parse_policy("tr(musig(@0,musig(@1,@2))/**)", out, sizeof(out))); // can't nest musig
+
+ // musig is currently disabled in multi_a/sortedmulti_a, until the parsing
+ // of such expressions is properly fixed in parse_policy
+ assert_true(0 > parse_policy("tr(@0/**,multi_a(1,musig(@1,@2)/**))", out, sizeof(out)));
+ assert_true(0 >
+ parse_policy("tr(@0/**,sortedmulti_a(2,musig(@1,@2)/**,@3/**))", out, sizeof(out)));
}
enum TestMode {
Why this scored 42/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.