feat(types): FHECiphertextHeader + FHEPrecompileArtifact

LP-137-FHE-TYPING. New package github.com/luxfi/fhe/types wrapping every
FHE buffer with the metadata required for safe dispatch:

- FHEScheme (uint32 enum): TFHE, FHEW, CKKS, BFV, BGV.
- FHECiphertextHeader (144-byte struct): ParamsHash, KeyID, CircuitID,
  Scheme, Level, N, ModulusCount, Domain, Reserved. Digest() returns
  SHA-256 of canonical 144-byte little-endian encoding; deterministic
  across runs. MatchesContext() rejects N + Domain mismatch.
- FHEPrecompileArtifact (232-byte struct): seven 32-byte digest fields
  (ParamsHash, KeyRoot, InputCiphertextRoot, OutputCiphertextRoot,
  CircuitRoot, ThresholdTranscriptRoot, AttestationRoot) + OpCount +
  FailedCount. Digest() is the contribution to fchain_fhe_root.
  IsThreshold / IsAttested flag-helpers on the *Root fields.

Layout byte-stable with the C++ mirror at luxcpp/fhe/include/lux/fhe/types.
Cross-language byte image tests verify both struct types.

14 Go tests pass.
This commit is contained in:
Hanzo AI
2026-04-27 12:47:39 -07:00
parent 5797cae572
commit d5d2f4083d
6 changed files with 702 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package types
import (
"crypto/sha256"
"encoding/binary"
)
// FHEPrecompileArtifact is the fixed-size record produced by an FHE
// precompile invocation. It is the unit of evidence consumed by the
// QuasarGPU integration layer (LP-132) and the FChainTFHE cert lane
// (LP-013, LP-020 §3.0).
//
// All *Root fields are 32-byte SHA-256 digests over the canonical encoding
// of the corresponding payload. Zero (all-zero bytes) signals "not
// applicable for this artifact" and must be accepted by the verifier as
// long as the corresponding feature was not requested.
//
// Layout is byte-stable across Go and C++. Total size: 232 bytes.
type FHEPrecompileArtifact struct {
// ParamsHash binds the artifact to a specific FHE parameter set.
// 32 bytes, offset 0.
ParamsHash [32]byte
// KeyRoot is the digest of the public/evaluation-key material used.
// 32 bytes, offset 32.
KeyRoot [32]byte
// InputCiphertextRoot is a Merkle / hash-list digest of all input
// ciphertext headers consumed by this precompile call.
// 32 bytes, offset 64.
InputCiphertextRoot [32]byte
// OutputCiphertextRoot is the digest of the output ciphertext headers
// produced by this precompile call.
// 32 bytes, offset 96.
OutputCiphertextRoot [32]byte
// CircuitRoot is the digest of the circuit / policy program executed.
// 32 bytes, offset 128.
CircuitRoot [32]byte
// ThresholdTranscriptRoot is the digest of the threshold-decryption
// transcript when this artifact is part of a threshold round.
// All-zero if not threshold.
// 32 bytes, offset 160.
ThresholdTranscriptRoot [32]byte
// AttestationRoot is the digest of the GPU attestation chain proving
// the precompile ran on attested hardware (LP-127).
// All-zero if confidential attestation was not requested.
// 32 bytes, offset 192.
AttestationRoot [32]byte
// OpCount is the number of FHE operations (gates, mults, rotations)
// charged to this artifact.
// 4 bytes, offset 224.
OpCount uint32
// FailedCount is the number of operations that failed validation
// (e.g. domain mismatch rejected at the boundary). Non-zero values
// mean the artifact is observable evidence of a misconfigured caller.
// 4 bytes, offset 228.
FailedCount uint32
}
// canonicalEncoding returns the deterministic byte representation used for
// hashing.
func (a *FHEPrecompileArtifact) canonicalEncoding() []byte {
buf := make([]byte, 232)
copy(buf[0:32], a.ParamsHash[:])
copy(buf[32:64], a.KeyRoot[:])
copy(buf[64:96], a.InputCiphertextRoot[:])
copy(buf[96:128], a.OutputCiphertextRoot[:])
copy(buf[128:160], a.CircuitRoot[:])
copy(buf[160:192], a.ThresholdTranscriptRoot[:])
copy(buf[192:224], a.AttestationRoot[:])
binary.LittleEndian.PutUint32(buf[224:228], a.OpCount)
binary.LittleEndian.PutUint32(buf[228:232], a.FailedCount)
return buf
}
// Digest returns SHA-256 of the canonical encoding. This value is the
// `fchain_fhe_root` contribution from this precompile call.
func (a *FHEPrecompileArtifact) Digest() [32]byte {
return sha256.Sum256(a.canonicalEncoding())
}
// IsThreshold reports whether this artifact participated in a threshold
// decryption round.
func (a *FHEPrecompileArtifact) IsThreshold() bool {
return a.ThresholdTranscriptRoot != [32]byte{}
}
// IsAttested reports whether this artifact was produced under hardware
// attestation.
func (a *FHEPrecompileArtifact) IsAttested() bool {
return a.AttestationRoot != [32]byte{}
}
+131
View File
@@ -0,0 +1,131 @@
// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package types
import (
"bytes"
"testing"
"unsafe"
)
func makeArtifact() FHEPrecompileArtifact {
var a FHEPrecompileArtifact
for i := range a.ParamsHash {
a.ParamsHash[i] = byte(i)
}
for i := range a.KeyRoot {
a.KeyRoot[i] = byte(i + 1)
}
for i := range a.InputCiphertextRoot {
a.InputCiphertextRoot[i] = byte(i + 2)
}
for i := range a.OutputCiphertextRoot {
a.OutputCiphertextRoot[i] = byte(i + 3)
}
for i := range a.CircuitRoot {
a.CircuitRoot[i] = byte(i + 4)
}
for i := range a.ThresholdTranscriptRoot {
a.ThresholdTranscriptRoot[i] = byte(i + 5)
}
for i := range a.AttestationRoot {
a.AttestationRoot[i] = byte(i + 6)
}
a.OpCount = 1024
a.FailedCount = 0
return a
}
func TestFHEPrecompileArtifact_DigestDeterministic(t *testing.T) {
a1 := makeArtifact()
a2 := makeArtifact()
d1 := a1.Digest()
d2 := a2.Digest()
if d1 != d2 {
t.Errorf("two equal artifacts produced different digests:\n d1=%x\n d2=%x", d1, d2)
}
}
func TestFHEPrecompileArtifact_DigestSensitiveToFields(t *testing.T) {
base := makeArtifact()
baseDigest := base.Digest()
mutate := func(name string, mut func(*FHEPrecompileArtifact)) {
t.Run(name, func(t *testing.T) {
a := makeArtifact()
mut(&a)
if got := a.Digest(); got == baseDigest {
t.Errorf("mutating %s did not change digest", name)
}
})
}
mutate("ParamsHash", func(a *FHEPrecompileArtifact) { a.ParamsHash[0] ^= 0xFF })
mutate("KeyRoot", func(a *FHEPrecompileArtifact) { a.KeyRoot[0] ^= 0xFF })
mutate("InputCiphertextRoot", func(a *FHEPrecompileArtifact) { a.InputCiphertextRoot[0] ^= 0xFF })
mutate("OutputCiphertextRoot", func(a *FHEPrecompileArtifact) { a.OutputCiphertextRoot[0] ^= 0xFF })
mutate("CircuitRoot", func(a *FHEPrecompileArtifact) { a.CircuitRoot[0] ^= 0xFF })
mutate("ThresholdTranscriptRoot", func(a *FHEPrecompileArtifact) { a.ThresholdTranscriptRoot[0] ^= 0xFF })
mutate("AttestationRoot", func(a *FHEPrecompileArtifact) { a.AttestationRoot[0] ^= 0xFF })
mutate("OpCount", func(a *FHEPrecompileArtifact) { a.OpCount++ })
mutate("FailedCount", func(a *FHEPrecompileArtifact) { a.FailedCount++ })
}
func TestFHEPrecompileArtifact_IsThresholdAndIsAttested(t *testing.T) {
var a FHEPrecompileArtifact
if a.IsThreshold() {
t.Errorf("zero artifact should not be threshold")
}
if a.IsAttested() {
t.Errorf("zero artifact should not be attested")
}
a.ThresholdTranscriptRoot[0] = 1
if !a.IsThreshold() {
t.Errorf("non-zero ThresholdTranscriptRoot should be threshold")
}
a.AttestationRoot[31] = 1
if !a.IsAttested() {
t.Errorf("non-zero AttestationRoot should be attested")
}
}
func TestFHEPrecompileArtifact_CanonicalEncoding(t *testing.T) {
a := makeArtifact()
enc := a.canonicalEncoding()
if len(enc) != 232 {
t.Errorf("canonical encoding len = %d, want 232", len(enc))
}
// determinism
enc2 := a.canonicalEncoding()
if !bytes.Equal(enc, enc2) {
t.Errorf("canonical encoding not deterministic")
}
}
func TestFHEPrecompileArtifact_Layout(t *testing.T) {
const wantSize = 232
if got := unsafe.Sizeof(FHEPrecompileArtifact{}); got != wantSize {
t.Fatalf("sizeof(FHEPrecompileArtifact) = %d, want %d", got, wantSize)
}
var a FHEPrecompileArtifact
base := uintptr(unsafe.Pointer(&a))
cases := []struct {
name string
off uintptr
want uintptr
}{
{"ParamsHash", uintptr(unsafe.Pointer(&a.ParamsHash)) - base, 0},
{"KeyRoot", uintptr(unsafe.Pointer(&a.KeyRoot)) - base, 32},
{"InputCiphertextRoot", uintptr(unsafe.Pointer(&a.InputCiphertextRoot)) - base, 64},
{"OutputCiphertextRoot", uintptr(unsafe.Pointer(&a.OutputCiphertextRoot)) - base, 96},
{"CircuitRoot", uintptr(unsafe.Pointer(&a.CircuitRoot)) - base, 128},
{"ThresholdTranscriptRoot", uintptr(unsafe.Pointer(&a.ThresholdTranscriptRoot)) - base, 160},
{"AttestationRoot", uintptr(unsafe.Pointer(&a.AttestationRoot)) - base, 192},
{"OpCount", uintptr(unsafe.Pointer(&a.OpCount)) - base, 224},
{"FailedCount", uintptr(unsafe.Pointer(&a.FailedCount)) - base, 228},
}
for _, c := range cases {
if c.off != c.want {
t.Errorf("offset of %s = %d, want %d", c.name, c.off, c.want)
}
}
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package types
import (
"crypto/sha256"
"encoding/binary"
lattice "github.com/luxfi/lattice/v7/types"
)
// FHECiphertextHeader wraps every FHE buffer with the metadata required for
// safe dispatch.
//
// Without this header, two ciphertexts produced under different parameter
// sets, evaluation keys, or domains can be combined silently and produce
// either undecryptable ciphertexts or an outright privacy break (key
// reuse across circuits).
//
// Layout is byte-stable across Go and C++. Field order MUST NOT change
// without updating the C++ mirror and matching layout tests.
//
// Total size: 144 bytes.
type FHECiphertextHeader struct {
// ParamsHash is the SHA-256 of the canonical encoding of the full
// parameter set (q, p, N, log_p, sigma, dnum, etc.).
// 32 bytes, offset 0.
ParamsHash [32]byte
// KeyID is the SHA-256 of the public/evaluation-key material that
// produced or will operate on this ciphertext.
// 32 bytes, offset 32.
KeyID [32]byte
// CircuitID is the SHA-256 of the circuit / policy program under
// which this ciphertext was produced. Used to bind ciphertexts to
// their authorised computation set.
// 32 bytes, offset 64.
CircuitID [32]byte
// Scheme identifies the FHE scheme.
// 4 bytes, offset 96.
Scheme FHEScheme
// Level is the modulus-switching level (multiplicative depth budget).
// 4 bytes, offset 100.
Level uint32
// N is the polynomial ring degree.
// 4 bytes, offset 104.
N uint32
// ModulusCount is the number of RNS moduli currently active.
// 4 bytes, offset 108.
ModulusCount uint32
// Domain is the polynomial domain the ciphertext data lives in.
// 1 byte, offset 112.
Domain lattice.PolyDomain
// _pad pads to the next 8-byte boundary so the trailing 32-byte
// fields stay aligned at offsets that are multiples of 8.
// 7 bytes, offset 113.
_pad [7]uint8
// Reserved provides 24 bytes for forward-compatible extension fields
// (e.g. attestation hashes, transcript pointers). Initialised to zero
// and ignored by Digest() unless promoted to a named field.
// 24 bytes, offset 120.
Reserved [24]byte
}
// canonicalEncoding returns the deterministic byte representation used for
// hashing. Field order matches struct layout; integers use little-endian.
func (h *FHECiphertextHeader) canonicalEncoding() []byte {
buf := make([]byte, 144)
copy(buf[0:32], h.ParamsHash[:])
copy(buf[32:64], h.KeyID[:])
copy(buf[64:96], h.CircuitID[:])
binary.LittleEndian.PutUint32(buf[96:100], uint32(h.Scheme))
binary.LittleEndian.PutUint32(buf[100:104], h.Level)
binary.LittleEndian.PutUint32(buf[104:108], h.N)
binary.LittleEndian.PutUint32(buf[108:112], h.ModulusCount)
buf[112] = uint8(h.Domain)
// bytes 113..119 are pad, fixed zero
copy(buf[120:144], h.Reserved[:])
return buf
}
// Digest returns the SHA-256 of the canonical encoding of the header.
//
// Determinism: same field values always yield the same digest, across
// processes and runs. This is the value bound into precompile artifacts and
// threshold transcripts.
func (h *FHECiphertextHeader) Digest() [32]byte {
return sha256.Sum256(h.canonicalEncoding())
}
// MatchesContext reports whether this ciphertext header is compatible with
// the given NTTContext, i.e. the polynomial degree and domain agree.
//
// Use this at the kernel boundary BEFORE dispatching a forward/inverse NTT
// or a pointwise multiply: if the ciphertext is in PolyDomainNTTMontgomery
// but the kernel's OutputDomain is PolyDomainNTTStandard, the dispatch is
// silently corrupting and must be rejected.
func (h *FHECiphertextHeader) MatchesContext(ctx *lattice.NTTContext) bool {
if ctx == nil {
return false
}
if h.N != ctx.N {
return false
}
// The header's Domain describes the *current* domain of the buffer.
// A kernel with InputDomain == h.Domain accepts this ciphertext.
return h.Domain == ctx.InputDomain
}
+194
View File
@@ -0,0 +1,194 @@
// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package types
import (
"bytes"
"testing"
"unsafe"
lattice "github.com/luxfi/lattice/v7/types"
)
func TestFHEScheme_StableValues(t *testing.T) {
cases := []struct {
s FHEScheme
want uint32
}{
{FHESchemeTFHE, 0},
{FHESchemeFHEW, 1},
{FHESchemeCKKS, 2},
{FHESchemeBFV, 3},
{FHESchemeBGV, 4},
}
for _, c := range cases {
if uint32(c.s) != c.want {
t.Errorf("FHEScheme(%s) = %d, want %d", c.s, c.s, c.want)
}
}
}
func TestFHEScheme_StableStrings(t *testing.T) {
cases := []struct {
s FHEScheme
want string
}{
{FHESchemeTFHE, "TFHE"},
{FHESchemeFHEW, "FHEW"},
{FHESchemeCKKS, "CKKS"},
{FHESchemeBFV, "BFV"},
{FHESchemeBGV, "BGV"},
{FHEScheme(0xFF), "Unknown"},
}
for _, c := range cases {
if got := c.s.String(); got != c.want {
t.Errorf("FHEScheme(%d).String() = %q, want %q", c.s, got, c.want)
}
}
}
// makeHeader returns a deterministic test header.
func makeHeader() FHECiphertextHeader {
var h FHECiphertextHeader
for i := range h.ParamsHash {
h.ParamsHash[i] = byte(i)
}
for i := range h.KeyID {
h.KeyID[i] = byte(i + 0x40)
}
for i := range h.CircuitID {
h.CircuitID[i] = byte(i + 0x80)
}
h.Scheme = FHESchemeCKKS
h.Level = 7
h.N = 1 << 14
h.ModulusCount = 8
h.Domain = lattice.PolyDomainNTTMontgomery
return h
}
// TestFHECiphertextHeader_DigestDeterministic asserts that the same field
// values always produce the same digest. Determinism is the basis for
// using the digest as a binding tag in precompile artifacts.
func TestFHECiphertextHeader_DigestDeterministic(t *testing.T) {
h1 := makeHeader()
h2 := makeHeader()
d1 := h1.Digest()
d2 := h2.Digest()
if d1 != d2 {
t.Errorf("two equal headers produced different digests:\n d1=%x\n d2=%x", d1, d2)
}
// Stability: re-digesting the same struct must give the same result.
for i := 0; i < 4; i++ {
if got := h1.Digest(); got != d1 {
t.Errorf("re-digest %d differed: %x vs %x", i, got, d1)
}
}
}
// TestFHECiphertextHeader_DigestSensitiveToFields asserts that changing
// any single field changes the digest.
func TestFHECiphertextHeader_DigestSensitiveToFields(t *testing.T) {
base := makeHeader()
baseDigest := base.Digest()
mutate := func(name string, mut func(*FHECiphertextHeader)) {
t.Run(name, func(t *testing.T) {
h := makeHeader()
mut(&h)
if got := h.Digest(); got == baseDigest {
t.Errorf("mutating %s did not change digest", name)
}
})
}
mutate("ParamsHash", func(h *FHECiphertextHeader) { h.ParamsHash[0] ^= 0xFF })
mutate("KeyID", func(h *FHECiphertextHeader) { h.KeyID[31] ^= 0xFF })
mutate("CircuitID", func(h *FHECiphertextHeader) { h.CircuitID[15] ^= 0xFF })
mutate("Scheme", func(h *FHECiphertextHeader) { h.Scheme = FHESchemeBGV })
mutate("Level", func(h *FHECiphertextHeader) { h.Level++ })
mutate("N", func(h *FHECiphertextHeader) { h.N <<= 1 })
mutate("ModulusCount", func(h *FHECiphertextHeader) { h.ModulusCount++ })
mutate("Domain", func(h *FHECiphertextHeader) { h.Domain = lattice.PolyDomainStandard })
mutate("Reserved", func(h *FHECiphertextHeader) { h.Reserved[0] = 1 })
}
func TestFHECiphertextHeader_MatchesContext(t *testing.T) {
h := makeHeader() // N=16384, Domain=NTTMontgomery
good := &lattice.NTTContext{
Modulus: 0xFFFFFFFFFFFFFFC5, N: 1 << 14,
InputDomain: lattice.PolyDomainNTTMontgomery, RootDomain: lattice.PolyDomainNTTMontgomery,
OutputDomain: lattice.PolyDomainMontgomery,
}
if !h.MatchesContext(good) {
t.Errorf("matching N+Domain should match")
}
wrongN := &lattice.NTTContext{
Modulus: 0xFFFFFFFFFFFFFFC5, N: 1 << 12,
InputDomain: lattice.PolyDomainNTTMontgomery, RootDomain: lattice.PolyDomainNTTMontgomery,
OutputDomain: lattice.PolyDomainMontgomery,
}
if h.MatchesContext(wrongN) {
t.Errorf("different N should not match")
}
wrongDomain := &lattice.NTTContext{
Modulus: 0xFFFFFFFFFFFFFFC5, N: 1 << 14,
InputDomain: lattice.PolyDomainNTTStandard, RootDomain: lattice.PolyDomainNTTStandard,
OutputDomain: lattice.PolyDomainStandard,
}
if h.MatchesContext(wrongDomain) {
t.Errorf("different Domain should not match")
}
if h.MatchesContext(nil) {
t.Errorf("nil ctx should not match")
}
}
// TestFHECiphertextHeader_CanonicalEncodingDeterministic verifies the byte
// representation used by Digest() is identical for identical inputs and
// has the expected length.
func TestFHECiphertextHeader_CanonicalEncodingDeterministic(t *testing.T) {
h1 := makeHeader()
h2 := makeHeader()
enc1 := h1.canonicalEncoding()
enc2 := h2.canonicalEncoding()
if !bytes.Equal(enc1, enc2) {
t.Fatalf("canonical encoding not deterministic\n e1=%x\n e2=%x", enc1, enc2)
}
if len(enc1) != 144 {
t.Errorf("canonical encoding len = %d, want 144", len(enc1))
}
}
// TestFHECiphertextHeader_Layout pins the byte layout for cgo compatibility.
func TestFHECiphertextHeader_Layout(t *testing.T) {
const wantSize = 144
if got := unsafe.Sizeof(FHECiphertextHeader{}); got != wantSize {
t.Fatalf("sizeof(FHECiphertextHeader) = %d, want %d", got, wantSize)
}
var h FHECiphertextHeader
base := uintptr(unsafe.Pointer(&h))
cases := []struct {
name string
off uintptr
want uintptr
}{
{"ParamsHash", uintptr(unsafe.Pointer(&h.ParamsHash)) - base, 0},
{"KeyID", uintptr(unsafe.Pointer(&h.KeyID)) - base, 32},
{"CircuitID", uintptr(unsafe.Pointer(&h.CircuitID)) - base, 64},
{"Scheme", uintptr(unsafe.Pointer(&h.Scheme)) - base, 96},
{"Level", uintptr(unsafe.Pointer(&h.Level)) - base, 100},
{"N", uintptr(unsafe.Pointer(&h.N)) - base, 104},
{"ModulusCount", uintptr(unsafe.Pointer(&h.ModulusCount)) - base, 108},
{"Domain", uintptr(unsafe.Pointer(&h.Domain)) - base, 112},
{"Reserved", uintptr(unsafe.Pointer(&h.Reserved)) - base, 120},
}
for _, c := range cases {
if c.off != c.want {
t.Errorf("offset of %s = %d, want %d", c.name, c.off, c.want)
}
}
}
+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package types
import (
"bytes"
"testing"
"unsafe"
lattice "github.com/luxfi/lattice/v7/types"
)
// TestFHECiphertextHeader_CrossLangByteImage asserts byte-identical layout
// against the C++ mirror at luxcpp/fhe/test/types/types_test.cpp
// (test_ciphertext_header_memcmp).
func TestFHECiphertextHeader_CrossLangByteImage(t *testing.T) {
var h FHECiphertextHeader
for i := range h.ParamsHash {
h.ParamsHash[i] = byte(i)
}
for i := range h.KeyID {
h.KeyID[i] = byte(i + 0x40)
}
for i := range h.CircuitID {
h.CircuitID[i] = byte(i + 0x80)
}
h.Scheme = FHESchemeCKKS
h.Level = 7
h.N = 16384
h.ModulusCount = 8
h.Domain = lattice.PolyDomainNTTMontgomery
want := make([]byte, 144)
for i := 0; i < 32; i++ {
want[i] = byte(i)
want[32+i] = byte(i + 0x40)
want[64+i] = byte(i + 0x80)
}
// scheme = CKKS (2)
want[96] = 0x02
// level = 7
want[100] = 0x07
// N = 16384 = 0x4000 little-endian
want[104] = 0x00
want[105] = 0x40
// modulus_count = 8
want[108] = 0x08
// domain = NTTMontgomery (3)
want[112] = 0x03
// 113..119 pad zero, 120..143 reserved zero (already)
got := unsafe.Slice((*byte)(unsafe.Pointer(&h)), unsafe.Sizeof(h))
if !bytes.Equal(got, want) {
t.Fatalf("FHECiphertextHeader byte image mismatch with C++ side\n got: %x\n want: %x", got, want)
}
}
// TestFHEPrecompileArtifact_CrossLangByteImage asserts byte-identical layout
// against the C++ mirror at luxcpp/fhe/test/types/types_test.cpp
// (test_artifact_memcmp).
func TestFHEPrecompileArtifact_CrossLangByteImage(t *testing.T) {
var a FHEPrecompileArtifact
for i := range a.ParamsHash {
a.ParamsHash[i] = byte(i)
}
for i := range a.KeyRoot {
a.KeyRoot[i] = byte(i + 1)
}
for i := range a.InputCiphertextRoot {
a.InputCiphertextRoot[i] = byte(i + 2)
}
for i := range a.OutputCiphertextRoot {
a.OutputCiphertextRoot[i] = byte(i + 3)
}
for i := range a.CircuitRoot {
a.CircuitRoot[i] = byte(i + 4)
}
for i := range a.ThresholdTranscriptRoot {
a.ThresholdTranscriptRoot[i] = byte(i + 5)
}
for i := range a.AttestationRoot {
a.AttestationRoot[i] = byte(i + 6)
}
a.OpCount = 1024
a.FailedCount = 0
want := make([]byte, 232)
for i := 0; i < 32; i++ {
want[i] = byte(i)
want[32+i] = byte(i + 1)
want[64+i] = byte(i + 2)
want[96+i] = byte(i + 3)
want[128+i] = byte(i + 4)
want[160+i] = byte(i + 5)
want[192+i] = byte(i + 6)
}
// op_count = 1024 = 0x00000400 LE
want[224] = 0x00
want[225] = 0x04
got := unsafe.Slice((*byte)(unsafe.Pointer(&a)), unsafe.Sizeof(a))
if !bytes.Equal(got, want) {
t.Fatalf("FHEPrecompileArtifact byte image mismatch with C++ side\n got: %x\n want: %x", got, want)
}
}
+53
View File
@@ -0,0 +1,53 @@
// Package types defines the FHE-GPU ciphertext header and precompile artifact
// types. These wrap every FHE buffer with the minimal metadata required to
// reject domain mismatches, parameter mismatches, and key/circuit drift at
// the kernel boundary.
//
// Reference: LP-137-FHE-TYPING.md.
//
// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package types
// FHEScheme identifies which FHE scheme produced or consumes a ciphertext.
//
// Wire stable: enum values are fixed and MUST NOT be reordered. Add new
// schemes by appending.
type FHEScheme uint32
const (
// FHESchemeTFHE is the TFHE/FHEW boolean-circuit family with blind
// rotation bootstrap, as implemented by luxfi/fhe over luxfi/lattice.
FHESchemeTFHE FHEScheme = 0
// FHESchemeFHEW is the FHEW variant (kept distinct from TFHE for
// parameter-set hashing even though the kernel path can be shared).
FHESchemeFHEW FHEScheme = 1
// FHESchemeCKKS is approximate-arithmetic CKKS (real/complex).
FHESchemeCKKS FHEScheme = 2
// FHESchemeBFV is exact-arithmetic BFV (integer).
FHESchemeBFV FHEScheme = 3
// FHESchemeBGV is exact-arithmetic BGV (integer).
FHESchemeBGV FHEScheme = 4
)
// String returns a stable, human-readable name for the scheme.
func (s FHEScheme) String() string {
switch s {
case FHESchemeTFHE:
return "TFHE"
case FHESchemeFHEW:
return "FHEW"
case FHESchemeCKKS:
return "CKKS"
case FHESchemeBFV:
return "BFV"
case FHESchemeBGV:
return "BGV"
default:
return "Unknown"
}
}