Skip to content
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

FROST Trusted Dealer #278

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
135 changes: 135 additions & 0 deletions include/secp256k1_frost.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ extern "C" {
* This module implements a variant of Flexible Round-Optimized Schnorr
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
* (https://crysp.uwaterloo.ca/software/frost/).
*
* The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public
* key tweaking.
*/

/** Opaque data structures
Expand All @@ -25,6 +28,15 @@ extern "C" {
* comparison, use the corresponding serialization and parsing functions.
*/

/** Opaque data structure that caches information about key tweaking.
*
* Guaranteed to be 101 bytes in size. It can be safely copied/moved. No
* serialization and parsing functions.
*/
typedef struct {
unsigned char data[101];
} secp256k1_frost_keygen_cache;

/** Opaque data structure that holds a signer's _secret_ share.
*
* Guaranteed to be 36 bytes in size. Serialized and parsed with
Expand Down Expand Up @@ -136,6 +148,129 @@ SECP256K1_API int secp256k1_frost_compute_pubshare(
const secp256k1_pubkey *vss_commitment
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);

/** Computes a group public key and uses it to initialize a keygen_cache
*
* Returns: 0 if the arguments are invalid, 1 otherwise
* Args: ctx: pointer to a context object
* Out: keygen_cache: pointer to a frost_keygen_cache struct that is required
* for signing (or observing the signing session and
* verifying partial signatures).
* In: pubshares: input array of pointers to the public verification
* shares of the participants ordered by the IDs of the
* participants
* n_pubshares: the total number of public verification shares
* ids33: array of the 33-byte participant IDs of the signers
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_gen(
const secp256k1_context *ctx,
secp256k1_frost_keygen_cache *keygen_cache,
const secp256k1_pubkey * const *pubshares,
size_t n_pubshares,
const unsigned char * const *ids33
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);

/** Obtain the group public key from a keygen_cache.
*
* This is only useful if you need the non-xonly public key, in particular for
* plain (non-xonly) tweaking or batch-verifying multiple key aggregations
* (not implemented).
*
* Returns: 0 if the arguments are invalid, 1 otherwise
* Args: ctx: pointer to a context object
* Out: pk: the FROST group public key.
* In: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by
* `frost_pubkey_gen`
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get(
const secp256k1_context *ctx,
secp256k1_pubkey *pk,
const secp256k1_frost_keygen_cache *keygen_cache
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Apply ordinary "EC" tweaking to a public key in a given keygen_cache by
* adding the generator multiplied with `tweak32` to it. This is useful for
* deriving child keys from a group public key via BIP32.
*
* The tweaking method is the same as `secp256k1_ec_pubkey_tweak_add`. So after
* the following pseudocode buf and buf2 have identical contents (absent
* earlier failures).
*
* secp256k1_frost_pubkey_gen(..., keygen_cache, ...)
* secp256k1_frost_pubkey_tweak(..., keygen_cache, xonly_pk)
* secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, keygen_cache, tweak32)
* secp256k1_ec_pubkey_serialize(..., buf, output_pk)
* secp256k1_frost_pubkey_get(..., ec_pk, xonly_pk)
* secp256k1_ec_pubkey_tweak_add(..., ec_pk, tweak32)
* secp256k1_ec_pubkey_serialize(..., buf2, ec_pk)
*
* This function is required if you want to _sign_ for a tweaked group key.
* On the other hand, if you are only computing a public key, but not intending
* to create a signature for it, you can just use
* `secp256k1_ec_pubkey_tweak_add`.
*
* Returns: 0 if the arguments are invalid or the resulting public key would be
* invalid (only when the tweak is the negation of the corresponding
* secret key). 1 otherwise.
* Args: ctx: pointer to a context object
* Out: output_pubkey: pointer to a public key to store the result. Will be set
* to an invalid value if this function returns 0. If you
* do not need it, this arg can be NULL.
* In/Out: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by
* `frost_pubkey_tweak`
* In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid
* according to `secp256k1_ec_seckey_verify`, this function
* returns 0. For uniformly random 32-byte arrays the
* chance of being invalid is negligible (around 1 in
* 2^128).
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_add(
const secp256k1_context *ctx,
secp256k1_pubkey *output_pubkey,
secp256k1_frost_keygen_cache *keygen_cache,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);

/** Apply x-only tweaking to a public key in a given keygen_cache by adding the
* generator multiplied with `tweak32` to it. This is useful for creating
* Taproot outputs.
*
* The tweaking method is the same as `secp256k1_xonly_pubkey_tweak_add`. So in
* the following pseudocode xonly_pubkey_tweak_add_check (absent earlier
* failures) returns 1.
*
* secp256k1_frost_pubkey_gen(..., keygen_cache, ..., ..., ...)
* secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, keygen_cache, tweak32)
* secp256k1_xonly_pubkey_serialize(..., buf, output_pk)
* secp256k1_frost_pubkey_get(..., pk, keygen_cache)
* secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., pk, tweak32)
*
* This function is required if you want to _sign_ for a tweaked group key.
* On the other hand, if you are only computing a public key, but not intending
* to create a signature for it, you can just use
* `secp256k1_xonly_pubkey_tweak_add`.
*
* Returns: 0 if the arguments are invalid or the resulting public key would be
* invalid (only when the tweak is the negation of the corresponding
* secret key). 1 otherwise.
* Args: ctx: pointer to a context object
* Out: output_pubkey: pointer to a public key to store the result. Will be set
* to an invalid value if this function returns 0. If you
* do not need it, this arg can be NULL.
* In/Out: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by
* `frost_pubkey_tweak`
* In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid
* according to secp256k1_ec_seckey_verify, this function
* returns 0. For uniformly random 32-byte arrays the
* chance of being invalid is negligible (around 1 in
* 2^128).
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_tweak_add(
const secp256k1_context *ctx,
secp256k1_pubkey *output_pubkey,
secp256k1_frost_keygen_cache *keygen_cache,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);

#ifdef __cplusplus
}
#endif
Expand Down
12 changes: 12 additions & 0 deletions src/modules/frost/keygen.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,16 @@
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_frost.h"

#include "../../group.h"
#include "../../scalar.h"

typedef struct {
secp256k1_ge pk;
/* tweak is identical to value tacc[v] in the specification. */
secp256k1_scalar tweak;
/* parity_acc corresponds to gacc[v] in the spec. If gacc[v] is -1,
* parity_acc is 1. Otherwise, parity_acc is 0. */
int parity_acc;
} secp256k1_keygen_cache_internal;

#endif
175 changes: 175 additions & 0 deletions src/modules/frost/keygen_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,39 @@
#include "../../hash.h"
#include "../../scalar.h"

static const unsigned char secp256k1_frost_keygen_cache_magic[4] = { 0x40, 0x25, 0x2e, 0x41 };

/* A tweak cache consists of
* - 4 byte magic set during initialization to allow detecting an uninitialized
* object.
* - 64 byte aggregate (and potentially tweaked) public key
* - 1 byte the parity of the internal key (if tweaked, otherwise 0)
* - 32 byte tweak
*/
/* Requires that cache_i->pk is not infinity. */
static void secp256k1_keygen_cache_save(secp256k1_frost_keygen_cache *cache, secp256k1_keygen_cache_internal *cache_i) {
unsigned char *ptr = cache->data;
memcpy(ptr, secp256k1_frost_keygen_cache_magic, 4);
ptr += 4;
secp256k1_ge_to_bytes(ptr, &cache_i->pk);
ptr += 64;
*ptr = cache_i->parity_acc;
ptr += 1;
secp256k1_scalar_get_b32(ptr, &cache_i->tweak);
}

static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_keygen_cache_internal *cache_i, const secp256k1_frost_keygen_cache *cache) {
const unsigned char *ptr = cache->data;
ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_keygen_cache_magic, 4) == 0);
ptr += 4;
secp256k1_ge_from_bytes(&cache_i->pk, ptr);
ptr += 64;
cache_i->parity_acc = *ptr & 1;
ptr += 1;
secp256k1_scalar_set_b32(&cache_i->tweak, ptr, NULL);
return 1;
}

