frost: nonce aggregation and adaptor signatures

This commit adds nonce aggregation, as well as adaptor signatures.
This commit is contained in:
Jesse Posner
2024-07-15 22:56:47 -07:00
parent 17c47e9708
commit 67c21beadd
7 changed files with 568 additions and 1 deletions

View File

@@ -4,3 +4,4 @@ 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
noinst_HEADERS += src/modules/frost/adaptor_impl.h

View File

@@ -0,0 +1,168 @@
/***********************************************************************
* Copyright (c) 2022-2024 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_FROST_ADAPTOR_IMPL_H
#define SECP256K1_MODULE_FROST_ADAPTOR_IMPL_H
#include <string.h>
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_frost.h"
#include "session.h"
#include "../../scalar.h"
int secp256k1_frost_nonce_parity(const secp256k1_context* ctx, int *nonce_parity, const secp256k1_frost_session *session) {
secp256k1_frost_session_internal session_i;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(nonce_parity != NULL);
ARG_CHECK(session != NULL);
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
return 0;
}
*nonce_parity = session_i.fin_nonce_parity;
return 1;
}
int secp256k1_frost_verify_adaptor(const secp256k1_context* ctx, const unsigned char *pre_sig64, const unsigned char *msg32, const secp256k1_xonly_pubkey *pubkey, const secp256k1_pubkey *adaptor, int nonce_parity) {
secp256k1_scalar s;
secp256k1_scalar e;
secp256k1_gej rj;
secp256k1_ge pk;
secp256k1_gej pkj;
secp256k1_ge r;
unsigned char buf[32];
int overflow;
secp256k1_ge adaptorp;
secp256k1_xonly_pubkey noncepk;
secp256k1_gej fin_nonce_ptj;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(pre_sig64 != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(pubkey != NULL);
ARG_CHECK(adaptor != NULL);
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
if (!secp256k1_xonly_pubkey_parse(ctx, &noncepk, &pre_sig64[0])) {
return 0;
}
if (!secp256k1_xonly_pubkey_load(ctx, &r, &noncepk)) {
return 0;
}
if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) {
return 0;
}
if (!nonce_parity) {
secp256k1_ge_neg(&adaptorp, &adaptorp);
}
secp256k1_gej_set_ge(&fin_nonce_ptj, &adaptorp);
secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &r, NULL);
if (secp256k1_gej_is_infinity(&fin_nonce_ptj)) {
/* unreachable with overwhelming probability */
return 0;
}
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
if (overflow) {
return 0;
}
if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
return 0;
}
/* Compute e. */
secp256k1_fe_get_b32(buf, &pk.x);
secp256k1_schnorrsig_challenge(&e, &pre_sig64[0], msg32, 32, buf);
/* Compute rj = s*G + (-e)*pkj */
secp256k1_scalar_negate(&e, &e);
secp256k1_gej_set_ge(&pkj, &pk);
secp256k1_ecmult(&rj, &pkj, &e, &s);
/* secp256k1_ge_set_gej_var(&r, &rj); */
if (secp256k1_gej_is_infinity(&rj)) {
return 0;
}
secp256k1_gej_neg(&rj, &rj);
secp256k1_gej_add_var(&rj, &rj, &fin_nonce_ptj, NULL);
return secp256k1_gej_is_infinity(&rj);
}
int secp256k1_frost_adapt(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *pre_sig64, const unsigned char *sec_adaptor32, int nonce_parity) {
secp256k1_scalar s;
secp256k1_scalar t;
int overflow;
int ret = 1;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(sig64 != NULL);
ARG_CHECK(pre_sig64 != NULL);
ARG_CHECK(sec_adaptor32 != NULL);
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
if (overflow) {
return 0;
}
secp256k1_scalar_set_b32(&t, sec_adaptor32, &overflow);
ret &= !overflow;
/* Determine if the secret adaptor should be negated.
*
* The frost_session stores the X-coordinate and the parity of the "final nonce"
* (r + t)*G, where r*G is the aggregate public nonce and t is the secret adaptor.
*
* Since a BIP340 signature requires an x-only public nonce, in the case where
* (r + t)*G has odd Y-coordinate (i.e. nonce_parity == 1), the x-only public nonce
* corresponding to the signature is actually (-r - t)*G. Thus adapting a
* pre-signature requires negating t in this case.
*/
if (nonce_parity) {
secp256k1_scalar_negate(&t, &t);
}
secp256k1_scalar_add(&s, &s, &t);
secp256k1_scalar_get_b32(&sig64[32], &s);
memmove(sig64, pre_sig64, 32);
secp256k1_scalar_clear(&t);
return ret;
}
int secp256k1_frost_extract_adaptor(const secp256k1_context* ctx, unsigned char *sec_adaptor32, const unsigned char *sig64, const unsigned char *pre_sig64, int nonce_parity) {
secp256k1_scalar t;
secp256k1_scalar s;
int overflow;
int ret = 1;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(sec_adaptor32 != NULL);
ARG_CHECK(sig64 != NULL);
ARG_CHECK(pre_sig64 != NULL);
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
secp256k1_scalar_set_b32(&t, &sig64[32], &overflow);
ret &= !overflow;
secp256k1_scalar_negate(&t, &t);
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
if (overflow) {
return 0;
}
secp256k1_scalar_add(&t, &t, &s);
if (!nonce_parity) {
secp256k1_scalar_negate(&t, &t);
}
secp256k1_scalar_get_b32(sec_adaptor32, &t);
secp256k1_scalar_clear(&t);
return ret;
}
#endif

