Refs #1001, building on #1923.
Group operations currently inherit coordinate metadata that can be tighter than their advertised bounds, so successful VERIFY tests can depend on the caller that constructed a point. Require complete ge/gej inputs to have exactly the affine (4,3) or Jacobian (4,4,1) magnitude bounds, and publish those bounds at complete outputs after checking the original metadata and actual field limbs. This makes checking each operation against its declared input bounds sufficient, provided its reachable paths are exercised.
The contract also fixes normalized = 0: preserving an accidentally normalized input/output would leave the same dependence between callers. That deliberately extends the magnitude-only proposal and requires explicit normalization before parity, serialization and zero checks in several modules. Multiplication tables normalize where a tighter field bound is needed, and complete points are published after direct coordinate construction and the final Z adjustments; partial scratch values and failed parse outputs remain outside the complete-point contract, and clearing still wipes all bytes. Boxed bare-field arithmetic is deferred.
The group_magnitude, group_magnitude_tables and group_magnitude_reject test targets exercise physical representations and unchanged values/limbs, infinity, aliasing, conversions, parsing, serialization, clearing, signed table selection, and rejection of invalid input/output metadata. On POSIX/VERIFY builds, the 26 rejection cases include actual limbs inconsistent with their original claims, so relaxing metadata cannot hide invalid fields; initialized infinity coordinates are checked for exact zero limbs.
Validation of the final source tree:
- ARM64 Clang, default backend, all modules/examples: 242/242 tests at 64 iterations, with
-Werror -pedantic-errors. - Native x86_64 Clang: upstream
ci/ci.shwith int128, assembly, all modules/examples, 64 iterations, warnings as errors, symbol checking, benchmark smoke tests, Valgrind ctime checks, and byte-identical regenerated precomputation/test vectors. - Native x86_64 int64/10x26, ASan+UBSan, no assembly: 240/240 tests at 16 iterations.
- Fresh LLVM instrumentation preserving VERIFY: 150/150 instrumented changed production lines; 152/152 group branch outcomes after merging matching locations across VERIFY/noverify/exhaustive binaries and filtering assertion/preprocessor/void lines. Raw
group_impl.hcoverage is 99.66% lines and 71.24% branches; the filtered count is not raw branch coverage. - Additional local parent/patch differential checks: 1,030 keygen/ECDSA/recovery/Schnorr cases and 256 expanded module scenarios plus tweak boundaries, byte-identical across parent Release, patch Release and patched int64 ASan/UBSan builds. The expanded corpus covers ECDH, ElligatorSwift, extrakeys, MuSig and Silent Payments. Mutation checks also exercise the sensitivity of the new assertions.
The subprocess rejection tests require POSIX fork; they are not enabled on every platform.
Native x86_64, AMD EPYC 9R14, Ubuntu 24.04, Clang 18 Release with x86_64 assembly: ten alternating parent/patch pairs, 10,000 iterations per internal sample. Medians of reported round averages, in microseconds:
| Operation | Parent | Patch | Change |
|---|---|---|---|
| ecdh | 35.1 | 35.3 | +0.57% |
| ellswift_ecdh | 38 | 38.3 | +0.79% |
| ellswift_encode | 17.6 | 17.6 | +0.00% |
| ellswift_decode | 7.78 | 7.77 | -0.13% |
| ellswift_keygen | 34.4 | 34.4 | +0.00% |
| ec_keygen | 16.8 | 16.8 | +0.00% |
| ecdsa_sign | 22.7 | 22.7 | +0.00% |
| ecdsa_verify | 36.6 | 36.7 | +0.27% |
| ecdsa_recover | 37.8 | 37.9 | +0.26% |
| schnorrsig_sign | 17.8 | 17.8 | +0.00% |
| schnorrsig_verify | 37.1 | 37.2 | +0.27% |
ECDH and ElligatorSwift ECDH show small measured increases; this is not a zero-overhead claim. The measurements are specific to this configuration, with finite sampling and rounded benchmark output.
<details> <summary>Reproduction commands</summary>
All modules are enabled (including optional recovery). The ordinary suite and the focused new tests can be run with:
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_C_FLAGS='-Werror -pedantic-errors' \
-DSECP256K1_ENABLE_MODULE_RECOVERY=ON -DSECP256K1_BUILD_EXAMPLES=ON
cmake --build build -j2
SECP256K1_TEST_ITERS=64 ctest --test-dir build --output-on-failure
build/bin/tests --target=group_magnitude --target=group_magnitude_tables \
--target=group_magnitude_reject --iterations=16
For the 10x26 sanitizer configuration, additionally select SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int64, SECP256K1_ASM=OFF, SECP256K1_BUILD_CTIME_TESTS=OFF, and append -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer to CMAKE_C_FLAGS.
The native benchmarks use identical Clang Release configurations on parent and patch, with SECP256K1_ASM=x86_64 and recovery enabled. Run ten pairs, alternating which revision runs first:
SECP256K1_BENCH_ITERS=10000 build/bin/bench ecdh ellswift keygen sign verify recover
Each invocation performs ten internal samples per operation. Results in the table are medians of the ten reported round averages; the machine ran no other validation work during measurement.
</details>
One behavior difference for corrupted opaque pubkeys outside the API contract: an X storage value equal to the field modulus now invokes the illegal-argument callback instead of passing the old zero check.
Implementation and validation used AI assistance under human direction; the checks reported here were executed, and are finite evidence rather than a proof of correctness or constant-time behavior on every platform.