Corona is Module-LWE (Ringtail/Raccoon line, ePrint 2024/1113), the
same family as Pulsar (ML-DSA) and ML-KEM. The stack comment already
labels Pulsar (MLWE); Corona was the lone RLWE mislabel. Comment only.
Root cause of the never-passing 'HQC PQClean cgo e2e (NIST KAT
roundtrip)' job, in two layers:
1. The lux-build runner had no C compiler (gcc/cc/clang absent), so cgo
aborted before any code ran. Fixed in prior commits by installing
build-essential + selecting an available CC.
2. With a compiler present, the cgo build then failed on
accel@v1.1.9/ops/code/code_cpu.go's '#include "lux/gpu/hqc.h"' and
'-lluxgpu_hqc' — the native HQC library (built from luxfi/mlx) that
is NOT present in CI (nor in most consumers). accel v1.1.9 gated that
native path on '//go:build cgo', so every cgo build hard-required the
native lib/header.
Fix: bump github.com/luxfi/accel v1.1.9 -> v1.2.4, which gates the
native path behind '-tags=lux_hqc_native' and otherwise returns
code.ErrNativeHQCUnavailable (documented: 'callers should fall back to
their own CPU KEM path'). crypto/hqc now honours that contract:
- gpu.go: batchEncapsulateGPU/batchDecapsulateGPU treat
ErrNativeHQCUnavailable as 'declined' (return false,nil) so callers
fall back to the per-item PQClean path. Secret buffers are zeroised on
the decline path.
- gpu_cgo.go: GF2Polymul falls back to the pure-Go Karatsuba path on
ErrNativeHQCUnavailable. Removed the duplicated vecNSize64 /
redMaskForMode / GF2Add (now shared).
- gpu_polymul_purego.go (new, tag-neutral): the canonical pure-Go,
PQClean-byte-identical GF(2)^N polymul (Karatsuba + reduce + base_mul),
extracted from gpu_nocgo.go so BOTH builds share one implementation.
- gpu_nocgo.go: trimmed to the thin !cgo public wrappers over the shared
helpers.
- randombytes_shim.c: split. Default (hqc_pqclean, no native) forwards
randombytes -> hqcGoRandombytes only; the native seed-buffer dispatch
(which references lux_hqc_randombytes / lux_hqc_seed_has_bytes from the
native lib) moves to randombytes_shim_native.c, gated on lux_hqc_native.
Previously the default build referenced those native-only symbols
unconditionally, which would not link without the native lib.
NOT a crypto/KAT regression: the NIST KAT roundtrip (TestKAT_HQC128/192/
256), TestRoundTrip_AllModes, TestDeterminism, and the GF2 polymul
known-answer vectors all pass byte-for-byte. The GPU batch tests skip
gracefully when the native kernel is absent (their existing t.Skip path).
Verified: CGO_ENABLED=1 go test -tags=hqc_pqclean ./hqc/ passes from a
clean cache; cgo (with/without hqc_pqclean) and nocgo builds all green;
broader suite unchanged (only pre-existing pkg-config failures in cgo/
and hash/poseidon2 remain, unrelated).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Adds GPU/CPU split for HQC's hot inner-loop primitives:
gpu_nocgo.go Pure-Go bit-sliced Karatsuba mod (X^n - 1), byte-equal
to PQClean's PQCLEAN_HQC{128,192,256}_CLEAN_vect_mul.
Constant-time on secret operands (masked nibble table
lookups; no data-dependent branches or indices).
Modern Go compiler emits SIMD-friendly XOR loops on
amd64 / arm64.
gpu_cgo.go Same surface, routed through luxfi/accel/hqc -> the
C kernel in libluxgpu_hqc.a (also byte-equal to PQClean).
gpu_kat_test.go KAT cross-check across both builds — vectors
captured from the cgo path's PQClean kernel,
verified bit-identical from the pure-Go path
(100-iter checksum + single-vector KAT).
gpu_polymul_test.go Algebraic properties (commutativity, identity,
zero, distributivity, associativity fuzz x600).
gpu_bench_test.go Single-shot + batched benchmarks per mode.
Updated gpu.go to route the existing batchEncapsulateGPU /
batchDecapsulateGPU through accel/ops/code's batch surface (replaces
the old "not implemented" stubs). Updated randombytes_shim.c to be
the single canonical PQClean entropy bridge.
Determinism contract: validators reach consensus on HQC encapsulation
ciphertexts, so any byte-level divergence between the two paths is a
consensus fork. The KAT test pins this contract.
Test status (all three configs green):
CGO_ENABLED=0 go test ./hqc/... 18.1s
CGO_ENABLED=1 go test ./hqc/... 44.2s
CGO_ENABLED=1 go test -tags=hqc_pqclean ./hqc/.. 5.5s
NIST KATs (HQC128/192/256 encap/decap byte-vectors): all PASS.
Bench numbers on Apple M1 Max (single-shot GF2 polymul, ns/op):
HQC-128: pure-Go 2.03 ms / cgo 4.10 ms
HQC-192: pure-Go 6.28 ms / cgo (n.m.)
HQC-256: pure-Go 11.20 ms / cgo (n.m.)
The cgo path is currently slower than pure-Go because libluxgpu_hqc.a
ships from luxcpp/gpu/build with CMAKE_BUILD_TYPE empty (no -O3). With
the CMake build switched to Release, cgo will return to its expected
~30us / poly margin.
Wired via:
crypto/hqc -> accel/hqc -> accel/ops/code -> luxcpp/gpu/libluxgpu_hqc
HQC is code-based (GF(2)^N polynomials + Reed-Muller/Reed-Solomon),
NOT lattice-based like the other PQ primitives. The accel.LatticeOps
surface publishes NTT kernels (Kyber/Dilithium-shaped) that don't
apply to HQC's hot path.
This file mirrors mlkem/gpu.go API surface so the backend selector
resolves GPU consistently across all KEMs; today the batch* functions
return false (no-op) and the caller transparently falls back to PQClean
CGO.
To activate real GPU dispatch:
1. Add GF2N polymul + Reed-Solomon decode kernels to luxfi/mlx/src
(Metal + CUDA + WebGPU backends)
2. Expose via accel.LatticeOps (HQCEncapsBatch, HQCDecapsBatch)
3. Wire the bodies here
Tests: ok github.com/luxfi/crypto/hqc 0.373s
Aligns hqc/slhdsa comments + dudect timing test docs with the canonical
PQ signature family naming:
Pulsar (M-LWE threshold ML-DSA)
Corona (R-LWE / Corona wrapper)
Magnetar (SLH-DSA threshold via GF(257) Shamir)
Old name 'Comet' was the working title for Magnetar during early
development; this commit completes the rename in:
- hqc/hqc.go (family-disjoint gap comment)
- slhdsa/gpu.go (FIPS 205 GPU dispatch header)
- slhdsa/gpu_test.go (test descriptions)
Also includes:
- mlkem/ct/dudect/{decaps,encaps,keygen}_ct.go + .c — timing analysis
harness improvements (decaps is the most CT-critical ML-KEM routine)
- mlkem/ct/dudect/run-m1-overnight.sh — operator runbook for Apple M1
overnight timing analysis
- .gitignore — build artifacts (dudect/, *.dylib, *.so, results/)
Replaces the stub backend (ErrBackendNotWired) with a working byte-
for-byte NIST-compatible HQC implementation. Build tag hqc_pqclean
activates the cgo path; default builds remain stub for portability.
Vendored:
- PQClean HQC-128/192/256 clean reference C (2023-04-30 NIST
submission), public domain
- PQClean shared fips202 (SHA-3 / SHAKE)
- Per-mode/per-source shim TUs (36 total) to keep PQClean's
static helpers isolated and avoid duplicate-symbol collisions
- randombytes_shim.c wiring PQClean's randombytes() to a Go-side
io.Reader callback, mutex-protected for concurrent dispatch
Tests:
- TestKAT_HQC128/192/256 — all three NIST KAT vectors verified
byte-for-byte against META.yml nistkat-sha256
- TestRoundTrip_AllModes — keygen → encap → decap roundtrip,
shared secrets byte-equal in all 3 modes
- TestDeterminism_AllModes — same seed produces identical ct/ss
(load-bearing for the precompile's on-chain determinism)
- -race clean
Correction: ciphertext sizes updated from 4481/9026/14469 (older
HQC round-3 listing) to PQClean's 4433/8978/14421 (2023-04-30 NIST
submission); the prior numbers would have failed round-trip with
a 48-byte mismatch.
Closes the gap on the KEM side parallel to Comet on the signature side.
HQC's hardness rests on Syndrome Decoding on random quasi-cyclic codes
(NP-hard, no known classical or quantum polynomial-time attack), so a
structural break against MLWE does not compromise HQC and vice-versa.
Three modes pinned to NIST IR 8528 §4.1: HQC-128 (Cat 1), HQC-192 (Cat 3),
HQC-256 (Cat 5). Shared secret is 64 B for all modes (distinct from
ML-KEM's 32 B). Default build returns ErrBackendNotWired; activate via
-tags=hqc_pqclean (cgo to PQClean) or -tags=hqc_circl (when Cloudflare
CIRCL ships HQC).