Skip to content

Commit 04b189b

Browse files
committed
musig: optimize key aggregation using const 1 for 2nd key
1 parent ba52f06 commit 04b189b

File tree

3 files changed

+131
-49
lines changed

3 files changed

+131
-49
lines changed

include/secp256k1_musig.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ extern "C" {
2525
* magic: Set during initialization in `pubkey_combine` to allow
2626
* detecting an uninitialized object.
2727
* pk_hash: The 32-byte hash of the original public keys
28+
* second_pk: Serialized x-coordinate of the second public key in the list. Is 0
29+
* if there is none.
2830
* pk_parity: Whether the MuSig-aggregated point was negated when
2931
* converting it to the combined xonly pubkey.
3032
* is_tweaked: Whether the combined pubkey was tweaked
@@ -35,6 +37,7 @@ extern "C" {
3537
typedef struct {
3638
uint64_t magic;
3739
unsigned char pk_hash[32];
40+
unsigned char second_pk[32];
3841
int pk_parity;
3942
int is_tweaked;
4043
unsigned char tweak[32];

src/modules/musig/main_impl.h

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,23 +66,36 @@ static void secp256k1_musig_sha256_init_tagged(secp256k1_sha256 *sha) {
6666
sha->bytes = 64;
6767
}
6868

69-
/* Compute r = SHA256(ell, x). Assumes field element x is normalized. */
70-
static void secp256k1_musig_coefficient(secp256k1_scalar *r, const unsigned char *ell, secp256k1_fe *x) {
69+
/* Compute MuSig coefficient which is constant 1 for the second pubkey and
70+
* SHA256(ell, x) otherwise. second_pk_x can be NULL in case there is no
71+
* second_pk. Assumes both field elements x and second_pk_x are normalized. */
72+
static void secp256k1_musig_coefficient_internal(secp256k1_scalar *r, const unsigned char *ell, secp256k1_fe *x, const secp256k1_fe *second_pk_x) {
7173
secp256k1_sha256 sha;
7274
unsigned char buf[32];
7375

74-
secp256k1_musig_sha256_init_tagged(&sha);
75-
secp256k1_sha256_write(&sha, ell, 32);
76-
secp256k1_fe_get_b32(buf, x);
77-
secp256k1_sha256_write(&sha, buf, 32);
78-
secp256k1_sha256_finalize(&sha, buf);
79-
secp256k1_scalar_set_b32(r, buf, NULL);
76+
if (secp256k1_fe_cmp_var(x, second_pk_x) == 0) {
77+
secp256k1_scalar_set_int(r, 1);
78+
} else {
79+
secp256k1_musig_sha256_init_tagged(&sha);
80+
secp256k1_sha256_write(&sha, ell, 32);
81+
secp256k1_fe_get_b32(buf, x);
82+
secp256k1_sha256_write(&sha, buf, 32);
83+
secp256k1_sha256_finalize(&sha, buf);
84+
secp256k1_scalar_set_b32(r, buf, NULL);
85+
}
86+
}
87+
88+
static void secp256k1_musig_coefficient(secp256k1_scalar *r, const secp256k1_musig_pre_session *pre_session, secp256k1_fe *x) {
89+
secp256k1_fe second_pk_x;
90+
secp256k1_fe_set_b32(&second_pk_x, pre_session->second_pk);
91+
secp256k1_musig_coefficient_internal(r, pre_session->pk_hash, x, &second_pk_x);
8092
}
8193

8294
typedef struct {
8395
const secp256k1_context *ctx;
8496
unsigned char ell[32];
8597
const secp256k1_xonly_pubkey *pks;
98+
secp256k1_fe second_pk_x;
8699
} secp256k1_musig_pubkey_combine_ecmult_data;
87100

88101
/* Callback for batch EC multiplication to compute ell_0*P0 + ell_1*P1 + ... */
@@ -91,7 +104,11 @@ static int secp256k1_musig_pubkey_combine_callback(secp256k1_scalar *sc, secp256
91104
if (!secp256k1_xonly_pubkey_load(ctx->ctx, pt, &ctx->pks[idx])) {
92105
return 0;
93106
}
94-
secp256k1_musig_coefficient(sc, ctx->ell, &pt->x);
107+
if (secp256k1_fe_is_zero(&ctx->second_pk_x)
108+
&& secp256k1_memcmp_var(&ctx->pks[0], &ctx->pks[idx], sizeof(ctx->pks[0])) != 0) {
109+
ctx->second_pk_x = pt->x;
110+
}
111+
secp256k1_musig_coefficient_internal(sc, ctx->ell, &pt->x, &ctx->second_pk_x);
95112
return 1;
96113
}
97114

@@ -120,6 +137,9 @@ int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scrat
120137

121138
ecmult_data.ctx = ctx;
122139
ecmult_data.pks = pubkeys;
140+
/* No point on the curve has an X coordinate equal to 0 */
141+
secp256k1_fe_set_int(&ecmult_data.second_pk_x, 0);
142+
123143
if (!secp256k1_musig_compute_ell(ctx, ecmult_data.ell, pubkeys, n_pubkeys)) {
124144
return 0;
125145
}
@@ -136,6 +156,7 @@ int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scrat
136156
memcpy(pre_session->pk_hash, ecmult_data.ell, 32);
137157
pre_session->pk_parity = pk_parity;
138158
pre_session->is_tweaked = 0;
159+
secp256k1_fe_get_b32(pre_session->second_pk, &ecmult_data.second_pk_x);
139160
}
140161
return 1;
141162
}
@@ -218,7 +239,7 @@ int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_s
218239
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &secret);
219240
secp256k1_ge_set_gej(&p, &pj);
220241
secp256k1_fe_normalize_var(&p.x);
221-
secp256k1_musig_coefficient(&mu, session->pre_session.pk_hash, &p.x);
242+
secp256k1_musig_coefficient(&mu, &session->pre_session, &p.x);
222243
/* Compute the signer's public key point and determine if the secret is
223244
* negated before signing. That happens if if the signer's pubkey has an odd
224245
* Y coordinate XOR the MuSig-combined pubkey has an odd Y coordinate XOR
@@ -612,7 +633,7 @@ int secp256k1_musig_partial_sig_verify(const secp256k1_context* ctx, const secp2
612633
/* Multiplying the messagehash by the musig coefficient is equivalent
613634
* to multiplying the signer's public key by the coefficient, except
614635
* much easier to do. */
615-
secp256k1_musig_coefficient(&mu, session->pre_session.pk_hash, &pkp.x);
636+
secp256k1_musig_coefficient(&mu, &session->pre_session, &pkp.x);
616637
secp256k1_scalar_mul(&e, &e, &mu);
617638

618639
if (!secp256k1_xonly_pubkey_load(ctx, &rp, &signer->nonce)) {

src/modules/musig/tests_impl.h

Lines changed: 96 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,56 +1019,114 @@ void musig_sort_test(void) {
10191019
}
10201020
}
10211021

1022-
void musig_test_vectors(void) {
1022+
void musig_test_vectors_helper(unsigned char pk_ser[][32], int n_pks, const unsigned char *combined_pk_expected, int sort, int has_second_pk, int second_pk_idx) {
1023+
secp256k1_xonly_pubkey *pk = malloc(n_pks * sizeof(secp256k1_xonly_pubkey));
10231024
secp256k1_xonly_pubkey combined_pk;
10241025
unsigned char combined_pk_ser[32];
1025-
secp256k1_xonly_pubkey pk[2];
1026-
const unsigned char pk_ser1[32] = {
1027-
0xF9, 0x30, 0x8A, 0x01, 0x92, 0x58, 0xC3, 0x10,
1028-
0x49, 0x34, 0x4F, 0x85, 0xF8, 0x9D, 0x52, 0x29,
1029-
0xB5, 0x31, 0xC8, 0x45, 0x83, 0x6F, 0x99, 0xB0,
1030-
0x86, 0x01, 0xF1, 0x13, 0xBC, 0xE0, 0x36, 0xF9
1031-
};
1032-
const unsigned char pk_ser2[32] = {
1033-
0xDF, 0xF1, 0xD7, 0x7F, 0x2A, 0x67, 0x1C, 0x5F,
1034-
0x36, 0x18, 0x37, 0x26, 0xDB, 0x23, 0x41, 0xBE,
1035-
0x58, 0xFE, 0xAE, 0x1D, 0xA2, 0xDE, 0xCE, 0xD8,
1036-
0x43, 0x24, 0x0F, 0x7B, 0x50, 0x2B, 0xA6, 0x59
1037-
};
1038-
const unsigned char combined_pk_expected[32] = {
1039-
0x4B, 0xFC, 0x12, 0x07, 0x07, 0x7D, 0x48, 0xEC,
1040-
0x99, 0x98, 0xD4, 0xD4, 0xFA, 0x62, 0xD9, 0x9A,
1041-
0x2F, 0x59, 0x1A, 0x4A, 0xC6, 0x19, 0xEC, 0xFD,
1042-
0xA6, 0x82, 0x5D, 0xCC, 0xDF, 0xA0, 0x79, 0xF9,
1043-
};
1044-
const unsigned char combined_pk_expected2[32] = {
1045-
0x08, 0xD9, 0xB8, 0x2A, 0x26, 0x7B, 0x8B, 0x8D,
1046-
0x85, 0xC6, 0x18, 0xAF, 0x56, 0xB2, 0xFB, 0x9A,
1047-
0x68, 0x7D, 0x1A, 0xC6, 0xA9, 0x22, 0xF4, 0x87,
1048-
0xC2, 0xD8, 0x48, 0x94, 0x5C, 0xC0, 0x19, 0xD0,
1049-
};
1050-
1051-
CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk[0], pk_ser1));
1052-
CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk[1], pk_ser2));
1053-
CHECK(secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk, NULL, pk, 2) == 1);
1026+
secp256k1_musig_pre_session pre_session;
1027+
secp256k1_fe second_pk_x;
1028+
int i;
1029+
1030+
for (i = 0; i < n_pks; i++) {
1031+
CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk[i], pk_ser[i]));
1032+
}
1033+
1034+
if (sort) {
1035+
CHECK(secp256k1_musig_sort_pubkeys(ctx, pk, n_pks));
1036+
}
1037+
CHECK(secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk, &pre_session, pk, n_pks) == 1);
1038+
CHECK(secp256k1_fe_set_b32(&second_pk_x, pre_session.second_pk));
1039+
CHECK(secp256k1_fe_is_zero(&second_pk_x) == !has_second_pk);
1040+
if (!secp256k1_fe_is_zero(&second_pk_x)) {
1041+
CHECK(memcmp(&pk_ser[second_pk_idx], &pre_session.second_pk, sizeof(pk_ser[second_pk_idx])) == 0);
1042+
}
10541043
CHECK(secp256k1_xonly_pubkey_serialize(ctx, combined_pk_ser, &combined_pk));
10551044
/* TODO: remove */
1056-
/* int i, j; */
1045+
/* int k, l; */
10571046
/* printf("const unsigned char combined_pk_expected[32] = {\n"); */
1058-
/* for (i = 0; i < 4; i++) { */
1047+
/* for (k = 0; k < 4; k++) { */
10591048
/* printf(" "); */
1060-
/* for (j = 0; j < 8; j++) { */
1061-
/* printf("0x%02X, ", combined_pk_ser[i*8+j]); */
1049+
/* for (l = 0; l < 8; l++) { */
1050+
/* printf("0x%02X, ", combined_pk_ser[k*8+l]); */
10621051
/* } */
10631052
/* printf("\n"); */
10641053
/* } */
10651054
/* printf("};\n"); */
10661055
CHECK(memcmp(combined_pk_ser, combined_pk_expected, sizeof(combined_pk_ser)) == 0);
1056+
free(pk);
1057+
}
10671058

