Files
lattice/gpu/gpu_test.go
T
zeekay ae2f23dd35 fix(ci): unblock make checks — quoting, gofmt, lint:ignore, no-stub policy
Static-checks job has been failing on every PR (including #3) due to a
shell-interpolation bug in the Makefile + a build of accumulated lint
findings the bug had been hiding. Cleaned up end-to-end so make checks
runs green and PR #3 (security/readuint-slice-dos-2026-03-03) can land.

1. Makefile fmt-step: quote $$FMTOUT in `[ -z $$FMTOUT ]` test.
   Unquoted, multi-file go-fmt output word-splits and the test sees
   `[ -z FILE1 FILE2 ]` -> "/bin/sh: [: FILE2: unexpected operator".
   Same fix to the trailing `echo $$FMTOUT`.

2. go fmt sweep: gpu/gpu.go, gpu/gpu_test.go, types/reduction_test.go,
   types/trust_test.go had whitespace-only formatter findings that the
   crashing fmt-test step had been hiding.

3. ST1003 (mixed-case identifier): four wire-format-pinned consts in
   types/trust.go (TrustCpuGpuCompositeTEE / IOLevelCpu*) carry a
   //lint:ignore directive — their String() return values are part of
   the wire/log contract documented at line 80 ("do not rename"), so
   the Go-side identifiers must stay byte-aligned with the wire spelling.

4. ST1005 (capitalized error string): two error strings in gpu/gpu.go
   rephrased to lead with a noun ("ring degree N" / "modulus Q-1"
   instead of capital "N" / capital "Q") without changing semantics.

5. ST1000 (package comment): gpu/gpu_montgomery_*.go file header
   reformatted so the "Package gpu" line is the leading sentence.

6. G104 (unhandled errors): four `prng.Read(b)` call sites in gpu/gpu.go
   now `_, _ = prng.Read(b)` — the PRNG is keyed sampling output; read
   length is bounded by buffer length and a short read here is not a
   correctness issue.

7. G115 (int -> uint conversion): types/reduction.go switches `bitlen`
   to int (matches bits.Len64's return type, used only against literal
   small ints in the comparison ladder). gpu/gpu_montgomery_purego.go
   adds /* #nosec G115 */ on uint32(s.N) — s.N is validated >0 and
   bounded by ring degree above.

8. NO-STUB POLICY: gpu/gpu_montgomery_stub.go renamed to
   gpu/gpu_montgomery_purego.go and given a real pure-Go body that
   delegates to ring.SubRing.NTT / INTT (the canonical Go implementation
   in this repo). The "stub" name was misleading: there should never be
   a Lux Go file that returns "GPU unavailable" errors when the cgo+gpu
   build tag is unset — the pure-Go path is the canonical reference and
   always works. Output is byte-equal to the cgo+gpu path, identical
   semantics across build tags.

   Same rename for ring/ntt_simd_stub.go -> ring/ntt_simd_dispatch_purego.go
   with comment update: it's a dispatch hook (returns false to signal
   "use canonical scalar NTT below"), not an incomplete impl.

Verification:
* make checks: all 6 substeps PASS (go fmt / vet / goimports / staticcheck /
  govulncheck / gosec) on this branch.