View File

@@ -19,6 +19,10 @@ typedef struct {
int parity_acc;
} secp256k1_tweak_cache_internal;
static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache);
static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share);
static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33);
#endif

View File

@@ -9,5 +9,6 @@
#include "keygen_impl.h"
#include "session_impl.h"
#include "adaptor_impl.h"
#endif

View File

@@ -7,4 +7,19 @@
#ifndef SECP256K1_MODULE_FROST_SESSION_H
#define SECP256K1_MODULE_FROST_SESSION_H
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_frost.h"
#include "../../scalar.h"
typedef struct {
int fin_nonce_parity;
unsigned char fin_nonce[32];
secp256k1_scalar noncecoef;
secp256k1_scalar challenge;
secp256k1_scalar s_part;
} secp256k1_frost_session_internal;
static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_frost_session_internal *session_i, const secp256k1_frost_session *session);
#endif

View File

@@ -74,6 +74,49 @@ static int secp256k1_frost_pubnonce_load(const secp256k1_context* ctx, secp256k1
return 1;
}
static const unsigned char secp256k1_frost_session_cache_magic[4] = { 0x5c, 0x11, 0xa8, 0x3 };
/* A session consists of
* - 4 byte session cache magic
* - 1 byte the parity of the final nonce
* - 32 byte serialized x-only final nonce
* - 32 byte nonce coefficient b
* - 32 byte signature challenge hash e
* - 32 byte scalar s that is added to the partial signatures of the signers
*/
static void secp256k1_frost_session_save(secp256k1_frost_session *session, const secp256k1_frost_session_internal *session_i) {
unsigned char *ptr = session->data;
memcpy(ptr, secp256k1_frost_session_cache_magic, 4);
ptr += 4;
*ptr = session_i->fin_nonce_parity;
ptr += 1;
memcpy(ptr, session_i->fin_nonce, 32);
ptr += 32;
secp256k1_scalar_get_b32(ptr, &session_i->noncecoef);
ptr += 32;
secp256k1_scalar_get_b32(ptr, &session_i->challenge);
ptr += 32;
secp256k1_scalar_get_b32(ptr, &session_i->s_part);
}
static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_frost_session_internal *session_i, const secp256k1_frost_session *session) {
const unsigned char *ptr = session->data;
ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_session_cache_magic, 4) == 0);
ptr += 4;
session_i->fin_nonce_parity = *ptr;
ptr += 1;
memcpy(session_i->fin_nonce, ptr, 32);
ptr += 32;
secp256k1_scalar_set_b32(&session_i->noncecoef, ptr, NULL);
ptr += 32;
secp256k1_scalar_set_b32(&session_i->challenge, ptr, NULL);
ptr += 32;
secp256k1_scalar_set_b32(&session_i->s_part, ptr, NULL);
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;
@@ -230,4 +273,200 @@ int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secn
return ret;
}
static int secp256k1_frost_sum_nonces(const secp256k1_context* ctx, secp256k1_gej *summed_nonces, const secp256k1_frost_pubnonce * const *pubnonces, size_t n_pubnonces) {
size_t i;
int j;
secp256k1_gej_set_infinity(&summed_nonces[0]);
secp256k1_gej_set_infinity(&summed_nonces[1]);
for (i = 0; i < n_pubnonces; i++) {
secp256k1_ge nonce_pt[2];
if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonces[i])) {
return 0;
}
for (j = 0; j < 2; j++) {
secp256k1_gej_add_ge_var(&summed_nonces[j], &summed_nonces[j], &nonce_pt[j], NULL);
}
}
return 1;
}
/* TODO: consider updating to frost-08 to address maleability at the cost of performance */
/* See https://github.com/cfrg/draft-irtf-cfrg-frost/pull/217 */
static int secp256k1_frost_compute_noncehash(const secp256k1_context* ctx, unsigned char *noncehash, const unsigned char *msg, const secp256k1_frost_pubnonce * const *pubnonces, size_t n_pubnonces, const unsigned char *pk32, const unsigned char * const *ids33) {
unsigned char buf[66];
secp256k1_sha256 sha;
size_t i;
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/noncecoef", sizeof("FROST/noncecoef") - 1);
/* TODO: sort by index */
for (i = 0; i < n_pubnonces; i++) {
secp256k1_scalar idx;
if (!secp256k1_frost_compute_indexhash(&idx, ids33[i])) {
return 0;
}
secp256k1_scalar_get_b32(buf, &idx);
secp256k1_sha256_write(&sha, buf, 32);
if (!secp256k1_frost_pubnonce_serialize(ctx, buf, pubnonces[i])) {
return 0;
}
secp256k1_sha256_write(&sha, buf, sizeof(buf));
}
secp256k1_sha256_write(&sha, pk32, 32);
secp256k1_sha256_write(&sha, msg, 32);
secp256k1_sha256_finalize(&sha, noncehash);
return 1;
}
static int secp256k1_frost_nonce_process_internal(const secp256k1_context* ctx, int *fin_nonce_parity, unsigned char *fin_nonce, secp256k1_scalar *b, secp256k1_gej *aggnoncej, const unsigned char *msg, const secp256k1_frost_pubnonce * const *pubnonces, size_t n_pubnonces, const unsigned char *pk32, const unsigned char * const *ids33) {
unsigned char noncehash[32];
secp256k1_ge fin_nonce_pt;
secp256k1_gej fin_nonce_ptj;
secp256k1_ge aggnonce[2];
secp256k1_ge_set_gej(&aggnonce[0], &aggnoncej[0]);
secp256k1_ge_set_gej(&aggnonce[1], &aggnoncej[1]);
if (!secp256k1_frost_compute_noncehash(ctx, noncehash, msg, pubnonces, n_pubnonces, pk32, ids33)) {
return 0;
}
/* fin_nonce = aggnonce[0] + b*aggnonce[1] */
secp256k1_scalar_set_b32(b, noncehash, NULL);
secp256k1_ecmult(&fin_nonce_ptj, &aggnoncej[1], b, NULL);
secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &aggnonce[0], NULL);
secp256k1_ge_set_gej(&fin_nonce_pt, &fin_nonce_ptj);
if (secp256k1_ge_is_infinity(&fin_nonce_pt)) {
/* unreachable with overwhelming probability */
return 0;
}
secp256k1_fe_normalize_var(&fin_nonce_pt.x);
secp256k1_fe_get_b32(fin_nonce, &fin_nonce_pt.x);
secp256k1_fe_normalize_var(&fin_nonce_pt.y);
*fin_nonce_parity = secp256k1_fe_is_odd(&fin_nonce_pt.y);
return 1;
}
static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsigned char * const *ids33, size_t n_participants, const unsigned char *my_id33) {
size_t i;
secp256k1_scalar num;
secp256k1_scalar den;
secp256k1_scalar party_idx;
secp256k1_scalar_set_int(&num, 1);
secp256k1_scalar_set_int(&den, 1);
if (!secp256k1_frost_compute_indexhash(&party_idx, my_id33)) {
return 0;
}
for (i = 0; i < n_participants; i++) {
secp256k1_scalar mul;
if (!secp256k1_frost_compute_indexhash(&mul, ids33[i])) {
return 0;
}
if (secp256k1_scalar_eq(&mul, &party_idx)) {
continue;
}
secp256k1_scalar_negate(&mul, &mul);
secp256k1_scalar_mul(&num, &num, &mul);
secp256k1_scalar_add(&mul, &mul, &party_idx);
secp256k1_scalar_mul(&den, &den, &mul);
}
secp256k1_scalar_inverse_var(&den, &den);
secp256k1_scalar_mul(r, &num, &den);
return 1;
}
int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_session *session, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, const unsigned char *my_id33, const unsigned char * const *ids33, const secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_pubkey *adaptor) {
secp256k1_ge aggnonce_pt[2];
secp256k1_gej aggnonce_ptj[2];
unsigned char fin_nonce[32];
secp256k1_frost_session_internal session_i = { 0 };
unsigned char pk32[32];
size_t i;
secp256k1_scalar l;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(session != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(pubnonces != NULL);
ARG_CHECK(ids33 != NULL);
ARG_CHECK(my_id33 != NULL);
ARG_CHECK(pk != NULL);
ARG_CHECK(n_pubnonces > 1);
if (!secp256k1_xonly_pubkey_serialize(ctx, pk32, pk)) {
return 0;
}
if (!secp256k1_frost_sum_nonces(ctx, aggnonce_ptj, pubnonces, n_pubnonces)) {
return 0;
}
for (i = 0; i < 2; i++) {
if (secp256k1_gej_is_infinity(&aggnonce_ptj[i])) {
/* There must be at least one dishonest signer. If we would return 0
here, we will never be able to determine who it is. Therefore, we
should continue such that the culprit is revealed when collecting
and verifying partial signatures.
However, dealing with the point at infinity (loading,
de-/serializing) would require a lot of extra code complexity.
Instead, we set the aggregate nonce to some arbitrary point (the
generator). This is secure, because it only restricts the
abilities of the attacker: an attacker that forces the sum of
nonces to be infinity by sending some maliciously generated nonce
pairs can be turned into an attacker that forces the sum to be
the generator (by simply adding the generator to one of the
malicious nonces), and this does not change the winning condition
of the EUF-CMA game. */
aggnonce_pt[i] = secp256k1_ge_const_g;
} else {
secp256k1_ge_set_gej(&aggnonce_pt[i], &aggnonce_ptj[i]);
}
}
/* Add public adaptor to nonce */
if (adaptor != NULL) {
secp256k1_ge adaptorp;
if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) {
return 0;
}
secp256k1_gej_add_ge_var(&aggnonce_ptj[0], &aggnonce_ptj[0], &adaptorp, NULL);
}
if (!secp256k1_frost_nonce_process_internal(ctx, &session_i.fin_nonce_parity, fin_nonce, &session_i.noncecoef, aggnonce_ptj, msg32, pubnonces, n_pubnonces, pk32, ids33)) {
return 0;
}
secp256k1_schnorrsig_challenge(&session_i.challenge, fin_nonce, msg32, 32, pk32);
/* If there is a tweak then set `challenge` times `tweak` to the `s`-part.*/
secp256k1_scalar_set_int(&session_i.s_part, 0);
if (tweak_cache != NULL) {
secp256k1_tweak_cache_internal cache_i;
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
return 0;
}
if (!secp256k1_scalar_is_zero(&cache_i.tweak)) {
secp256k1_scalar e_tmp;
secp256k1_scalar_mul(&e_tmp, &session_i.challenge, &cache_i.tweak);
if (secp256k1_fe_is_odd(&cache_i.pk.y)) {
secp256k1_scalar_negate(&e_tmp, &e_tmp);
}
secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &e_tmp);
}
}
/* Update the challenge by multiplying the Lagrange coefficient to prepare
* for signing. */
if (!secp256k1_frost_lagrange_coefficient(&l, ids33, n_pubnonces, my_id33)) {
return 0;
}
secp256k1_scalar_mul(&session_i.challenge, &session_i.challenge, &l);
memcpy(session_i.fin_nonce, fin_nonce, sizeof(session_i.fin_nonce));
secp256k1_frost_session_save(session, &session_i);
return 1;
}
#endif