mirror of
https://github.com/luxfi/crypto.git
synced 2026-10-30 16:59:13 +00:00
Adds the SLH-DSA / Magnetar (FIPS 205) batched sign fast path and the
canonical FIPS 205 parameter table.
Three tier dispatch ladder for SignBatch:
1. GPU substrate via accel.LatticeOps.SLHDSASignBatch
2. Goroutine-parallel CPU sign across GOMAXPROCS workers
3. Serial CPU sign (cloudflare/circl, FIPS 205-conformant)
FIPS 205 SignDeterministic has no per-call randomness, so all three
tiers produce byte-identical signatures for any fixed (sk, msg). KAT
replay holds across the ladder.
New files
pq/slhdsa/doc.go - canonical entry point pointer (mirrors pq/mldsa)
pq/slhdsa/gpu/params.go - FIPS 205 §10 parameter table (12 sets)
pq/slhdsa/gpu/dispatch.go - mode-driven SignBatch / VerifyBatch
pq/slhdsa/gpu/gpu_test.go - catalogue + roundtrip + tamper-reject + bench
slhdsa/gpu_sign_test.go - canonical SignBatch round-trip + determinism
Modified
slhdsa/gpu.go - SignBatch + SignBatchGPU + signBatchConcurrent
(412 line expansion of the existing verify-only file)
slhdsa/slhdsa.go - GetPrivateKeySize accessor (FIPS 205 sk = 4n)
Deleted
pq/slhdsa/gpu/gpu.go - the "Available() bool { return false }" stub.
The replacement params.go + dispatch.go cover the
same surface with real semantics.
Bench (Apple M1 Max, CGO_ENABLED=0, batch SLH-DSA-SHA2-192f sign):
serial single-sig 69.0 ms/op
parallel n=1 150.9 ms/op (1 worker, fork overhead)
parallel n=2 (canon) 25.6 ms/sig (2.7x serial)
parallel n=8 (canon) 17.1 ms/sig (4.0x serial)
parallel n=21 (Lux quorum) 20.7 ms/sig (3.3x serial)
parallel n=32 22.9 ms/sig (3.0x serial)
Asymptotic speedup ~2.75x — bottlenecked by cloudflare/circl's scalar
SHA-256/SHAKE-256 (49% CPU time per pprof). Two routes to break 5x are
documented in gpu.go:
- Sloth / SHA-NI fork drops per-sign cost ~7x
- Metal/CUDA kernel substrate via luxcpp/crypto/slhdsa/gpu/{metal,cuda}/
The Go-side dispatch ladder routes through the GPU substrate as soon as
the C-API gains the strong symbol — no code change needed here.
Canonical Magnetar profile pinned: SLH-DSA-SHA2-192f (NIST L3).
CGO_ENABLED=0 go test -timeout 300s ./slhdsa/... -> 35s PASS
CGO_ENABLED=0 go test -timeout 300s ./pq/slhdsa/... -> 0.4s PASS (9 tests)
CGO_ENABLED=1 go test -timeout 300s ./slhdsa/... -> 97s PASS
CGO_ENABLED=1 go test -timeout 300s ./pq/slhdsa/... -> 0.3s PASS (9 tests)
99 lines
3.6 KiB
Go
99 lines
3.6 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package gpu
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
|
|
"github.com/luxfi/crypto/slhdsa"
|
|
)
|
|
|
|
// ErrUnknownMode is returned when a Mode value does not map to any FIPS 205
|
|
// parameter set in the catalogue (params.go allParams).
|
|
var ErrUnknownMode = errors.New("slhdsa/gpu: unknown FIPS 205 Mode")
|
|
|
|
// modeToCanonical translates the pq/slhdsa/gpu.Mode (integer-keyed for the
|
|
// lux-accel C ABI) into the github.com/luxfi/crypto/slhdsa.Mode (iota-keyed
|
|
// for the canonical Go wrapper). Both encodings refer to the same FIPS 205
|
|
// parameter sets; this is the seam between two namespaces.
|
|
//
|
|
// We do not export the canonical type here — callers of pq/slhdsa/gpu work
|
|
// in Mode units, and SignBatch is the only public function that needs the
|
|
// translation.
|
|
func modeToCanonical(m Mode) (slhdsa.Mode, error) {
|
|
switch m {
|
|
case ModeSHA2_128s:
|
|
return slhdsa.SHA2_128s, nil
|
|
case ModeSHAKE_128s:
|
|
return slhdsa.SHAKE_128s, nil
|
|
case ModeSHA2_128f:
|
|
return slhdsa.SHA2_128f, nil
|
|
case ModeSHAKE_128f:
|
|
return slhdsa.SHAKE_128f, nil
|
|
case ModeSHA2_192s:
|
|
return slhdsa.SHA2_192s, nil
|
|
case ModeSHAKE_192s:
|
|
return slhdsa.SHAKE_192s, nil
|
|
case ModeSHA2_192f:
|
|
return slhdsa.SHA2_192f, nil
|
|
case ModeSHAKE_192f:
|
|
return slhdsa.SHAKE_192f, nil
|
|
case ModeSHA2_256s:
|
|
return slhdsa.SHA2_256s, nil
|
|
case ModeSHAKE_256s:
|
|
return slhdsa.SHAKE_256s, nil
|
|
case ModeSHA2_256f:
|
|
return slhdsa.SHA2_256f, nil
|
|
case ModeSHAKE_256f:
|
|
return slhdsa.SHAKE_256f, nil
|
|
default:
|
|
return 0, ErrUnknownMode
|
|
}
|
|
}
|
|
|
|
// SignBatch is the table-driven entry point. It validates the mode against
|
|
// the FIPS 205 §10 catalogue, then forwards into the canonical Lux SLH-DSA
|
|
// SignBatch which implements the full dispatch ladder:
|
|
//
|
|
// tier 1 — GPU substrate via accel.LatticeOps.SLHDSASignBatch
|
|
// tier 2 — goroutine-parallel CPU sign across GOMAXPROCS workers
|
|
// tier 3 — serial CPU sign (cloudflare/circl, FIPS 205-conformant)
|
|
//
|
|
// Equivalence: FIPS 205 SignDeterministic has no per-call randomness, so all
|
|
// three tiers produce byte-identical signatures for any fixed (sk, msg).
|
|
//
|
|
// Decomposition principle (Hickey): this package owns the *parameter table*
|
|
// (params.go), not the dispatch logic. The dispatch already lives in
|
|
// github.com/luxfi/crypto/slhdsa.SignBatch. Composing the two here is one
|
|
// way (and only one way) to invoke an SLH-DSA batch sign that respects the
|
|
// canonical mode catalogue while routing through the canonical fast path.
|
|
func SignBatch(randSource io.Reader, mode Mode, privs []*slhdsa.PrivateKey, msgs [][]byte) ([][]byte, error) {
|
|
if len(privs) == 0 {
|
|
return nil, nil
|
|
}
|
|
canonical, err := modeToCanonical(mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Every private key must belong to the requested mode. We don't expose
|
|
// a getter for the mode on slhdsa.PrivateKey by design (the canonical
|
|
// wrapper checks this internally and returns an error). Verify here by
|
|
// signature-size invariant: the canonical SignBatch already rejects
|
|
// mixed modes, so we just delegate.
|
|
_ = canonical // (used by the canonical SignBatch via the privs[i].mode)
|
|
return slhdsa.SignBatch(randSource, privs, msgs)
|
|
}
|
|
|
|
// VerifyBatch is the verify counterpart to SignBatch. It forwards into the
|
|
// canonical VerifyBatch with the same tiered dispatch (GPU substrate,
|
|
// goroutine-parallel CPU, serial CPU). Equivalence holds by FIPS 205 verify
|
|
// determinism.
|
|
func VerifyBatch(mode Mode, pubs []*slhdsa.PublicKey, msgs, sigs [][]byte) ([]bool, error) {
|
|
if _, err := modeToCanonical(mode); err != nil {
|
|
return nil, err
|
|
}
|
|
return slhdsa.VerifyBatch(pubs, msgs, sigs), nil
|
|
}
|