feat: add FHE example applications for sealing, voting, media auth, provenance, CRDTs, and statistics

Six production-ready example applications demonstrating the FHE library:
- cmd/seal: document integrity sealing with encrypted verification
- cmd/vote: encrypted voting system with threshold tallying
- cmd/mediaseal: media content authentication with FHE
- cmd/provenance: AI model provenance tracking with encrypted attestation
- cmd/crdt: encrypted LWW-Register for fheCRDT architectures (LP-6500)
- cmd/stats: secure multiparty statistics over encrypted data

Also pins luxfi/mdns to v0.1.0 and excludes compiled binaries from git.
This commit is contained in:
Zach Kelling
2026-02-13 12:44:18 -08:00
parent 148b20c2c7
commit b6f42849c1
8 changed files with 1033 additions and 1 deletions
+8
View File
@@ -82,3 +82,11 @@ forge-cache/
.git-backup/
python-sdk
ml-sdk
# Compiled example binaries
/crdt
/mediaseal
/provenance
/seal
/stats
/vote
+183
View File
@@ -0,0 +1,183 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command crdt demonstrates an encrypted Last-Writer-Wins Register (LWW-Register)
// using FHE -- a core building block for fheCRDT architectures (LP-6500).
//
// Two nodes each write an encrypted (value, timestamp) pair. The merge
// operation compares timestamps homomorphically and selects the latest
// value using MUX gates, all without decrypting individual entries.
// Only the final merged state is decrypted.
//
// Usage:
//
// go run ./cmd/crdt
// go run ./cmd/crdt -bitsval 4 -bitsts 4
package main
import (
"flag"
"fmt"
"os"
"time"
"github.com/luxfi/fhe"
)
// encryptedRegister holds an encrypted LWW-Register entry.
type encryptedRegister struct {
value []*fhe.Ciphertext // encrypted value bits (LSB-first)
ts []*fhe.Ciphertext // encrypted timestamp bits (LSB-first)
}
func main() {
bitsVal := flag.Int("bitsval", 4, "bits for value (1..8)")
bitsTS := flag.Int("bitsts", 4, "bits for timestamp (1..8)")
flag.Parse()
if *bitsVal < 1 || *bitsVal > 8 || *bitsTS < 1 || *bitsTS > 8 {
fmt.Fprintln(os.Stderr, "error: bit widths must be 1..8")
os.Exit(1)
}
// Simulate two concurrent writes.
nodeAVal, nodeATS := uint8(7), uint8(3) // Node A writes 7 at time 3
nodeBVal, nodeBTS := uint8(12), uint8(5) // Node B writes 12 at time 5 (later)
fmt.Println("=== Encrypted LWW-Register CRDT ===")
fmt.Printf("Node A: value=%d, timestamp=%d\n", nodeAVal, nodeATS)
fmt.Printf("Node B: value=%d, timestamp=%d\n", nodeBVal, nodeBTS)
fmt.Printf("Expected winner: Node B (later timestamp)\n\n")
// Setup FHE.
fmt.Println("Initialising FHE...")
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
// Encrypt both entries.
regA := encryptEntry(enc, nodeAVal, nodeATS, *bitsVal, *bitsTS)
regB := encryptEntry(enc, nodeBVal, nodeBTS, *bitsVal, *bitsTS)
// Merge: compare timestamps, select latest.
fmt.Println("Merging homomorphically (comparing timestamps, selecting value)...")
t0 := time.Now()
merged, err := merge(eval, regA, regB, *bitsVal, *bitsTS)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
elapsed := time.Since(t0)
// Decrypt merged result.
mergedVal := decryptUint(dec, merged.value)
mergedTS := decryptUint(dec, merged.ts)
fmt.Printf("\n--- Merged State ---\n")
fmt.Printf("Value: %d\n", mergedVal)
fmt.Printf("Timestamp: %d\n", mergedTS)
fmt.Printf("Elapsed: %v\n", elapsed)
if mergedVal == uint8(nodeBVal) && mergedTS == uint8(nodeBTS) {
fmt.Println("PASS: merge selected the latest write (Node B).")
} else {
fmt.Println("FAIL: unexpected merge result!")
}
}
func encryptEntry(enc *fhe.Encryptor, val, ts uint8, bitsVal, bitsTS int) *encryptedRegister {
return &encryptedRegister{
value: encryptUint(enc, val, bitsVal),
ts: encryptUint(enc, ts, bitsTS),
}
}
func encryptUint(enc *fhe.Encryptor, v uint8, nbits int) []*fhe.Ciphertext {
cts := make([]*fhe.Ciphertext, nbits)
for i := 0; i < nbits; i++ {
cts[i] = enc.Encrypt((v>>i)&1 == 1)
}
return cts
}
func decryptUint(dec *fhe.Decryptor, cts []*fhe.Ciphertext) uint8 {
var v uint8
for i, ct := range cts {
if dec.Decrypt(ct) {
v |= 1 << i
}
}
return v
}
// merge performs LWW-Register merge: compare timestamps MSB-to-LSB,
// then MUX-select the winning entry's value and timestamp.
func merge(eval *fhe.Evaluator, a, b *encryptedRegister, bitsVal, bitsTS int) (*encryptedRegister, error) {
// Compare timestamps: is B > A? (MSB-first scan)
// bGtA starts as false, eqSoFar starts as true.
bGtA := eval.NOT(a.ts[0]) // dummy init, overwritten below
var eqSoFar *fhe.Ciphertext
// We build bGtA from MSB down.
for i := bitsTS - 1; i >= 0; i-- {
// bitGt = B[i] AND NOT(A[i]) -- B's bit is 1, A's is 0
bitGt, err := eval.ANDNY(a.ts[i], b.ts[i])
if err != nil {
return nil, fmt.Errorf("ts bit %d ANDNY: %w", i, err)
}
// bitEq = XNOR(A[i], B[i])
bitEq, err := eval.XNOR(a.ts[i], b.ts[i])
if err != nil {
return nil, fmt.Errorf("ts bit %d XNOR: %w", i, err)
}
if i == bitsTS-1 {
bGtA = bitGt
eqSoFar = bitEq
} else {
// bGtA = bGtA OR (eqSoFar AND bitGt)
contrib, err := eval.AND(eqSoFar, bitGt)
if err != nil {
return nil, fmt.Errorf("ts bit %d AND: %w", i, err)
}
bGtA, err = eval.OR(bGtA, contrib)
if err != nil {
return nil, fmt.Errorf("ts bit %d OR: %w", i, err)
}
// eqSoFar = eqSoFar AND bitEq
eqSoFar, err = eval.AND(eqSoFar, bitEq)
if err != nil {
return nil, fmt.Errorf("ts bit %d eq-chain: %w", i, err)
}
}
}
// MUX-select value and timestamp: if bGtA then B else A.
mergedVal := make([]*fhe.Ciphertext, bitsVal)
for i := 0; i < bitsVal; i++ {
v, err := eval.MUX(bGtA, b.value[i], a.value[i])
if err != nil {
return nil, fmt.Errorf("val MUX bit %d: %w", i, err)
}
mergedVal[i] = v
}
mergedTS := make([]*fhe.Ciphertext, bitsTS)
for i := 0; i < bitsTS; i++ {
t, err := eval.MUX(bGtA, b.ts[i], a.ts[i])
if err != nil {
return nil, fmt.Errorf("ts MUX bit %d: %w", i, err)
}
mergedTS[i] = t
}
return &encryptedRegister{value: mergedVal, ts: mergedTS}, nil
}
+168
View File
@@ -0,0 +1,168 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command mediaseal demonstrates media content authentication using FHE.
//
// A media file (image, video, audio) is hashed and the hash bits are
// encrypted under FHE, creating a "media seal" with provenance metadata.
// Verification checks a file against the seal homomorphically.
// Tamper detection: if even one byte is changed, verification fails.
//
// Usage:
//
// go run ./cmd/mediaseal -file photo.jpg -creator "Alice" -device "iPhone"
// go run ./cmd/mediaseal -file photo.jpg -creator "Alice" -device "iPhone" -verify photo.jpg
// go run ./cmd/mediaseal -file photo.jpg -creator "Alice" -device "iPhone" -verify tampered.jpg
package main
import (
"crypto/sha256"
"flag"
"fmt"
"io"
"os"
"time"
"github.com/luxfi/fhe"
)
func main() {
filePath := flag.String("file", "", "path to original media file")
creator := flag.String("creator", "unknown", "creator name")
device := flag.String("device", "unknown", "capture device")
verifyPath := flag.String("verify", "", "path to file to verify against seal")
bits := flag.Int("bits", 8, "hash bits to seal (1..256)")
flag.Parse()
if *filePath == "" {
fmt.Fprintln(os.Stderr, "error: -file is required")
flag.Usage()
os.Exit(1)
}
if *bits < 1 || *bits > 256 {
fmt.Fprintln(os.Stderr, "error: -bits must be 1..256")
os.Exit(1)
}
// Hash original.
origHash, err := hashFile(*filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
ts := time.Now().UTC().Format(time.RFC3339)
fmt.Println("=== Media Seal ===")
fmt.Printf(" File: %s\n", *filePath)
fmt.Printf(" Creator: %s\n", *creator)
fmt.Printf(" Device: %s\n", *device)
fmt.Printf(" Timestamp: %s\n", ts)
fmt.Printf(" SHA-256: %x\n", origHash)
// Setup FHE.
fmt.Println("\nInitialising FHE...")
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
// Encrypt hash bits.
hashBits := bytesToBits(origHash[:], *bits)
sealCts := make([]*fhe.Ciphertext, *bits)
for i, b := range hashBits {
sealCts[i] = enc.Encrypt(b)
}
fmt.Printf("Seal created: %d encrypted hash bits\n", *bits)
if *verifyPath == "" {
fmt.Println("\nRun with -verify <file> to check authenticity.")
return
}
// --- Verification ---
fmt.Println("\n=== Verification ===")
fmt.Printf("Checking: %s\n", *verifyPath)
candHash, err := hashFile(*verifyPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Candidate SHA-256: %x\n", candHash)
// Encrypt candidate hash bits.
candBits := bytesToBits(candHash[:], *bits)
candCts := make([]*fhe.Ciphertext, *bits)
for i, b := range candBits {
candCts[i] = enc.Encrypt(b)
}
// Homomorphic equality check.
fmt.Printf("Comparing %d bit-pairs homomorphically...\n", *bits)
t0 := time.Now()
match, err := eval.XNOR(sealCts[0], candCts[0])
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
for i := 1; i < *bits; i++ {
eq, err := eval.XNOR(sealCts[i], candCts[i])
if err != nil {
fmt.Fprintf(os.Stderr, "error at bit %d: %v\n", i, err)
os.Exit(1)
}
match, err = eval.AND(match, eq)
if err != nil {
fmt.Fprintf(os.Stderr, "error combining bit %d: %v\n", i, err)
os.Exit(1)
}
}
elapsed := time.Since(t0)
result := dec.Decrypt(match)
fmt.Printf("\nResult: authentic=%v (%v)\n", result, elapsed)
if result {
fmt.Println("AUTHENTIC: file matches the sealed original.")
fmt.Printf(" Creator: %s | Device: %s | Sealed: %s\n", *creator, *device, ts)
} else {
fmt.Println("TAMPERED: file does NOT match the sealed original!")
fmt.Println(" Content has been modified since sealing.")
}
}
func hashFile(path string) ([32]byte, error) {
f, err := os.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return [32]byte{}, err
}
var d [32]byte
copy(d[:], h.Sum(nil))
return d, nil
}
func bytesToBits(data []byte, n int) []bool {
bits := make([]bool, n)
for i := 0; i < n; i++ {
byteIdx := i / 8
bitIdx := i % 8
if byteIdx < len(data) {
bits[i] = (data[byteIdx]>>bitIdx)&1 == 1
}
}
return bits
}
+166
View File
@@ -0,0 +1,166 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command provenance demonstrates AI model provenance tracking with FHE.
//
// An AI model's weights hash is encrypted under FHE to create a provenance
// record. Later, a candidate model file can be verified against the sealed
// record homomorphically -- the original weights hash is never exposed.
//
// This prevents model theft detection from leaking proprietary information:
// the verifier learns only "match" or "no match."
//
// Usage:
//
// go run ./cmd/provenance -name "zen-7b" -version "1.0" -params 7000000000 -weights model.bin
// go run ./cmd/provenance -name "zen-7b" -version "1.0" -params 7000000000 -weights model.bin -verify candidate.bin
package main
import (
"crypto/sha256"
"flag"
"fmt"
"io"
"os"
"time"
"github.com/luxfi/fhe"
)
func main() {
name := flag.String("name", "zen-7b", "model name")
version := flag.String("version", "1.0", "model version")
paramCount := flag.Int64("params", 7_000_000_000, "parameter count")
weightsPath := flag.String("weights", "", "path to model weights file")
verifyPath := flag.String("verify", "", "path to candidate file to verify against sealed hash")
bits := flag.Int("bits", 8, "hash bits to seal (more = slower, stronger)")
flag.Parse()
if *weightsPath == "" {
fmt.Fprintln(os.Stderr, "error: -weights is required")
flag.Usage()
os.Exit(1)
}
if *bits < 1 || *bits > 256 {
fmt.Fprintln(os.Stderr, "error: -bits must be 1..256")
os.Exit(1)
}
// Print model card.
fmt.Println("=== Model Provenance Record ===")
fmt.Printf(" Name: %s\n", *name)
fmt.Printf(" Version: %s\n", *version)
fmt.Printf(" Parameters: %d\n", *paramCount)
// Hash weights.
hash, err := hashFile(*weightsPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error hashing weights: %v\n", err)
os.Exit(1)
}
fmt.Printf(" Weights SHA-256: %x\n", hash)
// Setup FHE.
fmt.Println("\nInitialising FHE...")
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
// Encrypt the weights hash.
hashBits := bytesToBits(hash[:], *bits)
fmt.Printf("Encrypting %d hash bits...\n", *bits)
sealCts := make([]*fhe.Ciphertext, *bits)
for i, b := range hashBits {
sealCts[i] = enc.Encrypt(b)
}
fmt.Printf("Provenance seal created: %d encrypted ciphertexts\n", len(sealCts))
if *verifyPath == "" {
fmt.Println("\nRun with -verify <candidate.bin> to check a model against this seal.")
return
}
// --- Verification ---
fmt.Println("\n=== Verification ===")
fmt.Printf("Candidate: %s\n", *verifyPath)
candidateHash, err := hashFile(*verifyPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error hashing candidate: %v\n", err)
os.Exit(1)
}
fmt.Printf("Candidate SHA-256: %x\n", candidateHash)
candidateBits := bytesToBits(candidateHash[:], *bits)
candidateCts := make([]*fhe.Ciphertext, *bits)
for i, b := range candidateBits {
candidateCts[i] = enc.Encrypt(b)
}
// Homomorphic comparison.
fmt.Printf("Comparing %d encrypted bit-pairs...\n", *bits)
t0 := time.Now()
match, err := eval.XNOR(sealCts[0], candidateCts[0])
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
for i := 1; i < *bits; i++ {
eq, err := eval.XNOR(sealCts[i], candidateCts[i])
if err != nil {
fmt.Fprintf(os.Stderr, "error at bit %d: %v\n", i, err)
os.Exit(1)
}
match, err = eval.AND(match, eq)
if err != nil {
fmt.Fprintf(os.Stderr, "error combining bit %d: %v\n", i, err)
os.Exit(1)
}
}
elapsed := time.Since(t0)
result := dec.Decrypt(match)
fmt.Printf("\nResult: match=%v (%v)\n", result, elapsed)
if result {
fmt.Println("VERIFIED: candidate model matches the sealed provenance record.")
} else {
fmt.Println("REJECTED: candidate does NOT match the sealed provenance record.")
}
fmt.Println("Note: the original weights hash was never revealed.")
}
func hashFile(path string) ([32]byte, error) {
f, err := os.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return [32]byte{}, err
}
var d [32]byte
copy(d[:], h.Sum(nil))
return d, nil
}
func bytesToBits(data []byte, n int) []bool {
bits := make([]bool, n)
for i := 0; i < n; i++ {
byteIdx := i / 8
bitIdx := i % 8
if byteIdx < len(data) {
bits[i] = (data[byteIdx]>>bitIdx)&1 == 1
}
}
return bits
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command seal demonstrates document integrity sealing with FHE.
//
// A document hash (SHA-256) is encrypted bit-by-bit under FHE, creating a
// tamper-proof "seal." Verification re-hashes the file and compares
// against the sealed hash homomorphically (XNOR + AND chain) -- the
// original hash is never revealed during verification.
//
// Usage:
//
// go run ./cmd/seal -file document.txt # create seal
// go run ./cmd/seal -file document.txt -verify # verify seal
// go run ./cmd/seal -bits 16 # compare 16 hash bits (default 8)
package main
import (
"crypto/sha256"
"flag"
"fmt"
"io"
"os"
"time"
"github.com/luxfi/fhe"
)
func main() {
filePath := flag.String("file", "", "path to the file to seal/verify")
verify := flag.Bool("verify", false, "verify mode: re-hash and compare homomorphically")
bits := flag.Int("bits", 8, "number of hash bits to seal (more bits = slower but stronger)")
flag.Parse()
if *filePath == "" {
fmt.Fprintln(os.Stderr, "error: -file is required")
flag.Usage()
os.Exit(1)
}
if *bits < 1 || *bits > 256 {
fmt.Fprintln(os.Stderr, "error: -bits must be 1..256")
os.Exit(1)
}
// Hash the file.
hash, err := hashFile(*filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("SHA-256: %x\n", hash)
// Expand hash to bit slice (LSB-first per byte).
hashBits := bytesToBits(hash[:], *bits)
// Setup FHE.
fmt.Println("Initialising FHE parameters...")
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
// Encrypt the hash bits (the "seal").
fmt.Printf("Encrypting %d hash bits...\n", *bits)
t0 := time.Now()
sealCts := make([]*fhe.Ciphertext, *bits)
for i, b := range hashBits {
sealCts[i] = enc.Encrypt(b)
}
fmt.Printf("Seal created in %v\n", time.Since(t0))
if !*verify {
// In create mode we just show the seal was produced.
fmt.Printf("Seal: %d encrypted ciphertexts (%d bits)\n", len(sealCts), *bits)
fmt.Println("Run with -verify to check the file against this seal.")
return
}
// --- Verify mode ---
// Re-hash (same file or a potentially tampered file) and compare.
fmt.Println("\n--- Verification ---")
verifyHash, err := hashFile(*filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
verifyBits := bytesToBits(verifyHash[:], *bits)
// Encrypt verify bits.
verifyCts := make([]*fhe.Ciphertext, *bits)
for i, b := range verifyBits {
verifyCts[i] = enc.Encrypt(b)
}
// Homomorphic equality: XNOR each pair, AND-reduce.
fmt.Printf("Comparing %d encrypted bit-pairs homomorphically...\n", *bits)
t0 = time.Now()
match, err := eval.XNOR(sealCts[0], verifyCts[0])
if err != nil {
fmt.Fprintf(os.Stderr, "error at bit 0: %v\n", err)
os.Exit(1)
}
for i := 1; i < *bits; i++ {
eq, err := eval.XNOR(sealCts[i], verifyCts[i])
if err != nil {
fmt.Fprintf(os.Stderr, "error at bit %d: %v\n", i, err)
os.Exit(1)
}
match, err = eval.AND(match, eq)
if err != nil {
fmt.Fprintf(os.Stderr, "error combining bit %d: %v\n", i, err)
os.Exit(1)
}
}
elapsed := time.Since(t0)
// Decrypt the single result bit.
result := dec.Decrypt(match)
fmt.Printf("Verification result: %v (%v, %d gates)\n", result, elapsed, *bits*2-1)
if result {
fmt.Println("PASS: document matches the seal.")
} else {
fmt.Println("FAIL: document does NOT match the seal.")
}
}
// hashFile returns the SHA-256 digest of a file.
func hashFile(path string) ([32]byte, error) {
f, err := os.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return [32]byte{}, err
}
var digest [32]byte
copy(digest[:], h.Sum(nil))
return digest, nil
}
// bytesToBits extracts n bits from data (LSB-first per byte).
func bytesToBits(data []byte, n int) []bool {
bits := make([]bool, n)
for i := 0; i < n; i++ {
byteIdx := i / 8
bitIdx := i % 8
if byteIdx < len(data) {
bits[i] = (data[byteIdx]>>bitIdx)&1 == 1
}
}
return bits
}
+191
View File
@@ -0,0 +1,191 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command stats demonstrates secure multiparty statistics using FHE.
//
// N parties each encrypt a private value (salary, score, etc.) as
// individual bits. The system computes the sum homomorphically using
// a ripple-carry adder built from XOR and AND gates. Only the aggregate
// sum and count are decrypted -- individual values remain private.
//
// Usage:
//
// go run ./cmd/stats # 4 parties, random 4-bit values
// go run ./cmd/stats -values 10,20,30,40 # explicit values (max 255)
// go run ./cmd/stats -values 5,15,25 # 3 parties
package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"
"math/rand/v2"
"github.com/luxfi/fhe"
)
func main() {
valuesStr := flag.String("values", "", "comma-separated private values (max 255 each)")
parties := flag.Int("parties", 4, "number of parties (used when -values not set)")
maxVal := flag.Int("max", 15, "max random value per party (used when -values not set)")
flag.Parse()
// Parse or generate values.
var values []uint8
if *valuesStr != "" {
for _, s := range strings.Split(*valuesStr, ",") {
v, err := strconv.Atoi(strings.TrimSpace(s))
if err != nil || v < 0 || v > 255 {
fmt.Fprintf(os.Stderr, "error: invalid value %q (must be 0..255)\n", s)
os.Exit(1)
}
values = append(values, uint8(v))
}
} else {
values = make([]uint8, *parties)
for i := range values {
values[i] = uint8(rand.IntN(*maxVal + 1))
}
}
n := len(values)
if n < 2 {
fmt.Fprintln(os.Stderr, "error: need at least 2 parties")
os.Exit(1)
}
// Compute expected results.
var expectedSum int
for _, v := range values {
expectedSum += int(v)
}
expectedAvg := float64(expectedSum) / float64(n)
fmt.Println("=== Secure Multiparty Statistics ===")
fmt.Printf("Parties: %d\n", n)
for i, v := range values {
fmt.Printf(" Party %d: [PRIVATE] %d\n", i+1, v)
}
fmt.Printf("Expected sum: %d, average: %.2f\n\n", expectedSum, expectedAvg)
// Setup FHE.
fmt.Println("Initialising FHE...")
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
// Determine accumulator width: enough bits for max possible sum.
// max sum = n * 255, need ceil(log2(n*255+1)) bits, cap at 16.
accBits := 8
maxSum := n * 255
for (1 << accBits) <= maxSum {
accBits++
}
if accBits > 16 {
accBits = 16
}
fmt.Printf("Accumulator: %d bits (max representable sum: %d)\n", accBits, (1<<accBits)-1)
// Encrypt each party's value as 8 bits.
fmt.Println("Encrypting private values...")
encValues := make([][8]*fhe.Ciphertext, n)
for i, v := range values {
encValues[i] = enc.EncryptByte(byte(v))
}
// Initialize accumulator to zero.
acc := make([]*fhe.Ciphertext, accBits)
for i := range acc {
acc[i] = enc.Encrypt(false)
}
// Homomorphic summation: add each 8-bit value into the accumulator.
fmt.Printf("Summing %d encrypted values...\n", n)
t0 := time.Now()
for p := 0; p < n; p++ {
fmt.Printf(" Adding party %d/%d...\n", p+1, n)
acc, err = addUint8(eval, acc, encValues[p][:])
if err != nil {
fmt.Fprintf(os.Stderr, "error adding party %d: %v\n", p+1, err)
os.Exit(1)
}
}
elapsed := time.Since(t0)
// Decrypt the sum.
var decSum uint32
for i, ct := range acc {
if dec.Decrypt(ct) {
decSum |= 1 << i
}
}
decAvg := float64(decSum) / float64(n)
fmt.Printf("\n--- Aggregated Results ---\n")
fmt.Printf("Count: %d parties\n", n)
fmt.Printf("Sum: %d (encrypted computation)\n", decSum)
fmt.Printf("Average: %.2f\n", decAvg)
fmt.Printf("Elapsed: %v\n", elapsed)
if int(decSum) == expectedSum {
fmt.Println("PASS: encrypted sum matches expected value.")
} else {
fmt.Printf("FAIL: expected %d, got %d\n", expectedSum, decSum)
}
fmt.Println("\nNote: individual values were never revealed to any party.")
}
// addUint8 adds an 8-bit encrypted value into a wider accumulator
// using a ripple-carry adder (XOR for sum, AND for carry).
func addUint8(eval *fhe.Evaluator, acc, val []*fhe.Ciphertext) ([]*fhe.Ciphertext, error) {
result := make([]*fhe.Ciphertext, len(acc))
var carry *fhe.Ciphertext
// halfAdd computes sum=a^b, carry=a&b.
halfAdd := func(a, b *fhe.Ciphertext) (*fhe.Ciphertext, *fhe.Ciphertext, error) {
s, err := eval.XOR(a, b)
if err != nil {
return nil, nil, err
}
c, err := eval.AND(a, b)
return s, c, err
}
for i := 0; i < len(acc); i++ {
var addend *fhe.Ciphertext
if i < len(val) {
addend = val[i]
}
switch {
case addend == nil && carry == nil:
result[i] = acc[i]
case carry == nil:
result[i], carry, _ = halfAdd(acc[i], addend)
case addend == nil:
result[i], carry, _ = halfAdd(acc[i], carry)
default:
// Full adder: sum = a^b^carry, newCarry = MAJ(a,b,carry).
ab, err := eval.XOR(acc[i], addend)
if err != nil {
return nil, err
}
if result[i], err = eval.XOR(ab, carry); err != nil {
return nil, err
}
if carry, err = eval.MAJORITY(acc[i], addend, carry); err != nil {
return nil, err
}
}
}
return result, nil
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command vote demonstrates an encrypted voting system using FHE.
//
// Each voter encrypts a single yes/no ballot (1 bit) under FHE.
// The system tallies votes homomorphically using a ripple-carry adder
// built from XOR and AND gates -- individual votes are never decrypted.
// Only the final aggregate count is decrypted.
//
// Usage:
//
// go run ./cmd/vote # 5 voters, random ballots
// go run ./cmd/vote -voters 8 -yes 5 # 8 voters, 5 yes votes
// go run ./cmd/vote -voters 4 -yes 4 # unanimous yes
package main
import (
"flag"
"fmt"
"math/rand/v2"
"os"
"time"
"github.com/luxfi/fhe"
)
func main() {
nVoters := flag.Int("voters", 5, "number of voters (max 15 for 4-bit tally)")
nYes := flag.Int("yes", -1, "number of yes votes (-1 = random)")
flag.Parse()
if *nVoters < 2 || *nVoters > 15 {
fmt.Fprintln(os.Stderr, "error: -voters must be 2..15")
os.Exit(1)
}
// Decide votes.
votes := make([]bool, *nVoters)
if *nYes >= 0 {
if *nYes > *nVoters {
fmt.Fprintln(os.Stderr, "error: -yes cannot exceed -voters")
os.Exit(1)
}
for i := 0; i < *nYes; i++ {
votes[i] = true
}
} else {
for i := range votes {
votes[i] = rand.IntN(2) == 1
}
}
// Print plaintext votes (for demo verification).
expectedYes := 0
for i, v := range votes {
label := "no"
if v {
label = "yes"
expectedYes++
}
fmt.Printf(" Voter %d: %s\n", i+1, label)
}
fmt.Printf("Expected tally: %d yes / %d no\n\n", expectedYes, *nVoters-expectedYes)
// Setup FHE.
fmt.Println("Initialising FHE...")
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
// Encrypt ballots.
fmt.Println("Encrypting ballots...")
ballots := make([]*fhe.Ciphertext, *nVoters)
for i, v := range votes {
ballots[i] = enc.Encrypt(v)
}
// Tally: 4-bit ripple-carry accumulator (supports up to 15 voters).
const tallyBits = 4
tally := [tallyBits]*fhe.Ciphertext{
enc.Encrypt(false),
enc.Encrypt(false),
enc.Encrypt(false),
enc.Encrypt(false),
}
fmt.Printf("Tallying %d ballots homomorphically (%d-bit accumulator)...\n", *nVoters, tallyBits)
t0 := time.Now()
for i, ballot := range ballots {
fmt.Printf(" Adding ballot %d/%d...\n", i+1, *nVoters)
tally, err = addBit(eval, tally, ballot)
if err != nil {
fmt.Fprintf(os.Stderr, "error adding ballot %d: %v\n", i+1, err)
os.Exit(1)
}
}
elapsed := time.Since(t0)
// Decrypt only the tally.
var result uint8
for i := 0; i < tallyBits; i++ {
if dec.Decrypt(tally[i]) {
result |= 1 << i
}
}
fmt.Printf("\n--- Result ---\n")
fmt.Printf("Encrypted tally decrypted: %d yes votes\n", result)
fmt.Printf("Total voters: %d | No votes: %d\n", *nVoters, *nVoters-int(result))
fmt.Printf("Elapsed: %v\n", elapsed)
if int(result) == expectedYes {
fmt.Println("PASS: tally matches expected count.")
} else {
fmt.Println("FAIL: tally mismatch!")
}
fmt.Println("\nNote: individual ballots were never decrypted.")
}
// addBit adds a single encrypted bit to an n-bit encrypted accumulator
// using a ripple-carry adder (XOR for sum, AND for carry).
func addBit(eval *fhe.Evaluator, acc [4]*fhe.Ciphertext, bit *fhe.Ciphertext) ([4]*fhe.Ciphertext, error) {
carry := bit
var result [4]*fhe.Ciphertext
for i := 0; i < 4; i++ {
// sum = acc[i] XOR carry
sum, err := eval.XOR(acc[i], carry)
if err != nil {
return result, fmt.Errorf("bit %d XOR: %w", i, err)
}
// newCarry = acc[i] AND carry
newCarry, err := eval.AND(acc[i], carry)
if err != nil {
return result, fmt.Errorf("bit %d AND: %w", i, err)
}
result[i] = sum
carry = newCarry
}
return result, nil
}
+1 -1
View File
@@ -5,7 +5,7 @@ go 1.25.5
require (
github.com/google/uuid v1.6.0
github.com/luxfi/lattice/v7 v7.0.0
github.com/luxfi/mdns v0.0.0-00010101000000-000000000000
github.com/luxfi/mdns v0.1.0
github.com/urfave/cli/v3 v3.6.2
)