mirror of
https://github.com/luxfi/fhe.git
synced 2026-09-24 00:27:17 +00:00
feat: 7 FHE demo programs (dark pool, compliance, MM, auction, voting, NAV, proof)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# FHE Demo Programs
|
||||
|
||||
Seven runnable demos demonstrating Fully Homomorphic Encryption use cases for digital securities.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
cd ~/work/lux/fhe
|
||||
go build ./... # verify the fhe package compiles
|
||||
```
|
||||
|
||||
## Demos
|
||||
|
||||
### 1. Dark Pool (Encrypted Order Matching)
|
||||
|
||||
Traders submit encrypted limit orders. The matching engine compares bids against asks homomorphically. Only matched fills are decrypted.
|
||||
|
||||
```bash
|
||||
go run ./cmd/demos/darkpool
|
||||
```
|
||||
|
||||
### 2. Programmable Compliance
|
||||
|
||||
Verifies SEC diversification rules on an encrypted portfolio: no single position > 25%, total exposure < 80%. Only the boolean compliance result is revealed.
|
||||
|
||||
```bash
|
||||
go run ./cmd/demos/compliance
|
||||
```
|
||||
|
||||
### 3. Private Market Making
|
||||
|
||||
Three market makers submit encrypted bid/ask quotes. Best bid (highest) and best ask (lowest) are selected via encrypted comparisons. Losing quotes stay private.
|
||||
|
||||
```bash
|
||||
go run ./cmd/demos/marketmaker
|
||||
```
|
||||
|
||||
### 4. Encrypted Sealed-Bid Auction
|
||||
|
||||
Five participants submit encrypted bids. The winner is determined via tournament-style max comparisons. Only the winning bid is decrypted.
|
||||
|
||||
```bash
|
||||
go run ./cmd/demos/auction
|
||||
```
|
||||
|
||||
### 5. Private Shareholder Voting
|
||||
|
||||
100 shareholders vote encrypted yes/no. Votes are tallied using a ripple-carry adder (XOR + AND gates). Only the final count is decrypted.
|
||||
|
||||
```bash
|
||||
go run ./cmd/demos/voting
|
||||
go run ./cmd/demos/voting -voters 50 -yes 30
|
||||
```
|
||||
|
||||
### 6. Confidential NAV
|
||||
|
||||
An ETF's encrypted holdings (10 positions with share counts and prices) are used to compute NAV = sum(shares * price) / totalShares. Only the final NAV is revealed.
|
||||
|
||||
```bash
|
||||
go run ./cmd/demos/nav
|
||||
```
|
||||
|
||||
### 7. Compliance Proof
|
||||
|
||||
Proves two conditions on an encrypted portfolio: max position < 25% and no sanctioned counterparty. Outputs plaintext booleans while inputs stay encrypted.
|
||||
|
||||
```bash
|
||||
go run ./cmd/demos/proof
|
||||
```
|
||||
|
||||
## Run All
|
||||
|
||||
```bash
|
||||
for d in darkpool compliance marketmaker auction voting nav proof; do
|
||||
echo "=== $d ==="
|
||||
go run ./cmd/demos/$d
|
||||
echo
|
||||
done
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
All demos use the `github.com/luxfi/fhe` package with boolean circuit evaluation:
|
||||
|
||||
- **Parameters**: `PN10QP27` (128-bit security, N=1024)
|
||||
- **Boolean gates**: `Evaluator` with `AND`, `OR`, `XOR`, `NOT`, `ANDNY`, `MAJORITY` + bootstrapping
|
||||
- **Encryption**: `Encryptor.Encrypt(bool)` encrypts individual bits
|
||||
- **Decryption**: `Decryptor.Decrypt(*Ciphertext)` decodes bits (secret key holder only)
|
||||
- **Integers**: 8-bit values represented as `[8]*Ciphertext` (bit-decomposed, LSB first)
|
||||
- **Comparison**: MSB-first bitwise less-than circuit using `ANDNY` + `XOR` + `AND` + `OR`
|
||||
- **Addition**: Ripple-carry full adder using `XOR` + `MAJORITY`
|
||||
|
||||
FHE operations run without the secret key. The evaluator uses bootstrap keys (public) for noise management via programmable bootstrapping after each gate.
|
||||
|
||||
## Performance (Apple M-series, PN10QP27)
|
||||
|
||||
- **Key generation**: ~600ms-1s (one-time)
|
||||
- **Encryption**: ~5ms per 8-bit value (8 bit encryptions)
|
||||
- **Single gate** (AND, OR, XOR): ~400-600ms per gate
|
||||
- **8-bit comparison** (lt/gt/ge): ~12-16s (24 gates: 8x ANDNY + 8x XOR + 7x AND + 7x OR)
|
||||
- **8-bit addition**: ~10-13s per add (24 gates: 8x XOR + 8x XOR + 8x MAJORITY)
|
||||
- **Vote tally** (1 ballot): ~5.8s (14 gates: 7x XOR + 7x AND for 7-bit accumulator)
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command auction demonstrates an encrypted sealed-bid auction using FHE.
|
||||
//
|
||||
// Five participants submit encrypted sealed bids. The system determines the
|
||||
// winner via boolean circuit max computation. Only the winning bid is decrypted.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/demos/auction
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/fhe"
|
||||
)
|
||||
|
||||
const bidBits = 8
|
||||
|
||||
type bid struct {
|
||||
participant string
|
||||
amount uint8
|
||||
encAmount [8]*fhe.Ciphertext
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== FHE Encrypted Sealed-Bid Auction Demo ===")
|
||||
fmt.Println("Determine winner without revealing losing bids")
|
||||
fmt.Println()
|
||||
|
||||
// Setup
|
||||
fmt.Print("Initializing FHE... ")
|
||||
t0 := time.Now()
|
||||
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)
|
||||
fmt.Printf("done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Five sealed bids
|
||||
bids := []bid{
|
||||
{participant: "Participant 1", amount: 84},
|
||||
{participant: "Participant 2", amount: 92},
|
||||
{participant: "Participant 3", amount: 97},
|
||||
{participant: "Participant 4", amount: 83},
|
||||
{participant: "Participant 5", amount: 88},
|
||||
}
|
||||
|
||||
fmt.Println("Sealed bids (shown for demo verification only):")
|
||||
for _, b := range bids {
|
||||
fmt.Printf(" %s: $%d\n", b.participant, b.amount)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Encrypt
|
||||
fmt.Println("Encrypting sealed bids...")
|
||||
t0 = time.Now()
|
||||
for i := range bids {
|
||||
bids[i].encAmount = encryptByte(enc, bids[i].amount)
|
||||
}
|
||||
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Tournament: find max via pairwise comparison
|
||||
fmt.Println("Determining winner via encrypted comparisons...")
|
||||
t0 = time.Now()
|
||||
winnerIdx := 0
|
||||
for i := 1; i < len(bids); i++ {
|
||||
t1 := time.Now()
|
||||
gtResult, err := gtEncrypted(eval, bids[i].encAmount, bids[winnerIdx].encAmount)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
isGreater := dec.Decrypt(gtResult)
|
||||
elapsed := time.Since(t1)
|
||||
if isGreater {
|
||||
fmt.Printf(" %s > %s: true (%v) -> new leader\n",
|
||||
bids[i].participant, bids[winnerIdx].participant, elapsed)
|
||||
winnerIdx = i
|
||||
} else {
|
||||
fmt.Printf(" %s > %s: false (%v)\n",
|
||||
bids[i].participant, bids[winnerIdx].participant, elapsed)
|
||||
}
|
||||
}
|
||||
fmt.Printf("Tournament done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Decrypt winner only
|
||||
winningAmount := decryptByte(dec, bids[winnerIdx].encAmount)
|
||||
|
||||
fmt.Println("--- Result ---")
|
||||
fmt.Printf("Winner: %s @ $%d\n", bids[winnerIdx].participant, winningAmount)
|
||||
fmt.Println("\nNote: losing bid amounts were never decrypted.")
|
||||
}
|
||||
|
||||
func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
|
||||
var bits [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
bits[i] = enc.Encrypt((v>>i)&1 == 1)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
|
||||
var v uint8
|
||||
for i := 0; i < 8; i++ {
|
||||
if dec.Decrypt(bits[i]) {
|
||||
v |= 1 << i
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ltEncrypted computes a < b on 8-bit encrypted values (MSB-first comparison).
|
||||
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
var isLess, isEqual *fhe.Ciphertext
|
||||
for i := bidBits - 1; i >= 0; i-- {
|
||||
bitLt, err := eval.ANDNY(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitXor, err := eval.XOR(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitEq := eval.NOT(bitXor)
|
||||
if isLess == nil {
|
||||
isLess = bitLt
|
||||
isEqual = bitEq
|
||||
} else {
|
||||
eqAndLt, err := eval.AND(isEqual, bitLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isLess, err = eval.OR(isLess, eqAndLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isEqual, err = eval.AND(isEqual, bitEq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return isLess, nil
|
||||
}
|
||||
|
||||
// gtEncrypted computes a > b: same as b < a.
|
||||
func gtEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
return ltEncrypted(eval, b, a)
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command compliance demonstrates programmable compliance checks on encrypted portfolios.
|
||||
//
|
||||
// A portfolio of 5 positions is encrypted. The system verifies SEC diversification
|
||||
// rules (no single position > 25% of total, total exposure < 80%) entirely on
|
||||
// encrypted data using boolean circuit comparisons. Only the boolean results are
|
||||
// decrypted.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/demos/compliance
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/fhe"
|
||||
)
|
||||
|
||||
const valueBits = 8 // 8-bit values (0-255)
|
||||
|
||||
type position struct {
|
||||
name string
|
||||
weight uint8 // percentage 0-100
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== FHE Programmable Compliance Demo ===")
|
||||
fmt.Println("Verify SEC diversification on encrypted portfolio")
|
||||
fmt.Println()
|
||||
|
||||
// Setup
|
||||
fmt.Print("Initializing FHE... ")
|
||||
t0 := time.Now()
|
||||
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)
|
||||
fmt.Printf("done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Portfolio
|
||||
portfolio := []position{
|
||||
{name: "AAPL", weight: 20},
|
||||
{name: "MSFT", weight: 18},
|
||||
{name: "NVDA", weight: 15},
|
||||
{name: "GOOG", weight: 12},
|
||||
{name: "AMZN", weight: 10},
|
||||
}
|
||||
var totalWeight uint8
|
||||
for _, p := range portfolio {
|
||||
totalWeight += p.weight
|
||||
fmt.Printf(" %s: %d%%\n", p.name, p.weight)
|
||||
}
|
||||
fmt.Printf(" Total exposure: %d%%\n\n", totalWeight)
|
||||
|
||||
// Encrypt positions
|
||||
fmt.Println("Encrypting portfolio positions...")
|
||||
t0 = time.Now()
|
||||
encWeights := make([][8]*fhe.Ciphertext, len(portfolio))
|
||||
for i, p := range portfolio {
|
||||
encWeights[i] = encryptByte(enc, p.weight)
|
||||
}
|
||||
// Encrypt threshold (25%) as constant for comparison
|
||||
encThreshold := encryptByte(enc, 25)
|
||||
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Check 1: no single position > 25%
|
||||
fmt.Println("Check 1: Each position <= 25%...")
|
||||
t0 = time.Now()
|
||||
allCompliant := enc.Encrypt(true) // accumulator
|
||||
for i, p := range portfolio {
|
||||
t1 := time.Now()
|
||||
// leEncrypted returns 1 if weight <= 25
|
||||
leResult, err := leEncrypted(eval, encWeights[i], encThreshold)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// AND into accumulator
|
||||
allCompliant, err = eval.AND(allCompliant, leResult)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(" %s checked (%v)\n", p.name, time.Since(t1))
|
||||
}
|
||||
check1 := dec.Decrypt(allCompliant)
|
||||
fmt.Printf(" Diversification: %v (%v)\n\n", check1, time.Since(t0))
|
||||
|
||||
// Check 2: total exposure < 80%
|
||||
fmt.Println("Check 2: Total exposure < 80%...")
|
||||
t0 = time.Now()
|
||||
// Sum weights using ripple-carry adder
|
||||
encTotal := encWeights[0]
|
||||
for i := 1; i < len(encWeights); i++ {
|
||||
t1 := time.Now()
|
||||
encTotal, err = addBytes(eval, enc, encTotal, encWeights[i])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error adding: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(" Added position %d (%v)\n", i+1, time.Since(t1))
|
||||
}
|
||||
|
||||
encMaxExposure := encryptByte(enc, 80)
|
||||
t1 := time.Now()
|
||||
ltResult, err := ltEncrypted(eval, encTotal, encMaxExposure)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
check2 := dec.Decrypt(ltResult)
|
||||
fmt.Printf(" Total < 80%%: %v (%v)\n\n", check2, time.Since(t1))
|
||||
|
||||
// Verify
|
||||
decTotal := decryptByte(dec, encTotal)
|
||||
fmt.Printf(" (Verification: encrypted total decrypts to %d%%)\n\n", decTotal)
|
||||
|
||||
// Final
|
||||
fmt.Println("--- Result ---")
|
||||
fmt.Printf("Portfolio compliant: %v\n", check1 && check2)
|
||||
fmt.Println("(Holdings were never revealed to the compliance engine)")
|
||||
}
|
||||
|
||||
func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
|
||||
var bits [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
bits[i] = enc.Encrypt((v>>i)&1 == 1)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
|
||||
var v uint8
|
||||
for i := 0; i < 8; i++ {
|
||||
if dec.Decrypt(bits[i]) {
|
||||
v |= 1 << i
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// addBytes adds two encrypted 8-bit values using ripple-carry.
|
||||
func addBytes(eval *fhe.Evaluator, enc *fhe.Encryptor, a, b [8]*fhe.Ciphertext) ([8]*fhe.Ciphertext, error) {
|
||||
carry := enc.Encrypt(false)
|
||||
var result [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
// sum = a XOR b XOR carry (full adder)
|
||||
abXor, err := eval.XOR(a[i], b[i])
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
sum, err := eval.XOR(abXor, carry)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
// carry = MAJORITY(a, b, carry)
|
||||
carry, err = eval.MAJORITY(a[i], b[i], carry)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result[i] = sum
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ltEncrypted computes a < b on encrypted 8-bit values.
|
||||
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
var isLess, isEqual *fhe.Ciphertext
|
||||
for i := valueBits - 1; i >= 0; i-- {
|
||||
bitLt, err := eval.ANDNY(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitXor, err := eval.XOR(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitEq := eval.NOT(bitXor)
|
||||
if isLess == nil {
|
||||
isLess = bitLt
|
||||
isEqual = bitEq
|
||||
} else {
|
||||
eqAndLt, err := eval.AND(isEqual, bitLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isLess, err = eval.OR(isLess, eqAndLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isEqual, err = eval.AND(isEqual, bitEq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return isLess, nil
|
||||
}
|
||||
|
||||
// leEncrypted computes a <= b: NOT(b < a).
|
||||
func leEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
bLtA, err := ltEncrypted(eval, b, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return eval.NOT(bLtA), nil
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command darkpool demonstrates an encrypted dark pool order matching system.
|
||||
//
|
||||
// Traders submit encrypted limit orders (price + quantity). The matching engine
|
||||
// compares encrypted bids against encrypted asks using FHE boolean circuits.
|
||||
// Only matched fills are decrypted -- unmatched orders remain private.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/demos/darkpool
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/fhe"
|
||||
)
|
||||
|
||||
const priceBits = 8 // 8-bit prices (0-255)
|
||||
|
||||
type order struct {
|
||||
id int
|
||||
isBid bool
|
||||
price uint8
|
||||
qty uint8
|
||||
|
||||
encPrice [8]*fhe.Ciphertext
|
||||
encQty [8]*fhe.Ciphertext
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== FHE Dark Pool Demo ===")
|
||||
fmt.Println("Encrypted order matching without revealing prices")
|
||||
fmt.Println()
|
||||
|
||||
// Setup
|
||||
fmt.Print("Initializing FHE... ")
|
||||
t0 := time.Now()
|
||||
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)
|
||||
fmt.Printf("done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Orders: 3 bids, 3 asks
|
||||
bids := []order{
|
||||
{id: 1, isBid: true, price: 85, qty: 100},
|
||||
{id: 2, isBid: true, price: 84, qty: 200},
|
||||
{id: 3, isBid: true, price: 86, qty: 75},
|
||||
}
|
||||
asks := []order{
|
||||
{id: 4, isBid: false, price: 84, qty: 100},
|
||||
{id: 5, isBid: false, price: 87, qty: 250},
|
||||
{id: 6, isBid: false, price: 85, qty: 90},
|
||||
}
|
||||
|
||||
// Encrypt all orders
|
||||
fmt.Println("Encrypting 6 limit orders...")
|
||||
t0 = time.Now()
|
||||
for i := range bids {
|
||||
bids[i].encPrice = encryptByte(enc, bids[i].price)
|
||||
bids[i].encQty = encryptByte(enc, bids[i].qty)
|
||||
}
|
||||
for i := range asks {
|
||||
asks[i].encPrice = encryptByte(enc, asks[i].price)
|
||||
asks[i].encQty = encryptByte(enc, asks[i].qty)
|
||||
}
|
||||
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Match bids vs asks: bid.price >= ask.price
|
||||
fmt.Println("Matching encrypted orders (bid.price >= ask.price)...")
|
||||
type match struct {
|
||||
bid, ask order
|
||||
}
|
||||
var matches []match
|
||||
|
||||
for _, bid := range bids {
|
||||
for _, ask := range asks {
|
||||
t1 := time.Now()
|
||||
isGe, err := geEncrypted(eval, bid.encPrice, ask.encPrice)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error comparing: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
isMatch := dec.Decrypt(isGe)
|
||||
elapsed := time.Since(t1)
|
||||
|
||||
if isMatch {
|
||||
matches = append(matches, match{bid: bid, ask: ask})
|
||||
fmt.Printf(" Order %d vs %d: MATCH (%v)\n", bid.id, ask.id, elapsed)
|
||||
} else {
|
||||
fmt.Printf(" Order %d vs %d: no match (%v)\n", bid.id, ask.id, elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt matched fills
|
||||
fmt.Printf("\n--- Matched Fills (%d) ---\n", len(matches))
|
||||
for _, m := range matches {
|
||||
bidPrice := decryptByte(dec, m.bid.encPrice)
|
||||
askPrice := decryptByte(dec, m.ask.encPrice)
|
||||
bidQty := decryptByte(dec, m.bid.encQty)
|
||||
askQty := decryptByte(dec, m.ask.encQty)
|
||||
|
||||
fillQty := bidQty
|
||||
if askQty < fillQty {
|
||||
fillQty = askQty
|
||||
}
|
||||
fmt.Printf(" Matched: Buy %d @ $%d <-> Sell %d @ $%d (fill %d shares)\n",
|
||||
bidQty, bidPrice, askQty, askPrice, fillQty)
|
||||
}
|
||||
fmt.Println("\nNote: unmatched order prices were never revealed.")
|
||||
}
|
||||
|
||||
func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
|
||||
var bits [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
bits[i] = enc.Encrypt((v>>i)&1 == 1)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
|
||||
var v uint8
|
||||
for i := 0; i < 8; i++ {
|
||||
if dec.Decrypt(bits[i]) {
|
||||
v |= 1 << i
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// geEncrypted computes a >= b on encrypted 8-bit values using MSB-first comparison.
|
||||
// Returns an encrypted bit: 1 if a >= b, 0 otherwise.
|
||||
func geEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
// Compare from MSB to LSB: a >= b iff NOT (a < b)
|
||||
// a < b: find first bit where they differ (MSB to LSB); at that bit a=0, b=1
|
||||
var isLess, isEqual *fhe.Ciphertext
|
||||
|
||||
for i := priceBits - 1; i >= 0; i-- {
|
||||
// bitLt = NOT(a[i]) AND b[i] -- this bit says a < b at position i
|
||||
bitLt, err := eval.ANDNY(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bit %d ANDNY: %w", i, err)
|
||||
}
|
||||
// bitEq = NOT(a[i] XOR b[i]) -- bits are equal
|
||||
bitXor, err := eval.XOR(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bit %d XOR: %w", i, err)
|
||||
}
|
||||
bitEq := eval.NOT(bitXor)
|
||||
|
||||
if isLess == nil {
|
||||
isLess = bitLt
|
||||
isEqual = bitEq
|
||||
} else {
|
||||
// isLess = isLess OR (isEqual AND bitLt)
|
||||
eqAndLt, err := eval.AND(isEqual, bitLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isLess, err = eval.OR(isLess, eqAndLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// isEqual = isEqual AND bitEq
|
||||
isEqual, err = eval.AND(isEqual, bitEq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a >= b is NOT(a < b)
|
||||
return eval.NOT(isLess), nil
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command marketmaker demonstrates private market making with FHE.
|
||||
//
|
||||
// Three market makers submit encrypted bid/ask quotes. The system selects
|
||||
// the best bid (highest) and best ask (lowest) via boolean circuit comparisons.
|
||||
// Only the winning quotes are decrypted -- losing quotes stay private.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/demos/marketmaker
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/fhe"
|
||||
)
|
||||
|
||||
const priceBits = 8
|
||||
|
||||
type quote struct {
|
||||
maker string
|
||||
bid uint8
|
||||
ask uint8
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== FHE Private Market Making Demo ===")
|
||||
fmt.Println("Select best bid/ask from encrypted quotes")
|
||||
fmt.Println()
|
||||
|
||||
// Setup
|
||||
fmt.Print("Initializing FHE... ")
|
||||
t0 := time.Now()
|
||||
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)
|
||||
fmt.Printf("done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Three market makers
|
||||
quotes := []quote{
|
||||
{maker: "MM1", bid: 84, ask: 86},
|
||||
{maker: "MM2", bid: 85, ask: 87},
|
||||
{maker: "MM3", bid: 83, ask: 85},
|
||||
}
|
||||
for _, q := range quotes {
|
||||
fmt.Printf(" %s: bid $%d / ask $%d\n", q.maker, q.bid, q.ask)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Encrypt
|
||||
fmt.Println("Encrypting quotes...")
|
||||
t0 = time.Now()
|
||||
type encQuote struct {
|
||||
maker string
|
||||
encBid [8]*fhe.Ciphertext
|
||||
encAsk [8]*fhe.Ciphertext
|
||||
}
|
||||
encQuotes := make([]encQuote, len(quotes))
|
||||
for i, q := range quotes {
|
||||
encQuotes[i].maker = q.maker
|
||||
encQuotes[i].encBid = encryptByte(enc, q.bid)
|
||||
encQuotes[i].encAsk = encryptByte(enc, q.ask)
|
||||
}
|
||||
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Best bid (highest)
|
||||
fmt.Println("Finding best bid (highest)...")
|
||||
t0 = time.Now()
|
||||
bestBidIdx := 0
|
||||
for i := 1; i < len(encQuotes); i++ {
|
||||
t1 := time.Now()
|
||||
// is encQuotes[i].bid > encQuotes[bestBidIdx].bid?
|
||||
gtResult, err := gtEncrypted(eval, encQuotes[i].encBid, encQuotes[bestBidIdx].encBid)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
isGreater := dec.Decrypt(gtResult)
|
||||
fmt.Printf(" %s > %s: %v (%v)\n", encQuotes[i].maker, encQuotes[bestBidIdx].maker, isGreater, time.Since(t1))
|
||||
if isGreater {
|
||||
bestBidIdx = i
|
||||
}
|
||||
}
|
||||
fmt.Printf("Best bid found (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Best ask (lowest)
|
||||
fmt.Println("Finding best ask (lowest)...")
|
||||
t0 = time.Now()
|
||||
bestAskIdx := 0
|
||||
for i := 1; i < len(encQuotes); i++ {
|
||||
t1 := time.Now()
|
||||
ltResult, err := ltEncrypted(eval, encQuotes[i].encAsk, encQuotes[bestAskIdx].encAsk)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
isLess := dec.Decrypt(ltResult)
|
||||
fmt.Printf(" %s < %s: %v (%v)\n", encQuotes[i].maker, encQuotes[bestAskIdx].maker, isLess, time.Since(t1))
|
||||
if isLess {
|
||||
bestAskIdx = i
|
||||
}
|
||||
}
|
||||
fmt.Printf("Best ask found (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Decrypt winners
|
||||
bestBidPrice := decryptByte(dec, encQuotes[bestBidIdx].encBid)
|
||||
bestAskPrice := decryptByte(dec, encQuotes[bestAskIdx].encAsk)
|
||||
|
||||
fmt.Println("--- Result ---")
|
||||
fmt.Printf("Best bid: %s @ $%d\n", encQuotes[bestBidIdx].maker, bestBidPrice)
|
||||
fmt.Printf("Best ask: %s @ $%d\n", encQuotes[bestAskIdx].maker, bestAskPrice)
|
||||
fmt.Printf("Spread: $%d\n", bestAskPrice-bestBidPrice)
|
||||
fmt.Println("\nNote: losing quotes were never decrypted.")
|
||||
}
|
||||
|
||||
func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
|
||||
var bits [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
bits[i] = enc.Encrypt((v>>i)&1 == 1)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
|
||||
var v uint8
|
||||
for i := 0; i < 8; i++ {
|
||||
if dec.Decrypt(bits[i]) {
|
||||
v |= 1 << i
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ltEncrypted computes a < b on encrypted 8-bit values (MSB-first).
|
||||
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
var isLess, isEqual *fhe.Ciphertext
|
||||
for i := priceBits - 1; i >= 0; i-- {
|
||||
bitLt, err := eval.ANDNY(a[i], b[i]) // NOT(a) AND b
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitXor, err := eval.XOR(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitEq := eval.NOT(bitXor)
|
||||
if isLess == nil {
|
||||
isLess = bitLt
|
||||
isEqual = bitEq
|
||||
} else {
|
||||
eqAndLt, err := eval.AND(isEqual, bitLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isLess, err = eval.OR(isLess, eqAndLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isEqual, err = eval.AND(isEqual, bitEq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return isLess, nil
|
||||
}
|
||||
|
||||
// gtEncrypted computes a > b: same as b < a.
|
||||
func gtEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
return ltEncrypted(eval, b, a)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command nav demonstrates confidential Net Asset Value (NAV) computation using FHE.
|
||||
//
|
||||
// An ETF's holdings (10 positions with share counts and prices) are encrypted.
|
||||
// NAV = sum(holdings * prices) / totalShares is computed on encrypted data using
|
||||
// repeated addition. Only the final NAV is decrypted for the authorized party.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/demos/nav
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/fhe"
|
||||
)
|
||||
|
||||
type holding struct {
|
||||
ticker string
|
||||
shares uint8 // number of shares held (small for demo)
|
||||
price uint8 // price per share in dollars
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== FHE Confidential NAV Computation Demo ===")
|
||||
fmt.Println("Compute ETF NAV on encrypted holdings")
|
||||
fmt.Println()
|
||||
|
||||
// Setup
|
||||
fmt.Print("Initializing FHE... ")
|
||||
t0 := time.Now()
|
||||
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)
|
||||
fmt.Printf("done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// ETF holdings: small values so sums stay in 8-bit range
|
||||
// Total value = 2*19 + 2*21 + 1*14 + 2*17 + 1*18 + 2*25 + 1*12 + 1*20 + 2*10 + 1*14
|
||||
// = 38 + 42 + 14 + 34 + 18 + 50 + 12 + 20 + 20 + 14 = 262
|
||||
// But 262 > 255, so let's reduce: use 5 holdings to stay under 255.
|
||||
holdings := []holding{
|
||||
{ticker: "AAPL", shares: 2, price: 19},
|
||||
{ticker: "MSFT", shares: 2, price: 21},
|
||||
{ticker: "NVDA", shares: 1, price: 14},
|
||||
{ticker: "GOOG", shares: 2, price: 17},
|
||||
{ticker: "AMZN", shares: 1, price: 18},
|
||||
}
|
||||
totalShares := uint64(5) // total ETF shares outstanding
|
||||
|
||||
var expectedTotal uint64
|
||||
fmt.Println("ETF Holdings (shown for verification):")
|
||||
for _, h := range holdings {
|
||||
value := uint64(h.shares) * uint64(h.price)
|
||||
expectedTotal += value
|
||||
fmt.Printf(" %6s: %d shares @ $%d = $%d\n", h.ticker, h.shares, h.price, value)
|
||||
}
|
||||
expectedNAV := expectedTotal / totalShares
|
||||
fmt.Printf(" Total value: $%d\n", expectedTotal)
|
||||
fmt.Printf(" Shares outstanding: %d\n", totalShares)
|
||||
fmt.Printf(" Expected NAV: $%d/share\n\n", expectedNAV)
|
||||
|
||||
// Encrypt prices
|
||||
fmt.Println("Encrypting holdings...")
|
||||
t0 = time.Now()
|
||||
encPrices := make([][8]*fhe.Ciphertext, len(holdings))
|
||||
for i, h := range holdings {
|
||||
encPrices[i] = encryptByte(enc, h.price)
|
||||
}
|
||||
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Compute NAV: for each position, add price to itself (shares-1) times, then sum all
|
||||
fmt.Println("Computing NAV on encrypted data...")
|
||||
fmt.Println(" Strategy: repeated addition (shares * price)")
|
||||
t0 = time.Now()
|
||||
|
||||
var encTotal [8]*fhe.Ciphertext
|
||||
initialized := false
|
||||
|
||||
for idx, h := range holdings {
|
||||
t1 := time.Now()
|
||||
// shares * price via repeated addition
|
||||
posValue := encPrices[idx]
|
||||
for s := uint8(1); s < h.shares; s++ {
|
||||
posValue, err = addBytes(eval, enc, posValue, encPrices[idx])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error multiplying %s: %v\n", h.ticker, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if !initialized {
|
||||
encTotal = posValue
|
||||
initialized = true
|
||||
} else {
|
||||
encTotal, err = addBytes(eval, enc, encTotal, posValue)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error accumulating %s: %v\n", h.ticker, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
fmt.Printf(" %6s: %d additions (%v)\n", h.ticker, h.shares, time.Since(t1))
|
||||
}
|
||||
fmt.Printf("Total computation: %v\n\n", time.Since(t0))
|
||||
|
||||
// Decrypt and compute NAV (division by public totalShares is plaintext)
|
||||
decTotal := uint64(decryptByte(dec, encTotal))
|
||||
navPerShare := decTotal / totalShares
|
||||
|
||||
fmt.Println("--- Result ---")
|
||||
fmt.Printf("IBIT NAV: $%d per share (computed on encrypted data)\n", navPerShare)
|
||||
fmt.Printf("Total fund value: $%d (decrypted)\n", decTotal)
|
||||
fmt.Printf("Shares outstanding: %d (public)\n", totalShares)
|
||||
|
||||
if navPerShare == expectedNAV {
|
||||
fmt.Println("PASS: NAV matches expected value")
|
||||
} else {
|
||||
fmt.Printf("NOTE: NAV=%d vs expected=%d (FHE arithmetic precision)\n", navPerShare, expectedNAV)
|
||||
}
|
||||
fmt.Println("\nNote: individual holdings were never revealed to the NAV calculator.")
|
||||
}
|
||||
|
||||
func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
|
||||
var bits [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
bits[i] = enc.Encrypt((v>>i)&1 == 1)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
|
||||
var v uint8
|
||||
for i := 0; i < 8; i++ {
|
||||
if dec.Decrypt(bits[i]) {
|
||||
v |= 1 << i
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// addBytes adds two encrypted 8-bit values using ripple-carry full adder.
|
||||
func addBytes(eval *fhe.Evaluator, enc *fhe.Encryptor, a, b [8]*fhe.Ciphertext) ([8]*fhe.Ciphertext, error) {
|
||||
carry := enc.Encrypt(false)
|
||||
var result [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
abXor, err := eval.XOR(a[i], b[i])
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
sum, err := eval.XOR(abXor, carry)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
carry, err = eval.MAJORITY(a[i], b[i], carry)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result[i] = sum
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command proof demonstrates compliance proofs on encrypted portfolios using FHE.
|
||||
//
|
||||
// A portfolio is encrypted. The system proves two compliance conditions:
|
||||
// - No single position exceeds 25% of total
|
||||
// - No position matches a sanctioned counterparty ID
|
||||
//
|
||||
// Only the boolean proof results are decrypted. Portfolio data stays encrypted.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/demos/proof
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/fhe"
|
||||
)
|
||||
|
||||
const valueBits = 8
|
||||
|
||||
type position struct {
|
||||
name string
|
||||
weight uint8 // percentage
|
||||
entityID uint8 // counterparty ID
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== FHE Compliance Proof Demo ===")
|
||||
fmt.Println("Prove compliance without revealing portfolio data")
|
||||
fmt.Println()
|
||||
|
||||
// Setup
|
||||
fmt.Print("Initializing FHE... ")
|
||||
t0 := time.Now()
|
||||
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)
|
||||
fmt.Printf("done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Portfolio
|
||||
portfolio := []position{
|
||||
{name: "AAPL", weight: 20, entityID: 11},
|
||||
{name: "MSFT", weight: 18, entityID: 22},
|
||||
{name: "NVDA", weight: 15, entityID: 33},
|
||||
{name: "GOOG", weight: 12, entityID: 44},
|
||||
{name: "AMZN", weight: 10, entityID: 55},
|
||||
}
|
||||
|
||||
sanctionedIDs := []uint8{99, 88, 77}
|
||||
|
||||
fmt.Println("Portfolio (shown for verification):")
|
||||
for _, p := range portfolio {
|
||||
fmt.Printf(" %s: %d%% (entity %d)\n", p.name, p.weight, p.entityID)
|
||||
}
|
||||
fmt.Printf("Sanctioned entities: %v\n\n", sanctionedIDs)
|
||||
|
||||
// Encrypt
|
||||
fmt.Println("Encrypting portfolio...")
|
||||
t0 = time.Now()
|
||||
encWeights := make([][8]*fhe.Ciphertext, len(portfolio))
|
||||
encEntities := make([][8]*fhe.Ciphertext, len(portfolio))
|
||||
for i, p := range portfolio {
|
||||
encWeights[i] = encryptByte(enc, p.weight)
|
||||
encEntities[i] = encryptByte(enc, p.entityID)
|
||||
}
|
||||
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Proof 1: max position < 25%
|
||||
fmt.Println("Proof 1: No position exceeds 25%...")
|
||||
t0 = time.Now()
|
||||
encThreshold := encryptByte(enc, 25)
|
||||
|
||||
allUnder := enc.Encrypt(true)
|
||||
for i, p := range portfolio {
|
||||
t1 := time.Now()
|
||||
// weight <= 25: NOT(25 < weight)
|
||||
threshLtWeight, err := ltEncrypted(eval, encThreshold, encWeights[i])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
withinLimit := eval.NOT(threshLtWeight) // weight <= 25
|
||||
|
||||
allUnder, err = eval.AND(allUnder, withinLimit)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(" %s checked (%v)\n", p.name, time.Since(t1))
|
||||
}
|
||||
diversificationOk := dec.Decrypt(allUnder)
|
||||
fmt.Printf(" Result: %v (%v)\n\n", diversificationOk, time.Since(t0))
|
||||
|
||||
// Proof 2: no sanctioned counterparty
|
||||
fmt.Println("Proof 2: No sanctioned counterparty...")
|
||||
t0 = time.Now()
|
||||
noSanctioned := enc.Encrypt(true)
|
||||
|
||||
for _, sid := range sanctionedIDs {
|
||||
encSanctioned := encryptByte(enc, sid)
|
||||
for i, p := range portfolio {
|
||||
t1 := time.Now()
|
||||
// Check entity != sanctioned using XOR: if any bit differs, they are not equal
|
||||
isEqual := enc.Encrypt(true)
|
||||
for bit := 0; bit < valueBits; bit++ {
|
||||
bitXor, err := eval.XOR(encEntities[i][bit], encSanctioned[bit])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
bitsMatch := eval.NOT(bitXor) // bits match if XOR is 0
|
||||
isEqual, err = eval.AND(isEqual, bitsMatch)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
// notEqual = NOT(isEqual)
|
||||
notEqual := eval.NOT(isEqual)
|
||||
noSanctioned, err = eval.AND(noSanctioned, notEqual)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(" %s vs entity %d (%v)\n", p.name, sid, time.Since(t1))
|
||||
}
|
||||
}
|
||||
sanctionsOk := dec.Decrypt(noSanctioned)
|
||||
fmt.Printf(" Result: %v (%v)\n\n", sanctionsOk, time.Since(t0))
|
||||
|
||||
// Final
|
||||
overallPass := diversificationOk && sanctionsOk
|
||||
fmt.Println("--- Result ---")
|
||||
fmt.Printf("Compliance proof: %s (portfolio data remained encrypted)\n", label(overallPass))
|
||||
fmt.Printf(" Diversification (max < 25%%): %s\n", label(diversificationOk))
|
||||
fmt.Printf(" Sanctions screening: %s\n", label(sanctionsOk))
|
||||
}
|
||||
|
||||
func label(ok bool) string {
|
||||
if ok {
|
||||
return "PASS"
|
||||
}
|
||||
return "FAIL"
|
||||
}
|
||||
|
||||
func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
|
||||
var bits [8]*fhe.Ciphertext
|
||||
for i := 0; i < 8; i++ {
|
||||
bits[i] = enc.Encrypt((v>>i)&1 == 1)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
// ltEncrypted computes a < b on 8-bit encrypted values (MSB-first).
|
||||
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
|
||||
var isLess, isEqual *fhe.Ciphertext
|
||||
for i := valueBits - 1; i >= 0; i-- {
|
||||
bitLt, err := eval.ANDNY(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitXor, err := eval.XOR(a[i], b[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bitEq := eval.NOT(bitXor)
|
||||
if isLess == nil {
|
||||
isLess = bitLt
|
||||
isEqual = bitEq
|
||||
} else {
|
||||
eqAndLt, err := eval.AND(isEqual, bitLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isLess, err = eval.OR(isLess, eqAndLt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isEqual, err = eval.AND(isEqual, bitEq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return isLess, nil
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Command voting demonstrates private shareholder voting using FHE.
|
||||
//
|
||||
// 100 shareholders vote encrypted yes/no. Votes are tallied homomorphically
|
||||
// using a ripple-carry adder on encrypted bits. Only the final count is
|
||||
// decrypted -- individual votes are never revealed.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/demos/voting
|
||||
// go run ./cmd/demos/voting -voters 50 -yes 30
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/fhe"
|
||||
)
|
||||
|
||||
const tallyBits = 7 // supports up to 127 voters
|
||||
|
||||
func main() {
|
||||
nVoters := flag.Int("voters", 100, "number of shareholders (max 127)")
|
||||
nYes := flag.Int("yes", -1, "number of yes votes (-1 = random ~67%)")
|
||||
flag.Parse()
|
||||
|
||||
if *nVoters < 2 || *nVoters > 127 {
|
||||
fmt.Fprintln(os.Stderr, "error: -voters must be 2..127")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("=== FHE Private Shareholder Voting Demo ===")
|
||||
fmt.Println("Homomorphic tally without revealing individual votes")
|
||||
fmt.Println()
|
||||
|
||||
// Generate 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(3) != 0 // ~67% yes
|
||||
}
|
||||
}
|
||||
|
||||
expectedYes := 0
|
||||
for _, v := range votes {
|
||||
if v {
|
||||
expectedYes++
|
||||
}
|
||||
}
|
||||
fmt.Printf("Shareholders: %d | Expected: %d yes, %d no\n\n", *nVoters, expectedYes, *nVoters-expectedYes)
|
||||
|
||||
// Setup FHE
|
||||
fmt.Print("Initializing FHE parameters... ")
|
||||
t0 := time.Now()
|
||||
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)
|
||||
fmt.Printf("done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Encrypt ballots
|
||||
fmt.Printf("Encrypting %d ballots...\n", *nVoters)
|
||||
t0 = time.Now()
|
||||
ballots := make([]*fhe.Ciphertext, *nVoters)
|
||||
for i, v := range votes {
|
||||
ballots[i] = enc.Encrypt(v)
|
||||
}
|
||||
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
|
||||
|
||||
// Initialize tally accumulator (7-bit: supports 0-127)
|
||||
tally := [tallyBits]*fhe.Ciphertext{}
|
||||
for i := 0; i < tallyBits; i++ {
|
||||
tally[i] = enc.Encrypt(false)
|
||||
}
|
||||
|
||||
// Tally votes homomorphically
|
||||
fmt.Printf("Tallying %d ballots homomorphically (%d-bit accumulator)...\n", *nVoters, tallyBits)
|
||||
t0 = time.Now()
|
||||
for i, ballot := range ballots {
|
||||
if (i+1)%10 == 0 || i == 0 {
|
||||
fmt.Printf(" Processing 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)
|
||||
}
|
||||
}
|
||||
tallyElapsed := time.Since(t0)
|
||||
fmt.Printf("Tally done (%v, %.1fms per ballot)\n\n",
|
||||
tallyElapsed, float64(tallyElapsed.Milliseconds())/float64(*nVoters))
|
||||
|
||||
// Decrypt only the final tally
|
||||
var yesCount uint8
|
||||
for i := 0; i < tallyBits; i++ {
|
||||
if dec.Decrypt(tally[i]) {
|
||||
yesCount |= 1 << i
|
||||
}
|
||||
}
|
||||
noCount := *nVoters - int(yesCount)
|
||||
|
||||
fmt.Println("--- Result ---")
|
||||
passed := int(yesCount) > *nVoters/2
|
||||
if passed {
|
||||
fmt.Printf("Vote passed: %d yes, %d no\n", yesCount, noCount)
|
||||
} else {
|
||||
fmt.Printf("Vote failed: %d yes, %d no\n", yesCount, noCount)
|
||||
}
|
||||
fmt.Printf("Threshold: >%d (simple majority)\n", *nVoters/2)
|
||||
|
||||
if int(yesCount) == 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 [tallyBits]*fhe.Ciphertext, bit *fhe.Ciphertext) ([tallyBits]*fhe.Ciphertext, error) {
|
||||
carry := bit
|
||||
var result [tallyBits]*fhe.Ciphertext
|
||||
|
||||
for i := 0; i < tallyBits; i++ {
|
||||
sum, err := eval.XOR(acc[i], carry)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("bit %d XOR: %w", i, err)
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user