Files
accel/ops_lattice.go
Hanzo AI ed19d1cc62 accel: implement lux_lattice_ntt_mldsa_batch + lux_mldsa_verify_batch (was ErrNotSupported stub)
Pulsar audit (commit 2dd1383) identified these as the missing wiring blocking
GPU acceleration for ML-DSA threshold signing. This change ships the C ABI
plumbing end-to-end:

  1. internal/capi/include/lux/accel/c_api.h
     - declares lux_mldsa_sign_batch, lux_mldsa_verify_batch (FIPS 204; modes
       2/3/5 = ML-DSA-44/65/87)
     - declares lux_lattice_ntt_mldsa_batch (in-place forward/inverse NTT
       over Z_q[X]/(X^256+1), q=8380417)

  2. internal/capi/stub.go — adds weak-symbol stubs returning LUX_NO_BACKEND
     so the build succeeds without libluxaccel.

  3. internal/capi/capi.go — replaces the placeholder _ = stubs that returned
     ErrNotSupported with real cgo calls. Reorders the header search path so
     the vendored c_api.h takes precedence over older installed snapshots
     (single source of truth: ${SRCDIR}/include/lux/accel/c_api.h).

  4. ops_lattice.go — adds LatticeNTTMLDSABatch to the LatticeOps interface,
     consumed by both cgoLatticeOps (ops_c.go) and stubLatticeOps (ops.go).

  5. accel_c.go — top-level Go API:
       MLDSAVerifyBatch(mode, msgs, sigs, pks, msgWidth)
       MLDSASignBatch(mode, msgs, sks, msgWidth)
       LatticeNTTMLDSABatch(polys, inverse)
     Below MLDSABatchThreshold=8 each returns ErrNotSupported so callers
     route to their per-element CPU oracle (pulsar's existing fallback).

  6. errors.go — adds MLDSAMode{44,65,87}, per-mode pk/sk/sig widths,
     MLDSANTTPolyLen=256, MLDSABatchThreshold=8.

  7. ops/lattice/mldsa_ntt.go — Go CPU oracle byte-equal to
     PQCLEAN_MLDSA65_CLEAN_ntt and PQCLEAN_MLDSA65_CLEAN_invntt_tomont.
     Same 256-entry zeta table the lux-private/gpu-kernels/ops/lattice/
     ntt_mldsa kernels embed across every backend (CUDA/HIP/Metal/WGSL).

Tests:
  - ops/lattice/mldsa_ntt_test.go: round-trip identity, zero-input, constant-
    poly evaluation, zeta-table endpoint pins, Montgomery-reduce edge case,
    batch=22 (Pulsar Round-2 shape) byte-equality to per-poly oracle.
  - accel_test.go: MLDSAVerifyBatch/MLDSASignBatch/LatticeNTTMLDSABatch
    surface checks, FIPS 204 width constants.

Verified:
  - go test ./... PASS on cgo + CGO_ENABLED=0 + -tags=accel.
  - pulsar TestPulsar_GPU_ByteEqual PASS (~/work/lux/pulsar regression).
  - go vet clean, gofmt clean.
2026-06-03 12:23:15 -07:00

153 lines
6.0 KiB
Go

package accel
// LatticeOps provides GPU-accelerated lattice-based cryptography operations.
// Implements NIST post-quantum standards: ML-KEM (Kyber) and ML-DSA (Dilithium).
type LatticeOps interface {
// KyberKeyGen generates Kyber (ML-KEM) key pair.
// pk: [1184] bytes (Kyber768 public key)
// sk: [2400] bytes (Kyber768 secret key)
KyberKeyGen(pk, sk *UntypedTensor) error
// KyberKeyGenBatch generates multiple key pairs in parallel.
// pk: [N, 1184] bytes
// sk: [N, 2400] bytes
KyberKeyGenBatch(pk, sk *UntypedTensor) error
// KyberEncaps encapsulates shared secret.
// pk: [1184] bytes public key
// ct: [1088] bytes ciphertext output
// ss: [32] bytes shared secret output
KyberEncaps(pk, ct, ss *UntypedTensor) error
// KyberEncapsBatch performs batch encapsulation.
// pk: [N, 1184] bytes
// ct: [N, 1088] bytes
// ss: [N, 32] bytes
KyberEncapsBatch(pk, ct, ss *UntypedTensor) error
// KyberDecaps decapsulates shared secret.
// ct: [1088] bytes ciphertext
// sk: [2400] bytes secret key
// ss: [32] bytes shared secret output
KyberDecaps(ct, sk, ss *UntypedTensor) error
// KyberDecapsBatch performs batch decapsulation.
// ct: [N, 1088] bytes
// sk: [N, 2400] bytes
// ss: [N, 32] bytes
KyberDecapsBatch(ct, sk, ss *UntypedTensor) error
// DilithiumKeyGen generates Dilithium (ML-DSA) key pair.
// pk: [1952] bytes (Dilithium3 public key)
// sk: [4016] bytes (Dilithium3 secret key)
DilithiumKeyGen(pk, sk *UntypedTensor) error
// DilithiumSign signs a message.
// msg: [msg_len] bytes message
// sk: [4016] bytes secret key
// sig: [3293] bytes signature output
DilithiumSign(msg, sk, sig *UntypedTensor) error
// DilithiumSignBatch signs multiple messages in parallel.
// msgs: [N, msg_len] bytes
// sk: [4016] bytes (same key for all)
// sigs: [N, 3293] bytes
DilithiumSignBatch(msgs, sk, sigs *UntypedTensor) error
// DilithiumVerify verifies a signature.
// msg: [msg_len] bytes
// sig: [3293] bytes
// pk: [1952] bytes
// Returns true if valid.
DilithiumVerify(msg, sig, pk *UntypedTensor) (bool, error)
// DilithiumVerifyBatch verifies multiple signatures.
// msgs: [N, msg_len] bytes
// sigs: [N, 3293] bytes
// pks: [N, 1952] bytes
// results: [N] uint8 (1 = valid, 0 = invalid)
DilithiumVerifyBatch(msgs, sigs, pks, results *UntypedTensor) error
// MLDSAVerifyBatch verifies a batch of ML-DSA / Dilithium signatures at the
// given FIPS 204 NIST level. Unlike DilithiumVerifyBatch (which is pinned to
// ML-DSA-65 / Dilithium3 for backwards compatibility), this entry point
// accepts mode in {2, 3, 5} for ML-DSA-44, ML-DSA-65, ML-DSA-87 respectively.
//
// Tensor shapes (n = batch size, per FIPS 204):
// ML-DSA-44 : pk=1312 sig=2420
// ML-DSA-65 : pk=1952 sig=3309
// ML-DSA-87 : pk=2592 sig=4627
//
// msgs : LUX_DTYPE_U8, shape [n, msg_width] (zero-padded right)
// sigs : LUX_DTYPE_U8, shape [n, sig_bytes]
// pks : LUX_DTYPE_U8, shape [n, pk_bytes]
// results : LUX_DTYPE_U8, shape [n] (1 = valid, 0 = invalid)
//
// FIPS 204 verify is deterministic, so GPU and CPU paths produce
// byte-identical accept/reject decisions per element. The results vector is
// dense (no early abort) so callers can audit per-signer outcomes.
MLDSAVerifyBatch(mode int, msgs, sigs, pks, results *UntypedTensor) error
// MLDSASignBatch signs a batch of messages with ML-DSA / Dilithium at the
// given FIPS 204 NIST level. mode in {2, 3, 5}.
//
// Sizes (FIPS 204):
// ML-DSA-44 : sk=2560 sig=2420
// ML-DSA-65 : sk=4032 sig=3309
// ML-DSA-87 : sk=4896 sig=4627
//
// msgs : [n, msg_width] bytes (zero-padded right)
// sks : [n, sk_bytes] bytes
// sigs : [n, sig_bytes] bytes
//
// ML-DSA signing is deterministic in hedged mode (per FIPS 204 §3.4) when
// the deterministic flag is set; the GPU path must select the same hedging
// mode as the caller-side CPU reference to remain byte-equal for KAT.
MLDSASignBatch(mode int, msgs, sks, sigs *UntypedTensor) error
// SLHDSASignBatch signs a batch of messages with SLH-DSA / Magnetar (FIPS 205).
// mode encodes the parameter set:
// 2 = SHA2-128f, 3 = SHA2-192f, 5 = SHA2-256f
// 12 = SHAKE-128f, 13 = SHAKE-192f, 15 = SHAKE-256f
// msgs: [N, msg_width] bytes (zero-padded right)
// sks: [N, sk_bytes] bytes (per-mode: 64 / 96 / 128)
// sigs: [N, sig_bytes] bytes (per-mode: 17088 / 35664 / 49856 for 'f')
SLHDSASignBatch(mode int, msgs, sks, sigs *UntypedTensor) error
// SLHDSAVerifyBatch verifies a batch of SLH-DSA / Magnetar (FIPS 205)
// signatures. mode encoding as for SLHDSASignBatch. Results vector is
// dense (no early abort) so callers can audit per-signer outcomes.
// msgs: [N, msg_width] bytes
// sigs: [N, sig_bytes] bytes
// pks: [N, pk_bytes] bytes (per-mode: 32 / 48 / 64)
// results: [N] uint8 (1 = valid, 0 = invalid)
SLHDSAVerifyBatch(mode int, msgs, sigs, pks, results *UntypedTensor) error
// PolynomialNTT performs NTT in lattice polynomial ring.
// Operates on polynomials in Z_q[X]/(X^256 + 1).
PolynomialNTT(input, output *UntypedTensor, q uint32) error
// PolynomialINTT performs inverse NTT.
PolynomialINTT(input, output *UntypedTensor, q uint32) error
// PolynomialMul multiplies polynomials in NTT domain.
PolynomialMul(a, b, c *UntypedTensor, q uint32) error
// PolynomialAdd adds polynomials.
PolynomialAdd(a, b, c *UntypedTensor, q uint32) error
// LatticeNTTMLDSABatch performs the in-place forward (inverse=false) or
// inverse (inverse=true) NTT over Z_q[X]/(X^256 + 1) with q = 8380417 (the
// ML-DSA / FIPS 204 prime), batched across N polynomials.
//
// Tensor shape:
// polys : LUX_DTYPE_I32, shape [N, 256] — in-place transform
//
// Byte-equal to PQCLEAN_MLDSA65_CLEAN_ntt (forward) and
// PQCLEAN_MLDSA65_CLEAN_invntt_tomont (inverse). Pulsar Round-2 fits the
// batch=22 dispatch shape; below the GPU-dispatch threshold the caller
// MUST route to the per-poly CPU oracle (the threshold gating lives in
// the consumer, not in this primitive).
LatticeNTTMLDSABatch(polys *UntypedTensor, inverse bool) error
}