Files
precompile/TESTING_GAPS.md
Hanzo AI f27d6fc836 corona → corona — final sweep (live source clean)
Follow-up to the main rename: caught README/PRIMER/LLM/MDX/Sol that survived
the first pass. Live source greps clean now (audit output:
/private/tmp/.../tasks/bs3iodq61.output → only build artifacts +
vendor/ + binaries remain, which auto-regenerate).
2026-06-06 15:46:26 -07:00

18 KiB

Test Coverage Gaps — Red Team Adversarial Review

Date: 2026-04-10 Reviewer: Red Team (adversarial security audit of test suite) Scope: ~/work/lux/precompile/ — all 62 test files across 40+ packages Finding: The test suite gives a false sense of security in several critical areas.


Executive Summary

The test suite is broad (341+ tests across 62 files) but shallow in exactly the places that matter for consensus-critical precompile code. The three most dangerous gaps:

  1. FROST "valid signature" test never actually verifies a valid signature — it uses mock data and only checks "no error", not "verification passed". This is a test that cannot fail even if verification is completely broken.
  2. Zero concurrency tests despite 15+ packages with global mutable state — not a single t.Parallel() or goroutine-based test in the entire suite. The FROST liftX cache, FHE ciphertext store, ZK verifier caches, and DEX state all have data races that are untested.
  3. No Known Answer Tests (KATs) for FIPS algorithms — ML-DSA (FIPS 204), SLH-DSA (FIPS 205), and ML-KEM (FIPS 203) all generate fresh keys per test. No test verifies the exact output against a NIST reference vector. If the underlying library is silently wrong, no test catches it.

1. Per-Package Missing Tests

1.1 Packages With ZERO Tests

Package Exported Functions Severity
modules RegisterModule, GetPrecompileModuleByAddress, GetPrecompileModule, RegisteredModules, ReservedAddress, AddressRange.Contains HIGH — RegisterModule is the trust root for all precompile registration; untested validation logic could allow address collisions or out-of-range registration
precompileconfig Upgrade.Timestamp, Upgrade.IsDisabled, Upgrade.Equal, uint64PtrEqual MEDIUM — upgrade logic controls when precompiles activate; nil pointer edge cases in Equal are untested

1.2 FROST — Tests That Prove Nothing

Missing Test File Severity
Valid signature with actual FROST threshold keygen + signing frost/contract_test.go:15 CRITICALTestFROSTVerify_ValidSignature uses byte(i) mock data, never generates a real FROST signature. The test asserts require.NoError but the precompile returns result[31]==0 (invalid) for this garbage input. The test never checks result[31]==1. This test passes whether verification works or is completely broken.
Concurrent liftXCache access frost/contract.go:151 HIGHliftXCache is a global map[[32]byte]curve.Point guarded by sync.RWMutex. No test exercises concurrent Run() calls. A lock ordering bug or missing lock would be a consensus-splitting data race.
Cache eviction / bounded size frost/contract.go:178 MEDIUM — Cache is bounded to 1024 entries but no test verifies behavior at capacity or eviction semantics.
Gas overflow with max uint32 totalSigners frost/contract_test.go HIGH — The CGGMP21 package has security_test.go testing gas overflow; FROST has the identical vulnerability pattern (FROSTVerifyBaseGas + totalSigners*FROSTVerifyPerSignerGas) but no equivalent test.

1.3 CGGMP21

Missing Test Severity
Empty input (0 bytes) MEDIUM
Nil input MEDIUM
All-zero input (MinInputSize bytes of 0x00) — threshold=0, total=0 MEDIUM
All-ones input (0xFF bytes) LOW
Input exactly one byte longer than MinInputSize LOW
Concurrent Run() invocations HIGH
Gas = 0 must return ErrOutOfGas HIGH — not tested
Gas = RequiredGas - 1 must return ErrOutOfGas HIGH — not tested
Gas = max uint64 must succeed without overflow MEDIUM

1.4 ML-DSA (mldsa/)

