Two new no_std crates wrapping the C-ABI from luxcpp/crypto/ntt and
luxcpp/crypto/poly_mul.
lux-crypto-ntt:
* forward / inverse — generic-prime NTT (caller-supplied modulus + root)
* Cyclone-FFT fast path engaged automatically when (modulus, root) ==
(Q=998244353, PRIMITIVE_ROOT=629671588)
* 6 spec-vector tests:
- forward+inverse roundtrip across n in {2,4,...,1024}
- q97/omega=28 generic-prime roundtrip (matches Go reference)
- input rejection: empty, non-pow2, modulus=0
lux-crypto-poly_mul:
* multiply / multiply_into — Z_Q[X]/(X^n+1) negacyclic convolution
* Pinned to Cyclone-FFT prime (the C-ABI rejects other rings)
* 4 spec-vector tests including the canonical 10-case KAT table
(sum/first/last byte-equal to luxfi/crypto/poly_mul Go reference)
and the (1+2X+3X^2+4X^3)*(5+6X+7X^2+8X^3) hand-written negacyclic case.
build.rs links statically against libntt_cpu.a / libpoly_mul_cpu.a
produced by luxcpp/crypto/ntt and luxcpp/crypto/poly_mul; CRYPTO_BUILD_DIR
or CRYPTO_DIR points at the cmake build / install layout.
Verified: cargo test -p lux-crypto-ntt -p lux-crypto-poly_mul
6/6 ntt tests PASS, 4/4 poly_mul tests PASS.
Canonical Rust binding for the Poseidon2 t=2 BN254 Merkle-Damgard
hasher landed in luxcpp/crypto/poseidon. Links statically against
libposeidon.a + libposeidon_cpu.a and exposes:
hash(input) -> [u8; 32]
hash_into(input, output) -> in-place fill of caller buffer
Byte-equal to upstream gnark-crypto v0.20.1 (NewMerkleDamgardHasher
over NewParameters(2, 6, 50)). 10 spec-vector integration tests
pass, including the canonical 32B/64B/96B block-boundary cases and
trivial avalanche/determinism checks.
Crate is no_std + forbid(unsafe_op_in_unsafe_fn) and follows the
existing per-algorithm crate layout.
Real Rust binding over the now-real luxcpp/crypto/blake3 C-ABI
(commit c4a1d2e0, "vendor BLAKE3 v1.5.0 reference C"). Exposes all four
canonical BLAKE3 modes:
hash(input) -> [u8; 32]
keyed_hash(key, input) -> [u8; 32]
derive_key(context_z, key_material) -> [u8; 32]
hash_xof(input, &mut output) -> ()
Statically links libblake3.a + libblake3_cpu.a from the build dir
(CRYPTO_BUILD_DIR or the canonical build-cto/ default).
Test (tests/spec_vectors.rs) parses upstream test_vectors.json (vendored
under luxcpp/crypto/blake3/test/vectors/) and asserts byte-equality
across all 35 cases x 4 modes = 140 assertions. cargo test PASS.
The lux-crypto umbrella crate's build.rs defaulted to ../../../../luxcpp/crypto/
build-canonical when neither CRYPTO_DIR nor CRYPTO_BUILD_DIR was set. That
directory is a partial build from before the 2026-04-26 brand-neutral C-ABI
rename (luxcpp commit e8690bb8 "drop lux_ prefix from C-ABI symbols"). Its
libsecp256k1_cpu.a still exports _lux_secp256k1_ecrecover (legacy) while the
Rust extern "C" block expects _secp256k1_ecrecover (post-rename, brand-neutral).
build-canonical also lacks banderwagon/sha256/ripemd160 archives.
All 17 sibling per-algorithm crates already default to build-cto; only the
umbrella was misaligned. Switching the umbrella default to build-cto makes the
workspace build out of the box.
Verified: cargo test --workspace --all-features reports 182 PASS, 0 fail
(matching the #97-retry baseline) with no env vars set, and also with explicit
CRYPTO_BUILD_DIR=/Users/z/work/luxcpp/crypto/build-cto.
Route github.com/crate-crypto/go-ipa and github.com/ethereum/go-verkle
through luxfi-maintained mirrors so all Lux crypto consumers converge on a
single canonical source. Pinned to luxfi/go-verkle v0.2.2 and
luxfi/go-ipa@d31adc04. See LUXFI-FORK.md in each fork for sync policy.
Tests verified: ./verkle/... and ./ipa/... pass.
slhdsa: 120 NIST FIPS 205 ACVP keygen vectors (revision FIPS205, vsId 53;
10 per parameter set x 12 sets), vendored from circl testdata
(public-domain NIST sample data). Each vector pins
(skSeed, skPrf, pkSeed) -> pk and is asserted byte-equal via the
circl backend our Go layer wraps. Round-trip check signs and
verifies once per parameter set.
lamport: 10 deterministic Lamport-SHA256 vectors derived from a documented
KDF chain (SHA-256 of "lamport-kat/v1\\0" || seed). Each vector
pins (seed, msg32) -> (PK digest, sig digest). The KDF contract is
the cross-layer determinism contract - the C++ body and Metal
driver must reproduce the same byte stream.
Test counts: slhdsa 24 (incl 120 KAT subtests), lamport 6 (incl 10 KAT).
Both packages PASS with GOWORK=off go test.
blake3: pure-Go body wrapping zeebo/blake3 (already in go.mod). Three modes
(hash/keyed/derive_key) plus XOF. KAT against the official BLAKE3
test_vectors.json (35 input lengths from 0 to 102400 bytes, 4 modes each
= 140 byte-equal assertions).
poseidon: extended Sum2 wrapper with Permutation2 (raw t=2 permutation) and
Compress2 (gnark-crypto Compress contract). KAT vectors generated from
gnark-crypto v0.20.1 (default params t=2, rF=6, rP=50, d=5) for 16
permutation cases.
Both layers assert byte-equal to the published spec, no oracle indirection.
poly_mul: new package implementing polynomial multiplication over the
canonical FFT prime Q = 998244353 = 119 * 2^23 + 1. Schoolbook + NTT-based
negacyclic convolution paths; both byte-equal across the 10 KAT vectors
embedded in the test file. Reference for the C++ port and Metal kernel.
pedersen: extend with DeterministicGenerators(seed) and CommitBatch.
DeterministicGenerators yields a reproducible (G, H) pair from a seed so
the C++ and GPU layers share the same KAT inputs. CommitBatch is the
shape the GPU driver consumes. 11 KAT vectors locked in.
ipa: add CheckMultiProofBatch wrapping the existing single-proof verifier.
10 KAT batched proofs, plus tamper-detect and length-mismatch tests.
verkle: add Verify re-export and VerifyBatch wrapper. 10 single-leaf
KAT proofs verified end-to-end through the upstream go-verkle Verify.
ed25519: documents RFC 8032 byte-equal Metal verify kernel (100 vectors)
+ N_threshold = 256 on M1 Max with 26.7x speedup at N=4096 vs equivalent-
shape CPU port. dGPU CUDA port pending in lux/crypto/ed25519/gpu/cuda/.
mldsa: documents structural-skeleton M1 kernel + dGPU residual. Per-thread
serial work on Apple Silicon (~122 us SHAKE256-dominated) vs ~42 us NEON
narrows expected M1 wall-clock speedup to ~7x at N=4096; CUDA H100/Ada
closes that gap (~33x ceiling).
mlkem: same shape as mldsa, ~75 us per-thread Metal vs ~26 us NEON.
SHA3/SHAKE256 chains are the M1 ceiling; dGPU port closes it.
- 3 new pure-Rust unit tests covering all Secp256k1Status::from_int
arms (0..=7 + invalid), all CryptoStatus::from_int arms (Ok variants
+ 5 typed error variants + Unknown), and all NIST mode dispatch
for mldsa/mlkem/slhdsa (Mode2, Mode3, Mode5).
- 9/9 tests passing via `cargo test --lib`.
- COVERAGE.md: pure-Rust dispatch helpers at 100% line; whole-crate
36.97% reflects FFI declaration weight (extern "C" blocks +
thin pub fn wrappers that forward to luxcpp/crypto C-ABI). The
cryptographic logic itself is tested in luxcpp/crypto/<alg>/test/.
- BENCHMARKS.md: explicit no-criterion-yet note; Rust crate adds
no measurable overhead to the FFI call so a Rust-side bench would
duplicate the C-side numbers within sampling noise.
- .gitignore: target/, *.profraw, *.profdata.
Adds Rust modules wrapping the canonical luxcpp/crypto C-ABI for:
- ML-DSA (FIPS 204) modes 2/3/5
- ML-KEM (FIPS 203) modes 2/3/5
- SLH-DSA (FIPS 205) modes 2/3/5
- Ed25519 keygen/sign/verify
- keccak256 single-shot
Each module exposes safe Rust wrappers with size validation and a typed
CryptoStatus enum from the unified lux_crypto.h return-code contract.
Brand-neutral surface (no LUX_ prefix on Rust constants); link_name
attributes map to the existing lux_<alg>_<op> archive symbols until the
next luxcpp rebuild flips the archives to brand-neutral.
build.rs links per-algorithm libcpp_cpu.a archives from the
build-canonical/<alg>/ subdirectories.
The -sys suffix is a Rust ecosystem convention for thin FFI bindings to
THIRD-PARTY C libraries (openssl-sys, libgit2-sys, etc.). We author the
C/C++ ourselves at luxcpp/crypto, so -sys would be misleading.
Brand-neutral rule: 'one and one way to do everything' — the crate is
lux-crypto, the lib name is lux_crypto. If a high-level safe wrapper is
ever needed, it lives inside this same crate as pub mod safe.
Mirrors the canonical pattern at github.com/luxfi/gpu (Rust binding to
luxfi/accel) — no -sys suffix on first-party crates.
Consumers update separately.
Phase-1 surface for Rust callers (Hanzo node, Lux node, Zoo node):
* secp256k1: lux_secp256k1_ecrecover + lux_secp256k1_ecrecover_batch
Build script locates luxcpp/crypto via LUX_CRYPTO_DIR / LUX_CRYPTO_BUILD_DIR
or defaults to a sibling-checkout layout. Links against libsecp256k1_cpu.a
and the platform C++ runtime.
Subsequent agents extend this with mldsa/mlkem/slhdsa/bls12381/etc.
luxfi/crypto becomes the single Go entry point for ALL Lux-family crypto.
Every public function in this module now dispatches between three
implementations through a runtime-selectable backend:
- vanilla: pure-Go reference (always available)
- cgo: native binding (blst, libsecp256k1, ckzg) where present
- gpu: batch acceleration via github.com/luxfi/accel
The dispatcher reads LUX_CRYPTO_BACKEND (auto|vanilla|cgo|gpu); auto
picks the most capable backend the binary was compiled and linked with.
New canonical packages:
backend/ runtime backend selector (env + programmatic)
internal/gpuhost/ accel session lifecycle, single per-process
keccak/ Keccak-256 with batch GPU dispatch
sha256/ SHA-256 with batch GPU dispatch
sha3/ SHA3 / SHAKE family
ripemd160/ RIPEMD-160 (Bitcoin/Lux address derivation)
ed25519/ Ed25519 with batch GPU verify
bn254/ canonical alias for bn256 (matches FIPS naming)
modexp/ canonical alias for bigmodexp
evm256/ EIP-196/197 precompile ABI wrappers
poseidon/ Poseidon2 hash via gnark-crypto
pedersen/ Pedersen commitments over BN254
ntt/ Number-Theoretic Transform reference
polymul/ negacyclic polynomial multiplication
Extended existing packages with batch GPU paths:
bls/batch.go BatchVerify routes through accel.BLSVerifyBatch
mldsa/batch.go BatchVerify (ML-DSA-65) via accel.DilithiumVerifyBatch
mlkem/batch.go BatchEncapsulate / BatchDecapsulate via Kyber kernels
secp256k1/batch.go BatchVerifySignature via accel.ECDSAVerifyBatch
GPU dispatch is gated on (a) backend.Default(), (b) batch size threshold,
and (c) accel.Available(). When any gate fails the call falls through to
the vanilla CPU path; output is byte-identical.
The legacy gpu/ stub is replaced with a thin probe surface (Available,
Backend, Devices, Version) that delegates to the same gpuhost session.
Tests show vanilla and gpu backends produce identical outputs across all
batch entry points (-race clean).
See AUDIT.md for the per-algorithm state matrix and honest gaps.
Private key material must never appear in EVM calldata — it is visible
to all nodes and permanently stored on-chain.
- Remove ML-DSA Sign + KeyGen precompiles (0x0113-0x0118)
- Remove ML-KEM Decapsulate + KeyGen precompiles (0x0123-0x0128)
- Remove SLH-DSA Sign precompiles (0x0136-0x0138)
- Add BLSVerifyPoP precompile (0x0167) to prevent rogue key attacks
- Cap BLSFastAggregate and BLSAggregateVerify to 64 keys/sigs max
- Precompile set is now verify-only + encapsulate-only
The pure-Go ML-KEM fallback was filling keys/ciphertexts/secrets with
rand.Read, silently producing incorrect results without CGO+liboqs.
Replaced with Go 1.24+ crypto/mlkem (FIPS 203) for both 768 and 1024
parameter sets.
Completed ML-DSA (0x0110-0x0118), ML-KEM (0x0120-0x0128), and SLH-DSA
(0x0130-0x0138) precompile registration that was left as a comment.
Update checkout@v5, setup-go@v6, cache@v5, codecov@v5. Pin
golangci-lint to v2.1.6 with version: "2" config. Fix release
workflow Go version to 1.26.1.
ToPrivateKey stored a reference to the input slice, not a copy.
If the caller zeroed the original bytes after creating the key,
the PrivateKey's internal bytes were also zeroed, causing Sign()
to fail with "invalid private key". Now copies input bytes.
Re-export github.com/ethereum/go-verkle and
github.com/ethereum/go-bigmodexpfix under luxfi/crypto namespace
so downstream packages (luxfi/geth) never import ethereum/* directly.
- Add secret/ package: wraps runtime/secret.Do() for secure key erasure
when built with GOEXPERIMENT=runtimesecret, no-op stub otherwise
- Add encryption/hpke.go: HPKE encryption using Go 1.26 stdlib crypto/hpke
with X25519 (classical) and ML-KEM-768+X25519 (post-quantum hybrid)
- Wrap BLS key generation (both CGO and pure Go) in secret.Do()
- Wrap HexToECDSA and LoadECDSA in secret.Do() for key byte cleanup
- Simplify random.go: crypto/rand.Read never errors in Go 1.26
- Update CI workflows to Go 1.26.1, add GOEXPERIMENT=runtimesecret job
- Update dependencies: circl 1.6.3, x/crypto 0.48.0, age 1.3.1
Add stub packages for GPU-accelerated cryptography that return
false for Available() to indicate GPU is not available:
- gpu/gpu.go: General GPU crypto operations stub
- pq/mlkem/gpu/gpu.go: ML-KEM GPU operations stub
These stubs allow the precompile package to compile without
requiring actual GPU hardware.