236 lines
12 KiB
C
236 lines
12 KiB
C
|
|
#ifndef SECP256K1_PREFRACTAL_H
|
||
|
|
#define SECP256K1_PREFRACTAL_H
|
||
|
|
|
||
|
|
#include "secp256k1_frost.h"
|
||
|
|
#include "secp256k1_musig.h"
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
extern "C" {
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
|
||
|
|
/** This module implements a nested FROST+MuSig2 signing scheme ("prefractal"),
|
||
|
|
* which lets a FROST t-of-n group occupy ONE participant slot of an ordinary
|
||
|
|
* MuSig2 (BIP 327) session.
|
||
|
|
*
|
||
|
|
* 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 construction is the nested signing scheme of frosty-musig
|
||
|
|
* (https://github.com/jesseposner/frosty-musig). Each group member computes
|
||
|
|
*
|
||
|
|
* s_i = k1_i + b_frost*b_musig*k2_i + e*a*lambda_i*g*gacc*d_i
|
||
|
|
*
|
||
|
|
* where b_frost is this module's nonce-binding coefficient and b_musig, e, a,
|
||
|
|
* g and gacc all belong to the OUTER MuSig2 session. The group publishes one
|
||
|
|
* ordinary MuSig2 public nonce and one ordinary MuSig2 partial signature, so
|
||
|
|
* cosigners cannot tell a group is involved and need no support for it.
|
||
|
|
*
|
||
|
|
* DELIBERATE DEVIATIONS FROM BIP 445 (see doc/prefractal.md):
|
||
|
|
*
|
||
|
|
* 1. b_frost does NOT commit to the message. BIP 445's nonce coefficient
|
||
|
|
* hashes the message, but this module is built for protocols that publish
|
||
|
|
* nonces before the message exists. The OUTER coefficient b_musig does
|
||
|
|
* commit to the message and binds b_frost through it, exactly as the
|
||
|
|
* iceberg module does with its own Iceberg/noncecoef tag.
|
||
|
|
*
|
||
|
|
* 2. There is NO g_frost factor. Stock FROST normalizes its threshold public
|
||
|
|
* key to even Y (see g_times_gacc_parity in the frost module), because it
|
||
|
|
* produces a BIP 340 x-only signature. Here the threshold public key is an
|
||
|
|
* inner participant of the outer key aggregation and is used as a FULL
|
||
|
|
* point, so all key-side parity normalization happens once, at the
|
||
|
|
* aggregate level, using the OUTER keyagg cache. Applying the frost-level
|
||
|
|
* factor would break the relation for every group whose key has odd Y.
|
||
|
|
*
|
||
|
|
* 3. The FROST tweak cache must be the identity (tacc == 0, gacc_parity == 0).
|
||
|
|
* The channel protocols this module targets tweak only the outer aggregate
|
||
|
|
* key. Every entry point below checks this rather than silently ignoring a
|
||
|
|
* tweaked cache.
|
||
|
|
*
|
||
|
|
* NONCE HANDLING: the caller supplies secnonces produced by
|
||
|
|
* secp256k1_frost_nonce_gen. As always, a secnonce MUST be used for exactly
|
||
|
|
* one signature; reuse across two different messages leaks the secret share
|
||
|
|
* and nothing here can detect it.
|
||
|
|
*
|
||
|
|
* The round-two signer set must be EXACTLY the round-one set: the Lagrange
|
||
|
|
* coefficients and the aggregate nonce are both defined over the
|
||
|
|
* participating set, so a proper subset produces an invalid signature with no
|
||
|
|
* error raised. Pass the same ids array to every function below.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/** Aggregate the group members' public nonces and export the group's
|
||
|
|
* OUTER-wire nonce.
|
||
|
|
*
|
||
|
|
* pubnonce_out is an ordinary MuSig2 public nonce, (R1, b_frost*R2), which is
|
||
|
|
* what the group sends to its cosigners. aggnonce_out is the UNSCALED FROST
|
||
|
|
* aggregate nonce, which the members need later for partial signing; it is an
|
||
|
|
* internal value and must be given back to secp256k1_prefractal_sign and
|
||
|
|
* secp256k1_prefractal_partial_sig_verify unchanged.
|
||
|
|
*
|
||
|
|
* b_frost = tagged_hash("Prefractal/noncecoef",
|
||
|
|
* ser32(u) || sorted ser32 ids || aggnonce66 ||
|
||
|
|
* cbytes_ext(thresh_pk))
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if a pubnonce could not be loaded, if the ids are
|
||
|
|
* invalid, or if either output nonce component is the point at
|
||
|
|
* infinity. A FROST aggregate nonce component may legitimately be
|
||
|
|
* infinity (BIP 445 NonceAgg), but a MuSig2 public nonce has no
|
||
|
|
* encoding for it, so such a session must be restarted with fresh
|
||
|
|
* nonces.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: pubnonce_out: the group's MuSig2 public nonce
|
||
|
|
* aggnonce_out: the group's unscaled FROST aggregate nonce
|
||
|
|
* In: pubnonces: array of pointers to the members' public nonces
|
||
|
|
* ids: array of the members' identifiers
|
||
|
|
* n_signers: number of members (must match the array lengths, at
|
||
|
|
* least 1 and at most SECP256K1_FROST_MAX_PARTICIPANTS)
|
||
|
|
* thresh_pk: the group's (untweaked) threshold public key
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_nonce_agg(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_musig_pubnonce *pubnonce_out,
|
||
|
|
secp256k1_frost_aggnonce *aggnonce_out,
|
||
|
|
const secp256k1_frost_pubnonce *const *pubnonces,
|
||
|
|
const uint32_t *ids,
|
||
|
|
size_t n_signers,
|
||
|
|
const secp256k1_pubkey *thresh_pk
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7);
|
||
|
|
|
||
|
|
/** Produce one group member's nested partial signature.
|
||
|
|
*
|
||
|
|
* Computes s_i = k1 + b_frost*b_musig*k2 + e*a*lambda_i*g*gacc*d_i, with both
|
||
|
|
* nonce scalars negated iff the OUTER final nonce has odd Y, and the key-side
|
||
|
|
* factor g*gacc taken from the OUTER keyagg cache. There is deliberately no
|
||
|
|
* g_frost factor (see the module notes above).
|
||
|
|
*
|
||
|
|
* The secnonce is wiped, so a second call with the same secnonce fails. The
|
||
|
|
* partial signature is self-verified before it is returned, as BIP 445
|
||
|
|
* recommends.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 on failure. Failure cases include: an invalidated
|
||
|
|
* or malformed secnonce, an invalid secret share, my_id not in ids,
|
||
|
|
* a secret share that does not match its pubshare, a non-identity
|
||
|
|
* tweak_cache, a nonce component at infinity, and a failed
|
||
|
|
* self-verification.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: partial_sig: the member's partial signature
|
||
|
|
* In/Out: secnonce: the member's secret nonce, wiped by this call
|
||
|
|
* In: secshare32: the member's 32-byte secret share
|
||
|
|
* my_id: the member's identifier
|
||
|
|
* ids: array of the participating members' identifiers,
|
||
|
|
* the SAME array given to _nonce_agg
|
||
|
|
* pubshares: array of the members' public shares, in the order
|
||
|
|
* of ids, or NULL to skip the share/pubshare check
|
||
|
|
* (providing them is recommended)
|
||
|
|
* n_signers: number of members
|
||
|
|
* aggnonce: the unscaled FROST aggregate nonce from _nonce_agg
|
||
|
|
* thresh_pk: the group's (untweaked) threshold public key
|
||
|
|
* tweak_cache: the group's FROST tweak cache, which MUST be the
|
||
|
|
* identity
|
||
|
|
* keyagg_cache: the OUTER MuSig2 keyagg cache, already carrying any
|
||
|
|
* BIP 341 tweak
|
||
|
|
* cosigner_aggnonce: the aggregate of the NON-group participants'
|
||
|
|
* MuSig2 public nonces
|
||
|
|
* msg32: the 32-byte message being signed
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_sign(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_frost_partial_sig *partial_sig,
|
||
|
|
secp256k1_frost_secnonce *secnonce,
|
||
|
|
const unsigned char *secshare32,
|
||
|
|
uint32_t my_id,
|
||
|
|
const uint32_t *ids,
|
||
|
|
const secp256k1_pubkey *pubshares,
|
||
|
|
size_t n_signers,
|
||
|
|
const secp256k1_frost_aggnonce *aggnonce,
|
||
|
|
const secp256k1_pubkey *thresh_pk,
|
||
|
|
const secp256k1_frost_tweak_cache *tweak_cache,
|
||
|
|
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||
|
|
const secp256k1_musig_aggnonce *cosigner_aggnonce,
|
||
|
|
const unsigned char *msg32
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11) SECP256K1_ARG_NONNULL(12) SECP256K1_ARG_NONNULL(13) SECP256K1_ARG_NONNULL(14);
|
||
|
|
|
||
|
|
/** Verify one group member's nested partial signature.
|
||
|
|
*
|
||
|
|
* Checks s_i*G == R1_i + b_frost*b_musig*R2_i + e*a*lambda_i*g*gacc*P_i, with
|
||
|
|
* the nonce points negated iff the OUTER final nonce has odd Y. The session is
|
||
|
|
* recomputed from the same parameters secp256k1_prefractal_sign takes, so the
|
||
|
|
* caller must pass exactly the same ids, aggnonce, keys and message.
|
||
|
|
*
|
||
|
|
* This is the identifiable-abort tool: when the aggregate signature fails,
|
||
|
|
* running this over each member's share names the one at fault.
|
||
|
|
*
|
||
|
|
* Returns: 1 if the partial signature is valid, 0 otherwise.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* In: partial_sig: the partial signature to verify
|
||
|
|
* pubnonce: the member's public nonce, as given to _nonce_agg
|
||
|
|
* pubshare: the member's public share
|
||
|
|
* my_id: the member's identifier
|
||
|
|
* ids: array of the participating members' identifiers
|
||
|
|
* n_signers: number of members
|
||
|
|
* aggnonce: the unscaled FROST aggregate nonce from _nonce_agg
|
||
|
|
* thresh_pk: the group's (untweaked) threshold public key
|
||
|
|
* tweak_cache: the group's FROST tweak cache, which MUST be the
|
||
|
|
* identity
|
||
|
|
* keyagg_cache: the OUTER MuSig2 keyagg cache
|
||
|
|
* cosigner_aggnonce: the aggregate of the NON-group participants' nonces
|
||
|
|
* msg32: the 32-byte message being signed
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_partial_sig_verify(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
const secp256k1_frost_partial_sig *partial_sig,
|
||
|
|
const secp256k1_frost_pubnonce *pubnonce,
|
||
|
|
const secp256k1_pubkey *pubshare,
|
||
|
|
uint32_t my_id,
|
||
|
|
const uint32_t *ids,
|
||
|
|
size_t n_signers,
|
||
|
|
const secp256k1_frost_aggnonce *aggnonce,
|
||
|
|
const secp256k1_pubkey *thresh_pk,
|
||
|
|
const secp256k1_frost_tweak_cache *tweak_cache,
|
||
|
|
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||
|
|
const secp256k1_musig_aggnonce *cosigner_aggnonce,
|
||
|
|
const unsigned char *msg32
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11) SECP256K1_ARG_NONNULL(12) SECP256K1_ARG_NONNULL(13);
|
||
|
|
|
||
|
|
/** Sum the members' partial signatures into one ordinary MuSig2 partial
|
||
|
|
* signature.
|
||
|
|
*
|
||
|
|
* The result is ready for secp256k1_musig_partial_sig_agg alongside the
|
||
|
|
* cosigners' partial signatures. The sum is plain because the FROST tweak
|
||
|
|
* cache is required to be the identity: with a tweak there would be an
|
||
|
|
* additional e*g*tacc term to fold in, and this module does not support that.
|
||
|
|
*
|
||
|
|
* This does not check that the shares are the ones the members would have
|
||
|
|
* produced; secp256k1_prefractal_partial_sig_verify answers that, one share at
|
||
|
|
* a time.
|
||
|
|
*
|
||
|
|
* Returns: 1 on success, 0 if a partial signature could not be loaded or if
|
||
|
|
* tweak_cache is not the identity.
|
||
|
|
* Args: ctx: pointer to a context object
|
||
|
|
* Out: sig_out: the resulting MuSig2 partial signature
|
||
|
|
* error_index: if non-NULL and a partial signature fails to load,
|
||
|
|
* receives its index in partial_sigs
|
||
|
|
* In: partial_sigs: array of pointers to the members' partial signatures
|
||
|
|
* n_sigs: number of partial signatures (at least 1 and at most
|
||
|
|
* SECP256K1_FROST_MAX_PARTICIPANTS)
|
||
|
|
* tweak_cache: the group's FROST tweak cache, which MUST be the
|
||
|
|
* identity
|
||
|
|
*/
|
||
|
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_partial_sig_agg(
|
||
|
|
const secp256k1_context *ctx,
|
||
|
|
secp256k1_musig_partial_sig *sig_out,
|
||
|
|
size_t *error_index,
|
||
|
|
const secp256k1_frost_partial_sig *const *partial_sigs,
|
||
|
|
size_t n_sigs,
|
||
|
|
const secp256k1_frost_tweak_cache *tweak_cache
|
||
|
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6);
|
||
|
|
|
||
|
|
#ifdef __cplusplus
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#endif /* SECP256K1_PREFRACTAL_H */
|