diff --git a/go.mod b/go.mod index d1c595d..414c8a6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,8 @@ require ( github.com/google/uuid v1.6.0 github.com/hanzoai/base v0.39.1 github.com/luxfi/database v1.17.44 - github.com/luxfi/lattice/v7 v7.0.0 + github.com/luxfi/lattice/v7 v7.1.0 + github.com/luxfi/math v1.3.0 github.com/luxfi/mdns v0.1.0 github.com/luxfi/metric v1.5.0 github.com/spf13/cobra v1.10.2 @@ -55,7 +56,6 @@ require ( github.com/luxfi/crypto v1.17.45 // indirect github.com/luxfi/ids v1.2.9 // indirect github.com/luxfi/log v1.4.1 // indirect - github.com/luxfi/math v1.2.3 // indirect github.com/luxfi/math/big v0.1.0 // indirect github.com/luxfi/mock v0.1.1 // indirect github.com/luxfi/zapdb/v4 v4.9.3 // indirect @@ -79,7 +79,7 @@ require ( go.temporal.io/api v1.62.6 // indirect go.temporal.io/sdk v1.41.1 // indirect go.uber.org/mock v0.6.0 // indirect - golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect golang.org/x/image v0.38.0 // indirect golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.52.0 // indirect diff --git a/go.sum b/go.sum index 83f42f4..622e825 100644 --- a/go.sum +++ b/go.sum @@ -115,12 +115,12 @@ github.com/luxfi/geth v1.16.69 h1:CHO6xTZ+A+3itk94ts4uyVRJajNVP3RxWTjJp5qGOlk= github.com/luxfi/geth v1.16.69/go.mod h1:8eEO1hW5sa6OH2VeCMaCPnRz28JBxFvCPBCWPLsU2ck= github.com/luxfi/ids v1.2.9 h1:+yjdhXW99drnd2Zlp1u/p8k3G23W3/1btJQ4ogHawUI= github.com/luxfi/ids v1.2.9/go.mod h1:khJOEdOPxd22yn0jcVrnbX1ADa0GHn5Y74gvCzN5BYc= -github.com/luxfi/lattice/v7 v7.0.0 h1:d1vgan6mlb2KtwYfPc1g69uxVYaoVYZmlrIm4aCO88w= -github.com/luxfi/lattice/v7 v7.0.0/go.mod h1:PFDdOkuGTQ0cbJMbKojzEJMGWUQmZW+wK9/wJ9F9fOs= +github.com/luxfi/lattice/v7 v7.1.0 h1:mr3HvN6olNTS2LT/xAW/JBhTqfvpsGmsopDMeR7BSJs= +github.com/luxfi/lattice/v7 v7.1.0/go.mod h1:IaaUN+3ysnBG4BA8ILRYG0j80+qtYDP4C5lkaDb2pDE= github.com/luxfi/log v1.4.1 h1:rIfFRodb9jrD/w7KayaUk0Oc+37PaQQdKEEMJCjR8gw= github.com/luxfi/log v1.4.1/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72jk= -github.com/luxfi/math v1.2.3 h1:BgvIFw/srPXFLbcqtoDhLJOfmBsn86GPA1iWgsoyUb4= -github.com/luxfi/math v1.2.3/go.mod h1:C8STnF2H+D6rqBPt248CiWY2TGuJgdtv/+4UqrT15iM= +github.com/luxfi/math v1.3.0 h1:KfHzmsVs4Xfcc3G8PaXotzHk0vkRpUdAna1/RXa9caM= +github.com/luxfi/math v1.3.0/go.mod h1:VoHe/+0yRPNS0ouzMhCPh5ky+myqjVrrBq4qEXFBSGs= github.com/luxfi/math/big v0.1.0 h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ= github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk= github.com/luxfi/mdns v0.1.0 h1:VB3mQcETc9j5SY1S6lAgFtuGr/rjWuDgPYnxS+OKWMQ= @@ -204,8 +204,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= -golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= diff --git a/wire/wire.go b/wire/wire.go new file mode 100644 index 0000000..a4a2270 --- /dev/null +++ b/wire/wire.go @@ -0,0 +1,51 @@ +// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package wire is fhe's wire-format hardening boundary. +// +// LP-107 Phase 5: fhe consumes luxfi/math/codec for bounded +// decoding of FHE ciphertext / key / param-pack frames. FHE wire +// formats nest more deeply than pulsar's lattice frames (a CKKS +// ciphertext is a Vec over an RNS chain of Vec; an +// EvaluationKey is a tuple of those), so the bounded depth + size +// caps in luxfi/math/codec are essential. +package wire + +import ( + "bytes" + "fmt" + + "github.com/luxfi/math/codec" +) + +// MaxFHESliceLen — FHE caps. PN11QP54 ring is 2048 coeffs/poly with +// up to ~10 levels in the RNS chain; 4096 is generous headroom. +const MaxFHESliceLen = 4096 + +// FHEWireLimits is the codec.Limits configuration FHE uses for +// every untrusted-input frame. +var FHEWireLimits = codec.Limits{ + MaxFrameBytes: 32 * 1024 * 1024, // 32 MiB — accommodates EvaluationKey + MaxUint16SliceLen: MaxFHESliceLen, + MaxUint32SliceLen: MaxFHESliceLen, + MaxUint64SliceLen: MaxFHESliceLen, + MaxDepth: 5, // Vec + Poly + RNS-Vec + scheme + chain +} + +// ValidateCiphertextFrame walks the outer length prefix of an FHE +// ciphertext wire frame. Returns an error wrapping codec.ErrLimitExceeded +// when the cap is exceeded. +// +// The bounded reader rejects the lattice issue #4 attack input class +// (huge varint length) before any allocation, sharing the substrate's +// hardening with pulsar and lens. +func ValidateCiphertextFrame(frame []byte) error { + r, err := codec.NewReader(bytes.NewReader(frame), FHEWireLimits) + if err != nil { + return fmt.Errorf("fhe/wire: NewReader: %w", err) + } + if _, err := r.ReadUint64Slice(); err != nil { + return fmt.Errorf("fhe/wire: outer length: %w", err) + } + return nil +} diff --git a/wire/wire_test.go b/wire/wire_test.go new file mode 100644 index 0000000..9c49851 --- /dev/null +++ b/wire/wire_test.go @@ -0,0 +1,42 @@ +// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package wire + +import ( + "bytes" + "errors" + "testing" + + "github.com/luxfi/math/codec" +) + +func encodeUvarint(out *bytes.Buffer, v uint64) { + for v >= 0x80 { + out.WriteByte(byte(v) | 0x80) + v >>= 7 + } + out.WriteByte(byte(v)) +} + +func TestValidateCiphertextFrame_RejectsHugeLength(t *testing.T) { + const huge = uint64(70_368_955_777_453) + var buf bytes.Buffer + encodeUvarint(&buf, huge) + err := ValidateCiphertextFrame(buf.Bytes()) + if err == nil { + t.Fatal("ValidateCiphertextFrame returned nil for huge length") + } + if !errors.Is(err, codec.ErrLimitExceeded) { + t.Errorf("err is not ErrLimitExceeded: %v", err) + } +} + +func TestValidateCiphertextFrame_OverCap(t *testing.T) { + var buf bytes.Buffer + encodeUvarint(&buf, uint64(MaxFHESliceLen+1)) + err := ValidateCiphertextFrame(buf.Bytes()) + if !errors.Is(err, codec.ErrLimitExceeded) { + t.Errorf("err is not ErrLimitExceeded: %v", err) + } +}