mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
The CLI printed a fixed name in its examples and help text while already taking that name as a parameter, so `operator` and `rpc` now interpolate what they were handed, the way `cluster` already did. The config file's per-env URL block goes with it. Nothing read it: EnvURLs computes a service address from the environment's domain suffix, which is what every command actually calls, so the block was a second unasked answer to where a service lives. An older config still loads — an unknown key is ignored — and its env block is dropped on the next write. The default org is no longer a name. The Tasks queue is named for this repo rather than written out three times. Fixture hostnames follow the shape the operator emits, which one of those files was already using. crdt/privacy_fhe.go cited a PDF that is not in the tree; the two figures it backed are in the design doc it already points at. Co-authored-by: Hanzo Dev <[email protected]>
108 lines
3.6 KiB
Go
108 lines
3.6 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. Measurements in 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-base 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)
|