mirror of
https://github.com/luxfi/math.git
synced 2026-07-27 03:38:49 +00:00
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.
132 lines
4.5 KiB
Go
132 lines
4.5 KiB
Go
// Copyright (c) 2026 Lux Industries Inc.
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package ntt is the canonical Number-Theoretic-Transform interface for
|
|
// luxfi/math.
|
|
//
|
|
// LP-107 §"NTT" — the canonical motivation. Production callers
|
|
// (luxfi/lattice, luxfi/pulsar, luxfi/fhe) consume this package's
|
|
// Service abstraction; concrete kernels live behind a Backend
|
|
// interface so AVX2 / NEON / CUDA / Metal / WGSL realizations are
|
|
// interchangeable.
|
|
//
|
|
// Phase 2 (this file): defines the public surface. The pure-Go
|
|
// reference Backend wraps github.com/luxfi/lattice/v7/ring's
|
|
// SubRing.NTT — the canonical Lattigo-derived Montgomery NTT — so
|
|
// callers see no behavior change. Phase 3 (LP-107) inverts the
|
|
// dependency: lattice/ring imports this package, and the Lattigo
|
|
// kernel body lives here.
|
|
//
|
|
// Determinism contract: for a fixed (Params, input []uint64), every
|
|
// registered Backend MUST produce byte-equal output. KATs in
|
|
// luxfi/math/ntt/test/kat enforce this across runtimes.
|
|
package ntt
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/math/backend"
|
|
"github.com/luxfi/math/params"
|
|
)
|
|
|
|
// Params identifies one NTT instance: ring degree N, modulus Q, and
|
|
// the canonical parameter ID for KAT lookup.
|
|
type Params struct {
|
|
// N is the ring dimension. Must be a power of two.
|
|
N uint32
|
|
// Q is the prime modulus. Must satisfy (Q - 1) | 2N (NTT-friendly).
|
|
Q uint64
|
|
// ID is the canonical parameter identifier (e.g. NTTPulsarN256).
|
|
ID params.NTTParamID
|
|
}
|
|
|
|
// Validate ensures (N is a power of two) AND ((Q - 1) | 2N).
|
|
func (p *Params) Validate() error {
|
|
if p == nil {
|
|
return fmt.Errorf("ntt: nil Params")
|
|
}
|
|
if p.N == 0 || p.N&(p.N-1) != 0 {
|
|
return fmt.Errorf("ntt: N=%d not a power of two", p.N)
|
|
}
|
|
if p.Q <= 1 {
|
|
return fmt.Errorf("ntt: Q=%d invalid (must be > 1)", p.Q)
|
|
}
|
|
if (p.Q-1)%(2*uint64(p.N)) != 0 {
|
|
return fmt.Errorf("ntt: Q-1=%d not divisible by 2N=%d (NTT-unfriendly)",
|
|
p.Q-1, 2*uint64(p.N))
|
|
}
|
|
if err := p.ID.Validate(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ErrUnsupportedParams is returned by a Backend that does not support
|
|
// the requested Params (e.g. CUDA backend asked for N=32 when it only
|
|
// implements N >= 256).
|
|
var ErrUnsupportedParams = errors.New("ntt: backend does not support these Params")
|
|
|
|
// Backend is the kernel interface every NTT realization implements.
|
|
// Forward and Inverse operate in-place and MUST produce byte-equal
|
|
// output across all registered backends for the same (Params, input).
|
|
type Backend interface {
|
|
// ID returns the BackendID for this backend.
|
|
ID() params.BackendID
|
|
// Supports reports whether this backend can handle p.
|
|
Supports(p *Params) bool
|
|
// Forward applies the forward NTT in-place. dst must have length
|
|
// batch*p.N. Returns ErrUnsupportedParams if Supports returns false.
|
|
Forward(dst []uint64, p *Params, batch uint32) error
|
|
// Inverse applies the inverse NTT in-place. Same length contract.
|
|
Inverse(dst []uint64, p *Params, batch uint32) error
|
|
}
|
|
|
|
// Service binds a Params to a chosen Backend (resolved via dispatch
|
|
// policy) and exposes the public Forward / Inverse methods every
|
|
// downstream caller uses.
|
|
type Service struct {
|
|
params *Params
|
|
backend Backend
|
|
policy backend.Policy
|
|
}
|
|
|
|
// NewService builds a Service for p under the given dispatch policy.
|
|
// The Backend is resolved at construction time from the registered
|
|
// backends; if no backend supports p, returns an error.
|
|
func NewService(p *Params, policy backend.Policy) (*Service, error) {
|
|
if err := p.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := policy.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
registered := registeredFor(p)
|
|
id, err := backend.Resolve(policy, registered)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ntt.NewService: %w", err)
|
|
}
|
|
b := lookup(id)
|
|
if b == nil {
|
|
return nil, fmt.Errorf("ntt: backend %s registered but lookup returned nil", id)
|
|
}
|
|
return &Service{params: p, backend: b, policy: policy}, nil
|
|
}
|
|
|
|
// Params returns the bound parameter set.
|
|
func (s *Service) Params() *Params { return s.params }
|
|
|
|
// Backend returns the resolved BackendID. Callers print this in logs
|
|
// to record which path executed.
|
|
func (s *Service) Backend() params.BackendID { return s.backend.ID() }
|
|
|
|
// Forward applies the forward NTT in-place via the resolved backend.
|
|
func (s *Service) Forward(dst []uint64, batch uint32) error {
|
|
return s.backend.Forward(dst, s.params, batch)
|
|
}
|
|
|
|
// Inverse applies the inverse NTT in-place via the resolved backend.
|
|
func (s *Service) Inverse(dst []uint64, batch uint32) error {
|
|
return s.backend.Inverse(dst, s.params, batch)
|
|
}
|