/* Computes indexhash = tagged_hash(pk) */
static int secp256k1_frost_compute_indexhash(secp256k1_scalar *indexhash, const unsigned char *id33) {
secp256k1_sha256 sha;
Expand Down Expand Up @@ -182,6 +215,13 @@ typedef struct {
const secp256k1_pubkey *vss_commitment;
} secp256k1_frost_evaluate_vss_ecmult_data;

typedef struct {
const secp256k1_context *ctx;
const secp256k1_pubkey * const* pubshares;
const unsigned char * const *ids33;
size_t n_pubshares;
} secp256k1_frost_interpolate_pubkey_ecmult_data;

static int secp256k1_frost_evaluate_vss_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) {
secp256k1_frost_evaluate_vss_ecmult_data *ctx = (secp256k1_frost_evaluate_vss_ecmult_data *) data;
if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitment[idx])) {
Expand All @@ -193,6 +233,23 @@ static int secp256k1_frost_evaluate_vss_ecmult_callback(secp256k1_scalar *sc, se
return 1;
}

static int secp256k1_frost_interpolate_pubkey_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) {
secp256k1_frost_interpolate_pubkey_ecmult_data *ctx = (secp256k1_frost_interpolate_pubkey_ecmult_data *) data;
secp256k1_scalar l;

if (!secp256k1_pubkey_load(ctx->ctx, pt, ctx->pubshares[idx])) {
return 0;
}

if (!secp256k1_frost_lagrange_coefficient(&l, ctx->ids33, ctx->n_pubshares, ctx->ids33[idx])) {
return 0;
}

*sc = l;

return 1;
}

