Lux Proposals
← All proposals
LP-0107Draft

Abstract

Lux currently has Montgomery arithmetic, NTT, RNS, polynomial operations, GPU dispatch, parameter registries, and bounded serialization scattered across lattice/, pulsar/, fhe/, crypto/ntt/, luxcpp/crypto/{pulsar,fhe,corona}/, and others — with overlap, drift potential, and no single canonical owner. LP-107 promotes a shared luxfi/math Go module + luxcpp/crypto/math C++ mirror as the substrate that every protocol repo consumes. Protocols own protocol semantics; math owns the kernels; KATs bind both layers across runtimes.

Motivation

The current layout has accumulated parallel implementations:

Outcomes of the current shape:

LP-107 proposes a single owner for the math substrate so every layer above it consumes the same primitives by reference, and every layer below it (C++, GPU) is a backend that must prove byte-equality against the canonical Go reference via KATs.

Layering


luxfi/math
    ↑
luxfi/lattice         (lattice algebra: rings, modules, trapdoors, commits)
    ↑
luxfi/pulsar          (PQ threshold protocol: sign, reshare, dkg2)
luxfi/lens            (curve threshold protocol)
luxfi/fhe             (FHE schemes/runtime)
    ↑
luxfi/consensus       (Quasar/Nebula — consume certificates, no math kernels)
luxfi/warp            (envelope codec — consume certificates only)

luxcpp/crypto/math    ↔ KAT parity ↔ luxfi/math

Specification

luxfi/math package layout

Backend dispatch


type BackendPolicy string

const (
    BackendPureGo       BackendPolicy = \"pure-go\"
    BackendNativeCPU    BackendPolicy = \"native-cpu\"
    BackendGPUPreferred BackendPolicy = \"gpu-preferred\"
    BackendGPURequired  BackendPolicy = \"gpu-required\"
)

type NTTBackend interface {
    Name() string
    Supports(params NTTParams) bool
    Forward(dst, src []uint64, params NTTParams) error
    Inverse(dst, src []uint64, params NTTParams) error
}

type NTTService struct {
    Params  NTTParams
    Backend NTTBackend
    Policy  BackendPolicy
}

Backends: PureGo, AVX2, NEON, CXX, CUDA, Metal, WGSL. For consensus paths, default policy is BackendPureGo or BackendNativeCPU (deterministic). GPU is opt-in only when equivalence is KAT-proven.

Dispatch contract

1. Inputs are canonical.

2. Outputs are canonical.

3. Backend selection MUST NOT alter transcript bytes.

4. Unsupported backend returns explicit error or falls back per policy.

5. KATs verify equivalence across all supported backends.

luxcpp/crypto/math mirror


luxcpp/crypto/math/
    modarith/
    ntt/
    poly/
    rns/
    codec/
    backend/
luxcpp/crypto/fhe/      (consumes math)
luxcpp/crypto/pulsar/   (consumes math + lattice)
luxcpp/crypto/lens/     (consumes math curves)

Dependency direction:


pulsar C++ -> math C++
fhe C++    -> math C++
lens C++   -> math C++ curves

NEVER math C++ -> pulsar etc.

Codec hardening (closes the ReadUint64Slice DoS class)


type Limits struct {
    MaxFrameBytes     int
    MaxUint16SliceLen int
    MaxUint32SliceLen int
    MaxUint64SliceLen int
    MaxDepth          int
}

func (r *Reader) ReadUint64SliceBounded(maxLen int) ([]uint64, error)

No recursion. No hidden growth. No unbounded allocation. Every protocol consuming wire data goes through math/codec (no protocol-local decoders).

Stub policy

Files that previously lived as *_stub.go returning errors are renamed and given real bodies:

| Old name | New name | Body |
|---|---|---|
| *_stub.go (pure-Go fallback) | *_purego.go | real Go body delegating to canonical reference |
| *_stub.go (CPU when GPU unavailable) | *_cpu_fallback.go | real CPU body, not accelerated |
| *_stub.go (intentionally not yet wired) | *_native_pending.go | error-return only when native impl is genuinely incomplete |
| *_stub.go (dispatcher hook) | *_dispatch_purego.go | returns false to signal canonical path |

A \"stub\" sounds fake. A \"purego fallback\" is production-valid.

Migration plan

Phase 1 — Stabilize current repos

1. Land lattice PR #3 (ReadUint64Slice DoS bound) + companion PR #5 (CI hygiene + no-stub policy).

2. Rename misleading stubs to purego fallback / dispatch.

3. Add comments explaining fallback semantics.

4. Keep APIs stable.

Phase 2 — Introduce luxfi/math

Create github.com/luxfi/math with copied canonical implementations of modarith, ntt, codec, params, backend. Add KATs that lock the byte-stream behavior.

Phase 3 — Make lattice consume math

lattice/ring internal NTT delegates to math/ntt. Keep wrapper compatibility. ring.SubRing.NTT becomes a thin shim over math.NTTService.Forward for the matched param set.

Phase 4 — Make pulsar consume lattice + math

Pulsar stops reaching into ad-hoc GPU/NTT code; it imports lattice for ring algebra and math for primitives.

Phase 5 — Make fhe consume math

FHE and Pulsar share NTT/RNS primitives where parameter-compatible.

Phase 6 — Bind luxcpp/crypto

luxcpp/crypto/math becomes the native backend for luxfi/math. Each Go primitive has a C++ counterpart that the Go-side dispatcher can route to via cgo when cgo+gpu (or cgo+cxx_cpu) is built.

Phase 7 — Cross-runtime KAT release gate

Before claiming v0.2.0:

What this is NOT

Rationale

The user-facing thesis: Lux separates cryptographic protocol semantics from high-performance arithmetic. Go is the canonical semantic reference; C++/GPU is the performance backend; KATs prove they are the same; protocols never own the math kernels.

Every other architectural decision in the post-quantum stack is downstream of this separation. Without it, every \"is this still byte-equal\" question requires reading the whole protocol repo. With it, the question is local to math + a pinned KAT manifest.

Backwards compatibility

Phase 1 is a no-op for callers (renames + comments only).

Phases 2–5 keep API stability for lattice, pulsar, fhe, lens consumers — internal delegation only.

Phase 6 is invisible to Go-only callers.

Phase 7 is a new release-gate, not a breaking change.

Reference