From 6e2398d018eede75fc02da9d613aae97643b81c7 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 17 Jun 2024 18:10:13 -0700 Subject: [PATCH] frost: share generation This commit adds share generation, as well as share serialization and parsing. --- include/secp256k1_frost.h | 83 +++++++++++ src/modules/frost/Makefile.am.include | 2 + src/modules/frost/keygen.h | 13 ++ src/modules/frost/keygen_impl.h | 191 ++++++++++++++++++++++++++ src/modules/frost/main_impl.h | 2 + 5 files changed, 291 insertions(+) create mode 100644 src/modules/frost/keygen.h create mode 100644 src/modules/frost/keygen_impl.h diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 93c68a19..0191fa1d 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -1,6 +1,8 @@ #ifndef SECP256K1_FROST_H #define SECP256K1_FROST_H +#include "secp256k1_extrakeys.h" + #ifdef __cplusplus extern "C" { #endif @@ -15,6 +17,87 @@ extern "C" { * (https://crysp.uwaterloo.ca/software/frost/). */ +/** Opaque data structures + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. If you + * need to convert to a format suitable for storage, transmission, or + * comparison, use the corresponding serialization and parsing functions. + */ + +/** Opaque data structure that holds a signer's _secret_ share. + * + * Guaranteed to be 36 bytes in size. Serialized and parsed with + * `frost_share_serialize` and `frost_share_parse`. + */ +typedef struct { + unsigned char data[36]; +} secp256k1_frost_share; + +/** Serialize a FROST share + * + * Returns: 1 when the share could be serialized, 0 otherwise + * Args: ctx: pointer to a context object + * Out: out32: pointer to a 32-byte array to store the serialized share + * In: share: pointer to the share + */ +SECP256K1_API int secp256k1_frost_share_serialize( + const secp256k1_context *ctx, + unsigned char *out32, + const secp256k1_frost_share *share +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse a FROST share. + * + * Returns: 1 when the share could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: share: pointer to a share object + * In: in32: pointer to the 32-byte share to be parsed + */ +SECP256K1_API int secp256k1_frost_share_parse( + const secp256k1_context *ctx, + secp256k1_frost_share *share, + const unsigned char *in32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Creates key shares + * + * To generate a key, each participant generates a share for each other + * participant. For example, in the case of 2 particpants, Alice and Bob, they + * each generate 2 shares, distribute 1 share to each other using a secure + * channel, and keep 1 for themselves. + * + * Each participant must transmit shares over secure channels to each other + * participant. + * + * Each call to this function must have a UNIQUE and uniformly RANDOM seed32 + * that must that must NOT BE REUSED in subsequent calls to this function and + * must be KEPT SECRET (even from other participants). + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: shares: pointer to the key shares + * vss_commitment: pointer to the VSS commitment + * pok64: pointer to the proof of knowledge + * In: seed32: 32-byte random seed as explained above. Must be + * unique to this call to secp256k1_frost_shares_gen + * and must be uniformly random. + * threshold: the minimum number of signers required to produce a + * signature + * n_participants: the total number of participants + * ids33: array of 33-byte participant IDs + */ +SECP256K1_API int secp256k1_frost_shares_gen( + const secp256k1_context *ctx, + secp256k1_frost_share *shares, + secp256k1_pubkey *vss_commitment, + unsigned char *pok64, + const unsigned char *seed32, + size_t threshold, + size_t n_participants, + 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); + #ifdef __cplusplus } #endif diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include index 5541884b..1fea08d2 100644 --- a/src/modules/frost/Makefile.am.include +++ b/src/modules/frost/Makefile.am.include @@ -1,2 +1,4 @@ include_HEADERS += include/secp256k1_frost.h noinst_HEADERS += src/modules/frost/main_impl.h +noinst_HEADERS += src/modules/frost/keygen.h +noinst_HEADERS += src/modules/frost/keygen_impl.h diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h new file mode 100644 index 00000000..bc1082a1 --- /dev/null +++ b/src/modules/frost/keygen.h @@ -0,0 +1,13 @@ +/********************************************************************** + * Copyright (c) 2021-2024 Jesse Posner * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_MODULE_FROST_KEYGEN_H +#define SECP256K1_MODULE_FROST_KEYGEN_H + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_frost.h" + +#endif diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h new file mode 100644 index 00000000..8d17b48a --- /dev/null +++ b/src/modules/frost/keygen_impl.h @@ -0,0 +1,191 @@ +/********************************************************************** + * Copyright (c) 2021-2024 Jesse Posner * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_MODULE_FROST_KEYGEN_IMPL_H +#define SECP256K1_MODULE_FROST_KEYGEN_IMPL_H + +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_frost.h" + +#include "keygen.h" +#include "../../ecmult.h" +#include "../../field.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../scalar.h" + +/* Computes indexhash = tagged_hash(pk) */ +static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33) { + secp256k1_sha256 sha; + unsigned char buf[32]; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/index", sizeof("FROST/index") - 1); + secp256k1_sha256_write(&sha, id33, 33); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(indexhash, buf, NULL); + /* The x-coordinate must not be zero (see + * draft-irtf-cfrg-frost-08#section-4.2.2) */ + if (secp256k1_scalar_is_zero(indexhash)) { + return 0; + } + + return 1; +} + +static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 }; + +static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_scalar *s) { + memcpy(&share->data[0], secp256k1_frost_share_magic, 4); + secp256k1_scalar_get_b32(&share->data[4], s); +} + +static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share) { + int overflow; + + ARG_CHECK(secp256k1_memcmp_var(&share->data[0], secp256k1_frost_share_magic, 4) == 0); + secp256k1_scalar_set_b32(s, &share->data[4], &overflow); + /* Parsed shares cannot overflow */ + VERIFY_CHECK(!overflow); + return 1; +} + +int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_share* share) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out32 != NULL); + ARG_CHECK(share != NULL); + memcpy(out32, &share->data[4], 32); + return 1; +} + +int secp256k1_frost_share_parse(const secp256k1_context* ctx, secp256k1_frost_share* share, const unsigned char *in32) { + secp256k1_scalar tmp; + int overflow; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(share != NULL); + ARG_CHECK(in32 != NULL); + + secp256k1_scalar_set_b32(&tmp, in32, &overflow); + if (overflow) { + return 0; + } + secp256k1_frost_share_save(share, &tmp); + return 1; +} + +static void secp256k1_frost_derive_coeff(secp256k1_scalar *coeff, const unsigned char *polygen32, size_t i) { + secp256k1_sha256 sha; + unsigned char buf[32]; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/coeffgen", sizeof("FROST/coeffgen") - 1); + secp256k1_sha256_write(&sha, polygen32, 32); + secp256k1_write_be64(&buf[0], i); + secp256k1_sha256_write(&sha, buf, 8); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(coeff, buf, NULL); +} + +static int secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubkey *vss_commitment, unsigned char *pok64, const unsigned char *polygen32, size_t threshold) { + secp256k1_sha256 sha; + unsigned char buf[32]; + secp256k1_keypair keypair; + secp256k1_gej rj; + secp256k1_ge rp; + size_t i; + int ret = 1; + + for (i = 0; i < threshold; i++) { + secp256k1_scalar coeff_i; + + secp256k1_frost_derive_coeff(&coeff_i, polygen32, i); + /* Compute proof-of-knowledge for constant term */ + if (i == threshold - 1) { + secp256k1_scalar_get_b32(buf, &coeff_i); + ret &= secp256k1_keypair_create(ctx, &keypair, buf); + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/KeygenPoK", sizeof("FROST/KeygenPoK") - 1); + secp256k1_sha256_finalize(&sha, buf); + + ret &= secp256k1_schnorrsig_sign32(ctx, pok64, buf, &keypair, NULL); + } + + /* Compute commitment to each coefficient */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &coeff_i); + secp256k1_ge_set_gej(&rp, &rj); + secp256k1_pubkey_save(&vss_commitment[threshold - i - 1], &rp); + } + return ret; +} + +static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigned char *polygen32, size_t threshold, const unsigned char *id33) { + secp256k1_scalar idx; + secp256k1_scalar share_i; + size_t i; + int ret = 1; + + /* Derive share */ + /* See draft-irtf-cfrg-frost-08#appendix-C.1 */ + secp256k1_scalar_set_int(&share_i, 0); + if (!secp256k1_frost_compute_indexhash(&idx, id33)) { + return 0; + } + for (i = 0; i < threshold; i++) { + secp256k1_scalar coeff_i; + + secp256k1_frost_derive_coeff(&coeff_i, polygen32, i); + /* Horner's method to evaluate polynomial to derive shares */ + secp256k1_scalar_add(&share_i, &share_i, &coeff_i); + if (i < threshold - 1) { + secp256k1_scalar_mul(&share_i, &share_i, &idx); + } + } + secp256k1_frost_share_save(share, &share_i); + + return ret; +} + +int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_share *shares, secp256k1_pubkey *vss_commitment, unsigned char *pok64, const unsigned char *seed32, size_t threshold, size_t n_participants, const unsigned char * const* ids33) { + secp256k1_sha256 sha; + unsigned char polygen[32]; + size_t i; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(shares != NULL); + for (i = 0; i < n_participants; i++) { + memset(&shares[i], 0, sizeof(shares[i])); + } + ARG_CHECK(vss_commitment != NULL); + ARG_CHECK(pok64 != NULL); + ARG_CHECK(seed32 != NULL); + ARG_CHECK(ids33 != NULL); + ARG_CHECK(threshold > 1); + ARG_CHECK(n_participants >= threshold); + + /* Commit to all inputs */ + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, seed32, 32); + secp256k1_write_be64(&polygen[0], threshold); + secp256k1_write_be64(&polygen[8], n_participants); + secp256k1_sha256_write(&sha, polygen, 16); + for (i = 0; i < n_participants; i++) { + secp256k1_sha256_write(&sha, ids33[i], 33); + } + secp256k1_sha256_finalize(&sha, polygen); + + ret &= secp256k1_frost_vss_gen(ctx, vss_commitment, pok64, polygen, threshold); + + for (i = 0; i < n_participants; i++) { + ret &= secp256k1_frost_share_gen(&shares[i], polygen, threshold, ids33[i]); + } + + return ret; +} + +#endif diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h index 0ba469d7..66068ba7 100644 --- a/src/modules/frost/main_impl.h +++ b/src/modules/frost/main_impl.h @@ -7,4 +7,6 @@ #ifndef SECP256K1_MODULE_FROST_MAIN #define SECP256K1_MODULE_FROST_MAIN +#include "keygen_impl.h" + #endif