diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 0191fa1d..804e39f6 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -98,6 +98,89 @@ SECP256K1_API int secp256k1_frost_shares_gen( const unsigned char * const* ids33 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8); +/** Aggregates shares + * + * As part of the key generation protocol, each participant receives a share + * from each participant, including a share they "receive" from themselves. + * This function verifies those shares against their VSS commitments, + * aggregates the shares, and then aggregates the commitments to each + * participant's first polynomial coefficient to derive the aggregate public + * key. + * + * If this function returns an error, `secp256k1_frost_share_verify` can be + * called on each share to determine which participants submitted faulty + * shares. + * + * Returns: 0 if the arguments are invalid, 1 otherwise (which does NOT mean + * the resulting signature verifies). + * Args: ctx: pointer to a context object + * Out: agg_share: the aggregated share + * agg_pk: the aggregated x-only public key + * In: shares: all key generation shares for the partcipant's index + * vss_commitments: coefficient commitments of all participants ordered by + * the x-only pubkeys of the participants + * n_shares: the total number of shares + * threshold: the minimum number of shares required to produce a + * signature + * id33: the 33-byte ID of the participant whose shares are being + * aggregated + */ +SECP256K1_API int secp256k1_frost_share_agg( + const secp256k1_context *ctx, + secp256k1_frost_share *agg_share, + secp256k1_xonly_pubkey *agg_pk, + const secp256k1_frost_share * const *shares, + const secp256k1_pubkey * const *vss_commitments, + size_t n_shares, + size_t threshold, + const unsigned char *id33 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8); + +/** Verifies a share received during a key generation session + * + * The signature is verified against the VSS commitment received with the + * share. This is only useful for purposes of determining which share(s) are + * invalid if share_agg returns an error. + * + * Returns: 0 if the arguments are invalid or the share does not verify, 1 + * otherwise + * Args ctx: pointer to a context object + * In: threshold: the minimum number of signers required to produce a + * signature + * id33: the 33-byte participant ID of the share recipient + * share: pointer to a key generation share + * vss_commitment: the VSS commitment associated with the share + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_share_verify( + const secp256k1_context *ctx, + size_t threshold, + const unsigned char *id33, + const secp256k1_frost_share *share, + const secp256k1_pubkey * const *vss_commitment +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Computes a public verification share used for verifying partial signatures + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * In: pubshare: pointer to a struct to store the public verification + * share + * threshold: the minimum number of signers required to produce a + * signature + * id33: the 33-byte participant ID of the participant whose + * partial signature will be verified with the pubshare + * vss_commitments: coefficient commitments of all participants + * n_participants: the total number of participants + */ +SECP256K1_API int secp256k1_frost_compute_pubshare( + const secp256k1_context *ctx, + secp256k1_pubkey *pubshare, + size_t threshold, + const unsigned char *id33, + const secp256k1_pubkey * const *vss_commitments, + size_t n_participants +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + #ifdef __cplusplus } #endif diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index 8d17b48a..8b8ecddf 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -188,4 +188,228 @@ int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_sha return ret; } +typedef struct { + const secp256k1_context *ctx; + secp256k1_scalar idx; + secp256k1_scalar idxn; + const secp256k1_pubkey * const* vss_commitment; +} secp256k1_frost_verify_share_ecmult_data; + +typedef struct { + const secp256k1_context *ctx; + secp256k1_scalar idx; + secp256k1_scalar idxn; + const secp256k1_pubkey * const* vss_commitments; + size_t threshold; +} secp256k1_frost_compute_pubshare_ecmult_data; + +typedef struct { + const secp256k1_context *ctx; + const secp256k1_pubkey * const* pks; + size_t threshold; +} secp256k1_frost_pubkey_combine_ecmult_data; + +static int secp256k1_frost_verify_share_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_verify_share_ecmult_data *ctx = (secp256k1_frost_verify_share_ecmult_data *) data; + if (!secp256k1_pubkey_load(ctx->ctx, pt, *(ctx->vss_commitment)+idx)) { + return 0; + } + *sc = ctx->idxn; + secp256k1_scalar_mul(&ctx->idxn, &ctx->idxn, &ctx->idx); + + return 1; +} + +static int secp256k1_frost_compute_pubshare_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_compute_pubshare_ecmult_data *ctx = (secp256k1_frost_compute_pubshare_ecmult_data *) data; + + if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitments[idx/ctx->threshold][idx % ctx->threshold])) { + return 0; + } + if (idx != 0 && idx % ctx->threshold == 0) { + secp256k1_scalar_set_int(&ctx->idxn, 1); + } + *sc = ctx->idxn; + secp256k1_scalar_mul(&ctx->idxn, &ctx->idxn, &ctx->idx); + + return 1; +} + +static int secp256k1_frost_pubkey_combine_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_pubkey_combine_ecmult_data *ctx = (secp256k1_frost_pubkey_combine_ecmult_data *) data; + + secp256k1_scalar_set_int(sc, 1); + /* the public key is the first index of each set of coefficients */ + return secp256k1_pubkey_load(ctx->ctx, pt, &ctx->pks[idx][0]); +} + +/* See draft-irtf-cfrg-frost-08#appendix-C.2 */ +static int secp256k1_frost_vss_verify_internal(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_scalar *share, const secp256k1_pubkey * const* vss_commitment) { + secp256k1_scalar share_neg; + secp256k1_gej tmpj, snj; + secp256k1_ge sng; + secp256k1_frost_verify_share_ecmult_data verify_share_ecmult_data; + + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + + /* Use an EC multi-multiplication to verify the following equation: + * 0 = - share_i*G + idx^0*vss_commitment[0] + * + ... + * + idx^(threshold - 1)*vss_commitment[threshold - 1]*/ + verify_share_ecmult_data.ctx = ctx; + verify_share_ecmult_data.vss_commitment = vss_commitment; + /* Evaluate the public polynomial at the idx */ + if (!secp256k1_frost_compute_indexhash(&verify_share_ecmult_data.idx, id33)) { + return 0; + } + secp256k1_scalar_set_int(&verify_share_ecmult_data.idxn, 1); + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &tmpj, NULL, secp256k1_frost_verify_share_ecmult_callback, (void *) &verify_share_ecmult_data, threshold)) { + return 0; + } + secp256k1_scalar_negate(&share_neg, share); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &snj, &share_neg); + secp256k1_ge_set_gej(&sng, &snj); + secp256k1_gej_add_ge(&tmpj, &tmpj, &sng); + return secp256k1_gej_is_infinity(&tmpj); +} + +/* See draft-irtf-cfrg-frost-08#appendix-C.2 */ +int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_frost_share *share, const secp256k1_pubkey * const* vss_commitment) { + secp256k1_scalar share_i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(id33 != NULL); + ARG_CHECK(share != NULL); + ARG_CHECK(vss_commitment != NULL); + ARG_CHECK(threshold > 1); + + if (!secp256k1_frost_share_load(ctx, &share_i, share)) { + return 0; + } + + return secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, vss_commitment); +} + +int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pubkey *pubshare, size_t threshold, const unsigned char *id33, const secp256k1_pubkey * const* vss_commitments, size_t n_participants) { + secp256k1_gej pkj; + secp256k1_ge pkp, tmp; + secp256k1_frost_compute_pubshare_ecmult_data compute_pubshare_ecmult_data; + secp256k1_frost_pubkey_combine_ecmult_data pubkey_combine_ecmult_data; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubshare != NULL); + memset(pubshare, 0, sizeof(*pubshare)); + ARG_CHECK(id33 != NULL); + ARG_CHECK(vss_commitments != NULL); + ARG_CHECK(n_participants > 1); + ARG_CHECK(threshold > 1); + + if (threshold > n_participants) { + return 0; + } + + /* Use an EC multi-multiplication to compute the following equation: + * agg_share_i*G = ( + * idx^0*vss_commitment[0][0] + ... + * + idx^(t - 1)*vss_commitment[0][t - 1] + * ) + ... + * + ( + * idx^0*vss_commitment[n - 1][0] + ... + * + idx^(t - 1)*vss_commitment[n - 1][t - 1] + * )*/ + compute_pubshare_ecmult_data.ctx = ctx; + compute_pubshare_ecmult_data.vss_commitments = vss_commitments; + compute_pubshare_ecmult_data.threshold = threshold; + /* Evaluate the public polynomial at the idx */ + if (!secp256k1_frost_compute_indexhash(&compute_pubshare_ecmult_data.idx, id33)) { + return 0; + } + secp256k1_scalar_set_int(&compute_pubshare_ecmult_data.idxn, 1); + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_compute_pubshare_ecmult_callback, (void *) &compute_pubshare_ecmult_data, n_participants*threshold)) { + return 0; + } + secp256k1_ge_set_gej(&tmp, &pkj); + + /* Combine pubkeys */ + pubkey_combine_ecmult_data.ctx = ctx; + pubkey_combine_ecmult_data.pks = vss_commitments; + pubkey_combine_ecmult_data.threshold = threshold; + + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_pubkey_combine_callback, (void *) &pubkey_combine_ecmult_data, n_participants)) { + return 0; + } + secp256k1_ge_set_gej(&pkp, &pkj); + secp256k1_fe_normalize_var(&pkp.y); + if (secp256k1_fe_is_odd(&pkp.y)) { + secp256k1_ge_neg(&tmp, &tmp); + } + + secp256k1_pubkey_save(pubshare, &tmp); + + return 1; +} + +int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, secp256k1_xonly_pubkey *agg_pk, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, size_t n_shares, size_t threshold, const unsigned char *id33) { + secp256k1_frost_pubkey_combine_ecmult_data pubkey_combine_ecmult_data; + secp256k1_gej pkj; + secp256k1_ge pkp; + int pk_parity; + secp256k1_scalar acc; + size_t i; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(agg_share != NULL); + memset(agg_share, 0, sizeof(*agg_share)); + ARG_CHECK(agg_pk != NULL); + memset(agg_pk, 0, sizeof(*agg_pk)); + ARG_CHECK(shares != NULL); + ARG_CHECK(vss_commitments != NULL); + ARG_CHECK(id33 != NULL); + ARG_CHECK(n_shares > 1); + ARG_CHECK(threshold > 1); + + if (threshold > n_shares) { + return 0; + } + + secp256k1_scalar_clear(&acc); + for (i = 0; i < n_shares; i++) { + secp256k1_scalar share_i; + + if (!secp256k1_frost_share_load(ctx, &share_i, shares[i])) { + return 0; + } + /* Verify share against commitments */ + ret &= secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, &vss_commitments[i]); + secp256k1_scalar_add(&acc, &acc, &share_i); + } + + /* Combine pubkeys */ + pubkey_combine_ecmult_data.ctx = ctx; + pubkey_combine_ecmult_data.pks = vss_commitments; + pubkey_combine_ecmult_data.threshold = threshold; + + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_pubkey_combine_callback, (void *) &pubkey_combine_ecmult_data, n_shares)) { + return 0; + } + + secp256k1_ge_set_gej(&pkp, &pkj); + secp256k1_fe_normalize_var(&pkp.y); + pk_parity = secp256k1_extrakeys_ge_even_y(&pkp); + secp256k1_xonly_pubkey_save(agg_pk, &pkp); + + /* Invert the aggregate share if the combined pubkey has an odd Y coordinate. */ + if (pk_parity == 1) { + secp256k1_scalar_negate(&acc, &acc); + } + secp256k1_frost_share_save(agg_share, &acc); + + return ret; +} + #endif