749 lines
38 KiB
C
749 lines
38 KiB
C
|
|
#ifndef SECP256K1_ICEBERG_H
|
||
|
|
#define SECP256K1_ICEBERG_H
|
||
|
|
|
||
|
|
#include "secp256k1_musig.h"
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
extern "C" {
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include <stddef.h>
|
||
|
|
|
||
|
|
/** This module implements Iceberg, a threshold scheme that lets a group of
|
||
|
|
* parties stand in for a single MuSig2 (BIP 327) participant.
|
||
|
|
*
|
||
|
|
* WARNING: EXPERIMENTAL. Neither the scheme nor this implementation has been
|
||
|
|
* reviewed by anyone outside the project, and should not be used to protect
|
||
|
|
* anything of value.
|
||
|
|
*
|
||
|
|
* The group produces one ordinary MuSig2 public nonce and one ordinary MuSig2
|
||
|
|
* partial signature, so cosigners cannot tell a group is involved and need no
|
||
|
|
* changes.
|
||
|
|
*
|
||
|
|
* The order of calls, once per group and then once per signature:
|
||
|
|
*
|
||
|
|
* setup a distributed key generation produces one share each, then
|
||
|
|
* pubshare_gen and pubkey_agg give the group's public key,
|
||
|
|
* which goes into musig_pubkey_agg beside the cosigners'
|
||
|
|
* round 1 each member of the quorum runs nonce_gen; nonce_agg turns
|
||
|
|
* those into one ordinary musig_pubnonce, and from there
|
||
|
|
* upwards this is plain MuSig2
|
||
|
|
* round 2 each member runs partial_sign, which takes the message, the
|
||
|
|
* cosigners' nonce and the round-one contributions;
|
||
|
|
* partial_sig_agg turns the results into one ordinary
|
||
|
|
* musig_partial_sig
|
||
|
|
*
|
||
|
|
* You can find an example demonstrating the iceberg module in
|
||
|
|
* examples/iceberg.c. Further notes on API usage can be found in
|
||
|
|
* doc/iceberg.md.
|
||
|
|
*
|
||
|
|
* Nonces are derived from a session label, sid32 below, rather than stored,
|
||
|
|
* so no signer holds a secret nonce between the two rounds. A label is a
|
||
|
|
* 32-byte value the caller chooses, one per signing attempt; it is public and
|
||
|
|
* it need not be random, but it must never be used twice. That is narrower
|
||
|
|
* than keeping no state at all: a member still has to remember which labels it
|
||
|
|
* has already answered under. Choosing labels is the one thing this API cannot
|
||
|
|
* do for you, so read the note on secp256k1_iceberg_partial_sign before
|
||
|
|
* writing any of this.
|
||
|
|
*
|
||
|
|
* Three things constrain how this can be used:
|
||
|
|
*
|
||
|
|
* 1. Both rounds need a quorum of 2t-1 members online, of whom up to t-1 may
|
||
|
|
* be corrupt. Since 2t-1 members have to exist, the threshold is at most
|
||
|
|
* half the group rounded up: 2-of-3 and 3-of-5 can be expressed where
|
||
|
|
* 2-of-2 and 3-of-4 cannot. Constraint 3 then rules out 2-of-3 and 3-of-5
|
||
|
|
* as well, leaving 2-of-4 as the smallest usable group.
|
||
|
|
*
|
||
|
|
* 2. Messages between participants must travel over authenticated channels, and
|
||
|
|
* a contribution must be accepted only from the member its index names.
|
||
|
|
*
|
||
|
|
* Every check below works by outnumbering the at most t-1 corrupt members
|
||
|
|
* with t honest ones, so that count has to be real, and this library cannot
|
||
|
|
* take it for you. It can tell that an index names a member of the group and
|
||
|
|
* that no two contributions claim the same one. It cannot tell who sent
|
||
|
|
* anything, and no check on the values could: the values of both rounds lie
|
||
|
|
* on a polynomial of degree t-1, so once t of them are public, anyone can
|
||
|
|
* compute the rest. A contribution that verifies proves the value is right,
|
||
|
|
* which is a public property, not that the named member produced it.
|
||
|
|
*
|
||
|
|
* What that costs if it is skipped: an adversary supplying t of the 2t-1
|
||
|
|
* chooses the group's nonce. It picks a polynomial through the t-1 honest
|
||
|
|
* points it has to match, which leaves a coefficient free, and computes its
|
||
|
|
* own points on that. Every check here then passes.
|
||
|
|
*
|
||
|
|
* 3. The group must agree what it is signing before anyone answers, and
|
||
|
|
* agreeing while up to t-1 members misbehave needs n >= 3t-2 members to
|
||
|
|
* exist: 4 for a threshold of 2, 7 for 3, 10 for 4. Nothing here checks
|
||
|
|
* that, because it is a property of the group rather than of the
|
||
|
|
* arithmetic. It is still the bound to size a deployment with.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/** Maximum number of participants in a group.
|
||
|
|
*
|
||
|
|
* A participant holds one 32-byte seed per (t-1)-subset it is not a member of,
|
||
|
|
* so both storage and signing cost grow as C(n-1, t-1): 2 seeds at 2-of-3, 6 at
|
||
|
|
* 3-of-5, 126 at 5-of-10.
|
||
|
|
*
|
||
|
|
* It sizes two of the opaque types below, so it is part of this library's ABI
|
||
|
|
* and not something one member of a group can change on its own.
|
||
|
|
*/
|
||
|
|
#define SECP256K1_ICEBERG_MAX_PARTICIPANTS 10
|
||
|
|
|
||
|
|
/** Largest number of seeds one participant can hold, C(9, 4): the maximum of
|
||
|
|
* C(n-1, t-1) over every expressible configuration, since a quorum of 2t-1 has
|
||
|
|
* to fit in the group and so t is at most (n+1)/2.
|
||
|
|
*
|
||
|
|
* This is the number that makes a group expensive: it sizes two of the opaque
|
||
|
|
* types below, and the module's largest stack frames with them.
|
||
|
|
*/
|
||
|
|
#define SECP256K1_ICEBERG_MAX_SEEDS 126
|
||
|
|
|
||
|
|
/** Largest buffer secp256k1_iceberg_share_serialize can need. A share of a
|
||
|
|
* particular group serializes to 4 + 32*C(n-1, t-1) bytes, which is smaller
|
||
|
|
* for every configuration except the largest; use this to size a fixed buffer
|
||
|
|
* without computing binomials. */
|
||
|
|
#define SECP256K1_ICEBERG_SHARE_MAX_LEN (4 + 32 * SECP256K1_ICEBERG_MAX_SEEDS)
|
||
|
|
|
||
|
|
/** Opaque data structures
|
||
|
|
*
|
||
|
|
* The layout inside these is implementation defined and not portable between
|
||
|
|
* platforms or versions. Use the serialization and parsing functions to move
|
||
|
|
* them between machines or to storage. They contain no pointers, so they can
|
||
|
|
* be copied and moved. But a copy of a share is another copy of the secret,
|
||
|
|
* and erasing one means erasing all of them.
|
||
|
|
*
|
||
|
|
* A stray pointer or an uninitialized struct is caught rather than used.
|
||
|
|
*
|
||
|
|
* Every participant has an index in 1..n, assigned when the group is dealt. It
|
||
|
|
* travels inside the share and inside every object derived from one, so the
|
||
|
|
* aggregation functions can tell contributions apart, and a contribution
|
||
|
|
* separated from its index is not usable.
|
||
|
|
*
|
||
|
|
* The index is a claim the object makes about itself. This library checks only
|
||
|
|
* that it names a member the group has: everything above n is refused, and
|
||
|
|
* within 1..n nothing here tells a genuine contribution from one an adversary
|
||
|
|
* wrote. Binding it to a member is constraint 2 above.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/** Opaque data structure holding a participant's long-term secret share.
|
||
|
|
*
|
||
|
|
* This is the material to guard and to back up, and it is not one number: it
|
||
|
|
* is a collection of 32-byte seeds, one for every group of t-1 participants
|
||
|
|
* that this participant is NOT a member of.
|
||
|
|
*
|
||
|
|
* There is no resharing here, so a compromise or a change of membership means
|
||
|
|
* dealing the group again from scratch.
|
||
|
|
*
|
||
|
|
* Guaranteed to be 4040 bytes in size: sized for the worst case the
|
||
|
|
* compile-time maximum allows, whatever n and t you actually use. Serializing
|
||
|
|
* writes only the seeds in use.
|
||
|
|
*/
|
||
|
|
typedef struct secp256k1_iceberg_share {
|
||
|
|
unsigned char data[8 + 32 * SECP256K1_ICEBERG_MAX_SEEDS];
|
||
|
|
} secp256k1_iceberg_share;
|
||
|
|
|
||
|
|
/** Opaque data structure holding the Lagrange weights derived from a share.
|
||
|
|
*
|
||
|
|
* Despite traveling with the share, this contains NO secret material. The
|
||
|
|
* weights depend only on participant indices and subset structure, both
|
||
|
|
* public, so nothing here needs protecting. It exists purely so that signing
|
||
|
|
* does not repeat the work, and it can be discarded and rebuilt at any time --
|
||
|
|
* which is the only way to move one, since it has no serialized form.
|
||
|
|
*/
|
||
|
|
typedef struct secp256k1_iceberg_share_cache {
|
||
|
|
unsigned char data[8 + 32 * SECP256K1_ICEBERG_MAX_SEEDS];
|
||
|
|
} secp256k1_iceberg_share_cache;
|
||
|
|
|
||
|
|
/** Opaque data structure holding one participant's public key share.
|
||
|
|
*
|
||
|
|
* A commitment to the participant's key share: the point you get by
|
||
|
|
* multiplying that share by the generator, plus the index it belongs to. It
|
||
|
|
* reveals nothing about the share and is meant to be published. This is what
|
||
|
|
* the group aggregates into its public key, and what lets everyone check that
|
||
|
|
* nobody published a wrong one.
|
||
|
|
*
|
||
|
|
* Guaranteed to be 69 bytes in size. Serializes to 34 bytes.
|
||
|
|
*/
|
||
|
|
typedef struct secp256k1_iceberg_pubshare {
|
||
|
|
unsigned char data[69];
|
||
|
|
} secp256k1_iceberg_pubshare;
|
||
|
|
|
||
|
|
/** Derive the Lagrange weights for a share.
|
||
|
|
*
|
||
|
|
* An optimization, not a requirement. Three calls need these weights --
|
||
|
|
* secp256k1_iceberg_pubshare_gen, secp256k1_iceberg_nonce_gen and
|
||
|
|
* secp256k1_iceberg_partial_sign, and each accepts NULL and recomputes them,
|
||
|
|
* at the cost of one scalar inversion and multiplications growing as
|
||
|
|
* C(n-1, t-1). Build one if a participant signs often enough to care.
|
||
|
|
*
|
||
|
|
* The weights depend on n, t and the participant index and on nothing else, so
|
||
|
|
* a cache is checked against those three rather than against the seeds. One
|
||
|
|
* built for a different participant, threshold or group size calls the illegal
|
||
|
|
* callback. One built for a different group of the same shape is accepted: it
|
||
|
|
* holds the same weights the share would have derived.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success. A malformed share calls the illegal callback.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: cache: pointer to a cache object to initialize
|
||
|
|
* In: share: the share to derive weights for
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_share_cache_create(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_share_cache *cache,
|
||
|
|
const secp256k1_iceberg_share *share
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
/** Serialize a share. Writes 4 + 32*C(n-1, t-1) bytes.
|
||
|
|
*
|
||
|
|
* The encoding is a version byte, then n, then t, then this participant's
|
||
|
|
* index, then the seeds. Only the seeds are secret. The three before them are
|
||
|
|
* what makes the seeds interpretable (their number is C(n-1, t-1), and which
|
||
|
|
* subset each belongs to follows from n, t and the index), so a caller that
|
||
|
|
* needs any of the three reads it here, off its own buffer.
|
||
|
|
*
|
||
|
|
* On entry outlen is the size of the buffer; on return it is the number of
|
||
|
|
* bytes this share needs, whether or not they fitted. So a short buffer costs
|
||
|
|
* a second call rather than a guess, or size it at
|
||
|
|
* SECP256K1_ICEBERG_SHARE_MAX_LEN and never ask. A malformed share has no
|
||
|
|
* length to report and sets outlen to zero, so a caller looping on it stops
|
||
|
|
* rather than asking for the same buffer forever.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if the buffer is too small. A malformed share
|
||
|
|
* calls the illegal callback.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: out: buffer to write to
|
||
|
|
* In/Out: outlen: buffer size in, bytes needed out
|
||
|
|
* In: share: the share to serialize
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_share_serialize(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *out,
|
||
|
|
size_t *outlen,
|
||
|
|
const secp256k1_iceberg_share *share
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||
|
|
|
||
|
|
/** Parse a share.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if the input is malformed.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: share: the share to initialize
|
||
|
|
* In: in: serialized share
|
||
|
|
* inlen: its length
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_share_parse(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_share *share,
|
||
|
|
const unsigned char *in,
|
||
|
|
size_t inlen
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
/** Compute this participant's public key share.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success. A malformed share calls the illegal callback.
|
||
|
|
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||
|
|
* Out: pubshare: the public key share
|
||
|
|
* In: share: this participant's share
|
||
|
|
* cache: its Lagrange weights, or NULL to recompute them
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubshare_gen(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_pubshare *pubshare,
|
||
|
|
const secp256k1_iceberg_share *share,
|
||
|
|
const secp256k1_iceberg_share_cache *cache
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
/** Serialize a public key share to 34 bytes.
|
||
|
|
*
|
||
|
|
* Returns: 1, or 0 on a malformed public share, which is a caller error and
|
||
|
|
* also calls the illegal callback.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: out34: pointer to a 34-byte array to write to
|
||
|
|
* In: pubshare: the public key share to serialize
|
||
|
|
*/
|
||
|
|
SECP256K1_API int secp256k1_iceberg_pubshare_serialize(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *out34,
|
||
|
|
const secp256k1_iceberg_pubshare *pubshare
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
/** Parse a public key share from 34 bytes.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if the input does not encode a valid share.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: pubshare: the public key share to initialize
|
||
|
|
* In: in34: pointer to a 34-byte serialized share
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubshare_parse(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_pubshare *pubshare,
|
||
|
|
const unsigned char *in34
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
/** Verify public key shares and combine them into the group public key.
|
||
|
|
*
|
||
|
|
* Checks that the shares lie on a polynomial of degree at most t-1 and then
|
||
|
|
* interpolates them. A participant who published anything else raises the
|
||
|
|
* degree and is caught here, though the check proves only that the set is
|
||
|
|
* inconsistent, not which member is at fault.
|
||
|
|
*
|
||
|
|
* Soundness needs at least t honest points present to pin the true polynomial,
|
||
|
|
* which is where the quorum of 2t-1 comes from: with at most t-1 members
|
||
|
|
* corrupt, 2t-1 contributions leave t honest ones, and t points fix a
|
||
|
|
* polynomial of degree t-1. Those t honest points have to be real: this call
|
||
|
|
* refuses an index above n, and constraint 2 is the caller's half.
|
||
|
|
*
|
||
|
|
* The result is an ordinary public key. Pass it to secp256k1_musig_pubkey_agg
|
||
|
|
* alongside the cosigners' keys exactly as if it belonged to a single signer.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if there are fewer than 2t-1 shares or more than
|
||
|
|
* the group size, if the shares are inconsistent, if two carry the
|
||
|
|
* same index, or in the vanishingly unlikely case that they combine
|
||
|
|
* to the point at infinity.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: group_pk: the group's public key
|
||
|
|
* In: pubshares: array of pointers to public key shares
|
||
|
|
* n_pubshares: how many
|
||
|
|
* n: the group size the shares were dealt for. A share
|
||
|
|
* carrying an index above it is rejected: no member sits
|
||
|
|
* there, so nothing could have authenticated it
|
||
|
|
* t: the threshold, at least 1 and at most (n+1)/2. An n or
|
||
|
|
* a t outside its range is a caller bug and calls the
|
||
|
|
* illegal callback
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubkey_agg(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_pubkey *group_pk,
|
||
|
|
const secp256k1_iceberg_pubshare * const *pubshares,
|
||
|
|
size_t n_pubshares,
|
||
|
|
unsigned int n,
|
||
|
|
unsigned int t
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
/** Opaque data structure holding one participant's nonce contribution.
|
||
|
|
*
|
||
|
|
* Guaranteed to be 133 bytes in size. Serialized and parsed with
|
||
|
|
* secp256k1_iceberg_pubnonce_serialize and secp256k1_iceberg_pubnonce_parse.
|
||
|
|
*/
|
||
|
|
typedef struct secp256k1_iceberg_pubnonce {
|
||
|
|
unsigned char data[133];
|
||
|
|
} secp256k1_iceberg_pubnonce;
|
||
|
|
|
||
|
|
/** Opaque data structure holding the group's combined nonce.
|
||
|
|
*
|
||
|
|
* This is the group's internal nonce pair (R1, R2'), before the nesting
|
||
|
|
* coefficient is applied. What goes up to the cosigners is an ordinary
|
||
|
|
* secp256k1_musig_pubnonce instead.
|
||
|
|
*
|
||
|
|
* It is here for an implementation checking its own arithmetic, which needs the
|
||
|
|
* first 66 bytes of the b1 preimage and can get them nowhere else: the published
|
||
|
|
* pair has already had b1 applied and does not yield them back. The coefficient
|
||
|
|
* is
|
||
|
|
* b1 = H_Iceberg/noncecoef(R1 || R2' || P), where P is the group's public key
|
||
|
|
* and each point uses the 33-byte encoding MuSig2 uses for aggregate nonces.
|
||
|
|
* The published pair is (R1, b1*R2'): only the second point is scaled, because
|
||
|
|
* the coefficient enters as b1^(i-1).
|
||
|
|
*
|
||
|
|
* No call here takes one as input, so the serializer below is the only one
|
||
|
|
* that reads it. It is also the only object you can decline to receive:
|
||
|
|
* secp256k1_iceberg_nonce_agg writes it if you want it and skips it if you
|
||
|
|
* pass NULL.
|
||
|
|
*
|
||
|
|
* Guaranteed to be 132 bytes in size. Serialized and parsed with
|
||
|
|
* secp256k1_iceberg_aggnonce_serialize and secp256k1_iceberg_aggnonce_parse.
|
||
|
|
*/
|
||
|
|
typedef struct secp256k1_iceberg_aggnonce {
|
||
|
|
unsigned char data[132];
|
||
|
|
} secp256k1_iceberg_aggnonce;
|
||
|
|
|
||
|
|
/** Opaque data structure holding one participant's signature share.
|
||
|
|
*
|
||
|
|
* Guaranteed to be 37 bytes in size. Serialized and parsed with
|
||
|
|
* secp256k1_iceberg_partial_sig_serialize and
|
||
|
|
* secp256k1_iceberg_partial_sig_parse.
|
||
|
|
*/
|
||
|
|
typedef struct secp256k1_iceberg_partial_sig {
|
||
|
|
unsigned char data[37];
|
||
|
|
} secp256k1_iceberg_partial_sig;
|
||
|
|
|
||
|
|
/** Serialize and parse the round-one and round-two objects.
|
||
|
|
*
|
||
|
|
* pubnonce 67 bytes a member's nonce contribution, index and two points
|
||
|
|
* aggnonce 66 bytes the group's internal aggregate, no index
|
||
|
|
* partial_sig 33 bytes a member's signature share, index and one scalar
|
||
|
|
*
|
||
|
|
* A contribution and a signature share carry their participant index because
|
||
|
|
* everything that consumes them is indexed; an aggregate belongs to the group
|
||
|
|
* rather than to a member and so has none, which is the whole of the 67-versus-
|
||
|
|
* 66 difference. Points use the 33-byte encoding MuSig2 uses for aggregate
|
||
|
|
* nonces, all zero for the point at infinity. The encoding has room for it,
|
||
|
|
* though secp256k1_iceberg_nonce_agg refuses to emit one.
|
||
|
|
*
|
||
|
|
* Parsing rejects an index outside 1..SECP256K1_ICEBERG_MAX_PARTICIPANTS, a
|
||
|
|
* point that is not on the curve, and a signature share that is not a scalar
|
||
|
|
* below the group order. The parsers do not know the group size, so an index
|
||
|
|
* of 9 parses in a group of 5 and is caught when something that does know n
|
||
|
|
* tries to use it. None of these is a security boundary on its own, since a
|
||
|
|
* well-formed lie passes all of them, but each turns a value that would fail
|
||
|
|
* later into one that fails here.
|
||
|
|
*
|
||
|
|
* Returns: for a parser, 1 on success and 0 if the encoding is invalid, which
|
||
|
|
* is an ordinary thing for bytes off a network to be. For a
|
||
|
|
* serializer, 1, or 0 on a malformed object, which is a caller error
|
||
|
|
* and also calls the illegal callback.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out/In: the object and the fixed-size buffer named in each declaration
|
||
|
|
*/
|
||
|
|
SECP256K1_API int secp256k1_iceberg_pubnonce_serialize(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *out67,
|
||
|
|
const secp256k1_iceberg_pubnonce *pubnonce
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubnonce_parse(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_pubnonce *pubnonce,
|
||
|
|
const unsigned char *in67
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
SECP256K1_API int secp256k1_iceberg_aggnonce_serialize(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *out66,
|
||
|
|
const secp256k1_iceberg_aggnonce *aggnonce
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_aggnonce_parse(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_aggnonce *aggnonce,
|
||
|
|
const unsigned char *in66
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
SECP256K1_API int secp256k1_iceberg_partial_sig_serialize(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
unsigned char *out33,
|
||
|
|
const secp256k1_iceberg_partial_sig *partial_sig
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sig_parse(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_partial_sig *partial_sig,
|
||
|
|
const unsigned char *in33
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
/** Derive this participant's nonce contribution for a session.
|
||
|
|
*
|
||
|
|
* Depends on the share and the session label and nothing else: no message, no
|
||
|
|
* cosigner nonce, no key aggregation cache. That is what lets this round run
|
||
|
|
* before the message exists, which is the property the whole scheme is built
|
||
|
|
* around, and it is why a member absent from this round is not shut out of the
|
||
|
|
* next one: its contribution is determined, so it can work out what it would
|
||
|
|
* have been. There is no secret nonce object to hold between the rounds --
|
||
|
|
* round two recomputes the value from the share and the same label.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success. A malformed share calls the illegal callback.
|
||
|
|
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||
|
|
* Out: pubnonce: this participant's contribution, to publish
|
||
|
|
* In: share: this participant's share
|
||
|
|
* cache: its Lagrange weights, or NULL to recompute them
|
||
|
|
* sid32: the session label
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_nonce_gen(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_pubnonce *pubnonce,
|
||
|
|
const secp256k1_iceberg_share *share,
|
||
|
|
const secp256k1_iceberg_share_cache *cache,
|
||
|
|
const unsigned char *sid32
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||
|
|
SECP256K1_ARG_NONNULL(5);
|
||
|
|
|
||
|
|
/** Verify nonce contributions and combine them.
|
||
|
|
*
|
||
|
|
* Checks both nonce sharings the way key aggregation checks the key sharing,
|
||
|
|
* and for the same reason, then interpolates them into one ordinary MuSig2
|
||
|
|
* public nonce. From that nonce upwards this is plain MuSig2.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if there are fewer than 2t-1 contributions or more
|
||
|
|
* than the group size, if they are inconsistent, if two carry the
|
||
|
|
* same index, or if the group's nonce comes out at infinity.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: musig_pubnonce: the group's nonce, to publish to the cosigners
|
||
|
|
* aggnonce: the group's internal nonce, or NULL if you do not
|
||
|
|
* want it. Nothing in this API consumes one
|
||
|
|
* In: pubnonces: the contributions
|
||
|
|
* n_pubnonces: how many
|
||
|
|
* n: the group size. A contribution carrying an index
|
||
|
|
* above it is rejected
|
||
|
|
* t: the threshold, at least 1 and at most (n+1)/2. An n
|
||
|
|
* or a t outside its range is a caller bug and calls
|
||
|
|
* the illegal callback
|
||
|
|
* group_pk: the group's public key. It feeds the nesting
|
||
|
|
* coefficient, so round two must be given the same one
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_nonce_agg(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_musig_pubnonce *musig_pubnonce,
|
||
|
|
secp256k1_iceberg_aggnonce *aggnonce,
|
||
|
|
const secp256k1_iceberg_pubnonce * const *pubnonces,
|
||
|
|
size_t n_pubnonces,
|
||
|
|
unsigned int n,
|
||
|
|
unsigned int t,
|
||
|
|
const secp256k1_pubkey *group_pk
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2)
|
||
|
|
SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(8);
|
||
|
|
|
||
|
|
/** Does this key aggregation cache aggregate this list, and is the group in it?
|
||
|
|
*
|
||
|
|
* Run this once, where the cache is built, and not once per session: the key
|
||
|
|
* set belongs to the channel while a session belongs to a single signing
|
||
|
|
* attempt.
|
||
|
|
*
|
||
|
|
* secp256k1_iceberg_partial_sign takes the group's public key and the outer
|
||
|
|
* cache as two separate arguments and nothing ties them together, because
|
||
|
|
* nothing can: a cache records the hash of the key list, not the list, so
|
||
|
|
* membership is not a question it can answer. Passing a cache built over some
|
||
|
|
* other set of cosigners produces a well-formed coefficient for a key that is
|
||
|
|
* not in the aggregation, a signature share that is useless, and a spent session
|
||
|
|
* label, which is the part that matters. Retrying under that label with
|
||
|
|
* the right cache is then the attack, self-inflicted.
|
||
|
|
*
|
||
|
|
* MuSig2's own partial_sign asks for no key list, because there a wrong cache
|
||
|
|
* costs a nonce rather than a label.
|
||
|
|
*
|
||
|
|
* Tweaks are fine. The list hash is fixed when the keys are aggregated and no
|
||
|
|
* tweak touches it, so a cache carrying taproot tweaks still passes.
|
||
|
|
*
|
||
|
|
* Returns: 1 if keyagg_cache aggregates exactly this list of public keys, in
|
||
|
|
* this order, and group_pk is one of them. 0 if it aggregates some
|
||
|
|
* other list, or if group_pk is not in it. A malformed cache or public
|
||
|
|
* key is a caller bug rather than an answer, and calls the illegal
|
||
|
|
* callback as it does everywhere else in this module: neither has a
|
||
|
|
* wire format, so neither can have arrived from a peer
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* In: keyagg_cache: the outer MuSig2 key aggregation cache to check
|
||
|
|
* pubkeys: the keys it should have been built from, in the order
|
||
|
|
* they were passed to secp256k1_musig_pubkey_agg
|
||
|
|
* n_pubkeys: how many, at least 1
|
||
|
|
* group_pk: the group's public key, which must be one of them
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_keyagg_check(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||
|
|
const secp256k1_pubkey * const *pubkeys,
|
||
|
|
size_t n_pubkeys,
|
||
|
|
const secp256k1_pubkey *group_pk
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||
|
|
SECP256K1_ARG_NONNULL(5);
|
||
|
|
|
||
|
|
/** Produce this participant's signature share.
|
||
|
|
*
|
||
|
|
* A participant makes its own three secrets alone: both nonce scalars and its
|
||
|
|
* key share follow from its share and the label, with nobody else involved. What
|
||
|
|
* it cannot make alone is the group's aggregate nonce, which is a value at zero
|
||
|
|
* and so takes t points, and that aggregate is hashed into two of the
|
||
|
|
* coefficients those secrets get multiplied by. Hence the round-one
|
||
|
|
* contributions in the argument list below.
|
||
|
|
*
|
||
|
|
* It takes the message and the cosigners' nonce for the same reason, and
|
||
|
|
* recomputes the challenge from them. It also adds the group's own nonce
|
||
|
|
* internally, so "the aggregate nonce includes my contribution" holds by
|
||
|
|
* construction rather than by assumption.
|
||
|
|
*
|
||
|
|
* The rule this function cannot enforce for you: never call it twice with the
|
||
|
|
* same sid32. Not "never with the same message": never twice, whatever else you
|
||
|
|
* change. A participant's three secrets are fixed by the label alone and
|
||
|
|
* everything else in the signing equation is a coefficient over public values,
|
||
|
|
* so two answers under one label are two equations in three unknowns and three
|
||
|
|
* are three, at which point the key share falls out by elimination.
|
||
|
|
*
|
||
|
|
* Half of that is yours: a member can refuse to answer twice if it remembers
|
||
|
|
* what it has answered under, which means durable storage that is never rolled
|
||
|
|
* back, because this library holds nothing between calls. Recording the label
|
||
|
|
* is enough, and it is all that is enough: the label is one signing attempt,
|
||
|
|
* so a retry is a new label rather than a second answer under the old one. The
|
||
|
|
* other half is the group's: two members must not answer one label on different
|
||
|
|
* messages, which no member can detect, since what the others were shown is not
|
||
|
|
* something the protocol tells it. That is constraint 3 at the top of this file.
|
||
|
|
* Where the label is a commitment number and an attempt counter, both halves
|
||
|
|
* come from machinery the surrounding protocol runs anyway, and
|
||
|
|
* examples/iceberg.c shows the member's half.
|
||
|
|
*
|
||
|
|
* What it does check: it builds the group's aggregate from the contributions
|
||
|
|
* rather than accepting one, because the nesting coefficient is a hash of that
|
||
|
|
* aggregate and a coordinator free to invent it would hold a coefficient it
|
||
|
|
* could vary at will. It then interpolates the set and compares the result at
|
||
|
|
* this participant's own index against the contribution it derives locally,
|
||
|
|
* which ties the set to the label being signed under. Otherwise a consistent
|
||
|
|
* sharing from a different session would pass. It also counts the signer among
|
||
|
|
* the honest points of constraint 2: a set assembled by somebody else has to
|
||
|
|
* agree with this participant's own share as well as with the rest. The
|
||
|
|
* participant need not be one of the contributors, since it holds the share
|
||
|
|
* that determines what its own contribution would have been either way.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if there are fewer than 2t-1 contributions or more
|
||
|
|
* than the group size, if two carry the same index, or if the set is
|
||
|
|
* inconsistent or belongs to another session. The group size is not an
|
||
|
|
* argument here: it comes off the caller's own share. A malformed
|
||
|
|
* share, or a share cache built for a different participant, calls the
|
||
|
|
* illegal callback
|
||
|
|
* Args: ctx: pointer to a context object (not
|
||
|
|
* secp256k1_context_static)
|
||
|
|
* Out: partial_sig: the signature share to publish
|
||
|
|
* In: share: this participant's share
|
||
|
|
* cache: its Lagrange weights, or NULL to recompute them
|
||
|
|
* sid32: the session label, the same one round one used
|
||
|
|
* pubnonces: the group's own round-one contributions, one per
|
||
|
|
* member. Not the cosigners', which arrive already
|
||
|
|
* aggregated as cosigner_aggnonce below. Pass every valid
|
||
|
|
* one you have; any qualifying set gives the same result,
|
||
|
|
* so members need not agree on which
|
||
|
|
* n_pubnonces: how many, at least 2t-1 and at most the group size
|
||
|
|
* group_pk: the group's public key, the one round one was given
|
||
|
|
* keyagg_cache: the outer MuSig2 key aggregation cache
|
||
|
|
* msg32: the message being signed
|
||
|
|
* cosigner_aggnonce: the cosigners' aggregate nonce, theirs alone. Not the
|
||
|
|
* other group members', and not including the group's
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sign(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_iceberg_partial_sig *partial_sig,
|
||
|
|
const secp256k1_iceberg_share *share,
|
||
|
|
const secp256k1_iceberg_share_cache *cache,
|
||
|
|
const unsigned char *sid32,
|
||
|
|
const secp256k1_iceberg_pubnonce * const *pubnonces,
|
||
|
|
size_t n_pubnonces,
|
||
|
|
const secp256k1_pubkey *group_pk,
|
||
|
|
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||
|
|
const unsigned char *msg32,
|
||
|
|
const secp256k1_musig_aggnonce *cosigner_aggnonce
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||
|
|
SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8)
|
||
|
|
SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11);
|
||
|
|
|
||
|
|
/** Check one signature share against what its author published.
|
||
|
|
*
|
||
|
|
* Verifies s_k*G == +-(R1,k + b0*b1*R2,k) + e*a*g*gacc*D_k, the equation
|
||
|
|
* secp256k1_iceberg_partial_sign solves. Without this,
|
||
|
|
* secp256k1_iceberg_partial_sig_agg turns one bad share into a signature that
|
||
|
|
* fails to verify with nothing to say why.
|
||
|
|
*
|
||
|
|
* The participant's nonce is read off the contributions rather than passed in.
|
||
|
|
* They determine one polynomial per nonce, and its value at that index is what
|
||
|
|
* the participant's contribution had to be, so this also works for a member
|
||
|
|
* that sat out round one and published no contribution of its own.
|
||
|
|
*
|
||
|
|
* What a 0 means, and what it does not. It means this share does not satisfy
|
||
|
|
* that equation against this pubnonce and this public share. It does not name a
|
||
|
|
* culprit. A MuSig2 partial signature is forgeable by anyone who knows the
|
||
|
|
* session's public values, so a share that fails here may have been written by
|
||
|
|
* somebody other than the participant it is attributed to. Assigning blame is a
|
||
|
|
* stronger claim and needs an honest-supermajority quorum: 3t-2 members
|
||
|
|
* online at once, rather than the 2t-1 this scheme otherwise requires. Use this
|
||
|
|
* to find out that a signing attempt will fail before spending an aggregation
|
||
|
|
* on it, and to narrow where to look. Do not use it as evidence against a
|
||
|
|
* member.
|
||
|
|
*
|
||
|
|
* A 0 also does not distinguish a bad share from bad inputs. The same answer
|
||
|
|
* comes back if the message or the cosigners' aggregate nonce differs from the
|
||
|
|
* one the signer had. The set of contributions is the exception: any qualifying
|
||
|
|
* set from the session determines the same polynomial, so it need not be the
|
||
|
|
* set the signer used.
|
||
|
|
*
|
||
|
|
* Every other argument must be the one secp256k1_iceberg_partial_sign was
|
||
|
|
* given. The public share is what names the participant, and a signature share
|
||
|
|
* carrying a different index is refused rather than verified against its own,
|
||
|
|
* which catches the two arguments being drawn from different members.
|
||
|
|
*
|
||
|
|
* Returns: 1 if the share satisfies the equation, 0 otherwise, including a
|
||
|
|
* contribution count outside 2t-1..n, and including a malformed
|
||
|
|
* partial_sig, which is the object being examined. A malformed
|
||
|
|
* pubshare, key aggregation cache, group key or cosigner aggregate is
|
||
|
|
* the caller's own and calls the illegal callback
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* In: partial_sig: the signature share to check
|
||
|
|
* pubshare: the public share of the participant it is
|
||
|
|
* attributed to, which is what says which one that is
|
||
|
|
* pubnonces: a qualifying set from the same session. It need not
|
||
|
|
* be the set the signer used: any consistent 2t-1
|
||
|
|
* determine the same polynomial
|
||
|
|
* n_pubnonces: how many, at least 2t-1 and at most n
|
||
|
|
* n: the group size. A contribution carrying an index
|
||
|
|
* above it is rejected
|
||
|
|
* t: the threshold, at least 1 and at most (n+1)/2. An
|
||
|
|
* n or a t outside its range is a caller bug and
|
||
|
|
* calls the illegal callback
|
||
|
|
* group_pk: the group's aggregate public key
|
||
|
|
* keyagg_cache: the MuSig2 cache for the outer session
|
||
|
|
* msg32: the message being signed
|
||
|
|
* cosigner_aggnonce: the cosigners' aggregate nonce
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sig_verify(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
const secp256k1_iceberg_partial_sig *partial_sig,
|
||
|
|
const secp256k1_iceberg_pubshare *pubshare,
|
||
|
|
const secp256k1_iceberg_pubnonce * const *pubnonces,
|
||
|
|
size_t n_pubnonces,
|
||
|
|
unsigned int n,
|
||
|
|
unsigned int t,
|
||
|
|
const secp256k1_pubkey *group_pk,
|
||
|
|
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||
|
|
const unsigned char *msg32,
|
||
|
|
const secp256k1_musig_aggnonce *cosigner_aggnonce
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||
|
|
SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9)
|
||
|
|
SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11);
|
||
|
|
|
||
|
|
/** Combine signature shares into one MuSig2 partial signature.
|
||
|
|
*
|
||
|
|
* Interpolation needs t points and the quorum bound exists only for the degree
|
||
|
|
* check, so unlike the nonce round this takes t rather than 2t-1. The scheme
|
||
|
|
* as specified keeps 2t-1 online throughout regardless; this call simply does
|
||
|
|
* not need them all to have answered.
|
||
|
|
*
|
||
|
|
* The result is an ordinary MuSig2 partial signature. Pass it to
|
||
|
|
* secp256k1_musig_partial_sig_agg with the cosigners' partial signatures.
|
||
|
|
*
|
||
|
|
* Given more than t shares, this refuses a set that contradicts itself. The
|
||
|
|
* shares of one session lie on a polynomial of degree t-1, as the contributions
|
||
|
|
* of the nonce round do, so anything past the t needed to interpolate is a
|
||
|
|
* second opinion and is treated as one. Passing a spare share is therefore
|
||
|
|
* worth something, and can turn a call that would have succeeded into one that
|
||
|
|
* fails: at exactly t there is nothing to disagree with, and a bad share is
|
||
|
|
* aggregated into a signature that fails later without naming a share.
|
||
|
|
*
|
||
|
|
* This is not verification. It says the shares agree with each other, not that
|
||
|
|
* they are the ones the members would have produced, and it cannot tell you
|
||
|
|
* which of them is the odd one out. secp256k1_iceberg_partial_sig_verify
|
||
|
|
* answers both, one share at a time, against the public share it names.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if there are fewer than t shares or more than the
|
||
|
|
* group size, if a share carries an out-of-range index, if two carry
|
||
|
|
* the same one, or if more than t shares are given and they do not
|
||
|
|
* lie on one polynomial of degree t-1.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: musig_partial_sig: the group's partial signature
|
||
|
|
* In: partial_sigs: the signature shares. An uninitialized one calls
|
||
|
|
* the illegal callback;
|
||
|
|
* a share off the wire is fine, since
|
||
|
|
* secp256k1_iceberg_partial_sig_parse writes the
|
||
|
|
* same tag secp256k1_iceberg_partial_sign does
|
||
|
|
* n_partial_sigs: how many
|
||
|
|
* n: the group size. A share carrying an index above
|
||
|
|
* it is rejected
|
||
|
|
* t: the threshold, at least 1 and at most (n+1)/2.
|
||
|
|
* An n or a t outside its range is a caller bug
|
||
|
|
* and calls the illegal callback
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sig_agg(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_musig_partial_sig *musig_partial_sig,
|
||
|
|
const secp256k1_iceberg_partial_sig * const *partial_sigs,
|
||
|
|
size_t n_partial_sigs,
|
||
|
|
unsigned int n,
|
||
|
|
unsigned int t
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#endif /* SECP256K1_ICEBERG_H */
|