Files
zeekay cb2714a652 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

126 lines
4.0 KiB
Go

package accel
import "errors"
// Batch operation thresholds - minimum items for GPU acceleration to be worthwhile.
const (
BLSBatchVerifyThreshold = 64 // Min signatures for GPU batch verify
BLSBatchAggregateThreshold = 128 // Min items for GPU aggregation
HashBatchThreshold = 32 // Min items for GPU batch hash
NTTBatchThreshold = 4 // Min polynomials for GPU batch NTT
MSMBatchThreshold = 64 // Min points for GPU MSM
KyberBatchThreshold = 8 // Min operations for GPU batch
DilithiumBatchThreshold = 8 // Min operations for GPU batch
)
// Kyber key and ciphertext sizes (ML-KEM-768)
const (
KyberPublicKeySize = 1184
KyberSecretKeySize = 2400
KyberCiphertextSize = 1088
KyberSharedKeySize = 32
)
// Dilithium sizes (ML-DSA-65). The DilithiumSecretKeySize=4016 constant
// predates the FIPS 204 final fix that pinned the ML-DSA-65 secret key
// at 4032 bytes; new code should prefer the MLDSA* sizes below.
const (
DilithiumPublicKeySize = 1952
DilithiumSecretKeySize = 4016
DilithiumSignatureSize = 3309
)
// ML-DSA / FIPS 204 sizes per NIST level. Mode encoding matches the
// luxcpp/crypto/mldsa C ABI: 2 = ML-DSA-44, 3 = ML-DSA-65, 5 = ML-DSA-87.
const (
// Mode IDs.
MLDSAMode44 = 2
MLDSAMode65 = 3
MLDSAMode87 = 5
// Per-mode tensor widths (FIPS 204).
MLDSA44PublicKeySize = 1312
MLDSA44SecretKeySize = 2560
MLDSA44SignatureSize = 2420
MLDSA65PublicKeySize = 1952
MLDSA65SecretKeySize = 4032
MLDSA65SignatureSize = 3309
MLDSA87PublicKeySize = 2592
MLDSA87SecretKeySize = 4896
MLDSA87SignatureSize = 4627
// ML-DSA NTT poly width (FIPS 204 fixed at N = 256).
MLDSANTTPolyLen = 256
// MLDSABatchThreshold: minimum batch size at which the GPU
// dispatch path is engaged. Below this, callers should fall
// through to the per-element CPU oracle to amortise launch cost.
MLDSABatchThreshold = 8
)
var (
// ErrNoBackends indicates no GPU backends are available.
ErrNoBackends = errors.New("accel: no GPU backends available")
// ErrNotInitialized indicates the library was not initialized.
ErrNotInitialized = errors.New("accel: library not initialized")
// ErrInvalidArgument indicates an invalid argument was provided.
ErrInvalidArgument = errors.New("accel: invalid argument")
// ErrOutOfMemory indicates GPU memory allocation failed.
ErrOutOfMemory = errors.New("accel: out of GPU memory")
// ErrNotSupported indicates the operation is not supported.
ErrNotSupported = errors.New("accel: operation not supported")
// ErrKernelFailed indicates a GPU kernel execution failed.
ErrKernelFailed = errors.New("accel: kernel execution failed")
// ErrBackendNotFound indicates the requested backend is not available.
ErrBackendNotFound = errors.New("accel: backend not found")
// ErrSessionClosed indicates the session has been closed.
ErrSessionClosed = errors.New("accel: session closed")
// ErrShapeMismatch indicates tensor shapes are incompatible.
ErrShapeMismatch = errors.New("accel: tensor shape mismatch")
// ErrBatchSizeMismatch indicates mismatched batch input sizes.
ErrBatchSizeMismatch = errors.New("accel: mismatched batch input sizes")
// ErrNilInput indicates nil input in batch operation.
ErrNilInput = errors.New("accel: nil input in batch operation")
)
// Error wraps an error with additional context from the C library.
type Error struct {
Op string // Operation that failed
Backend BackendType
Err error
Detail string // Additional detail from C library
}
func (e *Error) Error() string {
if e.Detail != "" {
return e.Op + ": " + e.Err.Error() + ": " + e.Detail
}
return e.Op + ": " + e.Err.Error()
}
func (e *Error) Unwrap() error {
return e.Err
}
// newError creates a new Error with the given operation and underlying error.
func newError(op string, err error) *Error {
return &Error{Op: op, Err: err}
}
// newErrorWithDetail creates a new Error with detail message.
func newErrorWithDetail(op string, err error, detail string) *Error {
return &Error{Op: op, Err: err, Detail: detail}
}