Files

270 lines
7.8 KiB
Go

// Copyright (c) 2026 Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
//
// Body migrated from luxfi/lattice/v7/ring/subring.go.
// LP-107 Phase 3 — luxfi/math is the owner of the Lattigo-derived
// Montgomery NTT body and SubRing layout. luxfi/lattice/ring re-exports.
//
// Changes from upstream:
// - factorization.GetFactors -> GetFactors (same package)
// - utils.BitReverse64 -> BitReverse64 (same package)
// - bignum.NewInt(uint64) -> new(big.Int).SetUint64(...)
package subring
import (
"fmt"
"math/big"
"math/bits"
)
// MinimumRingDegreeForLoopUnrolledOperations is the minimum ring degree required to
// safely perform loop-unrolled operations.
const MinimumRingDegreeForLoopUnrolledOperations = 8
// Type is the type of ring used by the cryptographic scheme.
type Type int
// Standard and ConjugateInvariant are two types of Rings.
const (
Standard = Type(0) // Z[X]/(X^N + 1) (Default)
ConjugateInvariant = Type(1) // Z[X+X^-1]/(X^2N + 1)
)
// String returns the string representation of the ring Type.
func (rt Type) String() string {
switch rt {
case Standard:
return "Standard"
case ConjugateInvariant:
return "ConjugateInvariant"
default:
return "Invalid"
}
}
// SubRing is a struct storing precomputation
// for fast modular reduction and NTT for
// a given modulus.
type SubRing struct {
ntt NumberTheoreticTransformer
// Polynomial nb.Coefficients
N int
// Modulus
Modulus uint64
// Unique factors of Modulus-1
Factors []uint64
// 2^bit_length(Modulus) - 1
Mask uint64
// Fast reduction constants
BRedConstant [2]uint64 // Barrett Reduction
MRedConstant uint64 // Montgomery Reduction
*NTTTable // NTT related constants
}
// NewSubRing creates a new SubRing with the standard NTT.
// NTT constants still need to be generated using .GenNTTConstants(NthRoot uint64).
func NewSubRing(N int, Modulus uint64) (s *SubRing, err error) {
return NewSubRingWithCustomNTT(N, Modulus, NewNumberTheoreticTransformerStandard, 2*N)
}
// NewSubRingWithCustomNTT creates a new SubRing with degree N and modulus Modulus with user-defined NTT transform and primitive Nth root of unity.
// Modulus should be equal to 1 modulo the root of unity.
// N must be a power of two larger than 8. An error is returned with a nil *SubRing in the case of non NTT-enabling parameters.
func NewSubRingWithCustomNTT(N int, Modulus uint64, ntt func(*SubRing, int) NumberTheoreticTransformer, NthRoot int) (s *SubRing, err error) {
// Checks if N is a power of 2
if N < MinimumRingDegreeForLoopUnrolledOperations || (N&(N-1)) != 0 && N != 0 {
return nil, fmt.Errorf("invalid ring degree: must be a power of 2 greater than %d", MinimumRingDegreeForLoopUnrolledOperations)
}
if NthRoot <= 0 {
panic(fmt.Errorf("invalid NthRoot: NthRoot=%d should be greater than 0", NthRoot))
}
s = &SubRing{}
s.N = N
s.Modulus = Modulus
/* #nosec G115 -- Modulus is ensured to be greater than 0 */
s.Mask = (1 << uint64(bits.Len64(Modulus-1))) - 1
// Computes the fast modular reduction constants for the Ring
s.BRedConstant = GenBRedConstant(Modulus)
// If qi is not a power of 2, we can compute the MRed (otherwise, it
// would return an error as there is no valid Montgomery form mod a power of 2)
if (Modulus&(Modulus-1)) != 0 && Modulus != 0 {
s.MRedConstant = GenMRedConstant(Modulus)
}
s.NTTTable = new(NTTTable)
s.NthRoot = uint64(NthRoot)
s.ntt = ntt(s, N)
return
}
// Type returns the Type of subring which might be either `Standard` or `ConjugateInvariant`.
func (s *SubRing) Type() Type {
switch s.ntt.(type) {
case NumberTheoreticTransformerStandard:
return Standard
case NumberTheoreticTransformerConjugateInvariant:
return ConjugateInvariant
default:
// Sanity check
panic(fmt.Errorf("invalid NumberTheoreticTransformer type: %T", s.ntt))
}
}
// GenerateNTTConstants generates the NTT constant for the target SubRing.
// The fields `PrimitiveRoot` and `Factors` can be set manually to
// bypass the search for the primitive root (which requires to
// factor Modulus-1) and speedup the generation of the constants.
func (s *SubRing) GenerateNTTConstants() (err error) {
if s.N == 0 || s.Modulus == 0 {
return fmt.Errorf("invalid t parameters (missing)")
}
Modulus := s.Modulus
NthRoot := s.NthRoot
// Checks if each qi is prime and equal to 1 mod NthRoot
if !IsPrime(Modulus) {
return fmt.Errorf("invalid modulus: %d is not prime)", Modulus)
}
if Modulus&(NthRoot-1) != 1 {
return fmt.Errorf("invalid modulus: %d != 1 mod NthRoot)", Modulus)
}
// It is possible to manually set the primitive root along with the factors of q-1.
// This is notably useful when marshalling the SubRing, to avoid re-factoring q-1.
// If both are set, then checks that that the root is indeed primitive.
// Else, factorize q-1 and finds a primitive root.
if s.PrimitiveRoot != 0 && s.Factors != nil {
if err = CheckPrimitiveRoot(s.PrimitiveRoot, s.Modulus, s.Factors); err != nil {
return
}
} else {
if s.PrimitiveRoot, s.Factors, err = PrimitiveRoot(Modulus, s.Factors); err != nil {
return
}
}
logNthRoot := int(bits.Len64(NthRoot>>1) - 1)
// 1.1 Computes N^(-1) mod Q in Montgomery form
s.NInv = MForm(ModExp(NthRoot>>1, Modulus-2, Modulus), Modulus, s.BRedConstant)
// 1.2 Computes Psi and PsiInv in Montgomery form
// Computes Psi and PsiInv in Montgomery form
PsiMont := MForm(ModExp(s.PrimitiveRoot, (Modulus-1)/NthRoot, Modulus), Modulus, s.BRedConstant)
PsiInvMont := MForm(ModExp(s.PrimitiveRoot, Modulus-((Modulus-1)/NthRoot)-1, Modulus), Modulus, s.BRedConstant)
s.RootsForward = make([]uint64, NthRoot>>1)
s.RootsBackward = make([]uint64, NthRoot>>1)
s.RootsForward[0] = MForm(1, Modulus, s.BRedConstant)
s.RootsBackward[0] = MForm(1, Modulus, s.BRedConstant)
// Computes nttPsi[j] = nttPsi[j-1]*Psi and RootsBackward[j] = RootsBackward[j-1]*PsiInv
for j := uint64(1); j < NthRoot>>1; j++ {
indexReversePrev := BitReverse64(j-1, logNthRoot)
indexReverseNext := BitReverse64(j, logNthRoot)
s.RootsForward[indexReverseNext] = MRed(s.RootsForward[indexReversePrev], PsiMont, Modulus, s.MRedConstant)
s.RootsBackward[indexReverseNext] = MRed(s.RootsBackward[indexReversePrev], PsiInvMont, Modulus, s.MRedConstant)
}
return
}
// PrimitiveRoot computes the smallest primitive root of the given prime q
// The unique factors of q-1 can be given to speed up the search for the root.
func PrimitiveRoot(q uint64, factors []uint64) (uint64, []uint64, error) {
if factors != nil {
if err := CheckFactors(q-1, factors); err != nil {
return 0, factors, err
}
} else {
factorsBig := GetFactors(new(big.Int).SetUint64(q - 1)) //Factor q-1, might be slow
factors = make([]uint64, len(factorsBig))
for i := range factors {
factors[i] = factorsBig[i].Uint64()
}
}
notFoundPrimitiveRoot := true
var g uint64 = 2
for notFoundPrimitiveRoot {
g++
for _, factor := range factors {
// if for any factor of q-1, g^(q-1)/factor = 1 mod q, g is not a primitive root
if ModExp(g, (q-1)/factor, q) == 1 {
notFoundPrimitiveRoot = true
break
}
notFoundPrimitiveRoot = false
}
}
return g, factors, nil
}
// CheckFactors checks that the given list of factors contains
// all the unique primes of m.
func CheckFactors(m uint64, factors []uint64) (err error) {
for _, factor := range factors {
if !IsPrime(factor) {
return fmt.Errorf("composite factor")
}
for m%factor == 0 {
m /= factor
}
}
if m != 1 {
return fmt.Errorf("incomplete factor list")
}
return
}
// CheckPrimitiveRoot checks that g is a valid primitive root mod q,
// given the factors of q-1.
func CheckPrimitiveRoot(g, q uint64, factors []uint64) (err error) {
if err = CheckFactors(q-1, factors); err != nil {
return
}
for _, factor := range factors {
if ModExp(g, (q-1)/factor, q) == 1 {
return fmt.Errorf("invalid primitive root")
}
}
return
}