From a7d43377785584163c9fc4e14e606d22d3483164 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Fri, 4 Sep 2026 03:53:03 +0200 Subject: [PATCH] build: wire the frost_enrollment module into both build systems Second of six commits adding the frost_enrollment module. This one is scaffolding only: the five entry points are stubs that validate their pointer arguments, zero their outputs and return 0. What is being verified here is that the module configures, compiles, links, exports its symbols and registers its test module in both build systems -- so that the next commit changes nothing but arithmetic. Ordering is the one thing in this commit that can go silently wrong, and it goes wrong in opposite directions in the two build systems: - configure.ac executes its `if` blocks in file order, and enable_module_frost defaults to no (configure.ac:243). A block placed after the frost block at :601 that sets enable_module_frost=yes flips the variable too late: AM_CONDITIONAL goes true, so the header is installed and the Makefile fragment is pulled in, but -DENABLE_MODULE_FROST=1 is never appended, so src/secp256k1.c never includes frost's implementation and every secp256k1_frost_* symbol fails to link. The new block therefore goes ahead of both the frost block and prefractal's, which documents the same trap. - src/CMakeLists.txt processes dependents FIRST, so the same block goes above the FROST block there, beside prefractal's. Verified rather than assumed: configuring with ONLY --enable-module-frost-enrollment emits -DENABLE_MODULE_FROST=1 alongside -DENABLE_MODULE_FROST_ENROLLMENT=1, and the CMake summary prints "frost ON" for the same configuration -- the latter is what the PARENT_SCOPE lift buys, since the summary runs after add_subdirectory(src) and would otherwise report a module it is compiling in as OFF. The dependency guard is prefractal's implies-frost idiom, copied verbatim along with its reasoning. frost is default-OFF, so the `test x"$enable_module_frost" = x"no"` / `DEFINED X AND NOT X` guard every other module uses -- which reads as "the user disabled it explicitly" for a default-ON dependency -- is true by default here and cannot tell an explicit --disable-module-frost from the default once both are in the cache. Enabling frost-enrollment simply implies frost, with no error. The one frost-module change in the whole series is in this commit: src/modules/frost/session.h gains a declaration for secp256k1_frost_sort_ids, which is defined at session_impl.h:517 and declared nowhere. The params hash needs it to canonicalize identifier order. Prefractal reaches frost's statics through translation-unit ordering alone; rather than inherit reuse-by-link-order, this declares the function where keygen.h:48 already declares derive_pubshare_at, so the reuse goes through an interface. No behavior change: it is a declaration for an existing static definition in the same TU. CI wiring is two files, and skipping either half fails quietly: - ci/ci.sh gets FROST_ENROLLMENT in the reproduction header's variable list and --enable-module-frost-enrollment="$FROST_ENROLLMENT" after the prefractal line. - .github/workflows/ci.yml gets FROST_ENROLLMENT at every PREFRACTAL site: the global default, 11 inline matrix entries and 10 job-level env blocks. Without the default, ci.sh runs under set -eux with an empty $FROST_ENROLLMENT, passes --enable-module-frost-enrollment="", `test x"" = x"yes"` is false, and the module is off in all of CI while ci.sh visibly has the plumbing. Verified programmatically over the parsed workflow: across the 106 effective job contexts, PREFRACTAL and FROST_ENROLLMENT now agree in every single one (45 set to yes, no mismatches), no context sets FROST_ENROLLMENT without FROST or without EXPERIMENTAL, and no context leaves it undefined. ci.sh passes sh -n. The stub test is not a placeholder that has to be deleted later: every entry point must reject an empty helper set and leave its output zeroed, which is true of the stubs and stays true of the finished implementation, so it doubles as the check that all five symbols are reachable from the test binary. Verification. Autotools: ./autogen.sh, then a frost-enrollment-only configure and a full configure with frost, chilldkg, iceberg, prefractal and frost-enrollment all on -- both build with zero warnings under the project's -Werror-grade flag set, ./tests and ./exhaustive_tests exit 0, and `./tests -l` lists the frost_enrollment module. CMake: configure with -DSECP256K1_EXPERIMENTAL=ON -DSECP256K1_ENABLE_MODULE_FROST_ENROLLMENT=ON builds clean and ctest passes 391 tests. nm shows the five new symbols exported from libsecp256k1.so; tools/symbol-check.py could not be run here because python3-lief is not installed in this environment, but all five carry the required secp256k1_ prefix. make dist succeeds and the tarball carries src/modules/frost_enrollment/frost_enrollment.md alongside the other module documents. One unrelated observation from this build: a stale src/ctime_tests-ctime_tests.o left over from an earlier configure with a different module set will fail to link, because automake does not track CPPFLAGS changes across reconfigures. make clean between configurations with different module sets, not a fault in this change. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 33 ++++-- .gitignore | 1 + CMakeLists.txt | 5 + Makefile.am | 5 + ci/ci.sh | 3 +- configure.ac | 22 ++++ src/CMakeLists.txt | 14 +++ src/modules/frost/session.h | 7 ++ .../frost_enrollment/Makefile.am.include | 4 + .../frost_enrollment/enrollment_impl.h | 105 ++++++++++++++++++ src/modules/frost_enrollment/main_impl.h | 14 +++ src/modules/frost_enrollment/tests_impl.h | 52 +++++++++ src/secp256k1.c | 6 + src/tests.c | 7 ++ 14 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 src/modules/frost_enrollment/Makefile.am.include create mode 100644 src/modules/frost_enrollment/enrollment_impl.h create mode 100644 src/modules/frost_enrollment/main_impl.h create mode 100644 src/modules/frost_enrollment/tests_impl.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91780179..bd1bb555 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,7 @@ env: CHILLDKG: 'no' ICEBERG: 'no' PREFRACTAL: 'no' + FROST_ENROLLMENT: 'no' ### test options SECP256K1_TEST_ITERS: 64 BENCH: 'yes' @@ -108,14 +109,14 @@ jobs: matrix: configuration: - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes'} + - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes'} - env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes'} + - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes'} - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', PREFRACTAL: 'yes'} - - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', CPPFLAGS: '-DVERIFY' } + - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes'} + - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } @@ -185,6 +186,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CC: ${{ matrix.cc }} steps: @@ -226,6 +228,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CTIMETESTS: 'no' steps: @@ -269,6 +272,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CTIMETESTS: 'no' steps: @@ -303,6 +307,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CTIMETESTS: 'no' CC: ${{ matrix.cc }} @@ -356,6 +361,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CTIMETESTS: 'no' steps: @@ -414,6 +420,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CTIMETESTS: 'no' SECP256K1_TEST_ITERS: 2 @@ -456,6 +463,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CTIMETESTS: 'no' CFLAGS: '-fsanitize=undefined,address -g' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' @@ -515,6 +523,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CC: ${{ matrix.cc }} SECP256K1_TEST_ITERS: 32 ASM: 'no' @@ -553,6 +562,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' CTIMETESTS: 'no' strategy: @@ -585,15 +595,15 @@ jobs: fail-fast: false matrix: env_vars: - - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes' } + - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes' } - { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 } - - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes' } + - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', CC: 'gcc' } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CC: 'gcc' } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } + - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - BUILD: 'distcheck' steps: @@ -773,6 +783,7 @@ jobs: CHILLDKG: 'yes' ICEBERG: 'yes' PREFRACTAL: 'yes' + FROST_ENROLLMENT: 'yes' steps: - *CHECKOUT diff --git a/.gitignore b/.gitignore index 62334853..24404fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ contrib/gh-pr-create.sh frost_example chilldkg_example iceberg_example +frost_enrollment_example ### CMake /CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index a7c71e68..85788933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ 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_FROST_ENROLLMENT "Enable FROST enrollment module (experimental)." OFF) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) option(SECP256K1_ENABLE_MODULE_GENERATOR "Enable NUMS generator module." ON) @@ -142,6 +143,9 @@ if(NOT SECP256K1_EXPERIMENTAL) if(SECP256K1_ENABLE_MODULE_PREFRACTAL) message(FATAL_ERROR "Prefractal module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") endif() + if(SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT) + message(FATAL_ERROR "FROST enrollment module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") + endif() endif() set(SECP256K1_VALGRIND "AUTO" CACHE STRING "Build with extra checks for running inside Valgrind. [default=AUTO]") @@ -317,6 +321,7 @@ 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(" frost-enrollment .................... ${SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") message(" generator ........................... ${SECP256K1_ENABLE_MODULE_GENERATOR}") message(" rangeproof .......................... ${SECP256K1_ENABLE_MODULE_RANGEPROOF}") diff --git a/Makefile.am b/Makefile.am index 9467933a..15bc7644 100644 --- a/Makefile.am +++ b/Makefile.am @@ -320,6 +320,7 @@ EXTRA_DIST = autogen.sh CHANGELOG.md SECURITY.md EXTRA_DIST += doc/release-process.md doc/safegcd_implementation.md EXTRA_DIST += doc/ellswift.md doc/musig.md doc/iceberg.md doc/prefractal.md EXTRA_DIST += src/modules/frost/frost.md src/modules/chilldkg/chilldkg.md +EXTRA_DIST += src/modules/frost_enrollment/frost_enrollment.md EXTRA_DIST += examples/EXAMPLES_COPYING EXTRA_DIST += sage/gen_exhaustive_groups.sage EXTRA_DIST += sage/gen_split_lambda_constants.sage @@ -401,6 +402,10 @@ if ENABLE_MODULE_PREFRACTAL include src/modules/prefractal/Makefile.am.include endif +if ENABLE_MODULE_FROST_ENROLLMENT +include src/modules/frost_enrollment/Makefile.am.include +endif + if ENABLE_MODULE_ICEBERG include src/modules/iceberg/Makefile.am.include endif diff --git a/ci/ci.sh b/ci/ci.sh index b11e220c..40cc1ccf 100755 --- a/ci/ci.sh +++ b/ci/ci.sh @@ -15,7 +15,7 @@ print_environment() { ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ EXPERIMENTAL ECDH RECOVERY EXTRAKEYS SCHNORRSIG MUSIG SCHNORRSIG_HALFAGG ELLSWIFT \ ECDSA_S2C GENERATOR RANGEPROOF SURJECTIONPROOF WHITELIST ECDSAADAPTOR BPPP \ - FROST CHILLDKG ICEBERG PREFRACTAL SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \ + FROST CHILLDKG ICEBERG PREFRACTAL FROST_ENROLLMENT SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \ EXAMPLES \ HOST WRAPPER_CMD \ CC CFLAGS CPPFLAGS AR NM \ @@ -73,6 +73,7 @@ fi --enable-module-chilldkg="$CHILLDKG" \ --enable-module-iceberg="$ICEBERG" \ --enable-module-prefractal="$PREFRACTAL" \ + --enable-module-frost-enrollment="$FROST_ENROLLMENT" \ --enable-examples="$EXAMPLES" \ --enable-ctime-tests="$CTIMETESTS" \ --with-valgrind="$WITH_VALGRIND" \ diff --git a/configure.ac b/configure.ac index 683c3e14..81f426d6 100644 --- a/configure.ac +++ b/configure.ac @@ -260,6 +260,11 @@ AC_ARG_ENABLE(module_prefractal, [], [SECP_SET_DEFAULT([enable_module_prefractal], [no], [yes])]) +AC_ARG_ENABLE(module_frost_enrollment, + AS_HELP_STRING([--enable-module-frost-enrollment],[enable FROST enrollment module (experimental)]), + [], + [SECP_SET_DEFAULT([enable_module_frost_enrollment], [no], [yes])]) + # Test-only override of the (autodetected by the C code) "widemul" setting. # Legal values are: # * int64 (for [u]int64_t), @@ -530,6 +535,18 @@ if test x"$enable_module_ellswift" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" fi +# Like the prefractal block below, this must stay ahead of the frost block +# further down: the enable_module_frost assignment here is only observed by +# blocks that run after it. +if test x"$enable_module_frost_enrollment" = x"yes"; then + # frost defaults to no, so the "you disabled it explicitly" guard every + # other block uses would reject every frost-enrollment build; enabling + # frost-enrollment simply implies frost. See the prefractal block below for + # the full reasoning. + enable_module_frost=yes + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_FROST_ENROLLMENT=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 @@ -654,6 +671,9 @@ if test x"$enable_experimental" = x"no"; then 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_enrollment" = x"yes"; then + AC_MSG_ERROR([FROST enrollment 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 @@ -704,6 +724,7 @@ 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([ENABLE_MODULE_FROST_ENROLLMENT], [test x"$enable_module_frost_enrollment" = 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"]) @@ -749,6 +770,7 @@ 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 " module frost-enrollment = $enable_module_frost_enrollment" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 25c428b5..05ee9689 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,20 @@ if(SECP256K1_ENABLE_MODULE_ELLSWIFT) set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ellswift.h) endif() +# Like the prefractal block below, this must precede the FROST block: the +# set() call here is what forces frost on, and it is only observed by blocks +# that run after it. +if(SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT) + # frost defaults to OFF, so the "DEFINED AND NOT" guard the other blocks use + # would reject every frost-enrollment build; enabling frost-enrollment + # simply implies frost. See the prefractal block below for the full + # reasoning, including why the flag is also lifted into the parent scope. + set(SECP256K1_ENABLE_MODULE_FROST ON) + set(SECP256K1_ENABLE_MODULE_FROST ON PARENT_SCOPE) + add_compile_definitions(ENABLE_MODULE_FROST_ENROLLMENT=1) + set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_frost_enrollment.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) diff --git a/src/modules/frost/session.h b/src/modules/frost/session.h index 01bb1c4f..7c560a3c 100644 --- a/src/modules/frost/session.h +++ b/src/modules/frost/session.h @@ -31,6 +31,13 @@ typedef struct { int g_times_gacc_parity; } secp256k1_frost_session_internal; +/* Sorts n_ids identifiers from ids into out (which must have room for + * n_ids entries and must not alias ids) in ascending order. Used to + * canonicalize a signer set before hashing it, so that parties holding the + * same set in different orders agree on the digest. Requires n_ids <= + * SECP256K1_FROST_MAX_PARTICIPANTS. */ +static void secp256k1_frost_sort_ids(uint32_t *out, const uint32_t *ids, size_t n_ids); + /* Saves the two secret scalars k[0], k[1] into a secnonce. */ static void secp256k1_frost_secnonce_save(secp256k1_frost_secnonce *secnonce, const secp256k1_scalar *k); diff --git a/src/modules/frost_enrollment/Makefile.am.include b/src/modules/frost_enrollment/Makefile.am.include new file mode 100644 index 00000000..f8f13831 --- /dev/null +++ b/src/modules/frost_enrollment/Makefile.am.include @@ -0,0 +1,4 @@ +include_HEADERS += include/secp256k1_frost_enrollment.h +noinst_HEADERS += src/modules/frost_enrollment/main_impl.h +noinst_HEADERS += src/modules/frost_enrollment/enrollment_impl.h +noinst_HEADERS += src/modules/frost_enrollment/tests_impl.h diff --git a/src/modules/frost_enrollment/enrollment_impl.h b/src/modules/frost_enrollment/enrollment_impl.h new file mode 100644 index 00000000..5d082173 --- /dev/null +++ b/src/modules/frost_enrollment/enrollment_impl.h @@ -0,0 +1,105 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_FROST_ENROLLMENT_IMPL_H +#define SECP256K1_MODULE_FROST_ENROLLMENT_IMPL_H + +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_frost_enrollment.h" + +/* This module is compiled into the same translation unit as frost and is + * included after it, so it may use frost's static internals. It adds nothing + * to them: the Lagrange machinery, the identifier conventions and the id + * canonicalization all come from there. */ +#include "../frost/keygen.h" +#include "../frost/session.h" + +#include "../../eckey.h" +#include "../../group.h" +#include "../../hash.h" +#include "../../scalar.h" +#include "../../util.h" + +int secp256k1_frost_enrollment_params_hash(const secp256k1_context *ctx, unsigned char *out32, const secp256k1_pubkey *thresh_pk, const uint32_t *ids, size_t n_ids, uint32_t new_id, size_t n_participants, uint32_t threshold) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(out32 != NULL); + memset(out32, 0, 32); + ARG_CHECK(thresh_pk != NULL); + ARG_CHECK(ids != NULL); + (void)n_ids; + (void)new_id; + (void)n_participants; + (void)threshold; + return 0; +} + +int secp256k1_frost_enrollment_shares_gen(const secp256k1_context *ctx, unsigned char *shares32_out, unsigned char *params_hash32_out, unsigned char *session_secrand32, const unsigned char *secshare32, const secp256k1_pubkey *thresh_pk, const uint32_t *ids, size_t n_ids, uint32_t my_id, uint32_t new_id, size_t n_participants, uint32_t threshold) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(shares32_out != NULL); + ARG_CHECK(params_hash32_out != NULL); + memset(params_hash32_out, 0, 32); + ARG_CHECK(session_secrand32 != NULL); + ARG_CHECK(secshare32 != NULL); + ARG_CHECK(thresh_pk != NULL); + ARG_CHECK(ids != NULL); + (void)n_ids; + (void)my_id; + (void)new_id; + (void)n_participants; + (void)threshold; + return 0; +} + +int secp256k1_frost_enrollment_share_agg(const secp256k1_context *ctx, unsigned char *sigma32_out, uint32_t *mismatch_id, const unsigned char *all_shares32, const unsigned char *received_params_hashes32, const secp256k1_pubkey *thresh_pk, const uint32_t *ids, size_t n_ids, uint32_t my_id, uint32_t new_id, size_t n_participants, uint32_t threshold) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sigma32_out != NULL); + memset(sigma32_out, 0, 32); + if (mismatch_id != NULL) { + *mismatch_id = UINT32_MAX; + } + ARG_CHECK(all_shares32 != NULL); + ARG_CHECK(received_params_hashes32 != NULL); + ARG_CHECK(thresh_pk != NULL); + ARG_CHECK(ids != NULL); + (void)n_ids; + (void)my_id; + (void)new_id; + (void)n_participants; + (void)threshold; + return 0; +} + +int secp256k1_frost_enrollment_pubshare_derive(const secp256k1_context *ctx, secp256k1_pubkey *new_pubshare_out, const secp256k1_pubkey *pubshares, const uint32_t *ids, size_t n_ids, uint32_t new_id, size_t n_participants, uint32_t threshold) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(new_pubshare_out != NULL); + memset(new_pubshare_out, 0, sizeof(*new_pubshare_out)); + ARG_CHECK(pubshares != NULL); + ARG_CHECK(ids != NULL); + (void)n_ids; + (void)new_id; + (void)n_participants; + (void)threshold; + return 0; +} + +int secp256k1_frost_enrollment_secshare_gen(const secp256k1_context *ctx, unsigned char *secshare32_out, const unsigned char *sigmas32, const secp256k1_pubkey *thresh_pk, const uint32_t *ids, size_t n_ids, uint32_t new_id, size_t n_participants, uint32_t threshold, const unsigned char *expected_params_hash32, const secp256k1_pubkey *expected_pubshare) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secshare32_out != NULL); + memset(secshare32_out, 0, 32); + ARG_CHECK(sigmas32 != NULL); + ARG_CHECK(thresh_pk != NULL); + ARG_CHECK(ids != NULL); + (void)n_ids; + (void)new_id; + (void)n_participants; + (void)threshold; + (void)expected_params_hash32; + (void)expected_pubshare; + return 0; +} + +#endif diff --git a/src/modules/frost_enrollment/main_impl.h b/src/modules/frost_enrollment/main_impl.h new file mode 100644 index 00000000..023fe67c --- /dev/null +++ b/src/modules/frost_enrollment/main_impl.h @@ -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_FROST_ENROLLMENT_MAIN_H +#define SECP256K1_MODULE_FROST_ENROLLMENT_MAIN_H + +/* One layer only. Enrollment is three rounds of scalar arithmetic over key + * material the frost module already knows how to load and save, so there is + * no keygen, no serialization and no state of its own here. */ +#include "enrollment_impl.h" + +#endif diff --git a/src/modules/frost_enrollment/tests_impl.h b/src/modules/frost_enrollment/tests_impl.h new file mode 100644 index 00000000..bf880ba3 --- /dev/null +++ b/src/modules/frost_enrollment/tests_impl.h @@ -0,0 +1,52 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_FROST_ENROLLMENT_TESTS_IMPL_H +#define SECP256K1_MODULE_FROST_ENROLLMENT_TESTS_IMPL_H + +#include "../../../include/secp256k1_frost_enrollment.h" + +/* Every entry point must reject an empty helper set and leave its output + * zeroed. This holds for the stubs this commit adds and for the finished + * implementation alike, so it doubles as the check that all five symbols are + * reachable from the test binary. */ +static void run_frost_enrollment_rejects_empty_set_test(void) { + secp256k1_pubkey pk; + secp256k1_pubkey pubshare; + unsigned char buf32[32]; + unsigned char secrand32[32]; + unsigned char hash32[32]; + uint32_t ids[1] = { 0 }; + + memset(&pk, 0, sizeof(pk)); + memset(&pubshare, 0xff, sizeof(pubshare)); + memset(secrand32, 0x11, sizeof(secrand32)); + + memset(buf32, 0xff, sizeof(buf32)); + CHECK(secp256k1_frost_enrollment_params_hash(CTX, buf32, &pk, ids, 0, 0, 1, 2) == 0); + CHECK(secp256k1_is_zero_array(buf32, sizeof(buf32))); + + memset(buf32, 0xff, sizeof(buf32)); + memset(hash32, 0xff, sizeof(hash32)); + CHECK(secp256k1_frost_enrollment_shares_gen(CTX, buf32, hash32, secrand32, buf32, &pk, ids, 0, 0, 1, 1, 2) == 0); + CHECK(secp256k1_is_zero_array(hash32, sizeof(hash32))); + + memset(buf32, 0xff, sizeof(buf32)); + CHECK(secp256k1_frost_enrollment_share_agg(CTX, buf32, NULL, buf32, hash32, &pk, ids, 0, 0, 1, 1, 2) == 0); + CHECK(secp256k1_is_zero_array(buf32, sizeof(buf32))); + + CHECK(secp256k1_frost_enrollment_pubshare_derive(CTX, &pubshare, &pk, ids, 0, 0, 1, 2) == 0); + CHECK(secp256k1_is_zero_array((unsigned char *)&pubshare, sizeof(pubshare))); + + memset(buf32, 0xff, sizeof(buf32)); + CHECK(secp256k1_frost_enrollment_secshare_gen(CTX, buf32, buf32, &pk, ids, 0, 0, 1, 2, NULL, NULL) == 0); + CHECK(secp256k1_is_zero_array(buf32, sizeof(buf32))); +} + +static const struct tf_test_entry tests_frost_enrollment[] = { + CASE1(run_frost_enrollment_rejects_empty_set_test), +}; + +#endif /* SECP256K1_MODULE_FROST_ENROLLMENT_TESTS_IMPL_H */ diff --git a/src/secp256k1.c b/src/secp256k1.c index 54b726e4..5ccf1e2c 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -965,6 +965,12 @@ static int secp256k1_ge_parse_ext(secp256k1_ge* ge, const unsigned char *in33) { # include "modules/prefractal/main_impl.h" #endif +/* Must follow the frost include above: this module calls frost's static + * internals in place rather than duplicating them. */ +#ifdef ENABLE_MODULE_FROST_ENROLLMENT +# include "modules/frost_enrollment/main_impl.h" +#endif + #ifdef ENABLE_MODULE_ICEBERG # include "modules/iceberg/main_impl.h" #endif diff --git a/src/tests.c b/src/tests.c index 61f94e91..52eb49a2 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7932,6 +7932,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/prefractal/tests_impl.h" #endif +#ifdef ENABLE_MODULE_FROST_ENROLLMENT +# include "modules/frost_enrollment/tests_impl.h" +#endif + #ifdef ENABLE_MODULE_ICEBERG # include "modules/iceberg/tests_impl.h" #endif @@ -8313,6 +8317,9 @@ static const struct tf_test_module registry_modules[] = { #ifdef ENABLE_MODULE_PREFRACTAL MAKE_TEST_MODULE(prefractal), #endif +#ifdef ENABLE_MODULE_FROST_ENROLLMENT + MAKE_TEST_MODULE(frost_enrollment), +#endif #ifdef ENABLE_MODULE_ICEBERG MAKE_TEST_MODULE(iceberg), #endif