Adds `prefractal`, an experimental module that lets a FROST t-of-n group
occupy ONE participant slot of an ordinary MuSig2 (BIP 327) session. Each
member computes
s_i = k1_i + b_frost*b_musig*k2_i + e*a*lambda_i*g*gacc*d_i
and the group publishes one ordinary MuSig2 public nonce and one ordinary
MuSig2 partial signature, so cosigners need no support for it and cannot tell
a group is involved.
Four public functions, all sessionless (every call takes its session
parameters explicitly, so there are no new opaque types, magics or *_SIZE
constants to keep synchronised):
secp256k1_prefractal_nonce_agg group wire nonce + unscaled aggnonce
secp256k1_prefractal_sign one member's partial signature
secp256k1_prefractal_partial_sig_verify identifiable abort
secp256k1_prefractal_partial_sig_agg sum -> musig partial signature
Three deliberate deviations from BIP 445, all documented in the public header:
1. b_frost does not commit to the message. The target protocols publish the
group's wire nonce before the message exists, so a message-committing
coefficient could not be computed in round one and rebuilt later. The outer
b_musig does commit to the message and multiplies this one, so the product
still binds it. Same trade the iceberg module makes, for the same reason.
The preimage is BIP 445's with the message dropped and the group key
carried in full rather than x-only, since it is used as a full point
downstream.
2. There is NO g_frost factor. Stock FROST normalises its threshold key to
even Y (g_times_gacc_parity = gacc_parity ^ pk_odd, frost/session_impl.h
:664) because it produces a BIP 340 x-only signature. Here the threshold
key is an inner participant of the outer key aggregation and is used as a
full point, so all key-side parity normalisation happens once, at the
aggregate level, off the OUTER keyagg cache. Note this is NOT implied by
the tweak cache being the identity: with an identity cache g_frost is still
-1 for every odd-Y group key, i.e. about half of them. Importing frost's
key-side parity here would yield a signer that works for even-Y groups and
fails for odd-Y ones.
3. The FROST tweak cache must be the identity (tacc == 0, gacc_parity == 0).
Checked in sign and partial_sig_verify, not only in partial_sig_agg, so the
key a member signs under is tied to the cache that was validated; sign and
verify additionally require thresh_pk to equal the cache's own key so the
two arguments cannot disagree.
The verification equation lives in one helper used both by sign's BIP 445
self-check and by partial_sig_verify, so the two cannot drift apart.
Build wiring. Three files order their module blocks differently and the
constraints point in opposite directions:
- src/secp256k1.c: the include goes AFTER frost and musig, because the
module calls their static internals.
- src/CMakeLists.txt: the block goes BEFORE both, because its set() calls
are only observed by blocks that run later.
- configure.ac: the block likewise goes before the musig block, NOT at
iceberg's position further down. configure.ac orders musig and frost ahead
of iceberg, and iceberg's late enable_module_musig=yes is harmless only
because musig defaults to yes. frost defaults to no, so a late
force-enable would leave -DENABLE_MODULE_FROST=1 unemitted while
AM_CONDITIONAL still observed the mutation - a library whose secp256k1.c
never included frost, built alongside frost's own sources.
frost is also the first default-OFF module anything depends on, which breaks
the dependency-guard idiom used everywhere else in both build systems: the
existing "DEFINED X AND NOT X" (CMake) and "x$X = xno" (autotools) tests read
as "the user disabled it explicitly" only for default-ON modules, and are true
by default for a default-OFF one. Since neither build system can distinguish
an explicit disable from the default once both are in the cache, enabling
prefractal simply implies frost; the guard is kept for musig, where it still
means what it says. The CMake block additionally lifts both dependencies into
the parent scope so the top-level configuration summary reports what was
actually built rather than printing "frost OFF" while compiling frost in.
Verified on both build systems:
cmake -B build -DSECP256K1_ENABLE_MODULE_PREFRACTAL=ON -DSECP256K1_BUILD_TESTS=ON
-> musig/frost/prefractal all ON, tests pass, 4 prefractal symbols exported
cmake -B build -DSECP256K1_BUILD_TESTS=ON
-> prefractal OFF, default build unchanged, tests pass
./configure --enable-experimental --enable-module-prefractal && make && make check
-> frost=yes forced on, -DENABLE_MODULE_FROST=1 emitted, 3/3 pass
./configure --enable-module-prefractal
-> correctly refused: "Prefractal module is experimental"
tests_impl.h is a placeholder here so the module links; the real suite lands
next.