diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 524a4ab7..99cbcafe 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -15,6 +15,9 @@ extern "C" { * This module implements a variant of Flexible Round-Optimized Schnorr * Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg * (https://crysp.uwaterloo.ca/software/frost/). + * + * Following the convention used in the MuSig module, the API uses the singular + * term "nonce" to refer to the two "nonces" used by the FROST scheme. */ /** Opaque data structures @@ -34,6 +37,61 @@ typedef struct { unsigned char data[36]; } secp256k1_frost_share; +/** Opaque data structure that holds a signer's _secret_ nonce. + * + * Guaranteed to be 68 bytes in size. + * + * WARNING: This structure MUST NOT be copied or read or written to directly. + * A signer who is online throughout the whole process and can keep this + * structure in memory can use the provided API functions for a safe standard + * workflow. See + * https://blockstream.com/2019/02/18/musig-a-new-multisignature-standard/ for + * more details about the risks associated with serializing or deserializing + * this structure. + * + * We repeat, copying this data structure can result in nonce reuse which will + * leak the secret signing key. + */ +typedef struct { + unsigned char data[68]; +} secp256k1_frost_secnonce; + +/** Opaque data structure that holds a signer's public nonce. +* +* Guaranteed to be 132 bytes in size. It can be safely copied/moved. +* Serialized and parsed with `frost_pubnonce_serialize` and +* `frost_pubnonce_parse`. +*/ +typedef struct { + unsigned char data[132]; +} secp256k1_frost_pubnonce; + +/** Parse a signer's public nonce. + * + * Returns: 1 when the nonce could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: nonce: pointer to a nonce object + * In: in66: pointer to the 66-byte nonce to be parsed + */ +SECP256K1_API int secp256k1_frost_pubnonce_parse( + const secp256k1_context *ctx, + secp256k1_frost_pubnonce *nonce, + const unsigned char *in66 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize a signer's public nonce + * + * Returns: 1 when the nonce could be serialized, 0 otherwise + * Args: ctx: pointer to a context object + * Out: out66: pointer to a 66-byte array to store the serialized nonce + * In: nonce: pointer to the nonce + */ +SECP256K1_API int secp256k1_frost_pubnonce_serialize( + const secp256k1_context *ctx, + unsigned char *out66, + const secp256k1_frost_pubnonce *nonce +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + /** Serialize a FROST share * * Returns: 1 when the share could be serialized, 0 otherwise @@ -181,6 +239,59 @@ SECP256K1_API int secp256k1_frost_compute_pubshare( size_t n_participants ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); +/** Starts a signing session by generating a nonce + * + * This function outputs a secret nonce that will be required for signing and a + * corresponding public nonce that is intended to be sent to other signers. + * + * FROST, like MuSig, differs from regular Schnorr signing in that + * implementers _must_ take special care to not reuse a nonce. This can be + * ensured by following these rules: + * + * 1. Each call to this function must have a UNIQUE session_id32 that must NOT BE + * REUSED in subsequent calls to this function. + * If you do not provide a seckey, session_id32 _must_ be UNIFORMLY RANDOM + * AND KEPT SECRET (even from other signers). If you do provide a seckey, + * session_id32 can instead be a counter (that must never repeat!). However, + * it is recommended to always choose session_id32 uniformly at random. + * 2. If you already know the seckey, message or aggregate public key, they + * can be optionally provided to derive the nonce and increase + * misuse-resistance. The extra_input32 argument can be used to provide + * additional data that does not repeat in normal scenarios, such as the + * current time. + * 3. Avoid copying (or serializing) the secnonce. This reduces the possibility + * that it is used more than once for signing. + * + * Remember that nonce reuse will leak the secret key! + * Note that using the same agg_share for multiple FROST sessions is fine. + * + * Returns: 0 if the arguments are invalid and 1 otherwise + * Args: ctx: pointer to a context object (not secp256k1_context_static) + * Out: secnonce: pointer to a structure to store the secret nonce + * pubnonce: pointer to a structure to store the public nonce + * In: session_id32: a 32-byte session_id32 as explained above. Must be + * unique to this call to secp256k1_frost_nonce_gen and + * must be uniformly random unless you really know what you + * are doing. + * agg_share: the aggregated share that will later be used for + * signing, if already known (can be NULL) + * msg32: the 32-byte message that will later be signed, if + * already known (can be NULL) + * agg_pk: the FROST-aggregated public key (can be NULL) + * extra_input32: an optional 32-byte array that is input to the nonce + * derivation function (can be NULL) + */ +SECP256K1_API int secp256k1_frost_nonce_gen( + const secp256k1_context *ctx, + secp256k1_frost_secnonce *secnonce, + secp256k1_frost_pubnonce *pubnonce, + const unsigned char *session_id32, + const secp256k1_frost_share *agg_share, + const unsigned char *msg32, + const secp256k1_xonly_pubkey *agg_pk, + const unsigned char *extra_input32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/group.h b/src/group.h index 81b159c8..43cf57fd 100644 --- a/src/group.h +++ b/src/group.h @@ -202,6 +202,10 @@ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf); */ static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge); +static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge); + +static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data); + /** Check invariants on an affine group element (no-op unless VERIFY is enabled). */ static void secp256k1_ge_verify(const secp256k1_ge *a); #define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a) diff --git a/src/group_impl.h b/src/group_impl.h index f27b7d99..fb475217 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -914,6 +914,23 @@ static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) { return secp256k1_fe_is_square_var(&yz); } +static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) { + if (secp256k1_ge_is_infinity(ge)) { + memset(data, 0, 64); + } else { + secp256k1_ge_to_bytes(data, ge); + } +} + +static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) { + unsigned char zeros[64] = { 0 }; + if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) { + secp256k1_ge_set_infinity(ge); + } else { + secp256k1_ge_from_bytes(ge, data); + } +} + static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) { #ifdef EXHAUSTIVE_TEST_ORDER secp256k1_gej out; diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include index 1fea08d2..7e44e6ea 100644 --- a/src/modules/frost/Makefile.am.include +++ b/src/modules/frost/Makefile.am.include @@ -2,3 +2,5 @@ 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 +noinst_HEADERS += src/modules/frost/session.h +noinst_HEADERS += src/modules/frost/session_impl.h diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index bc1082a1..c6811423 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -10,4 +10,8 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_frost.h" +#include "../../scalar.h" + +static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share); + #endif diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h index 66068ba7..a126f452 100644 --- a/src/modules/frost/main_impl.h +++ b/src/modules/frost/main_impl.h @@ -8,5 +8,6 @@ #define SECP256K1_MODULE_FROST_MAIN #include "keygen_impl.h" +#include "session_impl.h" #endif diff --git a/src/modules/frost/session.h b/src/modules/frost/session.h new file mode 100644 index 00000000..d4b922be --- /dev/null +++ b/src/modules/frost/session.h @@ -0,0 +1,10 @@ +/********************************************************************** + * 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_SESSION_H +#define SECP256K1_MODULE_FROST_SESSION_H + +#endif diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h new file mode 100644 index 00000000..07c53196 --- /dev/null +++ b/src/modules/frost/session_impl.h @@ -0,0 +1,233 @@ +/********************************************************************** + * 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_SESSION_IMPL_H +#define SECP256K1_MODULE_FROST_SESSION_IMPL_H + +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_frost.h" + +#include "keygen.h" +#include "session.h" +#include "../../eckey.h" +#include "../../hash.h" +#include "../../scalar.h" +#include "../../util.h" + +static const unsigned char secp256k1_frost_secnonce_magic[4] = { 0x84, 0x7d, 0x46, 0x25 }; + +static void secp256k1_frost_secnonce_save(secp256k1_frost_secnonce *secnonce, secp256k1_scalar *k) { + memcpy(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4); + secp256k1_scalar_get_b32(&secnonce->data[4], &k[0]); + secp256k1_scalar_get_b32(&secnonce->data[36], &k[1]); +} + +static int secp256k1_frost_secnonce_load(const secp256k1_context* ctx, secp256k1_scalar *k, secp256k1_frost_secnonce *secnonce) { + int is_zero; + ARG_CHECK(secp256k1_memcmp_var(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4) == 0); + secp256k1_scalar_set_b32(&k[0], &secnonce->data[4], NULL); + secp256k1_scalar_set_b32(&k[1], &secnonce->data[36], NULL); + /* We make very sure that the nonce isn't invalidated by checking the values + * in addition to the magic. */ + is_zero = secp256k1_scalar_is_zero(&k[0]) & secp256k1_scalar_is_zero(&k[1]); + secp256k1_declassify(ctx, &is_zero, sizeof(is_zero)); + ARG_CHECK(!is_zero); + return 1; +} + +/* If flag is true, invalidate the secnonce; otherwise leave it. Constant-time. */ +static void secp256k1_frost_secnonce_invalidate(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, int flag) { + secp256k1_memczero(secnonce->data, sizeof(secnonce->data), flag); + /* The flag argument is usually classified. So, above code makes the magic + * classified. However, we need the magic to be declassified to be able to + * compare it during secnonce_load. */ + secp256k1_declassify(ctx, secnonce->data, sizeof(secp256k1_frost_secnonce_magic)); +} + +static const unsigned char secp256k1_frost_pubnonce_magic[4] = { 0x8b, 0xcf, 0xe2, 0xc2 }; + +/* Requires that none of the provided group elements is infinity. Works for both + * frost_pubnonce and frost_aggnonce. */ +static void secp256k1_frost_pubnonce_save(secp256k1_frost_pubnonce* nonce, secp256k1_ge* ge) { + int i; + memcpy(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4); + for (i = 0; i < 2; i++) { + secp256k1_point_save_ext(nonce->data + 4+64*i, &ge[i]); + } +} + +/* Works for both frost_pubnonce and frost_aggnonce. Returns 1 unless the nonce + * wasn't properly initialized */ +static int secp256k1_frost_pubnonce_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_frost_pubnonce* nonce) { + int i; + + ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4) == 0); + for (i = 0; i < 2; i++) { + secp256k1_point_load_ext(&ge[i], nonce->data + 4+64*i); + } + return 1; +} + +int secp256k1_frost_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_frost_pubnonce* nonce) { + secp256k1_ge ge[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out66 != NULL); + memset(out66, 0, 66); + ARG_CHECK(nonce != NULL); + + if (!secp256k1_frost_pubnonce_load(ctx, ge, nonce)) { + return 0; + } + for (i = 0; i < 2; i++) { + int ret; + size_t size = 33; + ret = secp256k1_eckey_pubkey_serialize(&ge[i], &out66[33*i], &size, 1); +#ifdef VERIFY + /* serialize must succeed because the point was just loaded */ + VERIFY_CHECK(ret && size == 33); +#else + (void) ret; +#endif + } + return 1; +} + +int secp256k1_frost_pubnonce_parse(const secp256k1_context* ctx, secp256k1_frost_pubnonce* nonce, const unsigned char *in66) { + secp256k1_ge ge[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(nonce != NULL); + ARG_CHECK(in66 != NULL); + for (i = 0; i < 2; i++) { + if (!secp256k1_eckey_pubkey_parse(&ge[i], &in66[33*i], 33)) { + return 0; + } + if (!secp256k1_ge_is_in_correct_subgroup(&ge[i])) { + return 0; + } + } + /* The group elements can not be infinity because they were just parsed */ + secp256k1_frost_pubnonce_save(nonce, ge); + return 1; +} + +static void secp256k1_nonce_function_frost(secp256k1_scalar *k, const unsigned char *session_id, const unsigned char *msg32, const unsigned char *key32, const unsigned char *pk32, const unsigned char *extra_input32) { + secp256k1_sha256 sha; + unsigned char seed[32]; + unsigned char i; + enum { n_extra_in = 4 }; + const unsigned char *extra_in[n_extra_in]; + + /* TODO: this doesn't have the same sidechannel resistance as the BIP340 + * nonce function because the seckey feeds directly into SHA. */ + + /* Subtract one from `sizeof` to avoid hashing the implicit null byte */ + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/nonce", sizeof("FROST/nonce") - 1); + secp256k1_sha256_write(&sha, session_id, 32); + extra_in[0] = msg32; + extra_in[1] = key32; + extra_in[2] = pk32; + extra_in[3] = extra_input32; + for (i = 0; i < n_extra_in; i++) { + unsigned char len; + if (extra_in[i] != NULL) { + len = 32; + secp256k1_sha256_write(&sha, &len, 1); + secp256k1_sha256_write(&sha, extra_in[i], 32); + } else { + len = 0; + secp256k1_sha256_write(&sha, &len, 1); + } + } + secp256k1_sha256_finalize(&sha, seed); + + for (i = 0; i < 2; i++) { + unsigned char buf[32]; + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, seed, 32); + secp256k1_sha256_write(&sha, &i, sizeof(i)); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(&k[i], buf, NULL); + } +} + +int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, secp256k1_frost_pubnonce *pubnonce, const unsigned char *session_id32, const secp256k1_frost_share *share, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, const unsigned char *extra_input32) { + secp256k1_scalar k[2]; + secp256k1_ge nonce_pt[2]; + int i; + unsigned char pk_ser[32]; + unsigned char *pk_ser_ptr = NULL; + unsigned char sk_ser[32]; + unsigned char *sk_ser_ptr = NULL; + int sk_serialize_success; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secnonce != NULL); + memset(secnonce, 0, sizeof(*secnonce)); + ARG_CHECK(pubnonce != NULL); + memset(pubnonce, 0, sizeof(*pubnonce)); + ARG_CHECK(session_id32 != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + if (share == NULL) { + /* Check in constant time that the session_id is not 0 as a + * defense-in-depth measure that may protect against a faulty RNG. */ + unsigned char acc = 0; + for (i = 0; i < 32; i++) { + acc |= session_id32[i]; + } + ret &= !!acc; + memset(&acc, 0, sizeof(acc)); + } + + if (share != NULL) { + /* Check that the share is valid to be able to sign for it later. */ + secp256k1_scalar sk; + + ret &= secp256k1_frost_share_load(ctx, &sk, share); + secp256k1_scalar_clear(&sk); + + sk_serialize_success = secp256k1_frost_share_serialize(ctx, sk_ser, share); + sk_ser_ptr = sk_ser; +#ifdef VERIFY + VERIFY_CHECK(sk_serialize_success); +#else + (void) sk_serialize_success; +#endif + } + + if (pk != NULL) { + if (!secp256k1_xonly_pubkey_serialize(ctx, pk_ser, pk)) { + return 0; + } + pk_ser_ptr = pk_ser; + } + secp256k1_nonce_function_frost(k, session_id32, msg32, sk_ser_ptr, pk_ser_ptr, extra_input32); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[0])); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[1])); + VERIFY_CHECK(!secp256k1_scalar_eq(&k[0], &k[1])); + secp256k1_frost_secnonce_save(secnonce, k); + secp256k1_frost_secnonce_invalidate(ctx, secnonce, !ret); + + for (i = 0; i < 2; i++) { + secp256k1_gej nonce_ptj; + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj, &k[i]); + secp256k1_ge_set_gej(&nonce_pt[i], &nonce_ptj); + secp256k1_declassify(ctx, &nonce_pt[i], sizeof(nonce_pt)); + secp256k1_scalar_clear(&k[i]); + } + /* nonce_pt won't be infinity because k != 0 with overwhelming probability */ + secp256k1_frost_pubnonce_save(pubnonce, nonce_pt); + return ret; +} + +#endif diff --git a/src/modules/musig/keyagg.h b/src/modules/musig/keyagg.h index 620522fe..1497659d 100644 --- a/src/modules/musig/keyagg.h +++ b/src/modules/musig/keyagg.h @@ -27,12 +27,6 @@ typedef struct { int parity_acc; } secp256k1_keyagg_cache_internal; -/* point_save_ext and point_load_ext are identical to point_save and point_load - * except that they allow saving and loading the point at infinity */ -static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge); - -static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data); - static int secp256k1_keyagg_cache_load(const secp256k1_context* ctx, secp256k1_keyagg_cache_internal *cache_i, const secp256k1_musig_keyagg_cache *cache); static void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_keyagg_cache_internal *cache_i, secp256k1_ge *pk); diff --git a/src/modules/musig/keyagg_impl.h b/src/modules/musig/keyagg_impl.h index aff95542..992fd9a3 100644 --- a/src/modules/musig/keyagg_impl.h +++ b/src/modules/musig/keyagg_impl.h @@ -17,23 +17,6 @@ #include "../../hash.h" #include "../../util.h" -static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) { - if (secp256k1_ge_is_infinity(ge)) { - memset(data, 0, 64); - } else { - secp256k1_ge_to_bytes(data, ge); - } -} - -static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) { - unsigned char zeros[64] = { 0 }; - if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) { - secp256k1_ge_set_infinity(ge); - } else { - secp256k1_ge_from_bytes(ge, data); - } -} - static const unsigned char secp256k1_musig_keyagg_cache_magic[4] = { 0xf4, 0xad, 0xbb, 0xdf }; /* A keyagg cache consists of