mirror of
https://github.com/luxfi/mlwe.git
synced 2026-10-30 15:24:54 +00:00
233 lines
7.8 KiB
Go
233 lines
7.8 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package mlwe is the shared Module-LWE base for the Lux post-quantum
|
|
// signature schemes.
|
|
//
|
|
// Both Pulsar (FIPS 204 / ML-DSA, q = 8 380 417, a 23-bit ring) and
|
|
// Corona (Ringtail/Raccoon, q = 0x1000000004A01, a 48-bit ring) are
|
|
// Module-LWE constructions over R_q = Z_q[X]/(X^256 + 1). They share
|
|
// the same module arithmetic, the same SHAKE-derived samplers, the
|
|
// same Shamir/Lagrange secret-sharing, the same SP 800-185 transcript
|
|
// hashing, and the same bit-packing codecs. This module factors that
|
|
// common surface out exactly once.
|
|
//
|
|
// # Layering
|
|
//
|
|
// This root package defines only VALUE TYPES (Poly, PolyVec, PolyMat,
|
|
// Profile) and small INTERFACES (Ring, RoundingRing, Codec). It has no
|
|
// internal dependencies. Concrete rings live under ring/; samplers
|
|
// under sample/; the module-level (Module-SIS) norm and rounding
|
|
// toolkit under modulesis/; secret sharing under share/; the hash
|
|
// transcript under transcript/; bit packing under codec/.
|
|
//
|
|
// # Coefficient width
|
|
//
|
|
// A Poly stores coefficients as uint64. This is the single
|
|
// representation that holds BOTH the 23-bit ML-DSA modulus and the
|
|
// 48-bit Corona modulus without truncation. A concrete Ring is free to
|
|
// narrow internally (ring/mldsa keeps a [256]uint32 fast core and
|
|
// converts at the interface boundary) so that byte-for-byte FIPS 204
|
|
// output and constant-time Montgomery arithmetic are preserved.
|
|
//
|
|
// # Domain of a Poly
|
|
//
|
|
// The Poly type does not tag whether its coefficients are in the
|
|
// coefficient domain or the NTT (evaluation) domain. The domain is a
|
|
// property of the value at a given point in a computation, tracked by
|
|
// the caller, exactly as in the reference FIPS 204 code. NTT moves a
|
|
// value coefficient->evaluation; INTT moves it back. MulNTT is only
|
|
// meaningful on evaluation-domain operands.
|
|
package mlwe
|
|
|
|
// Poly is an element of R_q = Z_q[X]/(X^N + 1) with N coefficients.
|
|
// Coeffs[i] is the coefficient of X^i, reduced into [0, q) in the
|
|
// coefficient domain (NTT-domain intermediates may be larger but stay
|
|
// well below 2^64 for every supported ring).
|
|
type Poly struct {
|
|
Coeffs []uint64
|
|
}
|
|
|
|
// Clone returns a deep copy of p.
|
|
func (p Poly) Clone() Poly {
|
|
c := make([]uint64, len(p.Coeffs))
|
|
copy(c, p.Coeffs)
|
|
return Poly{Coeffs: c}
|
|
}
|
|
|
|
// PolyVec is a module vector: an ordered list of ring elements. In
|
|
// Module-LWE a secret/error vector and a commitment are PolyVecs.
|
|
type PolyVec []Poly
|
|
|
|
// Clone returns a deep copy of v.
|
|
func (v PolyVec) Clone() PolyVec {
|
|
out := make(PolyVec, len(v))
|
|
for i := range v {
|
|
out[i] = v[i].Clone()
|
|
}
|
|
return out
|
|
}
|
|
|
|
// PolyMat is a module matrix in row-major order: Mat[i] is row i, a
|
|
// PolyVec of length L (the column count). The public matrix A of a
|
|
// Module-LWE instance is a PolyMat.
|
|
type PolyMat []PolyVec
|
|
|
|
// Rows reports the number of rows (the module dimension K).
|
|
func (m PolyMat) Rows() int { return len(m) }
|
|
|
|
// Cols reports the number of columns (the secret dimension L), or 0 for
|
|
// an empty matrix.
|
|
func (m PolyMat) Cols() int {
|
|
if len(m) == 0 {
|
|
return 0
|
|
}
|
|
return len(m[0])
|
|
}
|
|
|
|
// Profile is the parameter profile of a concrete Module-LWE instance.
|
|
// It names the ring shape and the rounding parameters that the FIPS 204
|
|
// (and Ringtail) decomposition primitives are gated on. A Profile is a
|
|
// pure value: equal Profiles describe identical instances.
|
|
type Profile struct {
|
|
// Name is the canonical human-readable identifier, e.g.
|
|
// "ML-DSA-65".
|
|
Name string
|
|
|
|
// N is the ring degree (256 for every Lux Module-LWE instance).
|
|
N int
|
|
|
|
// Q is the prime modulus.
|
|
Q uint64
|
|
|
|
// K is the module dimension (row count of A).
|
|
K int
|
|
|
|
// L is the secret dimension (column count of A).
|
|
L int
|
|
|
|
// Eta is the secret/error coefficient bound for the centered
|
|
// binomial sampler.
|
|
Eta int
|
|
|
|
// Tau is the challenge Hamming weight for SampleInBall.
|
|
Tau int
|
|
|
|
// Gamma1 is the mask coefficient bound (the ExpandMask range is
|
|
// the open interval (-Gamma1, Gamma1]).
|
|
Gamma1 uint32
|
|
|
|
// Gamma2 is the low-order rounding range (alpha = 2*Gamma2). It
|
|
// gates Decompose, HighBits and UseHint.
|
|
Gamma2 uint32
|
|
|
|
// Omega is the maximum hint Hamming weight.
|
|
Omega int
|
|
}
|
|
|
|
// Ring is the irreducible per-instance polynomial arithmetic of a
|
|
// Module-LWE ring. It is deliberately small: every higher-order
|
|
// operation (matrix-vector products, vector norms, vector rounding)
|
|
// composes these primitives in the modulesis package and therefore
|
|
// works uniformly across rings.
|
|
//
|
|
// NTT and INTT use the implementation's native (Montgomery) convention.
|
|
// The contract a caller may rely on is the MULTIPLICATION round-trip:
|
|
//
|
|
// INTT(MulNTT(NTT(a), NTT(b))) == a * b in R_q
|
|
//
|
|
// The bare round-trip INTT(NTT(p)) is NOT promised to equal p: a
|
|
// constant-time Montgomery ring (such as ring/mldsa) leaves a fixed
|
|
// Montgomery factor on the bare round-trip that the multiplication path
|
|
// cancels. See ring/mldsa for the exact convention.
|
|
type Ring interface {
|
|
// N returns the ring degree.
|
|
N() int
|
|
|
|
// Q returns the prime modulus.
|
|
Q() uint64
|
|
|
|
// NewPoly returns a freshly allocated zero polynomial with N
|
|
// coefficients.
|
|
NewPoly() Poly
|
|
|
|
// NTT transforms p in place from the coefficient domain to the
|
|
// NTT (evaluation) domain.
|
|
NTT(p Poly)
|
|
|
|
// INTT transforms p in place from the NTT domain back to the
|
|
// coefficient domain.
|
|
INTT(p Poly)
|
|
|
|
// Add sets dst = a + b (coefficient-wise, reduced into [0, q)).
|
|
// dst may alias a or b.
|
|
Add(dst, a, b Poly)
|
|
|
|
// Sub sets dst = a - b (coefficient-wise, reduced into [0, q)).
|
|
// dst may alias a or b.
|
|
Sub(dst, a, b Poly)
|
|
|
|
// MulNTT sets dst = a o b, the coefficient-wise product of two
|
|
// NTT-domain operands. dst may alias a or b.
|
|
MulNTT(dst, a, b Poly)
|
|
|
|
// MatVec returns A*x over R_q for a coefficient-domain matrix A
|
|
// and coefficient-domain vector x, with the result in the
|
|
// coefficient domain. Forward and inverse transforms are handled
|
|
// internally.
|
|
MatVec(a PolyMat, x PolyVec) PolyVec
|
|
|
|
// InfNorm returns the centered infinity norm of p: the maximum
|
|
// over coefficients of min(c, q-c). p must be in [0, q).
|
|
InfNorm(p Poly) uint64
|
|
|
|
// Codec returns the byte codec bound to this ring's degree.
|
|
Codec() Codec
|
|
}
|
|
|
|
// RoundingRing is a Ring that also supports the FIPS 204 high/low-bit
|
|
// decomposition primitives. These are gated on the instance's Gamma2
|
|
// (and the Power2Round drop length D), so they live on the concrete
|
|
// ring rather than as free functions.
|
|
//
|
|
// Power2Round, Decompose, HighBits and UseHint all operate
|
|
// coefficient-wise and require their inputs reduced into [0, q). Their
|
|
// PolyVec liftings are in the modulesis package.
|
|
type RoundingRing interface {
|
|
Ring
|
|
|
|
// Power2Round splits each coefficient r = r1*2^D + r0 with r0 in
|
|
// (-2^(D-1), 2^(D-1)], returning (r0, r1) with r0 represented as
|
|
// r0 mod q in [0, q).
|
|
Power2Round(p Poly) (r0, r1 Poly)
|
|
|
|
// Decompose splits each coefficient r = r1*(2*Gamma2) + r0 with r0
|
|
// centered in (-Gamma2, Gamma2], returning (low, high) with low =
|
|
// r0 mod q in [0, q) and high = r1.
|
|
Decompose(p Poly) (low, high Poly)
|
|
|
|
// HighBits returns the high part r1 of Decompose.
|
|
HighBits(p Poly) Poly
|
|
|
|
// UseHint applies one FIPS 204 (Algorithm 40) hint bit per
|
|
// coefficient to r, returning the corrected high part. hint
|
|
// coefficients must be 0 or 1.
|
|
UseHint(hint, r Poly) Poly
|
|
}
|
|
|
|
// Codec serializes ring elements to and from bytes using little-endian
|
|
// (LSB-first) bit packing, the FIPS 204 wire convention. A Codec is
|
|
// bound to a fixed ring degree N.
|
|
type Codec interface {
|
|
// N returns the ring degree this codec packs.
|
|
N() int
|
|
|
|
// Pack packs the low `bits` bits of each of p's N coefficients
|
|
// into ceil(N*bits/8) bytes, LSB-first.
|
|
Pack(p Poly, bits int) []byte
|
|
|
|
// Unpack is the inverse of Pack: it reads N coefficients of `bits`
|
|
// bits each from data.
|
|
Unpack(data []byte, bits int) Poly
|
|
}
|