Documentation
¶
Overview ¶
Package codec is the bounded-decode contract for every wire format in luxfi/math (and downstream luxfi/lattice, luxfi/pulsar, luxfi/fhe).
LP-107 §"Codec and bounded reader should be centralized" — the canonical motivation. The lattigo `ReadUint64Slice` recursion bug (issue #2) and `Vector[T].ReadFrom` OOM bug (issue #4) both stemmed from unbounded slice decode on untrusted wire data; this package fixes that class permanently.
Contract:
- No recursion. Slice readers are iterative; depth is bounded by the configured Limits.
- No hidden growth. Every `make([]T, n)` is preceded by a `n <= cap` check against caller-supplied Limits.
- No unbounded allocation. The largest cap is application-supplied and surfaces in error messages.
- All readers are deterministic and reentrant; failure leaves the reader at the byte where the bound was exceeded.
Index ¶
- Constants
- Variables
- func HexDecode(s string) ([]byte, error)
- func HexEncode(b []byte) string
- func MakeUvarintFrame(length uint64, payload []uint64) []byte
- func WriteKATBundle(w io.Writer, b *KATBundle) error
- func WriteKATBundleFile(path string, b *KATBundle) error
- type KATBundle
- type KATEntry
- type LimitError
- type Limits
- type Reader
- func (r *Reader) Consumed() int
- func (r *Reader) EnterDepth() error
- func (r *Reader) ExitDepth()
- func (r *Reader) ReadUint16() (uint16, error)
- func (r *Reader) ReadUint16Slice() ([]uint16, error)
- func (r *Reader) ReadUint32() (uint32, error)
- func (r *Reader) ReadUint32Slice() ([]uint32, error)
- func (r *Reader) ReadUint64() (uint64, error)
- func (r *Reader) ReadUint64Slice() ([]uint64, error)
Constants ¶
const KATSchemaV1 = "lux.math.kat.v1"
Variables ¶
var DefaultLimitsLatticeWire = Limits{
MaxFrameBytes: 16 * 1024 * 1024,
MaxUint16SliceLen: 4096,
MaxUint32SliceLen: 4096,
MaxUint64SliceLen: 4096,
MaxDepth: 4,
}
DefaultLimitsLatticeWire is the conservative default for wire-format decoding of lattice polynomials at the canonical Pulsar parameters (R_q = Z_q[X]/(X^256 + 1), Q ≈ 2^48). Callers SHOULD use a configuration tuned to their parameter set rather than this default.
MaxUint64SliceLen = 4096 matches Pulsar Vector[Poly] cap.
MaxFrameBytes = 16 MiB allows a worst-case threshold ceremony
transcript without truncation.
MaxDepth = 4 Pulsar wire is 2 levels (Vector + Poly);
4 leaves headroom for FHE chains.
var ErrLimitExceeded = errors.New("codec: limit exceeded")
ErrLimitExceeded is the sentinel for any limit-bound rejection. errors.Is(err, ErrLimitExceeded) holds for every cap violation.
Functions ¶
func MakeUvarintFrame ¶
MakeUvarintFrame returns the wire-format byte stream for a length- prefixed slice of uint64 with the supplied length. Used by KAT emitters to construct the canonical input bytes that ReadUint64Slice consumes.
func WriteKATBundle ¶
WriteKATBundle serializes the bundle to a writer in canonical JSON (sorted keys, indent=2). Two runs produce byte-equal output when the entries are byte-equal.
func WriteKATBundleFile ¶
WriteKATBundleFile writes the bundle to a file at path.
Types ¶
type KATBundle ¶
type KATBundle struct {
Schema string `json:"schema"` // "lux.math.kat.v1"
Entries []KATEntry `json:"entries"`
}
KATBundle is a collection of entries written to a JSON file at a stable path. C++ replay tests load the same file by path.
func ReadKATBundle ¶
ReadKATBundle deserializes a bundle from a reader.
func ReadKATBundleFile ¶
ReadKATBundleFile reads a bundle from a file at path.
type KATEntry ¶
type KATEntry struct {
// Header carries the canonical (parameter, backend, hash-suite,
// implementation, version) tuple every KAT must surface.
Header params.KATHeader `json:"header"`
// Test is the human-readable test name (e.g. "ReadUint64Slice/
// happy-path-3-elements", "MontMul/q=PulsarQ/100-random-pairs").
Test string `json:"test"`
// InputHex is the hex-encoded input byte stream consumed by the
// substrate primitive under test.
InputHex string `json:"input_hex"`
// OutputHex is the hex-encoded canonical output byte stream.
OutputHex string `json:"output_hex"`
// OutputSHA256 is the SHA-256 commitment over OutputHex's raw
// bytes (NOT the hex string). Used as a fast cross-runtime
// equality check; full byte-stream is in OutputHex for diffing
// on mismatch.
OutputSHA256 string `json:"output_sha256"`
}
KATEntry is one cross-runtime KAT vector. Each entry pins a specific (test, parameter set, backend, input) tuple and records the SHA-256 (or BLAKE2b) digest of the byte-stream the substrate produces for that input.
Cross-runtime contract: emitting `KATEntry.Output` from Go and replaying it from the C++ side (luxcpp/crypto/math/test) MUST produce a byte-equal stream. Any divergence is a release-gate failure.
type LimitError ¶
type LimitError struct {
What string // human-readable name of the cap, e.g. "MaxUint64SliceLen"
Limit int
Got uint64
}
LimitError carries the specific limit that was exceeded plus the observed value. Wraps ErrLimitExceeded.
type Limits ¶
type Limits struct {
// MaxFrameBytes caps the total number of input bytes the Reader
// will consume in a single decode call. Used to bound peek-ahead
// buffers. 0 means unset and is treated as an error.
MaxFrameBytes int
// MaxUint16SliceLen caps the number of elements in a uint16 slice.
MaxUint16SliceLen int
// MaxUint32SliceLen caps the number of elements in a uint32 slice.
MaxUint32SliceLen int
// MaxUint64SliceLen caps the number of elements in a uint64 slice.
MaxUint64SliceLen int
// MaxDepth caps how deeply a recursively-shaped wire format may
// nest before the reader rejects (Vector[Poly] etc. are 2 levels).
MaxDepth int
}
Limits caps the largest slice / depth a Reader will accept on a single decode call. Callers MUST construct Limits explicitly; there is no implicit default.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader wraps an io.Reader and a Limits config. Every slice-reading method on Reader is bounded by Limits.
func NewReader ¶
NewReader constructs a Reader from an io.Reader and a Limits config. Returns an error if Limits is invalid.
func (*Reader) EnterDepth ¶
EnterDepth bumps the nesting counter and returns an error if the configured MaxDepth is exceeded. Caller MUST pair with ExitDepth.
func (*Reader) ReadUint16 ¶
ReadUint16 reads a single little-endian uint16.
func (*Reader) ReadUint16Slice ¶
ReadUint16Slice reads a length-prefixed slice of little-endian uint16. The length is read as a varint capped by MaxUint16SliceLen; iterative (no recursion).
func (*Reader) ReadUint32 ¶
ReadUint32 reads a single little-endian uint32.
func (*Reader) ReadUint32Slice ¶
ReadUint32Slice reads a length-prefixed slice of little-endian uint32.
func (*Reader) ReadUint64 ¶
ReadUint64 reads a single little-endian uint64.
func (*Reader) ReadUint64Slice ¶
ReadUint64Slice reads a length-prefixed slice of little-endian uint64. Bounded by MaxUint64SliceLen. The length-prefix is always read as a varint; values > MaxUint64SliceLen are rejected before any allocation.
This is the centralized fix for both lattigo issue #2 (recursive `ReadUint64Slice`) and issue #4 (`Vector[T].ReadFrom` unbounded allocation). Callers consuming untrusted lattice wire data MUST go through this method, never lattigo's raw `utils/buffer.ReadUint64Slice`.