#ifndef SECP256K1_FROST_ENROLLMENT_H #define SECP256K1_FROST_ENROLLMENT_H #include "secp256k1.h" #include "secp256k1_frost.h" #ifdef __cplusplus extern "C" { #endif #include #include /** 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. * * This is also the natural place to pre-validate a parameter tuple: it * enforces exactly the same constraints as the four functions below, and * nothing else. Note that an unusable `thresh_pk` object is API MISUSE, not * an invalid parameter: like every other entry point in the library, this * function reports it through the illegal-argument callback rather than by * returning 0. * * Returns: 0 if the parameters are invalid, 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, smaller than n_participants and different * from new_id; the order is irrelevant. * n_ids: number of helpers u. Must be between threshold and * n_participants. * 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_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`. Not written at all when * the parameter tuple is rejected, since its extent is * only known to be u*32 once n_ids has been validated * against the rest of the tuple; zeroed on every * failure detected after that point. * 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. It then sums the shares. * * `mismatch_id` reports fault attribution for BOTH ways a specific helper's * contribution can be at fault: * * - its parameters hash disagrees with the recomputed one, meaning that * helper ran round 1.1 on a different parameter tuple or in a different * group; * - its entry in `all_shares32` is not a valid scalar (it is not smaller * than the group order), meaning the value was corrupted in transit or * fabricated. * * In both cases the function returns 0 and, if `mismatch_id` is not NULL, * stores the IDENTIFIER of the responsible helper there -- not an array * index, which would be ambiguous because identifiers need not be 0..u-1. * The two causes are not distinguished, so a caller should not report one of * them specifically. Note that the second cause can name the CALLER'S OWN * identifier, since the share kept locally is summed along with the rest. * * `mismatch_id` is set to UINT32_MAX when the failure has neither cause, * which covers every invalid-parameter and API-misuse case. * * 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). When it * is non-NULL, ctx must have been initialized for * signing; when it is NULL, no context capability * beyond the default is required. */ 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 */