Skip to content

Commit

Permalink
Musig key aggregation and address generation
Browse files Browse the repository at this point in the history
  • Loading branch information
bigspider committed Mar 11, 2024
1 parent fb65497 commit d8ca7d4
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 7 deletions.
35 changes: 28 additions & 7 deletions src/handler/lib/policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "../lib/get_merkle_leaf_element.h"
#include "../lib/get_preimage.h"
#include "../../crypto.h"
#include "../../musig/musig.h"
#include "../../common/base58.h"
#include "../../common/bitvector.h"
#include "../../common/read.h"
Expand Down Expand Up @@ -462,14 +463,34 @@ __attribute__((warn_unused_result)) static int get_derived_pubkey(

serialized_extended_pubkey_t ext_pubkey;

if (key_expr->type != KEY_EXPRESSION_NORMAL) {
PRINTF("Not implemented\n"); // TODO
return -1;
}
if (key_expr->type == KEY_EXPRESSION_NORMAL) {
if (0 > get_extended_pubkey(dispatcher_context, wdi, key_expr->k.key_index, &ext_pubkey)) {
return -1;
}
} else if (key_expr->type == KEY_EXPRESSION_MUSIG) {
musig_aggr_key_info_t *musig_info = r_musig_aggr_key_info(&key_expr->m.musig_info);
uint16_t *key_indexes = r_uint16(&musig_info->key_indexes);
plain_pk_t keys[MAX_PUBKEYS_PER_MUSIG];
for (int i = 0; i < musig_info->n; i++) {
// we use ext_pubkey as a temporary variable; will overwrite later
if (0 > get_extended_pubkey(dispatcher_context, wdi, key_indexes[i], &ext_pubkey)) {
return -1;
}
memcpy(keys[i], ext_pubkey.compressed_pubkey, sizeof(ext_pubkey.compressed_pubkey));
}

int ret = get_extended_pubkey(dispatcher_context, wdi, key_expr->k.key_index, &ext_pubkey);
if (ret < 0) {
return -1;
musig_keyagg_context_t musig_ctx;
musig_key_agg(keys, musig_info->n, &musig_ctx);

// compute the aggregated extended pubkey
memset(&ext_pubkey, 0, sizeof(ext_pubkey));
write_u32_be(ext_pubkey.version, 0, BIP32_PUBKEY_VERSION);

ext_pubkey.compressed_pubkey[0] = (musig_ctx.Q.y[31] % 2 == 0) ? 2 : 3;
memcpy(&ext_pubkey.compressed_pubkey[1], musig_ctx.Q.x, sizeof(musig_ctx.Q.x));
memcpy(&ext_pubkey.chain_code, BIP_MUSIG_CHAINCODE, sizeof(BIP_MUSIG_CHAINCODE));
} else {
LEDGER_ASSERT(false, "Unreachable code");
}

// we derive the /<change>/<address_index> child of this pubkey
Expand Down
146 changes: 146 additions & 0 deletions src/musig/musig.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include <stdbool.h>

#include "musig.h"

#include "../crypto.h"
#include "../secp256k1.h"

static const uint8_t BIP0327_keyagg_coeff_tag[] =
{'K', 'e', 'y', 'A', 'g', 'g', ' ', 'c', 'o', 'e', 'f', 'f', 'i', 'c', 'i', 'e', 'n', 't'};
static const uint8_t BIP0327_keyagg_list_tag[] =
{'K', 'e', 'y', 'A', 'g', 'g', ' ', 'l', 'i', 's', 't'};

static inline bool is_point_infinite(const point_t *P) {
return P->prefix == 0;
}

static inline void set_point_infinite(point_t *P) {
memset(P->raw, 0, sizeof(point_t));
}

static int point_add(const point_t *P1, const point_t *P2, point_t *out) {
if (is_point_infinite(P1)) {
memmove(out->raw, P2->raw, sizeof(point_t));
return CX_OK;
}
if (is_point_infinite(P2)) {
memmove(out->raw, P1->raw, sizeof(point_t));
return CX_OK;
}
if (memcmp(P1->x, P2->x, 32) == 0 && memcmp(P1->y, P2->y, 32) != 0) {
memset(out->raw, 0, sizeof(point_t));
return CX_OK;
}
return cx_ecfp_add_point_no_throw(CX_CURVE_SECP256K1, out->raw, P1->raw, P2->raw);
}

// out can be equal to P
static int point_negate(const point_t *P, point_t *out) {
if (is_point_infinite(P)) {
set_point_infinite(out);
return 0;
}
memmove(out->x, P->x, 32);

if (CX_OK != cx_math_sub_no_throw(out->y, secp256k1_p, P->y, 32)) return -1;

out->prefix = 4;
return 0;
}

static int cpoint(const uint8_t x[33], point_t *out) {
crypto_tr_lift_x(&x[1], out->raw);
if (is_point_infinite(out)) {
PRINTF("Invalid compressed point\n");
return -1;
}
if (x[0] == 2) {
return 0;
} else if (x[0] == 3) {
if (0 > point_negate(out, out)) {
return -1;
}
return 0;
} else {
PRINTF("Invalid compressed point: invalid prefix\n");
return -1;
}
}

static void musig_get_second_key(const plain_pk_t pubkeys[], size_t n_keys, plain_pk_t out) {
for (size_t i = 0; i < n_keys; i++) {
if (memcmp(pubkeys[0], pubkeys[i], sizeof(plain_pk_t)) != 0) {
memcpy(out, pubkeys[i], sizeof(plain_pk_t));
return;
}
}
memset(out, 0, sizeof(plain_pk_t));
}

static void musig_hash_keys(const plain_pk_t pubkeys[], size_t n_keys, uint8_t out[static 32]) {
cx_sha256_t hash_context;
crypto_tr_tagged_hash_init(&hash_context,
BIP0327_keyagg_list_tag,
sizeof(BIP0327_keyagg_list_tag));
for (size_t i = 0; i < n_keys; i++) {
crypto_hash_update(&hash_context.header, pubkeys[i], sizeof(plain_pk_t));
}
crypto_hash_digest(&hash_context.header, out, 32);
}

static void musig_key_agg_coeff_internal(const plain_pk_t pubkeys[],
size_t n_keys,
const plain_pk_t pk_,
const plain_pk_t pk2,
uint8_t out[static CX_SHA256_SIZE]) {
uint8_t L[CX_SHA256_SIZE];
musig_hash_keys(pubkeys, n_keys, L);
if (memcmp(pk_, pk2, sizeof(plain_pk_t)) == 0) {
memset(out, 0, CX_SHA256_SIZE);
out[31] = 1;
} else {
crypto_tr_tagged_hash(BIP0327_keyagg_coeff_tag,
sizeof(BIP0327_keyagg_coeff_tag),
L,
sizeof(L),
pk_,
sizeof(plain_pk_t),
out);

// result modulo secp256k1_n
int res = cx_math_modm_no_throw(out, CX_SHA256_SIZE, secp256k1_n, sizeof(secp256k1_n));

LEDGER_ASSERT(res == CX_OK, "Modular reduction failed");
}
}

int musig_key_agg(const plain_pk_t pubkeys[], size_t n_keys, musig_keyagg_context_t *ctx) {
plain_pk_t pk2;
musig_get_second_key(pubkeys, n_keys, pk2);

set_point_infinite(&ctx->Q);
for (size_t i = 0; i < n_keys; i++) {
point_t P;

// set P := P_i
if (0 > cpoint(pubkeys[i], &P)) {
PRINTF("Invalid pubkey in musig_key_agg\n");
return -1;
}

uint8_t a_i[32];
musig_key_agg_coeff_internal(pubkeys, n_keys, pubkeys[i], pk2, a_i);

// set P := a_i * P_i
if (CX_OK != cx_ecfp_scalar_mult_no_throw(CX_CURVE_SECP256K1, P.raw, a_i, 32)) {
PRINTF("Scalar multiplication failed in musig_key_agg\n");
return -1;
}

point_add(&ctx->Q, &P, &ctx->Q);
}
memset(ctx->tacc, 0, sizeof(ctx->tacc));
memset(ctx->gacc, 0, sizeof(ctx->gacc));
ctx->gacc[31] = 1;
return 0;
}
45 changes: 45 additions & 0 deletions src/musig/musig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include <stdint.h>
#include <stddef.h>

// TODO: rename once BIP number is assigned
static uint8_t BIP_MUSIG_CHAINCODE[32] = {
0x86, 0x80, 0x87, 0xCA, 0x02, 0xA6, 0xF9, 0x74, 0xC4, 0x59, 0x89, 0x24, 0xC3, 0x6B, 0x57, 0x76,
0x2D, 0x32, 0xCB, 0x45, 0x71, 0x71, 0x67, 0xE3, 0x00, 0x62, 0x2C, 0x71, 0x67, 0xE3, 0x89, 0x65};

typedef uint8_t plain_pk_t[33];
typedef uint8_t xonly_pk_t[32];

// An uncompressed pubkey, encoded as 04||x||y, where x and y are 32-byte big-endian coordinates.
// If the first byte (prefix) is 0, encodes the point at infinity.
typedef struct {
union {
uint8_t raw[65];
struct {
uint8_t prefix; // 0 for the point at infinity, otherwise 4.
uint8_t x[32];
uint8_t y[32];
};
};
} point_t;

typedef struct musig_keyagg_context_s {
point_t Q;
uint8_t gacc[32];
uint8_t tacc[32];
} musig_keyagg_context_t;

/**
* Computes the KeyAgg Context per BIP-0327.
*
* @param[in] pubkeys
* Pointer to a list of pubkeys.
* @param[in] n_keys
* Number of pubkeys.
* @param[out] musig_keyagg_context_t
* Pointer to receive the musig KeyAgg Context.
*
* @return 0 on success, a negative number in case of error.
*/
int musig_key_agg(const plain_pk_t pubkeys[], size_t n_keys, musig_keyagg_context_t *ctx);

0 comments on commit d8ca7d4

Please sign in to comment.