373 lines
19 KiB
C
373 lines
19 KiB
C
|
|
#ifndef SECP256K1_FROST_ENROLLMENT_H
|
||
|
|
#define SECP256K1_FROST_ENROLLMENT_H
|
||
|
|
|
||
|
|
#include "secp256k1.h"
|
||
|
|
#include "secp256k1_frost.h"
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
extern "C" {
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
|
||
|
|
/** This module implements FROST enrollment, the protocol of Section 4.1.1 of
|
||
|
|
* the FROST paper (https://eprint.iacr.org/2017/1155.pdf). It converts a
|
||
|
|
* (t, n) FROST group into a (t, n+1) group without re-running key generation
|
||
|
|
* and without any participant revealing its secret share. The same mechanism
|
||
|
|
* repairs a lost share: running it with the target identifier of an existing
|
||
|
|
* participant reproduces exactly that participant's share.
|
||
|
|
*
|
||
|
|
* 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!
|
||
|
|
*
|
||
|
|
* Unlike the frost module, this protocol has no BIP. It is specified only by
|
||
|
|
* the paper and by the reference proof of concept at
|
||
|
|
* https://github.com/siv2r/frost-enrollment. The tagged hash strings and the
|
||
|
|
* parameter serialization used here are this module's own, frozen by the
|
||
|
|
* regression vectors in src/modules/frost_enrollment/vectors.h.
|
||
|
|
*
|
||
|
|
* Identifiers follow the frost module: participants are identified by uint32
|
||
|
|
* values 0..n-1, and participant id i sits at polynomial x-coordinate i+1.
|
||
|
|
* The party receiving a share is identified by `new_id`, which selects the
|
||
|
|
* mode:
|
||
|
|
*
|
||
|
|
* - enrollment: new_id == n_participants. The group grows to n+1
|
||
|
|
* participants, so n_participants must be strictly smaller than
|
||
|
|
* SECP256K1_FROST_MAX_PARTICIPANTS.
|
||
|
|
* - repair: new_id < n_participants. The share of an existing participant
|
||
|
|
* is reproduced; n_participants may be SECP256K1_FROST_MAX_PARTICIPANTS.
|
||
|
|
*
|
||
|
|
* In both modes new_id must not appear in the helper id set.
|
||
|
|
*
|
||
|
|
* A run involves u helpers (threshold <= u <= n_participants), all of which
|
||
|
|
* must be existing participants, plus the target participant:
|
||
|
|
*
|
||
|
|
* 1. Round 1.1: every helper runs
|
||
|
|
* `secp256k1_frost_enrollment_shares_gen`, keeps the output share at
|
||
|
|
* its own position and sends each other output share, together with the
|
||
|
|
* returned parameters hash, to the helper it is aligned with.
|
||
|
|
* 2. Round 1.2: every helper runs
|
||
|
|
* `secp256k1_frost_enrollment_share_agg` over the share it kept and the
|
||
|
|
* shares it received. The function recomputes the parameters hash and
|
||
|
|
* compares every received hash against it, then sums the shares into a
|
||
|
|
* single value that is sent to the target participant along with the
|
||
|
|
* hash.
|
||
|
|
* 3. Round 2: the target participant runs
|
||
|
|
* `secp256k1_frost_enrollment_secshare_gen` over the u received values.
|
||
|
|
* It recomputes the parameters hash from the group key and parameters it
|
||
|
|
* believes, sums the values into its secret share, and verifies the
|
||
|
|
* result against the expected public share obtained from
|
||
|
|
* `secp256k1_frost_enrollment_pubshare_derive`.
|
||
|
|
*
|
||
|
|
* Afterwards, in enrollment mode, all participants must consistently update
|
||
|
|
* their record of n from n to n+1, and extend their table of public shares
|
||
|
|
* with the output of `secp256k1_frost_enrollment_pubshare_derive`.
|
||
|
|
*
|
||
|
|
* SECURITY: the values exchanged in rounds 1 and 2 are additive shares of
|
||
|
|
* real secret shares. They MUST be transmitted over confidential and
|
||
|
|
* authenticated channels. Like the chilldkg module, this module handles bytes
|
||
|
|
* only; transport is the caller's responsibility. Furthermore, the protocol
|
||
|
|
* contains no authorization step: any party that convinces t helpers to run
|
||
|
|
* it at a given identifier receives a valid share for that identifier. In
|
||
|
|
* repair mode that is an existing participant's actual share. Deciding who
|
||
|
|
* may be enrolled is a caller-side precondition.
|
||
|
|
*
|
||
|
|
* It is recommended to read the documentation in this include file carefully.
|
||
|
|
* Further notes on API usage can be found in
|
||
|
|
* src/modules/frost_enrollment/frost_enrollment.md.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/** Compute the enrollment parameters hash.
|
||
|
|
*
|
||
|
|
* The hash is
|
||
|
|
*
|
||
|
|
* out32 = tagged_hash("FROST enrollment/params_hash",
|
||
|
|
* cbytes(thresh_pk) || ser32(n_participants) ||
|
||
|
|
* ser32(threshold) || ser32(new_id) || ser32(n_ids) ||
|
||
|
|
* ser32(sorted_ids[0]) || ... ||
|
||
|
|
* ser32(sorted_ids[n_ids-1]))
|
||
|
|
*
|
||
|
|
* where cbytes is the 33-byte compressed serialization, ser32 is the 32-bit
|
||
|
|
* big-endian encoding and sorted_ids is `ids` in ascending order. Sorting
|
||
|
|
* makes the hash independent of the order in which a caller lists the helper
|
||
|
|
* set; the alignment of every other array in this API follows the caller's
|
||
|
|
* own `ids` order.
|
||
|
|
*
|
||
|
|
* Binding the threshold public key is what makes the hash identify a GROUP
|
||
|
|
* rather than a tuple of numbers: two unrelated groups that happen to share
|
||
|
|
* (t, n, ids, new_id) produce different hashes.
|
||
|
|
*
|
||
|
|
* Every party computes this value itself. The hash checks performed by
|
||
|
|
* `secp256k1_frost_enrollment_share_agg` and
|
||
|
|
* `secp256k1_frost_enrollment_secshare_gen` compare a received hash against a
|
||
|
|
* freshly recomputed one; they are never an equality test between two
|
||
|
|
* caller-supplied strings.
|
||
|
|
*
|
||
|
|
* This function operates on public data only.
|
||
|
|
*
|
||
|
|
* Returns: 0 if the arguments are invalid (duplicate ids, n_ids == 0, n_ids
|
||
|
|
* greater than SECP256K1_FROST_MAX_PARTICIPANTS, unparseable
|
||
|
|
* thresh_pk), 1 otherwise
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: out32: pointer to a 32-byte array for the hash. Set to zero
|
||
|
|
* if this function returns 0.
|
||
|
|
* In: thresh_pk: pointer to the threshold public key of the group
|
||
|
|
* ids: array of the u helper identifiers. Every id must be
|
||
|
|
* unique; the order is irrelevant.
|
||
|
|
* n_ids: number of helpers u
|
||
|
|
* new_id: identifier of the participant receiving the share
|
||
|
|
* n_participants: total number of participants n
|
||
|
|
* threshold: threshold t
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_params_hash(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *out32,
|
||
|
|
const secp256k1_pubkey *thresh_pk,
|
||
|
|
const uint32_t *ids,
|
||
|
|
size_t n_ids,
|
||
|
|
uint32_t new_id,
|
||
|
|
size_t n_participants,
|
||
|
|
uint32_t threshold
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||
|
|
|
||
|
|
/** Round 1.1: generate a helper's enrollment shares.
|
||
|
|
*
|
||
|
|
* Computes v = lambda_my_id(x_new) * secshare, where lambda is the Lagrange
|
||
|
|
* basis polynomial of my_id over the helper set evaluated at the target
|
||
|
|
* x-coordinate, and splits v into u additive shares that sum to v.
|
||
|
|
*
|
||
|
|
* `shares32_out` is aligned with `ids`: entry j is destined for the helper
|
||
|
|
* with identifier ids[j] and must be sent to it over a confidential,
|
||
|
|
* authenticated channel, together with `params_hash32_out`. The entry at
|
||
|
|
* my_id's own position is not sent anywhere; it is passed back into
|
||
|
|
* `secp256k1_frost_enrollment_share_agg` in round 1.2.
|
||
|
|
*
|
||
|
|
* The masking shares are derived from `session_secrand32` by tagged hashing,
|
||
|
|
* masked with the secret share as defense in depth against a broken random
|
||
|
|
* number generator (as `secp256k1_frost_nonce_gen` does). The derivation
|
||
|
|
* binds the parameters hash, and therefore the group key and the whole
|
||
|
|
* parameter tuple, for DOMAIN SEPARATION: two runs that share a seed but
|
||
|
|
* differ in group or parameters must not produce the same shares, because
|
||
|
|
* differencing the round-1.2 outputs would then leak share information. This
|
||
|
|
* binding cannot detect a parameter disagreement between helpers — these
|
||
|
|
* values are per-helper private randomness that nothing cross-checks — which
|
||
|
|
* is what the parameters hash comparison in round 1.2 is for.
|
||
|
|
*
|
||
|
|
* `session_secrand32` must be fresh uniformly random 32 bytes for every run.
|
||
|
|
* It is wiped before this function returns. `secshare32` is left unmodified.
|
||
|
|
*
|
||
|
|
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: shares32_out: pointer to an array of u*32 bytes for the enrollment
|
||
|
|
* shares, aligned with `ids`. Set to zero if this
|
||
|
|
* function returns 0.
|
||
|
|
* params_hash32_out: pointer to a 32-byte array for the parameters hash,
|
||
|
|
* identical to what
|
||
|
|
* `secp256k1_frost_enrollment_params_hash` returns for
|
||
|
|
* the same arguments. Set to zero if this function
|
||
|
|
* returns 0.
|
||
|
|
* In/Out:
|
||
|
|
* session_secrand32: pointer to a 32-byte array of fresh randomness. Must
|
||
|
|
* not be reused across runs. Wiped by this function.
|
||
|
|
* In: secshare32: pointer to the 32-byte secret share of my_id
|
||
|
|
* thresh_pk: pointer to the threshold public key of the group
|
||
|
|
* ids: array of the u helper identifiers. Every id must be
|
||
|
|
* unique, smaller than n_participants and different
|
||
|
|
* from new_id; the order is irrelevant but fixes the
|
||
|
|
* alignment of `shares32_out`.
|
||
|
|
* n_ids: number of helpers u. Must be between threshold and
|
||
|
|
* n_participants.
|
||
|
|
* my_id: own identifier. Must appear in `ids`.
|
||
|
|
* new_id: identifier of the participant receiving the share.
|
||
|
|
* Must equal n_participants (enrollment) or be smaller
|
||
|
|
* than it (repair).
|
||
|
|
* n_participants: total number of participants n. Must be at most
|
||
|
|
* SECP256K1_FROST_MAX_PARTICIPANTS, and strictly
|
||
|
|
* smaller in enrollment mode.
|
||
|
|
* threshold: threshold t. Must be at least 2 (see
|
||
|
|
* frost_enrollment.md) and at most n_participants.
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_shares_gen(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *shares32_out,
|
||
|
|
unsigned char *params_hash32_out,
|
||
|
|
unsigned char *session_secrand32,
|
||
|
|
const unsigned char *secshare32,
|
||
|
|
const secp256k1_pubkey *thresh_pk,
|
||
|
|
const uint32_t *ids,
|
||
|
|
size_t n_ids,
|
||
|
|
uint32_t my_id,
|
||
|
|
uint32_t new_id,
|
||
|
|
size_t n_participants,
|
||
|
|
uint32_t threshold
|
||
|
|
) 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(7);
|
||
|
|
|
||
|
|
/** Round 1.2: check parameter agreement and aggregate a helper's enrollment
|
||
|
|
* shares.
|
||
|
|
*
|
||
|
|
* The function recomputes its own parameters hash from `thresh_pk` and the
|
||
|
|
* parameter tuple it is given, and compares every entry of
|
||
|
|
* `received_params_hashes32` against it. On the first disagreement it returns
|
||
|
|
* 0 and, if `mismatch_id` is not NULL, stores the IDENTIFIER of the
|
||
|
|
* disagreeing helper there (not an array index, which would be ambiguous
|
||
|
|
* because identifiers need not be 0..u-1). `mismatch_id` is set to
|
||
|
|
* UINT32_MAX when the failure has another cause.
|
||
|
|
*
|
||
|
|
* Note the deliberately OPPOSITE own-slot conventions of the two u*32 input
|
||
|
|
* buffers, both of which are aligned with `ids`:
|
||
|
|
*
|
||
|
|
* - `all_shares32`: the entry at my_id's position IS read. It is the share
|
||
|
|
* `secp256k1_frost_enrollment_shares_gen` kept locally.
|
||
|
|
* - `received_params_hashes32`: the entry at my_id's position is NEVER
|
||
|
|
* read, and may be left zero. The own hash is recomputed, never taken
|
||
|
|
* from a buffer.
|
||
|
|
*
|
||
|
|
* The asymmetry is the point: it is what makes this a recomputation check
|
||
|
|
* rather than an equality test among caller-supplied strings, so a caller
|
||
|
|
* cannot launder a mismatch into a pass by filling its own slot with a
|
||
|
|
* received value.
|
||
|
|
*
|
||
|
|
* `sigma32_out` must be sent to the target participant over a confidential,
|
||
|
|
* authenticated channel, together with the parameters hash.
|
||
|
|
*
|
||
|
|
* Returns: 0 if the arguments are invalid or a parameters hash disagrees,
|
||
|
|
* 1 otherwise
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: sigma32_out: pointer to a 32-byte array for the aggregated share.
|
||
|
|
* Set to zero if this function returns 0.
|
||
|
|
* mismatch_id: pointer to an identifier to store the first helper
|
||
|
|
* whose parameters hash disagrees, or NULL
|
||
|
|
* In: all_shares32: pointer to an array of u*32 bytes, aligned with
|
||
|
|
* `ids`: the share kept locally at my_id's position and
|
||
|
|
* the shares received from the other helpers at theirs
|
||
|
|
* received_params_hashes32: pointer to an array of u*32 bytes, aligned
|
||
|
|
* with `ids`, holding the parameters hash received from
|
||
|
|
* each other helper. The entry at my_id's position is
|
||
|
|
* ignored.
|
||
|
|
* thresh_pk: pointer to the threshold public key of the group
|
||
|
|
* ids: array of the u helper identifiers, in the same order
|
||
|
|
* as in round 1.1
|
||
|
|
* n_ids: number of helpers u
|
||
|
|
* my_id: own identifier. Must appear in `ids`.
|
||
|
|
* new_id: identifier of the participant receiving the share
|
||
|
|
* n_participants: total number of participants n
|
||
|
|
* threshold: threshold t
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_share_agg(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *sigma32_out,
|
||
|
|
uint32_t *mismatch_id,
|
||
|
|
const unsigned char *all_shares32,
|
||
|
|
const unsigned char *received_params_hashes32,
|
||
|
|
const secp256k1_pubkey *thresh_pk,
|
||
|
|
const uint32_t *ids,
|
||
|
|
size_t n_ids,
|
||
|
|
uint32_t my_id,
|
||
|
|
uint32_t new_id,
|
||
|
|
size_t n_participants,
|
||
|
|
uint32_t threshold
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7);
|
||
|
|
|
||
|
|
/** Derive the public share at the target identifier.
|
||
|
|
*
|
||
|
|
* Computes sum_i lambda_i(x_new) * pubshares[i], the value of the group's
|
||
|
|
* public-share polynomial at the target participant's x-coordinate. This is
|
||
|
|
* the public counterpart of what the protocol produces, and it is used both
|
||
|
|
* to verify the new secret share in round 2 and to extend the group's table
|
||
|
|
* of public shares from n to n+1 entries after an enrollment.
|
||
|
|
*
|
||
|
|
* This function operates on public data only.
|
||
|
|
*
|
||
|
|
* Returns: 0 if the arguments are invalid or the result is the point at
|
||
|
|
* infinity, 1 otherwise
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: new_pubshare_out: pointer to a pubkey object for the derived public
|
||
|
|
* share. Set to zero if this function returns 0.
|
||
|
|
* In: pubshares: array of u pubkeys, aligned with `ids`, holding the
|
||
|
|
* public share of each helper
|
||
|
|
* ids: array of the u helper identifiers
|
||
|
|
* n_ids: number of helpers u
|
||
|
|
* new_id: identifier of the participant receiving the share
|
||
|
|
* n_participants: total number of participants n
|
||
|
|
* threshold: threshold t
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_pubshare_derive(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_pubkey *new_pubshare_out,
|
||
|
|
const secp256k1_pubkey *pubshares,
|
||
|
|
const uint32_t *ids,
|
||
|
|
size_t n_ids,
|
||
|
|
uint32_t new_id,
|
||
|
|
size_t n_participants,
|
||
|
|
uint32_t threshold
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||
|
|
|
||
|
|
/** Round 2: derive the target participant's secret share.
|
||
|
|
*
|
||
|
|
* In order:
|
||
|
|
*
|
||
|
|
* 1. If `expected_params_hash32` is not NULL, the parameters hash is
|
||
|
|
* recomputed from `thresh_pk` and the parameter tuple the target
|
||
|
|
* participant believes, and compared against it. The round-1.2 check
|
||
|
|
* covers helper against helper only; without this check, all helpers
|
||
|
|
* could agree on parameters that differ from the ones the target
|
||
|
|
* expects, or belong to a different group entirely, and the discrepancy
|
||
|
|
* would surface only as an unexplained failure of the public-share
|
||
|
|
* check.
|
||
|
|
* 2. The u values are summed into `secshare32_out`.
|
||
|
|
* 3. If `expected_pubshare` is not NULL, secshare*G is compared against it.
|
||
|
|
*
|
||
|
|
* `expected_pubshare` is load-bearing: it is the only check that a helper
|
||
|
|
* contributed a correct value. Pass NULL only if the resulting share is
|
||
|
|
* validated by other means.
|
||
|
|
*
|
||
|
|
* PRECONDITION, documented but not enforced: `thresh_pk` must come from a
|
||
|
|
* source the target participant authenticates independently of the helpers,
|
||
|
|
* and `expected_pubshare` must be derived from public shares validated
|
||
|
|
* against it with `secp256k1_frost_threshold_info_validate`. Otherwise both
|
||
|
|
* checks are circular: t colluding helpers can present a consistent but
|
||
|
|
* fabricated polynomial, and every check in this function passes on a
|
||
|
|
* worthless share. See frost_enrollment.md and examples/frost_enrollment.c.
|
||
|
|
*
|
||
|
|
* Returns: 0 if the arguments are invalid or a check fails, 1 otherwise
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: secshare32_out: pointer to a 32-byte array for the secret share. Set
|
||
|
|
* to zero if this function returns 0.
|
||
|
|
* In: sigmas32: pointer to an array of u*32 bytes, aligned with
|
||
|
|
* `ids`, holding the value received from each helper
|
||
|
|
* thresh_pk: pointer to the INDEPENDENTLY AUTHENTICATED threshold
|
||
|
|
* public key of the group
|
||
|
|
* ids: array of the u helper identifiers, in the same order
|
||
|
|
* as `sigmas32`
|
||
|
|
* n_ids: number of helpers u
|
||
|
|
* new_id: own identifier, the one the share is being derived
|
||
|
|
* for
|
||
|
|
* n_participants: total number of participants n
|
||
|
|
* threshold: threshold t
|
||
|
|
* expected_params_hash32: pointer to the 32-byte parameters hash received
|
||
|
|
* from the helpers, or NULL to skip the comparison
|
||
|
|
* expected_pubshare: pointer to the expected public share, from
|
||
|
|
* `secp256k1_frost_enrollment_pubshare_derive`, or NULL
|
||
|
|
* to skip the verification (not recommended)
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_secshare_gen(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *secshare32_out,
|
||
|
|
const unsigned char *sigmas32,
|
||
|
|
const secp256k1_pubkey *thresh_pk,
|
||
|
|
const uint32_t *ids,
|
||
|
|
size_t n_ids,
|
||
|
|
uint32_t new_id,
|
||
|
|
size_t n_participants,
|
||
|
|
uint32_t threshold,
|
||
|
|
const unsigned char *expected_params_hash32,
|
||
|
|
const secp256k1_pubkey *expected_pubshare
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#endif /* SECP256K1_FROST_ENROLLMENT_H */
|