Missing Test Severity
FIPS 204 KAT vectors (NIST ACVP test vectors for ML-DSA-44/65/87) HIGH — Tests generate fresh keys; a library bug producing wrong output would pass all tests
Nil input MEDIUM
Gas = 0 HIGH
Batch verify with count > 255 (overflow in byte(count)) MEDIUMcreateBatchInput uses byte(count >> 8), byte(count) which caps at 65535, but no test probes the boundary
Concurrent Run() MEDIUM
batchVerifyGPU — exported but untested MEDIUM
verifyGPU — exported but untested MEDIUM
readUint256 — exported but untested LOW

1.5 SLH-DSA (slhdsa/)

Missing Test Severity
FIPS 205 KAT vectors HIGH
All 12 modes tested (only SHA2_128s, SHA2_128f, SHAKE_128s, SHAKE_128f tested; missing SHA2_192s/f, SHAKE_192s/f, SHA2_256s/f, SHAKE_256s/f) HIGH — 8 of 12 modes have no end-to-end verification test
ModeName — exported but untested LOW
verifySLHDSAGPU — exported but untested MEDIUM
Gas = 0 (only tested once via TestSLHDSAVerify_OutOfGas with gas=1000, not gas=0) LOW

1.6 Pulsar Lattice Threshold (Corona/)

Missing Test Severity
deserializeSignature — exported, complex deserialization, untested directly MEDIUM
deserializePoly — exported but untested directly MEDIUM
initializeVector, initializeMatrix — exported but untested LOW
Malformed polynomial coefficients (negative, overflow modulus) HIGH — lattice signatures depend on coefficient bounds; out-of-range values could forge signatures
Gas = 0 MEDIUM
Concurrent Run() MEDIUM

1.7 ML-KEM (mlkem/)

Missing Test Severity
FIPS 203 KAT vectors (NIST ACVP for ML-KEM-512/768/1024) HIGH
Determinism: two encapsulations with same seed must produce same output MEDIUM — tests don't control randomness
Decapsulation with wrong ciphertext (CCA2 test) HIGH — ML-KEM must reject tampered ciphertexts; no test corrupts a ciphertext and checks decapsulation fails
getModeParams — exported but untested directly LOW
encapsulateGPU — exported but untested MEDIUM

1.8 secp256r1

Missing Test Severity
Actual NIST CAVP test vectors (current TestContract_NISTTestVector generates random keys, does NOT use NIST vectors) HIGH — the comment on line 241 says "In production, use full NIST test vectors" but the test uses rand.Reader
verifyGPU — exported but untested MEDIUM
Gas accounting (test calls c.Run(input) without gas parameter — different signature than StatefulPrecompiledContract) MEDIUM

1.9 KZG4844 (kzg4844/)

Missing Test Severity
EIP-4844 reference test vectors from consensus-specs HIGH — no trusted_setup.json verification, no EIP-4844 KAT
blobToCommitmentGPU — exported but untested MEDIUM
Blob with field element >= BLS12-381 modulus HIGHcreateValidBlob intentionally uses small values; no test verifies rejection of invalid field elements
Nil input MEDIUM

1.10 FHE (fhe/)

Missing Test Severity
Concurrent ciphertext store access (ciphertextStore and ciphertextTypes are global maps with no mutex) CRITICALfhe/contract.go:916-917 — two unprotected global maps. Zero concurrency tests. This is a consensus-splitting data race.
Ciphertext handle collision (two different ciphertexts producing the same hash) MEDIUM
Gas = 0 for Run() HIGH
TypeEuint128 and TypeEuint256 arithmetic tests MEDIUM — only uint8 tested
Noise budget exhaustion after chained operations HIGH — no test chains 10+ FHE operations to verify noise doesn't corrupt results

1.11 Packages With Thin But Adequate Tests

