Skip to content

FROST Trusted Dealer #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6216645
frost trusted dealer: initialize project
jesseposner Sep 5, 2024
833d4d8
frost trusted dealer: share generation
jesseposner Sep 5, 2024
a64a904
frost trusted dealer: share verification
jesseposner Sep 6, 2024
3787674
frost trusted dealer: key tweaking
jesseposner Sep 6, 2024
9fa15f5
frost trusted dealer: nonce generation
jesseposner Sep 10, 2024
af4dfaf
frost trusted dealer: nonce aggregation and adaptor signatures
jesseposner Sep 10, 2024
ab1bb83
frost trusted dealer: signature generation and aggregation
jesseposner Sep 10, 2024
aea613b
frost trusted dealer: add example file
jesseposner Sep 10, 2024
fafc1f1
frost trusted dealer: add tests
jesseposner Sep 10, 2024
f41560c
frost trusted dealer: add documentation file
jesseposner Sep 10, 2024
c139f91
frost trusted dealer: build: add CMake support
theStack Sep 21, 2024
f045caf
Use VERIFY_CHECK for zero x-coordinate
jesseposner Oct 4, 2024
298accf
Remove unused ret variable
jesseposner Oct 4, 2024
640d61c
Rename secp256k1_frost_share to secp256k1_frost_secshare
jesseposner Oct 4, 2024
4b387ef
Revise nonce reuse warning
jesseposner Oct 4, 2024
6aa7df2
Check for equality before computing indexhash
jesseposner Oct 4, 2024
eb543cb
Simplify code
jesseposner Oct 4, 2024
b8c671c
Improve documentation
jesseposner Oct 4, 2024
4ead1a7
wip
jesseposner Feb 14, 2025
039eb4a
- examples/frost.c: zero-init secp256k1_frost_keygen_cache to avoid UB.
BEULAHEVANJALIN Aug 8, 2025
9400d0b
fix(frost/tests): update tests to current FROST API
BEULAHEVANJALIN Aug 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions include/secp256k1_frost.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef SECP256K1_FROST_H
#define SECP256K1_FROST_H

#include "secp256k1_extrakeys.h"

#ifdef __cplusplus
extern "C" {
#endif
Expand All @@ -15,6 +17,82 @@ extern "C" {
* (https://crysp.uwaterloo.ca/software/frost/).
*/

/** Opaque data structures
*
* The exact representation of data inside is implementation defined and not
* guaranteed to be portable between different platforms or versions. If you
* need to convert to a format suitable for storage, transmission, or
* comparison, use the corresponding serialization and parsing functions.
*/

/** Opaque data structure that holds a signer's _secret_ share.
*
* Guaranteed to be 36 bytes in size. Serialized and parsed with
* `frost_share_serialize` and `frost_share_parse`.
*/
typedef struct {
unsigned char data[36];
} secp256k1_frost_share;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming nit: maybe call it secshare (both for the type and its instances and (de)ser function names in the API), in order to underline that this should be kept in secret? also since the public counterpart is called pubshare.


/** Serialize a FROST share
*
* Returns: 1 when the share could be serialized, 0 otherwise
* Args: ctx: pointer to a context object
* Out: out32: pointer to a 32-byte array to store the serialized share
* In: share: pointer to the share
*/
SECP256K1_API int secp256k1_frost_share_serialize(
const secp256k1_context *ctx,
unsigned char *out32,
const secp256k1_frost_share *share
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Parse a FROST share.
*
* Returns: 1 when the share could be parsed, 0 otherwise.
* Args: ctx: pointer to a context object
* Out: share: pointer to a share object
* In: in32: pointer to the 32-byte share to be parsed
*/
SECP256K1_API int secp256k1_frost_share_parse(
const secp256k1_context *ctx,
secp256k1_frost_share *share,
const unsigned char *in32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Creates key shares
*
* To generate a key, a trusted dealer generates a share for each participant.
*
* The trusted dealer must transmit shares over secure channels to each
* participant.
*
* Each call to this function must have a UNIQUE and uniformly RANDOM seed32
* that must that must NOT BE REUSED in subsequent calls to this function and
* must be KEPT SECRET (even from participants).
*
* Returns: 0 if the arguments are invalid, 1 otherwise
* Args: ctx: pointer to a context object
* Out: shares: pointer to the key shares
* vss_commitment: pointer to the VSS commitment
* In: seed32: 32-byte random seed as explained above. Must be
* unique to this call to secp256k1_frost_shares_gen
* and must be uniformly random.
* threshold: the minimum number of signers required to produce a
* signature
* n_participants: the total number of participants
* ids33: array of 33-byte participant IDs
*/
SECP256K1_API int secp256k1_frost_shares_gen(
const secp256k1_context *ctx,
secp256k1_frost_share *shares,
secp256k1_pubkey *vss_commitment,
const unsigned char *seed32,
size_t threshold,
size_t n_participants,
const unsigned char * const *ids33
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7);

#ifdef __cplusplus
}
#endif
Expand Down
2 changes: 2 additions & 0 deletions src/modules/frost/Makefile.am.include
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions src/modules/frost/keygen.h
Original file line number Diff line number Diff line change
@@ -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
178 changes: 178 additions & 0 deletions src/modules/frost/keygen_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**********************************************************************
* Copyright (c) 2021-2024 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/

#ifndef SECP256K1_MODULE_FROST_KEYGEN_IMPL_H
#define SECP256K1_MODULE_FROST_KEYGEN_IMPL_H

#include <string.h>

#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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the chance of this happening is negligible, a VERIFY_CHECK might be sufficient? Then the function could be void, also simplifying the call-sites.


return 1;
}

static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 };

static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_scalar *s) {
memcpy(&share->data[0], secp256k1_frost_share_magic, 4);
secp256k1_scalar_get_b32(&share->data[4], s);
}

static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share) {
int overflow;

/* The magic is non-secret so it can be declassified to allow branching. */
secp256k1_declassify(ctx, &share->data[0], 4);
ARG_CHECK(secp256k1_memcmp_var(&share->data[0], secp256k1_frost_share_magic, 4) == 0);
secp256k1_scalar_set_b32(s, &share->data[4], &overflow);
/* Parsed shares cannot overflow */
VERIFY_CHECK(!overflow);
return 1;
}

