diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 753eda0d..07328fef 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -17,6 +17,84 @@ 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: a secp256k1 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: a secp256k1 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 generation shares + * + * To generate a key, a trusted dealer generates a share for each other + * participant. + * + * Each participant _must_ have a secure channel with the trusted dealer with + * which they can transmit shares to each other. + * + * A new seed32 _must_ be used for each key generation session. The trusted + * dealer must NOT REUSE their respective seed32 again for subsequent key + * generation sessions. If a trusted dealer fails to complete this session or + * start a new session to generate a new key, they must NOT REUSE their + * respective seed32 again, but instead generate a new one. It is recommended + * to always choose seed32 uniformly at random to avoid their reuse. + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: shares: pointer to the key generation shares + * pubshares: pointer to the public verification shares + * pk: pointer to the x-only public key + * In: seed32: a 32-byte seed as explained above + * threshold: the minimum number of signers required to produce a + * signature + * n_participants: the total number of participants + */ +SECP256K1_API int secp256k1_frost_shares_trusted_gen( + const secp256k1_context *ctx, + secp256k1_frost_share *shares, + secp256k1_pubkey *pubshares, + secp256k1_xonly_pubkey *pk, + const unsigned char *seed32, + size_t threshold, + size_t n_participants +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + #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..ed864884 --- /dev/null +++ b/src/modules/frost/keygen.h @@ -0,0 +1,13 @@ +/********************************************************************** + * Copyright (c) 2021-2023 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..d9e904bc --- /dev/null +++ b/src/modules/frost/keygen_impl.h @@ -0,0 +1,146 @@ +/********************************************************************** + * Copyright (c) 2021-2023 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" + +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; + + /* The magic is non-secret so it can be declassified to allow branching. */ + secp256k1_declassify(ctx, &share->data[0], 4); + 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; +} + +int secp256k1_frost_shares_trusted_gen(const secp256k1_context *ctx, secp256k1_frost_share *shares, secp256k1_pubkey *pubshares, secp256k1_xonly_pubkey *pk, const unsigned char *seed32, size_t threshold, size_t n_participants) { + secp256k1_sha256 sha; + secp256k1_gej rj; + secp256k1_ge rp; + unsigned char polygen[32]; + size_t i, j; + int ret = 1; + int pk_parity = 0; + + 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(pubshares != NULL); + ARG_CHECK(pk != NULL); + ARG_CHECK(seed32 != NULL); + ARG_CHECK(threshold > 1); + ARG_CHECK(n_participants >= threshold); + + /* Commit to threshold, n_participants, and seed */ + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/trusted-shares-polygen", sizeof("FROST/trusted-shares-polygen") - 1); + 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); + secp256k1_sha256_finalize(&sha, polygen); + + /* Derive share */ + /* See draft-irtf-cfrg-frost-08#appendix-C.1 */ + for (i = 0; i < n_participants; i++) { + secp256k1_scalar share_i, idx; + + secp256k1_scalar_clear(&share_i); + + for (j = 0; j < threshold; j++) { + unsigned char buf[32]; + secp256k1_scalar coeff_i; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/trusted-shares-coeffgen", sizeof("FROST/trusted-shares-coeffgen") - 1); + secp256k1_sha256_write(&sha, polygen, 32); + secp256k1_write_be64(&buf[0], j); + secp256k1_sha256_write(&sha, buf, 8); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(&coeff_i, buf, NULL); + + /* Horner's method to evaluate polynomial to derive shares */ + secp256k1_scalar_add(&share_i, &share_i, &coeff_i); + if (j < threshold - 1) { + secp256k1_scalar_set_int(&idx, i + 1); + secp256k1_scalar_mul(&share_i, &share_i, &idx); + } + + /* Compute x-only public key for constant term */ + if (i == 0 && j == threshold - 1) { + /* Compute commitment to constant term */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &coeff_i); + secp256k1_ge_set_gej(&rp, &rj); + /* The commitment is non-secret so it can be declassified to + * allow branching. */ + secp256k1_declassify(ctx, &rp, sizeof(rp)); + secp256k1_fe_normalize_var(&rp.y); + pk_parity = secp256k1_extrakeys_ge_even_y(&rp); + secp256k1_xonly_pubkey_save(pk, &rp); + } + } + + if (pk_parity == 1) { + secp256k1_scalar_negate(&share_i, &share_i); + } + secp256k1_frost_share_save(&shares[i], &share_i); + /* Compute pubshare */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &share_i); + secp256k1_ge_set_gej(&rp, &rj); + secp256k1_pubkey_save(&pubshares[i], &rp); + } + + return ret; +} + +#endif diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h index 73772ddd..a5ce980f 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