From 78e3194a0eb81adc51420f217547343f7155a747 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:25:09 +0100 Subject: [PATCH] Musig key aggregation and address generation --- src/handler/lib/policy.c | 35 ++++++++-- src/musig/musig.c | 146 +++++++++++++++++++++++++++++++++++++++ src/musig/musig.h | 45 ++++++++++++ 3 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 src/musig/musig.c create mode 100644 src/musig/musig.h diff --git a/src/handler/lib/policy.c b/src/handler/lib/policy.c index 9d1029f93..21373019d 100644 --- a/src/handler/lib/policy.c +++ b/src/handler/lib/policy.c @@ -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" @@ -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 // child of this pubkey diff --git a/src/musig/musig.c b/src/musig/musig.c new file mode 100644 index 000000000..b4ffb4432 --- /dev/null +++ b/src/musig/musig.c @@ -0,0 +1,146 @@ +#include + +#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; +} diff --git a/src/musig/musig.h b/src/musig/musig.h new file mode 100644 index 000000000..d17f89b67 --- /dev/null +++ b/src/musig/musig.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +// 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);