* go test -count=1 ./...: all packages green; ring + ringqp + schemes/bgv
  + schemes/ckks + multiparty + utils/* all pass.
2026-05-04 08:37:27 -07:00

226 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build cgo
package gpu
import (
"testing"
)
// Test NTT-friendly prime used by Corona
const (
TestN uint32 = 256
TestQ uint64 = 0x1000000004A01 // 48-bit NTT-friendly prime
)
func TestGPUAvailable(t *testing.T) {
available := GPUAvailable()
backend := GetBackend()
t.Logf("GPU Available: %v, Backend: %s", available, backend)
}
func TestIsNTTPrime(t *testing.T) {
// Test Corona primes
tests := []struct {
N uint32
Q uint64
want bool
name string
}{
{256, 0x1000000004A01, true, "Corona Q"},
{256, 0x40201, true, "Corona QXi"}, // 262657 - prime, ≡ 1 (mod 512)
{256, 0x7FE01, true, "Corona QNu"}, // 523777 - prime, ≡ 1 (mod 512)
{256, 0x40000, false, "Non-prime 2^18"}, // 262144 - NOT prime
{256, 0x80000, false, "Non-prime 2^19"}, // 524288 - NOT prime
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsNTTPrime(tt.N, tt.Q)
if got != tt.want {
t.Errorf("IsNTTPrime(%d, 0x%X) = %v, want %v", tt.N, tt.Q, got, tt.want)
}
})
}
}
func TestNewNTTContext(t *testing.T) {
ctx, err := NewNTTContext(TestN, TestQ)
if err != nil {
t.Fatalf("NewNTTContext failed: %v", err)
}
defer ctx.Close()
if ctx.N != TestN {
t.Errorf("N = %d, want %d", ctx.N, TestN)
}
if ctx.Q != TestQ {
t.Errorf("Q = 0x%X, want 0x%X", ctx.Q, TestQ)
}
}
func TestNTTRoundTrip(t *testing.T) {
ctx, err := NewNTTContext(TestN, TestQ)
if err != nil {
t.Fatalf("NewNTTContext failed: %v", err)
}
defer ctx.Close()
// Create a test polynomial
poly := make([]uint64, TestN)
for i := range poly {
poly[i] = uint64(i+1) % TestQ
}
// Save original
original := make([]uint64, TestN)
copy(original, poly)
// Forward NTT
nttResult, err := ctx.NTT([][]uint64{poly})
if err != nil {
t.Fatalf("NTT failed: %v", err)
}
// Inverse NTT
inttResult, err := ctx.INTT(nttResult)
if err != nil {
t.Fatalf("INTT failed: %v", err)
}
// Verify round-trip
for i := range original {
if inttResult[0][i] != original[i] {
t.Errorf("Round-trip failed at index %d: got %d, want %d", i, inttResult[0][i], original[i])
}
}
t.Log("NTT round-trip successful")
}
func TestFindPrimitiveRoot(t *testing.T) {
root, err := FindPrimitiveRoot(TestN, TestQ)
if err != nil {
t.Fatalf("FindPrimitiveRoot failed: %v", err)
}
t.Logf("Primitive 2×%d-th root of unity mod 0x%X: %d", TestN, TestQ, root)
if root == 0 || root >= TestQ {
t.Errorf("Invalid root: %d", root)
}
}
func TestModInverse(t *testing.T) {
// Test N^{-1} mod Q
inv, err := ModInverse(uint64(TestN), TestQ)
if err != nil {
t.Fatalf("ModInverse failed: %v", err)
}
// Verify: N * N^{-1} = 1 (mod Q)
product := (uint64(TestN) * inv) % TestQ
if product != 1 {
t.Errorf("ModInverse verification failed: %d * %d = %d (mod Q), want 1", TestN, inv, product)
}
t.Logf("%d^{-1} mod 0x%X = %d", TestN, TestQ, inv)
}
func TestPolyArithmetic(t *testing.T) {
N := uint32(TestN)
Q := TestQ
a := make([]uint64, N)
b := make([]uint64, N)
for i := range a {
a[i] = uint64(i+1) % Q
b[i] = uint64(i+2) % Q
}
// Test addition
sum, err := PolyAdd(a, b, Q)
if err != nil {
t.Fatalf("PolyAdd failed: %v", err)
}
for i := range sum {
expected := (a[i] + b[i]) % Q
if sum[i] != expected {
t.Errorf("PolyAdd[%d] = %d, want %d", i, sum[i], expected)
}
}
// Test subtraction
diff, err := PolySub(a, b, Q)
if err != nil {
t.Fatalf("PolySub failed: %v", err)
}
for i := range diff {
var expected uint64
if a[i] >= b[i] {
expected = a[i] - b[i]
} else {
expected = Q - b[i] + a[i]
}
if diff[i] != expected {
t.Errorf("PolySub[%d] = %d, want %d", i, diff[i], expected)
}
}
// Test scalar multiplication
scalar := uint64(5)
product, err := PolyScalarMul(a, scalar, Q)
if err != nil {
t.Fatalf("PolyScalarMul failed: %v", err)
}
for i := range product {
expected := (a[i] * scalar) % Q
if product[i] != expected {
t.Errorf("PolyScalarMul[%d] = %d, want %d", i, product[i], expected)
}
}
t.Log("Polynomial arithmetic tests passed")
}
func BenchmarkNTT(b *testing.B) {
ctx, err := NewNTTContext(TestN, TestQ)
if err != nil {
b.Fatalf("NewNTTContext failed: %v", err)
}
defer ctx.Close()
poly := make([]uint64, TestN)
for i := range poly {
poly[i] = uint64(i) % TestQ
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := ctx.NTT([][]uint64{poly})
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkPolyMul(b *testing.B) {
ctx, err := NewNTTContext(TestN, TestQ)
if err != nil {
b.Fatalf("NewNTTContext failed: %v", err)
}
defer ctx.Close()
a := make([]uint64, TestN)
c := make([]uint64, TestN)
for i := range a {
a[i] = uint64(i) % TestQ
c[i] = uint64(i+1) % TestQ
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := ctx.PolyMul([][]uint64{a}, [][]uint64{c})
if err != nil {
b.Fatal(err)
}
}
}