mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
Merge pull request #5 from luxfi/fix/makefile-fmt-quoting
fix(ci): unblock make checks — quoting, gofmt, lint:ignore, no-stub policy
This commit is contained in:
@@ -10,11 +10,11 @@ checks: check_tools
|
||||
@echo Checking correct formatting of files
|
||||
|
||||
@FMTOUT=$$(go fmt ./...); \
|
||||
if [ -z $$FMTOUT ]; then\
|
||||
if [ -z "$$FMTOUT" ]; then\
|
||||
echo "go fmt: OK";\
|
||||
else \
|
||||
echo "go fmt: problems in files:";\
|
||||
echo $$FMTOUT;\
|
||||
echo "$$FMTOUT";\
|
||||
false;\
|
||||
fi
|
||||
|
||||
|
||||
+7
-7
@@ -42,12 +42,12 @@ type NTTContext struct {
|
||||
// returns a SubRing without those tables.
|
||||
func NewNTTContext(N uint32, Q uint64) (*NTTContext, error) {
|
||||
if N == 0 || (N&(N-1)) != 0 {
|
||||
return nil, fmt.Errorf("N must be a power of 2, got %d", N)
|
||||
return nil, fmt.Errorf("ring degree N must be a power of 2, got %d", N)
|
||||
}
|
||||
|
||||
// Check if Q is NTT-friendly (Q-1 divisible by 2N)
|
||||
if (Q-1)%(2*uint64(N)) != 0 {
|
||||
return nil, fmt.Errorf("Q-1 (%d) must be divisible by 2N (%d) for NTT-friendly prime", Q-1, 2*uint64(N))
|
||||
return nil, fmt.Errorf("modulus Q-1 (%d) must be divisible by 2N (%d) for NTT-friendly prime", Q-1, 2*uint64(N))
|
||||
}
|
||||
|
||||
r, err := ring.NewRing(int(N), []uint64{Q})
|
||||
@@ -271,9 +271,9 @@ func SampleGaussian(N uint32, Q uint64, sigma float64, seed []byte) ([]uint64, e
|
||||
var sample int64
|
||||
for {
|
||||
b := make([]byte, 8)
|
||||
prng.Read(b)
|
||||
_, _ = prng.Read(b)
|
||||
sample = int64(b[0]) | int64(b[1])<<8 | int64(b[2])<<16 | int64(b[3])<<24
|
||||
sample = sample % (2*bound + 1) - bound
|
||||
sample = sample%(2*bound+1) - bound
|
||||
if sample >= -bound && sample <= bound {
|
||||
break
|
||||
}
|
||||
@@ -282,7 +282,7 @@ func SampleGaussian(N uint32, Q uint64, sigma float64, seed []byte) ([]uint64, e
|
||||
// Accept with Gaussian probability
|
||||
prob := math.Exp(-float64(sample*sample) / (2 * sigma * sigma))
|
||||
threshold := make([]byte, 8)
|
||||
prng.Read(threshold)
|
||||
_, _ = prng.Read(threshold)
|
||||
if float64(threshold[0])/256.0 < prob {
|
||||
if sample >= 0 {
|
||||
result[i] = uint64(sample)
|
||||
@@ -314,7 +314,7 @@ func SampleUniform(N uint32, Q uint64, seed []byte) ([]uint64, error) {
|
||||
for i := uint32(0); i < N; i++ {
|
||||
// Sample uniform in [0, Q)
|
||||
b := make([]byte, 8)
|
||||
prng.Read(b)
|
||||
_, _ = prng.Read(b)
|
||||
val := new(big.Int).SetBytes(b)
|
||||
val.Mod(val, qBig)
|
||||
result[i] = val.Uint64()
|
||||
@@ -338,7 +338,7 @@ func SampleTernary(N uint32, Q uint64, density float64, seed []byte) ([]uint64,
|
||||
|
||||
for i := uint32(0); i < N; i++ {
|
||||
b := make([]byte, 2)
|
||||
reader.Read(b)
|
||||
_, _ = reader.Read(b)
|
||||
|
||||
// Probability of non-zero
|
||||
p := float64(b[0]) / 256.0
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
//go:build !cgo || !gpu
|
||||
|
||||
// Copyright (c) 2024-2025 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package gpu — pure-Go Montgomery NTT dispatch path.
|
||||
//
|
||||
// Default build (no cgo, or no `-tags gpu`) routes Forward/Backward
|
||||
// through the canonical pure-Go Montgomery NTT in
|
||||
// github.com/luxfi/lattice/v7/ring (SubRing.NTT / INTT). There is one
|
||||
// and only one Go implementation of the Montgomery NTT in the Lux
|
||||
// stack — this file does not re-implement it, only delegates.
|
||||
//
|
||||
// Output is byte-equal to the cgo+gpu path (which itself is byte-equal
|
||||
// to the pure-Go reference by contract). Callers see identical
|
||||
// semantics regardless of build tags; cgo + gpu is purely an opt-in
|
||||
// optimization, never a correctness boundary.
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
)
|
||||
|
||||
// MontgomeryNTTContext binds a luxfi/lattice/v7/ring.SubRing to the
|
||||
// pure-Go Montgomery NTT path. Forward and Backward delegate directly
|
||||
// to ring.SubRing.NTT and ring.SubRing.INTT — the canonical Go
|
||||
// implementation. There is no re-implementation here; this struct is
|
||||
// a thin dispatch wrapper that mirrors gpu_montgomery_cgo.go's API so
|
||||
// callers compile identically across build tags.
|
||||
type MontgomeryNTTContext struct {
|
||||
subring *ring.SubRing
|
||||
N uint32
|
||||
Q uint64
|
||||
mu sync.RWMutex
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
// NewMontgomeryNTTContext binds to a SubRing. The SubRing's existing
|
||||
// NTT tables are the source of truth — we hold a pointer, never a copy.
|
||||
func NewMontgomeryNTTContext(s *ring.SubRing) (*MontgomeryNTTContext, error) {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("nil SubRing")
|
||||
}
|
||||
if s.NTTTable == nil {
|
||||
return nil, fmt.Errorf("SubRing.NTTTable is nil -- call ring.NewRing first")
|
||||
}
|
||||
if s.N <= 0 {
|
||||
return nil, fmt.Errorf("invalid SubRing.N=%d", s.N)
|
||||
}
|
||||
if len(s.RootsForward) < s.N || len(s.RootsBackward) < s.N {
|
||||
return nil, fmt.Errorf(
|
||||
"SubRing roots length mismatch: have RootsForward=%d RootsBackward=%d, need >= N=%d",
|
||||
len(s.RootsForward), len(s.RootsBackward), s.N)
|
||||
}
|
||||
// s.N is positive (validated above) and bounded by ring degree
|
||||
// (16384 max in production); the int -> uint32 conversion is safe.
|
||||
/* #nosec G115 -- s.N is validated above to be > 0, bounded by max ring degree */
|
||||
N := uint32(s.N)
|
||||
return &MontgomeryNTTContext{
|
||||
subring: s,
|
||||
N: N,
|
||||
Q: s.Modulus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close releases the context. The pure-Go path holds no external
|
||||
// resources; Close is idempotent and exists only to mirror the cgo API.
|
||||
func (ctx *MontgomeryNTTContext) Close() {
|
||||
ctx.closed.Store(true)
|
||||
}
|
||||
|
||||
// Forward applies the NTT in-place via ring.SubRing.NTT. data must
|
||||
// have length batch*N. For batch > 1, processes each polynomial
|
||||
// sequentially.
|
||||
func (ctx *MontgomeryNTTContext) Forward(data []uint64, batch uint32) error {
|
||||
ctx.mu.RLock()
|
||||
defer ctx.mu.RUnlock()
|
||||
if ctx.closed.Load() {
|
||||
return fmt.Errorf("context closed")
|
||||
}
|
||||
N := int(ctx.N)
|
||||
if int(batch)*N > len(data) {
|
||||
return fmt.Errorf("buffer too small: need %d, got %d",
|
||||
int(batch)*N, len(data))
|
||||
}
|
||||
for i := uint32(0); i < batch; i++ {
|
||||
off := int(i) * N
|
||||
ctx.subring.NTT(data[off:off+N], data[off:off+N])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Backward applies the inverse NTT in-place via ring.SubRing.INTT.
|
||||
func (ctx *MontgomeryNTTContext) Backward(data []uint64, batch uint32) error {
|
||||
ctx.mu.RLock()
|
||||
defer ctx.mu.RUnlock()
|
||||
if ctx.closed.Load() {
|
||||
return fmt.Errorf("context closed")
|
||||
}
|
||||
N := int(ctx.N)
|
||||
if int(batch)*N > len(data) {
|
||||
return fmt.Errorf("buffer too small: need %d, got %d",
|
||||
int(batch)*N, len(data))
|
||||
}
|
||||
for i := uint32(0); i < batch; i++ {
|
||||
off := int(i) * N
|
||||
ctx.subring.INTT(data[off:off+N], data[off:off+N])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SubRing dispatch — pure-Go path
|
||||
// =============================================================================
|
||||
//
|
||||
// nttThreshold mirrors the cgo build's signature so callers can probe
|
||||
// and override consistently across build tags. In the pure-Go build
|
||||
// the value is honored only by Forward/Backward through an explicit
|
||||
// MontgomeryNTTContext; the ring core's SubRing.NTT runs on its own
|
||||
// pure-Go path without consulting this dispatcher.
|
||||
|
||||
var nttThreshold atomic.Uint32
|
||||
|
||||
// SetNTTThreshold stores the threshold.
|
||||
func SetNTTThreshold(n uint32) { nttThreshold.Store(n) }
|
||||
|
||||
// NTTThreshold returns the current threshold.
|
||||
func NTTThreshold() uint32 { return nttThreshold.Load() }
|
||||
|
||||
// Available reports whether a hardware-accelerated NTT path is
|
||||
// reachable. False in the pure-Go build: SubRing.NTT is the canonical
|
||||
// path and the ring core invokes it directly. Returning false here is
|
||||
// the contract by which NTTSubRingForward / NTTSubRingBackward signal
|
||||
// "use the default path" to the ring dispatcher.
|
||||
func Available() bool { return false }
|
||||
|
||||
// SubRing -> Montgomery context registry. Mirrors the cgo build's
|
||||
// surface so callers compile identically across builds.
|
||||
var (
|
||||
subRingRegistryMu sync.RWMutex
|
||||
subRingRegistry = make(map[*ring.SubRing]*MontgomeryNTTContext)
|
||||
)
|
||||
|
||||
// RegisterSubRing binds a Montgomery context to a SubRing. Subsequent
|
||||
// Forward/Backward calls delegate to the canonical pure-Go NTT in
|
||||
// ring.SubRing.NTT / INTT.
|
||||
func RegisterSubRing(s *ring.SubRing) (*MontgomeryNTTContext, error) {
|
||||
subRingRegistryMu.Lock()
|
||||
defer subRingRegistryMu.Unlock()
|
||||
if existing, ok := subRingRegistry[s]; ok && existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
ctx, err := NewMontgomeryNTTContext(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subRingRegistry[s] = ctx
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// UnregisterSubRing removes the binding.
|
||||
func UnregisterSubRing(s *ring.SubRing) {
|
||||
subRingRegistryMu.Lock()
|
||||
defer subRingRegistryMu.Unlock()
|
||||
if ctx, ok := subRingRegistry[s]; ok {
|
||||
ctx.Close()
|
||||
delete(subRingRegistry, s)
|
||||
}
|
||||
}
|
||||
|
||||
// LookupSubRing returns the bound context, or nil if none.
|
||||
func LookupSubRing(s *ring.SubRing) *MontgomeryNTTContext {
|
||||
subRingRegistryMu.RLock()
|
||||
defer subRingRegistryMu.RUnlock()
|
||||
return subRingRegistry[s]
|
||||
}
|
||||
|
||||
// NTTSubRingForward is the dispatcher hook consulted by ring.SubRing.
|
||||
// In the pure-Go build it always returns false: SubRing.NTT is the
|
||||
// canonical pure-Go path and the ring core runs it without our
|
||||
// involvement. Returning false signals "use the default path."
|
||||
func NTTSubRingForward(_ *ring.SubRing, _, _ []uint64) bool { return false }
|
||||
|
||||
// NTTSubRingBackward is the inverse dispatcher hook. Same contract as
|
||||
// NTTSubRingForward.
|
||||
func NTTSubRingBackward(_ *ring.SubRing, _, _ []uint64) bool { return false }
|
||||
@@ -1,69 +0,0 @@
|
||||
//go:build !cgo || !gpu
|
||||
|
||||
// Pure-Go stub for the Montgomery GPU dispatch path. Without cgo + gpu,
|
||||
// the GPU is unavailable; the public API matches gpu_montgomery_cgo.go
|
||||
// so callers compile identically and the fast-path always reports
|
||||
// "no GPU".
|
||||
//
|
||||
// Copyright (c) 2024-2025 Lux Industries Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package gpu
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/luxfi/lattice/v7/ring"
|
||||
)
|
||||
|
||||
// MontgomeryNTTContext is a no-op type when GPU is unavailable.
|
||||
type MontgomeryNTTContext struct{}
|
||||
|
||||
// NewMontgomeryNTTContext is a no-op stub.
|
||||
func NewMontgomeryNTTContext(_ *ring.SubRing) (*MontgomeryNTTContext, error) {
|
||||
return nil, errors.New("GPU unavailable: build with cgo + -tags gpu")
|
||||
}
|
||||
|
||||
// Close is a no-op.
|
||||
func (c *MontgomeryNTTContext) Close() {}
|
||||
|
||||
// Forward is a no-op stub.
|
||||
func (c *MontgomeryNTTContext) Forward(_ []uint64, _ uint32) error {
|
||||
return errors.New("GPU unavailable")
|
||||
}
|
||||
|
||||
// Backward is a no-op stub.
|
||||
func (c *MontgomeryNTTContext) Backward(_ []uint64, _ uint32) error {
|
||||
return errors.New("GPU unavailable")
|
||||
}
|
||||
|
||||
// nttThreshold is unused in the stub but kept to mirror the cgo path's
|
||||
// API surface so callers do not need build-tagged code paths.
|
||||
var nttThreshold atomic.Uint32
|
||||
|
||||
// SetNTTThreshold is a no-op when GPU is unavailable.
|
||||
func SetNTTThreshold(n uint32) { nttThreshold.Store(n) }
|
||||
|
||||
// NTTThreshold returns 0 when GPU is unavailable.
|
||||
func NTTThreshold() uint32 { return 0 }
|
||||
|
||||
// Available reports whether the GPU NTT path is reachable. Always false
|
||||
// in this build.
|
||||
func Available() bool { return false }
|
||||
|
||||
// RegisterSubRing is a no-op stub.
|
||||
func RegisterSubRing(_ *ring.SubRing) (*MontgomeryNTTContext, error) {
|
||||
return nil, errors.New("GPU unavailable")
|
||||
}
|
||||
|
||||
// UnregisterSubRing is a no-op.
|
||||
func UnregisterSubRing(_ *ring.SubRing) {}
|
||||
|
||||
// LookupSubRing always returns nil.
|
||||
func LookupSubRing(_ *ring.SubRing) *MontgomeryNTTContext { return nil }
|
||||
|
||||
// NTTSubRingForward always returns false in the stub.
|
||||
func NTTSubRingForward(_ *ring.SubRing, _, _ []uint64) bool { return false }
|
||||
|
||||
// NTTSubRingBackward always returns false in the stub.
|
||||
func NTTSubRingBackward(_ *ring.SubRing, _, _ []uint64) bool { return false }
|
||||
+3
-3
@@ -27,8 +27,8 @@ func TestIsNTTPrime(t *testing.T) {
|
||||
name string
|
||||
}{
|
||||
{256, 0x1000000004A01, true, "Corona Q"},
|
||||
{256, 0x40201, true, "Corona QXi"}, // 262657 - prime, ≡ 1 (mod 512)
|
||||
{256, 0x7FE01, true, "Corona QNu"}, // 523777 - prime, ≡ 1 (mod 512)
|
||||
{256, 0x40201, true, "Corona QXi"}, // 262657 - prime, ≡ 1 (mod 512)
|
||||
{256, 0x7FE01, true, "Corona QNu"}, // 523777 - prime, ≡ 1 (mod 512)
|
||||
{256, 0x40000, false, "Non-prime 2^18"}, // 262144 - NOT prime
|
||||
{256, 0x80000, false, "Non-prime 2^19"}, // 524288 - NOT prime
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func TestNTTRoundTrip(t *testing.T) {
|
||||
// Create a test polynomial
|
||||
poly := make([]uint64, TestN)
|
||||
for i := range poly {
|
||||
poly[i] = uint64(i + 1) % TestQ
|
||||
poly[i] = uint64(i+1) % TestQ
|
||||
}
|
||||
|
||||
// Save original
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//go:build !(goexperiment.simd && amd64)
|
||||
|
||||
package ring
|
||||
|
||||
// SIMD dispatch hooks for non-AVX2-amd64 builds.
|
||||
//
|
||||
// On builds without GOEXPERIMENT=simd or off-amd64, the AVX2 NTT path
|
||||
// is unavailable. These hooks return false so that nttCoreLazy /
|
||||
// inttCoreLazy (ntt.go:217, ntt.go:567) falls through to the
|
||||
// canonical pure-Go scalar NTT (nttLazy / nttUnrolled16Lazy /
|
||||
// inttLazy / inttUnrolled16Lazy in this same package).
|
||||
//
|
||||
// The pure-Go scalar NTT is the canonical implementation; SIMD is a
|
||||
// strict speed-up on top of it, never a correctness boundary. The
|
||||
// `_purego` suffix matches the rest of the build-tag pattern used in
|
||||
// gpu/gpu_montgomery_purego.go.
|
||||
|
||||
// nttCoreLazyAccel returns false on non-SIMD builds; the caller runs
|
||||
// the canonical pure-Go scalar NTT path.
|
||||
func nttCoreLazyAccel(p1, p2 []uint64, N int, Q, MRedConstant uint64, roots []uint64) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// inttCoreLazyAccel returns false on non-SIMD builds; the caller runs
|
||||
// the canonical pure-Go scalar INTT path.
|
||||
func inttCoreLazyAccel(p1, p2 []uint64, N int, Q, MRedConstant uint64, roots []uint64) bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
//go:build !(goexperiment.simd && amd64)
|
||||
|
||||
package ring
|
||||
|
||||
// nttCoreLazyAccel is the stub for non-SIMD builds.
|
||||
// Returns false to indicate no acceleration was applied.
|
||||
func nttCoreLazyAccel(p1, p2 []uint64, N int, Q, MRedConstant uint64, roots []uint64) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// inttCoreLazyAccel is the stub for non-SIMD builds.
|
||||
// Returns false to indicate no acceleration was applied.
|
||||
func inttCoreLazyAccel(p1, p2 []uint64, N int, Q, MRedConstant uint64, roots []uint64) bool {
|
||||
return false
|
||||
}
|
||||
+1
-1
@@ -159,7 +159,7 @@ func SafeBoundFor(mode ReductionMode, modulus uint64) uint32 {
|
||||
// conservative so that intermediate sums of k operands each in [0, k*q)
|
||||
// stay strictly below 2^64.
|
||||
func lazyModeFits(mode ReductionMode, modulus uint64) bool {
|
||||
bitlen := uint(bits.Len64(modulus))
|
||||
bitlen := bits.Len64(modulus) // returns int in [0, 64]
|
||||
switch mode {
|
||||
case ReductionStrictEveryOp:
|
||||
return bitlen <= 64
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestNewReductionBudget_RejectsTooLargeModulus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReductionBudget_ChargeAndReset(t *testing.T) {
|
||||
rb, err := NewReductionBudget(ReductionLazy4, (1 << 30) - 35)
|
||||
rb, err := NewReductionBudget(ReductionLazy4, (1<<30)-35)
|
||||
if err != nil {
|
||||
t.Fatalf("NewReductionBudget: %v", err)
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ const (
|
||||
TrustAttestedGpuOnly ComputeTrustMode = 1
|
||||
|
||||
// TrustCpuGpuCompositeTEE: CPU TEE + GPU TEE jointly attested.
|
||||
//lint:ignore ST1003 Public API; the "Cpu"/"Gpu" mixed-case spelling matches the wire-format String() return value documented at line 80.
|
||||
TrustCpuGpuCompositeTEE ComputeTrustMode = 2
|
||||
|
||||
// TrustConfidentialIO: protected IO path where the platform supports
|
||||
@@ -122,13 +123,16 @@ const (
|
||||
IOLevelNone ConfidentialIOLevel = 0
|
||||
|
||||
// IOLevelCpuTeeOnly: CPU TEE only.
|
||||
//lint:ignore ST1003 Public API; mixed-case spelling matches the wire-format String() return value documented above.
|
||||
IOLevelCpuTeeOnly ConfidentialIOLevel = 1
|
||||
|
||||
// IOLevelCpuGpuComposite: CPU TEE + GPU TEE attested, device buffers
|
||||
// protected.
|
||||
//lint:ignore ST1003 Public API; mixed-case spelling matches the wire-format String() return value documented above.
|
||||
IOLevelCpuGpuComposite ConfidentialIOLevel = 2
|
||||
|
||||
// IOLevelProtectedCpuGpuTransfer: encrypted CPU<->GPU PCIe transfer.
|
||||
//lint:ignore ST1003 Public API; mixed-case spelling matches the wire-format String() return value documented above.
|
||||
IOLevelProtectedCpuGpuTransfer ConfidentialIOLevel = 3
|
||||
|
||||
// IOLevelFullDeviceIOAttested: full attested device IO path
|
||||
|
||||
+3
-3
@@ -229,9 +229,9 @@ func TestTrustEnum_CrossLangByteImage(t *testing.T) {
|
||||
}
|
||||
|
||||
tt := trustTriple{
|
||||
Trust: TrustConfidentialIO, // 3
|
||||
IO: IOLevelProtectedCpuGpuTransfer, // 3
|
||||
Privacy: PrivacyValidatorKeyMaterial, // 3
|
||||
Trust: TrustConfidentialIO, // 3
|
||||
IO: IOLevelProtectedCpuGpuTransfer, // 3
|
||||
Privacy: PrivacyValidatorKeyMaterial, // 3
|
||||
_pad: 0,
|
||||
}
|
||||
want := []byte{0x03, 0x03, 0x03, 0x00}
|
||||
|
||||
Reference in New Issue
Block a user