Skip to content

Commit ab1bb83

Browse files
committed
frost trusted dealer: signature generation and aggregation
This commit adds signature generation and aggregation, as well as partial signature serialization and parsing.
1 parent af4dfaf commit ab1bb83

File tree

2 files changed

+322
-1
lines changed

2 files changed

+322
-1
lines changed

include/secp256k1_frost.h

+125-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ extern "C" {
1414
*
1515
* This module implements a variant of Flexible Round-Optimized Schnorr
1616
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
17-
* (https://crysp.uwaterloo.ca/software/frost/).
17+
* (https://crysp.uwaterloo.ca/software/frost/). Signatures are compatible with
18+
* BIP-340 ("Schnorr").
1819
*
1920
* The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public
2021
* key tweaking, and adaptor signatures.
@@ -88,6 +89,15 @@ typedef struct {
8889
unsigned char data[133];
8990
} secp256k1_frost_session;
9091

92+
/** Opaque data structure that holds a partial FROST signature.
93+
*
94+
* Guaranteed to be 36 bytes in size. Serialized and parsed with
95+
* `frost_partial_sig_serialize` and `frost_partial_sig_parse`.
96+
*/
97+
typedef struct {
98+
unsigned char data[36];
99+
} secp256k1_frost_partial_sig;
100+
91101
/** Parse a signer's public nonce.
92102
*
93103
* Returns: 1 when the nonce could be parsed, 0 otherwise.
@@ -114,6 +124,36 @@ SECP256K1_API int secp256k1_frost_pubnonce_serialize(
114124
const secp256k1_frost_pubnonce *nonce
115125
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
116126

127+
/** Serialize a FROST partial signature
128+
*
129+
* Returns: 1 when the signature could be serialized, 0 otherwise
130+
* Args: ctx: pointer to a context object
131+
* Out: out32: pointer to a 32-byte array to store the serialized signature
132+
* In: sig: pointer to the signature
133+
*/
134+
SECP256K1_API int secp256k1_frost_partial_sig_serialize(
135+
const secp256k1_context *ctx,
136+
unsigned char *out32,
137+
const secp256k1_frost_partial_sig *sig
138+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
139+
140+
/** Parse a FROST partial signature.
141+
*
142+
* Returns: 1 when the signature could be parsed, 0 otherwise.
143+
* Args: ctx: pointer to a context object
144+
* Out: sig: pointer to a signature object
145+
* In: in32: pointer to the 32-byte signature to be parsed
146+
*
147+
* After the call, sig will always be initialized. If parsing failed or the
148+
* encoded numbers are out of range, signature verification with it is
149+
* guaranteed to fail for every message and public key.
150+
*/
151+
SECP256K1_API int secp256k1_frost_partial_sig_parse(
152+
const secp256k1_context *ctx,
153+
secp256k1_frost_partial_sig *sig,
154+
const unsigned char *in32
155+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
156+
117157
/** Serialize a FROST share
118158
*
119159
* Returns: 1 when the share could be serialized, 0 otherwise
@@ -428,6 +468,90 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process(
428468
const secp256k1_pubkey *adaptor
429469
) 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);
430470

471+
/** Produces a partial signature
472+
*
473+
* This function overwrites the given secnonce with zeros and will abort if given a
474+
* secnonce that is all zeros. This is a best effort attempt to protect against nonce
475+
* reuse. However, this is of course easily defeated if the secnonce has been
476+
* copied (or serialized). Remember that nonce reuse will leak the secret share!
477+
*
478+
* Returns: 0 if the arguments are invalid or the provided secnonce has already
479+
* been used for signing, 1 otherwise
480+
* Args: ctx: pointer to a context object
481+
* Out: partial_sig: pointer to struct to store the partial signature
482+
* In/Out: secnonce: pointer to the secnonce struct created in
483+
* frost_nonce_gen that has been never used in a
484+
* partial_sign call before
485+
* In: agg_share: the aggregated share
486+
* session: pointer to the session that was created with
487+
* frost_nonce_process
488+
* keygen_cache: pointer to frost_keygen_cache struct
489+
*/
490+
SECP256K1_API int secp256k1_frost_partial_sign(
491+
const secp256k1_context *ctx,
492+
secp256k1_frost_partial_sig *partial_sig,
493+
secp256k1_frost_secnonce *secnonce,
494+
const secp256k1_frost_share *agg_share,
495+
const secp256k1_frost_session *session,
496+
const secp256k1_frost_keygen_cache *keygen_cache
497+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
498+
499+
/** Verifies an individual signer's partial signature
500+
*
501+
* The signature is verified for a specific signing session. In order to avoid
502+
* accidentally verifying a signature from a different or non-existing signing
503+
* session, you must ensure the following:
504+
* 1. The `keygen_cache` argument is identical to the one used to create the
505+
* `session` with `frost_nonce_process`.
506+
* 2. The `pubshare` argument must be the output of
507+
* `secp256k1_frost_compute_pubshare` for the signer's ID.
508+
* 3. The `pubnonce` argument must be identical to the one sent by the
509+
* signer and used to create the `session` with `frost_nonce_process`.
510+
*
511+
* This function can be used to assign blame for a failed signature.
512+
*
513+
* Returns: 0 if the arguments are invalid or the partial signature does not
514+
* verify, 1 otherwise
515+
* Args ctx: pointer to a context object
516+
* In: partial_sig: pointer to partial signature to verify, sent by
517+
* the signer associated with `pubnonce` and `pubkey`
518+
* pubnonce: public nonce of the signer in the signing session
519+
* pubshare: public verification share of the signer in the signing
520+
* session that is the output of
521+
* `secp256k1_frost_compute_pubshare`
522+
* session: pointer to the session that was created with
523+
* `frost_nonce_process`
524+
* keygen_cache: pointer to frost_keygen_cache struct
525+
*/
526+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_verify(
527+
const secp256k1_context *ctx,
528+
const secp256k1_frost_partial_sig *partial_sig,
529+
const secp256k1_frost_pubnonce *pubnonce,
530+
const secp256k1_pubkey *pubshare,
531+
const secp256k1_frost_session *session,
532+
const secp256k1_frost_keygen_cache *keygen_cache
533+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
534+
535+
/** Aggregates partial signatures
536+
*
537+
* Returns: 0 if the arguments are invalid, 1 otherwise (which does NOT mean
538+
* the resulting signature verifies).
539+
* Args: ctx: pointer to a context object
540+
* Out: sig64: complete (but possibly invalid) Schnorr signature
541+
* In: session: pointer to the session that was created with
542+
* frost_nonce_process
543+
* partial_sigs: array of pointers to partial signatures to aggregate
544+
* n_sigs: number of elements in the partial_sigs array. Must be
545+
* greater than 0.
546+
*/
547+
SECP256K1_API int secp256k1_frost_partial_sig_agg(
548+
const secp256k1_context *ctx,
549+
unsigned char *sig64,
550+
const secp256k1_frost_session *session,
551+
const secp256k1_frost_partial_sig * const *partial_sigs,
552+
size_t n_sigs
553+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
554+
431555
/** Extracts the nonce_parity bit from a session
432556
*
433557
* This is used for adaptor signatures.

src/modules/frost/session_impl.h

+197
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,23 @@ static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_
117117
return 1;
118118
}
119119

120+
static const unsigned char secp256k1_frost_partial_sig_magic[4] = { 0x8d, 0xd8, 0x31, 0x6e };
121+
122+
static void secp256k1_frost_partial_sig_save(secp256k1_frost_partial_sig* sig, secp256k1_scalar *s) {
123+
memcpy(&sig->data[0], secp256k1_frost_partial_sig_magic, 4);
124+
secp256k1_scalar_get_b32(&sig->data[4], s);
125+
}
126+
127+
static int secp256k1_frost_partial_sig_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_partial_sig* sig) {
128+
int overflow;
129+
130+
ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_frost_partial_sig_magic, 4) == 0);
131+
secp256k1_scalar_set_b32(s, &sig->data[4], &overflow);
132+
/* Parsed signatures can not overflow */
133+
VERIFY_CHECK(!overflow);
134+
return 1;
135+
}
136+
120137
int secp256k1_frost_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_frost_pubnonce* nonce) {
121138
secp256k1_ge ge[2];
122139
int i;
@@ -163,6 +180,29 @@ int secp256k1_frost_pubnonce_parse(const secp256k1_context* ctx, secp256k1_frost
163180
return 1;
164181
}
165182

183+
int secp256k1_frost_partial_sig_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_partial_sig* sig) {
184+
VERIFY_CHECK(ctx != NULL);
185+
ARG_CHECK(out32 != NULL);
186+
ARG_CHECK(sig != NULL);
187+
memcpy(out32, &sig->data[4], 32);
188+
return 1;
189+
}
190+
191+
int secp256k1_frost_partial_sig_parse(const secp256k1_context* ctx, secp256k1_frost_partial_sig* sig, const unsigned char *in32) {
192+
secp256k1_scalar tmp;
193+
int overflow;
194+
VERIFY_CHECK(ctx != NULL);
195+
ARG_CHECK(sig != NULL);
196+
ARG_CHECK(in32 != NULL);
197+
198+
secp256k1_scalar_set_b32(&tmp, in32, &overflow);
199+
if (overflow) {
200+
return 0;
201+
}
202+
secp256k1_frost_partial_sig_save(sig, &tmp);
203+
return 1;
204+
}
205+
166206
/* Write optional inputs into the hash */
167207
static void secp256k1_nonce_function_frost_helper(secp256k1_sha256 *sha, unsigned int prefix_size, const unsigned char *data32) {
168208
/* The spec requires length prefix to be 4 bytes for `extra_in`, 1 byte
@@ -424,4 +464,161 @@ int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_
424464
return 1;
425465
}
426466

467+
void secp256k1_frost_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) {
468+
secp256k1_scalar_clear(sk);
469+
secp256k1_scalar_clear(&k[0]);
470+
secp256k1_scalar_clear(&k[1]);
471+
}
472+
473+
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_keygen_cache *keygen_cache) {
474+
secp256k1_scalar sk;
475+
secp256k1_scalar k[2];
476+
secp256k1_scalar s;
477+
secp256k1_keygen_cache_internal cache_i;
478+
secp256k1_frost_session_internal session_i;
479+
int ret;
480+
481+
VERIFY_CHECK(ctx != NULL);
482+
483+
ARG_CHECK(secnonce != NULL);
484+
/* Fails if the magic doesn't match */
485+
ret = secp256k1_frost_secnonce_load(ctx, k, secnonce);
486+
/* Set nonce to zero to avoid nonce reuse. This will cause subsequent calls
487+
* of this function to fail */
488+
memset(secnonce, 0, sizeof(*secnonce));
489+
if (!ret) {
490+
secp256k1_frost_partial_sign_clear(&sk, k);
491+
return 0;
492+
}
493+
494+
ARG_CHECK(partial_sig != NULL);
495+
ARG_CHECK(share != NULL);
496+
ARG_CHECK(keygen_cache != NULL);
497+
ARG_CHECK(session != NULL);
498+
499+
if (!secp256k1_frost_share_load(ctx, &sk, share)) {
500+
secp256k1_frost_partial_sign_clear(&sk, k);
501+
return 0;
502+
}
503+
if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
504+
secp256k1_frost_partial_sign_clear(&sk, k);
505+
return 0;
506+
}
507+
508+
/* Negate sk if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc.
509+
* This corresponds to the line "Let d = g⋅gacc⋅d' mod n" in the
510+
* specification. */
511+
if ((secp256k1_fe_is_odd(&cache_i.pk.y)
512+
!= cache_i.parity_acc)) {
513+
secp256k1_scalar_negate(&sk, &sk);
514+
}
515+
516+
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
517+
secp256k1_frost_partial_sign_clear(&sk, k);
518+
return 0;
519+
}
520+
521+
if (session_i.fin_nonce_parity) {
522+
secp256k1_scalar_negate(&k[0], &k[0]);
523+
secp256k1_scalar_negate(&k[1], &k[1]);
524+
}
525+
526+
/* Sign */
527+
secp256k1_scalar_mul(&s, &session_i.challenge, &sk);
528+
secp256k1_scalar_mul(&k[1], &session_i.noncecoef, &k[1]);
529+
secp256k1_scalar_add(&k[0], &k[0], &k[1]);
530+
secp256k1_scalar_add(&s, &s, &k[0]);
531+
secp256k1_frost_partial_sig_save(partial_sig, &s);
532+
secp256k1_frost_partial_sign_clear(&sk, k);
533+
return 1;
534+
}
535+
536+
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_keygen_cache *keygen_cache) {
537+
secp256k1_keygen_cache_internal cache_i;
538+
secp256k1_frost_session_internal session_i;
539+
secp256k1_scalar e, s;
540+
secp256k1_gej pkj;
541+
secp256k1_ge nonce_pt[2];
542+
secp256k1_gej rj;
543+
secp256k1_gej tmp;
544+
secp256k1_ge pkp;
545+
546+
VERIFY_CHECK(ctx != NULL);
547+
ARG_CHECK(partial_sig != NULL);
548+
ARG_CHECK(pubnonce != NULL);
549+
ARG_CHECK(pubshare != NULL);
550+
ARG_CHECK(keygen_cache != NULL);
551+
ARG_CHECK(session != NULL);
552+
553+
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
554+
return 0;
555+
}
556+
557+
/* Compute "effective" nonce rj = aggnonce[0] + b*aggnonce[1] */
558+
/* TODO: use multiexp to compute -s*G + e*pubshare + aggnonce[0] + b*aggnonce[1] */
559+
if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonce)) {
560+
return 0;
561+
}
562+
secp256k1_gej_set_ge(&rj, &nonce_pt[1]);
563+
secp256k1_ecmult(&rj, &rj, &session_i.noncecoef, NULL);
564+
secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pt[0], NULL);
565+
566+
if (!secp256k1_pubkey_load(ctx, &pkp, pubshare)) {
567+
return 0;
568+
}
569+
if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
570+
return 0;
571+
}
572+
573+
secp256k1_scalar_set_int(&e, 1);
574+
/* Negate e if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc.
575+
* This corresponds to the line "Let g' = g⋅gacc mod n" and the multiplication "g'⋅e"
576+
* in the specification. */
577+
if (secp256k1_fe_is_odd(&cache_i.pk.y)
578+
!= cache_i.parity_acc) {
579+
secp256k1_scalar_negate(&e, &e);
580+
}
581+
secp256k1_scalar_mul(&e, &e, &session_i.challenge);
582+
583+
if (!secp256k1_frost_partial_sig_load(ctx, &s, partial_sig)) {
584+
return 0;
585+
}
586+
587+
/* Compute -s*G + e*pkj + rj (e already includes the lagrange coefficient l) */
588+
secp256k1_scalar_negate(&s, &s);
589+
secp256k1_gej_set_ge(&pkj, &pkp);
590+
secp256k1_ecmult(&tmp, &pkj, &e, &s);
591+
if (session_i.fin_nonce_parity) {
592+
secp256k1_gej_neg(&rj, &rj);
593+
}
594+
secp256k1_gej_add_var(&tmp, &tmp, &rj, NULL);
595+
596+
return secp256k1_gej_is_infinity(&tmp);
597+
}
598+
599+
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) {
600+
size_t i;
601+
secp256k1_frost_session_internal session_i;
602+
603+
VERIFY_CHECK(ctx != NULL);
604+
ARG_CHECK(sig64 != NULL);
605+
ARG_CHECK(session != NULL);
606+
ARG_CHECK(partial_sigs != NULL);
607+
ARG_CHECK(n_sigs > 0);
608+
609+
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
610+
return 0;
611+
}
612+
for (i = 0; i < n_sigs; i++) {
613+
secp256k1_scalar term;
614+
if (!secp256k1_frost_partial_sig_load(ctx, &term, partial_sigs[i])) {
615+
return 0;
616+
}
617+
secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &term);
618+
}
619+
secp256k1_scalar_get_b32(&sig64[32], &session_i.s_part);
620+
memcpy(&sig64[0], session_i.fin_nonce, 32);
621+
return 1;
622+
}
623+
427624
#endif

0 commit comments

Comments
 (0)