int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_share* share) {
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(out32 != NULL);
ARG_CHECK(share != NULL);
memcpy(out32, &share->data[4], 32);
return 1;
}

int secp256k1_frost_share_parse(const secp256k1_context* ctx, secp256k1_frost_share* share, const unsigned char *in32) {
secp256k1_scalar tmp;
int overflow;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(share != NULL);
ARG_CHECK(in32 != NULL);

secp256k1_scalar_set_b32(&tmp, in32, &overflow);
if (overflow) {
return 0;
}
secp256k1_frost_share_save(share, &tmp);
return 1;
}

static void secp256k1_frost_derive_coeff(secp256k1_scalar *coeff, const unsigned char *polygen32, size_t i) {
secp256k1_sha256 sha;
unsigned char buf[32];

secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/coeffgen", sizeof("FROST/coeffgen") - 1);
secp256k1_sha256_write(&sha, polygen32, 32);
secp256k1_write_be64(&buf[0], i);
secp256k1_sha256_write(&sha, buf, 8);
secp256k1_sha256_finalize(&sha, buf);
secp256k1_scalar_set_b32(coeff, buf, NULL);
}

static int secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubkey *vss_commitment, const unsigned char *polygen32, size_t threshold) {
secp256k1_gej rj;
secp256k1_ge rp;
size_t i;
int ret = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in secp256k1_frost_vss_gen: this value is never changed, so could remove ret and declare the function as void instead, also simplifying the call-site below.


/* Compute commitment to each coefficient */
for (i = 0; i < threshold; i++) {
secp256k1_scalar coeff_i;

secp256k1_frost_derive_coeff(&coeff_i, polygen32, i);
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &coeff_i);
secp256k1_ge_set_gej(&rp, &rj);
secp256k1_pubkey_save(&vss_commitment[threshold - i - 1], &rp);
}
return ret;
}

static int secp256k1_frost_share_gen(secp256k1_frost_share *share, const unsigned char *polygen32, size_t threshold, const unsigned char *id33) {
secp256k1_scalar idx;
secp256k1_scalar share_i;
size_t i;
int ret = 1;

/* Derive share */
/* See RFC 9591, appendix C.1 */
secp256k1_scalar_set_int(&share_i, 0);
if (!secp256k1_frost_compute_indexhash(&idx, id33)) {
return 0;
}
for (i = 0; i < threshold; i++) {
secp256k1_scalar coeff_i;

secp256k1_frost_derive_coeff(&coeff_i, polygen32, i);
/* Horner's method to evaluate polynomial to derive shares */
secp256k1_scalar_add(&share_i, &share_i, &coeff_i);
if (i < threshold - 1) {
secp256k1_scalar_mul(&share_i, &share_i, &idx);
}
}
secp256k1_frost_share_save(share, &share_i);

return ret;
}

int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_share *shares, secp256k1_pubkey *vss_commitment, const unsigned char *seed32, size_t threshold, size_t n_participants, const unsigned char * const* ids33) {
secp256k1_sha256 sha;
unsigned char polygen[32];
size_t i;
int ret = 1;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(shares != NULL);
for (i = 0; i < n_participants; i++) {
memset(&shares[i], 0, sizeof(shares[i]));
}
ARG_CHECK(vss_commitment != NULL);
ARG_CHECK(seed32 != NULL);
ARG_CHECK(ids33 != NULL);
ARG_CHECK(threshold > 1);
ARG_CHECK(n_participants >= threshold);

/* Commit to all inputs */
secp256k1_sha256_initialize(&sha);
secp256k1_sha256_write(&sha, seed32, 32);
secp256k1_write_be64(&polygen[0], threshold);
secp256k1_write_be64(&polygen[8], n_participants);
secp256k1_sha256_write(&sha, polygen, 16);
for (i = 0; i < n_participants; i++) {
secp256k1_sha256_write(&sha, ids33[i], 33);
}
secp256k1_sha256_finalize(&sha, polygen);

ret &= secp256k1_frost_vss_gen(ctx, vss_commitment, polygen, threshold);

for (i = 0; i < n_participants; i++) {
ret &= secp256k1_frost_share_gen(&shares[i], polygen, threshold, ids33[i]);
}

return ret;
}

#endif
2 changes: 2 additions & 0 deletions src/modules/frost/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
#ifndef SECP256K1_MODULE_FROST_MAIN
#define SECP256K1_MODULE_FROST_MAIN

#include "keygen_impl.h"

#endif