static int secp256k1_frost_evaluate_vss(const secp256k1_context* ctx, secp256k1_gej *share, size_t threshold, const unsigned char *id33, const secp256k1_pubkey *vss_commitment) {
secp256k1_frost_evaluate_vss_ecmult_data evaluate_vss_ecmult_data;

Expand Down Expand Up @@ -266,4 +323,122 @@ int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pub
return 1;
}

int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *agg_pk, const secp256k1_frost_keygen_cache *keyagg_cache) {
secp256k1_keygen_cache_internal cache_i;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(agg_pk != NULL);
memset(agg_pk, 0, sizeof(*agg_pk));
ARG_CHECK(keyagg_cache != NULL);

if(!secp256k1_keygen_cache_load(ctx, &cache_i, keyagg_cache)) {
return 0;
}
secp256k1_pubkey_save(agg_pk, &cache_i.pk);
return 1;
}

int secp256k1_frost_pubkey_gen(const secp256k1_context* ctx, secp256k1_frost_keygen_cache *cache, const secp256k1_pubkey * const *pubshares, size_t n_pubshares, const unsigned char * const *ids33) {
secp256k1_gej pkj;
secp256k1_frost_interpolate_pubkey_ecmult_data interpolate_pubkey_ecmult_data;
secp256k1_keygen_cache_internal cache_i = { 0 };

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(cache != NULL);
ARG_CHECK(pubshares != NULL);
ARG_CHECK(ids33 != NULL);
ARG_CHECK(n_pubshares > 1);

interpolate_pubkey_ecmult_data.ctx = ctx;
interpolate_pubkey_ecmult_data.pubshares = pubshares;
interpolate_pubkey_ecmult_data.ids33 = ids33;
interpolate_pubkey_ecmult_data.n_pubshares = n_pubshares;

/* TODO: add scratch */
if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_interpolate_pubkey_ecmult_callback, (void *) &interpolate_pubkey_ecmult_data, n_pubshares)) {
return 0;
}
secp256k1_ge_set_gej(&cache_i.pk, &pkj);
secp256k1_keygen_cache_save(cache, &cache_i);

return 1;
}

static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32, int xonly) {
secp256k1_keygen_cache_internal cache_i;
int overflow = 0;
secp256k1_scalar tweak;

VERIFY_CHECK(ctx != NULL);
if (output_pubkey != NULL) {
memset(output_pubkey, 0, sizeof(*output_pubkey));
}
ARG_CHECK(keygen_cache != NULL);
ARG_CHECK(tweak32 != NULL);

if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
return 0;
}
secp256k1_scalar_set_b32(&tweak, tweak32, &overflow);
if (overflow) {
return 0;
}
if (xonly && secp256k1_extrakeys_ge_even_y(&cache_i.pk)) {
cache_i.parity_acc ^= 1;
secp256k1_scalar_negate(&cache_i.tweak, &cache_i.tweak);
}
secp256k1_scalar_add(&cache_i.tweak, &cache_i.tweak, &tweak);
if (!secp256k1_eckey_pubkey_tweak_add(&cache_i.pk, &tweak)) {
return 0;
}
/* eckey_pubkey_tweak_add fails if cache_i.pk is infinity */
VERIFY_CHECK(!secp256k1_ge_is_infinity(&cache_i.pk));
secp256k1_keygen_cache_save(keygen_cache, &cache_i);
if (output_pubkey != NULL) {
secp256k1_pubkey_save(output_pubkey, &cache_i.pk);
}
return 1;
}

int secp256k1_frost_pubkey_ec_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32) {
return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, keygen_cache, tweak32, 0);
}

int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32) {
return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, keygen_cache, tweak32, 1);
}

static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const unsigned char * const *ids33, size_t n_participants, const unsigned char *my_id33) {
size_t i;
secp256k1_scalar num;
secp256k1_scalar den;
secp256k1_scalar party_idx;

secp256k1_scalar_set_int(&num, 1);
secp256k1_scalar_set_int(&den, 1);
jesseposner marked this conversation as resolved.
Show resolved Hide resolved
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe more a matter of taste, but the "skip for our own id" logic could be implemented before computing the _i_th indexhash by simply comparing the ids, e.g.

    if (secp256k1_memcmp_var(ids33[i], my_id33, 33) == 0) {
        return 0;
    }

(untested)


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