Package Assessment
blake3 Good coverage for operations and gas. Missing: concurrent Run(), nil input, all-ones input
ed25519 Good basic coverage. Missing: ed25519 RFC 8032 test vectors (uses random keys)
sr25519 Has known test vector (Alice's Substrate key). Missing: concurrent test
x25519 Has RFC 7748 vectors. Missing: low-order point rejection test
xwing Good for encapsulation. Missing: determinism test (controlled seed), concurrent test
babyjubjub Missing: concurrent test
bls12381 Missing: EIP-2537 reference vectors, concurrent test
curve25519 Missing: concurrent test
pasta Missing: concurrent test
pedersen Missing: concurrent test
poseidon Missing: concurrent test
hpke Missing: RFC 9180 test vectors, concurrent test
ring Missing: concurrent test
math Missing: concurrent test
stableswap Missing: concurrent test
compute Missing: concurrent test

2. Cross-Cutting Gaps

2.1 Address Uniqueness (Partial)

The registry/registry_test.go has TestNoAddressCollisions which checks AllPrecompiles for duplicates. However: this only checks the registry metadata, not the actual ContractAddress constants declared in each package. A package could declare ContractAddress = 0x1234 but register a different address in its init() function. No test cross-references.

Missing: A test that imports every package, calls RegisteredModules(), and verifies each module's Address matches its package-level ContractAddress constant.

2.2 LP Documentation vs Code Validation: NOT TESTED

No test parses the LP documentation (docs/) and verifies that documented addresses match code constants. The docs are a Next.js app (MDX), not machine-parseable test vectors.

2.3 Consensus Determinism: INADEQUATELY TESTED

Only blake3/contract_test.go has determinism tests (TestHashDeterminism, TestMerkleRootDeterminism). The following precompiles use randomness internally and have NO determinism test:

Package Source of Non-Determinism Determinism Test?
xwing rand.Reader in encapsulation NO — test explicitly checks outputs DIFFER (TestEncapsulate_DifferentInvocations), which is correct for KEM but means determinism is untestable without seed control
mlkem rand.Reader in encapsulation NO
fhe tfheRandom uses seed Partial — tests exist but don't verify same seed produces same output across runs
pqcrypto rand.Reader in ML-KEM encapsulation NO

2.4 Gas Griefing: SYSTEMATICALLY UNTESTED

No package has a complete gas edge case test suite. The required tests per precompile:

Test Packages That Have It Packages Missing It
gas = 0 NONE ALL
gas = RequiredGas - 1 ed25519 (implicitly) frost, cggmp21, mldsa, slhdsa, Corona, blake3, kzg4844, pqcrypto, mlkem, secp256r1, x25519, xwing, fhe, quantum, quasar, bls12381, babyjubjub, curve25519, pasta, pedersen, poseidon, hpke, ring, math, stableswap, compute
gas = RequiredGas (exact) mldsa, slhdsa Most others supply excess gas
gas = max uint64 contract (partial) ALL precompile packages

2.5 Nil and Empty Input: INCONSISTENTLY TESTED

Input Type Packages Testing It
nil input NONE explicitly (some packages may handle it via length check)
[]byte{} empty kzg4844, pasta, bls12381, xwing, poseidon, pedersen, curve25519, hpke, x25519, babyjubjub
All-zero input secp256r1 (TestContract_ZeroValues)
All-0xFF input NONE
Off-by-one short cggmp21, frost, mldsa, slhdsa, sr25519, ed25519, x25519, xwing, kzg4844
Off-by-one long secp256r1 (TestContract_InvalidInputLength with 161 bytes)

3. Test Anti-Patterns Found

3.1 CRITICAL: Test That Cannot Fail

File: frost/contract_test.go:15-58 Issue: TestFROSTVerify_ValidSignature fills input with byte(i) sequential data (not a real FROST signature). The test asserts require.NoError(t, err) and require.NotNil(t, result) but never checks result[31] == 1. Since the precompile returns result[31] == 0 for invalid signatures without error, this test passes whether FROST verification works or is entirely broken. This is a test that gives false assurance about the most critical Schnorr threshold signature precompile.

3.2 CRITICAL: Unprotected Global State in FHE

File: fhe/contract.go:916-917

var ciphertextStore = make(map[common.Hash][]byte)
var ciphertextTypes = make(map[common.Hash]uint8)

These global maps have NO mutex. The FHE test file (fhe/fhe_test.go) calls storeCiphertext and getCiphertext but never from concurrent goroutines. In production, multiple EVM transactions in the same block could execute FHE operations concurrently, causing a data race that crashes the node or produces inconsistent state.

3.3 HIGH: No subtle.ConstantTimeCompare in Tests

Zero test files use crypto/subtle.ConstantTimeCompare for comparing cryptographic outputs. While test-time timing leaks don't matter directly, the pattern of using require.Equal for byte comparisons means tests don't verify that the production code uses constant-time comparison. A grep for subtle.ConstantTimeCompare or hmac.Equal in non-test code would determine whether this is an actual production vulnerability.

3.4 HIGH: secp256r1 Tests Use Wrong Interface

File: secp256r1/contract_test.go:54 The test calls c.Run(input) (2 args), but the StatefulPrecompiledContract interface uses Run(accessibleState, caller, addr, input, gas, readOnly) (6 args). The Contract type in secp256r1 implements a different, simpler interface. This means gas accounting, caller validation, and read-only enforcement are completely untested for secp256r1.

3.5 HIGH: secp256r1 "NIST Test Vector" Uses Random Keys

File: secp256r1/contract_test.go:237-258

func TestContract_NISTTestVector(t *testing.T) {
    // NIST CAVP test vector (simplified example)
    // In production, use full NIST test vectors from FIPS 186-3
    privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

The function is named TestContract_NISTTestVector but generates a random key. This is not a test vector. The comment acknowledges this is a placeholder ("simplified example") but it was never replaced with actual NIST CAVP vectors.

3.6 MEDIUM: Benchmark Errors Silently Ignored

Multiple benchmark functions discard errors:

  • frost/contract_test.go:166: _, _, _ = precompile.Run(...)
  • cggmp21/contract_test.go:266: _, _, _ = precompile.Run(...)
  • mldsa/contract_test.go:420: _, _, _ = MLDSAVerifyPrecompile.Run(...)
  • ed25519/contract_test.go:145: Ed25519VerifyPrecompile.Run(...) (return values discarded)
  • secp256r1/contract_test.go:272: c.Run(input) (error discarded)

If the precompile starts returning errors, benchmarks silently measure error-path performance, not verification performance.

3.7 MEDIUM: Tests Don't Verify Gas Consumption Exactly

Most tests check remainingGas > 0 or remainingGas == 0 but don't verify suppliedGas - remainingGas == RequiredGas(input). This means a precompile could consume more or less gas than advertised and tests would not catch it.

3.8 LOW: Overlapping Test Files

dex/pause_freeze_test.go and dex/pausefreeze_test.go are two separate files that likely test the same functionality with different names. Potential for confusion or test collision.


4. Severity Summary

Severity Count Description
CRITICAL 3 FROST false-positive test, FHE unprotected global maps, zero concurrency tests for 15+ stateful packages
HIGH 18 Missing KATs for FIPS algorithms, missing gas=0 tests, missing gas overflow tests for FROST, SLH-DSA mode coverage gap, secp256r1 wrong interface, KZG4844 missing field modulus rejection
MEDIUM 22 Missing nil/empty inputs, untested exported functions, missing determinism tests, benchmark error swallowing
LOW 8 Untested utility functions, duplicate test file names, missing off-by-one-long tests

5. Priority Fix Order for Blue

  1. FROST: Write a real threshold keygen+sign+verify test — generate actual FROST keys, sign a message, verify through the precompile, assert result[31] == 1. The current test is security theater.

  2. FHE: Add mutex to ciphertextStore/ciphertextTypes OR document that concurrent access is impossible — if the EVM guarantees single-threaded execution within a block, document this assumption. If not, add sync.RWMutex.

  3. Add concurrency tests for FROST liftXCache, ZK caches, threshold manager, DEX state — run 100 goroutines calling Run() simultaneously with -race flag. Start with FROST (consensus-critical) and FHE (global maps).

  4. Add FIPS 203/204/205 KAT vectors — obtain test vectors from NIST ACVP and add them for ML-KEM, ML-DSA, and SLH-DSA. The current tests only prove "the library is internally consistent" not "the library is correct".

  5. Add gas=0 and gas=RequiredGas-1 tests to every precompile — a systematic table-driven test that calls Run() with insufficient gas and verifies ErrOutOfGas.

  6. Fix FROST gas overflow — port the CGGMP21 security_test.go pattern to FROST.

  7. Test modules/RegisterModule — verify address collision rejection, reserved range enforcement, config key uniqueness, BlackholeAddr rejection.

  8. Test precompileconfig/Upgrade.Equal — nil pointers, matching timestamps, disabled flags.

  9. Add secp256r1 NIST CAVP vectors — replace the fake "NISTTestVector" test with actual vectors.

  10. Test SLH-DSA modes 192 and 256 — 8 of 12 modes have no verification test.