mirror of
https://github.com/luxfi/accel.git
synced 2026-10-30 17:03:45 +00:00
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.
126 lines
4.0 KiB
Go
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}
|
|
}
|