prefractal: document the deviations and add the constant-time test
Some checks are pending
CI / Build arm64 Docker image (push) Waiting to run
CI / Build x64 Docker image (push) Waiting to run
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[BPPP:yes CHILLDKG:yes CPPFLAGS:-DVERIFY CTIMETESTS:no ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes RECOVERY:ye… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALF… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJE… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[BPPP:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:yes WHITEL… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[CFLAGS:-O1 ECDH:yes ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[BPPP:yes CHILLDKG:yes CPPFLAGS:-DVERIFY CTIMETESTS:no ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes RE… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNOR… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[BPPP:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:y… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[CFLAGS:-O1 ECDH:yes ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[BPPP:yes CHILLDKG:yes CPPFLAGS:-DVERIFY CTIMETESTS:no ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes … (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALFAG… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECT… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[BPPP:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:yes WHITELIS… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[CFLAGS:-O1 ECDH:yes ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[BPPP:yes CHILLDKG:yes CPPFLAGS:-DVERIFY CTIMETESTS:no ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes RECO… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRS… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:ye… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[BPPP:yes ECDSAADAPTOR:yes ECDSA_S2C:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:yes… (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[CFLAGS:-O1 ECDH:yes ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Blocked by required conditions
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[WIDEMUL:int128]]) (push) Blocked by required conditions
CI / i686: Linux (Debian stable) (clang --target=i686-pc-linux-gnu -isystem /usr/i686-linux-gnu/include, map[env_vars:map[]]) (push) Blocked by required conditions
CI / i686: Linux (Debian stable) (i686-linux-gnu-gcc, map[env_vars:map[]]) (push) Blocked by required conditions
CI / s390x (big-endian): Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Blocked by required conditions
CI / ARM32: Linux (Debian stable, QEMU) (map[env_vars:map[ASM:arm32 EXPERIMENTAL:yes]]) (push) Blocked by required conditions
CI / ARM32: Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Blocked by required conditions
CI / arm64: Linux (Debian stable) (clang, map[env_vars:map[]]) (push) Blocked by required conditions
CI / arm64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[]]) (push) Blocked by required conditions
CI / arm64: Linux (Debian stable) (gcc, map[env_vars:map[]]) (push) Blocked by required conditions
CI / arm64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[]]) (push) Blocked by required conditions
CI / ppc64le: Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Blocked by required conditions
CI / Valgrind arm64 (memcheck) (push) Blocked by required conditions
CI / Valgrind i686 (memcheck) (push) Blocked by required conditions
CI / Valgrind x64 (memcheck) (push) Blocked by required conditions
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:auto CC:clang]]) (push) Blocked by required conditions
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:auto CC:i686-linux-gnu-gcc HOST:i686-linux-gnu]]) (push) Blocked by required conditions
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:no CC:clang ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Blocked by required conditions
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:no CC:i686-linux-gnu-gcc ECMULTGENKB:2 ECMULTWINDOW:2 HOST:i686-linux-gnu]]) (push) Blocked by required conditions
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g CTIMETESTS:no]]) (push) Blocked by required conditions
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g -O3 CTIMETESTS:yes ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Blocked by required conditions
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g CTIMETESTS:yes]]) (push) Blocked by required conditions
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g CTIMETESTS:no]]) (push) Blocked by required conditions
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g -O3 CTIMETESTS:yes ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Blocked by required conditions
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g CTIMETESTS:yes]]) (push) Blocked by required conditions
CI / i686 (mingw32-w64): Windows (Debian stable, Wine) (push) Blocked by required conditions
CI / x86_64 (mingw32-w64): Windows (Debian stable, Wine) (push) Blocked by required conditions
CI / x86_64: macOS Sequoia, Valgrind (map[BPPP:yes CC:gcc CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HA… (push) Waiting to run
CI / x86_64: macOS Sequoia, Valgrind (map[BPPP:yes CHILLDKG:yes CPPFLAGS:-DVERIFY CTIMETESTS:no ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCH… (push) Waiting to run
CI / x86_64: macOS Sequoia, Valgrind (map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:y… (push) Waiting to run
CI / x86_64: macOS Sequoia, Valgrind (map[BPPP:yes CHILLDKG:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes FROST:yes GENERATOR:yes ICEBERG:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTION… (push) Waiting to run
CI / x86_64: macOS Sequoia, Valgrind (map[BUILD:distcheck]) (push) Waiting to run
CI / x86_64: macOS Sequoia, Valgrind (map[ECMULTGENKB:2 ECMULTWINDOW:4 WIDEMUL:int128_struct]) (push) Waiting to run
CI / x86_64: macOS Sequoia, Valgrind (map[RECOVERY:yes WIDEMUL:int128]) (push) Waiting to run
CI / ARM64: macOS Sonoma (map[BPPP:yes CC:gcc ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:yes WHITELIST:yes WID… (push) Waiting to run
CI / ARM64: macOS Sonoma (map[BPPP:yes CPPFLAGS:-DVERIFY ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:yes WHITEL… (push) Waiting to run
CI / ARM64: macOS Sonoma (map[BPPP:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes RECOVERY:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:yes WHITELIST:yes WIDEMUL:in… (push) Waiting to run
CI / ARM64: macOS Sonoma (map[BPPP:yes ECDH:yes ECDSAADAPTOR:yes ECDSA_S2C:yes ELLSWIFT:yes EXPERIMENTAL:yes EXTRAKEYS:yes GENERATOR:yes MUSIG:yes RANGEPROOF:yes SCHNORRSIG:yes SCHNORRSIG_HALFAGG:yes SURJECTIONPROOF:yes WHITELIST:yes WIDEMUL:int128]) (push) Waiting to run
CI / ARM64: macOS Sonoma (map[BUILD:distcheck]) (push) Waiting to run
CI / ARM64: macOS Sonoma (map[ECMULTGENKB:2 ECMULTWINDOW:4 WIDEMUL:int128_struct]) (push) Waiting to run
CI / ARM64: macOS Sonoma (map[RECOVERY:yes WIDEMUL:int128]) (push) Waiting to run
CI / x86 (MSVC): Windows (VS 2022) (push) Waiting to run
CI / x64 (MSVC): Windows (VS 2022, static) (push) Waiting to run
CI / x64 (MSVC): Windows (VS 2022, shared) (push) Waiting to run
CI / x64 (MSVC): Windows (VS 2022, int128_struct with __(u)mulh) (push) Waiting to run
CI / x64 (MSVC): Windows (VS 2022, int128_struct) (push) Waiting to run
CI / x64 (clang-cl): Windows (VS 2022, static) (push) Waiting to run
CI / x64 (clang-cl): Windows (VS 2022, shared) (push) Waiting to run
CI / x64 (clang-cl): Windows (VS 2022, int128_struct with __(u)mulh) (push) Waiting to run
CI / x64 (clang-cl): Windows (VS 2022, int128_struct) (push) Waiting to run
CI / x64 (MSVC): C++ (public headers) (push) Waiting to run
CI / C++ -fpermissive (entire project) (map[env_vars:map[]]) (push) Blocked by required conditions
CI / C++ (public headers) (push) Blocked by required conditions
CI / SageMath prover (push) Waiting to run
CI / release (push) Waiting to run

doc/prefractal.md writes down the three deliberate deviations from BIP 445
where a reviewer will find them, since none of them is visible from the API
and two of them are actively counterintuitive:

1. b_frost does not commit to the message, because the target protocols
   publish the group's wire nonce before the message exists. The outer b_musig
   does commit to it and multiplies b_frost everywhere it appears.

2. There is no g_frost factor, and the reason is NOT that the tweak cache is
   the identity. The frost key-side factor is g*gacc; an identity cache gives
   gacc = 1, but g is still -1 for every odd-Y threshold key. The doc spells
   this out because "identity cache, therefore no key term" is the plausible
   wrong reason, and acting on it yields a signer that works for even-Y groups
   and fails for odd-Y ones.

3. The frost tweak cache must be the identity, checked at signing and
   verification and not only at aggregation, so the key a member signs under
   is tied to the cache that was validated.

It also records the two caller obligations the module cannot enforce - one
secnonce per signature, and round-two signers EQUAL to round-one contributors
- and notes that iceberg tolerates a round-two subset where this module must
not, since callers moving between the two would otherwise transpose the rule.

The build section documents the three-way ordering constraint rather than
leaving the next person to copy iceberg's positions, which are wrong for
configure.ac.

The ctime test adds a 2-of-2 nested group with a stock musig cosigner, taken
as far as one signature share, marking the threshold key, the secret shares
and the session randomness as secret and everything else as public. Nonce
generation passes msg = NULL, which is how the module is actually driven.

Verified: valgrind -q ./build/bin/ctime_tests exits 0, so nothing in the
prefractal signing path branches on secret data. Full test suite green.

The example program from the plan's optional list is not included; the test
suite covers the same ground and the doc carries the usage rules.
This commit is contained in:
Kgothatso Ngako
2026-09-04 01:06:14 +02:00
parent f0014c4c49
commit b66c757b7f
2 changed files with 363 additions and 0 deletions

265
doc/prefractal.md Normal file
View File

@@ -0,0 +1,265 @@
# Prefractal: a nested FROST+MuSig2 signer
**WARNING: EXPERIMENTAL.** Neither the scheme nor this implementation has been
reviewed by anyone outside the project. Do not use it to protect anything of
value. The construction comes from [frosty-musig][frosty], which is unaudited
research code, and it is built on this repository's `frost` module, which is
itself marked experimental and unstable.
[frosty]: https://github.com/jesseposner/frosty-musig
## What it does
It lets a FROST `t`-of-`n` group occupy **one participant slot** of an ordinary
MuSig2 (BIP 327) session. The group publishes one ordinary MuSig2 public nonce
and one ordinary MuSig2 partial signature. Cosigners need no support for any of
this and cannot tell a group is involved.
The motivating shape is a 2-of-2 taproot output where one of the two
"participants" is really a threshold group.
## The signing equation
Each member `i` of the participating set computes
```
s_i = k1_i + b_frost * b_musig * k2_i + e * a * lambda_i * g * gacc * d_i
```
- `k1_i`, `k2_i` — the member's two nonce scalars, both negated iff the OUTER
final nonce has odd Y.
- `b_frost` — this module's nonce-binding coefficient (below).
- `b_musig`, `e`, `a`, `g`, `gacc` — all from the OUTER MuSig2 session: the
nonce coefficient, the BIP 340 challenge over the aggregate key, the
key-aggregation coefficient of the group's threshold public key, and the
aggregate key's parity bookkeeping.
- `lambda_i` — the member's Lagrange interpolating value over the participating
set.
- `d_i` — the member's secret share.
The group's wire nonce is its FROST aggregate nonce with the second component
premultiplied by `b_frost`:
```
pubnonce = (R1, b_frost * R2)
```
The aggregator sums the members' shares. That is a plain sum, with no
interpolation, because `lambda_i` is already folded into each share.
## Three deliberate deviations from BIP 445
These are the parts a reviewer should look at hardest. Each one is a
considered trade, and each one is enforced or pinned somewhere in the code.
### 1. `b_frost` does not commit to the message
BIP 445's nonce coefficient hashes the message. This module's does not:
```
b_frost = tagged_hash("Prefractal/noncecoef",
ser32(u) || sorted ser32 ids || aggnonce66 ||
cbytes_ext(thresh_pk))
```
**Why.** The protocols this module targets publish the group's wire nonce
*before the message exists*. A lightning channel's funding signer publishes a
verification nonce at commitment number `N` long before the transaction that
nonce will sign has been built. A coefficient that hashed the message could not
be computed in round one and rebuilt identically in round two.
**Why it is not fatal.** The outer coefficient `b_musig` *does* commit to the
message, via `secp256k1_musig_nonce_process_internal`, and it multiplies
`b_frost` in every term where `b_frost` appears. The product binds the message.
This is the same trade the `iceberg` module makes, for the same reason, with
its own `Iceberg/noncecoef` tag.
**What is different from BIP 445's preimage.** The message is dropped, and the
threshold public key is hashed in its full 33-byte extended encoding rather
than x-only, because the key is used as a full point everywhere downstream (see
deviation 2) and the binding should cover the point that is actually in play.
**Pinned by.** `run_prefractal_midstate_test` checks the tagged-hash constant
against a freshly initialised one. Nothing else in the tree would notice a
changed `b_frost`; it would simply produce signatures that do not verify.
### 2. There is no `g_frost` factor
Stock FROST negates the secret share when the threshold public key has odd Y:
```c
/* frost/session_impl.h:664 */
session_i->g_times_gacc_parity = cache_i->gacc_parity ^ pk_odd;
/* frost/session_impl.h:797-800 */
if (session_i->g_times_gacc_parity) {
secp256k1_scalar_negate(&d, &d);
}
```
It does this because standalone FROST produces a BIP 340 x-only signature, so
the effective secret is normalised to the even-Y representative of the
threshold key.
**Here that must not happen.** The threshold public key is an *inner
participant* of the outer key aggregation. It enters `secp256k1_musig_pubkey_agg`
as a full 33-byte point, and MuSig2 does no per-participant parity
normalisation: the only key-side flip is at the aggregate level, off the OUTER
keyagg cache. So the group's members must reconstruct `d` with `d*G = thresh_pk`
exactly as dealt, whatever its Y parity.
**The trap.** It is tempting to say "the FROST tweak cache is the identity, so
the frost key-side factor is 1". That is **false**. The factor is `g * gacc`.
An identity cache gives `gacc = 1`, but `g` is still `-1` for every threshold
key with odd Y — roughly half of all groups. An implementation that reused
`secp256k1_frost_get_session_values`'s key-side handling would produce a signer
that works for even-Y groups and fails for odd-Y ones.
**Pinned by.** The test suite carries two *fixed* threshold secret keys, one of
each Y parity, and `run_prefractal_odd_y_group_key_test` asserts the parity of
its own fixture so it cannot quietly stop testing what it is named after. This
was verified by mutation: injecting the `pk_odd` negation makes the odd-Y test
fail while the even-Y one still passes. With a randomly seeded fixture that
would have been a coin flip per run.
### 3. The FROST tweak cache must be the identity
`tacc == 0` and `gacc_parity == 0`, checked by every entry point that takes a
cache.
**Why.** The target protocols tweak only the *outer* aggregate key — the BIP
341 key-path tweak is applied to the MuSig2 keyagg cache and handled by the
stock outer session. A frost-level tweak would add an `e * g * tacc` term that
the aggregator would have to fold in, and this module's aggregator is a plain
sum.
**Where it is checked.** In `secp256k1_prefractal_sign` and
`secp256k1_prefractal_partial_sig_verify`, not only in
`secp256k1_prefractal_partial_sig_agg`. Checking only at aggregation would be
too late and too weak: the signing path would never see the cache, so nothing
would tie the key a member signed under to the cache that was validated. `sign`
and `partial_sig_verify` additionally require `thresh_pk` to equal the cache's
own key, so the two arguments cannot disagree.
A tweak-aware aggregation variant (folding `e * g_musig * tacc`, as
frosty-musig's `nested_frost_partial_sig_agg` does) is a possible later
extension. It is not implemented.
## Rules the caller must follow
### One secnonce, one signature
The usual FROST rule, and this module cannot enforce it any better than FROST
can. `secp256k1_prefractal_sign` wipes the secnonce, so a second call with the
same one fails — including when the first call failed for some other reason,
which is why a member whose signing attempt was refused must generate a fresh
nonce rather than retry.
Deployments that derive nonces deterministically from a session label (which is
how a protocol gets a nonce it can publish early and rebuild later) inherit a
sharper version of the rule: **one label signs one message, group-wide**. Two
different messages under one label leak the secret share, and nothing raises an
error.
### The round-two signer set must equal the round-one set
Not a subset — the same set.
`lambda_i` and the aggregate nonce are both defined over the participating set.
If round one aggregates over `C` and only `S ⊂ C` signs, then the nonce terms
of `C \ S` are still in `R` while their key shares are absent from `sum(s_i)`,
and `sum_{i in S} lambda_i^C * d_i != d`. The result is an invalid signature
with no error raised at signing time.
This is worth stating explicitly because the `iceberg` module in this same
repository *does* tolerate a subset: its `2t-1` / `t` split comes from VSS
interpolation over the contributions, and FROST has no equivalent. Callers
porting between the two must not transpose the rule.
`run_prefractal_partial_sig_verify_test` covers the detectable half of this: a
share made for one signer set does not verify under another.
### Nonces at infinity
A FROST aggregate nonce component may legitimately be the point at infinity
(BIP 445 NonceAgg), but a MuSig2 public nonce has no encoding for one. Both
columns can reach infinity independently — the first is passed through
unscaled, the second only after the `b_frost` multiplication — and
`secp256k1_prefractal_nonce_agg` refuses both. Such a session has to be
restarted with fresh nonces.
## API
All four functions are sessionless: every call takes its session parameters
explicitly, so there are no opaque session objects, no new magics and no
`*_SIZE` constants to keep synchronised across bindings.
| Function | Role |
| --- | --- |
| `secp256k1_prefractal_nonce_agg` | round one: group wire nonce + unscaled aggnonce |
| `secp256k1_prefractal_sign` | round two: one member's partial signature |
| `secp256k1_prefractal_partial_sig_verify` | identifiable abort |
| `secp256k1_prefractal_partial_sig_agg` | sum shares into a MuSig2 partial signature |
`aggnonce_out` from `nonce_agg` is an internal value, not a wire value: it is
the *unscaled* FROST aggregate, and it must be handed back to `sign` and
`partial_sig_verify` unchanged. The wire value is `pubnonce_out`, an ordinary
66-byte MuSig2 public nonce.
Members generate their nonces with the stock `secp256k1_frost_nonce_gen`. This
module adds no nonce generation of its own.
## Relationship to the other modules
- **`frost`** stays pure, vector-pinned BIP 445. This module deliberately does
not live inside it: the deviations above are not BIP 445, and keeping them
behind their own opt-in flag gives auditors a clean scope boundary. It also
keeps `frost`'s dependency graph honest — `frost` depends only on
`schnorrsig`, and every pure-FROST consumer would otherwise have to build
`musig` too.
- **`musig`** is used unmodified, through its internals. Cosigners run stock
MuSig2 throughout.
- **`iceberg`** solves the same outer problem with a different inner scheme. The
two differ in ways that do not transfer: iceberg's quorum is `2t-1` in round
one and `t` in round two and it tolerates a round-two subset; prefractal uses
`t` in both rounds and requires set equality. Iceberg cannot express 2-of-2 or
3-of-4; prefractal can.
## Build
The module depends on both `frost` and `musig` and forces them on.
```
cmake -B build -DSECP256K1_ENABLE_MODULE_PREFRACTAL=ON -DSECP256K1_BUILD_TESTS=ON
cmake --build build && ./build/bin/tests --target=prefractal
```
```
./autogen.sh
./configure --enable-experimental --enable-module-prefractal
make && make check
```
Three files order their module blocks differently, and the constraints point in
opposite directions. Anyone adding a module by copying this one should read
this rather than copying `iceberg`'s positions:
- `src/secp256k1.c` — the include goes **after** `frost` and `musig`, because
the module calls their `static` internals and the whole library is one
translation unit.
- `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.
`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. Neither build system can distinguish an explicit
disable from the default once both are in the cache, so enabling `prefractal`
simply implies `frost`; the guard is kept for `musig`, where it still means what
it says.

View File

@@ -61,6 +61,10 @@
#include "../include/secp256k1_chilldkg.h"
#endif
#ifdef ENABLE_MODULE_PREFRACTAL
#include "../include/secp256k1_prefractal.h"
#endif
#ifdef ENABLE_MODULE_ICEBERG
#include "../include/secp256k1_iceberg.h"
#include "../include/secp256k1_iceberg_dealer.h"
@@ -593,6 +597,100 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
}
#endif
#ifdef ENABLE_MODULE_PREFRACTAL
{
/* A 2-of-2 nested group with one stock musig cosigner, taken as far as
* one signature share. Secret here is the threshold key, the secret
* shares derived from it, and the session randomness the nonces come
* from. Not secret: the identifiers, the public shares, the threshold
* public key, both aggregate nonces, the outer keyagg cache, b_frost,
* and the resulting partial signature. */
unsigned char thresh_seckey[32];
unsigned char secshares[2 * 32];
unsigned char session_secrand[2][32];
unsigned char cosigner_seckey[32];
secp256k1_pubkey thresh_pk, pubshares[2], cosigner_pk;
const secp256k1_pubkey *outer_pubkeys[2];
uint32_t pf_ids[2] = { 0, 1 };
secp256k1_frost_tweak_cache pf_cache;
secp256k1_frost_secnonce pf_secnonce[2];
secp256k1_frost_pubnonce pf_pubnonce[2];
const secp256k1_frost_pubnonce *pf_pubnonce_ptrs[2];
secp256k1_frost_aggnonce pf_aggnonce;
secp256k1_frost_partial_sig pf_partial_sig;
secp256k1_xonly_pubkey outer_xonly;
secp256k1_musig_keyagg_cache outer_cache;
secp256k1_musig_pubnonce group_pubnonce, cosigner_pubnonce;
const secp256k1_musig_pubnonce *just_cosigner[1];
secp256k1_musig_secnonce cosigner_secnonce;
secp256k1_musig_aggnonce cosigner_aggnonce;
unsigned char cosigner_secrand[32];
pf_pubnonce_ptrs[0] = &pf_pubnonce[0];
pf_pubnonce_ptrs[1] = &pf_pubnonce[1];
SECP256K1_CHECKMEM_DEFINE(key, 32);
memcpy(thresh_seckey, key, sizeof(thresh_seckey));
thresh_seckey[0] = thresh_seckey[0] + 4;
memcpy(cosigner_seckey, key, sizeof(cosigner_seckey));
cosigner_seckey[0] = cosigner_seckey[0] + 5;
memcpy(session_secrand[0], key, 32);
session_secrand[0][0] = session_secrand[0][0] + 6;
memcpy(session_secrand[1], key, 32);
session_secrand[1][0] = session_secrand[1][0] + 7;
memcpy(cosigner_secrand, key, 32);
cosigner_secrand[0] = cosigner_secrand[0] + 8;
SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg));
SECP256K1_CHECKMEM_UNDEFINE(thresh_seckey, sizeof(thresh_seckey));
ret = secp256k1_frost_trusted_dealer_keygen(ctx, secshares, &thresh_pk, pubshares, 2, 2, thresh_seckey);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_DEFINE(&thresh_pk, sizeof(thresh_pk));
SECP256K1_CHECKMEM_DEFINE(pubshares, sizeof(pubshares));
CHECK(secp256k1_frost_tweak_cache_init(ctx, &pf_cache, &thresh_pk) == 1);
/* The outer aggregation and the cosigner's round one are entirely
* public as far as this module is concerned. */
SECP256K1_CHECKMEM_UNDEFINE(cosigner_seckey, sizeof(cosigner_seckey));
ret = secp256k1_ec_pubkey_create(ctx, &cosigner_pk, cosigner_seckey);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_DEFINE(&cosigner_pk, sizeof(cosigner_pk));
outer_pubkeys[0] = &thresh_pk;
outer_pubkeys[1] = &cosigner_pk;
CHECK(secp256k1_musig_pubkey_agg(ctx, &outer_xonly, &outer_cache, outer_pubkeys, 2) == 1);
SECP256K1_CHECKMEM_UNDEFINE(cosigner_secrand, sizeof(cosigner_secrand));
ret = secp256k1_musig_nonce_gen(ctx, &cosigner_secnonce, &cosigner_pubnonce, cosigner_secrand, cosigner_seckey, &cosigner_pk, msg, &outer_cache, NULL);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_DEFINE(&cosigner_pubnonce, sizeof(cosigner_pubnonce));
just_cosigner[0] = &cosigner_pubnonce;
CHECK(secp256k1_musig_nonce_agg(ctx, &cosigner_aggnonce, just_cosigner, 1) == 1);
/* Group round one. msg is NULL: the wire nonce is published before the
* message is known. */
for (i = 0; i < 2; i++) {
SECP256K1_CHECKMEM_UNDEFINE(session_secrand[i], 32);
SECP256K1_CHECKMEM_UNDEFINE(&secshares[32 * i], 32);
ret = secp256k1_frost_nonce_gen(ctx, &pf_secnonce[i], &pf_pubnonce[i], session_secrand[i], &secshares[32 * i], &pubshares[i], NULL, NULL, 0, NULL, 0);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_DEFINE(&pf_pubnonce[i], sizeof(pf_pubnonce[i]));
}
/* Aggregation is over published nonces only, so it is public. */
CHECK(secp256k1_prefractal_nonce_agg(ctx, &group_pubnonce, &pf_aggnonce, pf_pubnonce_ptrs, pf_ids, 2, &thresh_pk) == 1);
/* The share and the secnonce are secret; the partial signature is the
* public output. */
ret = secp256k1_prefractal_sign(ctx, &pf_partial_sig, &pf_secnonce[0], &secshares[0], pf_ids[0], pf_ids, pubshares, 2, &pf_aggnonce, &thresh_pk, &pf_cache, &outer_cache, &cosigner_aggnonce, msg);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_DEFINE(&pf_partial_sig, sizeof(pf_partial_sig));
}
#endif
#ifdef ENABLE_MODULE_ICEBERG
{
/* A 3-of-5 group, dealt from `key` and taken as far as one signature