frost: share aggregation
This commit adds share aggregation and verification, as well as computation of public verification shares.
This commit is contained in:
parent
6e2398d018
commit
a6f41950e8
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user