frost: nonce generation

This commits adds nonce generation, as well as serialization and
parsing.
This commit is contained in:
Jesse Posner 2024-07-15 17:54:31 -07:00
parent 197fb7efb9
commit f606507120
No known key found for this signature in database
GPG Key ID: 49A08EAB3A812D69
10 changed files with 382 additions and 23 deletions

View File

@ -15,6 +15,9 @@ extern "C" {
* This module implements a variant of Flexible Round-Optimized Schnorr * This module implements a variant of Flexible Round-Optimized Schnorr
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg * Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
* (https://crysp.uwaterloo.ca/software/frost/). * (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 /** Opaque data structures
@ -34,6 +37,61 @@ typedef struct {
unsigned char data[36]; unsigned char data[36];
} secp256k1_frost_share; } 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 /** Serialize a FROST share
* *
* Returns: 1 when the share could be serialized, 0 otherwise * 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 size_t n_participants
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); ) 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -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 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). */ /** Check invariants on an affine group element (no-op unless VERIFY is enabled). */
static void secp256k1_ge_verify(const secp256k1_ge *a); static void secp256k1_ge_verify(const secp256k1_ge *a);
#define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a) #define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a)

View File

@ -914,6 +914,23 @@ static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) {
return secp256k1_fe_is_square_var(&yz); 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) { static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) {
#ifdef EXHAUSTIVE_TEST_ORDER #ifdef EXHAUSTIVE_TEST_ORDER
secp256k1_gej out; secp256k1_gej out;

View File

@ -2,3 +2,5 @@ include_HEADERS += include/secp256k1_frost.h
noinst_HEADERS += src/modules/frost/main_impl.h noinst_HEADERS += src/modules/frost/main_impl.h
noinst_HEADERS += src/modules/frost/keygen.h noinst_HEADERS += src/modules/frost/keygen.h
noinst_HEADERS += src/modules/frost/keygen_impl.h noinst_HEADERS += src/modules/frost/keygen_impl.h
noinst_HEADERS += src/modules/frost/session.h
noinst_HEADERS += src/modules/frost/session_impl.h

View File

@ -10,4 +10,8 @@
#include "../../../include/secp256k1.h" #include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_frost.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 #endif

View File

@ -8,5 +8,6 @@
#define SECP256K1_MODULE_FROST_MAIN #define SECP256K1_MODULE_FROST_MAIN
#include "keygen_impl.h" #include "keygen_impl.h"
#include "session_impl.h"
#endif #endif

View File

@ -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

View File

@ -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 <string.h>
#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

View File

@ -27,12 +27,6 @@ typedef struct {
int parity_acc; int parity_acc;
} secp256k1_keyagg_cache_internal; } 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 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); static void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_keyagg_cache_internal *cache_i, secp256k1_ge *pk);

View File

@ -17,23 +17,6 @@
#include "../../hash.h" #include "../../hash.h"
#include "../../util.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 }; static const unsigned char secp256k1_musig_keyagg_cache_magic[4] = { 0xf4, 0xad, 0xbb, 0xdf };
/* A keyagg cache consists of /* A keyagg cache consists of