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.
230 lines
7.2 KiB
Go
230 lines
7.2 KiB
Go
// Copyright (c) 2026 Lux Industries Inc.
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package params is the single Lux registry of cryptographic parameter
|
|
// identifiers. Every other package in luxfi/math (and downstream
|
|
// luxfi/lattice, luxfi/pulsar, luxfi/fhe, luxfi/lens) keys off these
|
|
// IDs; every cross-runtime KAT carries them; every backend dispatch
|
|
// uses them to route work.
|
|
//
|
|
// LP-107 §"Parameter registry" — the canonical motivation. There must
|
|
// be exactly one place that names "Pulsar's modulus" or
|
|
// "FHE PN10QP27 ring dimension"; this package is that place.
|
|
//
|
|
// IDs are stable strings — wire-formatted, log-printable, KAT-keyed.
|
|
// Renaming an ID is a breaking change. New IDs append; existing IDs
|
|
// never change semantics.
|
|
package params
|
|
|
|
import "fmt"
|
|
|
|
// ModulusID names a single prime modulus.
|
|
//
|
|
// Production identifiers MUST satisfy: stable string, lowercase, hex
|
|
// representation of the modulus where applicable, prefixed by the
|
|
// owning protocol/scheme name. Validation: see Modulus.Validate.
|
|
type ModulusID string
|
|
|
|
const (
|
|
// ModPulsarQ — Pulsar/LP-073 canonical NTT-friendly prime.
|
|
// Q = 0x1000000004A01 ≈ 2^48; satisfies (Q - 1) | 2N for N = 256.
|
|
ModPulsarQ ModulusID = "pulsar-q-0x1000000004a01"
|
|
|
|
// ModNTT998 — classical NTT-friendly prime 998244353
|
|
// (used by general vector kernels and tests; not production crypto).
|
|
ModNTT998 ModulusID = "ntt-998244353"
|
|
|
|
// ModFHE_PN10QP27 — first FHE production parameter set; 27-bit Q,
|
|
// ring dimension N = 1024.
|
|
ModFHE_PN10QP27 ModulusID = "fhe-pn10qp27"
|
|
|
|
// ModFHE_PN11QP54 — second FHE production parameter set; 54-bit Q,
|
|
// ring dimension N = 2048.
|
|
ModFHE_PN11QP54 ModulusID = "fhe-pn11qp54"
|
|
|
|
// ModFHE_PN9QP28_STD128 — STD128-tagged FHE parameter set;
|
|
// ring dimension N = 512.
|
|
ModFHE_PN9QP28_STD128 ModulusID = "fhe-pn9qp28-std128"
|
|
)
|
|
|
|
// String makes ModulusID printable.
|
|
func (m ModulusID) String() string { return string(m) }
|
|
|
|
// Validate reports whether m is a known modulus identifier in this
|
|
// process. Unknown IDs are rejected — there is no implicit registration.
|
|
func (m ModulusID) Validate() error {
|
|
switch m {
|
|
case ModPulsarQ,
|
|
ModNTT998,
|
|
ModFHE_PN10QP27,
|
|
ModFHE_PN11QP54,
|
|
ModFHE_PN9QP28_STD128:
|
|
return nil
|
|
}
|
|
return fmt.Errorf("params: unknown ModulusID %q", string(m))
|
|
}
|
|
|
|
// NTTParamID names an (N, Q, root) triple for an NTT instance.
|
|
// One ModulusID may have multiple NTTParamID values (different N).
|
|
type NTTParamID string
|
|
|
|
const (
|
|
// NTTPulsarN256 — Pulsar's R_q = Z_q[X]/(X^256 + 1) at Q = ModPulsarQ.
|
|
NTTPulsarN256 NTTParamID = "pulsar-n256-q0x1000000004a01"
|
|
|
|
// NTTFHE_PN10QP27_N1024 — FHE PN10QP27 ring at N = 1024.
|
|
NTTFHE_PN10QP27_N1024 NTTParamID = "fhe-pn10qp27-n1024"
|
|
|
|
// NTTFHE_PN11QP54_N2048 — FHE PN11QP54 ring at N = 2048.
|
|
NTTFHE_PN11QP54_N2048 NTTParamID = "fhe-pn11qp54-n2048"
|
|
|
|
// NTTFHE_PN9QP28_N512 — FHE PN9QP28 ring at N = 512.
|
|
NTTFHE_PN9QP28_N512 NTTParamID = "fhe-pn9qp28-n512"
|
|
)
|
|
|
|
// String makes NTTParamID printable.
|
|
func (p NTTParamID) String() string { return string(p) }
|
|
|
|
// Validate reports whether p is a known NTT parameter identifier.
|
|
func (p NTTParamID) Validate() error {
|
|
switch p {
|
|
case NTTPulsarN256,
|
|
NTTFHE_PN10QP27_N1024,
|
|
NTTFHE_PN11QP54_N2048,
|
|
NTTFHE_PN9QP28_N512:
|
|
return nil
|
|
}
|
|
return fmt.Errorf("params: unknown NTTParamID %q", string(p))
|
|
}
|
|
|
|
// FHEParamID names a complete FHE scheme parameter set (ring + RNS
|
|
// chain + key-switching topology + bootstrap structure). Distinct
|
|
// from NTTParamID: one FHEParamID owns one or more NTTParamIDs.
|
|
type FHEParamID string
|
|
|
|
const (
|
|
FHE_PN10QP27 FHEParamID = "fhe-pn10qp27"
|
|
FHE_PN11QP54 FHEParamID = "fhe-pn11qp54"
|
|
FHE_PN9QP28_STD128 FHEParamID = "fhe-pn9qp28-std128"
|
|
)
|
|
|
|
// String makes FHEParamID printable.
|
|
func (f FHEParamID) String() string { return string(f) }
|
|
|
|
// Validate reports whether f is a known FHE parameter set.
|
|
func (f FHEParamID) Validate() error {
|
|
switch f {
|
|
case FHE_PN10QP27, FHE_PN11QP54, FHE_PN9QP28_STD128:
|
|
return nil
|
|
}
|
|
return fmt.Errorf("params: unknown FHEParamID %q", string(f))
|
|
}
|
|
|
|
// PulsarParamID names a Pulsar threshold-signature parameter set.
|
|
type PulsarParamID string
|
|
|
|
const (
|
|
// PulsarLP073 — canonical LP-073 Pulsar parameter set.
|
|
PulsarLP073 PulsarParamID = "pulsar-lp073"
|
|
)
|
|
|
|
// String makes PulsarParamID printable.
|
|
func (p PulsarParamID) String() string { return string(p) }
|
|
|
|
// Validate reports whether p is a known Pulsar parameter set.
|
|
func (p PulsarParamID) Validate() error {
|
|
if p == PulsarLP073 {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("params: unknown PulsarParamID %q", string(p))
|
|
}
|
|
|
|
// HashSuiteID names a hash construction profile.
|
|
type HashSuiteID string
|
|
|
|
const (
|
|
HashPulsarSHA3 HashSuiteID = "pulsar-sha3-v1"
|
|
HashBLAKE3 HashSuiteID = "blake3-v1"
|
|
)
|
|
|
|
// String makes HashSuiteID printable.
|
|
func (h HashSuiteID) String() string { return string(h) }
|
|
|
|
// Validate reports whether h is a known hash suite.
|
|
func (h HashSuiteID) Validate() error {
|
|
switch h {
|
|
case HashPulsarSHA3, HashBLAKE3:
|
|
return nil
|
|
}
|
|
return fmt.Errorf("params: unknown HashSuiteID %q", string(h))
|
|
}
|
|
|
|
// BackendID names a math-substrate backend (CPU pure-Go, native CPU,
|
|
// CUDA, Metal, WGSL). The same NTT/Modarith/Poly contract may be
|
|
// realized by multiple backends; KATs prove they produce byte-equal
|
|
// output.
|
|
type BackendID string
|
|
|
|
const (
|
|
BackendPureGo BackendID = "pure-go"
|
|
BackendNative BackendID = "native-cpu"
|
|
BackendAVX2 BackendID = "avx2"
|
|
BackendNEON BackendID = "neon"
|
|
BackendCUDA BackendID = "cuda"
|
|
BackendMetal BackendID = "metal"
|
|
BackendWGSL BackendID = "wgsl"
|
|
)
|
|
|
|
// String makes BackendID printable.
|
|
func (b BackendID) String() string { return string(b) }
|
|
|
|
// Validate reports whether b is a known backend.
|
|
func (b BackendID) Validate() error {
|
|
switch b {
|
|
case BackendPureGo, BackendNative, BackendAVX2, BackendNEON,
|
|
BackendCUDA, BackendMetal, BackendWGSL:
|
|
return nil
|
|
}
|
|
return fmt.Errorf("params: unknown BackendID %q", string(b))
|
|
}
|
|
|
|
// KATHeader is the canonical key-set every KAT vector MUST carry.
|
|
// LP-107 §"Parameter registry" requirement: every KAT entry binds
|
|
// itself to a specific (parameter_set, modulus, backend, hash_suite,
|
|
// implementation_version) tuple so cross-runtime replay can match
|
|
// like-for-like.
|
|
type KATHeader struct {
|
|
ParameterSet string `json:"parameter_set"`
|
|
ModulusID ModulusID `json:"modulus_id"`
|
|
BackendID BackendID `json:"backend_id"`
|
|
HashSuiteID HashSuiteID `json:"hash_suite_id"`
|
|
ImplementationName string `json:"implementation_name"`
|
|
ImplementationVersion string `json:"implementation_version"`
|
|
}
|
|
|
|
// Validate ensures every required field is set and known.
|
|
func (h *KATHeader) Validate() error {
|
|
if h == nil {
|
|
return fmt.Errorf("params: nil KATHeader")
|
|
}
|
|
if h.ParameterSet == "" {
|
|
return fmt.Errorf("params: KATHeader.ParameterSet is empty")
|
|
}
|
|
if err := h.ModulusID.Validate(); err != nil {
|
|
return fmt.Errorf("KATHeader: %w", err)
|
|
}
|
|
if err := h.BackendID.Validate(); err != nil {
|
|
return fmt.Errorf("KATHeader: %w", err)
|
|
}
|
|
if err := h.HashSuiteID.Validate(); err != nil {
|
|
return fmt.Errorf("KATHeader: %w", err)
|
|
}
|
|
if h.ImplementationName == "" {
|
|
return fmt.Errorf("params: KATHeader.ImplementationName is empty")
|
|
}
|
|
if h.ImplementationVersion == "" {
|
|
return fmt.Errorf("params: KATHeader.ImplementationVersion is empty")
|
|
}
|
|
return nil
|
|
}
|