frost: share aggregation
This commit adds share aggregation and verification, as well as computation of public verification shares.
This commit is contained in:
		
							parent
							
								
									2336b02fad
								
							
						
					
					
						commit
						197fb7efb9
					
				| @ -98,6 +98,89 @@ SECP256K1_API int secp256k1_frost_shares_gen( | |||||||
|     const unsigned char * const* ids33 |     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); | ) 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 | ||||||
|  |  *  Out:    pubshare: pointer to a struct to store the public verification | ||||||
|  |  *                    share | ||||||
|  |  *  In:    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 | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -188,4 +188,228 @@ int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_sha | |||||||
|     return ret; |     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 | #endif | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user