Files
math/backend/backend_test.go
T
Hanzo AI e018a9c3d1 LP-107 Phase 2: math substrate — params, backend, codec, modarith,
ntt, poly, rns, sample

Eight pure-Go reference packages that own the canonical semantics of
every cryptographic-math primitive Lux protocols share. Backends
(AVX2 / NEON / cgo+C++ / CUDA / Metal / WGSL) plug in behind the
substrate; KATs gate byte-equality across them.

Packages added:

* params/    — ModulusID, NTTParamID, FHEParamID, PulsarParamID,
               HashSuiteID, BackendID; KATHeader required-fields
               schema. Stable string IDs; renaming is a breaking
               change.

* backend/   — Policy enum (PureGo / NativeCPU / GPUPreferred /
               GPURequired); Resolve(policy, registered) returns the
               BackendID per LP-107's fallback chain. GPURequired
               returns ErrUnavailable when no GPU backend is
               registered.

* codec/     — Bounded readers (closes lattice issues #2 + #4 DoS
               class permanently). Limits + LimitError + Reader.
               ReadUint{16,32,64}Slice all reject the 70T-element
               attack input before allocating; depth + frame-bytes
               caps; iterative (no recursion). Regression test
               TestReadUint64Slice_RejectsHugeLength encodes the
               original lattice issue #4 input.

* modarith/  — Modulus type with QInv, R2, Barrett constants
               (computed via math/big once per modulus). AddMod /
               SubMod / MulMod (Div64 reference path). MontMulMod /
               ToMontgomery / FromMontgomery cross-checked against
               MulMod across 100 random Pulsar-Q pairs.
               ReductionMode enum byte-equal to lattice/types.

* ntt/       — Service + Backend interface; pure-Go backend
               delegates to lattice/v7/ring.SubRing.NTT/INTT (the
               canonical Lattigo-derived Montgomery NTT body — no
               re-implementation, just dispatch). Round-trip +
               batch + determinism tests on Pulsar N=256.

* poly/      — Add / Sub / ScalarMul / PointwiseMul (NTT-domain) /
               negacyclic Mul (NTT round-trip). Composes modarith +
               ntt; no lower-level reach.

* rns/       — Basis(Moduli, Name) for FHE RNS chains. Single +
               two-prime construction validated; rejects even moduli.
               Phase 3 will add basis-extension and modulus-switching.

* sample/    — Uniform (rejection sampling, mask-and-retry); Ternary
               (density + sign byte); CenteredBinomial (popcount
               difference); DiscreteGaussianRejection (6-sigma cutoff).
               All take an io.Reader so callers can KAT-replay.

Architecture invariants enforced in package docs:

  - Go is the canonical semantic reference.
  - Backend selection MUST NOT alter transcript bytes (consensus
    paths default to PolicyPureGo / PolicyNativeCPU).
  - No unbounded codec readers anywhere near wire formats.
  - No re-implementation: where lattice/v7/ring already owns the
    canonical body, we delegate, not fork.
  - One ID space; renaming is a breaking change.

Test posture: 8/8 packages green via `GOWORK=off go test ./...`.

Phases 3-7 (queued):
  3. lattice consumes math
  4. pulsar consumes lattice + math
  5. fhe consumes math
  6. luxcpp/crypto/math native backend mirror
  7. cross-runtime KAT release gate

See SUBSTRATE.md for the full posture and migration plan; full LP at
~/work/lux/lps/LP-107-lux-math-substrate.md.
2026-05-04 09:28:23 -07:00

109 lines
3.0 KiB
Go

// Copyright (c) 2026 Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package backend
import (
"errors"
"testing"
"github.com/luxfi/math/params"
)
func TestPolicy_String(t *testing.T) {
for _, tc := range []struct {
p Policy
want string
}{
{PolicyPureGo, "pure-go"},
{PolicyNativeCPU, "native-cpu"},
{PolicyGPUPreferred, "gpu-preferred"},
{PolicyGPURequired, "gpu-required"},
} {
if got := tc.p.String(); got != tc.want {
t.Errorf("Policy(%d).String() = %q, want %q", tc.p, got, tc.want)
}
}
}
func TestPolicy_Validate(t *testing.T) {
for _, p := range []Policy{
PolicyPureGo, PolicyNativeCPU, PolicyGPUPreferred, PolicyGPURequired,
} {
if err := p.Validate(); err != nil {
t.Errorf("%s: %v", p, err)
}
}
if err := Policy(99).Validate(); err == nil {
t.Error("Policy(99).Validate() returned nil")
}
}
func TestResolve_PureGo(t *testing.T) {
r := map[params.BackendID]bool{params.BackendPureGo: true}
got, err := Resolve(PolicyPureGo, r)
if err != nil || got != params.BackendPureGo {
t.Errorf("PureGo resolve: %v %s", err, got)
}
}
func TestResolve_NativeCPU_Fallback(t *testing.T) {
// Only pure-go registered; native-cpu policy must fall back.
r := map[params.BackendID]bool{params.BackendPureGo: true}
got, err := Resolve(PolicyNativeCPU, r)
if err != nil || got != params.BackendPureGo {
t.Errorf("NativeCPU fallback: %v %s", err, got)
}
// AVX2 registered: native-cpu should pick it.
r2 := map[params.BackendID]bool{
params.BackendPureGo: true, params.BackendAVX2: true,
}
got, err = Resolve(PolicyNativeCPU, r2)
if err != nil || got != params.BackendNative && got != params.BackendAVX2 {
t.Errorf("NativeCPU with AVX2: %v %s", err, got)
}
}
func TestResolve_GPUPreferred_FallbackChain(t *testing.T) {
// No GPU, no native — falls back to pure-go.
r := map[params.BackendID]bool{params.BackendPureGo: true}
got, err := Resolve(PolicyGPUPreferred, r)
if err != nil || got != params.BackendPureGo {
t.Errorf("GPUPreferred → pure-go fallback: %v %s", err, got)
}
// CUDA registered: GPUPreferred picks CUDA.
r2 := map[params.BackendID]bool{
params.BackendPureGo: true, params.BackendCUDA: true,
}
got, err = Resolve(PolicyGPUPreferred, r2)
if err != nil || got != params.BackendCUDA {
t.Errorf("GPUPreferred with CUDA: %v %s", err, got)
}
}
func TestResolve_GPURequired_NoGPU_Errors(t *testing.T) {
r := map[params.BackendID]bool{params.BackendPureGo: true}
_, err := Resolve(PolicyGPURequired, r)
if !errors.Is(err, ErrUnavailable) {
t.Errorf("GPURequired with no GPU: want ErrUnavailable, got %v", err)
}
}
func TestResolve_GPURequired_Metal_OK(t *testing.T) {
r := map[params.BackendID]bool{params.BackendMetal: true}
got, err := Resolve(PolicyGPURequired, r)
if err != nil || got != params.BackendMetal {
t.Errorf("GPURequired with Metal: %v %s", err, got)
}
}
func TestResolve_UnknownPolicy(t *testing.T) {
r := map[params.BackendID]bool{params.BackendPureGo: true}
_, err := Resolve(Policy(99), r)
if err == nil {
t.Error("Resolve(unknown) returned nil")
}
}