From 6216645b3832717366ee83a146c7efd509a1d124 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Thu, 5 Sep 2024 14:36:04 -0700 Subject: [PATCH 01/18] frost trusted dealer: initialize project This commit adds the foundational configuration and building scripts and an initial structure for the project. --- Makefile.am | 4 ++++ README.md | 1 + configure.ac | 18 ++++++++++++++++++ include/secp256k1_frost.h | 22 ++++++++++++++++++++++ src/modules/frost/Makefile.am.include | 2 ++ src/modules/frost/main_impl.h | 10 ++++++++++ src/secp256k1.c | 4 ++++ 7 files changed, 61 insertions(+) create mode 100644 include/secp256k1_frost.h create mode 100644 src/modules/frost/Makefile.am.include create mode 100644 src/modules/frost/main_impl.h diff --git a/Makefile.am b/Makefile.am index 329f86ca8..ce92c94f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -320,3 +320,7 @@ endif if ENABLE_MODULE_ECDSA_ADAPTOR include src/modules/ecdsa_adaptor/Makefile.am.include endif + +if ENABLE_MODULE_FROST +include src/modules/frost/Makefile.am.include +endif diff --git a/README.md b/README.md index 88bdb2ba4..76bc0e10c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Added features: * Experimental module for Confidential Assets (Pedersen commitments, range proofs, and [surjection proofs](src/modules/surjection/surjection.md)). * Experimental module for Bulletproofs++ range proofs. * Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md). +* Experimental module for FROST. Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable. diff --git a/configure.ac b/configure.ac index 4d2a6e67e..21923df96 100644 --- a/configure.ac +++ b/configure.ac @@ -240,6 +240,11 @@ AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) +AC_ARG_ENABLE(module_frost, + AS_HELP_STRING([--enable-module-frost],[enable FROST module (experimental)]), + [], + [SECP_SET_DEFAULT([enable_module_frost], [no], [yes])]) + # Test-only override of the (autodetected by the C code) "widemul" setting. # Legal values are: # * int64 (for [u]int64_t), @@ -530,6 +535,14 @@ if test x"$enable_module_ecdh" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1" fi +if test x"$enable_module_frost" = x"yes"; then + if test x"$enable_module_schnorrsig" = x"no"; then + AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the frost module.]) + fi + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_FROST=1" + enable_module_schnorrsig=yes +fi + if test x"$enable_external_default_callbacks" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1" fi @@ -582,6 +595,9 @@ else if test x"$set_asm" = x"arm32"; then AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.]) fi + if test x"$enable_module_frost" = x"yes"; then + AC_MSG_ERROR([FROST module is experimental. Use --enable-experimental to allow.]) + fi fi ### @@ -611,6 +627,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDSA_S2C], [test x"$enable_module_ecdsa_s2c" = x" AM_CONDITIONAL([ENABLE_MODULE_ECDSA_ADAPTOR], [test x"$enable_module_ecdsa_adaptor" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_BPPP], [test x"$enable_module_bppp" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG_HALFAGG], [test x"$enable_module_schnorrsig_halfagg" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_FROST], [test x"$enable_module_frost" = x"yes"]) AM_CONDITIONAL([USE_REDUCED_SURJECTION_PROOF_SIZE], [test x"$use_reduced_surjection_proof_size" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) @@ -651,6 +668,7 @@ echo " module ecdsa-s2c = $enable_module_ecdsa_s2c" echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor" echo " module bppp = $enable_module_bppp" echo " module schnorrsig-halfagg = $enable_module_schnorrsig_halfagg" +echo " module frost = $enable_module_frost" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h new file mode 100644 index 000000000..6a8e627c6 --- /dev/null +++ b/include/secp256k1_frost.h @@ -0,0 +1,22 @@ +#ifndef SECP256K1_FROST_H +#define SECP256K1_FROST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** This code is currently a work in progress. It's not secure nor stable. + * IT IS EXTREMELY DANGEROUS AND RECKLESS TO USE THIS MODULE IN PRODUCTION! + * + * 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/). + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include new file mode 100644 index 000000000..5541884b0 --- /dev/null +++ b/src/modules/frost/Makefile.am.include @@ -0,0 +1,2 @@ +include_HEADERS += include/secp256k1_frost.h +noinst_HEADERS += src/modules/frost/main_impl.h diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h new file mode 100644 index 000000000..0ba469d7f --- /dev/null +++ b/src/modules/frost/main_impl.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_MAIN +#define SECP256K1_MODULE_FROST_MAIN + +#endif diff --git a/src/secp256k1.c b/src/secp256k1.c index 4c5782693..783b59cc9 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -908,3 +908,7 @@ static int secp256k1_ge_parse_ext(secp256k1_ge* ge, const unsigned char *in33) { #ifdef ENABLE_MODULE_SURJECTIONPROOF # include "modules/surjection/main_impl.h" #endif + +#ifdef ENABLE_MODULE_FROST +# include "modules/frost/main_impl.h" +#endif From 833d4d829dde570d968495d772ee7b67e5ecbd0c Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Thu, 5 Sep 2024 15:04:11 -0700 Subject: [PATCH 02/18] frost trusted dealer: share generation This commit adds trusted share generation, as well as share serialization and parsing. --- include/secp256k1_frost.h | 78 +++++++++++ src/modules/frost/Makefile.am.include | 2 + src/modules/frost/keygen.h | 13 ++ src/modules/frost/keygen_impl.h | 178 ++++++++++++++++++++++++++ src/modules/frost/main_impl.h | 2 + 5 files changed, 273 insertions(+) create mode 100644 src/modules/frost/keygen.h create mode 100644 src/modules/frost/keygen_impl.h diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 6a8e627c6..46df9e02a 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -1,6 +1,8 @@ #ifndef SECP256K1_FROST_H #define SECP256K1_FROST_H +#include "secp256k1_extrakeys.h" + #ifdef __cplusplus extern "C" { #endif @@ -15,6 +17,82 @@ extern "C" { * (https://crysp.uwaterloo.ca/software/frost/). */ +/** Opaque data structures + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. If you + * need to convert to a format suitable for storage, transmission, or + * comparison, use the corresponding serialization and parsing functions. + */ + +/** Opaque data structure that holds a signer's _secret_ share. + * + * Guaranteed to be 36 bytes in size. Serialized and parsed with + * `frost_share_serialize` and `frost_share_parse`. + */ +typedef struct { + unsigned char data[36]; +} secp256k1_frost_share; + +/** Serialize a FROST share + * + * Returns: 1 when the share could be serialized, 0 otherwise + * Args: ctx: pointer to a context object + * Out: out32: pointer to a 32-byte array to store the serialized share + * In: share: pointer to the share + */ +SECP256K1_API int secp256k1_frost_share_serialize( + const secp256k1_context *ctx, + unsigned char *out32, + const secp256k1_frost_share *share +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse a FROST share. + * + * Returns: 1 when the share could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: share: pointer to a share object + * In: in32: pointer to the 32-byte share to be parsed + */ +SECP256K1_API int secp256k1_frost_share_parse( + const secp256k1_context *ctx, + secp256k1_frost_share *share, + const unsigned char *in32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Creates key shares + * + * To generate a key, a trusted dealer generates a share for each participant. + * + * The trusted dealer must transmit shares over secure channels to each + * participant. + * + * Each call to this function must have a UNIQUE and uniformly RANDOM seed32 + * that must that must NOT BE REUSED in subsequent calls to this function and + * must be KEPT SECRET (even from participants). + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: shares: pointer to the key shares + * vss_commitment: pointer to the VSS commitment + * In: seed32: 32-byte random seed as explained above. Must be + * unique to this call to secp256k1_frost_shares_gen + * and must be uniformly random. + * threshold: the minimum number of signers required to produce a + * signature + * n_participants: the total number of participants + * ids33: array of 33-byte participant IDs + */ +SECP256K1_API int secp256k1_frost_shares_gen( + const secp256k1_context *ctx, + secp256k1_frost_share *shares, + secp256k1_pubkey *vss_commitment, + const unsigned char *seed32, + size_t threshold, + size_t n_participants, + const unsigned char * const *ids33 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7); + #ifdef __cplusplus } #endif diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include index 5541884b0..1fea08d2a 100644 --- a/src/modules/frost/Makefile.am.include +++ b/src/modules/frost/Makefile.am.include @@ -1,2 +1,4 @@ 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 diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h new file mode 100644 index 000000000..bc1082a1a --- /dev/null +++ b/src/modules/frost/keygen.h @@ -0,0 +1,13 @@ +/********************************************************************** + * 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_KEYGEN_H +#define SECP256K1_MODULE_FROST_KEYGEN_H + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_frost.h" + +#endif diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h new file mode 100644 index 000000000..bfc6e84bf --- /dev/null +++ b/src/modules/frost/keygen_impl.h @@ -0,0 +1,178 @@ +/********************************************************************** + * 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_KEYGEN_IMPL_H +#define SECP256K1_MODULE_FROST_KEYGEN_IMPL_H + +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_frost.h" + +#include "keygen.h" +#include "../../ecmult.h" +#include "../../field.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../scalar.h" + +/* Computes indexhash = tagged_hash(pk) */ +static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33) { + secp256k1_sha256 sha; + unsigned char buf[32]; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/index", sizeof("FROST/index") - 1); + secp256k1_sha256_write(&sha, id33, 33); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(indexhash, buf, NULL); + /* The x-coordinate must not be zero (see + * draft-irtf-cfrg-frost-08#section-4.2.2) */ + if (secp256k1_scalar_is_zero(indexhash)) { + return 0; + } + + return 1; +} + +static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 }; + +static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_scalar *s) { + memcpy(&share->data[0], secp256k1_frost_share_magic, 4); + secp256k1_scalar_get_b32(&share->data[4], s); +} + +static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share) { + int overflow; + + /* The magic is non-secret so it can be declassified to allow branching. */ + secp256k1_declassify(ctx, &share->data[0], 4); + ARG_CHECK(secp256k1_memcmp_var(&share->data[0], secp256k1_frost_share_magic, 4) == 0); + secp256k1_scalar_set_b32(s, &share->data[4], &overflow); + /* Parsed shares cannot overflow */ + VERIFY_CHECK(!overflow); + return 1; +} + +int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_share* share) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out32 != NULL); + ARG_CHECK(share != NULL); + memcpy(out32, &share->data[4], 32); + return 1; +} + +int secp256k1_frost_share_parse(const secp256k1_context* ctx, secp256k1_frost_share* share, const unsigned char *in32) { + secp256k1_scalar tmp; + int overflow; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(share != NULL); + ARG_CHECK(in32 != NULL); + + secp256k1_scalar_set_b32(&tmp, in32, &overflow); + if (overflow) { + return 0; + } + secp256k1_frost_share_save(share, &tmp); + return 1; +} + +static void secp256k1_frost_derive_coeff(secp256k1_scalar *coeff, const unsigned char *polygen32, size_t i) { + secp256k1_sha256 sha; + unsigned char buf[32]; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/coeffgen", sizeof("FROST/coeffgen") - 1); + secp256k1_sha256_write(&sha, polygen32, 32); + secp256k1_write_be64(&buf[0], i); + secp256k1_sha256_write(&sha, buf, 8); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(coeff, buf, NULL); +} + +static int secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubkey *vss_commitment, const unsigned char *polygen32, size_t threshold) { + secp256k1_gej rj; + secp256k1_ge rp; + size_t i; + int ret = 1; + + /* Compute commitment to each coefficient */ + for (i = 0; i < threshold; i++) { + secp256k1_scalar coeff_i; + + secp256k1_frost_derive_coeff(&coeff_i, polygen32, i); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &coeff_i); + secp256k1_ge_set_gej(&rp, &rj); + secp256k1_pubkey_save(&vss_commitment[threshold - i - 1], &rp); + } + return ret; +} + +static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigned char *polygen32, size_t threshold, const unsigned char *id33) { + secp256k1_scalar idx; + secp256k1_scalar share_i; + size_t i; + int ret = 1; + + /* Derive share */ + /* See RFC 9591, appendix C.1 */ + secp256k1_scalar_set_int(&share_i, 0); + if (!secp256k1_frost_compute_indexhash(&idx, id33)) { + return 0; + } + for (i = 0; i < threshold; i++) { + secp256k1_scalar coeff_i; + + secp256k1_frost_derive_coeff(&coeff_i, polygen32, i); + /* Horner's method to evaluate polynomial to derive shares */ + secp256k1_scalar_add(&share_i, &share_i, &coeff_i); + if (i < threshold - 1) { + secp256k1_scalar_mul(&share_i, &share_i, &idx); + } + } + secp256k1_frost_share_save(share, &share_i); + + return ret; +} + +int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_share *shares, secp256k1_pubkey *vss_commitment, const unsigned char *seed32, size_t threshold, size_t n_participants, const unsigned char * const* ids33) { + secp256k1_sha256 sha; + unsigned char polygen[32]; + size_t i; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(shares != NULL); + for (i = 0; i < n_participants; i++) { + memset(&shares[i], 0, sizeof(shares[i])); + } + ARG_CHECK(vss_commitment != NULL); + ARG_CHECK(seed32 != NULL); + ARG_CHECK(ids33 != NULL); + ARG_CHECK(threshold > 1); + ARG_CHECK(n_participants >= threshold); + + /* Commit to all inputs */ + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, seed32, 32); + secp256k1_write_be64(&polygen[0], threshold); + secp256k1_write_be64(&polygen[8], n_participants); + secp256k1_sha256_write(&sha, polygen, 16); + for (i = 0; i < n_participants; i++) { + secp256k1_sha256_write(&sha, ids33[i], 33); + } + secp256k1_sha256_finalize(&sha, polygen); + + ret &= secp256k1_frost_vss_gen(ctx, vss_commitment, polygen, threshold); + + for (i = 0; i < n_participants; i++) { + ret &= secp256k1_frost_share_gen(&shares[i], polygen, threshold, ids33[i]); + } + + return ret; +} + +#endif diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h index 0ba469d7f..66068ba77 100644 --- a/src/modules/frost/main_impl.h +++ b/src/modules/frost/main_impl.h @@ -7,4 +7,6 @@ #ifndef SECP256K1_MODULE_FROST_MAIN #define SECP256K1_MODULE_FROST_MAIN +#include "keygen_impl.h" + #endif From a64a9042fcec2bf811cced13724bbbbe609bc026 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Thu, 5 Sep 2024 22:25:55 -0700 Subject: [PATCH 03/18] frost trusted dealer: share verification This commit adds share verification, as well as computation of public verification shares. --- include/secp256k1_frost.h | 43 ++++++++++++++++ src/modules/frost/keygen_impl.h | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 46df9e02a..1e6457d69 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -93,6 +93,49 @@ SECP256K1_API int secp256k1_frost_shares_gen( const unsigned char * const *ids33 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7); +/** Verifies a share received during a key generation session + * + * The signature is verified against the VSS commitment received with the + * share. + * + * Returns: 0 if the arguments are invalid or the share does not verify, 1 + * otherwise + * Args ctx: pointer to a context object + * In: threshold: the minimum number of signers required to produce a + * signature + * id33: the 33-byte participant ID of the share recipient + * share: pointer to a key generation share + * vss_commitment: input array of the elements of the VSS commitment + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_share_verify( + const secp256k1_context *ctx, + size_t threshold, + const unsigned char *id33, + const secp256k1_frost_share *share, + const secp256k1_pubkey *vss_commitment +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Computes a public verification share used for verifying partial signatures + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: pubshare: pointer to a struct to store the public verification + * share + * In: threshold: the minimum number of signers required to produce a + * signature + * id33: the 33-byte participant ID of the participant whose + * partial signature will be verified with the pubshare + * vss_commitment: input array of the elements of the VSS commitment + * n_participants: the total number of participants + */ +SECP256K1_API int secp256k1_frost_compute_pubshare( + const secp256k1_context *ctx, + secp256k1_pubkey *pubshare, + size_t threshold, + const unsigned char *id33, + const secp256k1_pubkey *vss_commitment +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + #ifdef __cplusplus } #endif diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index bfc6e84bf..6b12521fb 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -175,4 +175,95 @@ int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_sha return ret; } +typedef struct { + const secp256k1_context *ctx; + secp256k1_scalar idx; + secp256k1_scalar idxn; + const secp256k1_pubkey *vss_commitment; +} secp256k1_frost_evaluate_vss_ecmult_data; + +static int secp256k1_frost_evaluate_vss_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_evaluate_vss_ecmult_data *ctx = (secp256k1_frost_evaluate_vss_ecmult_data *) data; + if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitment[idx])) { + return 0; + } + *sc = ctx->idxn; + secp256k1_scalar_mul(&ctx->idxn, &ctx->idxn, &ctx->idx); + + return 1; +} + +static int secp256k1_frost_evaluate_vss(const secp256k1_context* ctx, secp256k1_gej *share, size_t threshold, const unsigned char *id33, const secp256k1_pubkey *vss_commitment) { + secp256k1_frost_evaluate_vss_ecmult_data evaluate_vss_ecmult_data; + + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + + /* Use an EC multi-multiplication to verify the following equation: + * 0 = - share_i*G + idx^0*vss_commitment[0] + * + ... + * + idx^(threshold - 1)*vss_commitment[threshold - 1]*/ + evaluate_vss_ecmult_data.ctx = ctx; + evaluate_vss_ecmult_data.vss_commitment = vss_commitment; + /* Evaluate the public polynomial at the idx */ + if (!secp256k1_frost_compute_indexhash(&evaluate_vss_ecmult_data.idx, id33)) { + return 0; + } + secp256k1_scalar_set_int(&evaluate_vss_ecmult_data.idxn, 1); + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, share, NULL, secp256k1_frost_evaluate_vss_ecmult_callback, (void *) &evaluate_vss_ecmult_data, threshold)) { + return 0; + } + + return 1; +} + +/* See RFC 9591, appendix C.2 */ +int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_frost_share *share, const secp256k1_pubkey *vss_commitment) { + secp256k1_scalar share_i; + secp256k1_scalar share_neg; + secp256k1_gej tmpj, snj; + secp256k1_ge sng; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(id33 != NULL); + ARG_CHECK(share != NULL); + ARG_CHECK(vss_commitment != NULL); + ARG_CHECK(threshold > 1); + + if (!secp256k1_frost_share_load(ctx, &share_i, share)) { + return 0; + } + + if (!secp256k1_frost_evaluate_vss(ctx, &tmpj, threshold, id33, vss_commitment)) { + return 0; + } + + secp256k1_scalar_negate(&share_neg, &share_i); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &snj, &share_neg); + secp256k1_ge_set_gej(&sng, &snj); + secp256k1_gej_add_ge(&tmpj, &tmpj, &sng); + return secp256k1_gej_is_infinity(&tmpj); +} + +/* See RFC 9591, appendix C.2 */ +int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pubkey *pubshare, size_t threshold, const unsigned char *id33, const secp256k1_pubkey *vss_commitment) { + secp256k1_gej pkj; + secp256k1_ge tmp; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubshare != NULL); + memset(pubshare, 0, sizeof(*pubshare)); + ARG_CHECK(id33 != NULL); + ARG_CHECK(vss_commitment != NULL); + ARG_CHECK(threshold > 1); + + if (!secp256k1_frost_evaluate_vss(ctx, &pkj, threshold, id33, vss_commitment)) { + return 0; + } + secp256k1_ge_set_gej(&tmp, &pkj); + secp256k1_pubkey_save(pubshare, &tmp); + + return 1; +} + #endif From 3787674196c821016b82a28ea84d5a06fa07ca68 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Thu, 5 Sep 2024 22:49:58 -0700 Subject: [PATCH 04/18] frost trusted dealer: key tweaking This commits add BIP-341 ("Taproot") and BIP-32 ("ordinary") public key tweaking. --- include/secp256k1_frost.h | 135 ++++++++++++++++++++++++ src/modules/frost/keygen.h | 12 +++ src/modules/frost/keygen_impl.h | 175 ++++++++++++++++++++++++++++++++ 3 files changed, 322 insertions(+) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 1e6457d69..f2ea073f0 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/). + * + * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public + * key tweaking. */ /** Opaque data structures @@ -25,6 +28,15 @@ extern "C" { * comparison, use the corresponding serialization and parsing functions. */ +/** Opaque data structure that caches information about key tweaking. + * + * Guaranteed to be 101 bytes in size. It can be safely copied/moved. No + * serialization and parsing functions. + */ +typedef struct { + unsigned char data[101]; +} secp256k1_frost_keygen_cache; + /** Opaque data structure that holds a signer's _secret_ share. * * Guaranteed to be 36 bytes in size. Serialized and parsed with @@ -136,6 +148,129 @@ SECP256K1_API int secp256k1_frost_compute_pubshare( const secp256k1_pubkey *vss_commitment ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); +/** Computes a group public key and uses it to initialize a keygen_cache + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: keygen_cache: pointer to a frost_keygen_cache struct that is required + * for signing (or observing the signing session and + * verifying partial signatures). + * In: pubshares: input array of pointers to the public verification + * shares of the participants ordered by the IDs of the + * participants + * n_pubshares: the total number of public verification shares + * ids33: array of the 33-byte participant IDs of the signers + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_gen( + const secp256k1_context *ctx, + secp256k1_frost_keygen_cache *keygen_cache, + const secp256k1_pubkey * const *pubshares, + size_t n_pubshares, + const unsigned char * const *ids33 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); + +/** Obtain the group public key from a keygen_cache. + * + * This is only useful if you need the non-xonly public key, in particular for + * plain (non-xonly) tweaking or batch-verifying multiple key aggregations + * (not implemented). + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: pk: the FROST group public key. + * In: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by + * `frost_pubkey_gen` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get( + const secp256k1_context *ctx, + secp256k1_pubkey *pk, + const secp256k1_frost_keygen_cache *keygen_cache +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Apply ordinary "EC" tweaking to a public key in a given keygen_cache by + * adding the generator multiplied with `tweak32` to it. This is useful for + * deriving child keys from a group public key via BIP32. + * + * The tweaking method is the same as `secp256k1_ec_pubkey_tweak_add`. So after + * the following pseudocode buf and buf2 have identical contents (absent + * earlier failures). + * + * secp256k1_frost_pubkey_gen(..., keygen_cache, ...) + * secp256k1_frost_pubkey_tweak(..., keygen_cache, xonly_pk) + * secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, keygen_cache, tweak32) + * secp256k1_ec_pubkey_serialize(..., buf, output_pk) + * secp256k1_frost_pubkey_get(..., ec_pk, xonly_pk) + * secp256k1_ec_pubkey_tweak_add(..., ec_pk, tweak32) + * secp256k1_ec_pubkey_serialize(..., buf2, ec_pk) + * + * This function is required if you want to _sign_ for a tweaked group key. + * On the other hand, if you are only computing a public key, but not intending + * to create a signature for it, you can just use + * `secp256k1_ec_pubkey_tweak_add`. + * + * Returns: 0 if the arguments are invalid or the resulting public key would be + * invalid (only when the tweak is the negation of the corresponding + * secret key). 1 otherwise. + * Args: ctx: pointer to a context object + * Out: output_pubkey: pointer to a public key to store the result. Will be set + * to an invalid value if this function returns 0. If you + * do not need it, this arg can be NULL. + * In/Out: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by + * `frost_pubkey_tweak` + * In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid + * according to `secp256k1_ec_seckey_verify`, this function + * returns 0. For uniformly random 32-byte arrays the + * chance of being invalid is negligible (around 1 in + * 2^128). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_add( + const secp256k1_context *ctx, + secp256k1_pubkey *output_pubkey, + secp256k1_frost_keygen_cache *keygen_cache, + const unsigned char *tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Apply x-only tweaking to a public key in a given keygen_cache by adding the + * generator multiplied with `tweak32` to it. This is useful for creating + * Taproot outputs. + * + * The tweaking method is the same as `secp256k1_xonly_pubkey_tweak_add`. So in + * the following pseudocode xonly_pubkey_tweak_add_check (absent earlier + * failures) returns 1. + * + * secp256k1_frost_pubkey_gen(..., keygen_cache, ..., ..., ...) + * secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, keygen_cache, tweak32) + * secp256k1_xonly_pubkey_serialize(..., buf, output_pk) + * secp256k1_frost_pubkey_get(..., pk, keygen_cache) + * secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., pk, tweak32) + * + * This function is required if you want to _sign_ for a tweaked group key. + * On the other hand, if you are only computing a public key, but not intending + * to create a signature for it, you can just use + * `secp256k1_xonly_pubkey_tweak_add`. + * + * Returns: 0 if the arguments are invalid or the resulting public key would be + * invalid (only when the tweak is the negation of the corresponding + * secret key). 1 otherwise. + * Args: ctx: pointer to a context object + * Out: output_pubkey: pointer to a public key to store the result. Will be set + * to an invalid value if this function returns 0. If you + * do not need it, this arg can be NULL. + * In/Out: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by + * `frost_pubkey_tweak` + * In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid + * according to secp256k1_ec_seckey_verify, this function + * returns 0. For uniformly random 32-byte arrays the + * chance of being invalid is negligible (around 1 in + * 2^128). + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_tweak_add( + const secp256k1_context *ctx, + secp256k1_pubkey *output_pubkey, + secp256k1_frost_keygen_cache *keygen_cache, + const unsigned char *tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index bc1082a1a..d04431909 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -10,4 +10,16 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_frost.h" +#include "../../group.h" +#include "../../scalar.h" + +typedef struct { + secp256k1_ge pk; + /* tweak is identical to value tacc[v] in the specification. */ + secp256k1_scalar tweak; + /* parity_acc corresponds to gacc[v] in the spec. If gacc[v] is -1, + * parity_acc is 1. Otherwise, parity_acc is 0. */ + int parity_acc; +} secp256k1_keygen_cache_internal; + #endif diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index 6b12521fb..53fc23fcf 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -20,6 +20,39 @@ #include "../../hash.h" #include "../../scalar.h" +static const unsigned char secp256k1_frost_keygen_cache_magic[4] = { 0x40, 0x25, 0x2e, 0x41 }; + +/* A tweak cache consists of + * - 4 byte magic set during initialization to allow detecting an uninitialized + * object. + * - 64 byte aggregate (and potentially tweaked) public key + * - 1 byte the parity of the internal key (if tweaked, otherwise 0) + * - 32 byte tweak + */ +/* Requires that cache_i->pk is not infinity. */ +static void secp256k1_keygen_cache_save(secp256k1_frost_keygen_cache *cache, secp256k1_keygen_cache_internal *cache_i) { + unsigned char *ptr = cache->data; + memcpy(ptr, secp256k1_frost_keygen_cache_magic, 4); + ptr += 4; + secp256k1_ge_to_bytes(ptr, &cache_i->pk); + ptr += 64; + *ptr = cache_i->parity_acc; + ptr += 1; + secp256k1_scalar_get_b32(ptr, &cache_i->tweak); +} + +static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_keygen_cache_internal *cache_i, const secp256k1_frost_keygen_cache *cache) { + const unsigned char *ptr = cache->data; + ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_keygen_cache_magic, 4) == 0); + ptr += 4; + secp256k1_ge_from_bytes(&cache_i->pk, ptr); + ptr += 64; + cache_i->parity_acc = *ptr & 1; + ptr += 1; + secp256k1_scalar_set_b32(&cache_i->tweak, ptr, NULL); + return 1; +} + /* Computes indexhash = tagged_hash(pk) */ static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33) { secp256k1_sha256 sha; @@ -182,6 +215,13 @@ typedef struct { const secp256k1_pubkey *vss_commitment; } secp256k1_frost_evaluate_vss_ecmult_data; +typedef struct { + const secp256k1_context *ctx; + const secp256k1_pubkey * const* pubshares; + const unsigned char * const *ids33; + size_t n_pubshares; +} secp256k1_frost_interpolate_pubkey_ecmult_data; + static int secp256k1_frost_evaluate_vss_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { secp256k1_frost_evaluate_vss_ecmult_data *ctx = (secp256k1_frost_evaluate_vss_ecmult_data *) data; if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitment[idx])) { @@ -193,6 +233,23 @@ static int secp256k1_frost_evaluate_vss_ecmult_callback(secp256k1_scalar *sc, se return 1; } +static int secp256k1_frost_interpolate_pubkey_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_interpolate_pubkey_ecmult_data *ctx = (secp256k1_frost_interpolate_pubkey_ecmult_data *) data; + secp256k1_scalar l; + + if (!secp256k1_pubkey_load(ctx->ctx, pt, ctx->pubshares[idx])) { + return 0; + } + + if (!secp256k1_frost_lagrange_coefficient(&l, ctx->ids33, ctx->n_pubshares, ctx->ids33[idx])) { + return 0; + } + + *sc = l; + + return 1; +} + static int secp256k1_frost_evaluate_vss(const secp256k1_context* ctx, secp256k1_gej *share, size_t threshold, const unsigned char *id33, const secp256k1_pubkey *vss_commitment) { secp256k1_frost_evaluate_vss_ecmult_data evaluate_vss_ecmult_data; @@ -266,4 +323,122 @@ int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pub return 1; } +int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *agg_pk, const secp256k1_frost_keygen_cache *keyagg_cache) { + secp256k1_keygen_cache_internal cache_i; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(agg_pk != NULL); + memset(agg_pk, 0, sizeof(*agg_pk)); + ARG_CHECK(keyagg_cache != NULL); + + if(!secp256k1_keygen_cache_load(ctx, &cache_i, keyagg_cache)) { + return 0; + } + secp256k1_pubkey_save(agg_pk, &cache_i.pk); + return 1; +} + +int secp256k1_frost_pubkey_gen(const secp256k1_context* ctx, secp256k1_frost_keygen_cache *cache, const secp256k1_pubkey * const *pubshares, size_t n_pubshares, const unsigned char * const *ids33) { + secp256k1_gej pkj; + secp256k1_frost_interpolate_pubkey_ecmult_data interpolate_pubkey_ecmult_data; + secp256k1_keygen_cache_internal cache_i = { 0 }; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(cache != NULL); + ARG_CHECK(pubshares != NULL); + ARG_CHECK(ids33 != NULL); + ARG_CHECK(n_pubshares > 1); + + interpolate_pubkey_ecmult_data.ctx = ctx; + interpolate_pubkey_ecmult_data.pubshares = pubshares; + interpolate_pubkey_ecmult_data.ids33 = ids33; + interpolate_pubkey_ecmult_data.n_pubshares = n_pubshares; + + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_interpolate_pubkey_ecmult_callback, (void *) &interpolate_pubkey_ecmult_data, n_pubshares)) { + return 0; + } + secp256k1_ge_set_gej(&cache_i.pk, &pkj); + secp256k1_keygen_cache_save(cache, &cache_i); + + return 1; +} + +static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32, int xonly) { + secp256k1_keygen_cache_internal cache_i; + int overflow = 0; + secp256k1_scalar tweak; + + VERIFY_CHECK(ctx != NULL); + if (output_pubkey != NULL) { + memset(output_pubkey, 0, sizeof(*output_pubkey)); + } + ARG_CHECK(keygen_cache != NULL); + ARG_CHECK(tweak32 != NULL); + + if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) { + return 0; + } + secp256k1_scalar_set_b32(&tweak, tweak32, &overflow); + if (overflow) { + return 0; + } + if (xonly && secp256k1_extrakeys_ge_even_y(&cache_i.pk)) { + cache_i.parity_acc ^= 1; + secp256k1_scalar_negate(&cache_i.tweak, &cache_i.tweak); + } + secp256k1_scalar_add(&cache_i.tweak, &cache_i.tweak, &tweak); + if (!secp256k1_eckey_pubkey_tweak_add(&cache_i.pk, &tweak)) { + return 0; + } + /* eckey_pubkey_tweak_add fails if cache_i.pk is infinity */ + VERIFY_CHECK(!secp256k1_ge_is_infinity(&cache_i.pk)); + secp256k1_keygen_cache_save(keygen_cache, &cache_i); + if (output_pubkey != NULL) { + secp256k1_pubkey_save(output_pubkey, &cache_i.pk); + } + return 1; +} + +int secp256k1_frost_pubkey_ec_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32) { + return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, keygen_cache, tweak32, 0); +} + +int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32) { + return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, keygen_cache, tweak32, 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; +} + #endif From 9fa15f5005544749c663b15f169529788d5821b3 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 9 Sep 2024 18:28:16 -0700 Subject: [PATCH 05/18] frost trusted dealer: nonce generation This commits adds nonce generation, as well as serialization and parsing. --- include/secp256k1_frost.h | 113 ++++++++++++ src/group.h | 10 +- src/group_impl.h | 19 +- src/modules/frost/Makefile.am.include | 2 + src/modules/frost/keygen.h | 4 + src/modules/frost/main_impl.h | 1 + src/modules/frost/session.h | 13 ++ src/modules/frost/session_impl.h | 249 ++++++++++++++++++++++++++ src/modules/musig/keyagg.h | 6 - src/modules/musig/keyagg_impl.h | 21 +-- src/modules/musig/session_impl.h | 4 +- 11 files changed, 413 insertions(+), 29 deletions(-) create mode 100644 src/modules/frost/session.h create mode 100644 src/modules/frost/session_impl.h diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index f2ea073f0..c5590ac0c 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -18,6 +18,9 @@ extern "C" { * * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public * key tweaking. + * + * 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 @@ -46,6 +49,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 @@ -271,6 +329,61 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_twea const unsigned char *tweak32 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** 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 group 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 share! + * 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) + * keygen_cache: pointer to the keygen_cache that was used to create the group + * (and potentially tweaked) public key if already known + * (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_frost_keygen_cache *keygen_cache, + 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 81b159c81..11bfc6262 100644 --- a/src/group.h +++ b/src/group.h @@ -185,12 +185,20 @@ static void secp256k1_gej_rescale(secp256k1_gej *r, const secp256k1_fe *b); /** Convert a group element that is not infinity to a 64-byte array. The output * array is platform-dependent. */ -static void secp256k1_ge_to_bytes(unsigned char *buf, secp256k1_ge *a); +static void secp256k1_ge_to_bytes(unsigned char *buf, const secp256k1_ge *a); /** Convert a 64-byte array into group element. This function assumes that the * provided buffer correctly encodes a group element. */ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf); +/** Convert a group element (that is allowed to be infinity) to a 64-byte + * array. The output array is platform-dependent. */ +static void secp256k1_ge_to_bytes_ext(unsigned char *data, const secp256k1_ge *ge); + +/** Convert a 64-byte array into a group element. This function assumes that the + * provided buffer is the output of secp256k1_ge_to_bytes_ext. */ +static void secp256k1_ge_from_bytes_ext(secp256k1_ge *ge, const unsigned char *data); + /** Determine if a point (which is assumed to be on the curve) is in the correct (sub)group of the curve. * * In normal mode, the used group is secp256k1, which has cofactor=1 meaning that every point on the curve is in the diff --git a/src/group_impl.h b/src/group_impl.h index f27b7d994..f073b5059 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -965,7 +965,7 @@ static int secp256k1_ge_x_frac_on_curve_var(const secp256k1_fe *xn, const secp25 return secp256k1_fe_is_square_var(&r); } -static void secp256k1_ge_to_bytes(unsigned char *buf, secp256k1_ge *a) { +static void secp256k1_ge_to_bytes(unsigned char *buf, const secp256k1_ge *a) { secp256k1_ge_storage s; /* We require that the secp256k1_ge_storage type is exactly 64 bytes. @@ -985,4 +985,21 @@ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf) { secp256k1_ge_from_storage(r, &s); } +static void secp256k1_ge_to_bytes_ext(unsigned char *data, const secp256k1_ge *ge) { + if (secp256k1_ge_is_infinity(ge)) { + memset(data, 0, 64); + } else { + secp256k1_ge_to_bytes(data, ge); + } +} + +static void secp256k1_ge_from_bytes_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); + } +} + #endif /* SECP256K1_GROUP_IMPL_H */ diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include index 1fea08d2a..7e44e6ea8 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 d04431909..249194aa2 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -22,4 +22,8 @@ typedef struct { int parity_acc; } secp256k1_keygen_cache_internal; +static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_keygen_cache_internal *cache_i, const secp256k1_frost_keygen_cache *cache); + +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 66068ba77..a126f452e 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 000000000..f7e8da024 --- /dev/null +++ b/src/modules/frost/session.h @@ -0,0 +1,13 @@ +/********************************************************************** + * 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 + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_frost.h" + +#endif diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h new file mode 100644 index 000000000..61e21ee72 --- /dev/null +++ b/src/modules/frost/session_impl.h @@ -0,0 +1,249 @@ +/********************************************************************** + * 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_ge_to_bytes(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_ge_from_bytes(&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; +} + +/* Write optional inputs into the hash */ +static void secp256k1_nonce_function_frost_helper(secp256k1_sha256 *sha, unsigned int prefix_size, const unsigned char *data32) { + /* The spec requires length prefix to be 4 bytes for `extra_in`, 1 byte + * otherwise */ + VERIFY_CHECK(prefix_size == 4 || prefix_size == 1); + if (prefix_size == 4) { + /* Four byte big-endian value, pad first three bytes with 0 */ + unsigned char zero[3] = {0}; + secp256k1_sha256_write(sha, zero, 3); + } + if (data32 != NULL) { + unsigned char len = 32; + secp256k1_sha256_write(sha, &len, 1); + secp256k1_sha256_write(sha, data32, 32); + } else { + unsigned char len = 0; + secp256k1_sha256_write(sha, &len, 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 rand[32]; + unsigned char i; + + if (key32 != NULL) { + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/aux", sizeof("FROST/aux") - 1); + secp256k1_sha256_write(&sha, session_id, 32); + secp256k1_sha256_finalize(&sha, rand); + for (i = 0; i < 32; i++) { + rand[i] ^= key32[i]; + } + } else { + memcpy(rand, session_id, sizeof(rand)); + } + + /* 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, rand, sizeof(rand)); + secp256k1_nonce_function_frost_helper(&sha, 1, pk32); + secp256k1_nonce_function_frost_helper(&sha, 1, msg32); + secp256k1_nonce_function_frost_helper(&sha, 4, extra_input32); + + for (i = 0; i < 2; i++) { + unsigned char buf[32]; + secp256k1_sha256 sha_tmp = sha; + secp256k1_sha256_write(&sha_tmp, &i, 1); + secp256k1_sha256_finalize(&sha_tmp, 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_frost_keygen_cache *keygen_cache, const unsigned char *extra_input32) { + secp256k1_keygen_cache_internal cache_i; + 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)); + } + + /* Check that the share is valid to be able to sign for it later. */ + if (share != NULL) { + 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 (keygen_cache != NULL) { + if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) { + return 0; + } + /* The loaded point cache_i.pk can not be the point at infinity. */ + secp256k1_fe_get_b32(pk_ser, &cache_i.pk.x); + 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 620522feb..1497659d4 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 aff955420..1c221d77b 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 @@ -52,7 +35,7 @@ static void secp256k1_keyagg_cache_save(secp256k1_musig_keyagg_cache *cache, sec ptr += 4; secp256k1_ge_to_bytes(ptr, &cache_i->pk); ptr += 64; - secp256k1_point_save_ext(ptr, &cache_i->second_pk); + secp256k1_ge_to_bytes_ext(ptr, &cache_i->second_pk); ptr += 64; memcpy(ptr, cache_i->pk_hash, 32); ptr += 32; @@ -67,7 +50,7 @@ static int secp256k1_keyagg_cache_load(const secp256k1_context* ctx, secp256k1_k ptr += 4; secp256k1_ge_from_bytes(&cache_i->pk, ptr); ptr += 64; - secp256k1_point_load_ext(&cache_i->second_pk, ptr); + secp256k1_ge_from_bytes_ext(&cache_i->second_pk, ptr); ptr += 64; memcpy(cache_i->pk_hash, ptr, 32); ptr += 32; diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index ff87f2fd3..cab3376f9 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -84,7 +84,7 @@ static void secp256k1_musig_aggnonce_save(secp256k1_musig_aggnonce* nonce, secp2 int i; memcpy(&nonce->data[0], secp256k1_musig_aggnonce_magic, 4); for (i = 0; i < 2; i++) { - secp256k1_point_save_ext(&nonce->data[4 + 64*i], &ge[i]); + secp256k1_ge_to_bytes_ext(&nonce->data[4 + 64*i], &ge[i]); } } @@ -93,7 +93,7 @@ static int secp256k1_musig_aggnonce_load(const secp256k1_context* ctx, secp256k1 ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_musig_aggnonce_magic, 4) == 0); for (i = 0; i < 2; i++) { - secp256k1_point_load_ext(&ge[i], &nonce->data[4 + 64*i]); + secp256k1_ge_from_bytes_ext(&ge[i], &nonce->data[4 + 64*i]); } return 1; } From af4dfaf77d7a805e4a4904fe8879e427852b3482 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 9 Sep 2024 18:41:14 -0700 Subject: [PATCH 06/18] frost trusted dealer: nonce aggregation and adaptor signatures This commit adds nonce aggregation, as well as adaptor signatures. --- include/secp256k1_frost.h | 139 +++++++++++++++++++- src/modules/frost/Makefile.am.include | 1 + src/modules/frost/adaptor_impl.h | 168 ++++++++++++++++++++++++ src/modules/frost/keygen.h | 4 + src/modules/frost/main_impl.h | 1 + src/modules/frost/session.h | 12 ++ src/modules/frost/session_impl.h | 178 ++++++++++++++++++++++++++ 7 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 src/modules/frost/adaptor_impl.h diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index c5590ac0c..bbcdb5da8 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -17,7 +17,7 @@ extern "C" { * (https://crysp.uwaterloo.ca/software/frost/). * * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public - * key tweaking. + * key tweaking, and adaptor signatures. * * 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. @@ -78,6 +78,16 @@ typedef struct { unsigned char data[132]; } secp256k1_frost_pubnonce; +/** Opaque data structure that holds a FROST session. + * + * This structure is not required to be kept secret for the signing protocol + * to be secure. Guaranteed to be 133 bytes in size. It can be safely + * copied/moved. No serialization and parsing functions. + */ +typedef struct { + unsigned char data[133]; +} secp256k1_frost_session; + /** Parse a signer's public nonce. * * Returns: 1 when the nonce could be parsed, 0 otherwise. @@ -384,6 +394,133 @@ SECP256K1_API int secp256k1_frost_nonce_gen( const unsigned char *extra_input32 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** Takes the public nonces of all signers and computes a session that is + * required for signing and verification of partial signatures. The participant + * IDs can be sorted before combining, but the corresponding pubnonces must be + * resorted as well. All signers must use the same sorting of pubnonces, + * otherwise signing will fail. + * + * Returns: 0 if the arguments are invalid or if some signer sent invalid + * pubnonces, 1 otherwise + * Args: ctx: pointer to a context object + * Out: session: pointer to a struct to store the session + * In: pubnonces: array of pointers to public nonces sent by the signers + * n_pubnonces: number of elements in the pubnonces array. Must be + * greater than 0. + * msg32: the 32-byte message to sign + * myd_id33: the 33-byte ID of the participant who will use the + * session for signing + * ids33: array of the 33-byte participant IDs of the signers + * keygen_cache: pointer to frost_keygen_cache struct + * adaptor: optional pointer to an adaptor point encoded as a + * public key if this signing session is part of an + * adaptor signature protocol (can be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT 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 unsigned char *my_id33, + const unsigned char * const *ids33, + const secp256k1_frost_keygen_cache *keygen_cache, + 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); + +/** Extracts the nonce_parity bit from a session + * + * This is used for adaptor signatures. + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: nonce_parity: pointer to an integer that indicates the parity + * of the aggregate public nonce. Used for adaptor + * signatures. + * In: session: pointer to the session that was created with + * frost_nonce_process + */ +SECP256K1_API int secp256k1_frost_nonce_parity( + const secp256k1_context *ctx, + int *nonce_parity, + const secp256k1_frost_session *session +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Verifies that the adaptor can be extracted by combining the adaptor + * pre-signature and the completed signature. + * + * Returns: 0 if the arguments are invalid or the adaptor signature does not + * verify, 1 otherwise + * Args: ctx: pointer to a context object + * In: pre_sig64: 64-byte pre-signature + * msg32: the 32-byte message being verified + * pubkey: pointer to an x-only public key to verify with + * adaptor: pointer to the adaptor point being verified + * nonce_parity: the output of `frost_nonce_parity` called with the + * session used for producing the pre-signature + */ +SECP256K1_API 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_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Creates a signature from a pre-signature and an adaptor. + * + * If the sec_adaptor32 argument is incorrect, the output signature will be + * invalid. This function does not verify the signature. + * + * Returns: 0 if the arguments are invalid, or pre_sig64 or sec_adaptor32 contain + * invalid (overflowing) values. 1 otherwise (which does NOT mean the + * signature or the adaptor are valid!) + * Args: ctx: pointer to a context object + * Out: sig64: 64-byte signature. This pointer may point to the same + * memory area as `pre_sig`. + * In: pre_sig64: 64-byte pre-signature + * sec_adaptor32: 32-byte secret adaptor to add to the pre-signature + * nonce_parity: the output of `frost_nonce_parity` called with the + * session used for producing the pre-signature + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT 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_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Extracts a secret adaptor from a FROST pre-signature and corresponding + * signature + * + * This function will not fail unless given grossly invalid data; if it is + * merely given signatures that do not verify, the returned value will be + * nonsense. It is therefore important that all data be verified at earlier + * steps of any protocol that uses this function. In particular, this includes + * verifying all partial signatures that were aggregated into pre_sig64. + * + * Returns: 0 if the arguments are NULL, or sig64 or pre_sig64 contain + * grossly invalid (overflowing) values. 1 otherwise (which does NOT + * mean the signatures or the adaptor are valid!) + * Args: ctx: pointer to a context object + * Out:sec_adaptor32: 32-byte secret adaptor + * In: sig64: complete, valid 64-byte signature + * pre_sig64: the pre-signature corresponding to sig64, i.e., the + * aggregate of partial signatures without the secret + * adaptor + * nonce_parity: the output of `frost_nonce_parity` called with the + * session used for producing sig64 + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT 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_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include index 7e44e6ea8..f19f854f3 100644 --- a/src/modules/frost/Makefile.am.include +++ b/src/modules/frost/Makefile.am.include @@ -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 diff --git a/src/modules/frost/adaptor_impl.h b/src/modules/frost/adaptor_impl.h new file mode 100644 index 000000000..28b21f709 --- /dev/null +++ b/src/modules/frost/adaptor_impl.h @@ -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 + +#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 diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index 249194aa2..60f494973 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -26,4 +26,8 @@ static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_k 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); + +static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsigned char * const *ids33, size_t n_participants, const unsigned char *my_id33); + #endif diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h index a126f452e..92824c03a 100644 --- a/src/modules/frost/main_impl.h +++ b/src/modules/frost/main_impl.h @@ -9,5 +9,6 @@ #include "keygen_impl.h" #include "session_impl.h" +#include "adaptor_impl.h" #endif diff --git a/src/modules/frost/session.h b/src/modules/frost/session.h index f7e8da024..82bd915a4 100644 --- a/src/modules/frost/session.h +++ b/src/modules/frost/session.h @@ -10,4 +10,16 @@ #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 diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index 61e21ee72..156883e3d 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -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; @@ -246,4 +289,139 @@ 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_gej_set_infinity(&fin_nonce_ptj); + 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)) { + fin_nonce_pt = secp256k1_ge_const_g; + } + /* fin_nonce_pt is not the point at infinity */ + 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; +} + +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 unsigned char *my_id33, const unsigned char * const *ids33, const secp256k1_frost_keygen_cache *keygen_cache, const secp256k1_pubkey *adaptor) { + secp256k1_keygen_cache_internal cache_i; + secp256k1_gej aggnonce_ptj[2]; + unsigned char fin_nonce[32]; + secp256k1_frost_session_internal session_i = { 0 }; + unsigned char pk32[32]; + 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(keygen_cache != NULL); + ARG_CHECK(n_pubnonces > 1); + + if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) { + return 0; + } + secp256k1_fe_get_b32(pk32, &cache_i.pk.x); + + if (!secp256k1_frost_sum_nonces(ctx, aggnonce_ptj, pubnonces, n_pubnonces)) { + return 0; + } + /* 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 (!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 From ab1bb833cb9c385ed463785f1f7ebf3b0e16bc3f Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 9 Sep 2024 18:43:40 -0700 Subject: [PATCH 07/18] frost trusted dealer: 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 | 197 +++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+), 1 deletion(-) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index bbcdb5da8..d3947160d 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 @@ -428,6 +468,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 share! + * + * 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 + * keygen_cache: pointer to frost_keygen_cache struct + */ +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_keygen_cache *keygen_cache +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** 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 `keygen_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` + * keygen_cache: pointer to frost_keygen_cache struct + */ +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_keygen_cache *keygen_cache +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** 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 156883e3d..f32e0de61 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; +} + /* Write optional inputs into the hash */ static void secp256k1_nonce_function_frost_helper(secp256k1_sha256 *sha, unsigned int prefix_size, const unsigned char *data32) { /* 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_ 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_keygen_cache *keygen_cache) { + secp256k1_scalar sk; + secp256k1_scalar k[2]; + secp256k1_scalar s; + secp256k1_keygen_cache_internal cache_i; + 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(keygen_cache != NULL); + ARG_CHECK(session != NULL); + + if (!secp256k1_frost_share_load(ctx, &sk, share)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + + /* Negate sk if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc. + * This corresponds to the line "Let d = gâ‹…gaccâ‹…d' mod n" in the + * specification. */ + if ((secp256k1_fe_is_odd(&cache_i.pk.y) + != cache_i.parity_acc)) { + secp256k1_scalar_negate(&sk, &sk); + } + + if (!secp256k1_frost_session_load(ctx, &session_i, session)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + + 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_keygen_cache *keygen_cache) { + secp256k1_keygen_cache_internal cache_i; + 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(keygen_cache != 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; + } + if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) { + return 0; + } + + secp256k1_scalar_set_int(&e, 1); + /* Negate e if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc. + * This corresponds to the line "Let g' = gâ‹…gacc mod n" and the multiplication "g'â‹…e" + * in the specification. */ + 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 From aea613bc757635bb1ab2c94ae405bf7c87dc97cf Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 9 Sep 2024 18:45:40 -0700 Subject: [PATCH 08/18] frost trusted dealer: add example file This commit adds an example file to demonstrate how to use the module. --- .gitignore | 1 + Makefile.am | 11 ++ examples/frost.c | 278 ++++++++++++++++++++++++++++++++++++++ include/secp256k1_frost.h | 3 +- 4 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 examples/frost.c diff --git a/.gitignore b/.gitignore index b3ae618d4..3d20e46d9 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ libsecp256k1.pc contrib/gh-pr-create.sh musig_example +frost_example ### CMake /CMakeUserPresets.json diff --git a/Makefile.am b/Makefile.am index ce92c94f7..3fe70c607 100644 --- a/Makefile.am +++ b/Makefile.am @@ -195,6 +195,17 @@ musig_example_LDFLAGS += -lbcrypt endif TESTS += musig_example endif +if ENABLE_MODULE_FROST +noinst_PROGRAMS += frost_example +frost_example_SOURCES = examples/frost.c +frost_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC +frost_example_LDADD = libsecp256k1.la +frost_example_LDFLAGS = -static +if BUILD_WINDOWS +frost_example_LDFLAGS += -lbcrypt +endif +TESTS += frost_example +endif endif ### Precomputed tables diff --git a/examples/frost.c b/examples/frost.c new file mode 100644 index 000000000..828108928 --- /dev/null +++ b/examples/frost.c @@ -0,0 +1,278 @@ +/*********************************************************************** + * Copyright (c) 2021-2024 Jesse Posner * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +/** + * This file demonstrates how to use the FROST module to create a threshold + * signature. Additionally, see the documentation in include/secp256k1_frost.h. + */ + +#include +#include +#include + +#include +#include +#include + +#include "examples_util.h" + +struct signer_secrets { + secp256k1_keypair keypair; + secp256k1_frost_share share; + secp256k1_frost_secnonce secnonce; +}; + +struct signer { + secp256k1_pubkey pubshare; + secp256k1_frost_pubnonce pubnonce; + secp256k1_frost_session session; + secp256k1_frost_partial_sig partial_sig; + unsigned char id[33]; +}; + +/* Threshold required in creating the aggregate signature */ +#define THRESHOLD 3 + + +/* Number of public keys involved in creating the aggregate signature */ +#define N_SIGNERS 5 +/* Create a key pair, store it in signer_secrets->keypair and signer->pubkey */ +static int create_keypair(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) { + secp256k1_pubkey pubkey_tmp; + unsigned char seckey[32]; + size_t size = 33; + while (1) { + if (!fill_random(seckey, sizeof(seckey))) { + printf("Failed to generate randomness\n"); + return 1; + } + if (secp256k1_keypair_create(ctx, &signer_secrets->keypair, seckey)) { + break; + } + } + if (!secp256k1_keypair_pub(ctx, &pubkey_tmp, &signer_secrets->keypair)) { + return 0; + } + if (!secp256k1_ec_pubkey_serialize(ctx, signer->id, &size, &pubkey_tmp, SECP256K1_EC_COMPRESSED)) { + return 0; + } + return 1; +} + +/* Create shares and coefficient commitments */ +static int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) { + int i; + secp256k1_frost_share shares[N_SIGNERS]; + secp256k1_pubkey vss_commitment[THRESHOLD]; + const unsigned char *ids[N_SIGNERS]; + unsigned char seed[32]; + + if (!fill_random(seed, sizeof(seed))) { + return 0; + } + + for (i = 0; i < N_SIGNERS; i++) { + ids[i] = signer[i].id; + } + + /* Generate shares for the participants */ + if (!secp256k1_frost_shares_gen(ctx, shares, vss_commitment, seed, THRESHOLD, N_SIGNERS, ids)) { + return 0; + } + + /* Distribute shares and VSS commitment */ + for (i = 0; i < N_SIGNERS; i++) { + signer_secrets[i].share = shares[i]; + /* Each participant verifies their share. */ + if (!secp256k1_frost_share_verify(ctx, THRESHOLD, signer[i].id, &shares[i], vss_commitment)) { + return 0; + } + /* Each participant generates public verification shares that are + * used for verifying partial signatures. */ + if (!secp256k1_frost_compute_pubshare(ctx, &signer[i].pubshare, THRESHOLD, signer[i].id, vss_commitment)) { + return 0; + } + } + + return 1; +} + +/* Tweak the pubkey corresponding to the provided tweak cache, update the cache + * and return the tweaked aggregate pk. */ +static int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_frost_keygen_cache *cache) { + secp256k1_pubkey output_pk; + unsigned char ordinary_tweak[32] = "this could be a BIP32 tweak...."; + unsigned char xonly_tweak[32] = "this could be a taproot tweak.."; + + /* Ordinary tweaking which, for example, allows deriving multiple child + * public keys from a single aggregate key using BIP32 */ + if (!secp256k1_frost_pubkey_ec_tweak_add(ctx, NULL, cache, ordinary_tweak)) { + return 0; + } + /* If one is not interested in signing, the same output_pk can be obtained + * by calling `secp256k1_frost_pubkey_get` right after key aggregation to + * get the full pubkey and then call `secp256k1_ec_pubkey_tweak_add`. */ + + /* Xonly tweaking which, for example, allows creating taproot commitments */ + if (!secp256k1_frost_pubkey_xonly_tweak_add(ctx, &output_pk, cache, xonly_tweak)) { + return 0; + } + /* Note that if we wouldn't care about signing, we can arrive at the same + * output_pk by providing the untweaked public key to + * `secp256k1_xonly_pubkey_tweak_add` (after converting it to an xonly pubkey + * if necessary with `secp256k1_xonly_pubkey_from_pubkey`). */ + + /* Now we convert the output_pk to an xonly pubkey to allow to later verify + * the Schnorr signature against it. For this purpose we can ignore the + * `pk_parity` output argument; we would need it if we would have to open + * the taproot commitment. */ + if (!secp256k1_xonly_pubkey_from_pubkey(ctx, pk, NULL, &output_pk)) { + return 0; + } + return 1; +} + +/* Sign a message hash with the given threshold and aggregate shares and store + * the result in sig */ +static int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char *msg32, unsigned char *sig64, const secp256k1_frost_keygen_cache *cache) { + int i; + int signer_id = 0; + int signers[THRESHOLD]; + int is_signer[N_SIGNERS]; + const secp256k1_frost_pubnonce *pubnonces[THRESHOLD]; + const unsigned char *ids[THRESHOLD]; + const secp256k1_frost_partial_sig *partial_sigs[THRESHOLD]; + + for (i = 0; i < N_SIGNERS; i++) { + unsigned char session_id[32]; + /* Create random session ID. It is absolutely necessary that the session ID + * is unique for every call of secp256k1_frost_nonce_gen. Otherwise + * it's trivial for an attacker to extract the secret key! */ + if (!fill_random(session_id, sizeof(session_id))) { + return 0; + } + /* Initialize session and create secret nonce for signing and public + * nonce to send to the other signers. */ + if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_id, &signer_secrets[i].share, msg32, cache, NULL)) { + return 0; + } + is_signer[i] = 0; /* Initialize is_signer */ + } + /* Select a random subset of signers */ + for (i = 0; i < THRESHOLD; i++) { + unsigned int subset_seed; + + while (1) { + if (!fill_random((unsigned char*)&subset_seed, sizeof(subset_seed))) { + return 0; + } + signer_id = subset_seed % N_SIGNERS; + /* Check if signer has already been assigned */ + if (!is_signer[signer_id]) { + is_signer[signer_id] = 1; + signers[i] = signer_id; + break; + } + } + /* Mark signer as assigned */ + pubnonces[i] = &signer[signer_id].pubnonce; + ids[i] = signer[signer_id].id; + } + /* Signing communication round 1: Exchange nonces */ + for (i = 0; i < THRESHOLD; i++) { + signer_id = signers[i]; + if (!secp256k1_frost_nonce_process(ctx, &signer[signer_id].session, pubnonces, THRESHOLD, msg32, signer[signer_id].id, ids, cache, NULL)) { + return 0; + } + /* partial_sign will clear the secnonce by setting it to 0. That's because + * you must _never_ reuse the secnonce (or use the same session_id to + * create a secnonce). If you do, you effectively reuse the nonce and + * leak the secret key. */ + if (!secp256k1_frost_partial_sign(ctx, &signer[signer_id].partial_sig, &signer_secrets[signer_id].secnonce, &signer_secrets[signer_id].share, &signer[signer_id].session, cache)) { + return 0; + } + partial_sigs[i] = &signer[signer_id].partial_sig; + } + /* Communication round 2: A production system would exchange + * partial signatures here before moving on. */ + for (i = 0; i < THRESHOLD; i++) { + signer_id = signers[i]; + /* To check whether signing was successful, it suffices to either verify + * the aggregate signature with the aggregate public key using + * secp256k1_schnorrsig_verify, or verify all partial signatures of all + * signers individually. Verifying the aggregate signature is cheaper but + * verifying the individual partial signatures has the advantage that it + * can be used to determine which of the partial signatures are invalid + * (if any), i.e., which of the partial signatures cause the aggregate + * signature to be invalid and thus the protocol run to fail. It's also + * fine to first verify the aggregate sig, and only verify the individual + * sigs if it does not work. + */ + if (!secp256k1_frost_partial_sig_verify(ctx, &signer[signer_id].partial_sig, &signer[signer_id].pubnonce, &signer[signer_id].pubshare, &signer[signer_id].session, cache)) { + return 0; + } + } + return secp256k1_frost_partial_sig_agg(ctx, sig64, &signer[signer_id].session, partial_sigs, THRESHOLD); +} + +int main(void) { + secp256k1_context* ctx; + int i; + struct signer_secrets signer_secrets[N_SIGNERS]; + struct signer signers[N_SIGNERS]; + const secp256k1_pubkey *pubshares_ptr[N_SIGNERS]; + secp256k1_xonly_pubkey pk; + secp256k1_frost_keygen_cache keygen_cache; + const unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!"; + unsigned char sig[64]; + const unsigned char *id_ptr[5]; + + /* Create a context for signing and verification */ + ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + printf("Creating key pairs......"); + for (i = 0; i < N_SIGNERS; i++) { + if (!create_keypair(ctx, &signer_secrets[i], &signers[i])) { + printf("FAILED\n"); + return 1; + } + pubshares_ptr[i] = &signers[i].pubshare; + id_ptr[i] = signers[i].id; + } + printf("ok\n"); + printf("Creating shares........."); + if (!create_shares(ctx, signer_secrets, signers)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + printf("Generating public key..."); + if (!secp256k1_frost_pubkey_gen(ctx, &keygen_cache, pubshares_ptr, N_SIGNERS, id_ptr)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + printf("Tweaking................"); + /* Optionally tweak the aggregate key */ + if (!tweak(ctx, &pk, &keygen_cache)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + printf("Signing message........."); + if (!sign(ctx, signer_secrets, signers, msg, sig, &keygen_cache)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + printf("Verifying signature....."); + if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &pk)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + secp256k1_context_destroy(ctx); + return 0; +} diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index d3947160d..d0d01d36e 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -15,7 +15,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/). Signatures are compatible with - * BIP-340 ("Schnorr"). + * BIP-340 ("Schnorr"). There's an example C source file in the module's + * directory (examples/frost.c) that demonstrates how it can be used. * * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public * key tweaking, and adaptor signatures. From fafc1f14d8bcc370ad2390d46b465762c788d421 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 9 Sep 2024 18:47:02 -0700 Subject: [PATCH 09/18] frost trusted dealer: add tests Add api tests, nonce tests, tweak tests, sha256 tag tests, and constant time tests. --- .github/workflows/ci.yml | 33 +- ci/ci.sh | 3 +- src/ctime_tests.c | 103 +++ src/modules/frost/Makefile.am.include | 1 + src/modules/frost/tests_impl.h | 876 ++++++++++++++++++++++++++ src/tests.c | 30 +- 6 files changed, 1030 insertions(+), 16 deletions(-) create mode 100644 src/modules/frost/tests_impl.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36293f136..0c042da32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,7 @@ env: ECDSAADAPTOR: 'no' BPPP: 'no' SCHNORRSIG_HALFAGG: 'no' + FROST: 'no' ### test options SECP256K1_TEST_ITERS: BENCH: 'yes' @@ -79,14 +80,14 @@ jobs: matrix: configuration: - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'} + - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' } - env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'} + - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - - env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'} - - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CPPFLAGS: '-DVERIFY' } + - env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' } + - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } @@ -158,6 +159,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CC: ${{ matrix.cc }} steps: @@ -211,6 +213,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'no' steps: @@ -271,6 +274,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'no' steps: @@ -325,6 +329,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'no' strategy: @@ -389,6 +394,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'no' steps: @@ -450,6 +456,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'no' SECP256K1_TEST_ITERS: 2 @@ -510,6 +517,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'no' CFLAGS: '-fsanitize=undefined,address -g' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' @@ -576,6 +584,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'yes' CC: 'clang' SECP256K1_TEST_ITERS: 32 @@ -632,6 +641,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' CTIMETESTS: 'no' strategy: @@ -688,15 +698,15 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc', FROST: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - BUILD: 'distcheck' steps: @@ -816,6 +826,7 @@ jobs: ECDSAADAPTOR: 'yes' BPPP: 'yes' SCHNORRSIG_HALFAGG: 'yes' + FROST: 'yes' steps: - name: Checkout diff --git a/ci/ci.sh b/ci/ci.sh index 47c4ae67c..e9f603d9d 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -15,7 +15,7 @@ print_environment() { ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ EXPERIMENTAL ECDH RECOVERY SCHNORRSIG SCHNORRSIG_HALFAGG ELLSWIFT \ ECDSA_S2C GENERATOR RANGEPROOF WHITELIST MUSIG ECDSAADAPTOR BPPP \ - SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\ + FROST SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\ EXAMPLES \ HOST WRAPPER_CMD \ CC CFLAGS CPPFLAGS AR NM @@ -83,6 +83,7 @@ esac --enable-module-schnorrsig="$SCHNORRSIG" --enable-module-musig="$MUSIG" --enable-module-ecdsa-adaptor="$ECDSAADAPTOR" \ --enable-module-schnorrsig="$SCHNORRSIG" \ --enable-module-schnorrsig-halfagg="$SCHNORRSIG_HALFAGG" \ + --enable-module-frost="$FROST" \ --enable-examples="$EXAMPLES" \ --enable-ctime-tests="$CTIMETESTS" \ --with-valgrind="$WITH_VALGRIND" \ diff --git a/src/ctime_tests.c b/src/ctime_tests.c index 407d2cc6a..0d3471e01 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -47,6 +47,10 @@ #include "../include/secp256k1_musig.h" #endif +#ifdef ENABLE_MODULE_FROST +#include "include/secp256k1_frost.h" +#endif + static void run_tests(secp256k1_context *ctx, unsigned char *key); int main(void) { @@ -349,4 +353,103 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { CHECK(ret == 1); } #endif + +#ifdef ENABLE_MODULE_FROST + { + secp256k1_pubkey pk[2]; + unsigned char session_id[32]; + unsigned char seed[32]; + secp256k1_frost_secnonce secnonce[2]; + secp256k1_frost_pubnonce pubnonce[2]; + const secp256k1_frost_pubnonce *pubnonce_ptr[2]; + secp256k1_frost_keygen_cache cache; + secp256k1_frost_session session; + secp256k1_frost_partial_sig partial_sig; + const secp256k1_frost_partial_sig *partial_sig_ptr[1]; + unsigned char extra_input[32]; + unsigned char sec_adaptor[32]; + secp256k1_pubkey adaptor; + unsigned char pre_sig[64]; + int nonce_parity; + secp256k1_frost_share shares[2]; + secp256k1_pubkey vss_commitment[2]; + unsigned char key2[32]; + secp256k1_keypair keypair2; + unsigned char id[2][33]; + const unsigned char *id_ptr[2]; + size_t size = 33; + secp256k1_pubkey pubshare[2]; + const secp256k1_pubkey *pubshares_ptr[2]; + + id_ptr[0] = id[0]; + id_ptr[1] = id[1]; + pubnonce_ptr[0] = &pubnonce[0]; + pubnonce_ptr[1] = &pubnonce[1]; + SECP256K1_CHECKMEM_DEFINE(key, 32); + memcpy(seed, key, 32); + seed[0] = seed[0] + 1; + memcpy(extra_input, key, sizeof(extra_input)); + extra_input[0] = extra_input[0] + 2; + memcpy(sec_adaptor, key, sizeof(sec_adaptor)); + sec_adaptor[0] = extra_input[0] + 3; + memcpy(key2, key, sizeof(key2)); + key2[0] = key2[0] + 4; + memcpy(session_id, key, sizeof(session_id)); + session_id[0] = session_id[0] + 5; + partial_sig_ptr[0] = &partial_sig; + pubshares_ptr[0] = &pubshare[0]; + pubshares_ptr[1] = &pubshare[1]; + + CHECK(secp256k1_keypair_create(ctx, &keypair, key)); + CHECK(secp256k1_keypair_create(ctx, &keypair2, key2)); + CHECK(secp256k1_keypair_pub(ctx, &pk[0], &keypair)); + CHECK(secp256k1_keypair_pub(ctx, &pk[1], &keypair2)); + CHECK(secp256k1_ec_pubkey_serialize(ctx, id[0], &size, &pk[0], SECP256K1_EC_COMPRESSED)); + CHECK(secp256k1_ec_pubkey_serialize(ctx, id[1], &size, &pk[1], SECP256K1_EC_COMPRESSED)); + + /* shares_gen */ + SECP256K1_CHECKMEM_UNDEFINE(seed, 32); + ret = secp256k1_frost_shares_gen(ctx, shares, vss_commitment, seed, 2, 2, id_ptr); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + SECP256K1_CHECKMEM_UNDEFINE(&shares[0], sizeof(shares[0])); + SECP256K1_CHECKMEM_UNDEFINE(&shares[1], sizeof(shares[1])); + /* pubkey_gen */ + SECP256K1_CHECKMEM_DEFINE(&vss_commitment[0], sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1], sizeof(secp256k1_pubkey)); + CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[0], 2, id_ptr[0], vss_commitment)); + CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[1], 2, id_ptr[1], vss_commitment)); + CHECK(secp256k1_frost_pubkey_gen(ctx, &cache, pubshares_ptr, 2, id_ptr)); + /* nonce_gen */ + SECP256K1_CHECKMEM_UNDEFINE(session_id, sizeof(session_id)); + CHECK(secp256k1_ec_pubkey_create(ctx, &adaptor, sec_adaptor)); + SECP256K1_CHECKMEM_UNDEFINE(extra_input, sizeof(extra_input)); + SECP256K1_CHECKMEM_UNDEFINE(sec_adaptor, sizeof(sec_adaptor)); + ret = secp256k1_frost_nonce_gen(ctx, &secnonce[0], &pubnonce[0], session_id, &shares[0], msg, &cache, extra_input); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + ret = secp256k1_frost_nonce_gen(ctx, &secnonce[1], &pubnonce[1], session_id, &shares[1], msg, &cache, extra_input); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + /* partial_sign */ + /* Make sure that previous tests don't undefine msg. It's not used as a secret here. */ + SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg)); + CHECK(secp256k1_frost_nonce_process(ctx, &session, pubnonce_ptr, 2, msg, id_ptr[0], id_ptr, &cache, &adaptor) == 1); + ret = secp256k1_frost_partial_sign(ctx, &partial_sig, &secnonce[0], &shares[0], &session, &cache); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + /* adapt */ + SECP256K1_CHECKMEM_DEFINE(&partial_sig, sizeof(partial_sig)); + CHECK(secp256k1_frost_partial_sig_agg(ctx, pre_sig, &session, partial_sig_ptr, 1)); + SECP256K1_CHECKMEM_DEFINE(pre_sig, sizeof(pre_sig)); + CHECK(secp256k1_frost_nonce_parity(ctx, &nonce_parity, &session)); + ret = secp256k1_frost_adapt(ctx, sig, pre_sig, sec_adaptor, nonce_parity); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + /* extract_adaptor */ + ret = secp256k1_frost_extract_adaptor(ctx, sec_adaptor, sig, pre_sig, nonce_parity); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + } +#endif } diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include index f19f854f3..b21aa078f 100644 --- a/src/modules/frost/Makefile.am.include +++ b/src/modules/frost/Makefile.am.include @@ -5,3 +5,4 @@ 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 +noinst_HEADERS += src/modules/frost/tests_impl.h diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h new file mode 100644 index 000000000..137093118 --- /dev/null +++ b/src/modules/frost/tests_impl.h @@ -0,0 +1,876 @@ +/*********************************************************************** + * 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_TESTS_IMPL_H +#define SECP256K1_MODULE_FROST_TESTS_IMPL_H + +#include +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_frost.h" + +#include "session.h" +#include "keygen.h" +#include "../../scalar.h" +#include "../../scratch.h" +#include "../../field.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../util.h" + +static int frost_create_pk(unsigned char *id, const unsigned char *sk) { + int ret; + secp256k1_pubkey pubkey_tmp; + size_t size = 33; + + ret = secp256k1_ec_pubkey_create(CTX, &pubkey_tmp, sk); + ret &= secp256k1_ec_pubkey_serialize(CTX, id, &size, &pubkey_tmp, SECP256K1_EC_COMPRESSED); + + return ret; +} + +/* Simple (non-adaptor, non-tweaked) 3-of-5 FROST generate, sign, verify + * test. */ +void frost_simple_test(void) { + unsigned char sk[5][32]; + secp256k1_frost_pubnonce pubnonce[5]; + const secp256k1_frost_pubnonce *pubnonce_ptr[5]; + unsigned char msg[32]; + secp256k1_pubkey vss_commitment[3]; + secp256k1_xonly_pubkey pk_xonly; + secp256k1_pubkey pk; + unsigned char buf[32]; + secp256k1_frost_share shares[5]; + secp256k1_frost_secnonce secnonce[5]; + secp256k1_pubkey pubshare[5]; + secp256k1_frost_partial_sig partial_sig[5]; + const secp256k1_frost_partial_sig *partial_sig_ptr[5]; + unsigned char final_sig[64]; + secp256k1_frost_session session; + int i; + unsigned char id[5][33]; + const unsigned char *id_ptr[5]; + const secp256k1_pubkey *pubshare_ptr[5]; + secp256k1_frost_keygen_cache cache; + + for (i = 0; i < 5; i++) { + secp256k1_testrand256(sk[i]); + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + id_ptr[i] = id[i]; + pubshare_ptr[i] = &pubshare[i]; + + CHECK(frost_create_pk(id[i], sk[i])); + } + secp256k1_testrand256(buf); + CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, buf, 3, 5, id_ptr) == 1); + for (i = 0; i < 5; i++) { + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], &shares[i], vss_commitment) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], vss_commitment) == 1); + } + CHECK(secp256k1_frost_pubkey_gen(CTX, &cache, pubshare_ptr, 5, id_ptr) == 1); + + secp256k1_testrand256(msg); + for (i = 0; i < 3; i++) { + secp256k1_testrand256(buf); + + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], buf, &shares[i], NULL, NULL, NULL) == 1); + } + for (i = 0; i < 3; i++) { + CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, id_ptr[i], id_ptr, &cache, NULL) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &shares[i], &session, &cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], &pubshare[i], &session, &cache) == 1); + } + CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1); + CHECK(secp256k1_frost_pubkey_get(CTX, &pk, &cache) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_xonly, NULL, &pk) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk_xonly) == 1); +} + +void frost_pubnonce_summing_to_inf(secp256k1_frost_pubnonce *pubnonce) { + secp256k1_ge ge[2]; + int i; + secp256k1_gej summed_nonces[2]; + const secp256k1_frost_pubnonce *pubnonce_ptr[2]; + + ge[0] = secp256k1_ge_const_g; + ge[1] = secp256k1_ge_const_g; + + for (i = 0; i < 2; i++) { + secp256k1_frost_pubnonce_save(&pubnonce[i], ge); + pubnonce_ptr[i] = &pubnonce[i]; + secp256k1_ge_neg(&ge[0], &ge[0]); + secp256k1_ge_neg(&ge[1], &ge[1]); + } + + secp256k1_frost_sum_nonces(CTX, summed_nonces, pubnonce_ptr, 2); + CHECK(secp256k1_gej_is_infinity(&summed_nonces[0])); + CHECK(secp256k1_gej_is_infinity(&summed_nonces[1])); +} + +int frost_memcmp_and_randomize(unsigned char *value, const unsigned char *expected, size_t len) { + int ret; + size_t i; + ret = secp256k1_memcmp_var(value, expected, len); + for (i = 0; i < len; i++) { + value[i] = secp256k1_testrand_bits(8); + } + return ret; +} + +void frost_api_tests(void) { + secp256k1_frost_partial_sig partial_sig[5]; + const secp256k1_frost_partial_sig *partial_sig_ptr[5]; + secp256k1_frost_partial_sig invalid_partial_sig; + const secp256k1_frost_partial_sig *invalid_partial_sig_ptr[5]; + unsigned char final_sig[64]; + unsigned char pre_sig[64]; + unsigned char buf[32]; + unsigned char sk[5][32]; + unsigned char max64[64]; + unsigned char zeros68[68] = { 0 }; + unsigned char session_id[5][32]; + unsigned char seed[32]; + secp256k1_frost_secnonce secnonce[5]; + secp256k1_frost_secnonce secnonce_tmp; + secp256k1_frost_secnonce invalid_secnonce; + secp256k1_frost_pubnonce pubnonce[5]; + const secp256k1_frost_pubnonce *pubnonce_ptr[5]; + unsigned char pubnonce_ser[66]; + secp256k1_frost_pubnonce inf_pubnonce[5]; + secp256k1_frost_pubnonce invalid_pubnonce; + const secp256k1_frost_pubnonce *invalid_pubnonce_ptr[5]; + unsigned char msg[32]; + secp256k1_pubkey pk; + secp256k1_xonly_pubkey pk_xonly; + secp256k1_frost_keygen_cache keygen_cache; + secp256k1_frost_keygen_cache invalid_keygen_cache; + secp256k1_frost_session session[5]; + secp256k1_frost_session invalid_session; + secp256k1_xonly_pubkey invalid_pk; + unsigned char tweak[32]; + int nonce_parity; + unsigned char sec_adaptor[32]; + unsigned char sec_adaptor1[32]; + secp256k1_pubkey adaptor; + secp256k1_pubkey vss_commitment[3]; + secp256k1_pubkey invalid_vss_commitment[3]; + secp256k1_pubkey invalid_pubshare; + secp256k1_frost_share shares[5]; + secp256k1_frost_share invalid_share; + secp256k1_pubkey pubshare[5]; + int i; + unsigned char id[5][33]; + const unsigned char *id_ptr[5]; + const secp256k1_pubkey *pubshare_ptr[5]; + + /** setup **/ + memset(max64, 0xff, sizeof(max64)); + /* Simulate structs being uninitialized by setting it to 0s. We don't want + * to produce undefined behavior by actually providing uninitialized + * structs. */ + memset(&invalid_share, 0, sizeof(invalid_share)); + memset(&invalid_pk, 0, sizeof(invalid_pk)); + memset(&invalid_secnonce, 0, sizeof(invalid_secnonce)); + memset(&invalid_partial_sig, 0, sizeof(invalid_partial_sig)); + memset(&invalid_pubnonce, 0, sizeof(invalid_pubnonce)); + memset(&invalid_pubshare, 0, sizeof(invalid_pubshare)); + memset(&invalid_keygen_cache, 0, sizeof(invalid_keygen_cache)); + memset(&invalid_session, 0, sizeof(invalid_session)); + frost_pubnonce_summing_to_inf(inf_pubnonce); + + secp256k1_testrand256(sec_adaptor); + secp256k1_testrand256(msg); + secp256k1_testrand256(tweak); + CHECK(secp256k1_ec_pubkey_create(CTX, &adaptor, sec_adaptor) == 1); + secp256k1_testrand256(seed); + for (i = 0; i < 5; i++) { + pubnonce_ptr[i] = &pubnonce[i]; + invalid_pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + invalid_partial_sig_ptr[i] = &partial_sig[i]; + id_ptr[i] = id[i]; + pubshare_ptr[i] = &pubshare[i]; + secp256k1_testrand256(session_id[i]); + secp256k1_testrand256(sk[i]); + CHECK(frost_create_pk(id[i], sk[i])); + } + for (i = 0; i < 3; i++) { + invalid_vss_commitment[i] = vss_commitment[i]; + } + + invalid_pubnonce_ptr[0] = &invalid_pubnonce; + invalid_partial_sig_ptr[0] = &invalid_partial_sig; + invalid_vss_commitment[0] = invalid_pubshare; + + /** main test body **/ + + /** Key generation **/ + CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5, id_ptr) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, NULL, vss_commitment, seed, 3, 5, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, NULL, seed, 3, 5, id_ptr)); + for (i = 0; i < 5; i++) { + CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0); + } + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, vss_commitment, NULL, 3, 5, id_ptr)); + for (i = 0; i < 5; i++) { + CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0); + } + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 0, 5, id_ptr)); + for (i = 0; i < 5; i++) { + CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0); + } + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 0, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 2, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5, NULL)); + for (i = 0; i < 5; i++) { + CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0); + } + + CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5, id_ptr) == 1); + + /* Share verification */ + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[0], &shares[0], vss_commitment) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[1], &shares[0], vss_commitment) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, NULL, &shares[0], vss_commitment)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[0], NULL, vss_commitment)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[0], &invalid_share, vss_commitment)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[0], &shares[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[0], &shares[0], invalid_vss_commitment)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 0, id_ptr[4], &shares[0], vss_commitment)); + + /* Compute public verification share */ + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], vss_commitment) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, NULL, 3, id_ptr[0], vss_commitment)); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, NULL, vss_commitment)); + CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], NULL)); + CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], invalid_vss_commitment)); + CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 0, id_ptr[0], invalid_vss_commitment)); + CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 0, id_ptr[0], NULL)); + CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], invalid_vss_commitment)); + CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], NULL)); + CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0); + + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], vss_commitment) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[1], 3, id_ptr[1], vss_commitment) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[2], 3, id_ptr[2], vss_commitment) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[3], 3, id_ptr[3], vss_commitment) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[4], 3, id_ptr[4], vss_commitment) == 1); + + /* pubkey_gen */ + CHECK(secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, id_ptr) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, NULL, pubshare_ptr, 5, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, &keygen_cache, NULL, 5, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 0, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, NULL)); + + /* pubkey_get */ + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, NULL, &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, &pk, NULL)); + CHECK(secp256k1_memcmp_var(&pk, zeros68, sizeof(pk)) == 0); + CHECK(secp256k1_frost_pubkey_get(CTX, &pk, &keygen_cache) == 1); + + /* tweak_add */ + { + int (*tweak_func[2]) (const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32); + tweak_func[0] = secp256k1_frost_pubkey_ec_tweak_add; + tweak_func[1] = secp256k1_frost_pubkey_xonly_tweak_add; + + for (i = 0; i < 2; i++) { + secp256k1_pubkey tmp_output_pk; + secp256k1_frost_keygen_cache tmp_keygen_cache = keygen_cache; + CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_keygen_cache, tweak) == 1); + /* Reset keygen_cache */ + tmp_keygen_cache = keygen_cache; + CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_keygen_cache, tweak) == 1); + tmp_keygen_cache = keygen_cache; + CHECK((*tweak_func[i])(CTX, NULL, &tmp_keygen_cache, tweak) == 1); + tmp_keygen_cache = keygen_cache; + CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, NULL, tweak)); + CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0); + tmp_keygen_cache = keygen_cache; + CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &tmp_keygen_cache, NULL)); + CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0); + tmp_keygen_cache = keygen_cache; + CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_keygen_cache, max64) == 0); + CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0); + tmp_keygen_cache = keygen_cache; + /* Uninitialized keygen_cache */ + CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &invalid_keygen_cache, tweak)); + CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0); + } + } + + /** Session creation **/ + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_xonly, NULL, &pk) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &keygen_cache, max64) == 1); + CHECK_ILLEGAL(STATIC_CTX, secp256k1_frost_nonce_gen(STATIC_CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &keygen_cache, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, NULL, &pubnonce[0], session_id[0], &shares[0], msg, &keygen_cache, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], NULL, session_id[0], &shares[0], msg, &keygen_cache, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, &shares[0], msg, &keygen_cache, max64)); + CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0); + /* no seckey and session_id is 0 */ + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], zeros68, NULL, msg, &keygen_cache, max64) == 0); + CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0); + /* session_id 0 is fine when a seckey is provided */ + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], zeros68, &shares[0], msg, &keygen_cache, max64) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, msg, &keygen_cache, max64) == 1); + /* invalid share */ + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &invalid_share, msg, &keygen_cache, max64)); + CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], NULL, &keygen_cache, max64) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, NULL, max64) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &invalid_keygen_cache, max64)); + CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &keygen_cache, NULL) == 1); + + /* Every in-argument except session_id can be NULL */ + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, NULL, NULL, NULL) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[1], &pubnonce[1], session_id[1], &shares[1], NULL, NULL, NULL) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[2], &pubnonce[2], session_id[2], &shares[2], NULL, NULL, NULL) == 1); + + /** Serialize and parse public nonces **/ + CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, NULL, &pubnonce[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, NULL)); + CHECK(frost_memcmp_and_randomize(pubnonce_ser, zeros68, sizeof(pubnonce_ser)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &invalid_pubnonce)); + CHECK(frost_memcmp_and_randomize(pubnonce_ser, zeros68, sizeof(pubnonce_ser)) == 0); + CHECK(secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce[0]) == 1); + + CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], pubnonce_ser) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_parse(CTX, NULL, pubnonce_ser)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], NULL)); + CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], zeros68) == 0); + CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], pubnonce_ser) == 1); + + { + /* Check that serialize and parse results in the same value */ + secp256k1_frost_pubnonce tmp; + CHECK(secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce[0]) == 1); + CHECK(secp256k1_frost_pubnonce_parse(CTX, &tmp, pubnonce_ser) == 1); + CHECK(secp256k1_memcmp_var(&tmp, &pubnonce[0], sizeof(tmp)) == 0); + } + + /** Process nonces **/ + CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, &keygen_cache, &adaptor) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, NULL, pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, &keygen_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], NULL, 3, msg, id_ptr[0], id_ptr, &keygen_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 0, msg, id_ptr[0], id_ptr, &keygen_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], invalid_pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, &keygen_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, NULL, id_ptr[0], id_ptr, &keygen_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, NULL, id_ptr, &keygen_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, id_ptr[0], NULL, &keygen_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, NULL, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, &invalid_keygen_cache, &adaptor)); + CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, &keygen_cache, NULL) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, &keygen_cache, (secp256k1_pubkey *)&invalid_pk)); + + CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, id_ptr[0], id_ptr, &keygen_cache, &adaptor) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, id_ptr[1], id_ptr, &keygen_cache, &adaptor) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, id_ptr[2], id_ptr, &keygen_cache, &adaptor) == 1); + + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &keygen_cache) == 1); + /* The secnonce is set to 0 and subsequent signing attempts fail */ + CHECK(secp256k1_memcmp_var(&secnonce_tmp, zeros68, sizeof(secnonce_tmp)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &keygen_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, NULL, &secnonce_tmp, &shares[0], &session[0], &keygen_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], NULL, &shares[0], &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &invalid_secnonce, &shares[0], &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, NULL, &session[0], &keygen_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &invalid_share, &session[0], &keygen_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], NULL, &keygen_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &invalid_session, &keygen_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], NULL)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &invalid_keygen_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], &shares[0], &session[0], &keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], &shares[1], &session[1], &keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], &shares[2], &session[2], &keygen_cache) == 1); + + CHECK(secp256k1_frost_partial_sig_serialize(CTX, buf, &partial_sig[0]) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_serialize(CTX, NULL, &partial_sig[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_serialize(CTX, buf, NULL)); + CHECK(secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], buf) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_parse(CTX, NULL, buf)); + CHECK(secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], max64) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], NULL)); + + { + /* Check that serialize and parse results in the same value */ + secp256k1_frost_partial_sig tmp; + CHECK(secp256k1_frost_partial_sig_serialize(CTX, buf, &partial_sig[0]) == 1); + CHECK(secp256k1_frost_partial_sig_parse(CTX, &tmp, buf) == 1); + CHECK(secp256k1_memcmp_var(&tmp, &partial_sig[0], sizeof(tmp)) == 0); + } + + /** Partial signature verification */ + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[0], &pubshare[0], &session[0], &keygen_cache) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, NULL, &pubnonce[0], &pubshare[0], &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &invalid_partial_sig, &pubnonce[0], &pubshare[0], &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], NULL, &pubshare[0], &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &invalid_pubnonce, &pubshare[0], &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], NULL, &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &invalid_pubshare, &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], NULL, &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &invalid_session, &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &invalid_keygen_cache)); + + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], &pubshare[1], &session[1], &keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], &pubshare[2], &session[2], &keygen_cache) == 1); + + /** Signature aggregation and verification */ + CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 3) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, NULL, &session[0], partial_sig_ptr, 3)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, NULL, partial_sig_ptr, 3)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &invalid_session, partial_sig_ptr, 3)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], NULL, 3)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], invalid_partial_sig_ptr, 3)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 0)); + CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 1) == 1); + CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[1], partial_sig_ptr, 2) == 1); + CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[2], partial_sig_ptr, 3) == 1); + + /** Adaptor signature verification */ + CHECK(secp256k1_frost_nonce_parity(CTX, &nonce_parity, &session[0]) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, NULL, &session[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, &nonce_parity, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, &nonce_parity, &invalid_session)); + + CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, NULL, pre_sig, sec_adaptor, 0)); + CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, NULL, sec_adaptor, 0)); + CHECK(secp256k1_frost_adapt(CTX, final_sig, max64, sec_adaptor, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, pre_sig, NULL, 0)); + CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, max64, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, 2)); + /* sig and pre_sig argument point to the same location */ + memcpy(final_sig, pre_sig, sizeof(final_sig)); + CHECK(secp256k1_frost_adapt(CTX, final_sig, final_sig, sec_adaptor, nonce_parity) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk_xonly) == 1); + + CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk_xonly) == 1); + + /** Secret adaptor can be extracted from signature */ + CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, nonce_parity) == 1); + CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) == 0); + /* wrong nonce parity */ + CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, !nonce_parity) == 1); + CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) != 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, NULL, final_sig, pre_sig, 0)); + CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, NULL, pre_sig, 0)); + CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, max64, pre_sig, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, NULL, 0)); + CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, max64, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, 2)); +} + +void frost_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes) { + secp256k1_scalar k1[2], k2[2]; + + secp256k1_nonce_function_frost(k1, args[0], args[1], args[2], args[3], args[4]); + secp256k1_testrand_flip(args[n_flip], n_bytes); + secp256k1_nonce_function_frost(k2, args[0], args[1], args[2], args[3], args[4]); + CHECK(secp256k1_scalar_eq(&k1[0], &k2[0]) == 0); + CHECK(secp256k1_scalar_eq(&k1[1], &k2[1]) == 0); +} + +void frost_nonce_test(void) { + unsigned char *args[5]; + unsigned char session_id[32]; + unsigned char sk[32]; + unsigned char msg[32]; + unsigned char agg_pk[32]; + unsigned char extra_input[32]; + int i, j; + secp256k1_scalar k[5][2]; + + secp256k1_testrand_bytes_test(session_id, sizeof(session_id)); + secp256k1_testrand_bytes_test(sk, sizeof(sk)); + secp256k1_testrand_bytes_test(msg, sizeof(msg)); + secp256k1_testrand_bytes_test(agg_pk, sizeof(agg_pk)); + secp256k1_testrand_bytes_test(extra_input, sizeof(extra_input)); + + /* Check that a bitflip in an argument results in different nonces. */ + args[0] = session_id; + args[1] = msg; + args[2] = sk; + args[3] = agg_pk; + args[4] = extra_input; + for (i = 0; i < COUNT; i++) { + frost_nonce_bitflip(args, 0, sizeof(session_id)); + frost_nonce_bitflip(args, 1, sizeof(msg)); + frost_nonce_bitflip(args, 2, sizeof(sk)); + frost_nonce_bitflip(args, 3, sizeof(agg_pk)); + frost_nonce_bitflip(args, 4, sizeof(extra_input)); + } + /* Check that if any argument is NULL, a different nonce is produced than if + * any other argument is NULL. */ + memcpy(msg, session_id, sizeof(msg)); + memcpy(sk, session_id, sizeof(sk)); + memcpy(agg_pk, session_id, sizeof(agg_pk)); + memcpy(extra_input, session_id, sizeof(extra_input)); + secp256k1_nonce_function_frost(k[0], args[0], args[1], args[2], args[3], args[4]); + secp256k1_nonce_function_frost(k[1], args[0], NULL, args[2], args[3], args[4]); + secp256k1_nonce_function_frost(k[2], args[0], args[1], NULL, args[3], args[4]); + secp256k1_nonce_function_frost(k[3], args[0], args[1], args[2], NULL, args[4]); + secp256k1_nonce_function_frost(k[4], args[0], args[1], args[2], args[3], NULL); + for (i = 0; i < 5; i++) { + CHECK(!secp256k1_scalar_eq(&k[i][0], &k[i][1])); + for (j = i+1; j < 5; j++) { + CHECK(!secp256k1_scalar_eq(&k[i][0], &k[j][0])); + CHECK(!secp256k1_scalar_eq(&k[i][1], &k[j][1])); + } + } +} + +void frost_sha256_tag_test_internal(secp256k1_sha256 *sha_tagged, unsigned char *tag, size_t taglen) { + secp256k1_sha256 sha; + unsigned char buf[32]; + unsigned char buf2[32]; + size_t i; + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, tag, taglen); + secp256k1_sha256_finalize(&sha, buf); + /* buf = SHA256(tag) */ + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, buf, 32); + secp256k1_sha256_write(&sha, buf, 32); + /* Is buffer fully consumed? */ + CHECK((sha.bytes & 0x3F) == 0); + + /* Compare with tagged SHA */ + for (i = 0; i < 8; i++) { + CHECK(sha_tagged->s[i] == sha.s[i]); + } + secp256k1_sha256_write(&sha, buf, 32); + secp256k1_sha256_write(sha_tagged, buf, 32); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_sha256_finalize(sha_tagged, buf2); + CHECK(secp256k1_memcmp_var(buf, buf2, 32) == 0); +} + +/* Attempts to create a signature for the group public key using given secret + * keys and keygen_cache. */ +void frost_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const secp256k1_frost_share *sr0, const secp256k1_frost_share *sr1, const secp256k1_frost_share *sr2, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char * const* ids33, const secp256k1_pubkey *sr_pk0, const secp256k1_pubkey *sr_pk1, const secp256k1_pubkey *sr_pk2) { + unsigned char session_id[3][32]; + unsigned char msg[32]; + secp256k1_frost_secnonce secnonce[3]; + secp256k1_frost_pubnonce pubnonce[3]; + const secp256k1_frost_pubnonce *pubnonce_ptr[3]; + secp256k1_frost_session session[5]; + secp256k1_frost_partial_sig partial_sig[3]; + const secp256k1_frost_partial_sig *partial_sig_ptr[3]; + unsigned char final_sig[64]; + int i; + + for (i = 0; i < 3; i++) { + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + + secp256k1_testrand256(session_id[i]); + } + secp256k1_testrand256(msg); + + + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], sr0, NULL, NULL, NULL) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[1], &pubnonce[1], session_id[1], sr1, NULL, NULL, NULL) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[2], &pubnonce[2], session_id[2], sr2, NULL, NULL, NULL) == 1); + + CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, ids33[0], ids33, keygen_cache, NULL) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, ids33[1], ids33, keygen_cache, NULL) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, ids33[2], ids33, keygen_cache, NULL) == 1); + + + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], sr0, &session[0], keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], sr1, &session[1], keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], sr2, &session[2], keygen_cache) == 1); + + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], sr_pk0, &session[0], keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], sr_pk1, &session[1], keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], sr_pk2, &session[2], keygen_cache) == 1); + + CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session[0], partial_sig_ptr, 3) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), agg_pk) == 1); +} + +/* Create group public key P[0], tweak multiple times (using xonly and + * ordinary tweaking) and test signing. */ +void frost_tweak_test(void) { + unsigned char sk[5][32]; + secp256k1_pubkey pubshare[5]; + secp256k1_frost_keygen_cache keygen_cache; + enum { N_TWEAKS = 8 }; + secp256k1_pubkey P[N_TWEAKS + 1]; + secp256k1_xonly_pubkey P_xonly[N_TWEAKS + 1]; + unsigned char seed[32]; + secp256k1_pubkey vss_commitment[3]; + secp256k1_frost_share shares[5]; + int i; + unsigned char id[5][33]; + const unsigned char *id_ptr[5]; + const secp256k1_pubkey *pubshare_ptr[5]; + + /* Key Setup */ + for (i = 0; i < 5; i++) { + secp256k1_testrand256(sk[i]); + id_ptr[i] = id[i]; + pubshare_ptr[i] = &pubshare[i]; + + CHECK(frost_create_pk(id[i], sk[i])); + } + secp256k1_testrand256(seed); + CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5, id_ptr) == 1); + for (i = 0; i < 5; i++) { + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], &shares[i], vss_commitment) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], vss_commitment) == 1); + } + /* Compute P0 and test signing for it */ + CHECK(secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, id_ptr) == 1); + CHECK(secp256k1_frost_pubkey_get(CTX, &P[0], &keygen_cache) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &P_xonly[0], NULL, &P[0])); + frost_tweak_test_helper(&P_xonly[0], &shares[0], &shares[1], &shares[2], &keygen_cache, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); + + /* Compute Pi = f(Pj) + tweaki*G where where j = i-1 and try signing for */ + /* that key. If xonly is set to true, the function f is normalizes the input */ + /* point to have an even X-coordinate ("xonly-tweaking"). */ + /* Otherwise, the function f is the identity function. */ + for (i = 1; i <= N_TWEAKS; i++) { + unsigned char tweak[32]; + int P_parity; + int xonly = secp256k1_testrand_bits(1); + + secp256k1_testrand256(tweak); + if (xonly) { + CHECK(secp256k1_frost_pubkey_xonly_tweak_add(CTX, &P[i], &keygen_cache, tweak) == 1); + } else { + CHECK(secp256k1_frost_pubkey_ec_tweak_add(CTX, &P[i], &keygen_cache, tweak) == 1); + } + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &P_xonly[i], &P_parity, &P[i])); + /* Check that frost_pubkey_tweak_add produces same result as */ + /* xonly_pubkey_tweak_add or ec_pubkey_tweak_add. */ + if (xonly) { + unsigned char P_serialized[32]; + CHECK(secp256k1_xonly_pubkey_serialize(CTX, P_serialized, &P_xonly[i])); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, P_serialized, P_parity, &P_xonly[i-1], tweak) == 1); + } else { + secp256k1_pubkey tmp_key = P[i-1]; + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &tmp_key, tweak)); + CHECK(secp256k1_memcmp_var(&tmp_key, &P[i], sizeof(tmp_key)) == 0); + } + /* Test signing for P[i] */ + frost_tweak_test_helper(&P_xonly[i], &shares[0], &shares[1], &shares[2], &keygen_cache, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); + } +} + +/* Performs a FROST DKG */ +void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1_frost_share *shares, const unsigned char * const *ids33) { + secp256k1_pubkey vss_commitment[3]; + unsigned char seed[32]; + int i; + secp256k1_pubkey pubshare[5]; + const secp256k1_pubkey *pubshare_ptr[5]; + + secp256k1_testrand256(seed); + for (i = 0; i < 5; i++) { + pubshare_ptr[i] = &pubshare[i]; + } + CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5, ids33) == 1); + for (i = 0; i < 5; i++) { + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, ids33[i], vss_commitment) == 1); + } + CHECK(secp256k1_frost_pubkey_gen(CTX, keygen_cache, pubshare_ptr, 5, ids33) == 1); +} + +/* Signs a message with a FROST keypair */ +int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_share *shares, const unsigned char * const *ids33, const unsigned char *msg, const secp256k1_pubkey *adaptor, secp256k1_frost_keygen_cache *keygen_cache) { + unsigned char session_id[3][32]; + secp256k1_frost_secnonce secnonce[3]; + secp256k1_frost_pubnonce pubnonce[3]; + const secp256k1_frost_pubnonce *pubnonce_ptr[3]; + secp256k1_frost_partial_sig partial_sig[5]; + const secp256k1_frost_partial_sig *partial_sig_ptr[5]; + secp256k1_frost_session session; + int i; + int nonce_parity; + secp256k1_frost_session_internal session_i; + + for (i = 0; i < 3; i++) { + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + } + + for (i = 0; i < 3; i++) { + secp256k1_testrand256(session_id[i]); + + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id[i], &shares[i], NULL, NULL, NULL) == 1); + } + for (i = 0; i < 3; i++) { + CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, ids33[i], ids33, keygen_cache, adaptor) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &shares[i], &session, keygen_cache) == 1); + } + CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1); + + CHECK(secp256k1_frost_nonce_parity(CTX, &nonce_parity, &session)); + + secp256k1_frost_session_load(CTX, &session_i, &session); + + return nonce_parity; +} + +void frost_rand_scalar(secp256k1_scalar *scalar) { + unsigned char buf32[32]; + secp256k1_testrand256(buf32); + secp256k1_scalar_set_b32(scalar, buf32, NULL); +} + +void frost_multi_hop_lock_tests(void) { + secp256k1_frost_share shares_a[5]; + secp256k1_frost_share shares_b[5]; + secp256k1_xonly_pubkey pk_a; + secp256k1_xonly_pubkey pk_b; + secp256k1_pubkey tmp; + unsigned char sk_a[5][32]; + unsigned char sk_b[5][32]; + unsigned char asig_ab[64]; + unsigned char asig_bc[64]; + unsigned char pop[32]; + secp256k1_pubkey pubkey_pop; + unsigned char tx_ab[32]; + unsigned char tx_bc[32]; + unsigned char buf[32]; + secp256k1_scalar t1, t2, tp; + secp256k1_pubkey l, r; + secp256k1_ge l_ge, r_ge; + secp256k1_scalar deckey; + unsigned char sig_ab[64]; + unsigned char sig_bc[64]; + int nonce_parity_ab; + int nonce_parity_bc; + int i; + unsigned char id_a[5][33]; + const unsigned char *id_ptr_a[5]; + unsigned char id_b[5][33]; + const unsigned char *id_ptr_b[5]; + secp256k1_frost_keygen_cache cache_a; + secp256k1_frost_keygen_cache cache_b; + + /* Alice DKG */ + for (i = 0; i < 5; i++) { + secp256k1_testrand256(sk_a[i]); + id_ptr_a[i] = id_a[i]; + + CHECK(frost_create_pk(id_a[i], sk_a[i])); + } + frost_dkg_test_helper(&cache_a, shares_a, id_ptr_a); + CHECK(secp256k1_frost_pubkey_get(CTX, &tmp, &cache_a) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_a, NULL, &tmp) == 1); + + /* Bob DKG */ + for (i = 0; i < 5; i++) { + secp256k1_testrand256(sk_b[i]); + id_ptr_b[i] = id_b[i]; + + CHECK(frost_create_pk(id_b[i], sk_b[i])); + } + frost_dkg_test_helper(&cache_b, shares_b, id_ptr_b); + CHECK(secp256k1_frost_pubkey_get(CTX, &tmp, &cache_b) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_b, NULL, &tmp) == 1); + + /* Carol setup */ + /* Proof of payment */ + secp256k1_testrand256(pop); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey_pop, pop)); + + /* Alice setup */ + secp256k1_testrand256(tx_ab); + frost_rand_scalar(&t1); + frost_rand_scalar(&t2); + secp256k1_scalar_add(&tp, &t1, &t2); + /* Left lock */ + secp256k1_pubkey_load(CTX, &l_ge, &pubkey_pop); + CHECK(secp256k1_eckey_pubkey_tweak_add(&l_ge, &t1)); + secp256k1_pubkey_save(&l, &l_ge); + /* Right lock */ + secp256k1_pubkey_load(CTX, &r_ge, &pubkey_pop); + CHECK(secp256k1_eckey_pubkey_tweak_add(&r_ge, &tp)); + secp256k1_pubkey_save(&r, &r_ge); + /* Encrypt Alice's signature with the left lock as the encryption key */ + nonce_parity_ab = frost_sign_test_helper(asig_ab, shares_a, id_ptr_a, tx_ab, &l, &cache_a); + + /* Bob setup */ + CHECK(secp256k1_frost_verify_adaptor(CTX, asig_ab, tx_ab, &pk_a, &l, nonce_parity_ab) == 1); + secp256k1_testrand256(tx_bc); + /* Encrypt Bob's signature with the right lock as the encryption key */ + nonce_parity_bc = frost_sign_test_helper(asig_bc, shares_b, id_ptr_b, tx_bc, &r, &cache_b); + + /* Carol decrypt */ + CHECK(secp256k1_frost_verify_adaptor(CTX, asig_bc, tx_bc, &pk_b, &r, nonce_parity_bc) == 1); + secp256k1_scalar_set_b32(&deckey, pop, NULL); + secp256k1_scalar_add(&deckey, &deckey, &tp); + secp256k1_scalar_get_b32(buf, &deckey); + CHECK(secp256k1_frost_adapt(CTX, sig_bc, asig_bc, buf, nonce_parity_bc)); + CHECK(secp256k1_schnorrsig_verify(CTX, sig_bc, tx_bc, sizeof(tx_bc), &pk_b) == 1); + + /* Bob recover and decrypt */ + CHECK(secp256k1_frost_extract_adaptor(CTX, buf, sig_bc, asig_bc, nonce_parity_bc)); + secp256k1_scalar_set_b32(&deckey, buf, NULL); + secp256k1_scalar_negate(&t2, &t2); + secp256k1_scalar_add(&deckey, &deckey, &t2); + secp256k1_scalar_get_b32(buf, &deckey); + CHECK(secp256k1_frost_adapt(CTX, sig_ab, asig_ab, buf, nonce_parity_ab)); + CHECK(secp256k1_schnorrsig_verify(CTX, sig_ab, tx_ab, sizeof(tx_ab), &pk_a) == 1); + + /* Alice recover and derive proof of payment */ + CHECK(secp256k1_frost_extract_adaptor(CTX, buf, sig_ab, asig_ab, nonce_parity_ab)); + secp256k1_scalar_set_b32(&deckey, buf, NULL); + secp256k1_scalar_negate(&t1, &t1); + secp256k1_scalar_add(&deckey, &deckey, &t1); + secp256k1_scalar_get_b32(buf, &deckey); + CHECK(secp256k1_memcmp_var(buf, pop, 32) == 0); +} + +void run_frost_tests(void) { + int i; + + for (i = 0; i < COUNT; i++) { + frost_simple_test(); + } + frost_api_tests(); + frost_nonce_test(); + for (i = 0; i < COUNT; i++) { + /* Run multiple times to ensure that pk and nonce have different y + * parities */ + frost_tweak_test(); + } + for (i = 0; i < COUNT; i++) { + frost_multi_hop_lock_tests(); + } +} + +#endif diff --git a/src/tests.c b/src/tests.c index 7c2f30e35..2ebdab828 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4075,13 +4075,27 @@ static void test_add_neg_y_diff_x(void) { static void test_ge_bytes(void) { int i; - for (i = 0; i < COUNT; i++) { + for (i = 0; i < COUNT + 1; i++) { unsigned char buf[64]; secp256k1_ge p, q; - random_group_element_test(&p); - secp256k1_ge_to_bytes(buf, &p); - secp256k1_ge_from_bytes(&q, buf); + if (i == 0) { + secp256k1_ge_set_infinity(&p); + } else { + random_group_element_test(&p); + } + + if (!secp256k1_ge_is_infinity(&p)) { + secp256k1_ge_to_bytes(buf, &p); + + secp256k1_ge_from_bytes(&q, buf); + CHECK(secp256k1_ge_eq_var(&p, &q)); + + secp256k1_ge_from_bytes_ext(&q, buf); + CHECK(secp256k1_ge_eq_var(&p, &q)); + } + secp256k1_ge_to_bytes_ext(buf, &p); + secp256k1_ge_from_bytes_ext(&q, buf); CHECK(secp256k1_ge_eq_var(&p, &q)); } } @@ -7502,6 +7516,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/ecdsa_adaptor/tests_impl.h" #endif +#ifdef ENABLE_MODULE_FROST +# include "modules/frost/tests_impl.h" +#endif + static void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -7892,6 +7910,10 @@ int main(int argc, char **argv) { run_ecdsa_adaptor_tests(); #endif +#ifdef ENABLE_MODULE_FROST + run_frost_tests(); +#endif + /* util tests */ run_secp256k1_memczero_test(); run_secp256k1_byteorder_tests(); From f41560c0cd06d03e0f39dd5c72cc0b05d73366e5 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 9 Sep 2024 18:48:42 -0700 Subject: [PATCH 10/18] frost trusted dealer: add documentation file This commit adds a documentation file with detailed instructions for how to use the module properly. --- README.md | 2 +- include/secp256k1_frost.h | 3 ++ src/modules/frost/frost.md | 100 +++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/modules/frost/frost.md diff --git a/README.md b/README.md index 76bc0e10c..f7b3b0468 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Added features: * Experimental module for Confidential Assets (Pedersen commitments, range proofs, and [surjection proofs](src/modules/surjection/surjection.md)). * Experimental module for Bulletproofs++ range proofs. * Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md). -* Experimental module for FROST. +* Experimental module for [FROST](src/modules/frost/frost.md). Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable. diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index d0d01d36e..f083ce4d6 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -21,6 +21,9 @@ extern "C" { * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public * key tweaking, and adaptor signatures. * + * It is recommended to read the documentation in this include file carefully. + * Further notes on API usage can be found in src/modules/frost/frost.md + * * 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. */ diff --git a/src/modules/frost/frost.md b/src/modules/frost/frost.md new file mode 100644 index 000000000..62e23d0c5 --- /dev/null +++ b/src/modules/frost/frost.md @@ -0,0 +1,100 @@ +Notes on the frost module API +=========================== + +The following sections contain additional notes on the API of the frost module +(`include/secp256k1_frost.h`). A usage example can be found in +`examples/frost.c`. + +# API misuse + + + + +Users of the frost module must take great care to make sure of the following: + +1. Each participant exchanges public keys for identification and authentication + purposes. Partipants must provide the same public key to each other + participant. +2. The dealer establishes a secure communications channel with each participant + and uses that channel to transmit shares during key generation. +3. A unique set of coefficients per key generation session is generated in + `secp256k1_frost_shares_gen`. See the corresponding comment in + `include/secp256k1_frost.h` for how to ensure that. +4. The `pubnonces` provided to `secp256k1_frost_nonce_process` are sorted by + the corresponding lexicographic ordering of the x-only pubkey of each + participant, and the `ids33` provided to `secp256k1_frost_nonce_process` + are sorted lexicographically. +5. A unique nonce per signing session is generated in `secp256k1_frost_nonce_gen`. + See the corresponding comment in `include/secp256k1_frost.h` for how to ensure that. +6. The `secp256k1_frost_secnonce` structure is never copied or serialized. + See also the comment on `secp256k1_frost_secnonce` in `include/secp256k1_frost.h`. +7. Opaque data structures are never written to or read from directly. + Instead, only the provided accessor functions are used. +8. If adaptor signatures are used, all partial signatures are verified. + +# Key Generation + +1. Generate a keypair with `secp256k1_keypair_create` and obtain the public key + with `secp256k1_keypair_pub`, and distribute it to each other participant to + be used as an authentication key and identifier. +2. The trusted dealer generate a VSS commitment and shares with + `secp256k1_frost_shares_gen`. The VSS commitment must be broadcast to all + participants. Assign each participant a share according to the order of + `ids33` and distribute the shares to the participants using a secure + channel. +3. After receiving a share and VSS commitment from the dealer, call + `secp256k1_frost_share_verify` to verify the share. +4. Compute the public verification shares for each participant by calling + `secp256k1_frost_compute_pubshare` with the public key of the participant. + This share is required by `secp256k1_frost_partial_sig_verify` to verify + partial signatures generated by `secp256k1_frost_partial_sign`, and public + shares are required by `secp256k1_frost_pubkey_gen` to generate the group + public key. +5. Generate the group key by passing the public shares of all participants to + `secp256k1_frost_pubkey_gen`, which will initialize a key generation + context. The context can be passed to `secp256k1_frost_pubkey_get` to obtain + the group public key. + +# Tweaking + +A (Taproot) tweak can be added to the resulting public key with +`secp256k1_xonly_pubkey_tweak_add`, after converting it to an xonly pubkey if +necessary with `secp256k1_xonly_pubkey_from_pubkey`. + +An ordinary tweak can be added to the resulting public key with +`secp256k1_ec_pubkey_tweak_add`, after converting it to an ordinary pubkey if +necessary with `secp256k1_frost_pubkey_get`. + +Tweaks can also be chained together by tweaking an already tweaked key. + +# Signing + +1. Initialize the key generation context with `secp256k1_frost_pubkey_gen`. +2. Optionally add a tweak by calling `secp256k1_frost_pubkey_tweak` and then + `secp256k1_frost_pubkey_xonly_tweak_add` for a Taproot tweak and + `secp256k1_frost_pubkey_ec_tweak_add` for an ordinary tweak. +3. Generate a pair of secret and public nonce with `secp256k1_frost_nonce_gen` + and send the public nonce to the other signers. +4. Process the aggregate nonce with `secp256k1_frost_nonce_process`. +5. Create a partial signature with `secp256k1_frost_partial_sign`. +6. Verify the partial signatures (optional in some scenarios) with + `secp256k1_frost_partial_sig_verify`. +7. Someone (not necessarily the signer) obtains all partial signatures and + aggregates them into the final Schnorr signature using + `secp256k1_frost_partial_sig_agg`. + +The aggregate signature can be verified with `secp256k1_schnorrsig_verify`. + +Note that steps 1 to 3 can happen before the message to be signed is known to +the signers. Therefore, the communication round to exchange nonces can be +viewed as a pre-processing step that is run whenever convenient to the signers. +This disables some of the defense-in-depth measures that may protect against +API misuse in some cases. Similarly, the API supports an alternative protocol +flow where generating the key (see Key Generation above) is allowed to happen +after exchanging nonces (step 3). + +# Verification + +A participant who wants to verify the partial signatures, but does not sign +itself may do so using the above instructions except that the verifier skips +steps 3 and 5. From c139f915322ece31079494de0c8d1875bfb44288 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Sat, 21 Sep 2024 23:45:21 +0200 Subject: [PATCH 11/18] frost trusted dealer: build: add CMake support --- CMakeLists.txt | 10 ++++++++++ examples/CMakeLists.txt | 4 ++++ src/CMakeLists.txt | 3 +++ src/ctime_tests.c | 2 +- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d9d9d2c9..00a6a4c64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,9 +69,18 @@ option(SECP256K1_ENABLE_MODULE_MUSIG "Enable MuSig module." ON) option(SECP256K1_ENABLE_MODULE_ECDSA_ADAPTOR "Enable ecdsa adaptor signatures module." ON) option(SECP256K1_ENABLE_MODULE_ECDSA_S2C "Enable ECDSA sign-to-contract module." ON) option(SECP256K1_ENABLE_MODULE_BPPP "Enable Bulletproofs++ module." ON) +option(SECP256K1_ENABLE_MODULE_FROST "Enable FROST module." ON) # Processing must be done in a topological sorting of the dependency graph # (dependent module first). +if(SECP256K1_ENABLE_MODULE_FROST) + if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG) + message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the frost module.") + endif() + set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON) + add_compile_definitions(ENABLE_MODULE_FROST=1) +endif() + if(SECP256K1_ENABLE_MODULE_BPPP) if(DEFINED SECP256K1_ENABLE_MODULE_GENERATOR AND NOT SECP256K1_ENABLE_MODULE_GENERATOR) message(FATAL_ERROR "Module dependency error: You have disabled the generator module explicitly, but it is required by the bppp module.") @@ -362,6 +371,7 @@ message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG message(" ecdsa-s2c ........................... ${SECP256K1_ENABLE_MODULE_ECDSA_S2C}") message(" ecdsa-adaptor ....................... ${SECP256K1_ENABLE_MODULE_ECDSA_ADAPTOR}") message(" bppp ................................ ${SECP256K1_ENABLE_MODULE_BPPP}") +message(" frost ............................... ${SECP256K1_ENABLE_MODULE_FROST}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult gen precision bits ........... ${SECP256K1_ECMULT_GEN_PREC_BITS}") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 607bb6777..8e1c97019 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -28,3 +28,7 @@ endif() if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) add_example(schnorr) endif() + +if(SECP256K1_ENABLE_MODULE_FROST) + add_example(frost) +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27e902045..773796be6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,6 +120,9 @@ if(SECP256K1_INSTALL) "${PROJECT_SOURCE_DIR}/include/secp256k1.h" "${PROJECT_SOURCE_DIR}/include/secp256k1_preallocated.h" ) + if(SECP256K1_ENABLE_MODULE_FROST) + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_frost.h") + endif() if(SECP256K1_ENABLE_MODULE_BPPP) list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_bppp.h") endif() diff --git a/src/ctime_tests.c b/src/ctime_tests.c index 0d3471e01..4f8280f03 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -48,7 +48,7 @@ #endif #ifdef ENABLE_MODULE_FROST -#include "include/secp256k1_frost.h" +#include "../include/secp256k1_frost.h" #endif static void run_tests(secp256k1_context *ctx, unsigned char *key); From f045cafef175aeec14a7058dfe507c8911de19da Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 4 Oct 2024 13:59:54 -0700 Subject: [PATCH 12/18] Use VERIFY_CHECK for zero x-coordinate The x-coordinate for the participant is a hash output. This x-coordinate must not be zero, however, the probability of a sha256 output being zero is 1 in 2^256. Thus, it is sufficient to use VERIFY_CHECK, which allows the function to be void, which simplifies the call-sites, as suggested by @theStack. --- src/modules/frost/keygen.h | 2 +- src/modules/frost/keygen_impl.h | 28 ++++++++-------------------- src/modules/frost/session_impl.h | 4 +--- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index 60f494973..56b8c0519 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -26,7 +26,7 @@ static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_k 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); +static void secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33); static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsigned char * const *ids33, size_t n_participants, const unsigned char *my_id33); diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index 53fc23fcf..e46f67ea7 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -54,7 +54,7 @@ static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_k } /* Computes indexhash = tagged_hash(pk) */ -static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33) { +static void secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33) { secp256k1_sha256 sha; unsigned char buf[32]; @@ -62,13 +62,9 @@ static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const secp256k1_sha256_write(&sha, id33, 33); secp256k1_sha256_finalize(&sha, buf); secp256k1_scalar_set_b32(indexhash, buf, NULL); - /* The x-coordinate must not be zero (see - * draft-irtf-cfrg-frost-08#section-4.2.2) */ - if (secp256k1_scalar_is_zero(indexhash)) { - return 0; - } - - return 1; + /* The x-coordinate must not be zero (see RFC9591 4.2). */ + /* This occurs with negligble propability (1 in 2^256). */ + VERIFY_CHECK(!secp256k1_scalar_is_zero(indexhash)); } static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 }; @@ -152,9 +148,7 @@ static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigne /* Derive share */ /* See RFC 9591, appendix C.1 */ secp256k1_scalar_set_int(&share_i, 0); - if (!secp256k1_frost_compute_indexhash(&idx, id33)) { - return 0; - } + secp256k1_frost_compute_indexhash(&idx, id33); for (i = 0; i < threshold; i++) { secp256k1_scalar coeff_i; @@ -262,9 +256,7 @@ static int secp256k1_frost_evaluate_vss(const secp256k1_context* ctx, secp256k1_ evaluate_vss_ecmult_data.ctx = ctx; evaluate_vss_ecmult_data.vss_commitment = vss_commitment; /* Evaluate the public polynomial at the idx */ - if (!secp256k1_frost_compute_indexhash(&evaluate_vss_ecmult_data.idx, id33)) { - return 0; - } + secp256k1_frost_compute_indexhash(&evaluate_vss_ecmult_data.idx, id33); secp256k1_scalar_set_int(&evaluate_vss_ecmult_data.idxn, 1); /* TODO: add scratch */ if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, share, NULL, secp256k1_frost_evaluate_vss_ecmult_callback, (void *) &evaluate_vss_ecmult_data, threshold)) { @@ -416,15 +408,11 @@ static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsig secp256k1_scalar_set_int(&num, 1); secp256k1_scalar_set_int(&den, 1); - if (!secp256k1_frost_compute_indexhash(&party_idx, my_id33)) { - return 0; - } + secp256k1_frost_compute_indexhash(&party_idx, my_id33); for (i = 0; i < n_participants; i++) { secp256k1_scalar mul; - if (!secp256k1_frost_compute_indexhash(&mul, ids33[i])) { - return 0; - } + secp256k1_frost_compute_indexhash(&mul, ids33[i]); if (secp256k1_scalar_eq(&mul, &party_idx)) { continue; } diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index f32e0de61..1e28cc73a 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -360,9 +360,7 @@ static int secp256k1_frost_compute_noncehash(const secp256k1_context* ctx, unsig for (i = 0; i < n_pubnonces; i++) { secp256k1_scalar idx; - if (!secp256k1_frost_compute_indexhash(&idx, ids33[i])) { - return 0; - } + secp256k1_frost_compute_indexhash(&idx, ids33[i]); secp256k1_scalar_get_b32(buf, &idx); secp256k1_sha256_write(&sha, buf, 32); if (!secp256k1_frost_pubnonce_serialize(ctx, buf, pubnonces[i])) { From 298accf1873f2c4db25900230930c17f12981a8d Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 4 Oct 2024 14:08:05 -0700 Subject: [PATCH 13/18] Remove unused ret variable Suggested by @theStack. --- src/modules/frost/keygen_impl.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index e46f67ea7..795cf45ac 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -121,11 +121,10 @@ static void secp256k1_frost_derive_coeff(secp256k1_scalar *coeff, const unsigned secp256k1_scalar_set_b32(coeff, buf, NULL); } -static int secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubkey *vss_commitment, const unsigned char *polygen32, size_t threshold) { +static void secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubkey *vss_commitment, const unsigned char *polygen32, size_t threshold) { secp256k1_gej rj; secp256k1_ge rp; size_t i; - int ret = 1; /* Compute commitment to each coefficient */ for (i = 0; i < threshold; i++) { @@ -136,7 +135,6 @@ static int secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubke secp256k1_ge_set_gej(&rp, &rj); secp256k1_pubkey_save(&vss_commitment[threshold - i - 1], &rp); } - return ret; } static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigned char *polygen32, size_t threshold, const unsigned char *id33) { @@ -193,7 +191,7 @@ int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_sha } secp256k1_sha256_finalize(&sha, polygen); - ret &= secp256k1_frost_vss_gen(ctx, vss_commitment, polygen, threshold); + secp256k1_frost_vss_gen(ctx, vss_commitment, polygen, threshold); for (i = 0; i < n_participants; i++) { ret &= secp256k1_frost_share_gen(&shares[i], polygen, threshold, ids33[i]); From 640d61ca81ac03330052c36901452ee57e51dfb9 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 4 Oct 2024 14:20:13 -0700 Subject: [PATCH 14/18] Rename secp256k1_frost_share to secp256k1_frost_secshare Suggested by theStack. --- examples/frost.c | 4 ++-- include/secp256k1_frost.h | 14 +++++++------- src/ctime_tests.c | 2 +- src/modules/frost/keygen.h | 2 +- src/modules/frost/keygen_impl.h | 14 +++++++------- src/modules/frost/session_impl.h | 4 ++-- src/modules/frost/tests_impl.h | 18 +++++++++--------- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/examples/frost.c b/examples/frost.c index 828108928..566549e6e 100644 --- a/examples/frost.c +++ b/examples/frost.c @@ -21,7 +21,7 @@ struct signer_secrets { secp256k1_keypair keypair; - secp256k1_frost_share share; + secp256k1_frost_secshare share; secp256k1_frost_secnonce secnonce; }; @@ -65,7 +65,7 @@ static int create_keypair(const secp256k1_context* ctx, struct signer_secrets *s /* Create shares and coefficient commitments */ static int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) { int i; - secp256k1_frost_share shares[N_SIGNERS]; + secp256k1_frost_secshare shares[N_SIGNERS]; secp256k1_pubkey vss_commitment[THRESHOLD]; const unsigned char *ids[N_SIGNERS]; unsigned char seed[32]; diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index f083ce4d6..65cefb37a 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -52,7 +52,7 @@ typedef struct { */ typedef struct { unsigned char data[36]; -} secp256k1_frost_share; +} secp256k1_frost_secshare; /** Opaque data structure that holds a signer's _secret_ nonce. * @@ -168,7 +168,7 @@ SECP256K1_API int secp256k1_frost_partial_sig_parse( SECP256K1_API int secp256k1_frost_share_serialize( const secp256k1_context *ctx, unsigned char *out32, - const secp256k1_frost_share *share + const secp256k1_frost_secshare *share ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); /** Parse a FROST share. @@ -180,7 +180,7 @@ SECP256K1_API int secp256k1_frost_share_serialize( */ SECP256K1_API int secp256k1_frost_share_parse( const secp256k1_context *ctx, - secp256k1_frost_share *share, + secp256k1_frost_secshare *share, const unsigned char *in32 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); @@ -209,7 +209,7 @@ SECP256K1_API int secp256k1_frost_share_parse( */ SECP256K1_API int secp256k1_frost_shares_gen( const secp256k1_context *ctx, - secp256k1_frost_share *shares, + secp256k1_frost_secshare *shares, secp256k1_pubkey *vss_commitment, const unsigned char *seed32, size_t threshold, @@ -235,7 +235,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_share_verify( const secp256k1_context *ctx, size_t threshold, const unsigned char *id33, - const secp256k1_frost_share *share, + const secp256k1_frost_secshare *share, const secp256k1_pubkey *vss_commitment ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); @@ -432,7 +432,7 @@ SECP256K1_API int secp256k1_frost_nonce_gen( secp256k1_frost_secnonce *secnonce, secp256k1_frost_pubnonce *pubnonce, const unsigned char *session_id32, - const secp256k1_frost_share *agg_share, + const secp256k1_frost_secshare *agg_share, const unsigned char *msg32, const secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *extra_input32 @@ -495,7 +495,7 @@ 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_secshare *agg_share, const secp256k1_frost_session *session, const secp256k1_frost_keygen_cache *keygen_cache ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); diff --git a/src/ctime_tests.c b/src/ctime_tests.c index 4f8280f03..4100ab404 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -371,7 +371,7 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { secp256k1_pubkey adaptor; unsigned char pre_sig[64]; int nonce_parity; - secp256k1_frost_share shares[2]; + secp256k1_frost_secshare shares[2]; secp256k1_pubkey vss_commitment[2]; unsigned char key2[32]; secp256k1_keypair keypair2; diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index 56b8c0519..a8df89225 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -24,7 +24,7 @@ typedef struct { static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_keygen_cache_internal *cache_i, const secp256k1_frost_keygen_cache *cache); -static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share); +static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_secshare* share); static void secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33); diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index 795cf45ac..c8311af15 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -69,12 +69,12 @@ static void secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 }; -static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_scalar *s) { +static void secp256k1_frost_share_save(secp256k1_frost_secshare* share, secp256k1_scalar *s) { memcpy(&share->data[0], secp256k1_frost_share_magic, 4); secp256k1_scalar_get_b32(&share->data[4], s); } -static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share) { +static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_secshare* share) { int overflow; /* The magic is non-secret so it can be declassified to allow branching. */ @@ -86,7 +86,7 @@ static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_sc return 1; } -int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_share* share) { +int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_secshare* share) { VERIFY_CHECK(ctx != NULL); ARG_CHECK(out32 != NULL); ARG_CHECK(share != NULL); @@ -94,7 +94,7 @@ int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char return 1; } -int secp256k1_frost_share_parse(const secp256k1_context* ctx, secp256k1_frost_share* share, const unsigned char *in32) { +int secp256k1_frost_share_parse(const secp256k1_context* ctx, secp256k1_frost_secshare* share, const unsigned char *in32) { secp256k1_scalar tmp; int overflow; VERIFY_CHECK(ctx != NULL); @@ -137,7 +137,7 @@ static void secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubk } } -static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigned char *polygen32, size_t threshold, const unsigned char *id33) { +static int secp256k1_frost_share_gen(secp256k1_frost_secshare *share, const unsigned char *polygen32, size_t threshold, const unsigned char *id33) { secp256k1_scalar idx; secp256k1_scalar share_i; size_t i; @@ -162,7 +162,7 @@ static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigne return ret; } -int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_share *shares, secp256k1_pubkey *vss_commitment, const unsigned char *seed32, size_t threshold, size_t n_participants, const unsigned char * const* ids33) { +int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_secshare *shares, secp256k1_pubkey *vss_commitment, const unsigned char *seed32, size_t threshold, size_t n_participants, const unsigned char * const* ids33) { secp256k1_sha256 sha; unsigned char polygen[32]; size_t i; @@ -265,7 +265,7 @@ static int secp256k1_frost_evaluate_vss(const secp256k1_context* ctx, secp256k1_ } /* See RFC 9591, appendix C.2 */ -int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_frost_share *share, const secp256k1_pubkey *vss_commitment) { +int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_frost_secshare *share, const secp256k1_pubkey *vss_commitment) { secp256k1_scalar share_i; secp256k1_scalar share_neg; secp256k1_gej tmpj, snj; diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index 1e28cc73a..f2a44e8f6 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -255,7 +255,7 @@ static void secp256k1_nonce_function_frost(secp256k1_scalar *k, const unsigned c } } -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_frost_keygen_cache *keygen_cache, const unsigned char *extra_input32) { +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_secshare *share, const unsigned char *msg32, const secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *extra_input32) { secp256k1_keygen_cache_internal cache_i; secp256k1_scalar k[2]; secp256k1_ge nonce_pt[2]; @@ -468,7 +468,7 @@ void secp256k1_frost_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar * 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_keygen_cache *keygen_cache) { +int secp256k1_frost_partial_sign(const secp256k1_context* ctx, secp256k1_frost_partial_sig *partial_sig, secp256k1_frost_secnonce *secnonce, const secp256k1_frost_secshare *share, const secp256k1_frost_session *session, const secp256k1_frost_keygen_cache *keygen_cache) { secp256k1_scalar sk; secp256k1_scalar k[2]; secp256k1_scalar s; diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h index 137093118..9a2d5c6ba 100644 --- a/src/modules/frost/tests_impl.h +++ b/src/modules/frost/tests_impl.h @@ -45,7 +45,7 @@ void frost_simple_test(void) { secp256k1_xonly_pubkey pk_xonly; secp256k1_pubkey pk; unsigned char buf[32]; - secp256k1_frost_share shares[5]; + secp256k1_frost_secshare shares[5]; secp256k1_frost_secnonce secnonce[5]; secp256k1_pubkey pubshare[5]; secp256k1_frost_partial_sig partial_sig[5]; @@ -161,8 +161,8 @@ void frost_api_tests(void) { secp256k1_pubkey vss_commitment[3]; secp256k1_pubkey invalid_vss_commitment[3]; secp256k1_pubkey invalid_pubshare; - secp256k1_frost_share shares[5]; - secp256k1_frost_share invalid_share; + secp256k1_frost_secshare shares[5]; + secp256k1_frost_secshare invalid_share; secp256k1_pubkey pubshare[5]; int i; unsigned char id[5][33]; @@ -577,7 +577,7 @@ void frost_sha256_tag_test_internal(secp256k1_sha256 *sha_tagged, unsigned char /* Attempts to create a signature for the group public key using given secret * keys and keygen_cache. */ -void frost_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const secp256k1_frost_share *sr0, const secp256k1_frost_share *sr1, const secp256k1_frost_share *sr2, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char * const* ids33, const secp256k1_pubkey *sr_pk0, const secp256k1_pubkey *sr_pk1, const secp256k1_pubkey *sr_pk2) { +void frost_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const secp256k1_frost_secshare *sr0, const secp256k1_frost_secshare *sr1, const secp256k1_frost_secshare *sr2, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char * const* ids33, const secp256k1_pubkey *sr_pk0, const secp256k1_pubkey *sr_pk1, const secp256k1_pubkey *sr_pk2) { unsigned char session_id[3][32]; unsigned char msg[32]; secp256k1_frost_secnonce secnonce[3]; @@ -630,7 +630,7 @@ void frost_tweak_test(void) { secp256k1_xonly_pubkey P_xonly[N_TWEAKS + 1]; unsigned char seed[32]; secp256k1_pubkey vss_commitment[3]; - secp256k1_frost_share shares[5]; + secp256k1_frost_secshare shares[5]; int i; unsigned char id[5][33]; const unsigned char *id_ptr[5]; @@ -689,7 +689,7 @@ void frost_tweak_test(void) { } /* Performs a FROST DKG */ -void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1_frost_share *shares, const unsigned char * const *ids33) { +void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1_frost_secshare *shares, const unsigned char * const *ids33) { secp256k1_pubkey vss_commitment[3]; unsigned char seed[32]; int i; @@ -708,7 +708,7 @@ void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1 } /* Signs a message with a FROST keypair */ -int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_share *shares, const unsigned char * const *ids33, const unsigned char *msg, const secp256k1_pubkey *adaptor, secp256k1_frost_keygen_cache *keygen_cache) { +int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_secshare *shares, const unsigned char * const *ids33, const unsigned char *msg, const secp256k1_pubkey *adaptor, secp256k1_frost_keygen_cache *keygen_cache) { unsigned char session_id[3][32]; secp256k1_frost_secnonce secnonce[3]; secp256k1_frost_pubnonce pubnonce[3]; @@ -750,8 +750,8 @@ void frost_rand_scalar(secp256k1_scalar *scalar) { } void frost_multi_hop_lock_tests(void) { - secp256k1_frost_share shares_a[5]; - secp256k1_frost_share shares_b[5]; + secp256k1_frost_secshare shares_a[5]; + secp256k1_frost_secshare shares_b[5]; secp256k1_xonly_pubkey pk_a; secp256k1_xonly_pubkey pk_b; secp256k1_pubkey tmp; From 4b387efb3decfc1563432b5f7d34cba4f04fac94 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 4 Oct 2024 14:26:17 -0700 Subject: [PATCH 15/18] Revise nonce reuse warning Suggested by @theStack. --- include/secp256k1_frost.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 65cefb37a..c2e42e4a5 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -58,16 +58,13 @@ typedef struct { * * 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 + * 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. + * workflow. * - * We repeat, copying this data structure can result in nonce reuse which will - * leak the secret signing key. + * Copying this data structure can result in nonce reuse which will leak the + * secret signing key. */ typedef struct { unsigned char data[68]; From 6aa7df26357eee695e605d2d50947f5673e7289f Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 4 Oct 2024 14:34:31 -0700 Subject: [PATCH 16/18] Check for equality before computing indexhash As suggested by @theStack. --- src/modules/frost/keygen_impl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index c8311af15..f122a1cd8 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -410,11 +410,11 @@ static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsig for (i = 0; i < n_participants; i++) { secp256k1_scalar mul; - secp256k1_frost_compute_indexhash(&mul, ids33[i]); - if (secp256k1_scalar_eq(&mul, &party_idx)) { + if (secp256k1_memcmp_var(ids33[i], my_id33, 33) == 0) { continue; } + secp256k1_frost_compute_indexhash(&mul, ids33[i]); secp256k1_scalar_negate(&mul, &mul); secp256k1_scalar_mul(&num, &num, &mul); secp256k1_scalar_add(&mul, &mul, &party_idx); From eb543cb89cd99c892e48ed13930e0b5d52e9c775 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 4 Oct 2024 14:39:15 -0700 Subject: [PATCH 17/18] Simplify code Co-authored-by: Sebastian Falbesoner --- src/modules/frost/keygen_impl.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index f122a1cd8..0f79a9237 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -400,12 +400,9 @@ int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256 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 num = secp256k1_scalar_one; + secp256k1_scalar den = secp256k1_scalar_one; secp256k1_scalar party_idx; - - secp256k1_scalar_set_int(&num, 1); - secp256k1_scalar_set_int(&den, 1); secp256k1_frost_compute_indexhash(&party_idx, my_id33); for (i = 0; i < n_participants; i++) { secp256k1_scalar mul; From b8c671c347c5d6147d246ccf95e561aada433e5d Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 4 Oct 2024 14:39:49 -0700 Subject: [PATCH 18/18] Improve documentation Co-authored-by: Sebastian Falbesoner --- include/secp256k1_frost.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index c2e42e4a5..73d0c42eb 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -195,7 +195,7 @@ SECP256K1_API int secp256k1_frost_share_parse( * Returns: 0 if the arguments are invalid, 1 otherwise * Args: ctx: pointer to a context object * Out: shares: pointer to the key shares - * vss_commitment: pointer to the VSS commitment + * vss_commitment: output array of the elements of the VSS commitment * In: seed32: 32-byte random seed as explained above. Must be * unique to this call to secp256k1_frost_shares_gen * and must be uniformly random.