mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
Closes Red CRITICAL-2 (red-c-new-1). The luxcpp/accel layer's M-1
tri-state propagation (5a02e55b) was effectively defeated by Go
consumers that swallowed every error into a silent CPU fallback. The
consensus-split-prevention claim was hollow: a malformed input the
GPU rejected with INVALID_ARGUMENT would silently CPU-pass via
cloudflare/circl, which accepts arbitrary-length messages per FIPS
204. Two nodes running the same input could produce different
verdicts depending on whether they ran GPU or CPU → consensus split.
Fix: ErrInvalidArgument from accel propagates as a hard error;
ErrNotSupported, ErrOutOfMemory, ErrKernelFailed, ErrNoBackends
remain silent-fallback (they're recoverable: GPU path unavailable,
not contract violation). Three call sites updated:
* crypto/mldsa/gpu.go::batchVerifyGPU — returns (false, err) with
ErrInvalidArgument so batch.go::BatchVerify panics (no error
return, same shape as existing length-mismatch + mixed-mode
panics in the same function).
* crypto/mldsa/gpu.go::batchSignGPU — same shape; batch.go::
BatchSign already returns error so it surfaces directly.
* crypto/pq/mldsa/gpu/gpu_cgo.go::{signBatch,verifyBatch} — same
classifier; signBatch returns the error to BatchSign caller,
verifyBatch returns (nil, err) to BatchVerify caller.
Skip-list in batchVerifyGPU: when ErrInvalidArgument bubbles from
MLDSAVerifyBatch and the mode is ML-DSA-65, do NOT fall back to the
legacy DilithiumVerifyBatch path — that path runs the SAME C ABI
under a different dispatch slot and would reject again. Only mask
the legacy retry when the FIRST error was something other than
ErrInvalidArgument (NotSupported is the canonical case: a substrate
that doesn't publish the new MLDSAVerifyBatch slot but does publish
the legacy Dilithium3 kernel).
New test: pq/mldsa/gpu/invalid_argument_propagation_test.go locks
the policy in as a unit test. It verifies:
1. accel.ErrInvalidArgument exists.
2. errors.Is(...) detects it through fmt.Errorf %w wraps.
3. Other accel sentinels do not alias it.
4. The classifier ("hard" vs "recover") matches the dispatch code
for nil, ErrInvalidArgument (raw + wrapped), ErrNotSupported,
ErrOutOfMemory, ErrKernelFailed, ErrNoBackends, and an unrelated
error.
This is the propagation policy test the prompt asked for. A
real-input fault-injection test would need a > 2 GB synthetic
message (Go can't construct one in CI memory budgets) or a session
mock harness (overengineering for a single check); the policy test
locks the error-routing logic at the exact `errors.Is` boundary
where the contract lives.
luxgo crypto module version stays at current; no breaking-API change.