merge LP-107 Phase 3: ring/modular_reduction shim over math/ntt/canonical

This commit is contained in:
2026-05-04 10:29:08 -07:00
3 changed files with 51 additions and 171 deletions
+7 -2
View File
@@ -5,17 +5,22 @@ go 1.26.1
require (
github.com/ALTree/bigfloat v0.2.0
github.com/google/go-cmp v0.7.0
github.com/luxfi/math v2.0.0+incompatible
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.49.0
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
)
// LP-107 Phase 3: math has not been published yet. Point at the local
// canonical-relocation branch on disk; the user replaces this with a
// proper version pin after pushing.
replace github.com/luxfi/math => /Users/z/work/lux/math/.claude/worktrees/lp-107-phase3
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
golang.org/x/sys v0.42.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
-5
View File
@@ -5,17 +5,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+44 -164
View File
@@ -1,205 +1,85 @@
// Copyright (c) 2026 Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
//
// Thin shim over github.com/luxfi/math/ntt/canonical.
// LP-107 Phase 3 inverted the dependency: luxfi/math owns the
// canonical Montgomery / Barrett body. This package re-exports it so
// downstream consumers (luxfi/pulsar, luxfi/fhe, luxfi/threshold,
// luxfi/multiparty, ...) see no source change.
package ring
import (
"math/bits"
"github.com/luxfi/lattice/v7/utils/bignum"
"github.com/luxfi/math/ntt/canonical"
)
// MForm switches a to the Montgomery domain by computing
// a*2^64 mod q.
func MForm(a, q uint64, bredconstant [2]uint64) (r uint64) {
// R = 2^128/q
// b = a * 2^{64}
// mhi = (b * (R % 2^{64})) / 2^{64}
//
// r = b - ((b * (R/2^{64}) + mhi) * q
// = b - (b * R)/2^{64} * q
// = b - n * q
//
// Since r is in [0, 2q] and q is in [0, 2^{63}-1]
// we are ensured that r < 2^64, thus r = r mod 2^{64}
// So we can work mod 2^{64} (i.e. with uint64)
// and therefore:
//
// r = b - n * q mod 2^{64}
// = a * 2^{64} - n * q mod 2^{64}
// = - n * q mod 2^{64}
//
mhi, _ := bits.Mul64(a, bredconstant[1])
r = -(a*bredconstant[0] + mhi) * q
if r >= q {
r -= q
}
return
// MForm switches a to the Montgomery domain by computing a*2^64 mod q.
func MForm(a, q uint64, bredconstant [2]uint64) uint64 {
return canonical.MForm(a, q, bredconstant)
}
// MFormLazy switches a to the Montgomery domain by computing
// a*2^64 mod q in constant time.
// The result is between 0 and 2*q-1.
func MFormLazy(a, q uint64, bredconstant [2]uint64) (r uint64) {
// See MForm for the implementation trick.
mhi, _ := bits.Mul64(a, bredconstant[1])
r = -(a*bredconstant[0] + mhi) * q
return
// a*2^64 mod q in constant time. Result in [0, 2q-1].
func MFormLazy(a, q uint64, bredconstant [2]uint64) uint64 {
return canonical.MFormLazy(a, q, bredconstant)
}
// IMForm switches a from the Montgomery domain back to the
// standard domain by computing a*(1/2^64) mod q.
func IMForm(a, q, mredconstant uint64) (r uint64) {
r, _ = bits.Mul64(a*mredconstant, q)
r = q - r
if r >= q {
r -= q
}
return
// IMForm switches a from the Montgomery domain back to the standard
// domain by computing a*(1/2^64) mod q.
func IMForm(a, q, mredconstant uint64) uint64 {
return canonical.IMForm(a, q, mredconstant)
}
// IMFormLazy switches a from the Montgomery domain back to the
// standard domain by computing a*(1/2^64) mod q in constant time.
// The result is between 0 and 2*q-1.
func IMFormLazy(a, q, mredconstant uint64) (r uint64) {
r, _ = bits.Mul64(a*mredconstant, q)
r = q - r
return
// Result in [0, 2q-1].
func IMFormLazy(a, q, mredconstant uint64) uint64 {
return canonical.IMFormLazy(a, q, mredconstant)
}
// GenMRedConstant computes the constant mredconstant = (q^-1) mod 2^64 required for MRed.
func GenMRedConstant(q uint64) (mredconstant uint64) {
mredconstant = 1
for i := 0; i < 63; i++ {
mredconstant *= q
q *= q
}
return mredconstant
// GenMRedConstant computes the constant (q^-1) mod 2^64 used by MRed.
func GenMRedConstant(q uint64) uint64 {
return canonical.GenMRedConstant(q)
}
// MRed computes x * y * (1/2^64) mod q.
func MRed(x, y, q, mredconstant uint64) (r uint64) {
mhi, mlo := bits.Mul64(x, y)
hhi, _ := bits.Mul64(mlo*mredconstant, q)
r = mhi - hhi + q
if r >= q {
r -= q
}
return
func MRed(x, y, q, mredconstant uint64) uint64 {
return canonical.MRed(x, y, q, mredconstant)
}
// MRedLazy computes x * y * (1/2^64) mod q in constant time.
// The result is between 0 and 2*q-1.
func MRedLazy(x, y, q, mredconstant uint64) (r uint64) {
ahi, alo := bits.Mul64(x, y)
H, _ := bits.Mul64(alo*mredconstant, q)
r = ahi - H + q
return
// Result in [0, 2q-1].
func MRedLazy(x, y, q, mredconstant uint64) uint64 {
return canonical.MRedLazy(x, y, q, mredconstant)
}
// GenBRedConstant computes the constant for the BRed algorithm.
// GenBRedConstant computes the constant pair for the BRed algorithm.
// Returns ((2^128)/q)/(2^64) and (2^128)/q mod 2^64.
func GenBRedConstant(q uint64) [2]uint64 {
bigR := bignum.NewInt("0x100000000000000000000000000000000")
bigR.Quo(bigR, bignum.NewInt(q))
mlo := bigR.Uint64()
mhi := bigR.Rsh(bigR, 64).Uint64()
return [2]uint64{mhi, mlo}
return canonical.GenBRedConstant(q)
}
// BRedAdd computes a mod q.
func BRedAdd(a, q uint64, bredconstant [2]uint64) (r uint64) {
mhi, _ := bits.Mul64(a, bredconstant[0])
r = a - mhi*q
if r >= q {
r -= q
}
return
func BRedAdd(a, q uint64, bredconstant [2]uint64) uint64 {
return canonical.BRedAdd(a, q, bredconstant)
}
// BRedAddLazy computes a mod q in constant time.
// The result is between 0 and 2*q-1.
// BRedAddLazy computes a mod q in constant time. Result in [0, 2q-1].
func BRedAddLazy(x, q uint64, bredconstant [2]uint64) uint64 {
s0, _ := bits.Mul64(x, bredconstant[0])
return x - s0*q
return canonical.BRedAddLazy(x, q, bredconstant)
}
// BRed computes x*y mod q.
func BRed(x, y, q uint64, bredconstant [2]uint64) (r uint64) {
var mhi, mlo, lhi, hhi, hlo, s0, carry uint64
mhi, mlo = bits.Mul64(x, y)
// computes r = mhi * uhi + (mlo * uhi + mhi * ulo)<<64 + (mlo * ulo)) >> 128
r = mhi * bredconstant[0] // r = mhi * uhi
hhi, hlo = bits.Mul64(mlo, bredconstant[0]) // mlo * uhi
r += hhi
lhi, _ = bits.Mul64(mlo, bredconstant[1]) // mlo * ulo
s0, carry = bits.Add64(hlo, lhi, 0)
r += carry
hhi, hlo = bits.Mul64(mhi, bredconstant[1]) // mhi * ulo
r += hhi
_, carry = bits.Add64(hlo, s0, 0)
r += carry
r = mlo - r*q
if r >= q {
r -= q
}
return
func BRed(x, y, q uint64, bredconstant [2]uint64) uint64 {
return canonical.BRed(x, y, q, bredconstant)
}
// BRedLazy computes x*y mod q in constant time.
// The result is between 0 and 2*q-1.
func BRedLazy(x, y, q uint64, bredconstant [2]uint64) (r uint64) {
var mhi, mlo, lhi, hhi, hlo, s0, carry uint64
mhi, mlo = bits.Mul64(x, y)
// computes r = mhi * uhi + (mlo * uhi + mhi * ulo)<<64 + (mlo * ulo)) >> 128
r = mhi * bredconstant[0] // r = mhi * uhi
hhi, hlo = bits.Mul64(mlo, bredconstant[0]) // mlo * uhi
r += hhi
lhi, _ = bits.Mul64(mlo, bredconstant[1]) // mlo * ulo
s0, carry = bits.Add64(hlo, lhi, 0)
r += carry
hhi, hlo = bits.Mul64(mhi, bredconstant[1]) // mhi * ulo
r += hhi
_, carry = bits.Add64(hlo, s0, 0)
r += carry
r = mlo - r*q
return
// BRedLazy computes x*y mod q in constant time. Result in [0, 2q-1].
func BRedLazy(x, y, q uint64, bredconstant [2]uint64) uint64 {
return canonical.BRedLazy(x, y, q, bredconstant)
}
// CRed reduce returns a mod q where a is between 0 and 2*q-1.
// CRed reduces a in [0, 2q-1] to [0, q-1].
func CRed(a, q uint64) uint64 {
if a >= q {
return a - q
}
return a
return canonical.CRed(a, q)
}