Merge branch 'prefractal'
Some checks failed
CI / Build arm64 Docker image (push) Has been cancelled
CI / Build x64 Docker image (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / i686: Linux (Debian stable) (clang --target=i686-pc-linux-gnu -isystem /usr/i686-linux-gnu/include, map[env_vars:map[]]) (push) Has been cancelled
CI / i686: Linux (Debian stable) (i686-linux-gnu-gcc, map[env_vars:map[]]) (push) Has been cancelled
CI / s390x (big-endian): Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Has been cancelled
CI / ARM32: Linux (Debian stable, QEMU) (map[env_vars:map[ASM:arm32 EXPERIMENTAL:yes]]) (push) Has been cancelled
CI / ARM32: Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (clang, map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (gcc, map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[]]) (push) Has been cancelled
CI / ppc64le: Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Has been cancelled
CI / Valgrind arm64 (memcheck) (push) Has been cancelled
CI / Valgrind i686 (memcheck) (push) Has been cancelled
CI / Valgrind x64 (memcheck) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:auto CC:clang]]) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:auto CC:i686-linux-gnu-gcc HOST:i686-linux-gnu]]) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:no CC:clang ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:no CC:i686-linux-gnu-gcc ECMULTGENKB:2 ECMULTWINDOW:2 HOST:i686-linux-gnu]]) (push) Has been cancelled
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g CTIMETESTS:no]]) (push) Has been cancelled
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g -O3 CTIMETESTS:yes ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g CTIMETESTS:yes]]) (push) Has been cancelled
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g CTIMETESTS:no]]) (push) Has been cancelled
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g -O3 CTIMETESTS:yes ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g CTIMETESTS:yes]]) (push) Has been cancelled
CI / i686 (mingw32-w64): Windows (Debian stable, Wine) (push) Has been cancelled
CI / x86_64 (mingw32-w64): Windows (Debian stable, Wine) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: macOS Sequoia, Valgrind (map[BUILD:distcheck]) (push) Has been cancelled
CI / x86_64: macOS Sequoia, Valgrind (map[ECMULTGENKB:2 ECMULTWINDOW:4 WIDEMUL:int128_struct]) (push) Has been cancelled
CI / x86_64: macOS Sequoia, Valgrind (map[RECOVERY:yes WIDEMUL:int128]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / ARM64: macOS Sonoma (map[BUILD:distcheck]) (push) Has been cancelled
CI / ARM64: macOS Sonoma (map[ECMULTGENKB:2 ECMULTWINDOW:4 WIDEMUL:int128_struct]) (push) Has been cancelled
CI / ARM64: macOS Sonoma (map[RECOVERY:yes WIDEMUL:int128]) (push) Has been cancelled
CI / x86 (MSVC): Windows (VS 2022) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, static) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, shared) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, int128_struct with __(u)mulh) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, int128_struct) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, static) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, shared) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, int128_struct with __(u)mulh) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, int128_struct) (push) Has been cancelled
CI / x64 (MSVC): C++ (public headers) (push) Has been cancelled
CI / C++ -fpermissive (entire project) (map[env_vars:map[]]) (push) Has been cancelled
CI / C++ (public headers) (push) Has been cancelled
CI / SageMath prover (push) Has been cancelled
CI / release (push) Has been cancelled
Some checks failed
CI / Build arm64 Docker image (push) Has been cancelled
CI / Build x64 Docker image (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ELLSWIFT:yes EXTRAKEYS:yes MUSIG:yes RECOVERY:yes SCHNORRSIG:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ASM:x86_64 ELLSWIFT:yes WIDEMUL:int128]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[BENCH:no BUILD:distcheck CTIMETESTS:no WITH_VALGRIND:no]]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[CFLAGS:-O0 CTIMETESTS:no]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[CPPFLAGS:-DDETERMINISTIC]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ECMULTGENKB:86 ECMULTWINDOW:4]]) (push) Has been cancelled
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) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[ELLSWIFT:yes WIDEMUL:int128_struct]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[RECOVERY:yes WIDEMUL:int64]]) (push) Has been cancelled
CI / x86_64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[WIDEMUL:int128]]) (push) Has been cancelled
CI / i686: Linux (Debian stable) (clang --target=i686-pc-linux-gnu -isystem /usr/i686-linux-gnu/include, map[env_vars:map[]]) (push) Has been cancelled
CI / i686: Linux (Debian stable) (i686-linux-gnu-gcc, map[env_vars:map[]]) (push) Has been cancelled
CI / s390x (big-endian): Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Has been cancelled
CI / ARM32: Linux (Debian stable, QEMU) (map[env_vars:map[ASM:arm32 EXPERIMENTAL:yes]]) (push) Has been cancelled
CI / ARM32: Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (clang, map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (clang-snapshot, map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (gcc, map[env_vars:map[]]) (push) Has been cancelled
CI / arm64: Linux (Debian stable) (gcc-snapshot, map[env_vars:map[]]) (push) Has been cancelled
CI / ppc64le: Linux (Debian stable, QEMU) (map[env_vars:map[]]) (push) Has been cancelled
CI / Valgrind arm64 (memcheck) (push) Has been cancelled
CI / Valgrind i686 (memcheck) (push) Has been cancelled
CI / Valgrind x64 (memcheck) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:auto CC:clang]]) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:auto CC:i686-linux-gnu-gcc HOST:i686-linux-gnu]]) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:no CC:clang ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / UBSan, ASan, LSan (map[env_vars:map[ASM:no CC:i686-linux-gnu-gcc ECMULTGENKB:2 ECMULTWINDOW:2 HOST:i686-linux-gnu]]) (push) Has been cancelled
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g CTIMETESTS:no]]) (push) Has been cancelled
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g -O3 CTIMETESTS:yes ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / MSan (clang, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g CTIMETESTS:yes]]) (push) Has been cancelled
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g CTIMETESTS:no]]) (push) Has been cancelled
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g -O3 CTIMETESTS:yes ECMULTGENKB:2 ECMULTWINDOW:2]]) (push) Has been cancelled
CI / MSan (clang-snapshot, map[env_vars:map[CFLAGS:-fsanitize=memory -fsanitize-recover=memory -g CTIMETESTS:yes]]) (push) Has been cancelled
CI / i686 (mingw32-w64): Windows (Debian stable, Wine) (push) Has been cancelled
CI / x86_64 (mingw32-w64): Windows (Debian stable, Wine) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / x86_64: macOS Sequoia, Valgrind (map[BUILD:distcheck]) (push) Has been cancelled
CI / x86_64: macOS Sequoia, Valgrind (map[ECMULTGENKB:2 ECMULTWINDOW:4 WIDEMUL:int128_struct]) (push) Has been cancelled
CI / x86_64: macOS Sequoia, Valgrind (map[RECOVERY:yes WIDEMUL:int128]) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / ARM64: macOS Sonoma (map[BUILD:distcheck]) (push) Has been cancelled
CI / ARM64: macOS Sonoma (map[ECMULTGENKB:2 ECMULTWINDOW:4 WIDEMUL:int128_struct]) (push) Has been cancelled
CI / ARM64: macOS Sonoma (map[RECOVERY:yes WIDEMUL:int128]) (push) Has been cancelled
CI / x86 (MSVC): Windows (VS 2022) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, static) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, shared) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, int128_struct with __(u)mulh) (push) Has been cancelled
CI / x64 (MSVC): Windows (VS 2022, int128_struct) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, static) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, shared) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, int128_struct with __(u)mulh) (push) Has been cancelled
CI / x64 (clang-cl): Windows (VS 2022, int128_struct) (push) Has been cancelled
CI / x64 (MSVC): C++ (public headers) (push) Has been cancelled
CI / C++ -fpermissive (entire project) (map[env_vars:map[]]) (push) Has been cancelled
CI / C++ (public headers) (push) Has been cancelled
CI / SageMath prover (push) Has been cancelled
CI / release (push) Has been cancelled
Adds the prefractal module: a nested FROST+MuSig2 scheme letting a FROST t-of-n group occupy one participant slot of an ordinary MuSig2 (BIP 327) session. Three deliberate deviations from BIP 445, all documented in doc/prefractal.md: the nonce coefficient does not commit to the message (so a group can publish its wire nonce before the message exists); there is no g_frost factor, because the threshold key is an inner participant of the outer aggregation and is used as a full point; and the frost tweak cache must be the identity. Merged the same way the iceberg module was.
This commit is contained in:
@@ -54,6 +54,7 @@ option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_FROST "Enable FROST module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_CHILLDKG "Enable ChillDKG module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_ICEBERG "Enable Iceberg threshold-MuSig module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_PREFRACTAL "Enable Prefractal nested FROST+MuSig2 module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON)
|
||||
|
||||
option(SECP256K1_ENABLE_MODULE_GENERATOR "Enable NUMS generator module." ON)
|
||||
@@ -303,6 +304,7 @@ message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG
|
||||
message(" frost ............................... ${SECP256K1_ENABLE_MODULE_FROST}")
|
||||
message(" chilldkg ............................ ${SECP256K1_ENABLE_MODULE_CHILLDKG}")
|
||||
message(" iceberg ............................. ${SECP256K1_ENABLE_MODULE_ICEBERG}")
|
||||
message(" prefractal .......................... ${SECP256K1_ENABLE_MODULE_PREFRACTAL}")
|
||||
message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}")
|
||||
message(" generator ........................... ${SECP256K1_ENABLE_MODULE_GENERATOR}")
|
||||
message(" rangeproof .......................... ${SECP256K1_ENABLE_MODULE_RANGEPROOF}")
|
||||
|
||||
@@ -396,6 +396,10 @@ if ENABLE_MODULE_CHILLDKG
|
||||
include src/modules/chilldkg/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_PREFRACTAL
|
||||
include src/modules/prefractal/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_ICEBERG
|
||||
include src/modules/iceberg/Makefile.am.include
|
||||
endif
|
||||
|
||||
31
configure.ac
31
configure.ac
@@ -255,6 +255,11 @@ AC_ARG_ENABLE(module_iceberg,
|
||||
[],
|
||||
[SECP_SET_DEFAULT([enable_module_iceberg], [no], [yes])])
|
||||
|
||||
AC_ARG_ENABLE(module_prefractal,
|
||||
AS_HELP_STRING([--enable-module-prefractal],[enable Prefractal nested FROST+MuSig2 module (experimental)]),
|
||||
[],
|
||||
[SECP_SET_DEFAULT([enable_module_prefractal], [no], [yes])])
|
||||
|
||||
# Test-only override of the (autodetected by the C code) "widemul" setting.
|
||||
# Legal values are:
|
||||
# * int64 (for [u]int64_t),
|
||||
@@ -525,6 +530,27 @@ if test x"$enable_module_ellswift" = x"yes"; then
|
||||
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1"
|
||||
fi
|
||||
|
||||
# This block must stay ahead of the musig and frost blocks below. The
|
||||
# enable_module_* assignments here are only observed by blocks that run after
|
||||
# them, and frost defaults to "no": placing this at the iceberg block's
|
||||
# position (further down) would leave -DENABLE_MODULE_FROST=1 unemitted while
|
||||
# AM_CONDITIONAL still saw the mutation, which builds frost's sources into a
|
||||
# library whose secp256k1.c never included them.
|
||||
if test x"$enable_module_prefractal" = x"yes"; then
|
||||
# musig defaults to yes, so "no" here really does mean the user disabled it.
|
||||
if test x"$enable_module_musig" = x"no"; then
|
||||
AC_MSG_ERROR([Module dependency error: You have disabled the musig module explicitly, but it is required by the prefractal module.])
|
||||
fi
|
||||
# frost defaults to no, so the same test would reject every prefractal build.
|
||||
# SECP_SET_DEFAULT only runs in AC_ARG_ENABLE's action-if-not-given branch,
|
||||
# so telling an explicit --disable-module-frost from the default would mean
|
||||
# changing frost's own declaration; enabling prefractal simply implies frost
|
||||
# instead. The CMake block does the same, for the same reason.
|
||||
enable_module_frost=yes
|
||||
enable_module_musig=yes
|
||||
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_PREFRACTAL=1"
|
||||
fi
|
||||
|
||||
if test x"$enable_module_musig" = x"yes"; then
|
||||
if test x"$enable_module_schnorrsig" = x"no"; then
|
||||
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
|
||||
@@ -625,6 +651,9 @@ if test x"$enable_experimental" = x"no"; then
|
||||
if test x"$set_asm" = x"arm32"; then
|
||||
AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.])
|
||||
fi
|
||||
if test x"$enable_module_prefractal" = x"yes"; then
|
||||
AC_MSG_ERROR([Prefractal module is experimental. Use --enable-experimental to allow.])
|
||||
fi
|
||||
if test x"$enable_module_frost" = x"yes"; then
|
||||
AC_MSG_ERROR([FROST module is experimental. Use --enable-experimental to allow.])
|
||||
fi
|
||||
@@ -674,6 +703,7 @@ AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG_HALFAGG], [test x"$enable_module_schnor
|
||||
AM_CONDITIONAL([ENABLE_MODULE_FROST], [test x"$enable_module_frost" = x"yes"])
|
||||
AM_CONDITIONAL([ENABLE_MODULE_CHILLDKG], [test x"$enable_module_chilldkg" = x"yes"])
|
||||
AM_CONDITIONAL([ENABLE_MODULE_ICEBERG], [test x"$enable_module_iceberg" = x"yes"])
|
||||
AM_CONDITIONAL([ENABLE_MODULE_PREFRACTAL], [test x"$enable_module_prefractal" = x"yes"])
|
||||
AM_CONDITIONAL([USE_REDUCED_SURJECTION_PROOF_SIZE], [test x"$use_reduced_surjection_proof_size" = x"yes"])
|
||||
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
|
||||
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
|
||||
@@ -718,6 +748,7 @@ echo " module schnorrsig-halfagg = $enable_module_schnorrsig_halfagg"
|
||||
echo " module frost = $enable_module_frost"
|
||||
echo " module chilldkg = $enable_module_chilldkg"
|
||||
echo " module iceberg = $enable_module_iceberg"
|
||||
echo " module prefractal = $enable_module_prefractal"
|
||||
echo
|
||||
echo " asm = $set_asm"
|
||||
echo " ecmult window size = $set_ecmult_window"
|
||||
|
||||
265
doc/prefractal.md
Normal file
265
doc/prefractal.md
Normal 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.
|
||||
235
include/secp256k1_prefractal.h
Normal file
235
include/secp256k1_prefractal.h
Normal file
@@ -0,0 +1,235 @@
|
||||
#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 */
|
||||
@@ -73,6 +73,32 @@ if(SECP256K1_ENABLE_MODULE_ELLSWIFT)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ellswift.h)
|
||||
endif()
|
||||
|
||||
# Must precede the musig and frost blocks below: the set() calls here are what
|
||||
# force those modules on, and they are only observed by blocks that run after.
|
||||
if(SECP256K1_ENABLE_MODULE_PREFRACTAL)
|
||||
# musig defaults to ON, so the guard every other block in this file uses
|
||||
# reads as "the user turned it off explicitly" and is meaningful here.
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_MUSIG AND NOT SECP256K1_ENABLE_MODULE_MUSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the musig module explicitly, but it is required by the prefractal module.")
|
||||
endif()
|
||||
# frost is the first default-OFF module anything depends on. The same guard
|
||||
# cannot be used: option() always leaves the variable DEFINED, so for a
|
||||
# default-OFF module "DEFINED AND NOT" is true by default and would reject
|
||||
# every prefractal build. There is no way to tell an explicit -D...=OFF from
|
||||
# the default once both are in the cache, so enabling prefractal simply
|
||||
# implies frost.
|
||||
set(SECP256K1_ENABLE_MODULE_FROST ON)
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG ON)
|
||||
# Also lift them into the parent scope so the top-level configuration
|
||||
# summary, which runs after add_subdirectory(src), reports what was actually
|
||||
# built. Without this a prefractal-only configure prints "frost OFF" while
|
||||
# compiling frost in.
|
||||
set(SECP256K1_ENABLE_MODULE_FROST ON PARENT_SCOPE)
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG ON PARENT_SCOPE)
|
||||
add_compile_definitions(ENABLE_MODULE_PREFRACTAL=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_prefractal.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ICEBERG)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_MUSIG AND NOT SECP256K1_ENABLE_MODULE_MUSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the musig module explicitly, but it is required by the iceberg module.")
|
||||
|
||||
@@ -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
|
||||
|
||||
4
src/modules/prefractal/Makefile.am.include
Normal file
4
src/modules/prefractal/Makefile.am.include
Normal file
@@ -0,0 +1,4 @@
|
||||
include_HEADERS += include/secp256k1_prefractal.h
|
||||
noinst_HEADERS += src/modules/prefractal/main_impl.h
|
||||
noinst_HEADERS += src/modules/prefractal/session_impl.h
|
||||
noinst_HEADERS += src/modules/prefractal/tests_impl.h
|
||||
14
src/modules/prefractal/main_impl.h
Normal file
14
src/modules/prefractal/main_impl.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/***********************************************************************
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_MODULE_PREFRACTAL_MAIN_H
|
||||
#define SECP256K1_MODULE_PREFRACTAL_MAIN_H
|
||||
|
||||
/* One layer only. Everything this module does is a session computation over
|
||||
* values the frost and musig modules already know how to load and save, so
|
||||
* there is no keygen, no serialization and no state of its own here. */
|
||||
#include "session_impl.h"
|
||||
|
||||
#endif
|
||||
557
src/modules/prefractal/session_impl.h
Normal file
557
src/modules/prefractal/session_impl.h
Normal file
@@ -0,0 +1,557 @@
|
||||
/***********************************************************************
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_MODULE_PREFRACTAL_SESSION_IMPL_H
|
||||
#define SECP256K1_MODULE_PREFRACTAL_SESSION_IMPL_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../../../include/secp256k1_prefractal.h"
|
||||
|
||||
/* This module is compiled into the same translation unit as frost and musig
|
||||
* and is included after both, so it may use their static internals. It adds
|
||||
* nothing to either: every value it needs is loaded through their existing
|
||||
* helpers. */
|
||||
#include "../frost/keygen.h"
|
||||
#include "../frost/session.h"
|
||||
#include "../musig/keyagg.h"
|
||||
#include "../musig/session.h"
|
||||
|
||||
#include "../../group.h"
|
||||
#include "../../hash.h"
|
||||
#include "../../scalar.h"
|
||||
#include "../../util.h"
|
||||
|
||||
/* Initializes SHA256 with fixed midstate. This midstate was computed by
|
||||
* applying SHA256 to SHA256("Prefractal/noncecoef")||SHA256("Prefractal/noncecoef"). */
|
||||
static void secp256k1_prefractal_noncecoef_sha256_tagged(secp256k1_sha256 *sha) {
|
||||
static const uint32_t midstate[8] = {
|
||||
0x1d1f5957ul, 0x0c41e94ful, 0x0e1ec98ful, 0x70d8f48eul,
|
||||
0x8fb8bc46ul, 0x1eece984ul, 0x3b015f4bul, 0x443998c6ul
|
||||
};
|
||||
secp256k1_sha256_initialize_midstate(sha, 64, midstate);
|
||||
}
|
||||
|
||||
/* b_frost = H_Prefractal/noncecoef(ser32(u) || sorted ser32 ids || aggnonce66
|
||||
* || cbytes_ext(thresh_pk))
|
||||
*
|
||||
* The preimage is BIP 445's noncecoef preimage with the message dropped and
|
||||
* the group key carried in full rather than x-only. Both changes are
|
||||
* deliberate:
|
||||
*
|
||||
* - No message. The protocols this module serves publish the group's wire
|
||||
* nonce before the transaction being signed exists, so a coefficient that
|
||||
* hashed the message could not be computed in round one and rebuilt in round
|
||||
* two. The outer b_musig does hash the message and multiplies this one, so
|
||||
* the product still binds it. The iceberg module makes the same trade for the
|
||||
* same reason.
|
||||
*
|
||||
* - Full point. The group key is an inner participant of the outer key
|
||||
* aggregation and is used as a full point everywhere downstream, so the
|
||||
* binding covers the point that is actually in play, including its Y parity.
|
||||
* Iceberg's noncecoef hashes its group key the same way.
|
||||
*
|
||||
* Computed here and nowhere else: round one publishes the scaled nonce and
|
||||
* rounds two and three rebuild it, and the three have to agree exactly or every
|
||||
* signature the group produces is invalid. */
|
||||
static void secp256k1_prefractal_noncecoef(const secp256k1_context *ctx, secp256k1_scalar *b_frost, const secp256k1_ge *aggnonce_pts, const uint32_t *ids, size_t n_signers, const secp256k1_ge *thresh_pk) {
|
||||
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(ctx);
|
||||
uint32_t sorted_ids[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
secp256k1_ge pts[2], pk = *thresh_pk;
|
||||
secp256k1_sha256 sha;
|
||||
unsigned char aggnonce66[66];
|
||||
unsigned char buf[33];
|
||||
unsigned char out[32];
|
||||
size_t i;
|
||||
|
||||
pts[0] = aggnonce_pts[0];
|
||||
pts[1] = aggnonce_pts[1];
|
||||
|
||||
secp256k1_prefractal_noncecoef_sha256_tagged(&sha);
|
||||
secp256k1_write_be32(buf, (uint32_t)n_signers);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, buf, 4);
|
||||
/* Sorting keeps the coefficient independent of the caller's ordering, as
|
||||
* BIP 445 serialize_ids does. */
|
||||
secp256k1_frost_sort_ids(sorted_ids, ids, n_signers);
|
||||
for (i = 0; i < n_signers; i++) {
|
||||
secp256k1_write_be32(buf, sorted_ids[i]);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, buf, 4);
|
||||
}
|
||||
/* An aggregate nonce component at infinity is encoded as 33 zero bytes
|
||||
* (BIP 445 cbytes_ext). It cannot survive to the wire, but it can reach
|
||||
* this hash: the caller learns it is unusable from the infinity check in
|
||||
* secp256k1_prefractal_nonce_agg, after this runs. */
|
||||
secp256k1_musig_ge_serialize_ext(&aggnonce66[0], &pts[0]);
|
||||
secp256k1_musig_ge_serialize_ext(&aggnonce66[33], &pts[1]);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, aggnonce66, sizeof(aggnonce66));
|
||||
secp256k1_musig_ge_serialize_ext(buf, &pk);
|
||||
secp256k1_sha256_write(hash_ctx, &sha, buf, 33);
|
||||
secp256k1_sha256_finalize(hash_ctx, &sha, out);
|
||||
secp256k1_scalar_set_b32(b_frost, out, NULL);
|
||||
}
|
||||
|
||||
/* The nonce pair the group publishes, (R1, b_frost*R2), from its unscaled
|
||||
* aggregate. Only the second component is scaled.
|
||||
*
|
||||
* Returns 0 if either output is the point at infinity. A frost aggregate nonce
|
||||
* component may legitimately be infinity, but a musig pubnonce has no encoding
|
||||
* for one, so such a session is unusable and has to be restarted. Both
|
||||
* components are checked: the first is passed through unscaled and can be
|
||||
* infinity on its own if the members' first-column contributions cancel.
|
||||
*
|
||||
* Round one publishes this and the later rounds rebuild it, so it is written
|
||||
* once rather than three times. */
|
||||
static int secp256k1_prefractal_publish_nonce(secp256k1_ge *out, const secp256k1_ge *pre, const secp256k1_scalar *b_frost) {
|
||||
secp256k1_gej r2j, scaled;
|
||||
|
||||
out[0] = pre[0];
|
||||
secp256k1_gej_set_ge(&r2j, &pre[1]);
|
||||
secp256k1_ecmult(&scaled, &r2j, b_frost, NULL);
|
||||
secp256k1_ge_set_gej(&out[1], &scaled);
|
||||
return !secp256k1_ge_is_infinity(&out[0]) && !secp256k1_ge_is_infinity(&out[1]);
|
||||
}
|
||||
|
||||
/* The FROST tweak cache must be the identity: gacc == 1 and tacc == 0.
|
||||
*
|
||||
* With a tweak there would be an extra e*g*tacc term to fold into the
|
||||
* aggregation, which this module does not implement. Checking is not a
|
||||
* formality: a tweaked cache would otherwise be accepted and produce a
|
||||
* signature that fails only at the very end, against the outer aggregate.
|
||||
*
|
||||
* Note this says nothing about g_frost. Stock frost's key-side factor is
|
||||
* g*gacc where g is -1 for an odd-Y threshold key, so an identity cache does
|
||||
* NOT imply a factor of 1 there. This module has no g_frost term at all,
|
||||
* because the threshold key is used as a full point by the outer aggregation
|
||||
* (see the module notes in the public header). */
|
||||
static int secp256k1_prefractal_tweak_cache_is_identity(const secp256k1_context *ctx, secp256k1_ge *thresh_pk_out, const secp256k1_frost_tweak_cache *tweak_cache) {
|
||||
secp256k1_frost_tweak_cache_internal cache_i;
|
||||
|
||||
if (!secp256k1_frost_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
|
||||
return 0;
|
||||
}
|
||||
if (cache_i.gacc_parity != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_scalar_is_zero(&cache_i.tacc)) {
|
||||
return 0;
|
||||
}
|
||||
if (thresh_pk_out != NULL) {
|
||||
*thresh_pk_out = cache_i.thresh_pk;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Everything a member needs to turn its nonce and share into a partial
|
||||
* signature, and everything a verifier needs to check one.
|
||||
*
|
||||
* b0b1 is the product of the outer nonce coefficient and this module's, which
|
||||
* is the factor on the second nonce component. key_coef is e*a*g*gacc, the
|
||||
* factor on lambda_i*d_i. fin_parity says whether the outer final nonce came
|
||||
* out odd, which flips both nonce terms.
|
||||
*
|
||||
* The group's own wire nonce is rebuilt here from the aggregate rather than
|
||||
* taken as an argument, so a member never signs against a nonce a coordinator
|
||||
* chose for it: b_frost is a hash of exactly that aggregate, and a member that
|
||||
* accepted three fabricated aggregates under one label would be answering three
|
||||
* equations in its own secrets.
|
||||
*
|
||||
* This is the prefractal counterpart of secp256k1_iceberg_session_values, and
|
||||
* deliberately NOT of secp256k1_frost_get_session_values: the latter computes a
|
||||
* standalone FROST session, including the x-only normalization of the threshold
|
||||
* key that must not happen here. */
|
||||
static int secp256k1_prefractal_session_values(const secp256k1_context *ctx, secp256k1_scalar *b0b1, secp256k1_scalar *key_coef, int *fin_parity, const secp256k1_frost_aggnonce *aggnonce, const uint32_t *ids, size_t n_signers, const secp256k1_pubkey *thresh_pk, const secp256k1_musig_keyagg_cache *keyagg_cache, const secp256k1_musig_aggnonce *cosigner_aggnonce, const unsigned char *msg32) {
|
||||
secp256k1_keyagg_cache_internal cache_i;
|
||||
secp256k1_ge group_pts[2], cosigner_pts[2], total[2], pk;
|
||||
secp256k1_scalar b_frost, b_musig, a, e;
|
||||
secp256k1_gej acc;
|
||||
unsigned char agg_pk32[32], fin_nonce[32];
|
||||
int i;
|
||||
|
||||
if (!secp256k1_keyagg_cache_load(ctx, &cache_i, keyagg_cache)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_pubkey_load(ctx, &pk, thresh_pk)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_frost_aggnonce_load(ctx, group_pts, aggnonce)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_musig_aggnonce_load(ctx, cosigner_pts, cosigner_aggnonce)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Rebuild the group's published nonce with the same function round one
|
||||
* published it with, then add the cosigners' to it. */
|
||||
secp256k1_prefractal_noncecoef(ctx, &b_frost, group_pts, ids, n_signers, &pk);
|
||||
if (!secp256k1_prefractal_publish_nonce(total, group_pts, &b_frost)) {
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < 2; i++) {
|
||||
secp256k1_gej_set_ge(&acc, &total[i]);
|
||||
secp256k1_gej_add_ge_var(&acc, &acc, &cosigner_pts[i], NULL);
|
||||
secp256k1_ge_set_gej(&total[i], &acc);
|
||||
}
|
||||
|
||||
secp256k1_fe_get_b32(agg_pk32, &cache_i.pk.x);
|
||||
secp256k1_musig_nonce_process_internal(ctx, fin_parity, fin_nonce, &b_musig, total, agg_pk32, msg32);
|
||||
secp256k1_schnorrsig_challenge(secp256k1_get_hash_context(ctx), &e, fin_nonce, msg32, 32, agg_pk32);
|
||||
|
||||
secp256k1_scalar_mul(b0b1, &b_musig, &b_frost);
|
||||
|
||||
/* The key coefficient carries the aggregation weight and the parity
|
||||
* bookkeeping from BIP 340: e*a*g*gacc, where the sign flips if the
|
||||
* AGGREGATE key is odd exactly once against the accumulated parity. The
|
||||
* group key's own Y parity is deliberately not consulted: it is an inner
|
||||
* participant of this aggregation, not the key the signature verifies
|
||||
* against. */
|
||||
secp256k1_musig_keyaggcoef(secp256k1_get_hash_context(ctx), &a, &cache_i, &pk);
|
||||
secp256k1_scalar_mul(key_coef, &e, &a);
|
||||
if (secp256k1_fe_is_odd(&cache_i.pk.y) != cache_i.parity_acc) {
|
||||
secp256k1_scalar_negate(key_coef, key_coef);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The verification equation, shared by the self-check inside
|
||||
* secp256k1_prefractal_sign and by secp256k1_prefractal_partial_sig_verify so
|
||||
* the two can never drift apart:
|
||||
*
|
||||
* s_i*G == +-(R1_i + b_frost*b_musig*R2_i) + e*a*g*gacc*lambda_i*P_i
|
||||
*
|
||||
* rearranged into a single comparison against infinity. The sign on the nonce
|
||||
* term follows the OUTER final nonce's parity. */
|
||||
static int secp256k1_prefractal_verify_partial_sig(const secp256k1_scalar *s, const secp256k1_ge *nonce_pts, const secp256k1_ge *pubshare, const secp256k1_scalar *lambda, const secp256k1_scalar *b0b1, const secp256k1_scalar *key_coef, int fin_parity) {
|
||||
secp256k1_scalar coef, s_neg;
|
||||
secp256k1_gej rj, pkj, tmp;
|
||||
|
||||
/* The nonce components of a pubnonce are never the point at infinity. */
|
||||
VERIFY_CHECK(!secp256k1_ge_is_infinity(&nonce_pts[0]));
|
||||
VERIFY_CHECK(!secp256k1_ge_is_infinity(&nonce_pts[1]));
|
||||
secp256k1_gej_set_ge(&rj, &nonce_pts[1]);
|
||||
secp256k1_ecmult(&rj, &rj, b0b1, NULL);
|
||||
secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pts[0], NULL);
|
||||
if (fin_parity) {
|
||||
secp256k1_gej_neg(&rj, &rj);
|
||||
}
|
||||
|
||||
secp256k1_scalar_mul(&coef, key_coef, lambda);
|
||||
secp256k1_scalar_negate(&s_neg, s);
|
||||
secp256k1_gej_set_ge(&pkj, pubshare);
|
||||
secp256k1_ecmult(&tmp, &pkj, &coef, &s_neg);
|
||||
secp256k1_gej_add_var(&tmp, &tmp, &rj, NULL);
|
||||
return secp256k1_gej_is_infinity(&tmp);
|
||||
}
|
||||
|
||||
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_gej sumj[2];
|
||||
secp256k1_ge sum[2], published[2], pk;
|
||||
secp256k1_scalar b_frost;
|
||||
size_t i;
|
||||
int j;
|
||||
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(pubnonce_out != NULL);
|
||||
memset(pubnonce_out, 0, sizeof(*pubnonce_out));
|
||||
ARG_CHECK(aggnonce_out != NULL);
|
||||
memset(aggnonce_out, 0, sizeof(*aggnonce_out));
|
||||
ARG_CHECK(pubnonces != NULL);
|
||||
ARG_CHECK(ids != NULL);
|
||||
ARG_CHECK(thresh_pk != NULL);
|
||||
ARG_CHECK(n_signers >= 1 && n_signers <= SECP256K1_FROST_MAX_PARTICIPANTS);
|
||||
for (i = 0; i < n_signers; i++) {
|
||||
ARG_CHECK(pubnonces[i] != NULL);
|
||||
}
|
||||
/* The ids are what the coefficient commits to and what the Lagrange values
|
||||
* are computed over, so a duplicate has to be refused here rather than
|
||||
* producing a nonce nobody can sign against. */
|
||||
if (!secp256k1_frost_ids_are_valid(ids, n_signers)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_pubkey_load(ctx, &pk, thresh_pk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_gej_set_infinity(&sumj[0]);
|
||||
secp256k1_gej_set_infinity(&sumj[1]);
|
||||
for (i = 0; i < n_signers; i++) {
|
||||
secp256k1_ge pts[2];
|
||||
if (!secp256k1_frost_pubnonce_load(ctx, pts, pubnonces[i])) {
|
||||
return 0;
|
||||
}
|
||||
for (j = 0; j < 2; j++) {
|
||||
secp256k1_gej_add_ge_var(&sumj[j], &sumj[j], &pts[j], NULL);
|
||||
}
|
||||
}
|
||||
/* Either column sum may be infinity here (BIP 445 NonceAgg); the aggregate
|
||||
* nonce has an encoding for that, and the check that matters happens after
|
||||
* scaling, below. */
|
||||
secp256k1_ge_set_all_gej_var(sum, sumj, 2);
|
||||
|
||||
secp256k1_prefractal_noncecoef(ctx, &b_frost, sum, ids, n_signers, &pk);
|
||||
if (!secp256k1_prefractal_publish_nonce(published, sum, &b_frost)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_frost_aggnonce_save(aggnonce_out, sum);
|
||||
secp256k1_musig_pubnonce_save(pubnonce_out, published);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The secrets secp256k1_prefractal_sign holds. d and s are not yet meaningful
|
||||
* on its early error paths; clearing them there writes zeros over whatever the
|
||||
* stack held, which is what those paths want anyway. */
|
||||
static void secp256k1_prefractal_sign_clear(secp256k1_scalar *k, secp256k1_scalar *d, secp256k1_scalar *lambda, secp256k1_scalar *s) {
|
||||
secp256k1_scalar_clear(&k[0]);
|
||||
secp256k1_scalar_clear(&k[1]);
|
||||
secp256k1_scalar_clear(d);
|
||||
secp256k1_scalar_clear(lambda);
|
||||
secp256k1_scalar_clear(s);
|
||||
}
|
||||
|
||||
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_scalar k[2], d, lambda, s, b0b1, key_coef, tmp;
|
||||
secp256k1_gej nonce_ptj[2];
|
||||
secp256k1_ge nonce_pts[2], cache_pk, pk;
|
||||
size_t my_index = n_signers;
|
||||
size_t i;
|
||||
int fin_parity;
|
||||
int ret;
|
||||
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(secnonce != NULL);
|
||||
/* Fails if the magic doesn't match or the nonce has been invalidated. */
|
||||
ret = secp256k1_frost_secnonce_load(ctx, k, secnonce);
|
||||
/* Wipe the secnonce to prevent nonce reuse. This will cause subsequent
|
||||
* calls of this function with the same secnonce to fail. */
|
||||
secp256k1_memzero_explicit(secnonce, sizeof(*secnonce));
|
||||
if (!ret) {
|
||||
secp256k1_scalar_clear(&k[0]);
|
||||
secp256k1_scalar_clear(&k[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ARG_CHECK(partial_sig != NULL);
|
||||
memset(partial_sig, 0, sizeof(*partial_sig));
|
||||
ARG_CHECK(secshare32 != NULL);
|
||||
ARG_CHECK(ids != NULL);
|
||||
ARG_CHECK(aggnonce != NULL);
|
||||
ARG_CHECK(thresh_pk != NULL);
|
||||
ARG_CHECK(tweak_cache != NULL);
|
||||
ARG_CHECK(keyagg_cache != NULL);
|
||||
ARG_CHECK(cosigner_aggnonce != NULL);
|
||||
ARG_CHECK(msg32 != NULL);
|
||||
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
|
||||
|
||||
secp256k1_scalar_clear(&d);
|
||||
secp256k1_scalar_clear(&lambda);
|
||||
secp256k1_scalar_clear(&s);
|
||||
|
||||
if (n_signers < 1 || n_signers > SECP256K1_FROST_MAX_PARTICIPANTS) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
/* The tweak cache is checked HERE and not only at aggregation, so the key
|
||||
* the member signs under is tied to the cache that was validated. */
|
||||
if (!secp256k1_prefractal_tweak_cache_is_identity(ctx, &cache_pk, tweak_cache)) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_pubkey_load(ctx, &pk, thresh_pk)) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
/* An identity cache carries the threshold key untouched, so this pins the
|
||||
* key argument to the cache instead of letting the two disagree. */
|
||||
if (!secp256k1_ge_eq_var(&cache_pk, &pk)) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compute the pubnonce points from the unnegated nonces for the
|
||||
* self-verification below. k[0] != 0 and k[1] != 0 is guaranteed by
|
||||
* secnonce_load, so the points are not the point at infinity. */
|
||||
secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &nonce_ptj[0], &k[0]);
|
||||
secp256k1_ecmult_gen_gej(&ctx->ecmult_gen_ctx, &nonce_ptj[1], &k[1]);
|
||||
secp256k1_ge_set_all_gej(nonce_pts, nonce_ptj, 2);
|
||||
secp256k1_declassify(ctx, &nonce_pts, sizeof(nonce_pts));
|
||||
|
||||
/* The secret share must be nonzero and less than the curve order. We can
|
||||
* declassify the result of the check because branching on it only leaks
|
||||
* whether the provided secret share is a valid secret key, which is not
|
||||
* secret. */
|
||||
{
|
||||
int share_valid = secp256k1_scalar_set_b32_seckey(&d, secshare32);
|
||||
secp256k1_declassify(ctx, &share_valid, sizeof(share_valid));
|
||||
if (!share_valid) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < n_signers; i++) {
|
||||
if (ids[i] == my_id) {
|
||||
my_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (my_index == n_signers) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
/* If the pubshares are known, the secret share must match the signer's
|
||||
* pubshare (recommended by BIP 445). */
|
||||
if (pubshares != NULL) {
|
||||
secp256k1_ge expected, mine;
|
||||
secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &mine, &d);
|
||||
secp256k1_declassify(ctx, &mine, sizeof(mine));
|
||||
if (!secp256k1_pubkey_load(ctx, &expected, &pubshares[my_index])) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_ge_eq_var(&mine, &expected)) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!secp256k1_frost_derive_interpolating_value(&lambda, ids, n_signers, my_id)) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_prefractal_session_values(ctx, &b0b1, &key_coef, &fin_parity, aggnonce, ids, n_signers, thresh_pk, keyagg_cache, cosigner_aggnonce, msg32)) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* BIP 340: if the OUTER final nonce came out odd, both nonce terms flip.
|
||||
*
|
||||
* There is deliberately NO key-side flip for the group key here. See the
|
||||
* module notes in include/secp256k1_prefractal.h. */
|
||||
if (fin_parity) {
|
||||
secp256k1_scalar_negate(&k[0], &k[0]);
|
||||
secp256k1_scalar_negate(&k[1], &k[1]);
|
||||
}
|
||||
|
||||
/* s_i = k1 + b_frost*b_musig*k2 + e*a*g*gacc*lambda_i*d_i */
|
||||
secp256k1_scalar_mul(&s, &key_coef, &lambda);
|
||||
secp256k1_scalar_mul(&s, &s, &d);
|
||||
secp256k1_scalar_mul(&tmp, &b0b1, &k[1]);
|
||||
secp256k1_scalar_add(&s, &s, &tmp);
|
||||
secp256k1_scalar_add(&s, &s, &k[0]);
|
||||
|
||||
/* Self-verify the partial signature, as recommended by BIP 445. This can
|
||||
* only fail in case of an implementation bug or catastrophic hardware
|
||||
* failure, so the result of the verification is not secret. The partial
|
||||
* signature itself is declassified first: it is the public output of this
|
||||
* function, and the verification below multiplies with it in variable
|
||||
* time. */
|
||||
{
|
||||
int verified;
|
||||
secp256k1_ge mine;
|
||||
secp256k1_declassify(ctx, &s, sizeof(s));
|
||||
secp256k1_ecmult_gen_ge(&ctx->ecmult_gen_ctx, &mine, &d);
|
||||
secp256k1_declassify(ctx, &mine, sizeof(mine));
|
||||
verified = secp256k1_prefractal_verify_partial_sig(&s, nonce_pts, &mine, &lambda, &b0b1, &key_coef, fin_parity);
|
||||
secp256k1_declassify(ctx, &verified, sizeof(verified));
|
||||
if (!verified) {
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
secp256k1_frost_partial_sig_save(partial_sig, &s);
|
||||
secp256k1_prefractal_sign_clear(k, &d, &lambda, &s);
|
||||
secp256k1_scalar_clear(&tmp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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_scalar b0b1, key_coef, lambda, s;
|
||||
secp256k1_ge nonce_pts[2], share_pt, cache_pk, pk;
|
||||
int fin_parity;
|
||||
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(partial_sig != NULL);
|
||||
ARG_CHECK(pubnonce != NULL);
|
||||
ARG_CHECK(pubshare != NULL);
|
||||
ARG_CHECK(ids != NULL);
|
||||
ARG_CHECK(aggnonce != NULL);
|
||||
ARG_CHECK(thresh_pk != NULL);
|
||||
ARG_CHECK(tweak_cache != NULL);
|
||||
ARG_CHECK(keyagg_cache != NULL);
|
||||
ARG_CHECK(cosigner_aggnonce != NULL);
|
||||
ARG_CHECK(msg32 != NULL);
|
||||
|
||||
if (n_signers < 1 || n_signers > SECP256K1_FROST_MAX_PARTICIPANTS) {
|
||||
return 0;
|
||||
}
|
||||
/* Recomputed from the same parameters signing used, including this check,
|
||||
* so a share made under a tweaked cache cannot be validated by a verifier
|
||||
* that was handed one. */
|
||||
if (!secp256k1_prefractal_tweak_cache_is_identity(ctx, &cache_pk, tweak_cache)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_pubkey_load(ctx, &pk, thresh_pk)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_ge_eq_var(&cache_pk, &pk)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_frost_partial_sig_load(ctx, &s, partial_sig)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_frost_pubnonce_load(ctx, nonce_pts, pubnonce)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_pubkey_load(ctx, &share_pt, pubshare)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_frost_derive_interpolating_value(&lambda, ids, n_signers, my_id)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_prefractal_session_values(ctx, &b0b1, &key_coef, &fin_parity, aggnonce, ids, n_signers, thresh_pk, keyagg_cache, cosigner_aggnonce, msg32)) {
|
||||
return 0;
|
||||
}
|
||||
return secp256k1_prefractal_verify_partial_sig(&s, nonce_pts, &share_pt, &lambda, &b0b1, &key_coef, fin_parity);
|
||||
}
|
||||
|
||||
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_scalar s, term;
|
||||
size_t i;
|
||||
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(sig_out != NULL);
|
||||
memset(sig_out, 0, sizeof(*sig_out));
|
||||
ARG_CHECK(partial_sigs != NULL);
|
||||
ARG_CHECK(tweak_cache != NULL);
|
||||
ARG_CHECK(n_sigs >= 1 && n_sigs <= SECP256K1_FROST_MAX_PARTICIPANTS);
|
||||
for (i = 0; i < n_sigs; i++) {
|
||||
ARG_CHECK(partial_sigs[i] != NULL);
|
||||
}
|
||||
|
||||
/* The sum below is plain only because there is no accumulated tweak to
|
||||
* fold in. Checked again here, and not only in sign, because the shares and
|
||||
* the cache can reach a coordinator from different places. */
|
||||
if (!secp256k1_prefractal_tweak_cache_is_identity(ctx, NULL, tweak_cache)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_scalar_set_int(&s, 0);
|
||||
for (i = 0; i < n_sigs; i++) {
|
||||
if (!secp256k1_frost_partial_sig_load(ctx, &term, partial_sigs[i])) {
|
||||
if (error_index != NULL) {
|
||||
*error_index = i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
secp256k1_scalar_add(&s, &s, &term);
|
||||
}
|
||||
/* Saved through the musig helper rather than copied: the two partial
|
||||
* signature structs are the same size but carry different magics, so a
|
||||
* struct copy would produce something musig_partial_sig_agg rejects. */
|
||||
secp256k1_musig_partial_sig_save(sig_out, &s);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_MODULE_PREFRACTAL_SESSION_IMPL_H */
|
||||
544
src/modules/prefractal/tests_impl.h
Normal file
544
src/modules/prefractal/tests_impl.h
Normal file
@@ -0,0 +1,544 @@
|
||||
/***********************************************************************
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_MODULE_PREFRACTAL_TESTS_IMPL_H
|
||||
#define SECP256K1_MODULE_PREFRACTAL_TESTS_IMPL_H
|
||||
|
||||
#include "../../../include/secp256k1_prefractal.h"
|
||||
|
||||
/* Two FIXED threshold secret keys, one of each Y parity of the resulting
|
||||
* threshold public key.
|
||||
*
|
||||
* The odd one is the point of this pair. The nested equation has no g_frost
|
||||
* factor, and the reason is NOT that the tweak cache is the identity: stock
|
||||
* frost's key-side factor is g*gacc with g = -1 for an odd-Y threshold key, so
|
||||
* an implementation that imported frost's key-side parity would work for
|
||||
* even-Y groups and fail for odd-Y ones. A randomly seeded fixture makes that
|
||||
* a coin flip per run, so both parities are pinned here instead. */
|
||||
static const unsigned char prefractal_test_seckey_even[32] = {
|
||||
0x44, 0xa2, 0x82, 0x5e, 0x46, 0x26, 0xfa, 0x53,
|
||||
0xf5, 0x2c, 0x2e, 0x6a, 0x40, 0x7a, 0xfc, 0xb9,
|
||||
0xb7, 0xe8, 0x7d, 0x63, 0x30, 0x6b, 0x0d, 0x69,
|
||||
0xae, 0x3d, 0x0d, 0x29, 0xeb, 0x6c, 0xa6, 0x08
|
||||
};
|
||||
static const unsigned char prefractal_test_seckey_odd[32] = {
|
||||
0xd3, 0x27, 0x59, 0x3f, 0xe7, 0x53, 0xf6, 0xfd,
|
||||
0xe3, 0x8f, 0x29, 0xfd, 0x26, 0x39, 0xd4, 0x4f,
|
||||
0x62, 0x05, 0x4b, 0xab, 0xea, 0x21, 0xa3, 0x59,
|
||||
0xa4, 0x1d, 0x65, 0x1c, 0x81, 0xf1, 0xe0, 0x1e
|
||||
};
|
||||
|
||||
/* Everything one nested session needs, so the tests below can set one up in a
|
||||
* line and then poke at individual pieces. */
|
||||
typedef struct {
|
||||
unsigned int n, t;
|
||||
unsigned char secshares[SECP256K1_FROST_MAX_PARTICIPANTS][32];
|
||||
secp256k1_pubkey pubshares[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
secp256k1_pubkey thresh_pk;
|
||||
secp256k1_frost_tweak_cache tweak_cache;
|
||||
uint32_t ids[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
|
||||
secp256k1_frost_secnonce secnonces[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
secp256k1_frost_pubnonce pubnonces[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
const secp256k1_frost_pubnonce *pubnonce_ptrs[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
secp256k1_frost_aggnonce aggnonce;
|
||||
secp256k1_musig_pubnonce group_pubnonce;
|
||||
|
||||
secp256k1_keypair cosigner_keypair;
|
||||
secp256k1_pubkey cosigner_pk;
|
||||
secp256k1_xonly_pubkey agg_xonly;
|
||||
secp256k1_musig_keyagg_cache keyagg_cache;
|
||||
secp256k1_musig_secnonce cosigner_secnonce;
|
||||
secp256k1_musig_pubnonce cosigner_pubnonce;
|
||||
secp256k1_musig_aggnonce cosigner_aggnonce;
|
||||
secp256k1_musig_aggnonce full_aggnonce;
|
||||
|
||||
unsigned char msg[32];
|
||||
} prefractal_test_session;
|
||||
|
||||
/* Deal a group, build the outer 2-key aggregation with one stock musig
|
||||
* cosigner, and run round one on both sides.
|
||||
*
|
||||
* group_first picks which side of the outer aggregation the group key sits on.
|
||||
* BIP 327 KeyAgg gives the "second" distinct key a coefficient of exactly 1
|
||||
* and hashes every other one, so the two orders exercise genuinely different
|
||||
* arithmetic for the group; both are run everywhere below.
|
||||
*
|
||||
* xonly_tweak applies the BIP 341 style tweak to the OUTER cache only, which
|
||||
* is the arrangement the channel protocols use. */
|
||||
static void prefractal_test_setup(prefractal_test_session *s, unsigned int n, unsigned int t,
|
||||
const unsigned char *thresh_seckey, int group_first,
|
||||
const unsigned char *xonly_tweak) {
|
||||
const secp256k1_pubkey *pubkeys[2];
|
||||
const secp256k1_musig_pubnonce *just_cosigner[1];
|
||||
unsigned char cosigner_seckey[32], secrand[32], thresh_pk32[32];
|
||||
secp256k1_xonly_pubkey thresh_xonly;
|
||||
unsigned int k;
|
||||
|
||||
s->n = n;
|
||||
s->t = t;
|
||||
testrand256(s->msg);
|
||||
testrand256(cosigner_seckey);
|
||||
|
||||
CHECK(secp256k1_frost_trusted_dealer_keygen(CTX, s->secshares[0], &s->thresh_pk,
|
||||
s->pubshares, n, t, thresh_seckey) == 1);
|
||||
CHECK(secp256k1_frost_tweak_cache_init(CTX, &s->tweak_cache, &s->thresh_pk) == 1);
|
||||
for (k = 0; k < n; k++) {
|
||||
s->ids[k] = k;
|
||||
}
|
||||
|
||||
/* The group's key enters the outer aggregation as an ordinary public key,
|
||||
* in full. */
|
||||
CHECK(secp256k1_keypair_create(CTX, &s->cosigner_keypair, cosigner_seckey) == 1);
|
||||
CHECK(secp256k1_keypair_pub(CTX, &s->cosigner_pk, &s->cosigner_keypair) == 1);
|
||||
pubkeys[group_first ? 0 : 1] = &s->thresh_pk;
|
||||
pubkeys[group_first ? 1 : 0] = &s->cosigner_pk;
|
||||
CHECK(secp256k1_musig_pubkey_agg(CTX, &s->agg_xonly, &s->keyagg_cache, pubkeys, 2) == 1);
|
||||
if (xonly_tweak != NULL) {
|
||||
secp256k1_pubkey tweaked;
|
||||
CHECK(secp256k1_musig_pubkey_xonly_tweak_add(CTX, &tweaked, &s->keyagg_cache, xonly_tweak) == 1);
|
||||
CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &s->agg_xonly, NULL, &tweaked) == 1);
|
||||
}
|
||||
|
||||
/* Round one, cosigner side. */
|
||||
testrand256(secrand);
|
||||
CHECK(secp256k1_musig_nonce_gen(CTX, &s->cosigner_secnonce, &s->cosigner_pubnonce, secrand,
|
||||
cosigner_seckey, &s->cosigner_pk, s->msg, &s->keyagg_cache, NULL) == 1);
|
||||
just_cosigner[0] = &s->cosigner_pubnonce;
|
||||
CHECK(secp256k1_musig_nonce_agg(CTX, &s->cosigner_aggnonce, just_cosigner, 1) == 1);
|
||||
|
||||
/* Round one, group side. The nonces are generated the way the module
|
||||
* expects them: msg = NULL, because the wire nonce is published before the
|
||||
* message exists. */
|
||||
CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &thresh_xonly, NULL, &s->thresh_pk) == 1);
|
||||
CHECK(secp256k1_xonly_pubkey_serialize(CTX, thresh_pk32, &thresh_xonly) == 1);
|
||||
for (k = 0; k < t; k++) {
|
||||
testrand256(secrand);
|
||||
CHECK(secp256k1_frost_nonce_gen(CTX, &s->secnonces[k], &s->pubnonces[k], secrand,
|
||||
s->secshares[k], &s->pubshares[k], thresh_pk32,
|
||||
NULL, 0, NULL, 0) == 1);
|
||||
s->pubnonce_ptrs[k] = &s->pubnonces[k];
|
||||
}
|
||||
CHECK(secp256k1_prefractal_nonce_agg(CTX, &s->group_pubnonce, &s->aggnonce,
|
||||
s->pubnonce_ptrs, s->ids, t, &s->thresh_pk) == 1);
|
||||
|
||||
{
|
||||
const secp256k1_musig_pubnonce *all[2];
|
||||
all[group_first ? 0 : 1] = &s->group_pubnonce;
|
||||
all[group_first ? 1 : 0] = &s->cosigner_pubnonce;
|
||||
CHECK(secp256k1_musig_nonce_agg(CTX, &s->full_aggnonce, all, 2) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Round two on both sides, ending in a BIP 340 signature over the outer
|
||||
* aggregate key. Returns what secp256k1_schnorrsig_verify says about it. */
|
||||
static int prefractal_test_finish(prefractal_test_session *s, int group_first, unsigned char *sig64) {
|
||||
secp256k1_frost_partial_sig psigs[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
const secp256k1_frost_partial_sig *psig_ptrs[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
const secp256k1_musig_partial_sig *musig_psigs[2];
|
||||
secp256k1_musig_partial_sig group_psig, cosigner_psig;
|
||||
secp256k1_musig_session session;
|
||||
unsigned int k;
|
||||
|
||||
CHECK(secp256k1_musig_nonce_process(CTX, &session, &s->full_aggnonce, s->msg, &s->keyagg_cache, NULL) == 1);
|
||||
CHECK(secp256k1_musig_partial_sign(CTX, &cosigner_psig, &s->cosigner_secnonce,
|
||||
&s->cosigner_keypair, &s->keyagg_cache, &session) == 1);
|
||||
|
||||
for (k = 0; k < s->t; k++) {
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psigs[k], &s->secnonces[k], s->secshares[k],
|
||||
s->ids[k], s->ids, s->pubshares, s->t, &s->aggnonce,
|
||||
&s->thresh_pk, &s->tweak_cache, &s->keyagg_cache,
|
||||
&s->cosigner_aggnonce, s->msg) == 1);
|
||||
psig_ptrs[k] = &psigs[k];
|
||||
/* Every share verifies against its author's public share. */
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &psigs[k], &s->pubnonces[k],
|
||||
&s->pubshares[k], s->ids[k], s->ids, s->t,
|
||||
&s->aggnonce, &s->thresh_pk, &s->tweak_cache,
|
||||
&s->keyagg_cache, &s->cosigner_aggnonce,
|
||||
s->msg) == 1);
|
||||
}
|
||||
CHECK(secp256k1_prefractal_partial_sig_agg(CTX, &group_psig, NULL, psig_ptrs, s->t,
|
||||
&s->tweak_cache) == 1);
|
||||
|
||||
musig_psigs[group_first ? 0 : 1] = &group_psig;
|
||||
musig_psigs[group_first ? 1 : 0] = &cosigner_psig;
|
||||
CHECK(secp256k1_musig_partial_sig_agg(CTX, sig64, &session, musig_psigs, 2) == 1);
|
||||
return secp256k1_schnorrsig_verify(CTX, sig64, s->msg, 32, &s->agg_xonly);
|
||||
}
|
||||
|
||||
/* The round trip, over every t-of-n this module claims to support, both
|
||||
* lexicographic positions of the group key, both group-key Y parities, and
|
||||
* with and without the outer BIP 341 tweak. */
|
||||
static void run_prefractal_e2e_test(void) {
|
||||
static const unsigned char configs[][2] = { {2,2}, {3,2}, {5,3}, {4,3}, {7,5} };
|
||||
size_t config;
|
||||
int group_first, odd, tweaked;
|
||||
|
||||
for (config = 0; config < sizeof(configs) / sizeof(configs[0]); config++) {
|
||||
for (group_first = 0; group_first <= 1; group_first++) {
|
||||
for (odd = 0; odd <= 1; odd++) {
|
||||
for (tweaked = 0; tweaked <= 1; tweaked++) {
|
||||
prefractal_test_session s;
|
||||
unsigned char sig[64], tweak[32];
|
||||
testrand256(tweak);
|
||||
prefractal_test_setup(&s, configs[config][0], configs[config][1],
|
||||
odd ? prefractal_test_seckey_odd : prefractal_test_seckey_even,
|
||||
group_first, tweaked ? tweak : NULL);
|
||||
CHECK(prefractal_test_finish(&s, group_first, sig) == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The odd-Y case on its own, so a failure names the cause rather than showing
|
||||
* up as one iteration of the matrix above. If the g_frost factor were imported
|
||||
* from stock frost, this test would fail and the even-Y one would pass. */
|
||||
static void run_prefractal_odd_y_group_key_test(void) {
|
||||
prefractal_test_session s;
|
||||
secp256k1_xonly_pubkey xonly;
|
||||
unsigned char sig[64], ser[32];
|
||||
int parity = -1;
|
||||
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_odd, 1, NULL);
|
||||
/* Confirm the fixture really is odd-Y, so the test cannot quietly stop
|
||||
* testing what it is named after. */
|
||||
CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly, &parity, &s.thresh_pk) == 1);
|
||||
CHECK(secp256k1_xonly_pubkey_serialize(CTX, ser, &xonly) == 1);
|
||||
CHECK(parity == 1);
|
||||
CHECK(prefractal_test_finish(&s, 1, sig) == 1);
|
||||
}
|
||||
|
||||
/* partial_sig_verify is the identifiable-abort tool, so it has to say no to
|
||||
* everything that is not exactly the share it was asked about. */
|
||||
static void run_prefractal_partial_sig_verify_test(void) {
|
||||
prefractal_test_session s;
|
||||
secp256k1_frost_partial_sig psig, altered;
|
||||
uint32_t wrong_ids[SECP256K1_FROST_MAX_PARTICIPANTS];
|
||||
unsigned char other_msg[32];
|
||||
unsigned int k;
|
||||
|
||||
prefractal_test_setup(&s, 5, 3, prefractal_test_seckey_odd, 1, NULL);
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[0], s.secshares[0], s.ids[0],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 1);
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &psig, &s.pubnonces[0], &s.pubshares[0],
|
||||
s.ids[0], s.ids, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, s.msg) == 1);
|
||||
|
||||
/* A tampered share. */
|
||||
altered = psig;
|
||||
altered.data[10] ^= 0x40;
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &altered, &s.pubnonces[0], &s.pubshares[0],
|
||||
s.ids[0], s.ids, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, s.msg) == 0);
|
||||
|
||||
/* The right share against the wrong member's public share and nonce. */
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &psig, &s.pubnonces[1], &s.pubshares[1],
|
||||
s.ids[1], s.ids, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, s.msg) == 0);
|
||||
|
||||
/* A different signer set. lambda_i is defined over the participating set,
|
||||
* so a share made for one set does not verify under another even though
|
||||
* every other argument is unchanged. */
|
||||
for (k = 0; k < s.t; k++) {
|
||||
wrong_ids[k] = s.ids[k];
|
||||
}
|
||||
wrong_ids[s.t - 1] = s.ids[s.t];
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &psig, &s.pubnonces[0], &s.pubshares[0],
|
||||
s.ids[0], wrong_ids, s.t, &s.aggnonce,
|
||||
&s.thresh_pk, &s.tweak_cache, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, s.msg) == 0);
|
||||
|
||||
/* A different message gives the same 0 as a bad share, which is why a 0 is
|
||||
* not by itself evidence about a member. */
|
||||
memcpy(other_msg, s.msg, 32);
|
||||
other_msg[0] ^= 1;
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &psig, &s.pubnonces[0], &s.pubshares[0],
|
||||
s.ids[0], s.ids, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, other_msg) == 0);
|
||||
}
|
||||
|
||||
/* A non-identity frost tweak cache is refused by all three entry points that
|
||||
* take one. The check is in sign as well as agg so the key a member signs
|
||||
* under is tied to the cache that was validated. */
|
||||
static void run_prefractal_identity_cache_test(void) {
|
||||
prefractal_test_session s;
|
||||
secp256k1_frost_tweak_cache tweaked;
|
||||
secp256k1_frost_partial_sig psig;
|
||||
const secp256k1_frost_partial_sig *psig_ptrs[1];
|
||||
secp256k1_musig_partial_sig out;
|
||||
secp256k1_xonly_pubkey ignored;
|
||||
unsigned char tweak[32];
|
||||
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_odd, 1, NULL);
|
||||
testrand256(tweak);
|
||||
tweaked = s.tweak_cache;
|
||||
CHECK(secp256k1_frost_pubkey_xonly_tweak_add(CTX, &ignored, &tweaked, tweak) == 1);
|
||||
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[0], s.secshares[0], s.ids[0],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&tweaked, &s.keyagg_cache, &s.cosigner_aggnonce, s.msg) == 0);
|
||||
|
||||
/* A good share, then verification and aggregation handed the tweaked
|
||||
* cache. The secnonce above was consumed even on the failure path, so this
|
||||
* uses the other member's. */
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[1], s.secshares[1], s.ids[1],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 1);
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &psig, &s.pubnonces[1], &s.pubshares[1],
|
||||
s.ids[1], s.ids, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&tweaked, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, s.msg) == 0);
|
||||
psig_ptrs[0] = &psig;
|
||||
CHECK(secp256k1_prefractal_partial_sig_agg(CTX, &out, NULL, psig_ptrs, 1, &tweaked) == 0);
|
||||
/* The identity cache is accepted at the same call, so the refusal above is
|
||||
* about the tweak and not about the arguments in general. */
|
||||
CHECK(secp256k1_prefractal_partial_sig_agg(CTX, &out, NULL, psig_ptrs, 1, &s.tweak_cache) == 1);
|
||||
}
|
||||
|
||||
/* thresh_pk and the tweak cache have to describe the same key. Passing a cache
|
||||
* built for some other group is the mistake this catches. */
|
||||
static void run_prefractal_key_cache_mismatch_test(void) {
|
||||
prefractal_test_session s;
|
||||
secp256k1_frost_tweak_cache other_cache;
|
||||
secp256k1_pubkey other_pk, other_pubshares[8];
|
||||
unsigned char other_secshares[8][32];
|
||||
secp256k1_frost_partial_sig psig;
|
||||
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_odd, 1, NULL);
|
||||
CHECK(secp256k1_frost_trusted_dealer_keygen(CTX, other_secshares[0], &other_pk,
|
||||
other_pubshares, 3, 2,
|
||||
prefractal_test_seckey_even) == 1);
|
||||
CHECK(secp256k1_frost_tweak_cache_init(CTX, &other_cache, &other_pk) == 1);
|
||||
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[0], s.secshares[0], s.ids[0],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&other_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 0);
|
||||
}
|
||||
|
||||
/* A musig pubnonce cannot encode the point at infinity, so a group whose
|
||||
* aggregate nonce lands there has to be told rather than handed something
|
||||
* unusable. Either column can do it independently: the first is passed through
|
||||
* unscaled, the second is scaled by b_frost first.
|
||||
*
|
||||
* Both are reached the same way. A pubnonce and its negation sum to infinity
|
||||
* in both columns at once, so the first column is tested with that pair, and
|
||||
* for the second the members' second-column points are made to cancel while
|
||||
* the first column does not. */
|
||||
static void run_prefractal_infinity_nonce_test(void) {
|
||||
prefractal_test_session s;
|
||||
secp256k1_frost_pubnonce negated;
|
||||
const secp256k1_frost_pubnonce *ptrs[2];
|
||||
secp256k1_musig_pubnonce pubnonce_out;
|
||||
secp256k1_frost_aggnonce aggnonce_out;
|
||||
unsigned char ser[66], neg_ser[66];
|
||||
secp256k1_pubkey pt;
|
||||
uint32_t ids[2];
|
||||
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_odd, 1, NULL);
|
||||
ids[0] = s.ids[0];
|
||||
ids[1] = s.ids[1];
|
||||
|
||||
/* Negate both columns of member 0's nonce: every column then sums to
|
||||
* infinity. */
|
||||
CHECK(secp256k1_frost_pubnonce_serialize(CTX, ser, &s.pubnonces[0]) == 1);
|
||||
CHECK(secp256k1_ec_pubkey_parse(CTX, &pt, &ser[0], 33) == 1);
|
||||
CHECK(secp256k1_ec_pubkey_negate(CTX, &pt) == 1);
|
||||
{
|
||||
size_t len = 33;
|
||||
CHECK(secp256k1_ec_pubkey_serialize(CTX, &neg_ser[0], &len, &pt, SECP256K1_EC_COMPRESSED) == 1);
|
||||
}
|
||||
CHECK(secp256k1_ec_pubkey_parse(CTX, &pt, &ser[33], 33) == 1);
|
||||
CHECK(secp256k1_ec_pubkey_negate(CTX, &pt) == 1);
|
||||
{
|
||||
size_t len = 33;
|
||||
CHECK(secp256k1_ec_pubkey_serialize(CTX, &neg_ser[33], &len, &pt, SECP256K1_EC_COMPRESSED) == 1);
|
||||
}
|
||||
CHECK(secp256k1_frost_pubnonce_parse(CTX, &negated, neg_ser) == 1);
|
||||
|
||||
ptrs[0] = &s.pubnonces[0];
|
||||
ptrs[1] = &negated;
|
||||
CHECK(secp256k1_prefractal_nonce_agg(CTX, &pubnonce_out, &aggnonce_out, ptrs, ids, 2,
|
||||
&s.thresh_pk) == 0);
|
||||
|
||||
/* Now cancel only the second column: first column R1 + R1 is not infinity,
|
||||
* second column R2 + (-R2) is. This is the scaled component, so it reaches
|
||||
* the guard only after the b_frost multiplication. */
|
||||
memcpy(&neg_ser[0], &ser[0], 33);
|
||||
CHECK(secp256k1_frost_pubnonce_parse(CTX, &negated, neg_ser) == 1);
|
||||
CHECK(secp256k1_prefractal_nonce_agg(CTX, &pubnonce_out, &aggnonce_out, ptrs, ids, 2,
|
||||
&s.thresh_pk) == 0);
|
||||
|
||||
/* The untouched set still works, so the refusals above are about the
|
||||
* infinities and not about this arrangement of arguments. */
|
||||
ptrs[1] = &s.pubnonces[1];
|
||||
CHECK(secp256k1_prefractal_nonce_agg(CTX, &pubnonce_out, &aggnonce_out, ptrs, ids, 2,
|
||||
&s.thresh_pk) == 1);
|
||||
}
|
||||
|
||||
/* A secnonce is single use. sign wipes it, so the second call cannot produce
|
||||
* the second signature that would expose the share. The wiped secnonce is
|
||||
* caught by the magic check inside secp256k1_frost_secnonce_load, which is an
|
||||
* ARG_CHECK and so reaches the illegal-argument callback rather than returning
|
||||
* 0 - the same way stock secp256k1_frost_sign behaves. */
|
||||
static void run_prefractal_nonce_reuse_test(void) {
|
||||
prefractal_test_session s;
|
||||
secp256k1_frost_partial_sig psig;
|
||||
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_odd, 1, NULL);
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[0], s.secshares[0], s.ids[0],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 1);
|
||||
CHECK(secp256k1_is_zero_array(s.secnonces[0].data, sizeof(s.secnonces[0].data)));
|
||||
CHECK_ILLEGAL(CTX, secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[0], s.secshares[0],
|
||||
s.ids[0], s.ids, s.pubshares, s.t, &s.aggnonce,
|
||||
&s.thresh_pk, &s.tweak_cache, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, s.msg));
|
||||
|
||||
/* Every call that reaches the secnonce load wipes it, including the ones
|
||||
* that then fail for another reason. A member whose signing attempt was
|
||||
* refused must not retry with the same nonce. */
|
||||
CHECK(secp256k1_is_zero_array(s.secnonces[1].data, sizeof(s.secnonces[1].data)) == 0);
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[1], s.secshares[1], s.ids[2],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 0);
|
||||
CHECK(secp256k1_is_zero_array(s.secnonces[1].data, sizeof(s.secnonces[1].data)));
|
||||
}
|
||||
|
||||
/* Negative controls: shares that are individually well formed and only fail at
|
||||
* the very end, against the outer aggregate. These are the failures the module
|
||||
* cannot catch for the caller, so the tests pin where they do surface. */
|
||||
static void run_prefractal_negative_control_test(void) {
|
||||
prefractal_test_session s;
|
||||
unsigned char sig[64], tweak[32];
|
||||
|
||||
/* Wrong key order: the group signs under a cache that aggregates the two
|
||||
* keys in the other order, so a_musig is wrong. Every partial signature is
|
||||
* valid on its own terms and the final signature does not verify. */
|
||||
testrand256(tweak);
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_odd, 1, NULL);
|
||||
{
|
||||
const secp256k1_pubkey *swapped[2];
|
||||
secp256k1_musig_keyagg_cache wrong_cache;
|
||||
secp256k1_xonly_pubkey ignored;
|
||||
swapped[0] = &s.cosigner_pk;
|
||||
swapped[1] = &s.thresh_pk;
|
||||
CHECK(secp256k1_musig_pubkey_agg(CTX, &ignored, &wrong_cache, swapped, 2) == 1);
|
||||
s.keyagg_cache = wrong_cache;
|
||||
/* finish() would trip its own internal partial_sig_verify CHECKs only
|
||||
* if the shares disagreed with the session; they do not, because the
|
||||
* session is rebuilt from this same wrong cache. What fails is the
|
||||
* final BIP 340 verification against the ORIGINAL aggregate key. */
|
||||
CHECK(prefractal_test_finish(&s, 1, sig) == 0);
|
||||
}
|
||||
|
||||
/* Missing tweak: the group signs under the untweaked outer cache while the
|
||||
* signature is checked against the tweaked aggregate key. */
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_odd, 1, tweak);
|
||||
{
|
||||
const secp256k1_pubkey *pubkeys[2];
|
||||
secp256k1_musig_keyagg_cache untweaked;
|
||||
secp256k1_xonly_pubkey ignored;
|
||||
pubkeys[0] = &s.thresh_pk;
|
||||
pubkeys[1] = &s.cosigner_pk;
|
||||
CHECK(secp256k1_musig_pubkey_agg(CTX, &ignored, &untweaked, pubkeys, 2) == 1);
|
||||
s.keyagg_cache = untweaked;
|
||||
CHECK(prefractal_test_finish(&s, 1, sig) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Argument checking, and the bounds the module states in its header. */
|
||||
static void run_prefractal_api_test(void) {
|
||||
prefractal_test_session s;
|
||||
secp256k1_musig_pubnonce pubnonce_out;
|
||||
secp256k1_frost_aggnonce aggnonce_out;
|
||||
secp256k1_frost_partial_sig psig;
|
||||
const secp256k1_frost_partial_sig *psig_ptrs[1];
|
||||
secp256k1_musig_partial_sig out;
|
||||
uint32_t dup_ids[2];
|
||||
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_even, 1, NULL);
|
||||
|
||||
/* Duplicate identifiers make lambda_i undefined, so nonce_agg refuses
|
||||
* rather than producing a nonce nobody can sign against. */
|
||||
dup_ids[0] = s.ids[0];
|
||||
dup_ids[1] = s.ids[0];
|
||||
CHECK(secp256k1_prefractal_nonce_agg(CTX, &pubnonce_out, &aggnonce_out, s.pubnonce_ptrs,
|
||||
dup_ids, 2, &s.thresh_pk) == 0);
|
||||
/* n_signers out of range is a caller bug, not a peer-influenced value, so
|
||||
* it takes the illegal-argument path rather than returning 0 - the same
|
||||
* choice the frost module makes for its own counts. */
|
||||
CHECK_ILLEGAL(CTX, secp256k1_prefractal_nonce_agg(CTX, &pubnonce_out, &aggnonce_out,
|
||||
s.pubnonce_ptrs, s.ids, 0, &s.thresh_pk));
|
||||
|
||||
/* An id that is not in the signer set. */
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[0], s.secshares[0], s.ids[2],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 0);
|
||||
|
||||
/* A secret share that does not match the pubshare it is claimed for. */
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[1], s.secshares[0], s.ids[1],
|
||||
s.ids, s.pubshares, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 0);
|
||||
|
||||
/* Passing NULL pubshares skips that check, which is what the header says it
|
||||
* does; the signature is still correct. */
|
||||
prefractal_test_setup(&s, 3, 2, prefractal_test_seckey_even, 1, NULL);
|
||||
CHECK(secp256k1_prefractal_sign(CTX, &psig, &s.secnonces[0], s.secshares[0], s.ids[0],
|
||||
s.ids, NULL, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache, &s.cosigner_aggnonce,
|
||||
s.msg) == 1);
|
||||
CHECK(secp256k1_prefractal_partial_sig_verify(CTX, &psig, &s.pubnonces[0], &s.pubshares[0],
|
||||
s.ids[0], s.ids, s.t, &s.aggnonce, &s.thresh_pk,
|
||||
&s.tweak_cache, &s.keyagg_cache,
|
||||
&s.cosigner_aggnonce, s.msg) == 1);
|
||||
|
||||
/* n_sigs out of range, likewise. */
|
||||
psig_ptrs[0] = &psig;
|
||||
CHECK_ILLEGAL(CTX, secp256k1_prefractal_partial_sig_agg(CTX, &out, NULL, psig_ptrs, 0,
|
||||
&s.tweak_cache));
|
||||
}
|
||||
|
||||
/* The tagged hash this module defines. Pinning the midstate here is what keeps
|
||||
* a rewrite of the constant honest: nothing else in the tree would notice a
|
||||
* changed b_frost, it would just produce signatures that do not verify. */
|
||||
static void run_prefractal_midstate_test(void) {
|
||||
secp256k1_sha256 sha, sha_tagged;
|
||||
unsigned char tag[] = "Prefractal/noncecoef";
|
||||
unsigned char buf[32], buf_tagged[32];
|
||||
|
||||
secp256k1_sha256_initialize_tagged(secp256k1_get_hash_context(CTX), &sha, tag, sizeof(tag) - 1);
|
||||
secp256k1_prefractal_noncecoef_sha256_tagged(&sha_tagged);
|
||||
secp256k1_sha256_finalize(secp256k1_get_hash_context(CTX), &sha, buf);
|
||||
secp256k1_sha256_finalize(secp256k1_get_hash_context(CTX), &sha_tagged, buf_tagged);
|
||||
CHECK(secp256k1_memcmp_var(buf, buf_tagged, 32) == 0);
|
||||
}
|
||||
|
||||
static const struct tf_test_entry tests_prefractal[] = {
|
||||
CASE1(run_prefractal_midstate_test),
|
||||
CASE1(run_prefractal_e2e_test),
|
||||
CASE1(run_prefractal_odd_y_group_key_test),
|
||||
CASE1(run_prefractal_partial_sig_verify_test),
|
||||
CASE1(run_prefractal_identity_cache_test),
|
||||
CASE1(run_prefractal_key_cache_mismatch_test),
|
||||
CASE1(run_prefractal_infinity_nonce_test),
|
||||
CASE1(run_prefractal_nonce_reuse_test),
|
||||
CASE1(run_prefractal_negative_control_test),
|
||||
CASE1(run_prefractal_api_test),
|
||||
};
|
||||
|
||||
#endif /* SECP256K1_MODULE_PREFRACTAL_TESTS_IMPL_H */
|
||||
@@ -961,6 +961,10 @@ static int secp256k1_ge_parse_ext(secp256k1_ge* ge, const unsigned char *in33) {
|
||||
# include "modules/chilldkg/main_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_PREFRACTAL
|
||||
# include "modules/prefractal/main_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ICEBERG
|
||||
# include "modules/iceberg/main_impl.h"
|
||||
#endif
|
||||
|
||||
@@ -7928,6 +7928,10 @@ static void run_ecdsa_wycheproof(void) {
|
||||
# include "modules/chilldkg/tests_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_PREFRACTAL
|
||||
# include "modules/prefractal/tests_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ICEBERG
|
||||
# include "modules/iceberg/tests_impl.h"
|
||||
#endif
|
||||
@@ -8306,6 +8310,9 @@ static const struct tf_test_module registry_modules[] = {
|
||||
#ifdef ENABLE_MODULE_CHILLDKG
|
||||
MAKE_TEST_MODULE(chilldkg),
|
||||
#endif
|
||||
#ifdef ENABLE_MODULE_PREFRACTAL
|
||||
MAKE_TEST_MODULE(prefractal),
|
||||
#endif
|
||||
#ifdef ENABLE_MODULE_ICEBERG
|
||||
MAKE_TEST_MODULE(iceberg),
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user