mirror of
https://github.com/luxfi/math.git
synced 2026-07-27 03:38:49 +00:00
205 lines
5.8 KiB
Go
205 lines
5.8 KiB
Go
// Copyright (c) 2026 Lux Industries Inc.
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
//
|
|
// emit_sample_kat — produces the canonical cross-runtime KAT bundle
|
|
// for luxfi/math/sample. The C++ side at luxcpp/crypto/math/test/
|
|
// sample_cross_runtime_test.cpp reads the same JSON, drives a byte-
|
|
// buffer reader with the recorded entropy, and asserts byte-equal
|
|
// sample output.
|
|
//
|
|
// LP-107 Phase 6.5: Go emits → C++ verifies. Cross-runtime release gate
|
|
// for primitive distribution samplers.
|
|
//
|
|
// Usage:
|
|
//
|
|
// go run ./cmd/emit_sample_kat --out testdata/sample_kat.json
|
|
//
|
|
// Each entry contains:
|
|
//
|
|
// Test: "Uniform/q=PulsarQ/i=N" -> param: q
|
|
// "Ternary/q=PulsarQ/density=0.50/i=N" -> param: q, density
|
|
// "CenteredBinomial/q=PulsarQ/eta=2/i=N" -> param: q, eta
|
|
// InputHex: raw entropy bytes (4096) from a SHA-256 hash chain
|
|
// OutputHex: first 64 samples, packed little-endian (512 bytes)
|
|
//
|
|
// Entropy bytes are deterministic from the test name + index seed so
|
|
// emit runs are byte-stable.
|
|
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
|
|
"github.com/luxfi/math/codec"
|
|
"github.com/luxfi/math/params"
|
|
"github.com/luxfi/math/sample"
|
|
)
|
|
|
|
const (
|
|
PulsarQ uint64 = 0x1000000004A01
|
|
NTT998 uint64 = 998244353
|
|
|
|
NumSamples = 64
|
|
EntropyBytes = 4096
|
|
)
|
|
|
|
func main() {
|
|
out := flag.String("out", "sample_kat.json", "output JSON path")
|
|
flag.Parse()
|
|
|
|
header := params.KATHeader{
|
|
ParameterSet: "math.sample.v1",
|
|
ModulusID: params.ModPulsarQ,
|
|
BackendID: params.BackendPureGo,
|
|
HashSuiteID: params.HashBLAKE3,
|
|
ImplementationName: "luxfi/math/sample",
|
|
ImplementationVersion: "v1.3.0",
|
|
}
|
|
|
|
entries := []codec.KATEntry{}
|
|
|
|
// 10 Uniform/PulsarQ entries.
|
|
for i := 0; i < 10; i++ {
|
|
seed := fmt.Sprintf("sample/Uniform/PulsarQ/i=%d", i)
|
|
entropy := genEntropy(seed, EntropyBytes)
|
|
dst := make([]uint64, NumSamples)
|
|
if err := sample.Uniform(dst, PulsarQ, byteReader(entropy)); err != nil {
|
|
log.Fatalf("Uniform[%d]: %v", i, err)
|
|
}
|
|
entries = append(entries, mkEntry(header,
|
|
fmt.Sprintf("Uniform/q=PulsarQ/i=%d", i),
|
|
entropy, packU64s(dst)))
|
|
}
|
|
|
|
// 10 Uniform/NTT998 entries (smaller modulus, different mask).
|
|
for i := 0; i < 10; i++ {
|
|
seed := fmt.Sprintf("sample/Uniform/NTT998/i=%d", i)
|
|
entropy := genEntropy(seed, EntropyBytes)
|
|
dst := make([]uint64, NumSamples)
|
|
if err := sample.Uniform(dst, NTT998, byteReader(entropy)); err != nil {
|
|
log.Fatalf("Uniform/NTT998[%d]: %v", i, err)
|
|
}
|
|
entries = append(entries, mkEntry(header,
|
|
fmt.Sprintf("Uniform/q=NTT998/i=%d", i),
|
|
entropy, packU64s(dst)))
|
|
}
|
|
|
|
// 5 Ternary entries at density=0.5.
|
|
for i := 0; i < 5; i++ {
|
|
seed := fmt.Sprintf("sample/Ternary/d=0.50/i=%d", i)
|
|
entropy := genEntropy(seed, EntropyBytes)
|
|
dst := make([]uint64, NumSamples)
|
|
if err := sample.Ternary(dst, PulsarQ, 0.5, byteReader(entropy)); err != nil {
|
|
log.Fatalf("Ternary[%d]: %v", i, err)
|
|
}
|
|
entries = append(entries, mkEntry(header,
|
|
fmt.Sprintf("Ternary/q=PulsarQ/density=0.50/i=%d", i),
|
|
entropy, packU64s(dst)))
|
|
}
|
|
|
|
// 3 Ternary entries at density=0.25.
|
|
for i := 0; i < 3; i++ {
|
|
seed := fmt.Sprintf("sample/Ternary/d=0.25/i=%d", i)
|
|
entropy := genEntropy(seed, EntropyBytes)
|
|
dst := make([]uint64, NumSamples)
|
|
if err := sample.Ternary(dst, PulsarQ, 0.25, byteReader(entropy)); err != nil {
|
|
log.Fatalf("Ternary/0.25[%d]: %v", i, err)
|
|
}
|
|
entries = append(entries, mkEntry(header,
|
|
fmt.Sprintf("Ternary/q=PulsarQ/density=0.25/i=%d", i),
|
|
entropy, packU64s(dst)))
|
|
}
|
|
|
|
// 5 CenteredBinomial entries at eta=2.
|
|
for i := 0; i < 5; i++ {
|
|
seed := fmt.Sprintf("sample/CBD/eta=2/i=%d", i)
|
|
entropy := genEntropy(seed, EntropyBytes)
|
|
dst := make([]uint64, NumSamples)
|
|
if err := sample.CenteredBinomial(dst, PulsarQ, 2, byteReader(entropy)); err != nil {
|
|
log.Fatalf("CBD[%d]: %v", i, err)
|
|
}
|
|
entries = append(entries, mkEntry(header,
|
|
fmt.Sprintf("CenteredBinomial/q=PulsarQ/eta=2/i=%d", i),
|
|
entropy, packU64s(dst)))
|
|
}
|
|
|
|
// 3 CenteredBinomial entries at eta=4.
|
|
for i := 0; i < 3; i++ {
|
|
seed := fmt.Sprintf("sample/CBD/eta=4/i=%d", i)
|
|
entropy := genEntropy(seed, EntropyBytes)
|
|
dst := make([]uint64, NumSamples)
|
|
if err := sample.CenteredBinomial(dst, PulsarQ, 4, byteReader(entropy)); err != nil {
|
|
log.Fatalf("CBD/eta=4[%d]: %v", i, err)
|
|
}
|
|
entries = append(entries, mkEntry(header,
|
|
fmt.Sprintf("CenteredBinomial/q=PulsarQ/eta=4/i=%d", i),
|
|
entropy, packU64s(dst)))
|
|
}
|
|
|
|
bundle := &codec.KATBundle{
|
|
Schema: codec.KATSchemaV1,
|
|
Entries: entries,
|
|
}
|
|
if err := codec.WriteKATBundleFile(*out, bundle); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Printf("wrote %d entries to %s\n", len(entries), *out)
|
|
}
|
|
|
|
// genEntropy returns n bytes from a SHA-256 hash chain seeded by s.
|
|
func genEntropy(s string, n int) []byte {
|
|
out := make([]byte, 0, n)
|
|
var counter uint64
|
|
for len(out) < n {
|
|
h := sha256.New()
|
|
h.Write([]byte(s))
|
|
var ctr [8]byte
|
|
binary.LittleEndian.PutUint64(ctr[:], counter)
|
|
h.Write(ctr[:])
|
|
out = append(out, h.Sum(nil)...)
|
|
counter++
|
|
}
|
|
return out[:n]
|
|
}
|
|
|
|
// byteReader returns a stateful io.Reader over the supplied bytes.
|
|
// Each Read advances the internal offset.
|
|
func byteReader(buf []byte) io.Reader { return &bbReader{buf: buf} }
|
|
|
|
type bbReader struct {
|
|
buf []byte
|
|
off int
|
|
}
|
|
|
|
func (r *bbReader) Read(p []byte) (int, error) {
|
|
if r.off >= len(r.buf) {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(p, r.buf[r.off:])
|
|
r.off += n
|
|
return n, nil
|
|
}
|
|
|
|
func packU64s(vs []uint64) []byte {
|
|
out := make([]byte, 8*len(vs))
|
|
for i, v := range vs {
|
|
binary.LittleEndian.PutUint64(out[i*8:], v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func mkEntry(h params.KATHeader, name string, input, output []byte) codec.KATEntry {
|
|
digest := sha256.Sum256(output)
|
|
return codec.KATEntry{
|
|
Header: h,
|
|
Test: name,
|
|
InputHex: codec.HexEncode(input),
|
|
OutputHex: codec.HexEncode(output),
|
|
OutputSHA256: codec.HexEncode(digest[:]),
|
|
}
|
|
}
|