diff --git a/configure.ac b/configure.ac index 849200af0..c518fd718 100644 --- a/configure.ac +++ b/configure.ac @@ -492,6 +492,7 @@ fi if test x"$enable_module_musig" = x"yes"; then AC_DEFINE(ENABLE_MODULE_MUSIG, 1, [Define this symbol to enable the MuSig module]) + enable_module_schnorrsig=yes fi if test x"$enable_module_recovery" = x"yes"; then @@ -513,7 +514,8 @@ fi if test x"$enable_module_surjectionproof" = x"yes"; then AC_DEFINE(ENABLE_MODULE_SURJECTIONPROOF, 1, [Define this symbol to enable the surjection proof module]) fi - +# Test if extrakeys is set _after_ the MuSig module to allow the MuSig +# module to set enable_module_schnorrsig=yes if test x"$enable_module_schnorrsig" = x"yes"; then AC_DEFINE(ENABLE_MODULE_SCHNORRSIG, 1, [Define this symbol to enable the schnorrsig module]) enable_module_extrakeys=yes @@ -663,6 +665,7 @@ echo " module ecdh = $enable_module_ecdh" echo " module recovery = $enable_module_recovery" echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" +echo " module musig = $enable_module_musig" echo " module ecdsa-s2c = $enable_module_ecdsa_s2c" echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor" echo diff --git a/include/secp256k1_extrakeys.h b/include/secp256k1_extrakeys.h index 0a37fb6b9..4a3ca0b1a 100644 --- a/include/secp256k1_extrakeys.h +++ b/include/secp256k1_extrakeys.h @@ -166,6 +166,20 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add_ const unsigned char *tweak32 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); +/** Sorts xonly public keys according to secp256k1_xonly_pubkey_cmp + * + * Returns: 0 if the arguments are invalid. 1 otherwise. + * + * Args: ctx: pointer to a context object + * In: pubkeys: array of pointers to pubkeys to sort + * n_pubkeys: number of elements in the pubkeys array + */ +SECP256K1_API int secp256k1_xonly_sort( + const secp256k1_context* ctx, + const secp256k1_xonly_pubkey **pubkeys, + size_t n_pubkeys +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + /** Compute the keypair for a secret key. * * Returns: 1: secret was valid, keypair is ready to use diff --git a/include/secp256k1_musig.h b/include/secp256k1_musig.h index 46fe6c285..b85eacbb7 100644 --- a/include/secp256k1_musig.h +++ b/include/secp256k1_musig.h @@ -25,6 +25,8 @@ extern "C" { * magic: Set during initialization in `pubkey_combine` to allow * detecting an uninitialized object. * pk_hash: The 32-byte hash of the original public keys + * second_pk: Serialized x-coordinate of the second public key in the list. + * Filled with zeros if there is none. * pk_parity: Whether the MuSig-aggregated point was negated when * converting it to the combined xonly pubkey. * is_tweaked: Whether the combined pubkey was tweaked @@ -35,6 +37,7 @@ extern "C" { typedef struct { uint64_t magic; unsigned char pk_hash[32]; + unsigned char second_pk[32]; int pk_parity; int is_tweaked; unsigned char tweak[32]; @@ -94,7 +97,7 @@ typedef struct { * The workflow for this structure is as follows: * * 1. This structure is initialized with `musig_session_init` or - * `musig_session_init_verifier`, which set the `index` field, and zero out + * `musig_session_init_verifier`, which initializes * all other fields. The public session is initialized with the signers' * nonce_commitments. * @@ -111,14 +114,12 @@ typedef struct { * * Fields: * present: indicates whether the signer's nonce is set - * index: index of the signer in the MuSig key aggregation * nonce: public nonce, must be a valid curvepoint if the signer is `present` * nonce_commitment: commitment to the nonce, or all-bits zero if a commitment * has not yet been set */ typedef struct { int present; - uint32_t index; secp256k1_xonly_pubkey nonce; unsigned char nonce_commitment[32]; } secp256k1_musig_session_signer_data; @@ -137,8 +138,14 @@ typedef struct { } secp256k1_musig_partial_signature; /** Computes a combined public key and the hash of the given public keys. + * * Different orders of `pubkeys` result in different `combined_pk`s. * + * The pubkeys can be sorted before combining with `secp256k1_xonly_sort` which + * ensures the same resulting `combined_pk` for the same multiset of pubkeys. + * This is useful to do before pubkey_combine, such that the order of pubkeys + * does not affect the combined public key. + * * Returns: 1 if the public keys were successfully combined, 0 otherwise * Args: ctx: pointer to a context object initialized for verification * (cannot be NULL) @@ -147,9 +154,9 @@ typedef struct { * Out: combined_pk: the MuSig-combined xonly public key (cannot be NULL) * pre_session: if non-NULL, pointer to a musig_pre_session struct to be used in * `musig_session_init` or `musig_pubkey_tweak_add`. - * In: pubkeys: input array of public keys to combine. The order is important; - * a different order will result in a different combined public - * key (cannot be NULL) + * In: pubkeys: input array of pointers to public keys to combine. The order + * is important; a different order will result in a different + * combined public key (cannot be NULL) * n_pubkeys: length of pubkeys array. Must be greater than 0. */ SECP256K1_API int secp256k1_musig_pubkey_combine( @@ -157,7 +164,7 @@ SECP256K1_API int secp256k1_musig_pubkey_combine( secp256k1_scratch_space *scratch, secp256k1_xonly_pubkey *combined_pk, secp256k1_musig_pre_session *pre_session, - const secp256k1_xonly_pubkey *pubkeys, + const secp256k1_xonly_pubkey * const* pubkeys, size_t n_pubkeys ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); @@ -221,8 +228,6 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_pubkey_tweak_add( * `musig_pubkey_tweak_add` (cannot be NULL). * n_signers: length of signers array. Number of signers participating in * the MuSig. Must be greater than 0 and at most 2^32 - 1. - * my_index: index of this signer in the signers array. Must be less - * than `n_signers`. * seckey: the signer's 32-byte secret key (cannot be NULL) */ SECP256K1_API int secp256k1_musig_session_init( @@ -235,7 +240,6 @@ SECP256K1_API int secp256k1_musig_session_init( const secp256k1_xonly_pubkey *combined_pk, const secp256k1_musig_pre_session *pre_session, size_t n_signers, - size_t my_index, const unsigned char *seckey ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(11); diff --git a/src/modules/extrakeys/Makefile.am.include b/src/modules/extrakeys/Makefile.am.include index 0d901ec1f..fe496fd22 100644 --- a/src/modules/extrakeys/Makefile.am.include +++ b/src/modules/extrakeys/Makefile.am.include @@ -2,3 +2,5 @@ include_HEADERS += include/secp256k1_extrakeys.h noinst_HEADERS += src/modules/extrakeys/tests_impl.h noinst_HEADERS += src/modules/extrakeys/tests_exhaustive_impl.h noinst_HEADERS += src/modules/extrakeys/main_impl.h +noinst_HEADERS += src/modules/extrakeys/hsort.h +noinst_HEADERS += src/modules/extrakeys/hsort_impl.h diff --git a/src/modules/extrakeys/hsort.h b/src/modules/extrakeys/hsort.h new file mode 100644 index 000000000..0f57227c1 --- /dev/null +++ b/src/modules/extrakeys/hsort.h @@ -0,0 +1,22 @@ +/*********************************************************************** + * Copyright (c) 2021 Russell O'Connor, Jonas Nick * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_HSORT_H_ +#define SECP256K1_HSORT_H_ + +#include +#include + +/* In-place, iterative heapsort with an interface matching glibc's qsort_r. This + * is preferred over standard library implementations because they generally + * make no guarantee about being fast for malicious inputs. + * + * See the qsort_r manpage for a description of the interface. + */ +static void secp256k1_hsort(void *ptr, size_t count, size_t size, + int (*cmp)(const void *, const void *, void *), + void *cmp_data); +#endif diff --git a/src/modules/extrakeys/hsort_impl.h b/src/modules/extrakeys/hsort_impl.h new file mode 100644 index 000000000..fcc4b10be --- /dev/null +++ b/src/modules/extrakeys/hsort_impl.h @@ -0,0 +1,116 @@ +/*********************************************************************** + * Copyright (c) 2021 Russell O'Connor, Jonas Nick * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_HSORT_IMPL_H_ +#define SECP256K1_HSORT_IMPL_H_ + +#include "hsort.h" + +/* An array is a heap when, for all non-zero indexes i, the element at index i + * compares as less than or equal to the element at index parent(i) = (i-1)/2. + */ + +static SECP256K1_INLINE size_t child1(size_t i) { + VERIFY_CHECK(i <= (SIZE_MAX - 1)/2); + return 2*i + 1; +} + +static SECP256K1_INLINE size_t child2(size_t i) { + VERIFY_CHECK(i <= SIZE_MAX/2 - 1); + return child1(i)+1; +} + +static SECP256K1_INLINE void swap64(unsigned char *a, size_t i, size_t j, size_t stride) { + unsigned char tmp[64]; + VERIFY_CHECK(stride <= 64); + memcpy(tmp, a + i*stride, stride); + memmove(a + i*stride, a + j*stride, stride); + memcpy(a + j*stride, tmp, stride); +} + +static SECP256K1_INLINE void swap(unsigned char *a, size_t i, size_t j, size_t stride) { + while (64 < stride) { + swap64(a + (stride - 64), i, j, 64); + stride -= 64; + } + swap64(a, i, j, stride); +} + +static SECP256K1_INLINE void heap_down(unsigned char *a, size_t i, size_t heap_size, size_t stride, + int (*cmp)(const void *, const void *, void *), void *cmp_data) { + while (i < heap_size/2) { + VERIFY_CHECK(i <= SIZE_MAX/2 - 1); + /* Proof: + * i < heap_size/2 + * i + 1 <= heap_size/2 + * 2*i + 2 <= heap_size <= SIZE_MAX + * 2*i <= SIZE_MAX - 2 + */ + + VERIFY_CHECK(child1(i) < heap_size); + /* Proof: + * i < heap_size/2 + * i + 1 <= heap_size/2 + * 2*i + 2 <= heap_size + * 2*i + 1 < heap_size + * child1(i) < heap_size + */ + + /* Let [x] be notation for the contents at a[x*stride]. + * + * If [child1(i)] > [i] and [child2(i)] > [i], + * swap [i] with the larger child to ensure the new parent is larger + * than both children. When [child1(i)] == [child2(i)], swap [i] with + * [child2(i)]. + * Else if [child1(i)] > [i], swap [i] with [child1(i)]. + * Else if [child2(i)] > [i], swap [i] with [child2(i)]. + */ + if (child2(i) < heap_size + && 0 <= cmp(a + child2(i)*stride, a + child1(i)*stride, cmp_data)) { + if (0 < cmp(a + child2(i)*stride, a + i*stride, cmp_data)) { + swap(a, i, child2(i), stride); + i = child2(i); + } else { + /* At this point we have [child2(i)] >= [child1(i)] and we have + * [child2(i)] <= [i], and thus [child1(i)] <= [i] which means + * that the next comparison can be skipped. */ + return; + } + } else if (0 < cmp(a + child1(i)*stride, a + i*stride, cmp_data)) { + swap(a, i, child1(i), stride); + i = child1(i); + } else { + return; + } + } + /* heap_size/2 <= i + * heap_size/2 < i + 1 + * heap_size < 2*i + 2 + * heap_size <= 2*i + 1 + * heap_size <= child1(i) + * Thus child1(i) and child2(i) are now out of bounds and we are at a leaf. + */ +} + +/* In-place heap sort. */ +static void secp256k1_hsort(void *ptr, size_t count, size_t size, + int (*cmp)(const void *, const void *, void *), + void *cmp_data ) { + size_t i; + + for(i = count/2; 0 < i; --i) { + heap_down(ptr, i-1, count, size, cmp, cmp_data); + } + for(i = count; 1 < i; --i) { + /* Extract the largest value from the heap */ + swap(ptr, 0, i-1, size); + + /* Repair the heap condition */ + heap_down(ptr, 0, i-1, size, cmp, cmp_data); + } +} + +#endif diff --git a/src/modules/extrakeys/main_impl.h b/src/modules/extrakeys/main_impl.h index 8607bbede..beefecfde 100644 --- a/src/modules/extrakeys/main_impl.h +++ b/src/modules/extrakeys/main_impl.h @@ -9,6 +9,7 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_extrakeys.h" +#include "hsort_impl.h" static SECP256K1_INLINE int secp256k1_xonly_pubkey_load(const secp256k1_context* ctx, secp256k1_ge *ge, const secp256k1_xonly_pubkey *pubkey) { return secp256k1_pubkey_load(ctx, ge, (const secp256k1_pubkey *) pubkey); @@ -154,6 +155,28 @@ int secp256k1_xonly_pubkey_tweak_add_check(const secp256k1_context* ctx, const u && secp256k1_fe_is_odd(&pk.y) == tweaked_pk_parity; } +/* This struct wraps a const context pointer to satisfy the secp256k1_hsort api + * which expects a non-const cmp_data pointer. */ +typedef struct { + const secp256k1_context *ctx; +} secp256k1_xonly_sort_cmp_data; + +static int secp256k1_xonly_sort_cmp(const void* pk1, const void* pk2, void *cmp_data) { + return secp256k1_xonly_pubkey_cmp(((secp256k1_xonly_sort_cmp_data*)cmp_data)->ctx, + *(secp256k1_xonly_pubkey **)pk1, + *(secp256k1_xonly_pubkey **)pk2); +} + +int secp256k1_xonly_sort(const secp256k1_context* ctx, const secp256k1_xonly_pubkey **pubkeys, size_t n_pubkeys) { + secp256k1_xonly_sort_cmp_data cmp_data; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubkeys != NULL); + + cmp_data.ctx = ctx; + secp256k1_hsort(pubkeys, n_pubkeys, sizeof(*pubkeys), secp256k1_xonly_sort_cmp, &cmp_data); + return 1; +} + static void secp256k1_keypair_save(secp256k1_keypair *keypair, const secp256k1_scalar *sk, secp256k1_ge *pk) { secp256k1_scalar_get_b32(&keypair->data[0], sk); secp256k1_pubkey_save((secp256k1_pubkey *)&keypair->data[32], pk); diff --git a/src/modules/extrakeys/tests_impl.h b/src/modules/extrakeys/tests_impl.h index 4a5952714..781e5f7fe 100644 --- a/src/modules/extrakeys/tests_impl.h +++ b/src/modules/extrakeys/tests_impl.h @@ -571,6 +571,152 @@ void test_keypair_add(void) { secp256k1_context_destroy(verify); } +static void test_hsort_is_sorted(int *ints, size_t n) { + size_t i; + for (i = 1; i < n; i++) { + CHECK(ints[i-1] <= ints[i]); + } +} + +static int test_hsort_cmp(const void *i1, const void *i2, void *counter) { + *(size_t*)counter += 1; + return *(int*)i1 - *(int*)i2; +} + +#define NUM 64 +void test_hsort(void) { + int ints[NUM] = { 0 }; + size_t counter = 0; + int i, j; + + secp256k1_hsort(ints, 0, sizeof(ints[0]), test_hsort_cmp, &counter); + CHECK(counter == 0); + secp256k1_hsort(ints, 1, sizeof(ints[0]), test_hsort_cmp, &counter); + CHECK(counter == 0); + secp256k1_hsort(ints, NUM, sizeof(ints[0]), test_hsort_cmp, &counter); + CHECK(counter > 0); + test_hsort_is_sorted(ints, NUM); + + /* Test hsort with length n array and random elements in + * [-interval/2, interval/2] */ + for (i = 0; i < count; i++) { + int n = secp256k1_testrand_int(NUM); + int interval = secp256k1_testrand_int(64); + for (j = 0; j < n; j++) { + ints[j] = secp256k1_testrand_int(interval) - interval/2; + } + secp256k1_hsort(ints, n, sizeof(ints[0]), test_hsort_cmp, &counter); + test_hsort_is_sorted(ints, n); + } +} +#undef NUM + +void test_xonly_sort_helper(secp256k1_xonly_pubkey *pk, size_t *pk_order, size_t n_pk) { + size_t i; + const secp256k1_xonly_pubkey *pk_test[5]; + + for (i = 0; i < n_pk; i++) { + pk_test[i] = &pk[pk_order[i]]; + } + secp256k1_xonly_sort(ctx, pk_test, n_pk); + for (i = 0; i < n_pk; i++) { + CHECK(secp256k1_memcmp_var(pk_test[i], &pk[i], sizeof(*pk_test[i])) == 0); + } +} + +void permute(size_t *arr, size_t n) { + size_t i; + for (i = n - 1; i >= 1; i--) { + size_t tmp, j; + j = secp256k1_testrand_int(i + 1); + tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } +} + +void rand_xonly_pk(secp256k1_xonly_pubkey *pk) { + unsigned char seckey[32]; + secp256k1_keypair keypair; + secp256k1_testrand256(seckey); + CHECK(secp256k1_keypair_create(ctx, &keypair, seckey) == 1); + CHECK(secp256k1_keypair_xonly_pub(ctx, pk, NULL, &keypair) == 1); +} + +void test_xonly_sort_api(void) { + int ecount = 0; + secp256k1_xonly_pubkey pks[2]; + const secp256k1_xonly_pubkey *pks_ptr[2]; + secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount); + + pks_ptr[0] = &pks[0]; + pks_ptr[1] = &pks[1]; + + rand_xonly_pk(&pks[0]); + rand_xonly_pk(&pks[1]); + + CHECK(secp256k1_xonly_sort(none, pks_ptr, 2) == 1); + CHECK(secp256k1_xonly_sort(none, NULL, 2) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_xonly_sort(none, pks_ptr, 0) == 1); + /* Test illegal public keys */ + memset(&pks[0], 0, sizeof(pks[0])); + CHECK(secp256k1_xonly_sort(none, pks_ptr, 2) == 1); + CHECK(ecount == 2); + memset(&pks[1], 0, sizeof(pks[1])); + CHECK(secp256k1_xonly_sort(none, pks_ptr, 2) == 1); + CHECK(ecount > 2); + + secp256k1_context_destroy(none); +} + +void test_xonly_sort(void) { + secp256k1_xonly_pubkey pk[5]; + unsigned char pk_ser[5][32]; + int i; + size_t pk_order[5] = { 0, 1, 2, 3, 4 }; + + for (i = 0; i < 5; i++) { + memset(pk_ser[i], 0, sizeof(pk_ser[i])); + } + pk_ser[0][0] = 5; + pk_ser[1][0] = 8; + pk_ser[2][0] = 0x0a; + pk_ser[3][0] = 0x0b; + pk_ser[4][0] = 0x0c; + for (i = 0; i < 5; i++) { + CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk[i], pk_ser[i])); + } + + permute(pk_order, 1); + test_xonly_sort_helper(pk, pk_order, 1); + permute(pk_order, 2); + test_xonly_sort_helper(pk, pk_order, 2); + permute(pk_order, 3); + test_xonly_sort_helper(pk, pk_order, 3); + for (i = 0; i < count; i++) { + permute(pk_order, 4); + test_xonly_sort_helper(pk, pk_order, 4); + } + for (i = 0; i < count; i++) { + permute(pk_order, 5); + test_xonly_sort_helper(pk, pk_order, 5); + } + /* Check that sorting also works for random pubkeys */ + for (i = 0; i < count; i++) { + int j; + const secp256k1_xonly_pubkey *pk_ptr[5]; + for (j = 0; j < 5; j++) { + rand_xonly_pk(&pk[j]); + pk_ptr[j] = &pk[j]; + } + secp256k1_xonly_sort(ctx, pk_ptr, 5); + for (j = 1; j < 5; j++) { + CHECK(secp256k1_xonly_sort_cmp(&pk_ptr[j - 1], &pk_ptr[j], ctx) <= 0); + } + } +} + void run_extrakeys_tests(void) { /* xonly key test cases */ test_xonly_pubkey(); @@ -582,6 +728,10 @@ void run_extrakeys_tests(void) { /* keypair tests */ test_keypair(); test_keypair_add(); + + test_hsort(); + test_xonly_sort_api(); + test_xonly_sort(); } #endif diff --git a/src/modules/musig/example.c b/src/modules/musig/example.c index fa3f58335..dffec8505 100644 --- a/src/modules/musig/example.c +++ b/src/modules/musig/example.c @@ -40,7 +40,7 @@ int create_keypair(const secp256k1_context* ctx, unsigned char *seckey, secp256k } /* Sign a message hash with the given key pairs and store the result in sig */ -int sign(const secp256k1_context* ctx, unsigned char seckeys[][32], const secp256k1_xonly_pubkey* pubkeys, const unsigned char* msg32, unsigned char *sig64) { +int sign(const secp256k1_context* ctx, unsigned char seckeys[][32], const secp256k1_xonly_pubkey** pubkeys, const unsigned char* msg32, unsigned char *sig64) { secp256k1_musig_session musig_session[N_SIGNERS]; unsigned char nonce_commitment[N_SIGNERS][32]; const unsigned char *nonce_commitment_ptr[N_SIGNERS]; @@ -72,7 +72,7 @@ int sign(const secp256k1_context* ctx, unsigned char seckeys[][32], const secp25 } fclose(frand); /* Initialize session */ - if (!secp256k1_musig_session_init(ctx, &musig_session[i], signer_data[i], nonce_commitment[i], session_id32, msg32, &combined_pk, &pre_session, N_SIGNERS, i, seckeys[i])) { + if (!secp256k1_musig_session_init(ctx, &musig_session[i], signer_data[i], nonce_commitment[i], session_id32, msg32, &combined_pk, &pre_session, N_SIGNERS, seckeys[i])) { return 0; } nonce_commitment_ptr[i] = &nonce_commitment[i][0]; @@ -117,7 +117,7 @@ int sign(const secp256k1_context* ctx, unsigned char seckeys[][32], const secp25 * fine to first verify the combined sig, and only verify the individual * sigs if it does not work. */ - if (!secp256k1_musig_partial_sig_verify(ctx, &musig_session[i], &signer_data[i][j], &partial_sig[j], &pubkeys[j])) { + if (!secp256k1_musig_partial_sig_verify(ctx, &musig_session[i], &signer_data[i][j], &partial_sig[j], pubkeys[j])) { return 0; } } @@ -130,6 +130,7 @@ int sign(const secp256k1_context* ctx, unsigned char seckeys[][32], const secp25 int i; unsigned char seckeys[N_SIGNERS][32]; secp256k1_xonly_pubkey pubkeys[N_SIGNERS]; + const secp256k1_xonly_pubkey *pubkeys_ptr[N_SIGNERS]; secp256k1_xonly_pubkey combined_pk; unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!"; unsigned char sig[64]; @@ -142,16 +143,17 @@ int sign(const secp256k1_context* ctx, unsigned char seckeys[][32], const secp25 printf("FAILED\n"); return 1; } + pubkeys_ptr[i] = &pubkeys[i]; } printf("ok\n"); printf("Combining public keys..."); - if (!secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk, NULL, pubkeys, N_SIGNERS)) { + if (!secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk, NULL, pubkeys_ptr, N_SIGNERS)) { printf("FAILED\n"); return 1; } printf("ok\n"); printf("Signing message........."); - if (!sign(ctx, seckeys, pubkeys, msg, sig)) { + if (!sign(ctx, seckeys, pubkeys_ptr, msg, sig)) { printf("FAILED\n"); return 1; } diff --git a/src/modules/musig/main_impl.h b/src/modules/musig/main_impl.h index d397eab31..6556b0604 100644 --- a/src/modules/musig/main_impl.h +++ b/src/modules/musig/main_impl.h @@ -13,14 +13,14 @@ #include "hash.h" /* Computes ell = SHA256(pk[0], ..., pk[np-1]) */ -static int secp256k1_musig_compute_ell(const secp256k1_context *ctx, unsigned char *ell, const secp256k1_xonly_pubkey *pk, size_t np) { +static int secp256k1_musig_compute_ell(const secp256k1_context *ctx, unsigned char *ell, const secp256k1_xonly_pubkey * const* pk, size_t np) { secp256k1_sha256 sha; size_t i; secp256k1_sha256_initialize(&sha); for (i = 0; i < np; i++) { unsigned char ser[32]; - if (!secp256k1_xonly_pubkey_serialize(ctx, ser, &pk[i])) { + if (!secp256k1_xonly_pubkey_serialize(ctx, ser, pk[i])) { return 0; } secp256k1_sha256_write(&sha, ser, 32); @@ -30,79 +30,82 @@ static int secp256k1_musig_compute_ell(const secp256k1_context *ctx, unsigned ch } /* Initializes SHA256 with fixed midstate. This midstate was computed by applying - * SHA256 to SHA256("MuSig coefficient")||SHA256("MuSig coefficient"). */ + * SHA256 to SHA256("KeyAgg coefficient")||SHA256("KeyAgg coefficient"). */ static void secp256k1_musig_sha256_init_tagged(secp256k1_sha256 *sha) { secp256k1_sha256_initialize(sha); - sha->s[0] = 0x0fd0690cul; - sha->s[1] = 0xfefeae97ul; - sha->s[2] = 0x996eac7ful; - sha->s[3] = 0x5c30d864ul; - sha->s[4] = 0x8c4a0573ul; - sha->s[5] = 0xaca1a22ful; - sha->s[6] = 0x6f43b801ul; - sha->s[7] = 0x85ce27cdul; + sha->s[0] = 0x6ef02c5aul; + sha->s[1] = 0x06a480deul; + sha->s[2] = 0x1f298665ul; + sha->s[3] = 0x1d1134f2ul; + sha->s[4] = 0x56a0b063ul; + sha->s[5] = 0x52da4147ul; + sha->s[6] = 0xf280d9d4ul; + sha->s[7] = 0x4484be15ul; sha->bytes = 64; } -/* Compute r = SHA256(ell, idx). The four bytes of idx are serialized least significant byte first. */ -static void secp256k1_musig_coefficient(secp256k1_scalar *r, const unsigned char *ell, uint32_t idx) { +/* Compute KeyAgg coefficient which is constant 1 for the second pubkey and + * SHA256(ell, x) otherwise. second_pk_x can be NULL in case there is no + * second_pk. Assumes both field elements x and second_pk_x are normalized. */ +static void secp256k1_musig_keyaggcoef_internal(secp256k1_scalar *r, const unsigned char *ell, secp256k1_fe *x, const secp256k1_fe *second_pk_x) { secp256k1_sha256 sha; unsigned char buf[32]; - size_t i; - secp256k1_musig_sha256_init_tagged(&sha); - secp256k1_sha256_write(&sha, ell, 32); - /* We're hashing the index of the signer instead of its public key as specified - * in the MuSig paper. This reduces the total amount of data that needs to be - * hashed. - * Additionally, it prevents creating identical musig_coefficients for identical - * public keys. A participant Bob could choose his public key to be the same as - * Alice's, then replay Alice's messages (nonce and partial signature) to create - * a valid partial signature. This is not a problem for MuSig per se, but could - * result in subtle issues with protocols building on threshold signatures. - * With the assumption that public keys are unique, hashing the index is - * equivalent to hashing the public key. Because the public key can be - * identified by the index given the ordered list of public keys (included in - * ell), the index is just a different encoding of the public key.*/ - for (i = 0; i < sizeof(uint32_t); i++) { - unsigned char c = idx; - secp256k1_sha256_write(&sha, &c, 1); - idx >>= 8; - } - secp256k1_sha256_finalize(&sha, buf); - secp256k1_scalar_set_b32(r, buf, NULL); + if (secp256k1_fe_cmp_var(x, second_pk_x) == 0) { + secp256k1_scalar_set_int(r, 1); + } else { + secp256k1_musig_sha256_init_tagged(&sha); + secp256k1_sha256_write(&sha, ell, 32); + secp256k1_fe_get_b32(buf, x); + secp256k1_sha256_write(&sha, buf, 32); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(r, buf, NULL); + } +} + +/* Assumes both field elements x and second_pk_x are normalized. */ +static void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_musig_pre_session *pre_session, secp256k1_fe *x) { + secp256k1_fe second_pk_x; + secp256k1_fe_set_b32(&second_pk_x, pre_session->second_pk); + secp256k1_musig_keyaggcoef_internal(r, pre_session->pk_hash, x, &second_pk_x); } typedef struct { const secp256k1_context *ctx; unsigned char ell[32]; - const secp256k1_xonly_pubkey *pks; + const secp256k1_xonly_pubkey * const* pks; + secp256k1_fe second_pk_x; } secp256k1_musig_pubkey_combine_ecmult_data; /* Callback for batch EC multiplication to compute ell_0*P0 + ell_1*P1 + ... */ static int secp256k1_musig_pubkey_combine_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { secp256k1_musig_pubkey_combine_ecmult_data *ctx = (secp256k1_musig_pubkey_combine_ecmult_data *) data; - secp256k1_musig_coefficient(sc, ctx->ell, idx); - return secp256k1_xonly_pubkey_load(ctx->ctx, pt, &ctx->pks[idx]); + int ret; + ret = secp256k1_xonly_pubkey_load(ctx->ctx, pt, ctx->pks[idx]); + /* pubkey_load can't fail because the same pks have already been loaded (and + * we test this) */ + VERIFY_CHECK(ret); + secp256k1_musig_keyaggcoef_internal(sc, ctx->ell, &pt->x, &ctx->second_pk_x); + return 1; } static void secp256k1_musig_signers_init(secp256k1_musig_session_signer_data *signers, uint32_t n_signers) { uint32_t i; for (i = 0; i < n_signers; i++) { memset(&signers[i], 0, sizeof(signers[i])); - signers[i].index = i; signers[i].present = 0; } } static const uint64_t pre_session_magic = 0xf4adbbdf7c7dd304UL; -int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scratch_space *scratch, secp256k1_xonly_pubkey *combined_pk, secp256k1_musig_pre_session *pre_session, const secp256k1_xonly_pubkey *pubkeys, size_t n_pubkeys) { +int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scratch_space *scratch, secp256k1_xonly_pubkey *combined_pk, secp256k1_musig_pre_session *pre_session, const secp256k1_xonly_pubkey * const* pubkeys, size_t n_pubkeys) { secp256k1_musig_pubkey_combine_ecmult_data ecmult_data; secp256k1_gej pkj; secp256k1_ge pkp; int pk_parity; + size_t i; VERIFY_CHECK(ctx != NULL); ARG_CHECK(combined_pk != NULL); @@ -112,14 +115,28 @@ int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scrat ecmult_data.ctx = ctx; ecmult_data.pks = pubkeys; + /* No point on the curve has an X coordinate equal to 0 */ + secp256k1_fe_set_int(&ecmult_data.second_pk_x, 0); + for (i = 1; i < n_pubkeys; i++) { + secp256k1_ge pt; + if (!secp256k1_xonly_pubkey_load(ctx, &pt, pubkeys[i])) { + return 0; + } + if (secp256k1_memcmp_var(pubkeys[0], pubkeys[i], sizeof(*pubkeys[0])) != 0) { + ecmult_data.second_pk_x = pt.x; + break; + } + } + if (!secp256k1_musig_compute_ell(ctx, ecmult_data.ell, pubkeys, n_pubkeys)) { return 0; } if (!secp256k1_ecmult_multi_var(&ctx->error_callback, &ctx->ecmult_ctx, scratch, &pkj, NULL, secp256k1_musig_pubkey_combine_callback, (void *) &ecmult_data, n_pubkeys)) { + /* The current implementation of ecmult_multi_var makes this code unreachable with tests. */ return 0; } secp256k1_ge_set_gej(&pkp, &pkj); - secp256k1_fe_normalize(&pkp.y); + secp256k1_fe_normalize_var(&pkp.y); pk_parity = secp256k1_extrakeys_ge_even_y(&pkp); secp256k1_xonly_pubkey_save(combined_pk, &pkp); @@ -128,12 +145,14 @@ int secp256k1_musig_pubkey_combine(const secp256k1_context* ctx, secp256k1_scrat memcpy(pre_session->pk_hash, ecmult_data.ell, 32); pre_session->pk_parity = pk_parity; pre_session->is_tweaked = 0; + secp256k1_fe_get_b32(pre_session->second_pk, &ecmult_data.second_pk_x); } return 1; } int secp256k1_musig_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_musig_pre_session *pre_session, secp256k1_pubkey *output_pubkey, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) { secp256k1_ge pk; + int ret; VERIFY_CHECK(ctx != NULL); ARG_CHECK(pre_session != NULL); @@ -150,16 +169,17 @@ int secp256k1_musig_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_mus memcpy(pre_session->tweak, tweak32, 32); pre_session->is_tweaked = 1; - if (!secp256k1_pubkey_load(ctx, &pk, output_pubkey)) { - return 0; - } + ret = secp256k1_pubkey_load(ctx, &pk, output_pubkey); + /* Successful xonly_pubkey_tweak_add always returns valid output_pubkey */ + VERIFY_CHECK(ret); + pre_session->pk_parity = secp256k1_extrakeys_ge_even_y(&pk); return 1; } static const uint64_t session_magic = 0xd92e6fc1ee41b4cbUL; -int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_session *session, secp256k1_musig_session_signer_data *signers, unsigned char *nonce_commitment32, const unsigned char *session_id32, const unsigned char *msg32, const secp256k1_xonly_pubkey *combined_pk, const secp256k1_musig_pre_session *pre_session, size_t n_signers, size_t my_index, const unsigned char *seckey) { +int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_session *session, secp256k1_musig_session_signer_data *signers, unsigned char *nonce_commitment32, const unsigned char *session_id32, const unsigned char *msg32, const secp256k1_xonly_pubkey *combined_pk, const secp256k1_musig_pre_session *pre_session, size_t n_signers, const unsigned char *seckey) { unsigned char combined_ser[32]; int overflow; secp256k1_scalar secret; @@ -183,7 +203,6 @@ int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_s ARG_CHECK(n_signers > 0); ARG_CHECK(n_signers <= UINT32_MAX); - ARG_CHECK(my_index < n_signers); memset(session, 0, sizeof(*session)); @@ -206,7 +225,11 @@ int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_s secp256k1_scalar_clear(&secret); return 0; } - secp256k1_musig_coefficient(&mu, session->pre_session.pk_hash, (uint32_t) my_index); + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &secret); + secp256k1_ge_set_gej(&p, &pj); + secp256k1_fe_normalize_var(&p.x); + secp256k1_musig_keyaggcoef(&mu, &session->pre_session, &p.x); /* Compute the signer's public key point and determine if the secret is * negated before signing. That happens if if the signer's pubkey has an odd * Y coordinate XOR the MuSig-combined pubkey has an odd Y coordinate XOR @@ -215,7 +238,7 @@ int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_s * This can be seen by looking at the secret key belonging to `combined_pk`. * Let's define * P' := mu_0*|P_0| + ... + mu_n*|P_n| where P_i is the i-th public key - * point x_i*G, mu_i is the i-th musig coefficient and |.| is a function + * point x_i*G, mu_i is the i-th KeyAgg coefficient and |.| is a function * that normalizes a point to an even Y by negating if necessary similar to * secp256k1_extrakeys_ge_even_y. Then we have * P := |P'| + t*G where t is the tweak. @@ -226,9 +249,7 @@ int secp256k1_musig_session_init(const secp256k1_context* ctx, secp256k1_musig_s * b_i = -1 if (P_i != |P_i| XOR P' != |P'| XOR P != |P|) and 1 * otherwise. */ - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &secret); - secp256k1_ge_set_gej(&p, &pj); - secp256k1_fe_normalize(&p.y); + secp256k1_fe_normalize_var(&p.y); if((secp256k1_fe_is_odd(&p.y) + session->pre_session.pk_parity + (session->pre_session.is_tweaked @@ -596,10 +617,13 @@ int secp256k1_musig_partial_sig_verify(const secp256k1_context* ctx, const secp2 secp256k1_musig_compute_messagehash(ctx, msghash, session); secp256k1_scalar_set_b32(&e, msghash, NULL); - /* Multiplying the messagehash by the musig coefficient is equivalent + if (!secp256k1_xonly_pubkey_load(ctx, &pkp, pubkey)) { + return 0; + } + /* Multiplying the messagehash by the KeyAgg coefficient is equivalent * to multiplying the signer's public key by the coefficient, except * much easier to do. */ - secp256k1_musig_coefficient(&mu, session->pre_session.pk_hash, signer->index); + secp256k1_musig_keyaggcoef(&mu, &session->pre_session, &pkp.x); secp256k1_scalar_mul(&e, &e, &mu); if (!secp256k1_xonly_pubkey_load(ctx, &rp, &signer->nonce)) { @@ -619,9 +643,7 @@ int secp256k1_musig_partial_sig_verify(const secp256k1_context* ctx, const secp2 /* Compute rj = s*G + (-e)*pkj */ secp256k1_scalar_negate(&e, &e); - if (!secp256k1_xonly_pubkey_load(ctx, &pkp, pubkey)) { - return 0; - } + secp256k1_gej_set_ge(&pkj, &pkp); secp256k1_ecmult(&ctx->ecmult_ctx, &rj, &pkj, &e, &s); diff --git a/src/modules/musig/musig-spec.mediawiki b/src/modules/musig/musig-spec.mediawiki new file mode 100644 index 000000000..a408397a2 --- /dev/null +++ b/src/modules/musig/musig-spec.mediawiki @@ -0,0 +1,102 @@ +
+  Title: MuSig Key Aggregation
+  Author:
+  Status: Draft
+  License: BSD-2-Clause
+  Created: 2020-01-19
+
+ +== Introduction == + +=== Abstract === + +This document describes MuSig Key Aggregation in libsecp256k1-zkp. + +=== Copyright === + +This document is licensed under the 2-clause BSD license. + +=== Motivation === + +== Description == + +=== Design === + +* A function for sorting public keys allows to aggregate keys independent of the (initial) order. +* The KeyAgg coefficient is computed by hashing the key instead of key index. Otherwise, if the pubkey list gets sorted, the signer needs to translate between key indices pre- and post-sorting. +* The second unique key in the pubkey list gets the constant KeyAgg coefficient 1 which saves an exponentiation (see the MuSig2* appendix in the [https://eprint.iacr.org/2020/1261 MuSig2 paper]). + + +=== Specification === + +The following conventions are used, with constants as defined for [https://www.secg.org/sec2-v2.pdf secp256k1]. We note that adapting this specification to other elliptic curves is not straightforward and can result in an insecure schemeAmong other pitfalls, using the specification with a curve whose order is not close to the size of the range of the nonce derivation function is insecure.. +* Lowercase variables represent integers or byte arrays. +** The constant ''p'' refers to the field size, ''0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F''. +** The constant ''n'' refers to the curve order, ''0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141''. +* Uppercase variables refer to points on the curve with equation ''y2 = x3 + 7'' over the integers modulo ''p''. +** ''is_infinite(P)'' returns whether or not ''P'' is the point at infinity. +** ''x(P)'' and ''y(P)'' are integers in the range ''0..p-1'' and refer to the X and Y coordinates of a point ''P'' (assuming it is not infinity). +** The constant ''G'' refers to the base point, for which ''x(G) = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'' and ''y(G) = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8''. +** Addition of points refers to the usual [https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law elliptic curve group operation]. +** [https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication Multiplication (⋅) of an integer and a point] refers to the repeated application of the group operation. +* Functions and operations: +** ''||'' refers to byte array concatenation. +** The function ''x[i:j]'', where ''x'' is a byte array and ''i, j ≥ 0'', returns a ''(j - i)''-byte array with a copy of the ''i''-th byte (inclusive) to the ''j''-th byte (exclusive) of ''x''. +** The function ''bytes(x)'', where ''x'' is an integer, returns the 32-byte encoding of ''x'', most significant byte first. +** The function ''bytes(P)'', where ''P'' is a point, returns ''bytes(x(P))''. +** The function ''int(x)'', where ''x'' is a 32-byte array, returns the 256-bit unsigned integer whose most significant byte first encoding is ''x''. +** The function ''has_even_y(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns ''y(P) mod 2 = 0''. +** The function ''lift_x(x)'', where ''x'' is an integer in range ''0..p-1'', returns the point ''P'' for which ''x(P) = x'' + Given a candidate X coordinate ''x'' in the range ''0..p-1'', there exist either exactly two or exactly zero valid Y coordinates. If no valid Y coordinate exists, then ''x'' is not a valid X coordinate either, i.e., no point ''P'' exists for which ''x(P) = x''. The valid Y coordinates for a given candidate ''x'' are the square roots of ''c = x3 + 7 mod p'' and they can be computed as ''y = ±c(p+1)/4 mod p'' (see [https://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus Quadratic residue]) if they exist, which can be checked by squaring and comparing with ''c''. and ''has_even_y(P)'', or fails if no such point exists. The function ''lift_x(x)'' is equivalent to the following pseudocode: +*** Let ''c = x3 + 7 mod p''. +*** Let ''y = c(p+1)/4 mod p''. +*** Fail if ''c ≠ y2 mod p''. +*** Return the unique point ''P'' such that ''x(P) = x'' and ''y(P) = y'' if ''y mod 2 = 0'' or ''y(P) = p-y'' otherwise. +** The function ''hashtag(x)'' where ''tag'' is a UTF-8 encoded tag name and ''x'' is a byte array returns the 32-byte hash ''SHA256(SHA256(tag) || SHA256(tag) || x)''. + + +==== Key Sorting ==== + +Input: +* The number ''u'' of signatures with ''0 < u < 2^32'' +* The public keys ''pk1..u'': ''u'' 32-byte arrays + +The algorithm ''KeySort(pk1..u)'' is defined as: +* Return ''pk1..u'' sorted in lexicographical order. + +==== Key Aggregation ==== + +Input: +* The number ''u'' of signatures with ''0 < u < 2^32'' +* The public keys ''pk1..u'': ''u'' 32-byte arrays + +The algorithm ''KeyAgg(pk1..u)'' is defined as: +* For ''i = 1 .. u'': +** Let ''ai = KeyAggCoeff(pk1..u, i)''. +** Let ''Pi = lift_x(int(pki))''; fail if it fails. +* Let ''S = a1⋅P1 + a2⋅P1 + ... + au⋅Pu'' +* Fail if ''is_infinite(S)''. +* Return ''bytes(S)''. + +The algorithm ''HashKeys(pk1..u)'' is defined as: +* Return ''hash(pk1 || pk2 || ... || pku)'' + +The algorithm ''IsSecond(pk1..u, i)'' is defined as: +* For ''j = 1 .. u'': +** If ''pkj ≠ pk1'': +*** Return ''true'' if ''pkj = pki'', otherwise return ''false''. +* Return ''false'' + +The algorithm ''KeyAggCoeff(pk1..u, i)'' is defined as: +* Let ''L = HashKeys(pk1..u)''. +* Return 1 if ''IsSecond(pk1..u, i)'', otherwise return ''int(hashKeyAgg coefficient(L || pk) mod n''. + +== Applications == + +== Test Vectors and Reference Code == + +== Footnotes == + + + +== Acknowledgements == diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h index edd43eac2..eb7527fe9 100644 --- a/src/modules/musig/tests_impl.h +++ b/src/modules/musig/tests_impl.h @@ -30,6 +30,7 @@ void musig_simple_test(secp256k1_scratch_space *scratch) { secp256k1_musig_pre_session pre_session; unsigned char session_id[2][32]; secp256k1_xonly_pubkey pk[2]; + const secp256k1_xonly_pubkey *pk_ptr[2]; const unsigned char *ncs[2]; unsigned char public_nonce[3][32]; secp256k1_musig_partial_signature partial_sig[2]; @@ -41,12 +42,14 @@ void musig_simple_test(secp256k1_scratch_space *scratch) { secp256k1_testrand256(sk[1]); secp256k1_testrand256(msg); + pk_ptr[0] = &pk[0]; + pk_ptr[1] = &pk[1]; CHECK(secp256k1_xonly_pubkey_create(&pk[0], sk[0]) == 1); CHECK(secp256k1_xonly_pubkey_create(&pk[1], sk[1]) == 1); - CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk, &pre_session, pk, 2) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session[1], signer1, nonce_commitment[1], session_id[1], msg, &combined_pk, &pre_session, 2, 1, sk[1]) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 1); + CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session[1], signer1, nonce_commitment[1], session_id[1], msg, &combined_pk, &pre_session, 2, sk[1]) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 1); ncs[0] = nonce_commitment[0]; ncs[1] = nonce_commitment[1]; @@ -98,11 +101,16 @@ void musig_api_tests(secp256k1_scratch_space *scratch) { secp256k1_musig_pre_session pre_session; secp256k1_musig_pre_session pre_session_uninitialized; secp256k1_xonly_pubkey pk[2]; + const secp256k1_xonly_pubkey *pk_ptr[2]; + secp256k1_xonly_pubkey invalid_pk; + const secp256k1_xonly_pubkey *invalid_pk_ptr2[2]; + const secp256k1_xonly_pubkey *invalid_pk_ptr3[3]; unsigned char tweak[32]; unsigned char sec_adaptor[32]; unsigned char sec_adaptor1[32]; secp256k1_pubkey adaptor; + int i; /** setup **/ secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); @@ -123,6 +131,7 @@ void musig_api_tests(secp256k1_scratch_space *scratch) { * structs. */ memset(&pre_session_uninitialized, 0, sizeof(pre_session_uninitialized)); memset(&session_uninitialized, 0, sizeof(session_uninitialized)); + memset(&invalid_pk, 0, sizeof(invalid_pk)); secp256k1_testrand256(session_id[0]); secp256k1_testrand256(session_id[1]); @@ -132,43 +141,56 @@ void musig_api_tests(secp256k1_scratch_space *scratch) { secp256k1_testrand256(sec_adaptor); secp256k1_testrand256(tweak); + pk_ptr[0] = &pk[0]; + pk_ptr[1] = &pk[1]; CHECK(secp256k1_xonly_pubkey_create(&pk[0], sk[0]) == 1); CHECK(secp256k1_xonly_pubkey_create(&pk[1], sk[1]) == 1); CHECK(secp256k1_ec_pubkey_create(ctx, &adaptor, sec_adaptor) == 1); + for (i = 0; i < 2; i++) { + invalid_pk_ptr2[i] = &invalid_pk; + invalid_pk_ptr3[i] = &pk[i]; + } + /* invalid_pk_ptr3 has two valid, one invalid pk, which is important to test + * musig_pubkeys_combine */ + invalid_pk_ptr3[2] = &invalid_pk; /** main test body **/ /* Key combination */ ecount = 0; - CHECK(secp256k1_musig_pubkey_combine(none, scratch, &combined_pk, &pre_session, pk, 2) == 0); + CHECK(secp256k1_musig_pubkey_combine(none, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 0); CHECK(ecount == 1); - CHECK(secp256k1_musig_pubkey_combine(sign, scratch, &combined_pk, &pre_session, pk, 2) == 0); + CHECK(secp256k1_musig_pubkey_combine(sign, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 0); CHECK(ecount == 2); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 1); CHECK(ecount == 2); /* pubkey_combine does not require a scratch space */ - CHECK(secp256k1_musig_pubkey_combine(vrfy, NULL, &combined_pk, &pre_session, pk, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(vrfy, NULL, &combined_pk, &pre_session, pk_ptr, 2) == 1); CHECK(ecount == 2); /* A small scratch space works too, but will result in using an ineffecient algorithm */ scratch_small = secp256k1_scratch_space_create(ctx, 1); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch_small, &combined_pk, &pre_session, pk, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch_small, &combined_pk, &pre_session, pk_ptr, 2) == 1); secp256k1_scratch_space_destroy(ctx, scratch_small); CHECK(ecount == 2); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, NULL, &pre_session, pk, 2) == 0); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, NULL, &pre_session, pk_ptr, 2) == 0); CHECK(ecount == 3); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, NULL, pk, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, NULL, pk_ptr, 2) == 1); CHECK(ecount == 3); CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, NULL, 2) == 0); CHECK(ecount == 4); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk, 0) == 0); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, invalid_pk_ptr2, 2) == 0); CHECK(ecount == 5); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, NULL, 0) == 0); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, invalid_pk_ptr3, 3) == 0); CHECK(ecount == 6); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk_ptr, 0) == 0); + CHECK(ecount == 7); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, NULL, 0) == 0); + CHECK(ecount == 8); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk, 2) == 1); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk, 2) == 1); - CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(vrfy, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 1); /** Tweaking */ ecount = 0; @@ -208,47 +230,47 @@ void musig_api_tests(secp256k1_scratch_space *scratch) { /** Session creation **/ ecount = 0; - CHECK(secp256k1_musig_session_init(none, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(none, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 0); CHECK(ecount == 1); - CHECK(secp256k1_musig_session_init(vrfy, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(vrfy, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 0); CHECK(ecount == 2); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 1); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 1); CHECK(ecount == 2); - CHECK(secp256k1_musig_session_init(sign, NULL, signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, NULL, signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 0); CHECK(ecount == 3); - CHECK(secp256k1_musig_session_init(sign, &session[0], NULL, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], NULL, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 0); CHECK(ecount == 4); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, NULL, session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, NULL, session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 0); CHECK(ecount == 5); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], NULL, msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], NULL, msg, &combined_pk, &pre_session, 2, sk[0]) == 0); CHECK(ecount == 6); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], NULL, &combined_pk, &pre_session, 2, 0, sk[0]) == 1); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], NULL, &combined_pk, &pre_session, 2, sk[0]) == 1); CHECK(ecount == 6); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, NULL, &pre_session, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, NULL, &pre_session, 2, sk[0]) == 0); CHECK(ecount == 7); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, NULL, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, NULL, 2, sk[0]) == 0); CHECK(ecount == 8); /* Uninitialized pre_session */ - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session_uninitialized, 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session_uninitialized, 2, sk[0]) == 0); CHECK(ecount == 9); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 0, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 0, sk[0]) == 0); CHECK(ecount == 10); /* If more than UINT32_MAX fits in a size_t, test that session_init * rejects n_signers that high. */ if (SIZE_MAX > UINT32_MAX) { - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, ((size_t) UINT32_MAX) + 2, 0, sk[0]) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, ((size_t) UINT32_MAX) + 2, sk[0]) == 0); CHECK(ecount == 11); } else { ecount = 11; } - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, NULL) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, NULL) == 0); CHECK(ecount == 12); /* secret key overflows */ - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, ones) == 0); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, ones) == 0); CHECK(ecount == 12); - CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 1); - CHECK(secp256k1_musig_session_init(sign, &session[1], signer1, nonce_commitment[1], session_id[1], msg, &combined_pk, &pre_session, 2, 1, sk[1]) == 1); + CHECK(secp256k1_musig_session_init(sign, &session[0], signer0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 1); + CHECK(secp256k1_musig_session_init(sign, &session[1], signer1, nonce_commitment[1], session_id[1], msg, &combined_pk, &pre_session, 2, sk[1]) == 1); ncs[0] = nonce_commitment[0]; ncs[1] = nonce_commitment[1]; @@ -517,6 +539,7 @@ void musig_state_machine_diff_signer_msghash_test(unsigned char *msghash, secp25 secp256k1_musig_session_signer_data signers_tmp[2]; unsigned char sk_dummy[32]; secp256k1_xonly_pubkey pks_tmp[2]; + const secp256k1_xonly_pubkey *pks_tmp_ptr[2]; secp256k1_xonly_pubkey combined_pk_tmp; secp256k1_musig_pre_session pre_session_tmp; unsigned char nonce[32]; @@ -525,10 +548,12 @@ void musig_state_machine_diff_signer_msghash_test(unsigned char *msghash, secp25 secp256k1_testrand256(sk_dummy); pks_tmp[0] = pks[0]; CHECK(secp256k1_xonly_pubkey_create(&pks_tmp[1], sk_dummy) == 1); - CHECK(secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk_tmp, &pre_session_tmp, pks_tmp, 2) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session_tmp, signers_tmp, nonce_commitment, session_id, msg, &combined_pk_tmp, &pre_session_tmp, 2, 1, sk_dummy) == 1); + pks_tmp_ptr[0] = &pks_tmp[0]; + pks_tmp_ptr[1] = &pks_tmp[1]; + CHECK(secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk_tmp, &pre_session_tmp, pks_tmp_ptr, 2) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session_tmp, signers_tmp, nonce_commitment, session_id, msg, &combined_pk_tmp, &pre_session_tmp, 2, sk_dummy) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session, signers, nonce_commitment, session_id, msg, combined_pk, pre_session, 2, 0, sk) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session, signers, nonce_commitment, session_id, msg, combined_pk, pre_session, 2, sk) == 1); CHECK(memcmp(nonce_commitment, nonce_commitments[1], 32) == 0); /* Call get_public_nonce with different signers than the signers the session was * initialized with. */ @@ -557,7 +582,7 @@ int musig_state_machine_diff_signers_combine_nonce_test(secp256k1_xonly_pubkey * /* Initialize new signers */ secp256k1_testrand256(session_id); - CHECK(secp256k1_musig_session_init(ctx, &session, signers, nonce_commitment, session_id, msg, combined_pk, pre_session, 2, 1, sk) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session, signers, nonce_commitment, session_id, msg, combined_pk, pre_session, 2, sk) == 1); ncs[0] = nonce_commitment_other; ncs[1] = nonce_commitment; CHECK(secp256k1_musig_session_get_public_nonce(ctx, &session, signers, nonce, ncs, 2, NULL) == 1); @@ -589,7 +614,7 @@ void musig_state_machine_late_msg_test(secp256k1_xonly_pubkey *pks, secp256k1_xo secp256k1_musig_partial_signature partial_sig; secp256k1_context_set_illegal_callback(ctx_tmp, counting_illegal_callback_fn, &ecount); - CHECK(secp256k1_musig_session_init(ctx, &session, signers, nonce_commitment, session_id, NULL, combined_pk, pre_session, 2, 1, sk) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session, signers, nonce_commitment, session_id, NULL, combined_pk, pre_session, 2, sk) == 1); ncs[0] = nonce_commitment_other; ncs[1] = nonce_commitment; @@ -625,6 +650,7 @@ void musig_state_machine_tests(secp256k1_scratch_space *scratch) { unsigned char msg[32]; unsigned char sk[2][32]; secp256k1_xonly_pubkey pk[2]; + const secp256k1_xonly_pubkey *pk_ptr[2]; secp256k1_xonly_pubkey combined_pk; secp256k1_musig_pre_session pre_session; unsigned char nonce[2][32]; @@ -647,11 +673,13 @@ void musig_state_machine_tests(secp256k1_scratch_space *scratch) { secp256k1_testrand256(sk[0]); secp256k1_testrand256(sk[1]); secp256k1_testrand256(msg); + pk_ptr[0] = &pk[0]; + pk_ptr[1] = &pk[1]; CHECK(secp256k1_xonly_pubkey_create(&pk[0], sk[0]) == 1); CHECK(secp256k1_xonly_pubkey_create(&pk[1], sk[1]) == 1); - CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk, &pre_session, pk, 2) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session[0], signers0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, 0, sk[0]) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session[1], signers1, nonce_commitment[1], session_id[1], msg, &combined_pk, &pre_session, 2, 1, sk[1]) == 1); + CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk, &pre_session, pk_ptr, 2) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session[0], signers0, nonce_commitment[0], session_id[0], msg, &combined_pk, &pre_session, 2, sk[0]) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session[1], signers1, nonce_commitment[1], session_id[1], msg, &combined_pk, &pre_session, 2, sk[1]) == 1); /* Can't combine nonces unless we're through round 1 already */ ecount = 0; CHECK(secp256k1_musig_session_combine_nonces(ctx_tmp, &session[0], signers0, 2, NULL, NULL) == 0); @@ -736,7 +764,9 @@ void scriptless_atomic_swap(secp256k1_scratch_space *scratch) { unsigned char seckey_a[2][32]; unsigned char seckey_b[2][32]; secp256k1_xonly_pubkey pk_a[2]; + const secp256k1_xonly_pubkey *pk_a_ptr[2]; secp256k1_xonly_pubkey pk_b[2]; + const secp256k1_xonly_pubkey *pk_b_ptr[2]; secp256k1_musig_pre_session pre_session_a; secp256k1_musig_pre_session pre_session_b; secp256k1_xonly_pubkey combined_pk_a; @@ -765,22 +795,26 @@ void scriptless_atomic_swap(secp256k1_scratch_space *scratch) { secp256k1_testrand256(seckey_b[1]); secp256k1_testrand256(sec_adaptor); + pk_a_ptr[0] = &pk_a[0]; + pk_a_ptr[1] = &pk_a[1]; + pk_b_ptr[0] = &pk_b[0]; + pk_b_ptr[1] = &pk_b[1]; CHECK(secp256k1_xonly_pubkey_create(&pk_a[0], seckey_a[0])); CHECK(secp256k1_xonly_pubkey_create(&pk_a[1], seckey_a[1])); CHECK(secp256k1_xonly_pubkey_create(&pk_b[0], seckey_b[0])); CHECK(secp256k1_xonly_pubkey_create(&pk_b[1], seckey_b[1])); CHECK(secp256k1_ec_pubkey_create(ctx, &pub_adaptor, sec_adaptor)); - CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk_a, &pre_session_a, pk_a, 2)); - CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk_b, &pre_session_b, pk_b, 2)); + CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk_a, &pre_session_a, pk_a_ptr, 2)); + CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &combined_pk_b, &pre_session_b, pk_b_ptr, 2)); - CHECK(secp256k1_musig_session_init(ctx, &musig_session_a[0], data_a, noncommit_a[0], seed, msg32_a, &combined_pk_a, &pre_session_a, 2, 0, seckey_a[0])); - CHECK(secp256k1_musig_session_init(ctx, &musig_session_a[1], data_a, noncommit_a[1], seed, msg32_a, &combined_pk_a, &pre_session_a, 2, 1, seckey_a[1])); + CHECK(secp256k1_musig_session_init(ctx, &musig_session_a[0], data_a, noncommit_a[0], seed, msg32_a, &combined_pk_a, &pre_session_a, 2, seckey_a[0])); + CHECK(secp256k1_musig_session_init(ctx, &musig_session_a[1], data_a, noncommit_a[1], seed, msg32_a, &combined_pk_a, &pre_session_a, 2, seckey_a[1])); noncommit_a_ptr[0] = noncommit_a[0]; noncommit_a_ptr[1] = noncommit_a[1]; - CHECK(secp256k1_musig_session_init(ctx, &musig_session_b[0], data_b, noncommit_b[0], seed, msg32_b, &combined_pk_b, &pre_session_b, 2, 0, seckey_b[0])); - CHECK(secp256k1_musig_session_init(ctx, &musig_session_b[1], data_b, noncommit_b[1], seed, msg32_b, &combined_pk_b, &pre_session_b, 2, 1, seckey_b[1])); + CHECK(secp256k1_musig_session_init(ctx, &musig_session_b[0], data_b, noncommit_b[0], seed, msg32_b, &combined_pk_b, &pre_session_b, 2, seckey_b[0])); + CHECK(secp256k1_musig_session_init(ctx, &musig_session_b[1], data_b, noncommit_b[1], seed, msg32_b, &combined_pk_b, &pre_session_b, 2, seckey_b[1])); noncommit_b_ptr[0] = noncommit_b[0]; noncommit_b_ptr[1] = noncommit_b[1]; @@ -829,7 +863,7 @@ void scriptless_atomic_swap(secp256k1_scratch_space *scratch) { /* Checks that hash initialized by secp256k1_musig_sha256_init_tagged has the * expected state. */ void sha256_tag_test(void) { - char tag[17] = "MuSig coefficient"; + char tag[18] = "KeyAgg coefficient"; secp256k1_sha256 sha; secp256k1_sha256 sha_tagged; unsigned char buf[32]; @@ -837,9 +871,9 @@ void sha256_tag_test(void) { size_t i; secp256k1_sha256_initialize(&sha); - secp256k1_sha256_write(&sha, (unsigned char *) tag, 17); + secp256k1_sha256_write(&sha, (unsigned char *) tag, sizeof(tag)); secp256k1_sha256_finalize(&sha, buf); - /* buf = SHA256("MuSig coefficient") */ + /* buf = SHA256("KeyAgg coefficient") */ secp256k1_sha256_initialize(&sha); secp256k1_sha256_write(&sha, buf, 32); @@ -881,8 +915,8 @@ void musig_tweak_test_helper(const secp256k1_xonly_pubkey* combined_pubkey, cons CHECK(secp256k1_xonly_pubkey_create(&pk[0], sk0) == 1); CHECK(secp256k1_xonly_pubkey_create(&pk[1], sk1) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session[0], signers0, nonce_commitment[0], session_id[0], msg, combined_pubkey, pre_session, 2, 0, sk0) == 1); - CHECK(secp256k1_musig_session_init(ctx, &session[1], signers1, nonce_commitment[1], session_id[1], msg, combined_pubkey, pre_session, 2, 1, sk1) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session[0], signers0, nonce_commitment[0], session_id[0], msg, combined_pubkey, pre_session, 2, sk0) == 1); + CHECK(secp256k1_musig_session_init(ctx, &session[1], signers1, nonce_commitment[1], session_id[1], msg, combined_pubkey, pre_session, 2, sk1) == 1); /* Set nonce commitments */ ncs[0] = nonce_commitment[0]; ncs[1] = nonce_commitment[1]; @@ -909,6 +943,7 @@ void musig_tweak_test_helper(const secp256k1_xonly_pubkey* combined_pubkey, cons void musig_tweak_test(secp256k1_scratch_space *scratch) { unsigned char sk[2][32]; secp256k1_xonly_pubkey pk[2]; + const secp256k1_xonly_pubkey *pk_ptr[2]; secp256k1_musig_pre_session pre_session_P; secp256k1_musig_pre_session pre_session_Q; secp256k1_xonly_pubkey P; @@ -927,9 +962,11 @@ void musig_tweak_test(secp256k1_scratch_space *scratch) { secp256k1_testrand256(sk[1]); secp256k1_testrand256(contract); + pk_ptr[0] = &pk[0]; + pk_ptr[1] = &pk[1]; CHECK(secp256k1_xonly_pubkey_create(&pk[0], sk[0]) == 1); CHECK(secp256k1_xonly_pubkey_create(&pk[1], sk[1]) == 1); - CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &P, &pre_session_P, pk, 2) == 1); + CHECK(secp256k1_musig_pubkey_combine(ctx, scratch, &P, &pre_session_P, pk_ptr, 2) == 1); CHECK(secp256k1_xonly_pubkey_serialize(ctx, P_serialized, &P) == 1); secp256k1_sha256_initialize(&sha); @@ -950,6 +987,145 @@ void musig_tweak_test(secp256k1_scratch_space *scratch) { musig_tweak_test_helper(&Q_xonly, sk[0], sk[1], &pre_session_Q); } +void musig_test_vectors_helper(unsigned char pk_ser[][32], int n_pks, const unsigned char *combined_pk_expected, int has_second_pk, int second_pk_idx) { + secp256k1_xonly_pubkey *pk = malloc(n_pks * sizeof(*pk)); + const secp256k1_xonly_pubkey **pk_ptr = malloc(n_pks * sizeof(*pk_ptr)); + secp256k1_xonly_pubkey combined_pk; + unsigned char combined_pk_ser[32]; + secp256k1_musig_pre_session pre_session; + secp256k1_fe second_pk_x; + int i; + + for (i = 0; i < n_pks; i++) { + CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk[i], pk_ser[i])); + pk_ptr[i] = &pk[i]; + } + + CHECK(secp256k1_musig_pubkey_combine(ctx, NULL, &combined_pk, &pre_session, pk_ptr, n_pks) == 1); + CHECK(secp256k1_fe_set_b32(&second_pk_x, pre_session.second_pk)); + CHECK(secp256k1_fe_is_zero(&second_pk_x) == !has_second_pk); + if (!secp256k1_fe_is_zero(&second_pk_x)) { + CHECK(secp256k1_memcmp_var(&pk_ser[second_pk_idx], &pre_session.second_pk, sizeof(pk_ser[second_pk_idx])) == 0); + } + CHECK(secp256k1_xonly_pubkey_serialize(ctx, combined_pk_ser, &combined_pk)); + /* TODO: remove when test vectors are not expected to change anymore */ + /* int k, l; */ + /* printf("const unsigned char combined_pk_expected[32] = {\n"); */ + /* for (k = 0; k < 4; k++) { */ + /* printf(" "); */ + /* for (l = 0; l < 8; l++) { */ + /* printf("0x%02X, ", combined_pk_ser[k*8+l]); */ + /* } */ + /* printf("\n"); */ + /* } */ + /* printf("};\n"); */ + CHECK(secp256k1_memcmp_var(combined_pk_ser, combined_pk_expected, sizeof(combined_pk_ser)) == 0); + free(pk); + free(pk_ptr); +} + +void musig_test_vectors(void) { + size_t i; + unsigned char pk_ser_tmp[4][32]; + unsigned char pk_ser[3][32] = { + /* X1 */ + { + 0xF9, 0x30, 0x8A, 0x01, 0x92, 0x58, 0xC3, 0x10, + 0x49, 0x34, 0x4F, 0x85, 0xF8, 0x9D, 0x52, 0x29, + 0xB5, 0x31, 0xC8, 0x45, 0x83, 0x6F, 0x99, 0xB0, + 0x86, 0x01, 0xF1, 0x13, 0xBC, 0xE0, 0x36, 0xF9 + }, + /* X2 */ + { + 0xDF, 0xF1, 0xD7, 0x7F, 0x2A, 0x67, 0x1C, 0x5F, + 0x36, 0x18, 0x37, 0x26, 0xDB, 0x23, 0x41, 0xBE, + 0x58, 0xFE, 0xAE, 0x1D, 0xA2, 0xDE, 0xCE, 0xD8, + 0x43, 0x24, 0x0F, 0x7B, 0x50, 0x2B, 0xA6, 0x59 + }, + /* X3 */ + { + 0x35, 0x90, 0xA9, 0x4E, 0x76, 0x8F, 0x8E, 0x18, + 0x15, 0xC2, 0xF2, 0x4B, 0x4D, 0x80, 0xA8, 0xE3, + 0x14, 0x93, 0x16, 0xC3, 0x51, 0x8C, 0xE7, 0xB7, + 0xAD, 0x33, 0x83, 0x68, 0xD0, 0x38, 0xCA, 0x66 + } + }; + const unsigned char combined_pk_expected[4][32] = { + { /* 0 */ + 0xEA, 0x06, 0x7B, 0x01, 0x67, 0x24, 0x5A, 0x6F, + 0xED, 0xB1, 0xB1, 0x22, 0xBB, 0x03, 0xAB, 0x7E, + 0x5D, 0x48, 0x6C, 0x81, 0x83, 0x42, 0xE0, 0xE9, + 0xB6, 0x41, 0x79, 0xAD, 0x32, 0x8D, 0x9D, 0x19, + }, + { /* 1 */ + 0x14, 0xE1, 0xF8, 0x3E, 0x9E, 0x25, 0x60, 0xFB, + 0x2A, 0x6C, 0x04, 0x24, 0x55, 0x6C, 0x86, 0x8D, + 0x9F, 0xB4, 0x63, 0x35, 0xD4, 0xF7, 0x8D, 0x22, + 0x7D, 0x5D, 0x1D, 0x3C, 0x89, 0x90, 0x6F, 0x1E, + }, + { /* 2 */ + 0x70, 0x28, 0x8D, 0xF2, 0xB7, 0x60, 0x3D, 0xBE, + 0xA0, 0xC7, 0xB7, 0x41, 0xDD, 0xAA, 0xB9, 0x46, + 0x81, 0x14, 0x4E, 0x0B, 0x19, 0x08, 0x6C, 0x69, + 0xB2, 0x34, 0x89, 0xE4, 0xF5, 0xB7, 0x01, 0x9A, + }, + { /* 3 */ + 0x93, 0xEE, 0xD8, 0x24, 0xF2, 0x3C, 0x5A, 0xE1, + 0xC1, 0x05, 0xE7, 0x31, 0x09, 0x97, 0x3F, 0xCD, + 0x4A, 0xE3, 0x3A, 0x9F, 0xA0, 0x2F, 0x0A, 0xC8, + 0x5A, 0x3E, 0x55, 0x89, 0x07, 0x53, 0xB0, 0x67, + }, + }; + + for (i = 0; i < sizeof(combined_pk_expected)/sizeof(combined_pk_expected[0]); i++) { + size_t n_pks; + int has_second_pk; + int second_pk_idx; + switch (i) { + case 0: + /* [X1, X2, X3] */ + n_pks = 3; + memcpy(pk_ser_tmp[0], pk_ser[0], sizeof(pk_ser_tmp[0])); + memcpy(pk_ser_tmp[1], pk_ser[1], sizeof(pk_ser_tmp[1])); + memcpy(pk_ser_tmp[2], pk_ser[2], sizeof(pk_ser_tmp[2])); + has_second_pk = 1; + second_pk_idx = 1; + break; + case 1: + /* [X3, X2, X1] */ + n_pks = 3; + memcpy(pk_ser_tmp[2], pk_ser[0], sizeof(pk_ser_tmp[0])); + memcpy(pk_ser_tmp[1], pk_ser[1], sizeof(pk_ser_tmp[1])); + memcpy(pk_ser_tmp[0], pk_ser[2], sizeof(pk_ser_tmp[2])); + has_second_pk = 1; + second_pk_idx = 1; + break; + case 2: + /* [X1, X1, X1] */ + n_pks = 3; + memcpy(pk_ser_tmp[0], pk_ser[0], sizeof(pk_ser_tmp[0])); + memcpy(pk_ser_tmp[1], pk_ser[0], sizeof(pk_ser_tmp[1])); + memcpy(pk_ser_tmp[2], pk_ser[0], sizeof(pk_ser_tmp[2])); + has_second_pk = 0; + second_pk_idx = 0; /* unchecked */ + break; + case 3: + /* [X1, X1, X2, X2] */ + n_pks = 4; + memcpy(pk_ser_tmp[0], pk_ser[0], sizeof(pk_ser_tmp[0])); + memcpy(pk_ser_tmp[1], pk_ser[0], sizeof(pk_ser_tmp[1])); + memcpy(pk_ser_tmp[2], pk_ser[1], sizeof(pk_ser_tmp[2])); + memcpy(pk_ser_tmp[3], pk_ser[1], sizeof(pk_ser_tmp[3])); + has_second_pk = 1; + second_pk_idx = 3; + break; + default: + CHECK(0); + } + musig_test_vectors_helper(pk_ser_tmp, n_pks, combined_pk_expected[i], has_second_pk, second_pk_idx); + } +} + void run_musig_tests(void) { int i; secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 1024 * 1024); @@ -965,6 +1141,7 @@ void run_musig_tests(void) { scriptless_atomic_swap(scratch); musig_tweak_test(scratch); } + musig_test_vectors(); sha256_tag_test(); secp256k1_scratch_space_destroy(ctx, scratch);