From 2f3fa4cace7123d58a2948159ae60010182de4b5 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 17 Jun 2024 17:06:25 -0700 Subject: [PATCH 01/23] frost: 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..93c68a19b --- /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 2336b02fad5902b7049892a4b7627aef013e5b2a Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 17 Jun 2024 18:10:13 -0700 Subject: [PATCH 02/23] frost: share generation This commit adds share generation, as well as share serialization and parsing. --- include/secp256k1_frost.h | 83 +++++++++++ src/modules/frost/Makefile.am.include | 2 + src/modules/frost/keygen.h | 13 ++ src/modules/frost/keygen_impl.h | 191 ++++++++++++++++++++++++++ src/modules/frost/main_impl.h | 2 + 5 files changed, 291 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 93c68a19b..0191fa1d4 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,87 @@ 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, each participant generates a share for each other + * participant. For example, in the case of 2 particpants, Alice and Bob, they + * each generate 2 shares, distribute 1 share to each other using a secure + * channel, and keep 1 for themselves. + * + * Each participant must transmit shares over secure channels to each other + * 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 other 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 + * pok64: pointer to the proof of knowledge + * 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, + unsigned char *pok64, + 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(5) SECP256K1_ARG_NONNULL(8); + #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..8d17b48a3 --- /dev/null +++ b/src/modules/frost/keygen_impl.h @@ -0,0 +1,191 @@ +/********************************************************************** + * 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; + + 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, unsigned char *pok64, const unsigned char *polygen32, size_t threshold) { + secp256k1_sha256 sha; + unsigned char buf[32]; + secp256k1_keypair keypair; + secp256k1_gej rj; + secp256k1_ge rp; + size_t i; + int ret = 1; + + for (i = 0; i < threshold; i++) { + secp256k1_scalar coeff_i; + + secp256k1_frost_derive_coeff(&coeff_i, polygen32, i); + /* Compute proof-of-knowledge for constant term */ + if (i == threshold - 1) { + secp256k1_scalar_get_b32(buf, &coeff_i); + ret &= secp256k1_keypair_create(ctx, &keypair, buf); + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/KeygenPoK", sizeof("FROST/KeygenPoK") - 1); + secp256k1_sha256_finalize(&sha, buf); + + ret &= secp256k1_schnorrsig_sign32(ctx, pok64, buf, &keypair, NULL); + } + + /* Compute commitment to each coefficient */ + 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 draft-irtf-cfrg-frost-08#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, unsigned char *pok64, 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(pok64 != 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, pok64, 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 197fb7efb9c81d4661b2bd2fe7c5983d86edb047 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Sun, 14 Jul 2024 14:36:34 -0700 Subject: [PATCH 03/23] frost: share aggregation This commit adds share aggregation and verification, as well as computation of public verification shares. --- include/secp256k1_frost.h | 83 ++++++++++++ src/modules/frost/keygen_impl.h | 224 ++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 0191fa1d4..524a4ab74 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -98,6 +98,89 @@ 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(5) SECP256K1_ARG_NONNULL(8); +/** Aggregates shares + * + * As part of the key generation protocol, each participant receives a share + * from each participant, including a share they "receive" from themselves. + * This function verifies those shares against their VSS commitments, + * aggregates the shares, and then aggregates the commitments to each + * participant's first polynomial coefficient to derive the aggregate public + * key. + * + * If this function returns an error, `secp256k1_frost_share_verify` can be + * called on each share to determine which participants submitted faulty + * shares. + * + * 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: agg_share: the aggregated share + * agg_pk: the aggregated x-only public key + * In: shares: all key generation shares for the partcipant's index + * vss_commitments: coefficient commitments of all participants ordered by + * the x-only pubkeys of the participants + * n_shares: the total number of shares + * threshold: the minimum number of shares required to produce a + * signature + * id33: the 33-byte ID of the participant whose shares are being + * aggregated + */ +SECP256K1_API int secp256k1_frost_share_agg( + const secp256k1_context *ctx, + secp256k1_frost_share *agg_share, + secp256k1_xonly_pubkey *agg_pk, + const secp256k1_frost_share * const *shares, + const secp256k1_pubkey * const *vss_commitments, + size_t n_shares, + size_t threshold, + const unsigned char *id33 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8); + +/** Verifies a share received during a key generation session + * + * The signature is verified against the VSS commitment received with the + * share. This is only useful for purposes of determining which share(s) are + * invalid if share_agg returns an error. + * + * 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: the VSS commitment associated with the share + */ +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 * const *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_commitments: coefficient commitments of all participants + * 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 * const *vss_commitments, + size_t n_participants +) 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 8d17b48a3..8b8ecddfa 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -188,4 +188,228 @@ 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 * const* vss_commitment; +} secp256k1_frost_verify_share_ecmult_data; + +typedef struct { + const secp256k1_context *ctx; + secp256k1_scalar idx; + secp256k1_scalar idxn; + const secp256k1_pubkey * const* vss_commitments; + size_t threshold; +} secp256k1_frost_compute_pubshare_ecmult_data; + +typedef struct { + const secp256k1_context *ctx; + const secp256k1_pubkey * const* pks; + size_t threshold; +} secp256k1_frost_pubkey_combine_ecmult_data; + +static int secp256k1_frost_verify_share_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_verify_share_ecmult_data *ctx = (secp256k1_frost_verify_share_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_compute_pubshare_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_compute_pubshare_ecmult_data *ctx = (secp256k1_frost_compute_pubshare_ecmult_data *) data; + + if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitments[idx/ctx->threshold][idx % ctx->threshold])) { + return 0; + } + if (idx != 0 && idx % ctx->threshold == 0) { + secp256k1_scalar_set_int(&ctx->idxn, 1); + } + *sc = ctx->idxn; + secp256k1_scalar_mul(&ctx->idxn, &ctx->idxn, &ctx->idx); + + return 1; +} + +static int secp256k1_frost_pubkey_combine_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_pubkey_combine_ecmult_data *ctx = (secp256k1_frost_pubkey_combine_ecmult_data *) data; + + secp256k1_scalar_set_int(sc, 1); + /* the public key is the first index of each set of coefficients */ + return secp256k1_pubkey_load(ctx->ctx, pt, &ctx->pks[idx][0]); +} + +/* See draft-irtf-cfrg-frost-08#appendix-C.2 */ +static int secp256k1_frost_vss_verify_internal(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_scalar *share, const secp256k1_pubkey * const* vss_commitment) { + secp256k1_scalar share_neg; + secp256k1_gej tmpj, snj; + secp256k1_ge sng; + secp256k1_frost_verify_share_ecmult_data verify_share_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]*/ + verify_share_ecmult_data.ctx = ctx; + verify_share_ecmult_data.vss_commitment = vss_commitment; + /* Evaluate the public polynomial at the idx */ + if (!secp256k1_frost_compute_indexhash(&verify_share_ecmult_data.idx, id33)) { + return 0; + } + secp256k1_scalar_set_int(&verify_share_ecmult_data.idxn, 1); + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &tmpj, NULL, secp256k1_frost_verify_share_ecmult_callback, (void *) &verify_share_ecmult_data, threshold)) { + return 0; + } + secp256k1_scalar_negate(&share_neg, share); + 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 draft-irtf-cfrg-frost-08#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 * const* vss_commitment) { + secp256k1_scalar share_i; + + 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; + } + + return secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, vss_commitment); +} + +int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pubkey *pubshare, size_t threshold, const unsigned char *id33, const secp256k1_pubkey * const* vss_commitments, size_t n_participants) { + secp256k1_gej pkj; + secp256k1_ge pkp, tmp; + secp256k1_frost_compute_pubshare_ecmult_data compute_pubshare_ecmult_data; + secp256k1_frost_pubkey_combine_ecmult_data pubkey_combine_ecmult_data; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubshare != NULL); + memset(pubshare, 0, sizeof(*pubshare)); + ARG_CHECK(id33 != NULL); + ARG_CHECK(vss_commitments != NULL); + ARG_CHECK(n_participants > 1); + ARG_CHECK(threshold > 1); + + if (threshold > n_participants) { + return 0; + } + + /* Use an EC multi-multiplication to compute the following equation: + * agg_share_i*G = ( + * idx^0*vss_commitment[0][0] + ... + * + idx^(t - 1)*vss_commitment[0][t - 1] + * ) + ... + * + ( + * idx^0*vss_commitment[n - 1][0] + ... + * + idx^(t - 1)*vss_commitment[n - 1][t - 1] + * )*/ + compute_pubshare_ecmult_data.ctx = ctx; + compute_pubshare_ecmult_data.vss_commitments = vss_commitments; + compute_pubshare_ecmult_data.threshold = threshold; + /* Evaluate the public polynomial at the idx */ + if (!secp256k1_frost_compute_indexhash(&compute_pubshare_ecmult_data.idx, id33)) { + return 0; + } + secp256k1_scalar_set_int(&compute_pubshare_ecmult_data.idxn, 1); + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_compute_pubshare_ecmult_callback, (void *) &compute_pubshare_ecmult_data, n_participants*threshold)) { + return 0; + } + secp256k1_ge_set_gej(&tmp, &pkj); + + /* Combine pubkeys */ + pubkey_combine_ecmult_data.ctx = ctx; + pubkey_combine_ecmult_data.pks = vss_commitments; + pubkey_combine_ecmult_data.threshold = threshold; + + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_pubkey_combine_callback, (void *) &pubkey_combine_ecmult_data, n_participants)) { + return 0; + } + secp256k1_ge_set_gej(&pkp, &pkj); + secp256k1_fe_normalize_var(&pkp.y); + if (secp256k1_fe_is_odd(&pkp.y)) { + secp256k1_ge_neg(&tmp, &tmp); + } + + secp256k1_pubkey_save(pubshare, &tmp); + + return 1; +} + +int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, secp256k1_xonly_pubkey *agg_pk, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, size_t n_shares, size_t threshold, const unsigned char *id33) { + secp256k1_frost_pubkey_combine_ecmult_data pubkey_combine_ecmult_data; + secp256k1_gej pkj; + secp256k1_ge pkp; + int pk_parity; + secp256k1_scalar acc; + size_t i; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(agg_share != NULL); + memset(agg_share, 0, sizeof(*agg_share)); + ARG_CHECK(agg_pk != NULL); + memset(agg_pk, 0, sizeof(*agg_pk)); + ARG_CHECK(shares != NULL); + ARG_CHECK(vss_commitments != NULL); + ARG_CHECK(id33 != NULL); + ARG_CHECK(n_shares > 1); + ARG_CHECK(threshold > 1); + + if (threshold > n_shares) { + return 0; + } + + secp256k1_scalar_clear(&acc); + for (i = 0; i < n_shares; i++) { + secp256k1_scalar share_i; + + if (!secp256k1_frost_share_load(ctx, &share_i, shares[i])) { + return 0; + } + /* Verify share against commitments */ + ret &= secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, &vss_commitments[i]); + secp256k1_scalar_add(&acc, &acc, &share_i); + } + + /* Combine pubkeys */ + pubkey_combine_ecmult_data.ctx = ctx; + pubkey_combine_ecmult_data.pks = vss_commitments; + pubkey_combine_ecmult_data.threshold = threshold; + + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_pubkey_combine_callback, (void *) &pubkey_combine_ecmult_data, n_shares)) { + return 0; + } + + secp256k1_ge_set_gej(&pkp, &pkj); + secp256k1_fe_normalize_var(&pkp.y); + pk_parity = secp256k1_extrakeys_ge_even_y(&pkp); + secp256k1_xonly_pubkey_save(agg_pk, &pkp); + + /* Invert the aggregate share if the combined pubkey has an odd Y coordinate. */ + if (pk_parity == 1) { + secp256k1_scalar_negate(&acc, &acc); + } + secp256k1_frost_share_save(agg_share, &acc); + + return ret; +} + #endif From f60650712077fbe26b48624eec8de308125975dd Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 15 Jul 2024 17:54:31 -0700 Subject: [PATCH 04/23] frost: nonce generation This commits adds nonce generation, as well as serialization and parsing. --- include/secp256k1_frost.h | 111 ++++++++++++ src/group.h | 4 + src/group_impl.h | 17 ++ 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 | 10 ++ src/modules/frost/session_impl.h | 233 ++++++++++++++++++++++++++ src/modules/musig/keyagg.h | 6 - src/modules/musig/keyagg_impl.h | 17 -- 10 files changed, 382 insertions(+), 23 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 524a4ab74..99cbcafe6 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/). + * + * 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 @@ -34,6 +37,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 @@ -181,6 +239,59 @@ SECP256K1_API int secp256k1_frost_compute_pubshare( size_t n_participants ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); +/** Starts a signing session by generating a nonce + * + * This function outputs a secret nonce that will be required for signing and a + * corresponding public nonce that is intended to be sent to other signers. + * + * FROST, like MuSig, differs from regular Schnorr signing in that + * implementers _must_ take special care to not reuse a nonce. This can be + * ensured by following these rules: + * + * 1. Each call to this function must have a UNIQUE session_id32 that must NOT BE + * REUSED in subsequent calls to this function. + * If you do not provide a seckey, session_id32 _must_ be UNIFORMLY RANDOM + * AND KEPT SECRET (even from other signers). If you do provide a seckey, + * session_id32 can instead be a counter (that must never repeat!). However, + * it is recommended to always choose session_id32 uniformly at random. + * 2. If you already know the seckey, message or aggregate public key, they + * can be optionally provided to derive the nonce and increase + * misuse-resistance. The extra_input32 argument can be used to provide + * additional data that does not repeat in normal scenarios, such as the + * current time. + * 3. Avoid copying (or serializing) the secnonce. This reduces the possibility + * that it is used more than once for signing. + * + * Remember that nonce reuse will leak the secret key! + * Note that using the same agg_share for multiple FROST sessions is fine. + * + * Returns: 0 if the arguments are invalid and 1 otherwise + * Args: ctx: pointer to a context object (not secp256k1_context_static) + * Out: secnonce: pointer to a structure to store the secret nonce + * pubnonce: pointer to a structure to store the public nonce + * In: session_id32: a 32-byte session_id32 as explained above. Must be + * unique to this call to secp256k1_frost_nonce_gen and + * must be uniformly random unless you really know what you + * are doing. + * agg_share: the aggregated share that will later be used for + * signing, if already known (can be NULL) + * msg32: the 32-byte message that will later be signed, if + * already known (can be NULL) + * agg_pk: the FROST-aggregated public key (can be NULL) + * extra_input32: an optional 32-byte array that is input to the nonce + * derivation function (can be NULL) + */ +SECP256K1_API int secp256k1_frost_nonce_gen( + const secp256k1_context *ctx, + secp256k1_frost_secnonce *secnonce, + secp256k1_frost_pubnonce *pubnonce, + const unsigned char *session_id32, + const secp256k1_frost_share *agg_share, + const unsigned char *msg32, + const secp256k1_xonly_pubkey *agg_pk, + const unsigned char *extra_input32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/group.h b/src/group.h index 81b159c81..43cf57fd7 100644 --- a/src/group.h +++ b/src/group.h @@ -202,6 +202,10 @@ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf); */ static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge); +static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge); + +static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data); + /** Check invariants on an affine group element (no-op unless VERIFY is enabled). */ static void secp256k1_ge_verify(const secp256k1_ge *a); #define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a) diff --git a/src/group_impl.h b/src/group_impl.h index f27b7d994..fb475217d 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -914,6 +914,23 @@ static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) { return secp256k1_fe_is_square_var(&yz); } +static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) { + if (secp256k1_ge_is_infinity(ge)) { + memset(data, 0, 64); + } else { + secp256k1_ge_to_bytes(data, ge); + } +} + +static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) { + unsigned char zeros[64] = { 0 }; + if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) { + secp256k1_ge_set_infinity(ge); + } else { + secp256k1_ge_from_bytes(ge, data); + } +} + static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) { #ifdef EXHAUSTIVE_TEST_ORDER secp256k1_gej out; 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 bc1082a1a..c68114236 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -10,4 +10,8 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_frost.h" +#include "../../scalar.h" + +static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share); + #endif 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..d4b922beb --- /dev/null +++ b/src/modules/frost/session.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_SESSION_H +#define SECP256K1_MODULE_FROST_SESSION_H + +#endif diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h new file mode 100644 index 000000000..07c531965 --- /dev/null +++ b/src/modules/frost/session_impl.h @@ -0,0 +1,233 @@ +/********************************************************************** + * Copyright (c) 2021-2024 Jesse Posner * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_MODULE_FROST_SESSION_IMPL_H +#define SECP256K1_MODULE_FROST_SESSION_IMPL_H + +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_frost.h" + +#include "keygen.h" +#include "session.h" +#include "../../eckey.h" +#include "../../hash.h" +#include "../../scalar.h" +#include "../../util.h" + +static const unsigned char secp256k1_frost_secnonce_magic[4] = { 0x84, 0x7d, 0x46, 0x25 }; + +static void secp256k1_frost_secnonce_save(secp256k1_frost_secnonce *secnonce, secp256k1_scalar *k) { + memcpy(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4); + secp256k1_scalar_get_b32(&secnonce->data[4], &k[0]); + secp256k1_scalar_get_b32(&secnonce->data[36], &k[1]); +} + +static int secp256k1_frost_secnonce_load(const secp256k1_context* ctx, secp256k1_scalar *k, secp256k1_frost_secnonce *secnonce) { + int is_zero; + ARG_CHECK(secp256k1_memcmp_var(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4) == 0); + secp256k1_scalar_set_b32(&k[0], &secnonce->data[4], NULL); + secp256k1_scalar_set_b32(&k[1], &secnonce->data[36], NULL); + /* We make very sure that the nonce isn't invalidated by checking the values + * in addition to the magic. */ + is_zero = secp256k1_scalar_is_zero(&k[0]) & secp256k1_scalar_is_zero(&k[1]); + secp256k1_declassify(ctx, &is_zero, sizeof(is_zero)); + ARG_CHECK(!is_zero); + return 1; +} + +/* If flag is true, invalidate the secnonce; otherwise leave it. Constant-time. */ +static void secp256k1_frost_secnonce_invalidate(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, int flag) { + secp256k1_memczero(secnonce->data, sizeof(secnonce->data), flag); + /* The flag argument is usually classified. So, above code makes the magic + * classified. However, we need the magic to be declassified to be able to + * compare it during secnonce_load. */ + secp256k1_declassify(ctx, secnonce->data, sizeof(secp256k1_frost_secnonce_magic)); +} + +static const unsigned char secp256k1_frost_pubnonce_magic[4] = { 0x8b, 0xcf, 0xe2, 0xc2 }; + +/* Requires that none of the provided group elements is infinity. Works for both + * frost_pubnonce and frost_aggnonce. */ +static void secp256k1_frost_pubnonce_save(secp256k1_frost_pubnonce* nonce, secp256k1_ge* ge) { + int i; + memcpy(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4); + for (i = 0; i < 2; i++) { + secp256k1_point_save_ext(nonce->data + 4+64*i, &ge[i]); + } +} + +/* Works for both frost_pubnonce and frost_aggnonce. Returns 1 unless the nonce + * wasn't properly initialized */ +static int secp256k1_frost_pubnonce_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_frost_pubnonce* nonce) { + int i; + + ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4) == 0); + for (i = 0; i < 2; i++) { + secp256k1_point_load_ext(&ge[i], nonce->data + 4+64*i); + } + return 1; +} + +int secp256k1_frost_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_frost_pubnonce* nonce) { + secp256k1_ge ge[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out66 != NULL); + memset(out66, 0, 66); + ARG_CHECK(nonce != NULL); + + if (!secp256k1_frost_pubnonce_load(ctx, ge, nonce)) { + return 0; + } + for (i = 0; i < 2; i++) { + int ret; + size_t size = 33; + ret = secp256k1_eckey_pubkey_serialize(&ge[i], &out66[33*i], &size, 1); +#ifdef VERIFY + /* serialize must succeed because the point was just loaded */ + VERIFY_CHECK(ret && size == 33); +#else + (void) ret; +#endif + } + return 1; +} + +int secp256k1_frost_pubnonce_parse(const secp256k1_context* ctx, secp256k1_frost_pubnonce* nonce, const unsigned char *in66) { + secp256k1_ge ge[2]; + int i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(nonce != NULL); + ARG_CHECK(in66 != NULL); + for (i = 0; i < 2; i++) { + if (!secp256k1_eckey_pubkey_parse(&ge[i], &in66[33*i], 33)) { + return 0; + } + if (!secp256k1_ge_is_in_correct_subgroup(&ge[i])) { + return 0; + } + } + /* The group elements can not be infinity because they were just parsed */ + secp256k1_frost_pubnonce_save(nonce, ge); + return 1; +} + +static void secp256k1_nonce_function_frost(secp256k1_scalar *k, const unsigned char *session_id, const unsigned char *msg32, const unsigned char *key32, const unsigned char *pk32, const unsigned char *extra_input32) { + secp256k1_sha256 sha; + unsigned char seed[32]; + unsigned char i; + enum { n_extra_in = 4 }; + const unsigned char *extra_in[n_extra_in]; + + /* TODO: this doesn't have the same sidechannel resistance as the BIP340 + * nonce function because the seckey feeds directly into SHA. */ + + /* Subtract one from `sizeof` to avoid hashing the implicit null byte */ + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/nonce", sizeof("FROST/nonce") - 1); + secp256k1_sha256_write(&sha, session_id, 32); + extra_in[0] = msg32; + extra_in[1] = key32; + extra_in[2] = pk32; + extra_in[3] = extra_input32; + for (i = 0; i < n_extra_in; i++) { + unsigned char len; + if (extra_in[i] != NULL) { + len = 32; + secp256k1_sha256_write(&sha, &len, 1); + secp256k1_sha256_write(&sha, extra_in[i], 32); + } else { + len = 0; + secp256k1_sha256_write(&sha, &len, 1); + } + } + secp256k1_sha256_finalize(&sha, seed); + + for (i = 0; i < 2; i++) { + unsigned char buf[32]; + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, seed, 32); + secp256k1_sha256_write(&sha, &i, sizeof(i)); + secp256k1_sha256_finalize(&sha, buf); + secp256k1_scalar_set_b32(&k[i], buf, NULL); + } +} + +int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, secp256k1_frost_pubnonce *pubnonce, const unsigned char *session_id32, const secp256k1_frost_share *share, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, const unsigned char *extra_input32) { + secp256k1_scalar k[2]; + secp256k1_ge nonce_pt[2]; + int i; + unsigned char pk_ser[32]; + unsigned char *pk_ser_ptr = NULL; + unsigned char sk_ser[32]; + unsigned char *sk_ser_ptr = NULL; + int sk_serialize_success; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secnonce != NULL); + memset(secnonce, 0, sizeof(*secnonce)); + ARG_CHECK(pubnonce != NULL); + memset(pubnonce, 0, sizeof(*pubnonce)); + ARG_CHECK(session_id32 != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + if (share == NULL) { + /* Check in constant time that the session_id is not 0 as a + * defense-in-depth measure that may protect against a faulty RNG. */ + unsigned char acc = 0; + for (i = 0; i < 32; i++) { + acc |= session_id32[i]; + } + ret &= !!acc; + memset(&acc, 0, sizeof(acc)); + } + + if (share != NULL) { + /* Check that the share is valid to be able to sign for it later. */ + secp256k1_scalar sk; + + ret &= secp256k1_frost_share_load(ctx, &sk, share); + secp256k1_scalar_clear(&sk); + + sk_serialize_success = secp256k1_frost_share_serialize(ctx, sk_ser, share); + sk_ser_ptr = sk_ser; +#ifdef VERIFY + VERIFY_CHECK(sk_serialize_success); +#else + (void) sk_serialize_success; +#endif + } + + if (pk != NULL) { + if (!secp256k1_xonly_pubkey_serialize(ctx, pk_ser, pk)) { + return 0; + } + pk_ser_ptr = pk_ser; + } + secp256k1_nonce_function_frost(k, session_id32, msg32, sk_ser_ptr, pk_ser_ptr, extra_input32); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[0])); + VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[1])); + VERIFY_CHECK(!secp256k1_scalar_eq(&k[0], &k[1])); + secp256k1_frost_secnonce_save(secnonce, k); + secp256k1_frost_secnonce_invalidate(ctx, secnonce, !ret); + + for (i = 0; i < 2; i++) { + secp256k1_gej nonce_ptj; + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj, &k[i]); + secp256k1_ge_set_gej(&nonce_pt[i], &nonce_ptj); + secp256k1_declassify(ctx, &nonce_pt[i], sizeof(nonce_pt)); + secp256k1_scalar_clear(&k[i]); + } + /* nonce_pt won't be infinity because k != 0 with overwhelming probability */ + secp256k1_frost_pubnonce_save(pubnonce, nonce_pt); + return ret; +} + +#endif 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..992fd9a35 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 From 17c47e970868bb8e305e492e347f693f4ceb47c0 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 15 Jul 2024 21:55:32 -0700 Subject: [PATCH 05/23] frost: key tweaking This commits add BIP-341 ("Taproot") and BIP-32 ("ordinary") public key tweaking. --- include/secp256k1_frost.h | 129 ++++++++++++++++++++++++++++++++ src/modules/frost/keygen.h | 7 ++ src/modules/frost/keygen_impl.h | 111 +++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 99cbcafe6..c2d65543e 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -16,6 +16,9 @@ extern "C" { * 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. + * * 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. */ @@ -28,6 +31,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_tweak_cache; + /** Opaque data structure that holds a signer's _secret_ share. * * Guaranteed to be 36 bytes in size. Serialized and parsed with @@ -239,6 +251,123 @@ SECP256K1_API int secp256k1_frost_compute_pubshare( size_t n_participants ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); +/** Obtain the aggregate public key from a FROST x-only aggregate public key. + * + * This is only useful if you need the non-xonly public key, in particular for + * ordinary (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: ec_agg_pk: the FROST-aggregated public key. + * In: xonly_agg_pk: the aggregated x-only public key that is the output of + * `secp256k1_frost_share_agg` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get( + const secp256k1_context *ctx, + secp256k1_pubkey *ec_agg_pk, + const secp256k1_xonly_pubkey *xonly_agg_pk +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Initializes a tweak cache used for applying tweaks to a FROST key + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: tweak_cache: pointer to a frost_tweak_cache struct that is required + * for key tweaking + * In: agg_pk: the aggregated x-only public key that is the output of + * `secp256k1_frost_share_agg` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_tweak( + const secp256k1_context *ctx, + secp256k1_frost_tweak_cache *tweak_cache, + const secp256k1_xonly_pubkey *agg_pk +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Apply ordinary "EC" tweaking to a public key in a given tweak_cache by + * adding the generator multiplied with `tweak32` to it. This is useful for + * deriving child keys from an aggregate 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_share_agg(..., xonly_agg_pk, ...) + * secp256k1_frost_pubkey_tweak(..., tweak_cache, xonly_agg_pk) + * secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, tweak_cache, tweak32) + * secp256k1_ec_pubkey_serialize(..., buf, output_pk) + * secp256k1_frost_pubkey_get(..., ec_agg_pk, xonly_agg_pk) + * secp256k1_ec_pubkey_tweak_add(..., ec_agg_pk, tweak32) + * secp256k1_ec_pubkey_serialize(..., buf2, ec_agg_pk) + * + * This function is required if you want to _sign_ for a tweaked aggregate 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: tweak_cache: pointer to a `frost_tweak_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_tweak_cache *tweak_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 tweak_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_share_agg(..., agg_pk, ...) + * secp256k1_frost_pubkey_tweak(..., tweak_cache, agg_pk) + * secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, tweak_cache, tweak32) + * secp256k1_xonly_pubkey_serialize(..., buf, output_pk) + * secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32) + * + * This function is required if you want to _sign_ for a tweaked aggregate 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: tweak_cache: pointer to a `frost_tweak_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_tweak_cache *tweak_cache, + 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 diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index c68114236..bf96133fa 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -10,8 +10,15 @@ #include "../../../include/secp256k1.h" #include "../../../include/secp256k1_frost.h" +#include "../../group.h" #include "../../scalar.h" +typedef struct { + secp256k1_ge pk; + secp256k1_scalar tweak; + int parity_acc; +} secp256k1_tweak_cache_internal; + 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/keygen_impl.h b/src/modules/frost/keygen_impl.h index 8b8ecddfa..fadd27f28 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_tweak_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_tweak_cache_save(secp256k1_frost_tweak_cache *cache, secp256k1_tweak_cache_internal *cache_i) { + unsigned char *ptr = cache->data; + memcpy(ptr, secp256k1_frost_tweak_cache_magic, 4); + ptr += 4; + secp256k1_point_save_ext(ptr, &cache_i->pk); + ptr += 64; + *ptr = cache_i->parity_acc; + ptr += 1; + secp256k1_scalar_get_b32(ptr, &cache_i->tweak); +} + +static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache) { + const unsigned char *ptr = cache->data; + ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_tweak_cache_magic, 4) == 0); + ptr += 4; + secp256k1_point_load_ext(&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; @@ -412,4 +445,82 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar return ret; } +int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *ec_pk, const secp256k1_xonly_pubkey *xonly_pk) { + secp256k1_ge pk; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(ec_pk != NULL); + memset(ec_pk, 0, sizeof(*ec_pk)); + ARG_CHECK(xonly_pk != NULL); + + /* The output of keygen is an aggregated public key that *always* has an + * even Y coordinate. */ + if (!secp256k1_xonly_pubkey_load(ctx, &pk, xonly_pk)) { + return 0; + } + secp256k1_pubkey_save(ec_pk, &pk); + return 1; +} + +int secp256k1_frost_pubkey_tweak(const secp256k1_context* ctx, secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_xonly_pubkey *pk) { + secp256k1_tweak_cache_internal cache_i = { 0 }; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(tweak_cache != NULL); + ARG_CHECK(pk != NULL); + + /* The output of keygen is an aggregated public key that *always* has an + * even Y coordinate. */ + if (!secp256k1_xonly_pubkey_load(ctx, &cache_i.pk, pk)) { + return 0; + } + secp256k1_tweak_cache_save(tweak_cache, &cache_i); + + return 1; +} + +static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32, int xonly) { + secp256k1_tweak_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(tweak_cache != NULL); + ARG_CHECK(tweak32 != NULL); + + if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_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_tweak_cache_save(tweak_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_tweak_cache *tweak_cache, const unsigned char *tweak32) { + return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 0); +} + +int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32) { + return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 1); +} + #endif From 67c21beadd146026a88174643cb0a455cba8b7f9 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 15 Jul 2024 22:56:47 -0700 Subject: [PATCH 06/23] frost: nonce aggregation and adaptor signatures This commit adds nonce aggregation, as well as adaptor signatures. --- include/secp256k1_frost.h | 141 ++++++++++++++- 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 | 15 ++ src/modules/frost/session_impl.h | 239 ++++++++++++++++++++++++++ 7 files changed, 568 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 c2d65543e..fca711ac6 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. @@ -421,6 +431,135 @@ 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 + * agg_pk: the FROST-aggregated public key + * 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 + * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + * 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 secp256k1_xonly_pubkey *agg_pk, + const unsigned char *my_id33, + const unsigned char * const* ids33, + const secp256k1_frost_tweak_cache *tweak_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 bf96133fa..fb17f3c67 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -19,6 +19,10 @@ typedef struct { int parity_acc; } secp256k1_tweak_cache_internal; +static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache); + static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share); +static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33); + #endif 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 d4b922beb..82bd915a4 100644 --- a/src/modules/frost/session.h +++ b/src/modules/frost/session.h @@ -7,4 +7,19 @@ #ifndef SECP256K1_MODULE_FROST_SESSION_H #define SECP256K1_MODULE_FROST_SESSION_H +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_frost.h" + +#include "../../scalar.h" + +typedef struct { + int fin_nonce_parity; + unsigned char fin_nonce[32]; + secp256k1_scalar noncecoef; + secp256k1_scalar challenge; + secp256k1_scalar s_part; +} secp256k1_frost_session_internal; + +static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_frost_session_internal *session_i, const secp256k1_frost_session *session); + #endif diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index 07c531965..84ad02189 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; @@ -230,4 +273,200 @@ int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secn return ret; } +static int secp256k1_frost_sum_nonces(const secp256k1_context* ctx, secp256k1_gej *summed_nonces, const secp256k1_frost_pubnonce * const *pubnonces, size_t n_pubnonces) { + size_t i; + int j; + + secp256k1_gej_set_infinity(&summed_nonces[0]); + secp256k1_gej_set_infinity(&summed_nonces[1]); + + for (i = 0; i < n_pubnonces; i++) { + secp256k1_ge nonce_pt[2]; + if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonces[i])) { + return 0; + } + for (j = 0; j < 2; j++) { + secp256k1_gej_add_ge_var(&summed_nonces[j], &summed_nonces[j], &nonce_pt[j], NULL); + } + } + return 1; +} + +/* TODO: consider updating to frost-08 to address maleability at the cost of performance */ +/* See https://github.com/cfrg/draft-irtf-cfrg-frost/pull/217 */ +static int secp256k1_frost_compute_noncehash(const secp256k1_context* ctx, unsigned char *noncehash, const unsigned char *msg, const secp256k1_frost_pubnonce * const *pubnonces, size_t n_pubnonces, const unsigned char *pk32, const unsigned char * const *ids33) { + unsigned char buf[66]; + secp256k1_sha256 sha; + size_t i; + + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/noncecoef", sizeof("FROST/noncecoef") - 1); + /* TODO: sort by index */ + for (i = 0; i < n_pubnonces; i++) { + secp256k1_scalar idx; + + if (!secp256k1_frost_compute_indexhash(&idx, ids33[i])) { + return 0; + } + secp256k1_scalar_get_b32(buf, &idx); + secp256k1_sha256_write(&sha, buf, 32); + if (!secp256k1_frost_pubnonce_serialize(ctx, buf, pubnonces[i])) { + return 0; + } + secp256k1_sha256_write(&sha, buf, sizeof(buf)); + } + secp256k1_sha256_write(&sha, pk32, 32); + secp256k1_sha256_write(&sha, msg, 32); + secp256k1_sha256_finalize(&sha, noncehash); + return 1; +} + +static int secp256k1_frost_nonce_process_internal(const secp256k1_context* ctx, int *fin_nonce_parity, unsigned char *fin_nonce, secp256k1_scalar *b, secp256k1_gej *aggnoncej, const unsigned char *msg, const secp256k1_frost_pubnonce * const *pubnonces, size_t n_pubnonces, const unsigned char *pk32, const unsigned char * const *ids33) { + unsigned char noncehash[32]; + secp256k1_ge fin_nonce_pt; + secp256k1_gej fin_nonce_ptj; + secp256k1_ge aggnonce[2]; + + secp256k1_ge_set_gej(&aggnonce[0], &aggnoncej[0]); + secp256k1_ge_set_gej(&aggnonce[1], &aggnoncej[1]); + if (!secp256k1_frost_compute_noncehash(ctx, noncehash, msg, pubnonces, n_pubnonces, pk32, ids33)) { + return 0; + } + /* fin_nonce = aggnonce[0] + b*aggnonce[1] */ + secp256k1_scalar_set_b32(b, noncehash, NULL); + secp256k1_ecmult(&fin_nonce_ptj, &aggnoncej[1], b, NULL); + secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &aggnonce[0], NULL); + secp256k1_ge_set_gej(&fin_nonce_pt, &fin_nonce_ptj); + + if (secp256k1_ge_is_infinity(&fin_nonce_pt)) { + /* unreachable with overwhelming probability */ + return 0; + } + secp256k1_fe_normalize_var(&fin_nonce_pt.x); + secp256k1_fe_get_b32(fin_nonce, &fin_nonce_pt.x); + + secp256k1_fe_normalize_var(&fin_nonce_pt.y); + *fin_nonce_parity = secp256k1_fe_is_odd(&fin_nonce_pt.y); + return 1; +} + +static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsigned char * const *ids33, size_t n_participants, const unsigned char *my_id33) { + size_t i; + secp256k1_scalar num; + secp256k1_scalar den; + secp256k1_scalar party_idx; + + secp256k1_scalar_set_int(&num, 1); + secp256k1_scalar_set_int(&den, 1); + if (!secp256k1_frost_compute_indexhash(&party_idx, my_id33)) { + return 0; + } + for (i = 0; i < n_participants; i++) { + secp256k1_scalar mul; + + if (!secp256k1_frost_compute_indexhash(&mul, ids33[i])) { + return 0; + } + if (secp256k1_scalar_eq(&mul, &party_idx)) { + continue; + } + + secp256k1_scalar_negate(&mul, &mul); + secp256k1_scalar_mul(&num, &num, &mul); + secp256k1_scalar_add(&mul, &mul, &party_idx); + secp256k1_scalar_mul(&den, &den, &mul); + } + + secp256k1_scalar_inverse_var(&den, &den); + secp256k1_scalar_mul(r, &num, &den); + + return 1; +} + +int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_session *session, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, const unsigned char *my_id33, const unsigned char * const *ids33, const secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_pubkey *adaptor) { + secp256k1_ge aggnonce_pt[2]; + secp256k1_gej aggnonce_ptj[2]; + unsigned char fin_nonce[32]; + secp256k1_frost_session_internal session_i = { 0 }; + unsigned char pk32[32]; + size_t i; + secp256k1_scalar l; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(pubnonces != NULL); + ARG_CHECK(ids33 != NULL); + ARG_CHECK(my_id33 != NULL); + ARG_CHECK(pk != NULL); + ARG_CHECK(n_pubnonces > 1); + + if (!secp256k1_xonly_pubkey_serialize(ctx, pk32, pk)) { + return 0; + } + + if (!secp256k1_frost_sum_nonces(ctx, aggnonce_ptj, pubnonces, n_pubnonces)) { + return 0; + } + for (i = 0; i < 2; i++) { + if (secp256k1_gej_is_infinity(&aggnonce_ptj[i])) { + /* There must be at least one dishonest signer. If we would return 0 + here, we will never be able to determine who it is. Therefore, we + should continue such that the culprit is revealed when collecting + and verifying partial signatures. + However, dealing with the point at infinity (loading, + de-/serializing) would require a lot of extra code complexity. + Instead, we set the aggregate nonce to some arbitrary point (the + generator). This is secure, because it only restricts the + abilities of the attacker: an attacker that forces the sum of + nonces to be infinity by sending some maliciously generated nonce + pairs can be turned into an attacker that forces the sum to be + the generator (by simply adding the generator to one of the + malicious nonces), and this does not change the winning condition + of the EUF-CMA game. */ + aggnonce_pt[i] = secp256k1_ge_const_g; + } else { + secp256k1_ge_set_gej(&aggnonce_pt[i], &aggnonce_ptj[i]); + } + } + /* Add public adaptor to nonce */ + if (adaptor != NULL) { + secp256k1_ge adaptorp; + if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) { + return 0; + } + secp256k1_gej_add_ge_var(&aggnonce_ptj[0], &aggnonce_ptj[0], &adaptorp, NULL); + } + if (!secp256k1_frost_nonce_process_internal(ctx, &session_i.fin_nonce_parity, fin_nonce, &session_i.noncecoef, aggnonce_ptj, msg32, pubnonces, n_pubnonces, pk32, ids33)) { + return 0; + } + + secp256k1_schnorrsig_challenge(&session_i.challenge, fin_nonce, msg32, 32, pk32); + + /* If there is a tweak then set `challenge` times `tweak` to the `s`-part.*/ + secp256k1_scalar_set_int(&session_i.s_part, 0); + if (tweak_cache != NULL) { + secp256k1_tweak_cache_internal cache_i; + if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { + return 0; + } + if (!secp256k1_scalar_is_zero(&cache_i.tweak)) { + secp256k1_scalar e_tmp; + secp256k1_scalar_mul(&e_tmp, &session_i.challenge, &cache_i.tweak); + if (secp256k1_fe_is_odd(&cache_i.pk.y)) { + secp256k1_scalar_negate(&e_tmp, &e_tmp); + } + secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &e_tmp); + } + } + /* Update the challenge by multiplying the Lagrange coefficient to prepare + * for signing. */ + if (!secp256k1_frost_lagrange_coefficient(&l, ids33, n_pubnonces, my_id33)) { + return 0; + } + secp256k1_scalar_mul(&session_i.challenge, &session_i.challenge, &l); + memcpy(session_i.fin_nonce, fin_nonce, sizeof(session_i.fin_nonce)); + secp256k1_frost_session_save(session, &session_i); + return 1; +} + #endif From ef15156ffb37a3cc27758b0f9206827c0669e340 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Mon, 15 Jul 2024 23:01:42 -0700 Subject: [PATCH 07/23] frost: signature generation and aggregation This commit adds signature generation and aggregation, as well as partial signature serialization and parsing. --- include/secp256k1_frost.h | 126 +++++++++++++++++++- src/modules/frost/session_impl.h | 190 +++++++++++++++++++++++++++++++ 2 files changed, 315 insertions(+), 1 deletion(-) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index fca711ac6..88bb06f2e 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -14,7 +14,8 @@ extern "C" { * * This module implements a variant of Flexible Round-Optimized Schnorr * Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg - * (https://crysp.uwaterloo.ca/software/frost/). + * (https://crysp.uwaterloo.ca/software/frost/). Signatures are compatible with + * BIP-340 ("Schnorr"). * * The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public * key tweaking, and adaptor signatures. @@ -88,6 +89,15 @@ typedef struct { unsigned char data[133]; } secp256k1_frost_session; +/** Opaque data structure that holds a partial FROST signature. + * + * Guaranteed to be 36 bytes in size. Serialized and parsed with + * `frost_partial_sig_serialize` and `frost_partial_sig_parse`. + */ +typedef struct { + unsigned char data[36]; +} secp256k1_frost_partial_sig; + /** Parse a signer's public nonce. * * Returns: 1 when the nonce could be parsed, 0 otherwise. @@ -114,6 +124,36 @@ SECP256K1_API int secp256k1_frost_pubnonce_serialize( const secp256k1_frost_pubnonce *nonce ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +/** Serialize a FROST partial signature + * + * Returns: 1 when the signature could be serialized, 0 otherwise + * Args: ctx: pointer to a context object + * Out: out32: pointer to a 32-byte array to store the serialized signature + * In: sig: pointer to the signature + */ +SECP256K1_API int secp256k1_frost_partial_sig_serialize( + const secp256k1_context *ctx, + unsigned char *out32, + const secp256k1_frost_partial_sig *sig +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse a FROST partial signature. + * + * Returns: 1 when the signature could be parsed, 0 otherwise. + * Args: ctx: pointer to a context object + * Out: sig: pointer to a signature object + * In: in32: pointer to the 32-byte signature to be parsed + * + * After the call, sig will always be initialized. If parsing failed or the + * encoded numbers are out of range, signature verification with it is + * guaranteed to fail for every message and public key. + */ +SECP256K1_API int secp256k1_frost_partial_sig_parse( + const secp256k1_context *ctx, + secp256k1_frost_partial_sig *sig, + const unsigned char *in32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + /** Serialize a FROST share * * Returns: 1 when the share could be serialized, 0 otherwise @@ -467,6 +507,90 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process( const secp256k1_pubkey *adaptor ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8); +/** Produces a partial signature + * + * This function overwrites the given secnonce with zeros and will abort if given a + * secnonce that is all zeros. This is a best effort attempt to protect against nonce + * reuse. However, this is of course easily defeated if the secnonce has been + * copied (or serialized). Remember that nonce reuse will leak the secret key! + * + * Returns: 0 if the arguments are invalid or the provided secnonce has already + * been used for signing, 1 otherwise + * Args: ctx: pointer to a context object + * Out: partial_sig: pointer to struct to store the partial signature + * In/Out: secnonce: pointer to the secnonce struct created in + * frost_nonce_gen that has been never used in a + * partial_sign call before + * In: agg_share: the aggregated share + * session: pointer to the session that was created with + * frost_nonce_process + * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + */ +SECP256K1_API int secp256k1_frost_partial_sign( + const secp256k1_context *ctx, + secp256k1_frost_partial_sig *partial_sig, + secp256k1_frost_secnonce *secnonce, + const secp256k1_frost_share *agg_share, + const secp256k1_frost_session *session, + const secp256k1_frost_tweak_cache *tweak_cache +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Verifies an individual signer's partial signature + * + * The signature is verified for a specific signing session. In order to avoid + * accidentally verifying a signature from a different or non-existing signing + * session, you must ensure the following: + * 1. The `tweak_cache` argument is identical to the one used to create the + * `session` with `frost_nonce_process`. + * 2. The `pubshare` argument must be the output of + * `secp256k1_frost_compute_pubshare` for the signer's ID. + * 3. The `pubnonce` argument must be identical to the one sent by the + * signer and used to create the `session` with `frost_nonce_process`. + * + * This function can be used to assign blame for a failed signature. + * + * Returns: 0 if the arguments are invalid or the partial signature does not + * verify, 1 otherwise + * Args ctx: pointer to a context object + * In: partial_sig: pointer to partial signature to verify, sent by + * the signer associated with `pubnonce` and `pubkey` + * pubnonce: public nonce of the signer in the signing session + * pubshare: public verification share of the signer in the signing + * session that is the output of + * `secp256k1_frost_compute_pubshare` + * session: pointer to the session that was created with + * `frost_nonce_process` + * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_verify( + const secp256k1_context *ctx, + const secp256k1_frost_partial_sig *partial_sig, + const secp256k1_frost_pubnonce *pubnonce, + const secp256k1_pubkey *pubshare, + const secp256k1_frost_session *session, + const secp256k1_frost_tweak_cache *tweak_cache +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Aggregates partial signatures + * + * Returns: 0 if the arguments are invalid, 1 otherwise (which does NOT mean + * the resulting signature verifies). + * Args: ctx: pointer to a context object + * Out: sig64: complete (but possibly invalid) Schnorr signature + * In: session: pointer to the session that was created with + * frost_nonce_process + * partial_sigs: array of pointers to partial signatures to aggregate + * n_sigs: number of elements in the partial_sigs array. Must be + * greater than 0. + */ +SECP256K1_API int secp256k1_frost_partial_sig_agg( + const secp256k1_context *ctx, + unsigned char *sig64, + const secp256k1_frost_session *session, + const secp256k1_frost_partial_sig * const *partial_sigs, + size_t n_sigs +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + /** Extracts the nonce_parity bit from a session * * This is used for adaptor signatures. diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index 84ad02189..bba1f7095 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -117,6 +117,23 @@ static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_ return 1; } +static const unsigned char secp256k1_frost_partial_sig_magic[4] = { 0x8d, 0xd8, 0x31, 0x6e }; + +static void secp256k1_frost_partial_sig_save(secp256k1_frost_partial_sig* sig, secp256k1_scalar *s) { + memcpy(&sig->data[0], secp256k1_frost_partial_sig_magic, 4); + secp256k1_scalar_get_b32(&sig->data[4], s); +} + +static int secp256k1_frost_partial_sig_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_partial_sig* sig) { + int overflow; + + ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_frost_partial_sig_magic, 4) == 0); + secp256k1_scalar_set_b32(s, &sig->data[4], &overflow); + /* Parsed signatures can not overflow */ + VERIFY_CHECK(!overflow); + return 1; +} + int secp256k1_frost_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_frost_pubnonce* nonce) { secp256k1_ge ge[2]; int i; @@ -163,6 +180,29 @@ int secp256k1_frost_pubnonce_parse(const secp256k1_context* ctx, secp256k1_frost return 1; } +int secp256k1_frost_partial_sig_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_partial_sig* sig) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out32 != NULL); + ARG_CHECK(sig != NULL); + memcpy(out32, &sig->data[4], 32); + return 1; +} + +int secp256k1_frost_partial_sig_parse(const secp256k1_context* ctx, secp256k1_frost_partial_sig* sig, const unsigned char *in32) { + secp256k1_scalar tmp; + int overflow; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(in32 != NULL); + + secp256k1_scalar_set_b32(&tmp, in32, &overflow); + if (overflow) { + return 0; + } + secp256k1_frost_partial_sig_save(sig, &tmp); + return 1; +} + static void secp256k1_nonce_function_frost(secp256k1_scalar *k, const unsigned char *session_id, const unsigned char *msg32, const unsigned char *key32, const unsigned char *pk32, const unsigned char *extra_input32) { secp256k1_sha256 sha; unsigned char seed[32]; @@ -469,4 +509,154 @@ int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_ return 1; } +void secp256k1_frost_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) { + secp256k1_scalar_clear(sk); + secp256k1_scalar_clear(&k[0]); + secp256k1_scalar_clear(&k[1]); +} + +int secp256k1_frost_partial_sign(const secp256k1_context* ctx, secp256k1_frost_partial_sig *partial_sig, secp256k1_frost_secnonce *secnonce, const secp256k1_frost_share *share, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) { + secp256k1_scalar sk; + secp256k1_scalar k[2]; + secp256k1_scalar s; + secp256k1_frost_session_internal session_i; + int ret; + + VERIFY_CHECK(ctx != NULL); + + ARG_CHECK(secnonce != NULL); + /* Fails if the magic doesn't match */ + ret = secp256k1_frost_secnonce_load(ctx, k, secnonce); + /* Set nonce to zero to avoid nonce reuse. This will cause subsequent calls + * of this function to fail */ + memset(secnonce, 0, sizeof(*secnonce)); + if (!ret) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(share != NULL); + ARG_CHECK(session != NULL); + + if (!secp256k1_frost_share_load(ctx, &sk, share)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + if (!secp256k1_frost_session_load(ctx, &session_i, session)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + + if (tweak_cache != NULL) { + secp256k1_tweak_cache_internal cache_i; + if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { + secp256k1_frost_partial_sign_clear(&sk, k); + return 0; + } + if (secp256k1_fe_is_odd(&cache_i.pk.y) != cache_i.parity_acc) { + secp256k1_scalar_negate(&sk, &sk); + } + } + + if (session_i.fin_nonce_parity) { + secp256k1_scalar_negate(&k[0], &k[0]); + secp256k1_scalar_negate(&k[1], &k[1]); + } + + /* Sign */ + secp256k1_scalar_mul(&s, &session_i.challenge, &sk); + secp256k1_scalar_mul(&k[1], &session_i.noncecoef, &k[1]); + secp256k1_scalar_add(&k[0], &k[0], &k[1]); + secp256k1_scalar_add(&s, &s, &k[0]); + secp256k1_frost_partial_sig_save(partial_sig, &s); + secp256k1_frost_partial_sign_clear(&sk, k); + return 1; +} + +int secp256k1_frost_partial_sig_verify(const secp256k1_context* ctx, const secp256k1_frost_partial_sig *partial_sig, const secp256k1_frost_pubnonce *pubnonce, const secp256k1_pubkey *pubshare, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) { + secp256k1_frost_session_internal session_i; + secp256k1_scalar e, s; + secp256k1_gej pkj; + secp256k1_ge nonce_pt[2]; + secp256k1_gej rj; + secp256k1_gej tmp; + secp256k1_ge pkp; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(partial_sig != NULL); + ARG_CHECK(pubnonce != NULL); + ARG_CHECK(pubshare != NULL); + ARG_CHECK(session != NULL); + + if (!secp256k1_frost_session_load(ctx, &session_i, session)) { + return 0; + } + + /* Compute "effective" nonce rj = aggnonce[0] + b*aggnonce[1] */ + /* TODO: use multiexp to compute -s*G + e*pubshare + aggnonce[0] + b*aggnonce[1] */ + if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonce)) { + return 0; + } + secp256k1_gej_set_ge(&rj, &nonce_pt[1]); + secp256k1_ecmult(&rj, &rj, &session_i.noncecoef, NULL); + secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pt[0], NULL); + + if (!secp256k1_pubkey_load(ctx, &pkp, pubshare)) { + return 0; + } + + secp256k1_scalar_set_int(&e, 1); + if (tweak_cache != NULL) { + secp256k1_tweak_cache_internal cache_i; + if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { + return 0; + } + if (secp256k1_fe_is_odd(&cache_i.pk.y) + != cache_i.parity_acc) { + secp256k1_scalar_negate(&e, &e); + } + } + secp256k1_scalar_mul(&e, &e, &session_i.challenge); + + if (!secp256k1_frost_partial_sig_load(ctx, &s, partial_sig)) { + return 0; + } + /* Compute -s*G + e*pkj + rj (e already includes the lagrange coefficient l) */ + secp256k1_scalar_negate(&s, &s); + secp256k1_gej_set_ge(&pkj, &pkp); + secp256k1_ecmult(&tmp, &pkj, &e, &s); + if (session_i.fin_nonce_parity) { + secp256k1_gej_neg(&rj, &rj); + } + secp256k1_gej_add_var(&tmp, &tmp, &rj, NULL); + + return secp256k1_gej_is_infinity(&tmp); +} + +int secp256k1_frost_partial_sig_agg(const secp256k1_context* ctx, unsigned char *sig64, const secp256k1_frost_session *session, const secp256k1_frost_partial_sig * const* partial_sigs, size_t n_sigs) { + size_t i; + secp256k1_frost_session_internal session_i; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(session != NULL); + ARG_CHECK(partial_sigs != NULL); + ARG_CHECK(n_sigs > 0); + + if (!secp256k1_frost_session_load(ctx, &session_i, session)) { + return 0; + } + for (i = 0; i < n_sigs; i++) { + secp256k1_scalar term; + if (!secp256k1_frost_partial_sig_load(ctx, &term, partial_sigs[i])) { + return 0; + } + secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &term); + } + secp256k1_scalar_get_b32(&sig64[32], &session_i.s_part); + memcpy(&sig64[0], session_i.fin_nonce, 32); + return 1; +} + #endif From c2d48a7039cc91880bd714d79b775975518ff9b8 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Tue, 16 Jul 2024 21:43:29 -0700 Subject: [PATCH 08/23] frost: add example file This commit adds an example file to demonstrate how to use the module. --- .gitignore | 1 + Makefile.am | 11 ++ examples/frost.c | 294 ++++++++++++++++++++++++++++++++++++++ include/secp256k1_frost.h | 3 +- 4 files changed, 308 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..8a028cc46 --- /dev/null +++ b/examples/frost.c @@ -0,0 +1,294 @@ +/*********************************************************************** + * 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" +/* Number of public keys involved in creating the aggregate signature */ +#define N_SIGNERS 5 + +/* Threshold required in creating the aggregate signature */ +#define THRESHOLD 3 + +struct signer_secrets { + secp256k1_keypair keypair; + secp256k1_frost_share agg_share; + secp256k1_frost_secnonce secnonce; + unsigned char seed[32]; +}; + +struct signer { + secp256k1_pubkey pubshare; + secp256k1_frost_pubnonce pubnonce; + secp256k1_frost_session session; + secp256k1_frost_partial_sig partial_sig; + secp256k1_pubkey vss_commitment[THRESHOLD]; + unsigned char vss_hash[32]; + unsigned char pok[64]; + unsigned char id[33]; +}; + +/* Create a key pair and store it in seckey and pubkey */ +int create_keypair_and_seed(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) { + unsigned char seckey[32]; + secp256k1_pubkey pubkey_tmp; + 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; + } + if (!fill_random(signer_secrets->seed, sizeof(signer_secrets->seed))) { + return 0; + } + return 1; +} + +/* Create shares and coefficient commitments */ +int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, secp256k1_xonly_pubkey *pk) { + int i, j; + secp256k1_frost_share shares[N_SIGNERS][N_SIGNERS]; + const secp256k1_pubkey *vss_commitments[N_SIGNERS]; + const unsigned char *ids[N_SIGNERS]; + + for (i = 0; i < N_SIGNERS; i++) { + vss_commitments[i] = signer[i].vss_commitment; + ids[i] = signer[i].id; + } + + for (i = 0; i < N_SIGNERS; i++) { + /* Generate a polynomial share for the participants */ + if (!secp256k1_frost_shares_gen(ctx, shares[i], signer[i].vss_commitment, signer[i].pok, signer_secrets[i].seed, THRESHOLD, N_SIGNERS, ids)) { + return 0; + } + } + + /* KeyGen communication round 1: exchange shares and coefficient + * commitments */ + for (i = 0; i < N_SIGNERS; i++) { + const secp256k1_frost_share *assigned_shares[N_SIGNERS]; + + /* Each participant receives a share from each participant (including + * themselves) corresponding to their index. */ + for (j = 0; j < N_SIGNERS; j++) { + assigned_shares[j] = &shares[j][i]; + } + /* Each participant aggregates the shares they received. */ + if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, pk, assigned_shares, vss_commitments, N_SIGNERS, THRESHOLD, signer[i].id)) { + return 0; + } + for (j = 0; j < N_SIGNERS; j++) { + /* Each participant verifies their shares. share_agg calls this + * internally, so it is only neccessary to call this function if + * share_agg returns an error, to determine which participant(s) + * submitted faulty data. */ + if (!secp256k1_frost_share_verify(ctx, THRESHOLD, signer[i].id, assigned_shares[j], &vss_commitments[j])) { + return 0; + } + /* Each participant generates public verification shares that are + * used for verifying partial signatures. */ + if (!secp256k1_frost_compute_pubshare(ctx, &signer[j].pubshare, THRESHOLD, signer[j].id, vss_commitments, N_SIGNERS)) { + return 0; + } + } + } + + return 1; +} + +/* Tweak the pubkey corresponding to the provided tweak cache, update the cache + * and return the tweaked aggregate pk. */ +int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_frost_tweak_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.."; + + if (!secp256k1_frost_pubkey_tweak(ctx, cache, pk)) { + return 0; + } + + /* 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 */ +int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char* msg32, secp256k1_xonly_pubkey *pk, unsigned char *sig64, const secp256k1_frost_tweak_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].agg_share, msg32, pk, 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; + /* pubkeys[i] = &signer[signer_id].pubkey; */ + 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, pk, 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].agg_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]; + secp256k1_xonly_pubkey pk; + secp256k1_frost_tweak_cache cache; + unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!"; + unsigned char sig[64]; + + /* 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_and_seed(ctx, &signer_secrets[i], &signers[i])) { + printf("FAILED\n"); + return 1; + } + } + printf("ok\n"); + printf("Creating shares........."); + if (!create_shares(ctx, signer_secrets, signers, &pk)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + printf("Tweaking................"); + /* Optionally tweak the aggregate key */ + if (!tweak(ctx, &pk, &cache)) { + printf("FAILED\n"); + return 1; + } + printf("ok\n"); + printf("Signing message........."); + if (!sign(ctx, signer_secrets, signers, msg, &pk, sig, &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 88bb06f2e..13c5c3e12 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 fb9c5e8db928edb47cfa86fdf7753ed6729cb058 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Tue, 16 Jul 2024 23:05:16 -0700 Subject: [PATCH 09/23] frost: 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 | 119 ++++ src/modules/frost/Makefile.am.include | 1 + src/modules/frost/tests_impl.h | 946 ++++++++++++++++++++++++++ src/tests.c | 8 + 6 files changed, 1098 insertions(+), 12 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..d0e0a5a78 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,119 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { CHECK(ret == 1); } #endif + +#ifdef ENABLE_MODULE_FROST + { + secp256k1_pubkey pk[2]; + secp256k1_xonly_pubkey agg_pk; + unsigned char session_id[32]; + unsigned char seed[2][32]; + secp256k1_frost_secnonce secnonce[2]; + secp256k1_frost_pubnonce pubnonce[2]; + const secp256k1_frost_pubnonce *pubnonce_ptr[2]; + secp256k1_frost_tweak_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][2]; + const secp256k1_frost_share *share_ptr[2]; + secp256k1_frost_share agg_share; + const secp256k1_pubkey *vss_ptr[2]; + unsigned char pok[2][64]; + secp256k1_pubkey vss_commitment[2][2]; + unsigned char key2[32]; + secp256k1_keypair keypair2; + unsigned char id[2][33]; + const unsigned char *id_ptr[2]; + size_t size = 33; + + 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[0], key, 32); + seed[0][0] = seed[0][0] + 1; + memcpy(seed[0], key, 32); + seed[1][0] = seed[1][0] + 2; + memcpy(extra_input, key, sizeof(extra_input)); + extra_input[0] = extra_input[0] + 3; + memcpy(sec_adaptor, key, sizeof(sec_adaptor)); + sec_adaptor[0] = extra_input[0] + 4; + memcpy(key2, key, sizeof(key2)); + key2[0] = key2[0] + 5; + memcpy(session_id, key, sizeof(session_id)); + session_id[0] = session_id[0] + 6; + partial_sig_ptr[0] = &partial_sig; + share_ptr[0] = &shares[0][0]; + share_ptr[1] = &shares[1][0]; + vss_ptr[0] = vss_commitment[0]; + vss_ptr[1] = vss_commitment[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(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key2, 32); + SECP256K1_CHECKMEM_UNDEFINE(seed[0], 32); + SECP256K1_CHECKMEM_UNDEFINE(seed[1], 32); + ret = secp256k1_frost_shares_gen(ctx, shares[0], vss_commitment[0], pok[0], seed[0], 2, 2, id_ptr); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + ret = secp256k1_frost_shares_gen(ctx, shares[1], vss_commitment[1], pok[1], seed[1], 2, 2, id_ptr); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + /* share_agg */ + SECP256K1_CHECKMEM_DEFINE(&vss_commitment[0][0], sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_DEFINE(&vss_commitment[0][1], sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1][0], sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1][1], sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_DEFINE(pok[0], 64); + ret = secp256k1_frost_share_agg(ctx, &agg_share, &agg_pk, share_ptr, vss_ptr, 2, 2, id_ptr[0]); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + /* 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)); + CHECK(secp256k1_frost_pubkey_tweak(ctx, &cache, &agg_pk) == 1); + ret = secp256k1_frost_nonce_gen(ctx, &secnonce[0], &pubnonce[0], session_id, &agg_share, msg, &agg_pk, extra_input); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + ret = secp256k1_frost_nonce_gen(ctx, &secnonce[1], &pubnonce[1], session_id, &agg_share, msg, &agg_pk, extra_input); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + /* partial_sign */ + CHECK(secp256k1_frost_nonce_process(ctx, &session, pubnonce_ptr, 2, msg, &agg_pk, id_ptr[0], id_ptr, &cache, &adaptor) == 1); + ret = secp256k1_keypair_create(ctx, &keypair, key); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + ret = secp256k1_frost_partial_sign(ctx, &partial_sig, &secnonce[0], &agg_share, &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..ab256c7a1 --- /dev/null +++ b/src/modules/frost/tests_impl.h @@ -0,0 +1,946 @@ +/*********************************************************************** + * 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 aggregate, 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[5][3]; + const secp256k1_pubkey *vss_ptr[5]; + unsigned char pok[5][64]; + secp256k1_xonly_pubkey agg_pk; + unsigned char buf[5][32]; + secp256k1_frost_share shares[5][5]; + const secp256k1_frost_share *share_ptr[5]; + secp256k1_frost_share agg_share[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, j; + unsigned char id[5][33]; + const unsigned char *id_ptr[5]; + + for (i = 0; i < 5; i++) { + secp256k1_testrand256(buf[i]); + secp256k1_testrand256(sk[i]); + vss_ptr[i] = vss_commitment[i]; + pubnonce_ptr[i] = &pubnonce[i]; + partial_sig_ptr[i] = &partial_sig[i]; + id_ptr[i] = id[i]; + + CHECK(frost_create_pk(id[i], sk[i])); + } + for (i = 0; i < 5; i++) { + CHECK(secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], buf[i], 3, 5, id_ptr) == 1); + } + for (i = 0; i < 5; i++) { + for (j = 0; j < 5; j++) { + share_ptr[j] = &shares[j][i]; + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[j], 3, id_ptr[j], vss_ptr, 5) == 1); + } + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); + } + + secp256k1_testrand256(msg); + for (i = 0; i < 3; i++) { + secp256k1_testrand256(buf[i]); + + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], buf[i], &agg_share[i], NULL, NULL, NULL) == 1); + } + for (i = 0; i < 3; i++) { + CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, &agg_pk, id_ptr[i], id_ptr, NULL, NULL) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &agg_share[i], &session, NULL) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], &pubshare[i], &session, NULL) == 1); + } + CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &agg_pk) == 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[5][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_xonly_pubkey agg_pk; + secp256k1_pubkey full_agg_pk; + secp256k1_frost_tweak_cache tweak_cache; + secp256k1_frost_tweak_cache invalid_tweak_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[5][3]; + secp256k1_pubkey invalid_vss_commitment[5][3]; + const secp256k1_pubkey *vss_ptr[5]; + const secp256k1_pubkey *invalid_vss_ptr[5]; + secp256k1_pubkey invalid_vss_pk; + secp256k1_frost_share shares[5][5]; + secp256k1_frost_share invalid_share; + secp256k1_frost_share agg_share[5]; + unsigned char pok[5][64]; + const secp256k1_frost_share *share_ptr[5]; + const secp256k1_frost_share *invalid_share_ptr[5]; + secp256k1_pubkey pubshare[5]; + int i, j; + unsigned char id[5][33]; + const unsigned char *id_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_vss_pk, 0, sizeof(invalid_vss_pk)); + memset(&invalid_tweak_cache, 0, sizeof(invalid_tweak_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); + for (i = 0; i < 5; i++) { + pubnonce_ptr[i] = &pubnonce[i]; + invalid_pubnonce_ptr[i] = &pubnonce[i]; + vss_ptr[i] = vss_commitment[i]; + invalid_vss_ptr[i] = invalid_vss_commitment[i]; + partial_sig_ptr[i] = &partial_sig[i]; + invalid_partial_sig_ptr[i] = &partial_sig[i]; + id_ptr[i] = id[i]; + secp256k1_testrand256(session_id[i]); + secp256k1_testrand256(seed[i]); + secp256k1_testrand256(sk[i]); + CHECK(frost_create_pk(id[i], sk[i])); + } + invalid_pubnonce_ptr[0] = &invalid_pubnonce; + invalid_partial_sig_ptr[0] = &invalid_partial_sig; + for (i = 0; i < 5; i++) { + for (j = 0; j < 3; j++) { + invalid_vss_commitment[i][j] = invalid_vss_pk; + } + } + + /** main test body **/ + + /** Key generation **/ + for (i = 0; i < 5; i++) { + CHECK(secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 5, id_ptr) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, NULL, vss_commitment[i], pok[i], seed[i], 3, 5, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares[i], NULL, pok[i], seed[i], 3, 5, id_ptr)); + for (j = 0; j < 5; j++) { + CHECK(frost_memcmp_and_randomize(shares[i][j].data, zeros68, sizeof(shares[i][j].data)) == 0); + } + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], NULL, seed[i], 3, 5, id_ptr)); + for (j = 0; j < 5; j++) { + CHECK(frost_memcmp_and_randomize(shares[i][j].data, zeros68, sizeof(shares[i][j].data)) == 0); + } + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], NULL, 3, 5, id_ptr)); + for (j = 0; j < 5; j++) { + CHECK(frost_memcmp_and_randomize(shares[i][j].data, zeros68, sizeof(shares[i][j].data)) == 0); + } + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 0, 5, id_ptr)); + for (j = 0; j < 5; j++) { + CHECK(frost_memcmp_and_randomize(shares[i][j].data, zeros68, sizeof(shares[i][j].data)) == 0); + } + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 0, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 2, id_ptr)); + CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 5, NULL)); + for (j = 0; j < 5; j++) { + CHECK(frost_memcmp_and_randomize(shares[i][j].data, zeros68, sizeof(shares[i][j].data)) == 0); + } + + CHECK(secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 5, id_ptr) == 1); + } + + /* Share aggregation */ + for (i = 0; i < 5; i++) { + for (j = 0; j < 5; j++) { + share_ptr[j] = &shares[j][i]; + invalid_share_ptr[j] = &shares[j][i]; + } + invalid_share_ptr[0] = &invalid_share; + + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, NULL, &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, share_ptr, vss_ptr, 5, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, 5, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, 5, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + /* TODO: fix test */ + /* CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, invalid_vss_ptr, 5, 3, id_ptr[i])); */ + /* CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); */ + /* CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); */ + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, NULL)); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, invalid_share_ptr, vss_ptr, 5, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 0, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, 0, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 0, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, 5, 0, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); + } + + /* Share verification */ + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &vss_ptr[0]) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &vss_ptr[1]) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, NULL, share_ptr[0], &vss_ptr[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], NULL, &vss_ptr[1])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], &invalid_share, &vss_ptr[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &invalid_vss_ptr[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 0, id_ptr[4], share_ptr[0], &vss_ptr[0])); + + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &vss_ptr[0]) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[1], &vss_ptr[1]) == 1); + + /* Compute public verification share */ + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], vss_ptr, 5) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, NULL, 3, id_ptr[0], vss_ptr, 5)); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, NULL, vss_ptr, 5)); + 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, 5)); + 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_ptr, 5)); + 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_ptr, 5)); + 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, 5)); + 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_ptr, 0)); + 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, 0)); + 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_ptr, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[1], 3, id_ptr[1], vss_ptr, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[2], 3, id_ptr[2], vss_ptr, 5) == 1); + + /* pubkey_get */ + CHECK(secp256k1_frost_pubkey_get(CTX, &full_agg_pk, &agg_pk) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, NULL, &agg_pk)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, &full_agg_pk, NULL)); + CHECK(secp256k1_memcmp_var(&full_agg_pk, zeros68, sizeof(full_agg_pk)) == 0); + + /** Tweaking **/ + + /* pubkey_tweak */ + CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &agg_pk) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, NULL, &agg_pk)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &invalid_pk)); + + CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &agg_pk) == 1); + + /* tweak_add */ + { + int (*tweak_func[2]) (const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32); + tweak_func[0] = secp256k1_frost_pubkey_ec_tweak_add; + tweak_func[1] = secp256k1_frost_pubkey_xonly_tweak_add; + CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &agg_pk) == 1); + for (i = 0; i < 2; i++) { + secp256k1_pubkey tmp_output_pk; + secp256k1_frost_tweak_cache tmp_tweak_cache = tweak_cache; + CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1); + /* Reset tweak_cache */ + tmp_tweak_cache = tweak_cache; + CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1); + tmp_tweak_cache = tweak_cache; + CHECK((*tweak_func[i])(CTX, NULL, &tmp_tweak_cache, tweak) == 1); + tmp_tweak_cache = tweak_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_tweak_cache = tweak_cache; + CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, NULL)); + CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0); + tmp_tweak_cache = tweak_cache; + CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, max64) == 0); + CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0); + tmp_tweak_cache = tweak_cache; + /* Uninitialized tweak_cache */ + CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &invalid_tweak_cache, tweak)); + CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0); + } + } + + /** Session creation **/ + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, &agg_pk, max64) == 1); + CHECK_ILLEGAL(STATIC_CTX, secp256k1_frost_nonce_gen(STATIC_CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, &agg_pk, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, NULL, &pubnonce[0], session_id[0], &agg_share[0], msg, &agg_pk, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], NULL, session_id[0], &agg_share[0], msg, &agg_pk, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, &agg_share[0], msg, &agg_pk, 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, &agg_pk, 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, &agg_share[0], msg, &agg_pk, max64) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, msg, &agg_pk, max64) == 1); + /* invalid agg_share */ + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &invalid_share, msg, &agg_pk, 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], &agg_share[0], NULL, &agg_pk, max64) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, NULL, max64) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, &invalid_pk, 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], &agg_share[0], msg, &agg_pk, 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], &agg_share[1], NULL, NULL, NULL) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[2], &pubnonce[2], session_id[2], &agg_share[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, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, NULL, pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], NULL, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 0, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], invalid_pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, NULL, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, NULL, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, NULL, id_ptr, &tweak_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], NULL, &tweak_cache, &adaptor)); + CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, NULL, &adaptor) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &invalid_tweak_cache, &adaptor)); + CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, NULL) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, (secp256k1_pubkey *)&invalid_pk)); + + CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[1], id_ptr, &tweak_cache, &adaptor) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[2], id_ptr, &tweak_cache, &adaptor) == 1); + + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], &session[0], &tweak_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, &agg_share[0], &session[0], &tweak_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, NULL, &secnonce_tmp, &agg_share[0], &session[0], &tweak_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], NULL, &agg_share[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &invalid_secnonce, &agg_share[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, NULL, &session[0], &tweak_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], &tweak_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], NULL, &tweak_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], &invalid_session, &tweak_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], &session[0], NULL) == 1); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], &session[0], &invalid_tweak_cache)); + memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); + + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], &agg_share[0], &session[0], &tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], &agg_share[1], &session[1], &tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], &agg_share[2], &session[2], &tweak_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], &tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[0], &pubshare[0], &session[0], &tweak_cache) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, NULL, &pubnonce[0], &pubshare[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &invalid_partial_sig, &pubnonce[0], &pubshare[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], NULL, &pubshare[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &invalid_pubnonce, &pubshare[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], NULL, &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &invalid_vss_pk, &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], NULL, &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &invalid_session, &tweak_cache)); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], NULL) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &invalid_tweak_cache)); + + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], &pubshare[1], &session[1], &tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], &pubshare[2], &session[2], &tweak_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), &agg_pk) == 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), &agg_pk) == 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 < 4; i++) { + for (j = i+1; j < 5; j++) { + CHECK(secp256k1_scalar_eq(&k[i][0], &k[j][0]) == 0); + CHECK(secp256k1_scalar_eq(&k[i][1], &k[j][1]) == 0); + } + } +} + +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 aggregate public key using given secret + * keys and tweak_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_tweak_cache *tweak_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, agg_pk, ids33[0], ids33, tweak_cache, NULL) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, agg_pk, ids33[1], ids33, tweak_cache, NULL) == 1); + CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, agg_pk, ids33[2], ids33, tweak_cache, NULL) == 1); + + + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], sr0, &session[0], tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], sr1, &session[1], tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], sr2, &session[2], tweak_cache) == 1); + + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], sr_pk0, &session[0], tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], sr_pk1, &session[1], tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], sr_pk2, &session[2], tweak_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 aggregate 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_tweak_cache tweak_cache; + enum { N_TWEAKS = 8 }; + secp256k1_pubkey P[N_TWEAKS + 1]; + secp256k1_xonly_pubkey P_xonly[N_TWEAKS + 1]; + unsigned char seed[5][32]; + secp256k1_pubkey vss_commitment[5][3]; + const secp256k1_pubkey *vss_ptr[5]; + unsigned char pok[5][64]; + secp256k1_frost_share shares[5][5]; + const secp256k1_frost_share *share_ptr[5]; + secp256k1_frost_share agg_share[5]; + int i, j; + unsigned char id[5][33]; + const unsigned char *id_ptr[5]; + + /* Key Setup */ + for (i = 0; i < 5; i++) { + secp256k1_testrand256(seed[i]); + secp256k1_testrand256(sk[i]); + vss_ptr[i] = vss_commitment[i]; + id_ptr[i] = id[i]; + + CHECK(frost_create_pk(id[i], sk[i])); + } + for (i = 0; i < 5; i++) { + CHECK(secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 5, id_ptr) == 1); + } + for (i = 0; i < 5; i++) { + for (j = 0; j < 5; j++) { + share_ptr[j] = &shares[j][i]; + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[j], 3, id_ptr[j], vss_ptr, 5) == 1); + } + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &P_xonly[0], share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); + } + + frost_tweak_test_helper(&P_xonly[0], &agg_share[0], &agg_share[1], &agg_share[2], NULL, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); + CHECK(secp256k1_frost_pubkey_get(CTX, &P[0], &P_xonly[0])); + CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &P_xonly[0]) == 1); + + /* 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], &tweak_cache, tweak) == 1); + } else { + CHECK(secp256k1_frost_pubkey_ec_tweak_add(CTX, &P[i], &tweak_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], &agg_share[0], &agg_share[1], &agg_share[2], &tweak_cache, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); + } +} + +/* Performs a FROST DKG */ +void frost_dkg_test_helper(secp256k1_frost_share *agg_share, secp256k1_xonly_pubkey *agg_pk, const unsigned char * const* ids33) { + secp256k1_pubkey vss_commitment[5][3]; + const secp256k1_pubkey *vss_ptr[5]; + unsigned char pok[5][64]; + unsigned char seed[5][32]; + secp256k1_frost_share shares[5][5]; + const secp256k1_frost_share *share_ptr[5]; + int i, j; + + for (i = 0; i < 5; i++) { + secp256k1_testrand256(seed[i]); + vss_ptr[i] = vss_commitment[i]; + } + for (i = 0; i < 5; i++) { + CHECK(secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 5, ids33) == 1); + } + for (i = 0; i < 5; i++) { + for (j = 0; j < 5; j++) { + share_ptr[j] = &shares[j][i]; + } + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_pk, share_ptr, vss_ptr, 5, 3, ids33[i]) == 1); + } +} + +/* Signs a message with a FROST keypair */ +int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_share *agg_share, const secp256k1_xonly_pubkey *agg_pk, const unsigned char * const* ids33, const unsigned char *msg, const secp256k1_pubkey *adaptor) { + 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], agg_share, NULL, NULL, NULL) == 1); + } + for (i = 0; i < 3; i++) { + CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, agg_pk, ids33[i], ids33, NULL, adaptor) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &agg_share[i], &session, NULL) == 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 agg_share_a[5]; + secp256k1_frost_share agg_share_b[5]; + secp256k1_xonly_pubkey agg_pk_a; + secp256k1_xonly_pubkey agg_pk_b; + 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]; + + /* 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(agg_share_a, &agg_pk_a, id_ptr_a); + + /* 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(agg_share_b, &agg_pk_b, id_ptr_b); + + /* 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, agg_share_a, &agg_pk_a, id_ptr_a, tx_ab, &l); + + /* Bob setup */ + CHECK(secp256k1_frost_verify_adaptor(CTX, asig_ab, tx_ab, &agg_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, agg_share_b, &agg_pk_b, id_ptr_b, tx_bc, &r); + + /* Carol decrypt */ + CHECK(secp256k1_frost_verify_adaptor(CTX, asig_bc, tx_bc, &agg_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), &agg_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), &agg_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..00eb3bfe6 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7502,6 +7502,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 +7896,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 ef0f4aa38293d2efa4abfda1a2d63e3f0491184a Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Tue, 16 Jul 2024 23:33:54 -0700 Subject: [PATCH 10/23] frost: 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 | 95 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 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 13c5c3e12..f6764fa7e 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..c9bfa1347 --- /dev/null +++ b/src/modules/frost/frost.md @@ -0,0 +1,95 @@ +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. Each participant establishes a secure communications channel with each other + participant and uses that channel to transmit shares and commitments 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 x-only + public key with `secp256k1_keypair_xonly_pub`, and distribute it to each + other participant to be used as an authentication key and identifier. +2. Generate a VSS commitment, proof-of-knowledge, and shares with + `secp256k1_frost_shares_gen`. The VSS commitment and proof-of-knowledge 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 commitment set from each participant, call + `secp256k1_frost_share_agg` to compute the aggregate share, group public + key, and VSS hash. If this function returns an error, + `secp256k1_frost_share_verify` is called on each share to determine which + participants submitted faulty shares. +4. Optionally compute the public verification share by calling + `secp256k1_frost_compute_pubshare` with the x-only public key of each + participant. This share is required by `secp256k1_frost_partial_sig_verify` + to verify partial signatures generated by `secp256k1_frost_partial_sign`. + +# 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. 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. +2. Generate a pair of secret and public nonce with `secp256k1_frost_nonce_gen` + and send the public nonce to the other signers. +3. Process the aggregate nonce with `secp256k1_frost_nonce_process`. +4. Create a partial signature with `secp256k1_frost_partial_sign`. +5. Verify the partial signatures (optional in some scenarios) with + `secp256k1_frost_partial_sig_verify`. +6. 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 2). + +# 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 2 and 4. From 68b2867621062ac8b2d7b309a3f23923b595c424 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Thu, 1 Aug 2024 14:08:47 -0700 Subject: [PATCH 11/23] Verify proofs-of-knowledge --- examples/frost.c | 4 ++- include/secp256k1_frost.h | 3 +- src/ctime_tests.c | 6 +++- src/modules/frost/keygen_impl.h | 19 ++++++++++- src/modules/frost/tests_impl.h | 58 +++++++++++++++++++++------------ 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/examples/frost.c b/examples/frost.c index 8a028cc46..8a7e3b3bb 100644 --- a/examples/frost.c +++ b/examples/frost.c @@ -75,10 +75,12 @@ int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_se secp256k1_frost_share shares[N_SIGNERS][N_SIGNERS]; const secp256k1_pubkey *vss_commitments[N_SIGNERS]; const unsigned char *ids[N_SIGNERS]; + const unsigned char *poks[N_SIGNERS]; for (i = 0; i < N_SIGNERS; i++) { vss_commitments[i] = signer[i].vss_commitment; ids[i] = signer[i].id; + poks[i] = signer[i].pok; } for (i = 0; i < N_SIGNERS; i++) { @@ -99,7 +101,7 @@ int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_se assigned_shares[j] = &shares[j][i]; } /* Each participant aggregates the shares they received. */ - if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, pk, assigned_shares, vss_commitments, N_SIGNERS, THRESHOLD, signer[i].id)) { + if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, pk, assigned_shares, vss_commitments, poks, N_SIGNERS, THRESHOLD, signer[i].id)) { return 0; } for (j = 0; j < N_SIGNERS; j++) { diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index f6764fa7e..459cea029 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -255,10 +255,11 @@ SECP256K1_API int secp256k1_frost_share_agg( secp256k1_xonly_pubkey *agg_pk, const secp256k1_frost_share * const *shares, const secp256k1_pubkey * const *vss_commitments, + const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33 -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8); +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(9); /** Verifies a share received during a key generation session * diff --git a/src/ctime_tests.c b/src/ctime_tests.c index d0e0a5a78..44071b4d3 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -383,6 +383,7 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { unsigned char id[2][33]; const unsigned char *id_ptr[2]; size_t size = 33; + const unsigned char *pok_ptr[2]; id_ptr[0] = id[0]; id_ptr[1] = id[1]; @@ -406,6 +407,8 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { share_ptr[1] = &shares[1][0]; vss_ptr[0] = vss_commitment[0]; vss_ptr[1] = vss_commitment[1]; + pok_ptr[0] = pok[0]; + pok_ptr[1] = pok[1]; CHECK(secp256k1_keypair_create(ctx, &keypair, key)); CHECK(secp256k1_keypair_create(ctx, &keypair2, key2)); @@ -431,7 +434,8 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1][0], sizeof(secp256k1_pubkey)); SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1][1], sizeof(secp256k1_pubkey)); SECP256K1_CHECKMEM_DEFINE(pok[0], 64); - ret = secp256k1_frost_share_agg(ctx, &agg_share, &agg_pk, share_ptr, vss_ptr, 2, 2, id_ptr[0]); + SECP256K1_CHECKMEM_DEFINE(pok[1], 64); + ret = secp256k1_frost_share_agg(ctx, &agg_share, &agg_pk, share_ptr, vss_ptr, pok_ptr, 2, 2, id_ptr[0]); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); /* nonce_gen */ diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index fadd27f28..5a1d194ee 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -385,7 +385,7 @@ int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pub return 1; } -int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, secp256k1_xonly_pubkey *agg_pk, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, size_t n_shares, size_t threshold, const unsigned char *id33) { +int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, secp256k1_xonly_pubkey *agg_pk, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33) { secp256k1_frost_pubkey_combine_ecmult_data pubkey_combine_ecmult_data; secp256k1_gej pkj; secp256k1_ge pkp; @@ -393,6 +393,8 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar secp256k1_scalar acc; size_t i; int ret = 1; + secp256k1_sha256 sha; + unsigned char buf[32]; VERIFY_CHECK(ctx != NULL); ARG_CHECK(agg_share != NULL); @@ -400,6 +402,7 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar ARG_CHECK(agg_pk != NULL); memset(agg_pk, 0, sizeof(*agg_pk)); ARG_CHECK(shares != NULL); + ARG_CHECK(pok64s != NULL); ARG_CHECK(vss_commitments != NULL); ARG_CHECK(id33 != NULL); ARG_CHECK(n_shares > 1); @@ -409,6 +412,20 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar return 0; } + /* Verify proofs-of-knowledge */ + secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/KeygenPoK", sizeof("FROST/KeygenPoK") - 1); + secp256k1_sha256_finalize(&sha, buf); + for (i = 0; i < n_shares; i++) { + secp256k1_xonly_pubkey pk; + + if (!secp256k1_xonly_pubkey_from_pubkey(ctx, &pk, NULL, &vss_commitments[i][0])) { + return 0; + } + if (!secp256k1_schnorrsig_verify(ctx, pok64s[i], buf, 32, &pk)) { + return 0; + } + } + secp256k1_scalar_clear(&acc); for (i = 0; i < n_shares; i++) { secp256k1_scalar share_i; diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h index ab256c7a1..27719d3c3 100644 --- a/src/modules/frost/tests_impl.h +++ b/src/modules/frost/tests_impl.h @@ -58,6 +58,7 @@ void frost_simple_test(void) { int i, j; unsigned char id[5][33]; const unsigned char *id_ptr[5]; + const unsigned char *pok_ptr[5]; for (i = 0; i < 5; i++) { secp256k1_testrand256(buf[i]); @@ -66,6 +67,7 @@ void frost_simple_test(void) { pubnonce_ptr[i] = &pubnonce[i]; partial_sig_ptr[i] = &partial_sig[i]; id_ptr[i] = id[i]; + pok_ptr[i] = pok[i]; CHECK(frost_create_pk(id[i], sk[i])); } @@ -78,7 +80,7 @@ void frost_simple_test(void) { CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[j], 3, id_ptr[j], vss_ptr, 5) == 1); } - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); } secp256k1_testrand256(msg); @@ -177,6 +179,8 @@ void frost_api_tests(void) { int i, j; unsigned char id[5][33]; const unsigned char *id_ptr[5]; + const unsigned char *pok_ptr[5]; + const unsigned char *invalid_pok_ptr[5]; /** setup **/ memset(max64, 0xff, sizeof(max64)); @@ -205,6 +209,8 @@ void frost_api_tests(void) { partial_sig_ptr[i] = &partial_sig[i]; invalid_partial_sig_ptr[i] = &partial_sig[i]; id_ptr[i] = id[i]; + pok_ptr[i] = pok[i]; + invalid_pok_ptr[i] = pok[i]; secp256k1_testrand256(session_id[i]); secp256k1_testrand256(seed[i]); secp256k1_testrand256(sk[i]); @@ -212,6 +218,7 @@ void frost_api_tests(void) { } invalid_pubnonce_ptr[0] = &invalid_pubnonce; invalid_partial_sig_ptr[0] = &invalid_partial_sig; + invalid_pok_ptr[0] = max64; for (i = 0; i < 5; i++) { for (j = 0; j < 3; j++) { invalid_vss_commitment[i][j] = invalid_vss_pk; @@ -257,41 +264,48 @@ void frost_api_tests(void) { invalid_share_ptr[j] = &shares[j][i]; } invalid_share_ptr[0] = &invalid_share; - - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, NULL, &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i])); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, share_ptr, vss_ptr, 5, 3, id_ptr[i])); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, invalid_pok_ptr, 5, 3, id_ptr[i]) == 0); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, NULL, &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, pok_ptr, 5, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, invalid_vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); + CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, NULL, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, NULL)); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - /* TODO: fix test */ - /* CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, invalid_vss_ptr, 5, 3, id_ptr[i])); */ - /* CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); */ - /* CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); */ - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, invalid_share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, invalid_share_ptr, vss_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 0, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, 0, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 0, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, pok_ptr, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, 5, 0, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, NULL, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); } /* Share verification */ @@ -701,6 +715,7 @@ void frost_tweak_test(void) { int i, j; unsigned char id[5][33]; const unsigned char *id_ptr[5]; + const unsigned char *pok_ptr[5]; /* Key Setup */ for (i = 0; i < 5; i++) { @@ -708,6 +723,7 @@ void frost_tweak_test(void) { secp256k1_testrand256(sk[i]); vss_ptr[i] = vss_commitment[i]; id_ptr[i] = id[i]; + pok_ptr[i] = pok[i]; CHECK(frost_create_pk(id[i], sk[i])); } @@ -720,7 +736,7 @@ void frost_tweak_test(void) { CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[j], 3, id_ptr[j], vss_ptr, 5) == 1); } - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &P_xonly[0], share_ptr, vss_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &P_xonly[0], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); } frost_tweak_test_helper(&P_xonly[0], &agg_share[0], &agg_share[1], &agg_share[2], NULL, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); @@ -768,10 +784,12 @@ void frost_dkg_test_helper(secp256k1_frost_share *agg_share, secp256k1_xonly_pub secp256k1_frost_share shares[5][5]; const secp256k1_frost_share *share_ptr[5]; int i, j; + const unsigned char *pok_ptr[5]; for (i = 0; i < 5; i++) { secp256k1_testrand256(seed[i]); vss_ptr[i] = vss_commitment[i]; + pok_ptr[i] = pok[i]; } for (i = 0; i < 5; i++) { CHECK(secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 5, ids33) == 1); @@ -780,7 +798,7 @@ void frost_dkg_test_helper(secp256k1_frost_share *agg_share, secp256k1_xonly_pub for (j = 0; j < 5; j++) { share_ptr[j] = &shares[j][i]; } - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_pk, share_ptr, vss_ptr, 5, 3, ids33[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, ids33[i]) == 1); } } From cac1662e995fd2e4709fa8fbaf91e73a108949c8 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 16 Aug 2024 11:37:52 -0700 Subject: [PATCH 12/23] Output cache from keygen instead of x-only key --- examples/frost.c | 35 ++-- include/secp256k1_frost.h | 124 +++++------ src/ctime_tests.c | 19 +- src/modules/frost/frost.md | 48 +++-- src/modules/frost/keygen.h | 9 +- src/modules/frost/keygen_impl.h | 181 ++++++++-------- src/modules/frost/session_impl.h | 150 +++++-------- src/modules/frost/tests_impl.h | 347 ++++++++++++++++--------------- 8 files changed, 457 insertions(+), 456 deletions(-) diff --git a/examples/frost.c b/examples/frost.c index 8a7e3b3bb..15a4b67ec 100644 --- a/examples/frost.c +++ b/examples/frost.c @@ -37,7 +37,6 @@ struct signer { secp256k1_frost_session session; secp256k1_frost_partial_sig partial_sig; secp256k1_pubkey vss_commitment[THRESHOLD]; - unsigned char vss_hash[32]; unsigned char pok[64]; unsigned char id[33]; }; @@ -70,7 +69,7 @@ int create_keypair_and_seed(const secp256k1_context* ctx, struct signer_secrets } /* Create shares and coefficient commitments */ -int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, secp256k1_xonly_pubkey *pk) { +int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) { int i, j; secp256k1_frost_share shares[N_SIGNERS][N_SIGNERS]; const secp256k1_pubkey *vss_commitments[N_SIGNERS]; @@ -101,7 +100,7 @@ int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_se assigned_shares[j] = &shares[j][i]; } /* Each participant aggregates the shares they received. */ - if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, pk, assigned_shares, vss_commitments, poks, N_SIGNERS, THRESHOLD, signer[i].id)) { + if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, assigned_shares, vss_commitments, poks, N_SIGNERS, THRESHOLD, signer[i].id)) { return 0; } for (j = 0; j < N_SIGNERS; j++) { @@ -125,15 +124,11 @@ int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_se /* Tweak the pubkey corresponding to the provided tweak cache, update the cache * and return the tweaked aggregate pk. */ -int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_frost_tweak_cache *cache) { +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.."; - if (!secp256k1_frost_pubkey_tweak(ctx, cache, pk)) { - return 0; - } - /* 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)) { @@ -164,7 +159,7 @@ int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_fr /* Sign a message hash with the given threshold and aggregate shares and store * the result in sig */ -int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char* msg32, secp256k1_xonly_pubkey *pk, unsigned char *sig64, const secp256k1_frost_tweak_cache *cache) { +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]; @@ -183,7 +178,7 @@ int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, st } /* 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].agg_share, msg32, pk, NULL)) { + if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_id, &signer_secrets[i].agg_share, msg32, cache, NULL)) { return 0; } is_signer[i] = 0; /* Initialize is_signer */ @@ -212,7 +207,7 @@ int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, st /* 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, pk, signer[signer_id].id, ids, cache, NULL)) { + 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 @@ -251,10 +246,12 @@ int main(void) { 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_tweak_cache cache; + secp256k1_frost_keygen_cache keygen_cache; 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); @@ -264,23 +261,31 @@ int main(void) { 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, &pk)) { + 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, &cache)) { + if (!tweak(ctx, &pk, &keygen_cache)) { printf("FAILED\n"); return 1; } printf("ok\n"); printf("Signing message........."); - if (!sign(ctx, signer_secrets, signers, msg, &pk, sig, &cache)) { + if (!sign(ctx, signer_secrets, signers, msg, sig, &keygen_cache)) { printf("FAILED\n"); return 1; } diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 459cea029..e3b517bc3 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -43,7 +43,7 @@ extern "C" { */ typedef struct { unsigned char data[101]; -} secp256k1_frost_tweak_cache; +} secp256k1_frost_keygen_cache; /** Opaque data structure that holds a signer's _secret_ share. * @@ -219,7 +219,7 @@ SECP256K1_API int secp256k1_frost_shares_gen( const unsigned char *seed32, size_t threshold, size_t n_participants, - const unsigned char * const* ids33 + const unsigned char * const *ids33 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8); /** Aggregates shares @@ -239,10 +239,11 @@ SECP256K1_API int secp256k1_frost_shares_gen( * the resulting signature verifies). * Args: ctx: pointer to a context object * Out: agg_share: the aggregated share - * agg_pk: the aggregated x-only public key * In: shares: all key generation shares for the partcipant's index * vss_commitments: coefficient commitments of all participants ordered by - * the x-only pubkeys of the participants + * the IDs of the participants + * pok64s: proofs of knowledge for the shares ordered by the IDs of + * the participants * n_shares: the total number of shares * threshold: the minimum number of shares required to produce a * signature @@ -252,14 +253,13 @@ SECP256K1_API int secp256k1_frost_shares_gen( SECP256K1_API int secp256k1_frost_share_agg( const secp256k1_context *ctx, secp256k1_frost_share *agg_share, - secp256k1_xonly_pubkey *agg_pk, const secp256k1_frost_share * const *shares, const secp256k1_pubkey * const *vss_commitments, const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33 -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(9); +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8); /** Verifies a share received during a key generation session * @@ -306,56 +306,62 @@ SECP256K1_API int secp256k1_frost_compute_pubshare( size_t n_participants ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); -/** Obtain the aggregate public key from a FROST x-only aggregate public key. - * - * This is only useful if you need the non-xonly public key, in particular for - * ordinary (non-xonly) tweaking or batch-verifying multiple key aggregations - * (not implemented). +/** 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: ec_agg_pk: the FROST-aggregated public key. - * In: xonly_agg_pk: the aggregated x-only public key that is the output of - * `secp256k1_frost_share_agg` + * 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_get( +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_gen( const secp256k1_context *ctx, - secp256k1_pubkey *ec_agg_pk, - const secp256k1_xonly_pubkey *xonly_agg_pk -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + 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); -/** Initializes a tweak cache used for applying tweaks to a FROST key +/** 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: tweak_cache: pointer to a frost_tweak_cache struct that is required - * for key tweaking - * In: agg_pk: the aggregated x-only public key that is the output of - * `secp256k1_frost_share_agg` + * 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_tweak( +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get( const secp256k1_context *ctx, - secp256k1_frost_tweak_cache *tweak_cache, - const secp256k1_xonly_pubkey *agg_pk + 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 tweak_cache by +/** 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 an aggregate public key via BIP32. + * 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_share_agg(..., xonly_agg_pk, ...) - * secp256k1_frost_pubkey_tweak(..., tweak_cache, xonly_agg_pk) - * secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, tweak_cache, tweak32) + * 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_agg_pk, xonly_agg_pk) - * secp256k1_ec_pubkey_tweak_add(..., ec_agg_pk, tweak32) - * secp256k1_ec_pubkey_serialize(..., buf2, ec_agg_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 aggregate key. + * 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`. @@ -367,7 +373,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_tweak( * 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: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by + * 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 @@ -378,11 +384,11 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_tweak( SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_add( const secp256k1_context *ctx, secp256k1_pubkey *output_pubkey, - secp256k1_frost_tweak_cache *tweak_cache, + 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 tweak_cache by adding the +/** 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. * @@ -391,12 +397,12 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_a * failures) returns 1. * * secp256k1_frost_share_agg(..., agg_pk, ...) - * secp256k1_frost_pubkey_tweak(..., tweak_cache, agg_pk) - * secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, tweak_cache, tweak32) + * secp256k1_frost_pubkey_tweak(..., keygen_cache, agg_pk) + * secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, keygen_cache, tweak32) * secp256k1_xonly_pubkey_serialize(..., buf, output_pk) * secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32) * - * This function is required if you want to _sign_ for a tweaked aggregate key. + * 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`. @@ -408,7 +414,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_a * 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: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by + * 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 @@ -419,7 +425,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_a SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_tweak_add( const secp256k1_context *ctx, secp256k1_pubkey *output_pubkey, - secp256k1_frost_tweak_cache *tweak_cache, + secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); @@ -438,7 +444,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_twea * AND KEPT SECRET (even from other signers). If you do provide a seckey, * session_id32 can instead be a counter (that must never repeat!). However, * it is recommended to always choose session_id32 uniformly at random. - * 2. If you already know the seckey, message or aggregate public key, they + * 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 @@ -461,7 +467,9 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_twea * signing, if already known (can be NULL) * msg32: the 32-byte message that will later be signed, if * already known (can be NULL) - * agg_pk: the FROST-aggregated public key (can be NULL) + * 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) */ @@ -472,7 +480,7 @@ SECP256K1_API int secp256k1_frost_nonce_gen( const unsigned char *session_id32, const secp256k1_frost_share *agg_share, const unsigned char *msg32, - const secp256k1_xonly_pubkey *agg_pk, + 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); @@ -490,11 +498,10 @@ SECP256K1_API int secp256k1_frost_nonce_gen( * n_pubnonces: number of elements in the pubnonces array. Must be * greater than 0. * msg32: the 32-byte message to sign - * agg_pk: the FROST-aggregated public key * 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 - * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + * 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) @@ -505,10 +512,9 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process( const secp256k1_frost_pubnonce * const *pubnonces, size_t n_pubnonces, const unsigned char *msg32, - const secp256k1_xonly_pubkey *agg_pk, const unsigned char *my_id33, - const unsigned char * const* ids33, - const secp256k1_frost_tweak_cache *tweak_cache, + 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); @@ -529,7 +535,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process( * In: agg_share: the aggregated share * session: pointer to the session that was created with * frost_nonce_process - * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + * keygen_cache: pointer to frost_keygen_cache struct */ SECP256K1_API int secp256k1_frost_partial_sign( const secp256k1_context *ctx, @@ -537,15 +543,15 @@ SECP256K1_API int secp256k1_frost_partial_sign( secp256k1_frost_secnonce *secnonce, const secp256k1_frost_share *agg_share, const secp256k1_frost_session *session, - const secp256k1_frost_tweak_cache *tweak_cache -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + 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 `tweak_cache` argument is identical to the one used to create the + * 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. @@ -565,7 +571,7 @@ SECP256K1_API int secp256k1_frost_partial_sign( * `secp256k1_frost_compute_pubshare` * session: pointer to the session that was created with * `frost_nonce_process` - * tweak_cache: pointer to frost_tweak_cache struct (can be NULL) + * keygen_cache: pointer to frost_keygen_cache struct */ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_verify( const secp256k1_context *ctx, @@ -573,8 +579,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_verif const secp256k1_frost_pubnonce *pubnonce, const secp256k1_pubkey *pubshare, const secp256k1_frost_session *session, - const secp256k1_frost_tweak_cache *tweak_cache -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + 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 * diff --git a/src/ctime_tests.c b/src/ctime_tests.c index 44071b4d3..ce70cf1f2 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -357,13 +357,12 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { #ifdef ENABLE_MODULE_FROST { secp256k1_pubkey pk[2]; - secp256k1_xonly_pubkey agg_pk; unsigned char session_id[32]; unsigned char seed[2][32]; secp256k1_frost_secnonce secnonce[2]; secp256k1_frost_pubnonce pubnonce[2]; const secp256k1_frost_pubnonce *pubnonce_ptr[2]; - secp256k1_frost_tweak_cache cache; + secp256k1_frost_keygen_cache cache; secp256k1_frost_session session; secp256k1_frost_partial_sig partial_sig; const secp256k1_frost_partial_sig *partial_sig_ptr[1]; @@ -384,6 +383,8 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { const unsigned char *id_ptr[2]; size_t size = 33; const unsigned char *pok_ptr[2]; + secp256k1_pubkey pubshare[2]; + const secp256k1_pubkey *pubshares_ptr[2]; id_ptr[0] = id[0]; id_ptr[1] = id[1]; @@ -409,6 +410,8 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { vss_ptr[1] = vss_commitment[1]; pok_ptr[0] = pok[0]; pok_ptr[1] = pok[1]; + pubshares_ptr[0] = &pubshare[0]; + pubshares_ptr[1] = &pubshare[1]; CHECK(secp256k1_keypair_create(ctx, &keypair, key)); CHECK(secp256k1_keypair_create(ctx, &keypair2, key2)); @@ -435,23 +438,25 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1][1], sizeof(secp256k1_pubkey)); SECP256K1_CHECKMEM_DEFINE(pok[0], 64); SECP256K1_CHECKMEM_DEFINE(pok[1], 64); - ret = secp256k1_frost_share_agg(ctx, &agg_share, &agg_pk, share_ptr, vss_ptr, pok_ptr, 2, 2, id_ptr[0]); + ret = secp256k1_frost_share_agg(ctx, &agg_share, share_ptr, vss_ptr, pok_ptr, 2, 2, id_ptr[0]); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); + CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[0], 2, id_ptr[0], vss_ptr, 2)); + CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[1], 2, id_ptr[1], vss_ptr, 2)); + 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)); - CHECK(secp256k1_frost_pubkey_tweak(ctx, &cache, &agg_pk) == 1); - ret = secp256k1_frost_nonce_gen(ctx, &secnonce[0], &pubnonce[0], session_id, &agg_share, msg, &agg_pk, extra_input); + ret = secp256k1_frost_nonce_gen(ctx, &secnonce[0], &pubnonce[0], session_id, &agg_share, 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, &agg_share, msg, &agg_pk, extra_input); + ret = secp256k1_frost_nonce_gen(ctx, &secnonce[1], &pubnonce[1], session_id, &agg_share, msg, &cache, extra_input); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); /* partial_sign */ - CHECK(secp256k1_frost_nonce_process(ctx, &session, pubnonce_ptr, 2, msg, &agg_pk, id_ptr[0], id_ptr, &cache, &adaptor) == 1); + CHECK(secp256k1_frost_nonce_process(ctx, &session, pubnonce_ptr, 2, msg, id_ptr[0], id_ptr, &cache, &adaptor) == 1); ret = secp256k1_keypair_create(ctx, &keypair, key); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); diff --git a/src/modules/frost/frost.md b/src/modules/frost/frost.md index c9bfa1347..3512410bc 100644 --- a/src/modules/frost/frost.md +++ b/src/modules/frost/frost.md @@ -7,6 +7,9 @@ The following sections contain additional notes on the API of the frost module # 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 @@ -32,27 +35,31 @@ Users of the frost module must take great care to make sure of the following: # Key Generation -1. Generate a keypair with `secp256k1_keypair_create` and obtain the x-only - public key with `secp256k1_keypair_xonly_pub`, and distribute it to each - other participant to be used as an authentication key and identifier. +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. Generate a VSS commitment, proof-of-knowledge, and shares with `secp256k1_frost_shares_gen`. The VSS commitment and proof-of-knowledge 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 commitment set from each participant, call - `secp256k1_frost_share_agg` to compute the aggregate share, group public - key, and VSS hash. If this function returns an error, - `secp256k1_frost_share_verify` is called on each share to determine which - participants submitted faulty shares. -4. Optionally compute the public verification share by calling - `secp256k1_frost_compute_pubshare` with the x-only public key of each - participant. This share is required by `secp256k1_frost_partial_sig_verify` - to verify partial signatures generated by `secp256k1_frost_partial_sign`. + `secp256k1_frost_share_agg` to compute the aggregate share. If this function + returns an error, `secp256k1_frost_share_verify` is called on each share to + determine which participants submitted faulty shares. +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`. @@ -65,16 +72,17 @@ Tweaks can also be chained together by tweaking an already tweaked key. # Signing -1. Optionally add a tweak by calling `secp256k1_frost_pubkey_tweak` and then +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. -2. Generate a pair of secret and public nonce with `secp256k1_frost_nonce_gen` +3. Generate a pair of secret and public nonce with `secp256k1_frost_nonce_gen` and send the public nonce to the other signers. -3. Process the aggregate nonce with `secp256k1_frost_nonce_process`. -4. Create a partial signature with `secp256k1_frost_partial_sign`. -5. Verify the partial signatures (optional in some scenarios) with +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`. -6. Someone (not necessarily the signer) obtains all partial signatures and +7. Someone (not necessarily the signer) obtains all partial signatures and aggregates them into the final Schnorr signature using `secp256k1_frost_partial_sig_agg`. @@ -86,10 +94,10 @@ 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 2). +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 2 and 4. +steps 3 and 5. diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h index fb17f3c67..60f494973 100644 --- a/src/modules/frost/keygen.h +++ b/src/modules/frost/keygen.h @@ -15,14 +15,19 @@ 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_tweak_cache_internal; +} secp256k1_keygen_cache_internal; -static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache); +static int secp256k1_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_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/keygen_impl.h b/src/modules/frost/keygen_impl.h index 5a1d194ee..b152bca1e 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -20,7 +20,7 @@ #include "../../hash.h" #include "../../scalar.h" -static const unsigned char secp256k1_frost_tweak_cache_magic[4] = { 0x40, 0x25, 0x2e, 0x41 }; +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 @@ -30,22 +30,22 @@ static const unsigned char secp256k1_frost_tweak_cache_magic[4] = { 0x40, 0x25, * - 32 byte tweak */ /* Requires that cache_i->pk is not infinity. */ -static void secp256k1_tweak_cache_save(secp256k1_frost_tweak_cache *cache, secp256k1_tweak_cache_internal *cache_i) { +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_tweak_cache_magic, 4); + memcpy(ptr, secp256k1_frost_keygen_cache_magic, 4); ptr += 4; - secp256k1_point_save_ext(ptr, &cache_i->pk); + 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_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache) { +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_tweak_cache_magic, 4) == 0); + ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_keygen_cache_magic, 4) == 0); ptr += 4; - secp256k1_point_load_ext(&cache_i->pk, ptr); + secp256k1_ge_from_bytes(&cache_i->pk, ptr); ptr += 64; cache_i->parity_acc = *ptr & 1; ptr += 1; @@ -242,6 +242,13 @@ typedef struct { size_t threshold; } secp256k1_frost_pubkey_combine_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_verify_share_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { secp256k1_frost_verify_share_ecmult_data *ctx = (secp256k1_frost_verify_share_ecmult_data *) data; if (!secp256k1_pubkey_load(ctx->ctx, pt, *(ctx->vss_commitment)+idx)) { @@ -268,12 +275,21 @@ static int secp256k1_frost_compute_pubshare_ecmult_callback(secp256k1_scalar *sc return 1; } -static int secp256k1_frost_pubkey_combine_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { - secp256k1_frost_pubkey_combine_ecmult_data *ctx = (secp256k1_frost_pubkey_combine_ecmult_data *) data; +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; + } - secp256k1_scalar_set_int(sc, 1); - /* the public key is the first index of each set of coefficients */ - return secp256k1_pubkey_load(ctx->ctx, pt, &ctx->pks[idx][0]); + *sc = l; + + return 1; } /* See draft-irtf-cfrg-frost-08#appendix-C.2 */ @@ -326,9 +342,8 @@ int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pubkey *pubshare, size_t threshold, const unsigned char *id33, const secp256k1_pubkey * const* vss_commitments, size_t n_participants) { secp256k1_gej pkj; - secp256k1_ge pkp, tmp; + secp256k1_ge tmp; secp256k1_frost_compute_pubshare_ecmult_data compute_pubshare_ecmult_data; - secp256k1_frost_pubkey_combine_ecmult_data pubkey_combine_ecmult_data; VERIFY_CHECK(ctx != NULL); ARG_CHECK(pubshare != NULL); @@ -364,32 +379,12 @@ int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pub return 0; } secp256k1_ge_set_gej(&tmp, &pkj); - - /* Combine pubkeys */ - pubkey_combine_ecmult_data.ctx = ctx; - pubkey_combine_ecmult_data.pks = vss_commitments; - pubkey_combine_ecmult_data.threshold = threshold; - - /* TODO: add scratch */ - if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_pubkey_combine_callback, (void *) &pubkey_combine_ecmult_data, n_participants)) { - return 0; - } - secp256k1_ge_set_gej(&pkp, &pkj); - secp256k1_fe_normalize_var(&pkp.y); - if (secp256k1_fe_is_odd(&pkp.y)) { - secp256k1_ge_neg(&tmp, &tmp); - } - secp256k1_pubkey_save(pubshare, &tmp); return 1; } -int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, secp256k1_xonly_pubkey *agg_pk, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33) { - secp256k1_frost_pubkey_combine_ecmult_data pubkey_combine_ecmult_data; - secp256k1_gej pkj; - secp256k1_ge pkp; - int pk_parity; +int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33) { secp256k1_scalar acc; size_t i; int ret = 1; @@ -399,8 +394,6 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar VERIFY_CHECK(ctx != NULL); ARG_CHECK(agg_share != NULL); memset(agg_share, 0, sizeof(*agg_share)); - ARG_CHECK(agg_pk != NULL); - memset(agg_pk, 0, sizeof(*agg_pk)); ARG_CHECK(shares != NULL); ARG_CHECK(pok64s != NULL); ARG_CHECK(vss_commitments != NULL); @@ -437,67 +430,54 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar ret &= secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, &vss_commitments[i]); secp256k1_scalar_add(&acc, &acc, &share_i); } - - /* Combine pubkeys */ - pubkey_combine_ecmult_data.ctx = ctx; - pubkey_combine_ecmult_data.pks = vss_commitments; - pubkey_combine_ecmult_data.threshold = threshold; - - /* TODO: add scratch */ - if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_pubkey_combine_callback, (void *) &pubkey_combine_ecmult_data, n_shares)) { - return 0; - } - - secp256k1_ge_set_gej(&pkp, &pkj); - secp256k1_fe_normalize_var(&pkp.y); - pk_parity = secp256k1_extrakeys_ge_even_y(&pkp); - secp256k1_xonly_pubkey_save(agg_pk, &pkp); - - /* Invert the aggregate share if the combined pubkey has an odd Y coordinate. */ - if (pk_parity == 1) { - secp256k1_scalar_negate(&acc, &acc); - } secp256k1_frost_share_save(agg_share, &acc); return ret; } -int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *ec_pk, const secp256k1_xonly_pubkey *xonly_pk) { - secp256k1_ge pk; - +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(ec_pk != NULL); - memset(ec_pk, 0, sizeof(*ec_pk)); - ARG_CHECK(xonly_pk != NULL); + ARG_CHECK(agg_pk != NULL); + memset(agg_pk, 0, sizeof(*agg_pk)); + ARG_CHECK(keyagg_cache != NULL); - /* The output of keygen is an aggregated public key that *always* has an - * even Y coordinate. */ - if (!secp256k1_xonly_pubkey_load(ctx, &pk, xonly_pk)) { + if(!secp256k1_keygen_cache_load(ctx, &cache_i, keyagg_cache)) { return 0; } - secp256k1_pubkey_save(ec_pk, &pk); + secp256k1_pubkey_save(agg_pk, &cache_i.pk); return 1; } -int secp256k1_frost_pubkey_tweak(const secp256k1_context* ctx, secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_xonly_pubkey *pk) { - secp256k1_tweak_cache_internal cache_i = { 0 }; +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(tweak_cache != NULL); - ARG_CHECK(pk != 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; - /* The output of keygen is an aggregated public key that *always* has an - * even Y coordinate. */ - if (!secp256k1_xonly_pubkey_load(ctx, &cache_i.pk, pk)) { + /* 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_tweak_cache_save(tweak_cache, &cache_i); + 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_tweak_cache *tweak_cache, const unsigned char *tweak32, int xonly) { - secp256k1_tweak_cache_internal cache_i; +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; @@ -505,10 +485,10 @@ static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ct if (output_pubkey != NULL) { memset(output_pubkey, 0, sizeof(*output_pubkey)); } - ARG_CHECK(tweak_cache != NULL); + ARG_CHECK(keygen_cache != NULL); ARG_CHECK(tweak32 != NULL); - if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { + if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) { return 0; } secp256k1_scalar_set_b32(&tweak, tweak32, &overflow); @@ -525,19 +505,52 @@ static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ct } /* eckey_pubkey_tweak_add fails if cache_i.pk is infinity */ VERIFY_CHECK(!secp256k1_ge_is_infinity(&cache_i.pk)); - secp256k1_tweak_cache_save(tweak_cache, &cache_i); + 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_tweak_cache *tweak_cache, const unsigned char *tweak32) { - return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 0); +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_tweak_cache *tweak_cache, const unsigned char *tweak32) { - return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 1); +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 diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index bba1f7095..7128c644e 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -243,7 +243,8 @@ 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_xonly_pubkey *pk, 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_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; @@ -272,8 +273,8 @@ int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secn memset(&acc, 0, sizeof(acc)); } + /* Check that the share is valid to be able to sign for it later. */ if (share != NULL) { - /* Check that the share is valid to be able to sign for it later. */ secp256k1_scalar sk; ret &= secp256k1_frost_share_load(ctx, &sk, share); @@ -288,12 +289,15 @@ int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secn #endif } - if (pk != NULL) { - if (!secp256k1_xonly_pubkey_serialize(ctx, pk_ser, pk)) { + 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])); @@ -373,62 +377,27 @@ static int secp256k1_frost_nonce_process_internal(const secp256k1_context* ctx, } /* 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)) { - /* unreachable with overwhelming probability */ - return 0; + 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; } -static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsigned char * const *ids33, size_t n_participants, const unsigned char *my_id33) { - size_t i; - secp256k1_scalar num; - secp256k1_scalar den; - secp256k1_scalar party_idx; - - secp256k1_scalar_set_int(&num, 1); - secp256k1_scalar_set_int(&den, 1); - if (!secp256k1_frost_compute_indexhash(&party_idx, my_id33)) { - return 0; - } - for (i = 0; i < n_participants; i++) { - secp256k1_scalar mul; - - if (!secp256k1_frost_compute_indexhash(&mul, ids33[i])) { - return 0; - } - if (secp256k1_scalar_eq(&mul, &party_idx)) { - continue; - } - - secp256k1_scalar_negate(&mul, &mul); - secp256k1_scalar_mul(&num, &num, &mul); - secp256k1_scalar_add(&mul, &mul, &party_idx); - secp256k1_scalar_mul(&den, &den, &mul); - } - - secp256k1_scalar_inverse_var(&den, &den); - secp256k1_scalar_mul(r, &num, &den); - - return 1; -} - -int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_session *session, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, const unsigned char *my_id33, const unsigned char * const *ids33, const secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_pubkey *adaptor) { - secp256k1_ge aggnonce_pt[2]; +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]; - size_t i; secp256k1_scalar l; VERIFY_CHECK(ctx != NULL); @@ -437,37 +406,17 @@ int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_ ARG_CHECK(pubnonces != NULL); ARG_CHECK(ids33 != NULL); ARG_CHECK(my_id33 != NULL); - ARG_CHECK(pk != NULL); + ARG_CHECK(keygen_cache != NULL); ARG_CHECK(n_pubnonces > 1); - if (!secp256k1_xonly_pubkey_serialize(ctx, pk32, pk)) { + 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; } - for (i = 0; i < 2; i++) { - if (secp256k1_gej_is_infinity(&aggnonce_ptj[i])) { - /* There must be at least one dishonest signer. If we would return 0 - here, we will never be able to determine who it is. Therefore, we - should continue such that the culprit is revealed when collecting - and verifying partial signatures. - However, dealing with the point at infinity (loading, - de-/serializing) would require a lot of extra code complexity. - Instead, we set the aggregate nonce to some arbitrary point (the - generator). This is secure, because it only restricts the - abilities of the attacker: an attacker that forces the sum of - nonces to be infinity by sending some maliciously generated nonce - pairs can be turned into an attacker that forces the sum to be - the generator (by simply adding the generator to one of the - malicious nonces), and this does not change the winning condition - of the EUF-CMA game. */ - aggnonce_pt[i] = secp256k1_ge_const_g; - } else { - secp256k1_ge_set_gej(&aggnonce_pt[i], &aggnonce_ptj[i]); - } - } /* Add public adaptor to nonce */ if (adaptor != NULL) { secp256k1_ge adaptorp; @@ -484,19 +433,13 @@ int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_ /* If there is a tweak then set `challenge` times `tweak` to the `s`-part.*/ secp256k1_scalar_set_int(&session_i.s_part, 0); - if (tweak_cache != NULL) { - secp256k1_tweak_cache_internal cache_i; - if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { - return 0; - } - if (!secp256k1_scalar_is_zero(&cache_i.tweak)) { - secp256k1_scalar e_tmp; - secp256k1_scalar_mul(&e_tmp, &session_i.challenge, &cache_i.tweak); - if (secp256k1_fe_is_odd(&cache_i.pk.y)) { - secp256k1_scalar_negate(&e_tmp, &e_tmp); - } - secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &e_tmp); + 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. */ @@ -515,10 +458,11 @@ 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_tweak_cache *tweak_cache) { +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; @@ -537,26 +481,29 @@ int secp256k1_frost_partial_sign(const secp256k1_context* ctx, secp256k1_frost_p 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_frost_session_load(ctx, &session_i, session)) { + if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) { secp256k1_frost_partial_sign_clear(&sk, k); return 0; } - if (tweak_cache != NULL) { - secp256k1_tweak_cache_internal cache_i; - if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { - secp256k1_frost_partial_sign_clear(&sk, k); - return 0; - } - if (secp256k1_fe_is_odd(&cache_i.pk.y) != cache_i.parity_acc) { - secp256k1_scalar_negate(&sk, &sk); - } + /* 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) { @@ -574,7 +521,8 @@ int secp256k1_frost_partial_sign(const secp256k1_context* ctx, secp256k1_frost_p return 1; } -int secp256k1_frost_partial_sig_verify(const secp256k1_context* ctx, const secp256k1_frost_partial_sig *partial_sig, const secp256k1_frost_pubnonce *pubnonce, const secp256k1_pubkey *pubshare, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) { +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; @@ -587,6 +535,7 @@ int secp256k1_frost_partial_sig_verify(const secp256k1_context* ctx, const secp2 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)) { @@ -605,23 +554,24 @@ int secp256k1_frost_partial_sig_verify(const secp256k1_context* ctx, const secp2 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); - if (tweak_cache != NULL) { - secp256k1_tweak_cache_internal cache_i; - if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) { - return 0; - } - if (secp256k1_fe_is_odd(&cache_i.pk.y) - != cache_i.parity_acc) { - secp256k1_scalar_negate(&e, &e); - } + /* 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); diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h index 27719d3c3..ab33617aa 100644 --- a/src/modules/frost/tests_impl.h +++ b/src/modules/frost/tests_impl.h @@ -44,7 +44,8 @@ void frost_simple_test(void) { secp256k1_pubkey vss_commitment[5][3]; const secp256k1_pubkey *vss_ptr[5]; unsigned char pok[5][64]; - secp256k1_xonly_pubkey agg_pk; + secp256k1_xonly_pubkey pk_xonly; + secp256k1_pubkey pk; unsigned char buf[5][32]; secp256k1_frost_share shares[5][5]; const secp256k1_frost_share *share_ptr[5]; @@ -59,6 +60,8 @@ void frost_simple_test(void) { unsigned char id[5][33]; const unsigned char *id_ptr[5]; const unsigned char *pok_ptr[5]; + const secp256k1_pubkey *pubshare_ptr[5]; + secp256k1_frost_keygen_cache cache; for (i = 0; i < 5; i++) { secp256k1_testrand256(buf[i]); @@ -68,6 +71,7 @@ void frost_simple_test(void) { partial_sig_ptr[i] = &partial_sig[i]; id_ptr[i] = id[i]; pok_ptr[i] = pok[i]; + pubshare_ptr[i] = &pubshare[i]; CHECK(frost_create_pk(id[i], sk[i])); } @@ -78,10 +82,11 @@ void frost_simple_test(void) { for (j = 0; j < 5; j++) { share_ptr[j] = &shares[j][i]; CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[j], 3, id_ptr[j], vss_ptr, 5) == 1); } - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], vss_ptr, 5) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); } + CHECK(secp256k1_frost_pubkey_gen(CTX, &cache, pubshare_ptr, 5, id_ptr) == 1); secp256k1_testrand256(msg); for (i = 0; i < 3; i++) { @@ -90,12 +95,14 @@ void frost_simple_test(void) { CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], buf[i], &agg_share[i], NULL, NULL, NULL) == 1); } for (i = 0; i < 3; i++) { - CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, &agg_pk, id_ptr[i], id_ptr, NULL, NULL) == 1); - CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &agg_share[i], &session, NULL) == 1); - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], &pubshare[i], &session, NULL) == 1); + 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], &agg_share[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_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &agg_pk) == 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) { @@ -152,10 +159,10 @@ void frost_api_tests(void) { secp256k1_frost_pubnonce invalid_pubnonce; const secp256k1_frost_pubnonce *invalid_pubnonce_ptr[5]; unsigned char msg[32]; - secp256k1_xonly_pubkey agg_pk; - secp256k1_pubkey full_agg_pk; - secp256k1_frost_tweak_cache tweak_cache; - secp256k1_frost_tweak_cache invalid_tweak_cache; + 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; @@ -181,6 +188,7 @@ void frost_api_tests(void) { const unsigned char *id_ptr[5]; const unsigned char *pok_ptr[5]; const unsigned char *invalid_pok_ptr[5]; + const secp256k1_pubkey *pubshare_ptr[5]; /** setup **/ memset(max64, 0xff, sizeof(max64)); @@ -193,7 +201,7 @@ void frost_api_tests(void) { memset(&invalid_partial_sig, 0, sizeof(invalid_partial_sig)); memset(&invalid_pubnonce, 0, sizeof(invalid_pubnonce)); memset(&invalid_vss_pk, 0, sizeof(invalid_vss_pk)); - memset(&invalid_tweak_cache, 0, sizeof(invalid_tweak_cache)); + memset(&invalid_keygen_cache, 0, sizeof(invalid_keygen_cache)); memset(&invalid_session, 0, sizeof(invalid_session)); frost_pubnonce_summing_to_inf(inf_pubnonce); @@ -210,6 +218,7 @@ void frost_api_tests(void) { invalid_partial_sig_ptr[i] = &partial_sig[i]; id_ptr[i] = id[i]; pok_ptr[i] = pok[i]; + pubshare_ptr[i] = &pubshare[i]; invalid_pok_ptr[i] = pok[i]; secp256k1_testrand256(session_id[i]); secp256k1_testrand256(seed[i]); @@ -264,48 +273,34 @@ void frost_api_tests(void) { invalid_share_ptr[j] = &shares[j][i]; } invalid_share_ptr[0] = &invalid_share; - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, invalid_pok_ptr, 5, 3, id_ptr[i]) == 0); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, invalid_pok_ptr, 5, 3, id_ptr[i]) == 0); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, NULL, &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, NULL, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, NULL, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, invalid_vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, invalid_vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, NULL, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, NULL, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, NULL)); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], invalid_share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, invalid_share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, NULL, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 0, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, NULL, pok_ptr, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, NULL, pok_ptr, 5, 0, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, NULL, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, NULL, 5, 0, id_ptr[i])); - CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(frost_memcmp_and_randomize(agg_pk.data, zeros68, sizeof(agg_pk.data)) == 0); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); } /* Share verification */ @@ -342,75 +337,75 @@ void frost_api_tests(void) { CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], vss_ptr, 5) == 1); CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[1], 3, id_ptr[1], vss_ptr, 5) == 1); CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[2], 3, id_ptr[2], vss_ptr, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[3], 3, id_ptr[3], vss_ptr, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[4], 3, id_ptr[4], vss_ptr, 5) == 1); - /* pubkey_get */ - CHECK(secp256k1_frost_pubkey_get(CTX, &full_agg_pk, &agg_pk) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, NULL, &agg_pk)); - CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, &full_agg_pk, NULL)); - CHECK(secp256k1_memcmp_var(&full_agg_pk, zeros68, sizeof(full_agg_pk)) == 0); - - /** Tweaking **/ - - /* pubkey_tweak */ - CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &agg_pk) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, NULL, &agg_pk)); - CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, NULL)); - CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &invalid_pk)); + /* 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)); - CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &agg_pk) == 1); + /* 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_tweak_cache *tweak_cache, const unsigned char *tweak32); + 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; - CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &agg_pk) == 1); + for (i = 0; i < 2; i++) { secp256k1_pubkey tmp_output_pk; - secp256k1_frost_tweak_cache tmp_tweak_cache = tweak_cache; - CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1); - /* Reset tweak_cache */ - tmp_tweak_cache = tweak_cache; - CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1); - tmp_tweak_cache = tweak_cache; - CHECK((*tweak_func[i])(CTX, NULL, &tmp_tweak_cache, tweak) == 1); - tmp_tweak_cache = tweak_cache; + 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_tweak_cache = tweak_cache; - CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, NULL)); + 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_tweak_cache = tweak_cache; - CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, max64) == 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_tweak_cache = tweak_cache; - /* Uninitialized tweak_cache */ - CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &invalid_tweak_cache, tweak)); + 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_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, &agg_pk, max64) == 1); - CHECK_ILLEGAL(STATIC_CTX, secp256k1_frost_nonce_gen(STATIC_CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, &agg_pk, max64)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, NULL, &pubnonce[0], session_id[0], &agg_share[0], msg, &agg_pk, max64)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], NULL, session_id[0], &agg_share[0], msg, &agg_pk, max64)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, &agg_share[0], msg, &agg_pk, max64)); + 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], &agg_share[0], msg, &keygen_cache, max64) == 1); + CHECK_ILLEGAL(STATIC_CTX, secp256k1_frost_nonce_gen(STATIC_CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, &keygen_cache, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, NULL, &pubnonce[0], session_id[0], &agg_share[0], msg, &keygen_cache, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], NULL, session_id[0], &agg_share[0], msg, &keygen_cache, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, &agg_share[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, &agg_pk, max64) == 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, &agg_share[0], msg, &agg_pk, max64) == 1); - CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, msg, &agg_pk, max64) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], zeros68, &agg_share[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 agg_share */ - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &invalid_share, msg, &agg_pk, max64)); + 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], &agg_share[0], NULL, &agg_pk, max64) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], NULL, &keygen_cache, max64) == 1); CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, NULL, max64) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[0], msg, &invalid_pk, max64)); + CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[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], &agg_share[0], msg, &agg_pk, NULL) == 1); + CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &agg_share[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); @@ -440,50 +435,49 @@ void frost_api_tests(void) { } /** Process nonces **/ - CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, NULL, pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], NULL, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 0, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], invalid_pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, NULL, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, NULL, id_ptr[0], id_ptr, &tweak_cache, &adaptor)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, NULL, id_ptr, &tweak_cache, &adaptor)); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], NULL, &tweak_cache, &adaptor)); - CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, NULL, &adaptor) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &invalid_tweak_cache, &adaptor)); - CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, NULL) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, (secp256k1_pubkey *)&invalid_pk)); - - CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[0], id_ptr, &tweak_cache, &adaptor) == 1); - CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[1], id_ptr, &tweak_cache, &adaptor) == 1); - CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, &agg_pk, id_ptr[2], id_ptr, &tweak_cache, &adaptor) == 1); + 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, &agg_share[0], &session[0], &tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[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, &agg_share[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], &session[0], &keygen_cache)); memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, NULL, &secnonce_tmp, &agg_share[0], &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, NULL, &secnonce_tmp, &agg_share[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, &agg_share[0], &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &invalid_secnonce, &agg_share[0], &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, NULL, &session[0], &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], NULL, &agg_share[0], &session[0], &keygen_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &invalid_secnonce, &agg_share[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], &tweak_cache)); + 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, &agg_share[0], NULL, &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[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, &agg_share[0], &invalid_session, &tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], &invalid_session, &keygen_cache)); memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); - CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[0], &session[0], NULL) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[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, &agg_share[0], &session[0], &invalid_tweak_cache)); + CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &agg_share[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], &agg_share[0], &session[0], &tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], &agg_share[1], &session[1], &tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], &agg_share[2], &session[2], &tweak_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], &agg_share[0], &session[0], &keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], &agg_share[1], &session[1], &keygen_cache) == 1); + CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], &agg_share[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])); @@ -502,22 +496,22 @@ void frost_api_tests(void) { } /** Partial signature verification */ - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[0], &pubshare[0], &session[0], &tweak_cache) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, NULL, &pubnonce[0], &pubshare[0], &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &invalid_partial_sig, &pubnonce[0], &pubshare[0], &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], NULL, &pubshare[0], &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &invalid_pubnonce, &pubshare[0], &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], NULL, &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &invalid_vss_pk, &session[0], &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], NULL, &tweak_cache)); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &invalid_session, &tweak_cache)); - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], NULL) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &invalid_tweak_cache)); - - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], &pubshare[1], &session[1], &tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], &pubshare[2], &session[2], &tweak_cache) == 1); + 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_vss_pk, &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); @@ -547,10 +541,10 @@ void frost_api_tests(void) { /* 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), &agg_pk) == 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), &agg_pk) == 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); @@ -653,8 +647,8 @@ void frost_sha256_tag_test_internal(secp256k1_sha256 *sha_tagged, unsigned char } /* Attempts to create a signature for the aggregate public key using given secret - * keys and tweak_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_tweak_cache *tweak_cache, const unsigned char * const* ids33, const secp256k1_pubkey *sr_pk0, const secp256k1_pubkey *sr_pk1, const secp256k1_pubkey *sr_pk2) { + * 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]; @@ -679,18 +673,18 @@ void frost_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const secp256 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, agg_pk, ids33[0], ids33, tweak_cache, NULL) == 1); - CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, agg_pk, ids33[1], ids33, tweak_cache, NULL) == 1); - CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, agg_pk, ids33[2], ids33, tweak_cache, 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], tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], sr1, &session[1], tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], sr2, &session[2], tweak_cache) == 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], tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], sr_pk1, &session[1], tweak_cache) == 1); - CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], sr_pk2, &session[2], tweak_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); @@ -701,7 +695,7 @@ void frost_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const secp256 void frost_tweak_test(void) { unsigned char sk[5][32]; secp256k1_pubkey pubshare[5]; - secp256k1_frost_tweak_cache tweak_cache; + secp256k1_frost_keygen_cache keygen_cache; enum { N_TWEAKS = 8 }; secp256k1_pubkey P[N_TWEAKS + 1]; secp256k1_xonly_pubkey P_xonly[N_TWEAKS + 1]; @@ -716,6 +710,7 @@ void frost_tweak_test(void) { unsigned char id[5][33]; const unsigned char *id_ptr[5]; const unsigned char *pok_ptr[5]; + const secp256k1_pubkey *pubshare_ptr[5]; /* Key Setup */ for (i = 0; i < 5; i++) { @@ -724,6 +719,7 @@ void frost_tweak_test(void) { vss_ptr[i] = vss_commitment[i]; id_ptr[i] = id[i]; pok_ptr[i] = pok[i]; + pubshare_ptr[i] = &pubshare[i]; CHECK(frost_create_pk(id[i], sk[i])); } @@ -734,14 +730,15 @@ void frost_tweak_test(void) { for (j = 0; j < 5; j++) { share_ptr[j] = &shares[j][i]; CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[j], 3, id_ptr[j], vss_ptr, 5) == 1); } - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], &P_xonly[0], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], vss_ptr, 5) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); } - - frost_tweak_test_helper(&P_xonly[0], &agg_share[0], &agg_share[1], &agg_share[2], NULL, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); - CHECK(secp256k1_frost_pubkey_get(CTX, &P[0], &P_xonly[0])); - CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &P_xonly[0]) == 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], &agg_share[0], &agg_share[1], &agg_share[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 */ @@ -754,9 +751,9 @@ void frost_tweak_test(void) { secp256k1_testrand256(tweak); if (xonly) { - CHECK(secp256k1_frost_pubkey_xonly_tweak_add(CTX, &P[i], &tweak_cache, tweak) == 1); + 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], &tweak_cache, tweak) == 1); + 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 */ @@ -771,12 +768,12 @@ void frost_tweak_test(void) { CHECK(secp256k1_memcmp_var(&tmp_key, &P[i], sizeof(tmp_key)) == 0); } /* Test signing for P[i] */ - frost_tweak_test_helper(&P_xonly[i], &agg_share[0], &agg_share[1], &agg_share[2], &tweak_cache, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); + frost_tweak_test_helper(&P_xonly[i], &agg_share[0], &agg_share[1], &agg_share[2], &keygen_cache, id_ptr, &pubshare[0], &pubshare[1], &pubshare[2]); } } /* Performs a FROST DKG */ -void frost_dkg_test_helper(secp256k1_frost_share *agg_share, secp256k1_xonly_pubkey *agg_pk, const unsigned char * const* ids33) { +void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1_frost_share *agg_share, const unsigned char * const* ids33) { secp256k1_pubkey vss_commitment[5][3]; const secp256k1_pubkey *vss_ptr[5]; unsigned char pok[5][64]; @@ -785,11 +782,14 @@ void frost_dkg_test_helper(secp256k1_frost_share *agg_share, secp256k1_xonly_pub const secp256k1_frost_share *share_ptr[5]; int i, j; const unsigned char *pok_ptr[5]; + secp256k1_pubkey pubshare[5]; + const secp256k1_pubkey *pubshare_ptr[5]; for (i = 0; i < 5; i++) { secp256k1_testrand256(seed[i]); vss_ptr[i] = vss_commitment[i]; pok_ptr[i] = pok[i]; + pubshare_ptr[i] = &pubshare[i]; } for (i = 0; i < 5; i++) { CHECK(secp256k1_frost_shares_gen(CTX, shares[i], vss_commitment[i], pok[i], seed[i], 3, 5, ids33) == 1); @@ -798,12 +798,14 @@ void frost_dkg_test_helper(secp256k1_frost_share *agg_share, secp256k1_xonly_pub for (j = 0; j < 5; j++) { share_ptr[j] = &shares[j][i]; } - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_pk, share_ptr, vss_ptr, pok_ptr, 5, 3, ids33[i]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, ids33[i], vss_ptr, 5) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, ids33[i]) == 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 *agg_share, const secp256k1_xonly_pubkey *agg_pk, const unsigned char * const* ids33, const unsigned char *msg, const secp256k1_pubkey *adaptor) { +int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_share *agg_share, 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]; @@ -826,8 +828,8 @@ int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_share CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id[i], agg_share, NULL, NULL, NULL) == 1); } for (i = 0; i < 3; i++) { - CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, agg_pk, ids33[i], ids33, NULL, adaptor) == 1); - CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &agg_share[i], &session, NULL) == 1); + 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], &agg_share[i], &session, keygen_cache) == 1); } CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1); @@ -847,8 +849,9 @@ void frost_rand_scalar(secp256k1_scalar *scalar) { void frost_multi_hop_lock_tests(void) { secp256k1_frost_share agg_share_a[5]; secp256k1_frost_share agg_share_b[5]; - secp256k1_xonly_pubkey agg_pk_a; - secp256k1_xonly_pubkey agg_pk_b; + 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]; @@ -871,6 +874,8 @@ void frost_multi_hop_lock_tests(void) { 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++) { @@ -879,7 +884,9 @@ void frost_multi_hop_lock_tests(void) { CHECK(frost_create_pk(id_a[i], sk_a[i])); } - frost_dkg_test_helper(agg_share_a, &agg_pk_a, id_ptr_a); + frost_dkg_test_helper(&cache_a, agg_share_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++) { @@ -888,7 +895,9 @@ void frost_multi_hop_lock_tests(void) { CHECK(frost_create_pk(id_b[i], sk_b[i])); } - frost_dkg_test_helper(agg_share_b, &agg_pk_b, id_ptr_b); + frost_dkg_test_helper(&cache_b, agg_share_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 */ @@ -909,21 +918,21 @@ void frost_multi_hop_lock_tests(void) { 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, agg_share_a, &agg_pk_a, id_ptr_a, tx_ab, &l); + nonce_parity_ab = frost_sign_test_helper(asig_ab, agg_share_a, id_ptr_a, tx_ab, &l, &cache_a); /* Bob setup */ - CHECK(secp256k1_frost_verify_adaptor(CTX, asig_ab, tx_ab, &agg_pk_a, &l, nonce_parity_ab) == 1); + 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, agg_share_b, &agg_pk_b, id_ptr_b, tx_bc, &r); + nonce_parity_bc = frost_sign_test_helper(asig_bc, agg_share_b, id_ptr_b, tx_bc, &r, &cache_b); /* Carol decrypt */ - CHECK(secp256k1_frost_verify_adaptor(CTX, asig_bc, tx_bc, &agg_pk_b, &r, nonce_parity_bc) == 1); + 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), &agg_pk_b) == 1); + 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)); @@ -932,7 +941,7 @@ void frost_multi_hop_lock_tests(void) { 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), &agg_pk_a) == 1); + 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)); From 07b7321e5e6d83e753f0580b77d495e366ac1510 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 14:44:44 -0700 Subject: [PATCH 13/23] Fix valgrind tests --- src/ctime_tests.c | 10 +++++----- src/modules/frost/keygen_impl.h | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ctime_tests.c b/src/ctime_tests.c index ce70cf1f2..32b65fa40 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -421,8 +421,6 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { CHECK(secp256k1_ec_pubkey_serialize(ctx, id[1], &size, &pk[1], SECP256K1_EC_COMPRESSED)); /* shares_gen */ - SECP256K1_CHECKMEM_UNDEFINE(key, 32); - SECP256K1_CHECKMEM_UNDEFINE(key2, 32); SECP256K1_CHECKMEM_UNDEFINE(seed[0], 32); SECP256K1_CHECKMEM_UNDEFINE(seed[1], 32); ret = secp256k1_frost_shares_gen(ctx, shares[0], vss_commitment[0], pok[0], seed[0], 2, 2, id_ptr); @@ -431,6 +429,8 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { ret = secp256k1_frost_shares_gen(ctx, shares[1], vss_commitment[1], pok[1], seed[1], 2, 2, id_ptr); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); + SECP256K1_CHECKMEM_UNDEFINE(&shares[0][0], sizeof(shares[0][0])); + SECP256K1_CHECKMEM_UNDEFINE(&shares[1][0], sizeof(shares[1][0])); /* share_agg */ SECP256K1_CHECKMEM_DEFINE(&vss_commitment[0][0], sizeof(secp256k1_pubkey)); SECP256K1_CHECKMEM_DEFINE(&vss_commitment[0][1], sizeof(secp256k1_pubkey)); @@ -441,6 +441,7 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { ret = secp256k1_frost_share_agg(ctx, &agg_share, share_ptr, vss_ptr, pok_ptr, 2, 2, id_ptr[0]); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); + SECP256K1_CHECKMEM_UNDEFINE(&agg_share, sizeof(&agg_share)); CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[0], 2, id_ptr[0], vss_ptr, 2)); CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[1], 2, id_ptr[1], vss_ptr, 2)); CHECK(secp256k1_frost_pubkey_gen(ctx, &cache, pubshares_ptr, 2, id_ptr)); @@ -456,10 +457,9 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { 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_keypair_create(ctx, &keypair, key); - SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); - CHECK(ret == 1); ret = secp256k1_frost_partial_sign(ctx, &partial_sig, &secnonce[0], &agg_share, &session, &cache); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index b152bca1e..41156387e 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -81,6 +81,8 @@ static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_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 */ From 5c9fb08cba3a641c06462d9b6f31d69f7268022b Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 15:03:11 -0700 Subject: [PATCH 14/23] use set_int instead of clear --- src/modules/frost/keygen_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index 41156387e..d49b241ae 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -421,7 +421,7 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar } } - secp256k1_scalar_clear(&acc); + secp256k1_scalar_set_int(&acc, 0); for (i = 0; i < n_shares; i++) { secp256k1_scalar share_i; From 421bc0ac8e461ab444efc3296a4468515eb53e75 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 15:04:31 -0700 Subject: [PATCH 15/23] update comment --- include/secp256k1_frost.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index e3b517bc3..79cae3f00 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -452,7 +452,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_twea * 3. Avoid copying (or serializing) the secnonce. This reduces the possibility * that it is used more than once for signing. * - * Remember that nonce reuse will leak the secret key! + * 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 @@ -523,7 +523,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process( * This function overwrites the given secnonce with zeros and will abort if given a * secnonce that is all zeros. This is a best effort attempt to protect against nonce * reuse. However, this is of course easily defeated if the secnonce has been - * copied (or serialized). Remember that nonce reuse will leak the secret key! + * 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 From 7ee2fc0c9067d2eebab4f9f5e8fdab19af9876e3 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 15:12:46 -0700 Subject: [PATCH 16/23] update group element helpers --- src/group.h | 14 ++++++++----- src/group_impl.h | 36 ++++++++++++++++---------------- src/modules/frost/session_impl.h | 4 ++-- src/modules/musig/keyagg_impl.h | 4 ++-- src/modules/musig/session_impl.h | 4 ++-- src/tests.c | 22 +++++++++++++++---- 6 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/group.h b/src/group.h index 43cf57fd7..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 @@ -202,10 +210,6 @@ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf); */ static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge); -static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge); - -static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data); - /** Check invariants on an affine group element (no-op unless VERIFY is enabled). */ static void secp256k1_ge_verify(const secp256k1_ge *a); #define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a) diff --git a/src/group_impl.h b/src/group_impl.h index fb475217d..f073b5059 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -914,23 +914,6 @@ static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) { return secp256k1_fe_is_square_var(&yz); } -static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) { - if (secp256k1_ge_is_infinity(ge)) { - memset(data, 0, 64); - } else { - secp256k1_ge_to_bytes(data, ge); - } -} - -static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) { - unsigned char zeros[64] = { 0 }; - if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) { - secp256k1_ge_set_infinity(ge); - } else { - secp256k1_ge_from_bytes(ge, data); - } -} - static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) { #ifdef EXHAUSTIVE_TEST_ORDER secp256k1_gej out; @@ -982,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. @@ -1002,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/session_impl.h b/src/modules/frost/session_impl.h index 7128c644e..7140dc877 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -58,7 +58,7 @@ static void secp256k1_frost_pubnonce_save(secp256k1_frost_pubnonce* nonce, secp2 int i; memcpy(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4); for (i = 0; i < 2; i++) { - secp256k1_point_save_ext(nonce->data + 4+64*i, &ge[i]); + secp256k1_ge_to_bytes(nonce->data + 4+64*i, &ge[i]); } } @@ -69,7 +69,7 @@ static int secp256k1_frost_pubnonce_load(const secp256k1_context* ctx, secp256k1 ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4) == 0); for (i = 0; i < 2; i++) { - secp256k1_point_load_ext(&ge[i], nonce->data + 4+64*i); + secp256k1_ge_from_bytes(&ge[i], nonce->data + 4+64*i); } return 1; } diff --git a/src/modules/musig/keyagg_impl.h b/src/modules/musig/keyagg_impl.h index 992fd9a35..1c221d77b 100644 --- a/src/modules/musig/keyagg_impl.h +++ b/src/modules/musig/keyagg_impl.h @@ -35,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; @@ -50,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; } diff --git a/src/tests.c b/src/tests.c index 00eb3bfe6..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)); } } From 2b8fcf0c24c83b6188eeb8e71371c3419bd0afb6 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 15:17:56 -0700 Subject: [PATCH 17/23] update noncegen --- src/modules/frost/session_impl.h | 64 +++++++++++++++++++------------- src/modules/frost/tests_impl.h | 7 ++-- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h index 7140dc877..f32e0de61 100644 --- a/src/modules/frost/session_impl.h +++ b/src/modules/frost/session_impl.h @@ -203,42 +203,54 @@ int secp256k1_frost_partial_sig_parse(const secp256k1_context* ctx, secp256k1_fr 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 seed[32]; + unsigned char rand[32]; unsigned char i; - enum { n_extra_in = 4 }; - const unsigned char *extra_in[n_extra_in]; - /* TODO: this doesn't have the same sidechannel resistance as the BIP340 - * nonce function because the seckey feeds directly into SHA. */ + 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, session_id, 32); - extra_in[0] = msg32; - extra_in[1] = key32; - extra_in[2] = pk32; - extra_in[3] = extra_input32; - for (i = 0; i < n_extra_in; i++) { - unsigned char len; - if (extra_in[i] != NULL) { - len = 32; - secp256k1_sha256_write(&sha, &len, 1); - secp256k1_sha256_write(&sha, extra_in[i], 32); - } else { - len = 0; - secp256k1_sha256_write(&sha, &len, 1); - } - } - secp256k1_sha256_finalize(&sha, seed); + 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_initialize(&sha); - secp256k1_sha256_write(&sha, seed, 32); - secp256k1_sha256_write(&sha, &i, sizeof(i)); - secp256k1_sha256_finalize(&sha, buf); + 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); } } diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h index ab33617aa..33bc6c757 100644 --- a/src/modules/frost/tests_impl.h +++ b/src/modules/frost/tests_impl.h @@ -610,10 +610,11 @@ void frost_nonce_test(void) { 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 < 4; i++) { + 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]) == 0); - CHECK(secp256k1_scalar_eq(&k[i][1], &k[j][1]) == 0); + CHECK(!secp256k1_scalar_eq(&k[i][0], &k[j][0])); + CHECK(!secp256k1_scalar_eq(&k[i][1], &k[j][1])); } } } From 82b364605592a0e43ac16e5c7342e3c4b6667fb9 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 15:18:40 -0700 Subject: [PATCH 18/23] update rfc comment --- src/modules/frost/keygen_impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index d49b241ae..b9ced38c5 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -164,7 +164,7 @@ static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigne int ret = 1; /* Derive share */ - /* See draft-irtf-cfrg-frost-08#appendix-C.1 */ + /* See RFC 9591, appendix C.1 */ secp256k1_scalar_set_int(&share_i, 0); if (!secp256k1_frost_compute_indexhash(&idx, id33)) { return 0; From 18fbec7f505e8d2fea86fbd6559847e3413699b2 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 16:09:11 -0700 Subject: [PATCH 19/23] fix comment --- include/secp256k1_frost.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 79cae3f00..d0bdbe117 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -396,11 +396,11 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_a * the following pseudocode xonly_pubkey_tweak_add_check (absent earlier * failures) returns 1. * - * secp256k1_frost_share_agg(..., agg_pk, ...) - * secp256k1_frost_pubkey_tweak(..., keygen_cache, agg_pk) + * 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_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32) + * 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 From 09698c878e1f9e2b6546814daa2fa4e43d53a509 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Fri, 30 Aug 2024 16:35:54 -0700 Subject: [PATCH 20/23] fix comment --- src/modules/frost/keygen_impl.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index b9ced38c5..3628ecb86 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -294,7 +294,7 @@ static int secp256k1_frost_interpolate_pubkey_ecmult_callback(secp256k1_scalar * return 1; } -/* See draft-irtf-cfrg-frost-08#appendix-C.2 */ +/* See RFC 9591 */ static int secp256k1_frost_vss_verify_internal(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_scalar *share, const secp256k1_pubkey * const* vss_commitment) { secp256k1_scalar share_neg; secp256k1_gej tmpj, snj; @@ -325,7 +325,6 @@ static int secp256k1_frost_vss_verify_internal(const secp256k1_context* ctx, siz return secp256k1_gej_is_infinity(&tmpj); } -/* See draft-irtf-cfrg-frost-08#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 * const* vss_commitment) { secp256k1_scalar share_i; From 05fb9fd81924536bfdb117d42a426ee2e259e3d5 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Wed, 4 Sep 2024 18:41:38 -0700 Subject: [PATCH 21/23] fix import --- 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 d0bdbe117..4b53ea791 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -7,7 +7,7 @@ extern "C" { #endif -#include +#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! From 5f76404c6a488cad08595b67c6753bd2bef06082 Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Thu, 5 Sep 2024 22:15:26 -0700 Subject: [PATCH 22/23] remove unused code --- src/modules/frost/keygen_impl.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index 3628ecb86..cf2d727d4 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -238,12 +238,6 @@ typedef struct { size_t threshold; } secp256k1_frost_compute_pubshare_ecmult_data; -typedef struct { - const secp256k1_context *ctx; - const secp256k1_pubkey * const* pks; - size_t threshold; -} secp256k1_frost_pubkey_combine_ecmult_data; - typedef struct { const secp256k1_context *ctx; const secp256k1_pubkey * const* pubshares; From 4442867074a257898968db08ac7fc61e295285dd Mon Sep 17 00:00:00 2001 From: Jesse Posner Date: Wed, 6 Nov 2024 16:52:54 -0800 Subject: [PATCH 23/23] Return aggregated VSS when aggregating shares --- examples/frost.c | 7 +-- include/secp256k1_frost.h | 50 ++++++++--------- src/ctime_tests.c | 7 +-- src/modules/frost/keygen_impl.h | 95 ++++++++++++++++++++++++--------- src/modules/frost/tests_impl.h | 90 ++++++++++++++++--------------- 5 files changed, 150 insertions(+), 99 deletions(-) diff --git a/examples/frost.c b/examples/frost.c index 15a4b67ec..5f1ec6294 100644 --- a/examples/frost.c +++ b/examples/frost.c @@ -75,6 +75,7 @@ int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_se const secp256k1_pubkey *vss_commitments[N_SIGNERS]; const unsigned char *ids[N_SIGNERS]; const unsigned char *poks[N_SIGNERS]; + secp256k1_pubkey agg_vss_commitment[THRESHOLD]; for (i = 0; i < N_SIGNERS; i++) { vss_commitments[i] = signer[i].vss_commitment; @@ -100,7 +101,7 @@ int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_se assigned_shares[j] = &shares[j][i]; } /* Each participant aggregates the shares they received. */ - if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, assigned_shares, vss_commitments, poks, N_SIGNERS, THRESHOLD, signer[i].id)) { + if (!secp256k1_frost_share_agg(ctx, &signer_secrets[i].agg_share, agg_vss_commitment, assigned_shares, vss_commitments, poks, N_SIGNERS, THRESHOLD, signer[i].id)) { return 0; } for (j = 0; j < N_SIGNERS; j++) { @@ -108,12 +109,12 @@ int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_se * internally, so it is only neccessary to call this function if * share_agg returns an error, to determine which participant(s) * submitted faulty data. */ - if (!secp256k1_frost_share_verify(ctx, THRESHOLD, signer[i].id, assigned_shares[j], &vss_commitments[j])) { + if (!secp256k1_frost_share_verify(ctx, THRESHOLD, signer[i].id, assigned_shares[j], vss_commitments[j])) { return 0; } /* Each participant generates public verification shares that are * used for verifying partial signatures. */ - if (!secp256k1_frost_compute_pubshare(ctx, &signer[j].pubshare, THRESHOLD, signer[j].id, vss_commitments, N_SIGNERS)) { + if (!secp256k1_frost_compute_pubshare(ctx, &signer[j].pubshare, THRESHOLD, signer[j].id, agg_vss_commitment, N_SIGNERS)) { return 0; } } diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h index 4b53ea791..07b8d8247 100644 --- a/include/secp256k1_frost.h +++ b/include/secp256k1_frost.h @@ -237,29 +237,31 @@ SECP256K1_API int secp256k1_frost_shares_gen( * * 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: agg_share: the aggregated share - * In: shares: all key generation shares for the partcipant's index - * vss_commitments: coefficient commitments of all participants ordered by - * the IDs of the participants - * pok64s: proofs of knowledge for the shares ordered by the IDs of - * the participants - * n_shares: the total number of shares - * threshold: the minimum number of shares required to produce a - * signature - * id33: the 33-byte ID of the participant whose shares are being - * aggregated + * Args: ctx: pointer to a context object + * Out: agg_share: the aggregated share + * In: shares: all key generation shares for the partcipant's index + * agg_vss_commitment: the aggregated coefficient commitment + * vss_commitments: coefficient commitment of each participant ordered by + * the IDs of the participants + * pok64s: proofs of knowledge for the shares ordered by the IDs of + * the participants + * n_shares: the total number of shares + * threshold: the minimum number of shares required to produce a + * signature + * id33: the 33-byte ID of the participant whose shares are being + * aggregated */ SECP256K1_API int secp256k1_frost_share_agg( const secp256k1_context *ctx, secp256k1_frost_share *agg_share, + secp256k1_pubkey *agg_vss_commitment, const secp256k1_frost_share * const *shares, const secp256k1_pubkey * const *vss_commitments, const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33 -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8); +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(9); /** Verifies a share received during a key generation session * @@ -281,28 +283,28 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_share_verify( size_t threshold, const unsigned char *id33, const secp256k1_frost_share *share, - const secp256k1_pubkey * const *vss_commitment + 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_commitments: coefficient commitments of all participants - * n_participants: the total number of participants + * 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 + * agg_vss_commitment: the output of `secp256k1_frost_share_agg` + * 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 * const *vss_commitments, + const secp256k1_pubkey *agg_vss_commitment, size_t n_participants ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); diff --git a/src/ctime_tests.c b/src/ctime_tests.c index 32b65fa40..9e51e92a7 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -385,6 +385,7 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { const unsigned char *pok_ptr[2]; secp256k1_pubkey pubshare[2]; const secp256k1_pubkey *pubshares_ptr[2]; + secp256k1_pubkey agg_vss_commitment[2]; id_ptr[0] = id[0]; id_ptr[1] = id[1]; @@ -438,12 +439,12 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1][1], sizeof(secp256k1_pubkey)); SECP256K1_CHECKMEM_DEFINE(pok[0], 64); SECP256K1_CHECKMEM_DEFINE(pok[1], 64); - ret = secp256k1_frost_share_agg(ctx, &agg_share, share_ptr, vss_ptr, pok_ptr, 2, 2, id_ptr[0]); + ret = secp256k1_frost_share_agg(ctx, &agg_share, agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 2, 2, id_ptr[0]); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); SECP256K1_CHECKMEM_UNDEFINE(&agg_share, sizeof(&agg_share)); - CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[0], 2, id_ptr[0], vss_ptr, 2)); - CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[1], 2, id_ptr[1], vss_ptr, 2)); + CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[0], 2, id_ptr[0], agg_vss_commitment, 2)); + CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[1], 2, id_ptr[1], agg_vss_commitment, 2)); CHECK(secp256k1_frost_pubkey_gen(ctx, &cache, pubshares_ptr, 2, id_ptr)); /* nonce_gen */ SECP256K1_CHECKMEM_UNDEFINE(session_id, sizeof(session_id)); diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h index cf2d727d4..d18a5edd5 100644 --- a/src/modules/frost/keygen_impl.h +++ b/src/modules/frost/keygen_impl.h @@ -227,7 +227,7 @@ typedef struct { const secp256k1_context *ctx; secp256k1_scalar idx; secp256k1_scalar idxn; - const secp256k1_pubkey * const* vss_commitment; + const secp256k1_pubkey *vss_commitment; } secp256k1_frost_verify_share_ecmult_data; typedef struct { @@ -245,9 +245,15 @@ typedef struct { size_t n_pubshares; } secp256k1_frost_interpolate_pubkey_ecmult_data; +typedef struct { + const secp256k1_context *ctx; + size_t idxn; + const secp256k1_pubkey * const* vss_commitments; +} secp256k1_frost_vss_agg_ecmult_data; + static int secp256k1_frost_verify_share_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { secp256k1_frost_verify_share_ecmult_data *ctx = (secp256k1_frost_verify_share_ecmult_data *) data; - if (!secp256k1_pubkey_load(ctx->ctx, pt, *(ctx->vss_commitment)+idx)) { + if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitment[idx])) { return 0; } *sc = ctx->idxn; @@ -288,8 +294,20 @@ static int secp256k1_frost_interpolate_pubkey_ecmult_callback(secp256k1_scalar * return 1; } +static int secp256k1_frost_vss_agg_pubkey_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) { + secp256k1_frost_vss_agg_ecmult_data *ctx = (secp256k1_frost_vss_agg_ecmult_data *) data; + + if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitments[idx][ctx->idxn])) { + return 0; + } + + *sc = secp256k1_scalar_one; + + return 1; +} + /* See RFC 9591 */ -static int secp256k1_frost_vss_verify_internal(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_scalar *share, const secp256k1_pubkey * const* vss_commitment) { +static int secp256k1_frost_vss_verify_internal(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_scalar *share, const secp256k1_pubkey *vss_commitment) { secp256k1_scalar share_neg; secp256k1_gej tmpj, snj; secp256k1_ge sng; @@ -319,7 +337,7 @@ static int secp256k1_frost_vss_verify_internal(const secp256k1_context* ctx, siz return secp256k1_gej_is_infinity(&tmpj); } -int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, const unsigned char *id33, const secp256k1_frost_share *share, const secp256k1_pubkey * const* vss_commitment) { +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; VERIFY_CHECK(ctx != NULL); @@ -335,16 +353,17 @@ int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, return secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, vss_commitment); } -int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pubkey *pubshare, size_t threshold, const unsigned char *id33, const secp256k1_pubkey * const* vss_commitments, size_t n_participants) { - secp256k1_gej pkj; +int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pubkey *pubshare, size_t threshold, const unsigned char *id33, const secp256k1_pubkey *agg_vss_commitment, size_t n_participants) { + secp256k1_gej tmpj; secp256k1_ge tmp; - secp256k1_frost_compute_pubshare_ecmult_data compute_pubshare_ecmult_data; + secp256k1_frost_verify_share_ecmult_data verify_share_ecmult_data; VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); ARG_CHECK(pubshare != NULL); memset(pubshare, 0, sizeof(*pubshare)); ARG_CHECK(id33 != NULL); - ARG_CHECK(vss_commitments != NULL); + ARG_CHECK(agg_vss_commitment != NULL); ARG_CHECK(n_participants > 1); ARG_CHECK(threshold > 1); @@ -352,34 +371,55 @@ int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pub return 0; } - /* Use an EC multi-multiplication to compute the following equation: - * agg_share_i*G = ( - * idx^0*vss_commitment[0][0] + ... - * + idx^(t - 1)*vss_commitment[0][t - 1] - * ) + ... - * + ( - * idx^0*vss_commitment[n - 1][0] + ... - * + idx^(t - 1)*vss_commitment[n - 1][t - 1] - * )*/ - compute_pubshare_ecmult_data.ctx = ctx; - compute_pubshare_ecmult_data.vss_commitments = vss_commitments; - compute_pubshare_ecmult_data.threshold = threshold; + /* Use an EC multi-multiplication to verify the following equation: + * agg_share_i * G = idx^0*agg_vss_commitment[0] + * + ... + * + idx^(threshold - 1)*agg_vss_commitment[threshold - 1]*/ + verify_share_ecmult_data.ctx = ctx; + verify_share_ecmult_data.vss_commitment = agg_vss_commitment; /* Evaluate the public polynomial at the idx */ - if (!secp256k1_frost_compute_indexhash(&compute_pubshare_ecmult_data.idx, id33)) { + if (!secp256k1_frost_compute_indexhash(&verify_share_ecmult_data.idx, id33)) { return 0; } - secp256k1_scalar_set_int(&compute_pubshare_ecmult_data.idxn, 1); + secp256k1_scalar_set_int(&verify_share_ecmult_data.idxn, 1); /* TODO: add scratch */ - if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_compute_pubshare_ecmult_callback, (void *) &compute_pubshare_ecmult_data, n_participants*threshold)) { + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &tmpj, NULL, secp256k1_frost_verify_share_ecmult_callback, (void *) &verify_share_ecmult_data, threshold)) { return 0; } - secp256k1_ge_set_gej(&tmp, &pkj); + secp256k1_ge_set_gej(&tmp, &tmpj); secp256k1_pubkey_save(pubshare, &tmp); return 1; } -int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33) { +static int secp256k1_frost_vss_agg(const secp256k1_context* ctx, secp256k1_pubkey *agg_vss_commitment, const secp256k1_pubkey * const *vss_commitments, size_t n_participants, size_t threshold) { + secp256k1_gej tmpj; + secp256k1_ge tmp; + secp256k1_frost_vss_agg_ecmult_data vss_agg_ecmult_data; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(agg_vss_commitment != NULL); + ARG_CHECK(vss_commitments != NULL); + ARG_CHECK(n_participants > 1); + ARG_CHECK(threshold > 1); + + vss_agg_ecmult_data.ctx = ctx; + vss_agg_ecmult_data.vss_commitments = vss_commitments; + + for (vss_agg_ecmult_data.idxn = 0; vss_agg_ecmult_data.idxn < threshold; vss_agg_ecmult_data.idxn++) { + /* TODO: add scratch */ + if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &tmpj, NULL, secp256k1_frost_vss_agg_pubkey_ecmult_callback, (void *) &vss_agg_ecmult_data, n_participants)) { + return 0; + } + secp256k1_ge_set_gej(&tmp, &tmpj); + secp256k1_pubkey_save(&agg_vss_commitment[vss_agg_ecmult_data.idxn], &tmp); + } + + return 1; +} + +int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_share *agg_share, secp256k1_pubkey *agg_vss_commitment, const secp256k1_frost_share * const* shares, const secp256k1_pubkey * const* vss_commitments, const unsigned char * const *pok64s, size_t n_shares, size_t threshold, const unsigned char *id33) { secp256k1_scalar acc; size_t i; int ret = 1; @@ -422,10 +462,13 @@ int secp256k1_frost_share_agg(const secp256k1_context* ctx, secp256k1_frost_shar return 0; } /* Verify share against commitments */ - ret &= secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, &vss_commitments[i]); + ret &= secp256k1_frost_vss_verify_internal(ctx, threshold, id33, &share_i, vss_commitments[i]); secp256k1_scalar_add(&acc, &acc, &share_i); } secp256k1_frost_share_save(agg_share, &acc); + if (!secp256k1_frost_vss_agg(ctx, agg_vss_commitment, vss_commitments, n_shares, threshold)) { + return 0; + } return ret; } diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h index 33bc6c757..ca85df85e 100644 --- a/src/modules/frost/tests_impl.h +++ b/src/modules/frost/tests_impl.h @@ -62,6 +62,7 @@ void frost_simple_test(void) { const unsigned char *pok_ptr[5]; const secp256k1_pubkey *pubshare_ptr[5]; secp256k1_frost_keygen_cache cache; + secp256k1_pubkey agg_vss_commitment[3]; for (i = 0; i < 5; i++) { secp256k1_testrand256(buf[i]); @@ -81,10 +82,10 @@ void frost_simple_test(void) { for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { share_ptr[j] = &shares[j][i]; - CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], vss_ptr[j]) == 1); } - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], vss_ptr, 5) == 1); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], agg_vss_commitment, 5) == 1); } CHECK(secp256k1_frost_pubkey_gen(CTX, &cache, pubshare_ptr, 5, id_ptr) == 1); @@ -189,6 +190,7 @@ void frost_api_tests(void) { const unsigned char *pok_ptr[5]; const unsigned char *invalid_pok_ptr[5]; const secp256k1_pubkey *pubshare_ptr[5]; + secp256k1_pubkey agg_vss_commitment[3]; /** setup **/ memset(max64, 0xff, sizeof(max64)); @@ -273,72 +275,72 @@ void frost_api_tests(void) { invalid_share_ptr[j] = &shares[j][i]; } invalid_share_ptr[0] = &invalid_share; - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, invalid_pok_ptr, 5, 3, id_ptr[i]) == 0); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, invalid_pok_ptr, 5, 3, id_ptr[i]) == 0); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, NULL, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, NULL, agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, NULL, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, NULL, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, NULL, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, invalid_vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, invalid_vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, NULL, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, NULL, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 3, NULL)); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], invalid_share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, invalid_share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], NULL, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, NULL, vss_ptr, pok_ptr, 0, 3, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 0, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, NULL, pok_ptr, 5, 0, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, NULL, pok_ptr, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, NULL, 5, 0, id_ptr[i])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, NULL, 5, 0, id_ptr[i])); CHECK(frost_memcmp_and_randomize(agg_share[i].data, zeros68, sizeof(agg_share[i].data)) == 0); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); } /* Share verification */ - CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &vss_ptr[0]) == 1); - CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &vss_ptr[1]) == 0); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, NULL, share_ptr[0], &vss_ptr[0])); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], NULL, &vss_ptr[1])); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], &invalid_share, &vss_ptr[0])); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], vss_ptr[0]) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], vss_ptr[1]) == 0); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, NULL, share_ptr[0], vss_ptr[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], NULL, vss_ptr[1])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], &invalid_share, vss_ptr[0])); CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], NULL)); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &invalid_vss_ptr[0])); - CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 0, id_ptr[4], share_ptr[0], &vss_ptr[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], invalid_vss_ptr[0])); + CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 0, id_ptr[4], share_ptr[0], vss_ptr[0])); - CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], &vss_ptr[0]) == 1); - CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[1], &vss_ptr[1]) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[0], vss_ptr[0]) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[4], share_ptr[1], vss_ptr[1]) == 1); /* Compute public verification share */ - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], vss_ptr, 5) == 1); - CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, NULL, 3, id_ptr[0], vss_ptr, 5)); - CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, NULL, vss_ptr, 5)); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], agg_vss_commitment, 5) == 1); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, NULL, 3, id_ptr[0], agg_vss_commitment, 5)); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, NULL, agg_vss_commitment, 5)); 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, 5)); 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_ptr, 5)); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], invalid_vss_commitment[0], 5)); 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_ptr, 5)); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 0, id_ptr[0], invalid_vss_commitment[0], 5)); 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, 5)); 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_ptr, 0)); + CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], invalid_vss_commitment[0], 0)); 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, 0)); 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_ptr, 5) == 1); - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[1], 3, id_ptr[1], vss_ptr, 5) == 1); - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[2], 3, id_ptr[2], vss_ptr, 5) == 1); - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[3], 3, id_ptr[3], vss_ptr, 5) == 1); - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[4], 3, id_ptr[4], vss_ptr, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, id_ptr[0], agg_vss_commitment, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[1], 3, id_ptr[1], agg_vss_commitment, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[2], 3, id_ptr[2], agg_vss_commitment, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[3], 3, id_ptr[3], agg_vss_commitment, 5) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[4], 3, id_ptr[4], agg_vss_commitment, 5) == 1); /* pubkey_gen */ CHECK(secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, id_ptr) == 1); @@ -712,6 +714,7 @@ void frost_tweak_test(void) { const unsigned char *id_ptr[5]; const unsigned char *pok_ptr[5]; const secp256k1_pubkey *pubshare_ptr[5]; + secp256k1_pubkey agg_vss_commitment[3]; /* Key Setup */ for (i = 0; i < 5; i++) { @@ -730,10 +733,10 @@ void frost_tweak_test(void) { for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { share_ptr[j] = &shares[j][i]; - CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], &vss_ptr[j]) == 1); + CHECK(secp256k1_frost_share_verify(CTX, 3, id_ptr[i], share_ptr[j], vss_ptr[j]) == 1); } - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], vss_ptr, 5) == 1); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 3, id_ptr[i]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, id_ptr[i], agg_vss_commitment, 5) == 1); } /* Compute P0 and test signing for it */ CHECK(secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, id_ptr) == 1); @@ -785,6 +788,7 @@ void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1 const unsigned char *pok_ptr[5]; secp256k1_pubkey pubshare[5]; const secp256k1_pubkey *pubshare_ptr[5]; + secp256k1_pubkey agg_vss_commitment[3]; for (i = 0; i < 5; i++) { secp256k1_testrand256(seed[i]); @@ -799,8 +803,8 @@ void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1 for (j = 0; j < 5; j++) { share_ptr[j] = &shares[j][i]; } - CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, ids33[i], vss_ptr, 5) == 1); - CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], share_ptr, vss_ptr, pok_ptr, 5, 3, ids33[i]) == 1); + CHECK(secp256k1_frost_share_agg(CTX, &agg_share[i], agg_vss_commitment, share_ptr, vss_ptr, pok_ptr, 5, 3, ids33[i]) == 1); + CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, ids33[i], agg_vss_commitment, 5) == 1); } CHECK(secp256k1_frost_pubkey_gen(CTX, keygen_cache, pubshare_ptr, 5, ids33) == 1); }