Merge branch 'review-fixes'
This commit is contained in:
@@ -130,6 +130,15 @@ if(NOT SECP256K1_EXPERIMENTAL)
|
||||
if(SECP256K1_ASM STREQUAL "arm32")
|
||||
message(FATAL_ERROR "ARM32 assembly is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_FROST)
|
||||
message(FATAL_ERROR "FROST module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_CHILLDKG)
|
||||
message(FATAL_ERROR "ChillDKG module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_ICEBERG)
|
||||
message(FATAL_ERROR "Iceberg module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(SECP256K1_VALGRIND "AUTO" CACHE STRING "Build with extra checks for running inside Valgrind. [default=AUTO]")
|
||||
|
||||
@@ -318,7 +318,8 @@ maintainer-clean-local: clean-testvectors
|
||||
### Additional files to distribute
|
||||
EXTRA_DIST = autogen.sh CHANGELOG.md SECURITY.md
|
||||
EXTRA_DIST += doc/release-process.md doc/safegcd_implementation.md
|
||||
EXTRA_DIST += doc/ellswift.md doc/musig.md
|
||||
EXTRA_DIST += doc/ellswift.md doc/musig.md doc/iceberg.md
|
||||
EXTRA_DIST += src/modules/frost/frost.md src/modules/chilldkg/chilldkg.md
|
||||
EXTRA_DIST += examples/EXAMPLES_COPYING
|
||||
EXTRA_DIST += sage/gen_exhaustive_groups.sage
|
||||
EXTRA_DIST += sage/gen_split_lambda_constants.sage
|
||||
|
||||
@@ -5,15 +5,15 @@ The following sections contain additional notes on the API of the iceberg
|
||||
module (`include/secp256k1_iceberg.h`). A usage example can be found in
|
||||
`examples/iceberg.c`, which runs the whole flow and narrates it.
|
||||
|
||||
**This module is experimental.** It builds by default here, which is a
|
||||
development convenience rather than a statement that it is ready. Iceberg has a
|
||||
security proof, by reduction to NestedMuSig2's unforgeability, but it is in an
|
||||
anonymous conference submission that is still a working draft, and at the two
|
||||
nonces BIP-327 fixes that reduction holds in the algebraic group model rather
|
||||
than the plain random oracle model. The proof also assumes a property no library
|
||||
can provide (that a session label is used once, group-wide) and the known
|
||||
ways to lose a key all live in exactly that assumption. Do not put money behind
|
||||
this module.
|
||||
**This module is experimental.** It is off by default and on in dev mode, and
|
||||
both build systems refuse it outright without `--enable-experimental` or
|
||||
`-DSECP256K1_EXPERIMENTAL=ON`. Iceberg has a security proof, by reduction to
|
||||
NestedMuSig2's unforgeability, but it is in an anonymous conference submission
|
||||
that is still a working draft, and at the two nonces BIP-327 fixes that
|
||||
reduction holds in the algebraic group model rather than the plain random
|
||||
oracle model. The proof also assumes a property no library can provide (that a
|
||||
session label is used once, group-wide) and the known ways to lose a key all
|
||||
live in exactly that assumption. Do not put money behind this module.
|
||||
|
||||
Iceberg lets a *t*-of-*n* group act as a single MuSig2 participant. From outside,
|
||||
the result is an ordinary BIP-340 signature: nothing in it records that a group
|
||||
|
||||
@@ -520,6 +520,25 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_sign(
|
||||
* through NonceAgg as a pubnonce contribution, and a pubnonce's components
|
||||
* are never the point at infinity); if it does, this function fails.
|
||||
*
|
||||
* WARNING: the derivation above is the whole of what the nonce depends on. It
|
||||
* does NOT commit to the pubshares, to the untweaked threshold public key, or
|
||||
* to which tweaks the cache accumulated -- only to the x-only encoding of the
|
||||
* _tweaked_ threshold public key (this is BIP 445's det_nonce_hash, not a
|
||||
* deviation). Two tweak caches can therefore agree on that x-only key and
|
||||
* still disagree on the sign g*gacc that multiplies the secret share, because
|
||||
* Q and -Q have the same x-coordinate: a cache initialized from the threshold
|
||||
* public key and one initialized from its negation are the simplest example.
|
||||
* Two calls that differ only in that way emit the SAME pubnonce and two
|
||||
* partial signatures that differ only in the sign of the secret-share term,
|
||||
* which is two equations in the nonce and the secret share -- the secret
|
||||
* share falls out of the pair.
|
||||
*
|
||||
* The caller must therefore treat the tweak cache and the pubshares as fixed
|
||||
* key material belonging to the group, established once at key generation,
|
||||
* and never as per-session parameters accepted from a coordinator or any
|
||||
* other peer. Given that, repeating a call reproduces a byte-identical result
|
||||
* and is harmless, which is the point of a deterministic nonce.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or signing fails, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: partial_sig: pointer to a partial_sig object
|
||||
|
||||
@@ -104,7 +104,14 @@ static int secp256k1_chilldkg_xonly_load(secp256k1_ge *p, const unsigned char *i
|
||||
|
||||
static void secp256k1_chilldkg_pad33(unsigned char *out33, const char *str) {
|
||||
size_t len = strlen(str);
|
||||
VERIFY_CHECK(len <= 33);
|
||||
/* Every call site passes a string literal of the module, so this cannot
|
||||
* trigger. The clamp must not sit inside VERIFY_CHECK, which is compiled
|
||||
* out in noverify builds: an over-long tag would overflow out33 and make
|
||||
* the memset length below underflow to a huge value. */
|
||||
if (len > 33) {
|
||||
VERIFY_CHECK(0);
|
||||
len = 33;
|
||||
}
|
||||
memcpy(out33, str, len);
|
||||
memset(out33 + len, 0, 33 - len);
|
||||
}
|
||||
@@ -116,7 +123,20 @@ static void secp256k1_chilldkg_schnorrsig_sha256_tagged(const secp256k1_hash_ctx
|
||||
size_t prefix_len = strlen(tag_prefix);
|
||||
size_t subtag_len = strlen(subtag);
|
||||
|
||||
VERIFY_CHECK(prefix_len + subtag_len <= sizeof(tag));
|
||||
/* The longest tag the module builds is "BIP DKG/pop message" ||
|
||||
* "/challenge", 29 bytes. As in secp256k1_chilldkg_pad33, the bound is
|
||||
* enforced outside VERIFY_CHECK so that a future over-long tag cannot
|
||||
* overflow tag[] in a noverify build. Clamping rather than returning
|
||||
* early keeps sha initialized for the caller; a truncated tag changes
|
||||
* every hash the module computes, so the test vectors fail loudly. */
|
||||
if (prefix_len > sizeof(tag)) {
|
||||
VERIFY_CHECK(0);
|
||||
prefix_len = sizeof(tag);
|
||||
}
|
||||
if (subtag_len > sizeof(tag) - prefix_len) {
|
||||
VERIFY_CHECK(0);
|
||||
subtag_len = sizeof(tag) - prefix_len;
|
||||
}
|
||||
memcpy(tag, tag_prefix, prefix_len);
|
||||
memcpy(tag + prefix_len, subtag, subtag_len);
|
||||
secp256k1_sha256_initialize_tagged(hash_ctx, sha, tag, prefix_len + subtag_len);
|
||||
|
||||
@@ -96,6 +96,29 @@ Security notes
|
||||
unique for every call to `secp256k1_frost_nonce_gen`. Passing the secret
|
||||
share to `nonce_gen` is recommended as defense-in-depth against bad
|
||||
randomness.
|
||||
- `secp256k1_frost_deterministic_sign` has no `session_secrand32` to keep
|
||||
fresh; its safety rests instead on what the nonce derivation commits to. Per
|
||||
BIP 445's `det_nonce_hash` that is the secret share, `my_id`, `u`, the sorted
|
||||
ids, the aggothernonce, the **x-only** tweaked threshold public key, and the
|
||||
message — and nothing else. In particular it does not commit to the
|
||||
pubshares, to the untweaked threshold public key, or to the accumulated
|
||||
tweaks. Since `Q` and `-Q` share an x-coordinate, a tweak cache initialized
|
||||
from the threshold public key and one initialized from its negation present
|
||||
the same x-only key to the derivation while disagreeing on the sign `g*gacc`
|
||||
that multiplies the secret share. Two calls differing only in that produce
|
||||
the same pubnonce and partial signatures `s = k + e*lambda*d` and
|
||||
`s' = k - e*lambda*d`, where `k` is the identical `k1 + b*k2`; subtracting
|
||||
them yields `d` directly. The same holds for any two caches that agree on
|
||||
the tweaked x-only key but not on `g*gacc`.
|
||||
|
||||
This is a property of the specified derivation, not of this implementation,
|
||||
and it is not detectable from inside a single call: the self-verification in
|
||||
`Sign` passes in both cases, because each signature is individually valid
|
||||
under its own cache. The caller carries the obligation. Treat the tweak
|
||||
cache and the pubshares as fixed key material established once at key
|
||||
generation, and never accept either as a per-session parameter from the
|
||||
coordinator or another peer. Under that discipline a repeated call is
|
||||
byte-identical and harmless, which is what the deterministic nonce is for.
|
||||
- Final signatures produced by `secp256k1_frost_partial_sig_agg` are ordinary
|
||||
BIP340 signatures; they are verified with `secp256k1_schnorrsig_verify`
|
||||
against the (tweaked) x-only threshold public key.
|
||||
|
||||
@@ -293,6 +293,14 @@ int secp256k1_frost_trusted_dealer_keygen(const secp256k1_context *ctx, unsigned
|
||||
ret = 1;
|
||||
|
||||
cleanup:
|
||||
if (!ret) {
|
||||
/* The loop above may have written real secret shares for the first
|
||||
* few participants before failing. Zero the outputs again so that a
|
||||
* failed call leaves nothing usable behind, as promised above. */
|
||||
secp256k1_memzero_explicit(secshares32, n_participants * 32);
|
||||
memset(thresh_pk, 0, sizeof(*thresh_pk));
|
||||
memset(pubshares, 0, n_participants * sizeof(*pubshares));
|
||||
}
|
||||
secp256k1_scalar_clear(&secret);
|
||||
secp256k1_scalar_clear(&share);
|
||||
secp256k1_scalar_clear(&x);
|
||||
|
||||
@@ -530,7 +530,7 @@ static void frost_nonce_test_internal(void) {
|
||||
msglen = testrand_int(sizeof(msg) + 1);
|
||||
testrand256(msg);
|
||||
extra_in_len = testrand_int(sizeof(extra_in) + 1);
|
||||
testrand256(extra_in);
|
||||
testrand_bytes_test(extra_in, sizeof(extra_in));
|
||||
if (testrand_bits(1)) {
|
||||
secp256k1_pubkey pubshare_tmp;
|
||||
CHECK(secp256k1_ec_pubkey_create(CTX, &pubshare_tmp, secshare) == 1);
|
||||
|
||||
0
tools/test_vectors_chilldkg_generate.py
Normal file → Executable file
0
tools/test_vectors_chilldkg_generate.py
Normal file → Executable file
0
tools/test_vectors_frost_generate.py
Normal file → Executable file
0
tools/test_vectors_frost_generate.py
Normal file → Executable file
313
tools/test_vectors_iceberg_generate.py
Executable file
313
tools/test_vectors_iceberg_generate.py
Executable file
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate src/modules/iceberg/vectors.h from the Python reference.
|
||||
|
||||
git clone https://github.com/nkohen/Iceberg.git
|
||||
git -C Iceberg checkout 7b55ef6dc0dd6e11d1c14cb3bc0a675ff3487cce
|
||||
./tools/test_vectors_iceberg_generate.py Iceberg > src/modules/iceberg/vectors.h
|
||||
|
||||
That commit is "Fixed nonce generation security", Nadav Kohen, 12 January 2026.
|
||||
It is named here because vectors.h is the only thing in this repository that
|
||||
cannot be rebuilt from what the repository contains, and because a dozen comments
|
||||
in the module assert agreement with "the reference" as a correctness property. The
|
||||
reference is not vendored: it is not ours, and pinning a copy would hide it
|
||||
drifting.
|
||||
|
||||
There are no published test vectors for Iceberg, so these are produced by
|
||||
running the reference implementation and recording what it does. That makes
|
||||
them a cross-implementation check and not an authority: if both implementations
|
||||
are wrong in the same way, these vectors agree with the mistake. What they do
|
||||
catch is the case that matters most here, where one of the two drifts.
|
||||
|
||||
Two things about the reference have to be worked around, and both are recorded
|
||||
in its own notes:
|
||||
|
||||
* eval_lagrange removes an element from the set it is passed, which is the
|
||||
caller's set. Anything calling it twice with the same set gets a different
|
||||
answer the second time. Every call below hands it a copy.
|
||||
|
||||
* all_lagrange_coefficients does polynomial division in numpy float64. It is
|
||||
correct up to a quorum of 18 and silently wrong from 19, because the
|
||||
constant term of the product is the quorum's factorial and 19! is past
|
||||
2^53. It is replaced below with exact integer arithmetic.
|
||||
|
||||
The seeds are derived from a fixed string rather than taken from the reference's
|
||||
own key_gen, which uses the system random source. That is what makes the vectors
|
||||
reproducible; it also means the seeds have to be laid out in the same order the
|
||||
C module expects, which is lexicographic order of the (t-1)-subsets.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import itertools
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
|
||||
def combinations_in_rank_order(n, size):
|
||||
"""The subsets, in the order secp256k1_rss_subset_unrank produces them.
|
||||
|
||||
That function walks the members upward and takes a binomial-sized block at
|
||||
each step, which is plain lexicographic order on the ascending member list,
|
||||
the same order itertools produces. The whole seed layout depends on it.
|
||||
"""
|
||||
return [set(c) for c in itertools.combinations(range(1, n + 1), size)]
|
||||
|
||||
|
||||
def exact_lagrange_coefficients(indices, modulus):
|
||||
"""all_lagrange_coefficients, over the integers instead of float64.
|
||||
|
||||
For each j, the basis polynomial that is 1 at j and 0 at every other index,
|
||||
as coefficients from the constant term upward.
|
||||
|
||||
A guard rather than a fix: the largest configuration below has a quorum of
|
||||
9, where the reference's float64 path is still exact, so this changes no
|
||||
emitted byte today. It is what keeps that true if a larger one is added.
|
||||
"""
|
||||
def poly_mul(a, b):
|
||||
out = [0] * (len(a) + len(b) - 1)
|
||||
for i, x in enumerate(a):
|
||||
for j, y in enumerate(b):
|
||||
out[i + j] = (out[i + j] + x * y) % modulus
|
||||
return out
|
||||
|
||||
def poly_eval(coeffs, x):
|
||||
total = 0
|
||||
for c in reversed(coeffs):
|
||||
total = (total * x + c) % modulus
|
||||
return total
|
||||
|
||||
result = {}
|
||||
for j in sorted(indices):
|
||||
basis = [1]
|
||||
for index in sorted(indices):
|
||||
if index != j:
|
||||
basis = poly_mul(basis, [(-index) % modulus, 1])
|
||||
inverse = pow(poly_eval(basis, j), -1, modulus)
|
||||
result[j] = [(c * inverse) % modulus for c in basis]
|
||||
return result
|
||||
|
||||
|
||||
def hexbytes(data, indent):
|
||||
"""A C initializer for a byte array, wrapped to fit."""
|
||||
body = ", ".join("0x%02X" % b for b in data)
|
||||
return textwrap.fill(body, width=78 - indent,
|
||||
initial_indent=" " * indent,
|
||||
subsequent_indent=" " * indent)
|
||||
|
||||
|
||||
class Session:
|
||||
"""One configuration, dealt and signed once, with everything recorded."""
|
||||
|
||||
def __init__(self, iceberg, vpss, musig, label, n_parties, t):
|
||||
self.label = label
|
||||
self.n = n_parties
|
||||
self.t = t
|
||||
self.mu = 2 * t - 1
|
||||
self.iceberg = iceberg
|
||||
self.vpss = vpss
|
||||
self.musig = musig
|
||||
self._deal()
|
||||
self._aggregate()
|
||||
self._round_one()
|
||||
self._round_two()
|
||||
|
||||
def _seed(self, rank):
|
||||
material = "iceberg test vectors|%s|%d" % (self.label, rank)
|
||||
return hashlib.sha256(material.encode()).digest()
|
||||
|
||||
def _deal(self):
|
||||
"""One seed per (t-1)-subset, in rank order, then the shares.
|
||||
|
||||
Only the seeds go into the header. Each is held by n-(t-1)
|
||||
participants, so writing out every share would repeat most of them;
|
||||
the C side rebuilds each share from the same rank order.
|
||||
"""
|
||||
subsets = combinations_in_rank_order(self.n, self.t - 1)
|
||||
self.seeds = [self._seed(rank) for rank in range(len(subsets))]
|
||||
|
||||
self.shares = {}
|
||||
for k in range(1, self.n + 1):
|
||||
mine = [(seed, subset) for seed, subset in zip(self.seeds, subsets)
|
||||
if k not in subset]
|
||||
self.shares[k] = self.vpss.RSSShare(
|
||||
[self.vpss.RSSSummand(seed, subset) for seed, subset in mine])
|
||||
|
||||
def _aggregate(self):
|
||||
self.pubshares = {}
|
||||
for k in range(1, self.n + 1):
|
||||
_, pk_k = self.iceberg.key_gen(k, self.shares[k])
|
||||
self.pubshares[k] = pk_k
|
||||
quorum = [self.vpss.VPSSCommitment(k, self.pubshares[k])
|
||||
for k in range(1, self.mu + 1)]
|
||||
self.group_pk = self.iceberg.pk_agg(self.t, self.mu, quorum)
|
||||
|
||||
def _session_label(self):
|
||||
"""A session label for this vector.
|
||||
|
||||
The module takes any 32 bytes here, so this is an arbitrary but
|
||||
reproducible choice, not a derivation the scheme requires. Changing the
|
||||
rule changes every nonce and signature below it.
|
||||
"""
|
||||
keyagg = self.musig.key_agg([self.group_pk, self.cosigner_pk])
|
||||
agg_pk32 = self.musig.bytes_from_int(self.musig.x(keyagg.Q))
|
||||
preimage = agg_pk32 + self.group_pk + b"".join(
|
||||
self.cosigner_pubnonce[33 * i:33 * (i + 1)] for i in (0, 1))
|
||||
return self.musig.tagged_hash("Iceberg/sid", self.msg + preimage)
|
||||
|
||||
def _round_one(self):
|
||||
"""A cosigner whose nonce and key are fixed, so this is reproducible."""
|
||||
self.msg = hashlib.sha256(("message|" + self.label).encode()).digest()
|
||||
|
||||
cosigner_sk = hashlib.sha256(("cosigner|" + self.label).encode()).digest()
|
||||
self.cosigner_pk = self.musig.individual_pk(cosigner_sk)
|
||||
halves = [hashlib.sha256(("cosigner nonce %d|%s" % (i, self.label)).encode()).digest()
|
||||
for i in (1, 2)]
|
||||
self.cosigner_pubnonce = b"".join(
|
||||
self.musig.cbytes(self.musig.point_mul(
|
||||
self.musig.G, self.musig.int_from_bytes(h) % self.musig.n))
|
||||
for h in halves)
|
||||
|
||||
self.sid = self._session_label()
|
||||
|
||||
self.pubnonces = {}
|
||||
for k in range(1, self.n + 1):
|
||||
_, pubnonce_k = self.iceberg.nonce_gen(k, self.shares[k], self.sid)
|
||||
self.pubnonces[k] = pubnonce_k
|
||||
|
||||
quorum = [(k, self.pubnonces[k]) for k in range(1, self.mu + 1)]
|
||||
self.group_aggnonce = self.iceberg.nonce_agg(self.t, self.mu, quorum)
|
||||
self.group_pubnonce = self.iceberg.nonce_agg_ext(self.group_pk,
|
||||
self.group_aggnonce)
|
||||
|
||||
def _round_two(self):
|
||||
pubkeys = [self.group_pk, self.cosigner_pk]
|
||||
aggnonce = self.musig.nonce_agg([self.group_pubnonce, self.cosigner_pubnonce])
|
||||
upper = self.musig.SessionContext(aggnonce, pubkeys, [], [], self.msg)
|
||||
|
||||
self.psigs = {}
|
||||
for k in range(1, self.t + 1):
|
||||
self.psigs[k] = self.iceberg.sign(k, self.shares[k], self.group_pk,
|
||||
self.sid, self.group_aggnonce, upper)
|
||||
self.group_psig = self.iceberg.sign_agg(
|
||||
[(k, self.psigs[k]) for k in range(1, self.t + 1)])
|
||||
|
||||
def emit(self, out):
|
||||
out.append(" {")
|
||||
out.append(' "%s", %d, %d, %d, %d,'
|
||||
% (self.label, self.n, self.t, self.mu, len(self.seeds)))
|
||||
out.append(" { /* one seed per (t-1)-subset, in rank order */")
|
||||
for seed in self.seeds:
|
||||
out.append(" {")
|
||||
out.append(hexbytes(seed, 16))
|
||||
out.append(" },")
|
||||
out.append(" },")
|
||||
for name, blob in (("group_pk", self.group_pk),
|
||||
("msg", self.msg),
|
||||
("sid", self.sid),
|
||||
("cosigner_pk", self.cosigner_pk),
|
||||
("cosigner_pubnonce", self.cosigner_pubnonce),
|
||||
("group_aggnonce", self.group_aggnonce),
|
||||
("group_pubnonce", self.group_pubnonce),
|
||||
("group_psig", self.group_psig)):
|
||||
out.append(" { /* %s */" % name)
|
||||
out.append(hexbytes(blob, 12))
|
||||
out.append(" },")
|
||||
out.append(" { /* pubshares, participant 1 first */")
|
||||
for k in range(1, self.n + 1):
|
||||
out.append(" {")
|
||||
out.append(hexbytes(self.pubshares[k], 16))
|
||||
out.append(" },")
|
||||
out.append(" },")
|
||||
out.append(" { /* pubnonces, the mu participants of round one */")
|
||||
for k in range(1, self.mu + 1):
|
||||
out.append(" {")
|
||||
out.append(hexbytes(self.pubnonces[k], 16))
|
||||
out.append(" },")
|
||||
out.append(" },")
|
||||
out.append(" { /* signature shares, the first t participants */")
|
||||
for k in range(1, self.t + 1):
|
||||
out.append(" {")
|
||||
out.append(hexbytes(self.psigs[k], 16))
|
||||
out.append(" },")
|
||||
out.append(" },")
|
||||
out.append(" },")
|
||||
|
||||
|
||||
HEADER = """/**
|
||||
* Note: this file was autogenerated using test_vectors_iceberg_generate.py.
|
||||
* Do not edit. To regenerate:
|
||||
*
|
||||
* git clone https://github.com/nkohen/Iceberg.git
|
||||
* git -C Iceberg checkout 7b55ef6dc0dd6e11d1c14cb3bc0a675ff3487cce
|
||||
* ./tools/test_vectors_iceberg_generate.py Iceberg > src/modules/iceberg/vectors.h
|
||||
*
|
||||
* There are no published Iceberg test vectors. These record what the reference
|
||||
* does, so they catch the two implementations drifting apart, not a mistake
|
||||
* they might both make. That makes this the one file here that cannot be rebuilt
|
||||
* from what this repository contains, which is why the commit is named.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define ICEBERG_VECTOR_MAX_PARTICIPANTS %d
|
||||
#define ICEBERG_VECTOR_MAX_SEEDS %d
|
||||
|
||||
struct iceberg_session_vector {
|
||||
const char *label;
|
||||
unsigned int n;
|
||||
unsigned int t;
|
||||
unsigned int mu;
|
||||
size_t n_seeds;
|
||||
unsigned char seeds[ICEBERG_VECTOR_MAX_SEEDS][32];
|
||||
unsigned char group_pk[33];
|
||||
unsigned char msg[32];
|
||||
unsigned char sid[32];
|
||||
unsigned char cosigner_pk[33];
|
||||
unsigned char cosigner_pubnonce[66];
|
||||
unsigned char group_aggnonce[66];
|
||||
unsigned char group_pubnonce[66];
|
||||
unsigned char group_psig[32];
|
||||
unsigned char pubshares[ICEBERG_VECTOR_MAX_PARTICIPANTS][33];
|
||||
unsigned char pubnonces[ICEBERG_VECTOR_MAX_PARTICIPANTS][66];
|
||||
unsigned char psigs[ICEBERG_VECTOR_MAX_PARTICIPANTS][32];
|
||||
};
|
||||
|
||||
static const struct iceberg_session_vector iceberg_session_vectors[] = {"""
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print(__doc__)
|
||||
return 1
|
||||
sys.path.insert(0, sys.argv[1])
|
||||
|
||||
import musig
|
||||
import vpss
|
||||
import iceberg
|
||||
|
||||
# Both workarounds described at the top of this file. They are installed
|
||||
# before anything runs, so nothing below can accidentally use the originals.
|
||||
original_eval = vpss.eval_lagrange
|
||||
vpss.eval_lagrange = lambda indices, excluding, eval_at, modulus=musig.n: \
|
||||
original_eval(set(indices), excluding, eval_at, modulus)
|
||||
iceberg.eval_lagrange = vpss.eval_lagrange
|
||||
vpss.all_lagrange_coefficients = lambda indices, modulus=musig.n: \
|
||||
exact_lagrange_coefficients(indices, modulus)
|
||||
|
||||
configs = [("2of3", 3, 2), ("3of5", 5, 3), ("3of7", 7, 3),
|
||||
("4of7", 7, 4), ("5of9", 9, 5)]
|
||||
sessions = [Session(iceberg, vpss, musig, *config) for config in configs]
|
||||
|
||||
widest = max(s.n for s in sessions)
|
||||
most_seeds = max(len(s.seeds) for s in sessions)
|
||||
|
||||
out = [HEADER % (widest, most_seeds)]
|
||||
for session in sessions:
|
||||
session.emit(out)
|
||||
out.append("};")
|
||||
out.append("")
|
||||
print("\n".join(out))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user