From ef15156ffb37a3cc27758b0f9206827c0669e340 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 15 Jul 2024 23:01:42 -0700 Subject: [PATCH] frost: signature generation and aggregation This commit adds signature generation and aggregation, as well as partial signature serialization and parsing. --- include/secp256k1_frost.h | 126 +++++++++++++++++++- src/modules/frost/session_impl.h | 190 +++++++++++++++++++++++++++++++ 2 files changed, 315 insertions(+), 1 deletion(-) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index fca711ac..88bb06f2 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -14,7 +14,8 @@ 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/). + * (https://crysp.uwaterloo.ca/software/frost/). Signatures are compatible with + * BIP-340 ("Schnorr"). * * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public * key tweaking, and adaptor signatures. @@ -88,6 +89,15 @@ typedef struct { unsigned char data[133]; } secp256k1_frost_session; +/** Opaque data structure that holds a partial FROST signature. + * + * Guaranteed to be 36 bytes in size. Serialized and parsed with + * `frost_partial_sig_serialize` and `frost_partial_sig_parse`. + */ +typedef struct { + unsigned char data[36]; +} secp256k1_frost_partial_sig; + /** Parse a signer's public nonce. * * Returns: 1 when the nonce could be parsed, 0 otherwise. @@ -114,6 +124,36 @@ SECP256K1_API int secp256k1_frost_pubnonce_serialize( const secp256k1_frost_pubnonce *nonce ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +/** Serialize a FROST partial signature + * + * Returns: 1 when the signature could be serialized, 0 otherwise + * Args: ctx: pointer to a context object + * Out: out32: pointer to a 32-byte array to store the serialized signature + * In: sig: pointer to the signature + */ +SECP256K1_API int secp256k1_frost_partial_sig_serialize( + const secp256k1_context *ctx, + unsigned char *out32, + const secp256k1_frost_partial_sig *sig +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse a FROST partial signature. + * + * Returns: 1 when the signature could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: sig: pointer to a signature object + * In: in32: pointer to the 32-byte signature to be parsed + * + * After the call, sig will always be initialized. If parsing failed or the + * encoded numbers are out of range, signature verification with it is + * guaranteed to fail for every message and public key. + */ +SECP256K1_API int secp256k1_frost_partial_sig_parse( + const secp256k1_context *ctx, + secp256k1_frost_partial_sig *sig, + const unsigned char *in32 +) 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 @@ -467,6 +507,90 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process( const secp256k1_pubkey *adaptor ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8); +/** Produces a partial signature + * + * This function overwrites the given secnonce with zeros and will abort if given a + * secnonce that is all zeros. This is a best effort attempt to protect against nonce + * reuse. However, this is of course easily defeated if the secnonce has been + * copied (or serialized). Remember that nonce reuse will leak the secret key! + * + * Returns: 0 if the arguments are invalid or the provided secnonce has already + * been used for signing, 1 otherwise + * Args: ctx: pointer to a context object + * Out: partial_sig: pointer to struct to store the partial signature + * In/Out: secnonce: pointer to the secnonce struct created in + * frost_nonce_gen that has been never used in a + * partial_sign call before + * In: agg_share: the aggregated share + * session: pointer to the session that was created with + * frost_nonce_process + * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + */ +SECP256K1_API int secp256k1_frost_partial_sign( + const secp256k1_context *ctx, + secp256k1_frost_partial_sig *partial_sig, + secp256k1_frost_secnonce *secnonce, + const secp256k1_frost_share *agg_share, + const secp256k1_frost_session *session, + const secp256k1_frost_tweak_cache *tweak_cache +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Verifies an individual signer's partial signature + * + * The signature is verified for a specific signing session. In order to avoid + * accidentally verifying a signature from a different or non-existing signing + * session, you must ensure the following: + * 1. The `tweak_cache` argument is identical to the one used to create the + * `session` with `frost_nonce_process`. + * 2. The `pubshare` argument must be the output of + * `secp256k1_frost_compute_pubshare` for the signer's ID. + * 3. The `pubnonce` argument must be identical to the one sent by the + * signer and used to create the `session` with `frost_nonce_process`. + * + * This function can be used to assign blame for a failed signature. + * + * Returns: 0 if the arguments are invalid or the partial signature does not + * verify, 1 otherwise + * Args ctx: pointer to a context object + * In: partial_sig: pointer to partial signature to verify, sent by + * the signer associated with `pubnonce` and `pubkey` + * pubnonce: public nonce of the signer in the signing session + * pubshare: public verification share of the signer in the signing + * session that is the output of + * `secp256k1_frost_compute_pubshare` + * session: pointer to the session that was created with + * `frost_nonce_process` + * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_verify( + const secp256k1_context *ctx, + const secp256k1_frost_partial_sig *partial_sig, + const secp256k1_frost_pubnonce *pubnonce, + const secp256k1_pubkey *pubshare, + const secp256k1_frost_session *session, + const secp256k1_frost_tweak_cache *tweak_cache +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Aggregates partial signatures + * + * 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: sig64: complete (but possibly invalid) Schnorr signature + * In: session: pointer to the session that was created with + * frost_nonce_process + * partial_sigs: array of pointers to partial signatures to aggregate + * n_sigs: number of elements in the partial_sigs array. Must be + * greater than 0. + */ +SECP256K1_API int secp256k1_frost_partial_sig_agg( + const secp256k1_context *ctx, + unsigned char *sig64, + const secp256k1_frost_session *session, + const secp256k1_frost_partial_sig * const *partial_sigs, + size_t n_sigs +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + /** Extracts the nonce_parity bit from a session * * This is used for adaptor signatures. diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index 84ad0218..bba1f709 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -117,6 +117,23 @@ static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_ return 1; } +static const unsigned char secp256k1_frost_partial_sig_magic[4] = { 0x8d, 0xd8, 0x31, 0x6e }; + +static void secp256k1_frost_partial_sig_save(secp256k1_frost_partial_sig* sig, secp256k1_scalar *s) { + memcpy(&sig->data[0], secp256k1_frost_partial_sig_magic, 4); + secp256k1_scalar_get_b32(&sig->data[4], s); +} + +static int secp256k1_frost_partial_sig_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_partial_sig* sig) { + int overflow; + + ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_frost_partial_sig_magic, 4) == 0); + secp256k1_scalar_set_b32(s, &sig->data[4], &overflow); + /* Parsed signatures can not overflow */ + VERIFY_CHECK(!overflow); + 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; @@ -163,6 +180,29 @@ int secp256k1_frost_pubnonce_parse(const secp256k1_context* ctx, secp256k1_frost return 1; } +int secp256k1_frost_partial_sig_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_partial_sig* sig) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out32 != NULL); + ARG_CHECK(sig != NULL); + memcpy(out32, &sig->data[4], 32); + return 1; +} + +int secp256k1_frost_partial_sig_parse(const secp256k1_context* ctx, secp256k1_frost_partial_sig* sig, const unsigned char *in32) { + secp256k1_scalar tmp; + int overflow; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(in32 != NULL); + + secp256k1_scalar_set_b32(&tmp, in32, &overflow); + if (overflow) { + return 0; + } + secp256k1_frost_partial_sig_save(sig, &tmp); + 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]; @@ -469,4 +509,154 @@ int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_ return 1; } +void secp256k1_frost_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) { + secp256k1_scalar_clear(sk); + secp256k1_scalar_clear(&k[0]); + secp256k1_scalar_clear(&k[1]); +} + +int secp256k1_frost_partial_sign(const secp256k1_context* ctx, secp256k1_frost_partial_sig *partial_sig, secp256k1_frost_secnonce *secnonce, const secp256k1_frost_share *share, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) { + secp256k1_scalar sk; + secp256k1_scalar k[2]; + secp256k1_scalar s; + secp256k1_frost_session_internal session_i; + int ret; + + VERIFY_CHECK(ctx != NULL); + + ARG_CHECK(secnonce != NULL); + /* Fails if the magic doesn't match */ + ret = secp256k1_frost_secnonce_load(ctx, k, secnonce); + /* Set nonce to zero to avoid nonce reuse. This will cause subsequent calls + * of this function to fail */ + memset(secnonce, 0, sizeof(*secnonce)); + if (!ret) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(share != NULL); + ARG_CHECK(session != NULL); + + if (!secp256k1_frost_share_load(ctx, &sk, share)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + if (!secp256k1_frost_session_load(ctx, &session_i, session)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + + if (tweak_cache != NULL) { + secp256k1_tweak_cache_internal cache_i; + if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + if (secp256k1_fe_is_odd(&cache_i.pk.y) != cache_i.parity_acc) { + secp256k1_scalar_negate(&sk, &sk); + } + } + + if (session_i.fin_nonce_parity) { + secp256k1_scalar_negate(&k[0], &k[0]); + secp256k1_scalar_negate(&k[1], &k[1]); + } + + /* Sign */ + secp256k1_scalar_mul(&s, &session_i.challenge, &sk); + secp256k1_scalar_mul(&k[1], &session_i.noncecoef, &k[1]); + secp256k1_scalar_add(&k[0], &k[0], &k[1]); + secp256k1_scalar_add(&s, &s, &k[0]); + secp256k1_frost_partial_sig_save(partial_sig, &s); + secp256k1_frost_partial_sign_clear(&sk, k); + return 1; +} + +int secp256k1_frost_partial_sig_verify(const secp256k1_context* ctx, const secp256k1_frost_partial_sig *partial_sig, const secp256k1_frost_pubnonce *pubnonce, const secp256k1_pubkey *pubshare, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) { + secp256k1_frost_session_internal session_i; + secp256k1_scalar e, s; + secp256k1_gej pkj; + secp256k1_ge nonce_pt[2]; + secp256k1_gej rj; + secp256k1_gej tmp; + secp256k1_ge pkp; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(pubnonce != NULL); + ARG_CHECK(pubshare != NULL); + ARG_CHECK(session != NULL); + + if (!secp256k1_frost_session_load(ctx, &session_i, session)) { + return 0; + } + + /* Compute "effective" nonce rj = aggnonce[0] + b*aggnonce[1] */ + /* TODO: use multiexp to compute -s*G + e*pubshare + aggnonce[0] + b*aggnonce[1] */ + if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonce)) { + return 0; + } + secp256k1_gej_set_ge(&rj, &nonce_pt[1]); + secp256k1_ecmult(&rj, &rj, &session_i.noncecoef, NULL); + secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pt[0], NULL); + + if (!secp256k1_pubkey_load(ctx, &pkp, pubshare)) { + return 0; + } + + secp256k1_scalar_set_int(&e, 1); + if (tweak_cache != NULL) { + secp256k1_tweak_cache_internal cache_i; + if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { + return 0; + } + if (secp256k1_fe_is_odd(&cache_i.pk.y) + != cache_i.parity_acc) { + secp256k1_scalar_negate(&e, &e); + } + } + secp256k1_scalar_mul(&e, &e, &session_i.challenge); + + if (!secp256k1_frost_partial_sig_load(ctx, &s, partial_sig)) { + return 0; + } + /* Compute -s*G + e*pkj + rj (e already includes the lagrange coefficient l) */ + secp256k1_scalar_negate(&s, &s); + secp256k1_gej_set_ge(&pkj, &pkp); + secp256k1_ecmult(&tmp, &pkj, &e, &s); + if (session_i.fin_nonce_parity) { + secp256k1_gej_neg(&rj, &rj); + } + secp256k1_gej_add_var(&tmp, &tmp, &rj, NULL); + + return secp256k1_gej_is_infinity(&tmp); +} + +int secp256k1_frost_partial_sig_agg(const secp256k1_context* ctx, unsigned char *sig64, const secp256k1_frost_session *session, const secp256k1_frost_partial_sig * const* partial_sigs, size_t n_sigs) { + size_t i; + secp256k1_frost_session_internal session_i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(partial_sigs != NULL); + ARG_CHECK(n_sigs > 0); + + if (!secp256k1_frost_session_load(ctx, &session_i, session)) { + return 0; + } + for (i = 0; i < n_sigs; i++) { + secp256k1_scalar term; + if (!secp256k1_frost_partial_sig_load(ctx, &term, partial_sigs[i])) { + return 0; + } + secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &term); + } + secp256k1_scalar_get_b32(&sig64[32], &session_i.s_part); + memcpy(&sig64[0], session_i.fin_nonce, 32); + return 1; +} + #endif