1068-
CHECK(secp256k1_musig_sort_pubkeys(ctx, pk, 2));
1069-
CHECK(secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk, NULL, pk, 2) == 1);
1070-
CHECK(secp256k1_xonly_pubkey_serialize(ctx, combined_pk_ser, &combined_pk));
1071-
CHECK(memcmp(combined_pk_ser, combined_pk_expected2, sizeof(combined_pk_ser)) == 0);
1059+
void musig_test_vectors(void) {
1060+
unsigned char pk_ser[2][32] = {{
1061+
0xF9, 0x30, 0x8A, 0x01, 0x92, 0x58, 0xC3, 0x10,
1062+
0x49, 0x34, 0x4F, 0x85, 0xF8, 0x9D, 0x52, 0x29,
1063+
0xB5, 0x31, 0xC8, 0x45, 0x83, 0x6F, 0x99, 0xB0,
1064+
0x86, 0x01, 0xF1, 0x13, 0xBC, 0xE0, 0x36, 0xF9
1065+
}, {
1066+
0xDF, 0xF1, 0xD7, 0x7F, 0x2A, 0x67, 0x1C, 0x5F,
1067+
0x36, 0x18, 0x37, 0x26, 0xDB, 0x23, 0x41, 0xBE,
1068+
0x58, 0xFE, 0xAE, 0x1D, 0xA2, 0xDE, 0xCE, 0xD8,
1069+
0x43, 0x24, 0x0F, 0x7B, 0x50, 0x2B, 0xA6, 0x59
1070+
}};
1071+
int has_second_pk;
1072+
int sort;
1073+
int second_pk_idx;
1074+
{
1075+
const unsigned char combined_pk_expected[32] = {
1076+
0xF3, 0xEC, 0x05, 0x08, 0x4D, 0xC9, 0x1F, 0xB1,
1077+
0x21, 0x5E, 0xA7, 0x99, 0x86, 0xC7, 0x0A, 0x7E,
1078+
0x15, 0x37, 0x2E, 0xD4, 0x75, 0x5F, 0x95, 0xB4,
1079+
0xA6, 0x4F, 0x29, 0x1B, 0x0A, 0xD3, 0x35, 0x40
1080+
};
1081+
sort = 0;
1082+
has_second_pk = 1;
1083+
second_pk_idx = 1;
1084+
musig_test_vectors_helper(pk_ser, 2, combined_pk_expected, sort, has_second_pk, second_pk_idx);
1085+
}
1086+
{
1087+
const unsigned char combined_pk_expected[32] = {
1088+
0x63, 0x4D, 0x37, 0xCA, 0xCE, 0x85, 0xC6, 0x1D,
1089+
0x3E, 0x51, 0x92, 0x3B, 0x92, 0x44, 0xB1, 0xA2,
1090+
0x6A, 0x05, 0xB1, 0x52, 0x2E, 0xC8, 0x99, 0x21,
1091+
0x87, 0xA6, 0x56, 0xA3, 0x32, 0xA4, 0xC1, 0xA6,
1092+
};
1093+
sort = 1;
1094+
has_second_pk = 1;
1095+
second_pk_idx = 0;
1096+
musig_test_vectors_helper(pk_ser, 2, combined_pk_expected, sort, has_second_pk, second_pk_idx);
1097+
}
1098+
{
1099+
unsigned char pk_ser_tmp[3][32];
1100+
int i;
1101+
1102+
for (i = 0; i < 3; i++) {
1103+
memcpy(pk_ser_tmp[i], pk_ser[0], sizeof(pk_ser_tmp[i]));
1104+
}
1105+
{
1106+
const unsigned char combined_pk_expected[32] = {
1107+
0xA0, 0xFD, 0x5D, 0x2F, 0xCC, 0x4F, 0x90, 0xDF,
1108+
0x42, 0xD4, 0x26, 0x38, 0x31, 0x73, 0x0B, 0x21,
1109+
0xC4, 0xAB, 0x0E, 0xFA, 0xD2, 0x09, 0x10, 0xD0,
1110+
0x07, 0xED, 0xCB, 0x69, 0x1D, 0xD5, 0xD1, 0x82,
1111+
};
1112+
sort = 0;
1113+
has_second_pk = 0;
1114+
musig_test_vectors_helper(pk_ser_tmp, 3, combined_pk_expected, sort, has_second_pk, second_pk_idx);
1115+
}
1116+
{
1117+
const unsigned char combined_pk_expected[32] = {
1118+
0x74, 0x21, 0xD4, 0xBA, 0xCC, 0x4B, 0x10, 0x12,
1119+
0x5C, 0x77, 0x48, 0xB9, 0x47, 0xC8, 0xCB, 0x14,
1120+
0xF0, 0xFB, 0x14, 0xDA, 0xBE, 0x56, 0x8F, 0xDE,
1121+
0x6E, 0xB7, 0xAC, 0x55, 0x35, 0xCD, 0x84, 0x51,
1122+
};
1123+
sort = 0;
1124+
has_second_pk = 1;
1125+
second_pk_idx = 2;
1126+
memcpy(pk_ser_tmp[2], pk_ser[1], sizeof(pk_ser_tmp[2]));
1127+
musig_test_vectors_helper(pk_ser_tmp, 3, combined_pk_expected, sort, has_second_pk, second_pk_idx);
1128+
}
1129+
}
10721130
}
10731131

10741132
void run_musig_tests(void) {

0 commit comments

Comments
 (0)