Files
base/crdt/privacy_fhe.go

109 lines
3.7 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build fhe
// fheCRDT backend — ciphertexts that can be merged server-side.
//
// Behind the `fhe` build tag so the default binary has no TFHE
// dependency:
//
// go build -tags fhe ./...
//
// Status: research-grade. Only LWW-Register is implemented; the
// pipeline delivers <1 merge/sec and 136 KB per LWW ciphertext at
// production parameters. See audits/2026-04-12-fhecrdt-audit.pdf
// and docs/CRDT-PRIVACY-MODELS.md.
//
// Use age for production. Use fhe only where a third party must
// merge ciphertexts without holding the key — sealed auctions,
// cross-tenant aggregation — and the workload budget is measured
// in minutes per operation.
//
// Delegates to luxfi/fhe for TFHE key material and homomorphic
// gate circuits.
package crdt
import (
"encoding/json"
"fmt"
"github.com/luxfi/fhe"
)
// FHEPrivacy is the homomorphic-merge backend.
type FHEPrivacy struct {
ctx *fhe.Context // holds pk + eval keys
sk *fhe.SecretKey
}
// NewFHEPrivacy returns a backend bound to an existing FHE context.
// The secret key is required for EncryptOp and DecryptOp; a relay that
// only ever calls HomomorphicMerge can be constructed by passing a nil
// sk and a context initialised with only evaluation keys.
func NewFHEPrivacy(ctx *fhe.Context, sk *fhe.SecretKey) (*FHEPrivacy, error) {
if ctx == nil {
return nil, fmt.Errorf("crdt/privacy_fhe: context must not be nil")
}
return &FHEPrivacy{ctx: ctx, sk: sk}, nil
}
// Name returns the backend's wire tag.
func (*FHEPrivacy) Name() string { return "fhe/tfhe-v1" }
// SupportsHomomorphicMerge is true — that's the entire point.
func (*FHEPrivacy) SupportsHomomorphicMerge() bool { return true }
// EncryptOp serialises the op, encrypts each numeric field under TFHE,
// and wraps the result as an opaque blob.
//
// Scalar fields (counters, timestamps, LWW registers) are encrypted
// individually so HomomorphicMerge can touch them without decoding
// the full blob. Non-numeric fields (strings, blobs) are stored in
// an age-like seal alongside the homomorphic ciphertexts.
func (p *FHEPrivacy) EncryptOp(op Operation) ([]byte, error) {
if p.sk == nil {
return nil, fmt.Errorf("crdt/privacy_fhe: encrypt requires secret key")
}
payload, err := json.Marshal(op)
if err != nil {
return nil, fmt.Errorf("crdt/privacy_fhe: marshal op: %w", err)
}
// See audit doc and fhe/examples/encrypted-crdt/ for the field-wise
// encryption scheme. This scaffolding holds the signature so callers
// compile today; the full op-schema-aware encoder lands with the
// scientist sweep deliverables.
return p.ctx.EncryptBytes(p.sk, payload)
}
// DecryptOp reverses EncryptOp. Requires the secret key.
func (p *FHEPrivacy) DecryptOp(blob []byte) (Operation, error) {
var op Operation
if p.sk == nil {
return op, fmt.Errorf("crdt/privacy_fhe: decrypt requires secret key")
}
payload, err := p.ctx.DecryptBytes(p.sk, blob)
if err != nil {
return op, fmt.Errorf("crdt/privacy_fhe: decrypt: %w", err)
}
if err := json.Unmarshal(payload, &op); err != nil {
return op, fmt.Errorf("crdt/privacy_fhe: unmarshal op: %w", err)
}
return op, nil
}
// HomomorphicMerge folds two encrypted op blobs into one, without
// decryption. The merge semantics match the underlying CRDT's
// join-semilattice operation: max for counters, LWW compare for
// registers, union for sets.
//
// A relay only needs `p.ctx` (evaluation keys); it should be built
// with `sk = nil`. Secret keys stay on the replicas.
func (p *FHEPrivacy) HomomorphicMerge(a, b []byte) ([]byte, error) {
return p.ctx.Merge(a, b)
}
// Compile-time check.
var _ Privacy = (*FHEPrivacy)(nil)