rip(demos): delete cmd/demos/ — kill -tags demos, one way only

The 8 demo mains imported github.com/hanzoai/base (HTTP server scaffold)
which pulled the legacy Google Cloud / temporal / grpc transitive chain.
Demos were the only consumers of that chain in the fhe tree.

One way only: fhe is a cryptographic library, not an app harness. App
authors who want HTTP wiring can vendor hanzoai/base directly in their
own cmd binary — fhe doesn't ship that scaffold.
This commit is contained in:
Hanzo AI
2026-05-21 18:46:28 -07:00
parent 51f818bf56
commit dc279ecced
9 changed files with 0 additions and 2165 deletions
-102
View File
@@ -1,102 +0,0 @@
# 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)
-221
View File
@@ -1,221 +0,0 @@
//go:build demos
// 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.
//
// 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 serve
package main
import (
"fmt"
"log"
"sync"
"github.com/google/uuid"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const bidBits = 8
type encBid struct {
ID string
Participant string
EncAmount [8]*fhe.Ciphertext
}
type winnerResult struct {
ID string `json:"id"`
Participant string `json:"participant"`
Amount uint8 `json:"amount"`
}
type fheState struct {
mu sync.Mutex
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
bids []encBid
winner *winnerResult
}
func main() {
app := base.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
st, err := initFHE()
if err != nil {
return fmt.Errorf("fhe init: %w", err)
}
registerRoutes(se, st)
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// POST /v1/bids — submit sealed encrypted bid
g.POST("/bids", func(re *core.RequestEvent) error {
var req struct {
Participant string `json:"participant"`
Amount uint8 `json:"amount"`
}
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
if req.Participant == "" {
return re.JSON(400, map[string]string{"error": "participant required"})
}
st.mu.Lock()
defer st.mu.Unlock()
b := encBid{
ID: uuid.NewString(),
Participant: req.Participant,
EncAmount: encryptByte(st.enc, req.Amount),
}
st.bids = append(st.bids, b)
st.winner = nil
return re.JSON(201, map[string]string{
"id": b.ID,
"participant": b.Participant,
})
})
// POST /v1/determine-winner — homomorphic max comparison
g.POST("/determine-winner", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if len(st.bids) < 2 {
return re.JSON(400, map[string]string{"error": "need at least 2 bids"})
}
winnerIdx := 0
for i := 1; i < len(st.bids); i++ {
gtResult, err := gtEncrypted(st.eval, st.bids[i].EncAmount, st.bids[winnerIdx].EncAmount)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
if st.dec.Decrypt(gtResult) {
winnerIdx = i
}
}
st.winner = &winnerResult{
ID: st.bids[winnerIdx].ID,
Participant: st.bids[winnerIdx].Participant,
}
return re.JSON(200, map[string]string{
"winner_id": st.winner.ID,
"participant": st.winner.Participant,
"message": "winner determined, call POST /v1/reveal to decrypt amount",
})
})
// POST /v1/reveal — decrypt winning bid only
g.POST("/reveal", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if st.winner == nil {
return re.JSON(400, map[string]string{"error": "no winner determined yet"})
}
// Find the winner's encrypted bid
for _, b := range st.bids {
if b.ID == st.winner.ID {
st.winner.Amount = decryptByte(st.dec, b.EncAmount)
break
}
}
return re.JSON(200, st.winner)
})
}
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
}
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
}
func gtEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
return ltEncrypted(eval, b, a)
}
-248
View File
@@ -1,248 +0,0 @@
//go:build demos
// 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 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 serve
package main
import (
"fmt"
"log"
"sync"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const valueBits = 8
type encPosition struct {
Name string
EncWeight [8]*fhe.Ciphertext
}
type fheState struct {
mu sync.Mutex
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
portfolio []encPosition
result *complianceResult
}
type complianceResult struct {
Diversification bool `json:"diversification"`
ExposureUnder80 bool `json:"exposure_under_80"`
Overall bool `json:"overall"`
}
func main() {
app := base.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
st, err := initFHE()
if err != nil {
return fmt.Errorf("fhe init: %w", err)
}
registerRoutes(se, st)
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// POST /v1/portfolio — submit encrypted holdings
g.POST("/portfolio", func(re *core.RequestEvent) error {
var req struct {
Positions []struct {
Name string `json:"name"`
Weight uint8 `json:"weight"`
} `json:"positions"`
}
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
if len(req.Positions) == 0 {
return re.JSON(400, map[string]string{"error": "positions required"})
}
st.mu.Lock()
defer st.mu.Unlock()
st.portfolio = nil
st.result = nil
for _, p := range req.Positions {
st.portfolio = append(st.portfolio, encPosition{
Name: p.Name,
EncWeight: encryptByte(st.enc, p.Weight),
})
}
return re.JSON(201, map[string]any{
"count": len(st.portfolio),
"message": "portfolio encrypted",
})
})
// POST /v1/check — run SEC diversification check homomorphically
g.POST("/check", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if len(st.portfolio) == 0 {
return re.JSON(400, map[string]string{"error": "no portfolio loaded"})
}
// Check 1: no single position > 25%
encThreshold := encryptByte(st.enc, 25)
allCompliant := st.enc.Encrypt(true)
for _, p := range st.portfolio {
leResult, err := leEncrypted(st.eval, p.EncWeight, encThreshold)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
allCompliant, err = st.eval.AND(allCompliant, leResult)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
check1 := st.dec.Decrypt(allCompliant)
// Check 2: total exposure < 80%
encTotal := st.portfolio[0].EncWeight
var err error
for i := 1; i < len(st.portfolio); i++ {
encTotal, err = addBytes(st.eval, st.enc, encTotal, st.portfolio[i].EncWeight)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
encMaxExposure := encryptByte(st.enc, 80)
ltResult, err := ltEncrypted(st.eval, encTotal, encMaxExposure)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
check2 := st.dec.Decrypt(ltResult)
st.result = &complianceResult{
Diversification: check1,
ExposureUnder80: check2,
Overall: check1 && check2,
}
return re.JSON(200, st.result)
})
// GET /v1/result — get compliance result (boolean, not holdings)
g.GET("/result", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if st.result == nil {
return re.JSON(404, map[string]string{"error": "no check run yet"})
}
return re.JSON(200, st.result)
})
}
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 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
}
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
}
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
}
-253
View File
@@ -1,253 +0,0 @@
//go:build demos
// 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 serve
package main
import (
"fmt"
"log"
"sync"
"github.com/google/uuid"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const priceBits = 8
type encOrder struct {
ID string
Side string // "bid" or "ask"
EncPrice [8]*fhe.Ciphertext
EncQty [8]*fhe.Ciphertext
}
type matchResult struct {
BidID string `json:"bid_id"`
AskID string `json:"ask_id"`
}
type fheState struct {
mu sync.Mutex
params fhe.Parameters
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
orders map[string]*encOrder
matches []matchResult
}
func main() {
app := base.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
st, err := initFHE()
if err != nil {
return fmt.Errorf("fhe init: %w", err)
}
registerRoutes(se, st)
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
return &fheState{
params: params,
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
orders: make(map[string]*encOrder),
matches: nil,
}, nil
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// POST /v1/orders — submit encrypted order
g.POST("/orders", func(re *core.RequestEvent) error {
var req struct {
Side string `json:"side"`
Price uint8 `json:"price"`
Qty uint8 `json:"qty"`
}
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
if req.Side != "bid" && req.Side != "ask" {
return re.JSON(400, map[string]string{"error": "side must be bid or ask"})
}
st.mu.Lock()
defer st.mu.Unlock()
id := uuid.NewString()
o := &encOrder{
ID: id,
Side: req.Side,
EncPrice: encryptByte(st.enc, req.Price),
EncQty: encryptByte(st.enc, req.Qty),
}
st.orders[id] = o
return re.JSON(201, map[string]string{"id": id, "side": req.Side})
})
// GET /v1/orders — list orders (IDs + side only, values encrypted)
g.GET("/orders", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
type entry struct {
ID string `json:"id"`
Side string `json:"side"`
EncPrice string `json:"enc_price"`
}
var out []entry
for _, o := range st.orders {
out = append(out, entry{
ID: o.ID,
Side: o.Side,
EncPrice: "(encrypted)",
})
}
return re.JSON(200, out)
})
// POST /v1/match — run homomorphic matching on all encrypted orders
g.POST("/match", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
var bids, asks []*encOrder
for _, o := range st.orders {
if o.Side == "bid" {
bids = append(bids, o)
} else {
asks = append(asks, o)
}
}
st.matches = nil
for _, bid := range bids {
for _, ask := range asks {
isGe, err := geEncrypted(st.eval, bid.EncPrice, ask.EncPrice)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
if st.dec.Decrypt(isGe) {
st.matches = append(st.matches, matchResult{
BidID: bid.ID,
AskID: ask.ID,
})
}
}
}
return re.JSON(200, map[string]any{
"matches": st.matches,
"count": len(st.matches),
})
})
// POST /v1/reveal/{id} — decrypt an order by ID
g.POST("/reveal/{id}", func(re *core.RequestEvent) error {
id := re.Request.PathValue("id")
st.mu.Lock()
defer st.mu.Unlock()
o, ok := st.orders[id]
if !ok {
return re.JSON(404, map[string]string{"error": "order not found"})
}
price := decryptByte(st.dec, o.EncPrice)
qty := decryptByte(st.dec, o.EncQty)
return re.JSON(200, map[string]any{
"id": o.ID,
"side": o.Side,
"price": price,
"qty": qty,
})
})
}
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.
func geEncrypted(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])
if err != nil {
return nil, fmt.Errorf("bit %d ANDNY: %w", i, err)
}
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 {
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 eval.NOT(isLess), nil
}
-231
View File
@@ -1,231 +0,0 @@
//go:build demos
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command marketmaker demonstrates private market making with FHE.
//
// 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 serve
package main
import (
"fmt"
"log"
"sync"
"github.com/google/uuid"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const priceBits = 8
type encQuote struct {
ID string
Maker string
EncBid [8]*fhe.Ciphertext
EncAsk [8]*fhe.Ciphertext
}
type spreadResult struct {
BestBidMaker string `json:"best_bid_maker"`
BestBidPrice uint8 `json:"best_bid_price"`
BestAskMaker string `json:"best_ask_maker"`
BestAskPrice uint8 `json:"best_ask_price"`
Spread int `json:"spread"`
}
type fheState struct {
mu sync.Mutex
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
quotes []encQuote
spread *spreadResult
}
func main() {
app := base.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
st, err := initFHE()
if err != nil {
return fmt.Errorf("fhe init: %w", err)
}
registerRoutes(se, st)
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// POST /v1/quotes — submit encrypted quote
g.POST("/quotes", func(re *core.RequestEvent) error {
var req struct {
Maker string `json:"maker"`
Bid uint8 `json:"bid"`
Ask uint8 `json:"ask"`
}
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
if req.Maker == "" {
return re.JSON(400, map[string]string{"error": "maker required"})
}
st.mu.Lock()
defer st.mu.Unlock()
q := encQuote{
ID: uuid.NewString(),
Maker: req.Maker,
EncBid: encryptByte(st.enc, req.Bid),
EncAsk: encryptByte(st.enc, req.Ask),
}
st.quotes = append(st.quotes, q)
st.spread = nil // invalidate cached spread
return re.JSON(201, map[string]string{"id": q.ID, "maker": q.Maker})
})
// POST /v1/best — compute best bid/ask homomorphically
g.POST("/best", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if len(st.quotes) < 2 {
return re.JSON(400, map[string]string{"error": "need at least 2 quotes"})
}
// Best bid (highest)
bestBidIdx := 0
for i := 1; i < len(st.quotes); i++ {
gtResult, err := gtEncrypted(st.eval, st.quotes[i].EncBid, st.quotes[bestBidIdx].EncBid)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
if st.dec.Decrypt(gtResult) {
bestBidIdx = i
}
}
// Best ask (lowest)
bestAskIdx := 0
for i := 1; i < len(st.quotes); i++ {
ltResult, err := ltEncrypted(st.eval, st.quotes[i].EncAsk, st.quotes[bestAskIdx].EncAsk)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
if st.dec.Decrypt(ltResult) {
bestAskIdx = i
}
}
bestBid := decryptByte(st.dec, st.quotes[bestBidIdx].EncBid)
bestAsk := decryptByte(st.dec, st.quotes[bestAskIdx].EncAsk)
st.spread = &spreadResult{
BestBidMaker: st.quotes[bestBidIdx].Maker,
BestBidPrice: bestBid,
BestAskMaker: st.quotes[bestAskIdx].Maker,
BestAskPrice: bestAsk,
Spread: int(bestAsk) - int(bestBid),
}
return re.JSON(200, st.spread)
})
// GET /v1/spread — get cached spread
g.GET("/spread", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if st.spread == nil {
return re.JSON(404, map[string]string{"error": "no spread computed yet"})
}
return re.JSON(200, st.spread)
})
}
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
}
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])
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
}
func gtEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
return ltEncrypted(eval, b, a)
}
-221
View File
@@ -1,221 +0,0 @@
//go:build demos
// 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 (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 serve
package main
import (
"fmt"
"log"
"sync"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
type encHolding struct {
Ticker string
Shares uint8
EncPrice [8]*fhe.Ciphertext
}
type navResult struct {
TotalValue uint64 `json:"total_value"`
SharesOutstanding uint64 `json:"shares_outstanding"`
NAVPerShare uint64 `json:"nav_per_share"`
}
type fheState struct {
mu sync.Mutex
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
holdings []encHolding
sharesOutstanding uint64
result *navResult
}
func main() {
app := base.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
st, err := initFHE()
if err != nil {
return fmt.Errorf("fhe init: %w", err)
}
registerRoutes(se, st)
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// POST /v1/holdings — submit encrypted holdings
g.POST("/holdings", func(re *core.RequestEvent) error {
var req struct {
SharesOutstanding uint64 `json:"shares_outstanding"`
Holdings []struct {
Ticker string `json:"ticker"`
Shares uint8 `json:"shares"`
Price uint8 `json:"price"`
} `json:"holdings"`
}
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
if len(req.Holdings) == 0 {
return re.JSON(400, map[string]string{"error": "holdings required"})
}
if req.SharesOutstanding == 0 {
return re.JSON(400, map[string]string{"error": "shares_outstanding required"})
}
st.mu.Lock()
defer st.mu.Unlock()
st.holdings = nil
st.result = nil
st.sharesOutstanding = req.SharesOutstanding
for _, h := range req.Holdings {
st.holdings = append(st.holdings, encHolding{
Ticker: h.Ticker,
Shares: h.Shares,
EncPrice: encryptByte(st.enc, h.Price),
})
}
return re.JSON(201, map[string]any{
"count": len(st.holdings),
"message": "holdings encrypted",
})
})
// POST /v1/compute — compute NAV homomorphically
g.POST("/compute", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if len(st.holdings) == 0 {
return re.JSON(400, map[string]string{"error": "no holdings loaded"})
}
var encTotal [8]*fhe.Ciphertext
initialized := false
for _, h := range st.holdings {
// shares * price via repeated addition
posValue := h.EncPrice
var err error
for s := uint8(1); s < h.Shares; s++ {
posValue, err = addBytes(st.eval, st.enc, posValue, h.EncPrice)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
if !initialized {
encTotal = posValue
initialized = true
} else {
encTotal, err = addBytes(st.eval, st.enc, encTotal, posValue)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
}
decTotal := uint64(decryptByte(st.dec, encTotal))
st.result = &navResult{
TotalValue: decTotal,
SharesOutstanding: st.sharesOutstanding,
NAVPerShare: decTotal / st.sharesOutstanding,
}
return re.JSON(200, map[string]string{
"message": "NAV computed, call GET /v1/nav to see result",
})
})
// GET /v1/nav — get encrypted NAV value
g.GET("/nav", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if st.result == nil {
return re.JSON(404, map[string]string{"error": "no NAV computed yet"})
}
return re.JSON(200, st.result)
})
}
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
}
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
}
-242
View File
@@ -1,242 +0,0 @@
//go:build demos
// 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 serve
package main
import (
"fmt"
"log"
"sync"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const valueBits = 8
type encPosition struct {
Name string
EncWeight [8]*fhe.Ciphertext
EncEntity [8]*fhe.Ciphertext
}
type proofResult struct {
Diversification bool `json:"diversification"`
Sanctions bool `json:"sanctions"`
Overall bool `json:"overall"`
}
type fheState struct {
mu sync.Mutex
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
portfolio []encPosition
sanctionedIDs []uint8
result *proofResult
}
func main() {
app := base.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
st, err := initFHE()
if err != nil {
return fmt.Errorf("fhe init: %w", err)
}
registerRoutes(se, st)
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// POST /v1/positions — submit encrypted positions
g.POST("/positions", func(re *core.RequestEvent) error {
var req struct {
SanctionedIDs []uint8 `json:"sanctioned_ids"`
Positions []struct {
Name string `json:"name"`
Weight uint8 `json:"weight"`
EntityID uint8 `json:"entity_id"`
} `json:"positions"`
}
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
if len(req.Positions) == 0 {
return re.JSON(400, map[string]string{"error": "positions required"})
}
st.mu.Lock()
defer st.mu.Unlock()
st.portfolio = nil
st.result = nil
st.sanctionedIDs = req.SanctionedIDs
for _, p := range req.Positions {
st.portfolio = append(st.portfolio, encPosition{
Name: p.Name,
EncWeight: encryptByte(st.enc, p.Weight),
EncEntity: encryptByte(st.enc, p.EntityID),
})
}
return re.JSON(201, map[string]any{
"count": len(st.portfolio),
"sanctioned": len(st.sanctionedIDs),
"message": "portfolio encrypted",
})
})
// POST /v1/prove — generate compliance proof (boolean output)
g.POST("/prove", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if len(st.portfolio) == 0 {
return re.JSON(400, map[string]string{"error": "no portfolio loaded"})
}
// Proof 1: no position > 25%
encThreshold := encryptByte(st.enc, 25)
allUnder := st.enc.Encrypt(true)
for _, p := range st.portfolio {
threshLtWeight, err := ltEncrypted(st.eval, encThreshold, p.EncWeight)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
withinLimit := st.eval.NOT(threshLtWeight)
allUnder, err = st.eval.AND(allUnder, withinLimit)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
diversificationOk := st.dec.Decrypt(allUnder)
// Proof 2: no sanctioned counterparty
noSanctioned := st.enc.Encrypt(true)
for _, sid := range st.sanctionedIDs {
encSanctioned := encryptByte(st.enc, sid)
for _, p := range st.portfolio {
isEqual := st.enc.Encrypt(true)
for bit := 0; bit < valueBits; bit++ {
bitXor, err := st.eval.XOR(p.EncEntity[bit], encSanctioned[bit])
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
bitsMatch := st.eval.NOT(bitXor)
isEqual, err = st.eval.AND(isEqual, bitsMatch)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
notEqual := st.eval.NOT(isEqual)
var err error
noSanctioned, err = st.eval.AND(noSanctioned, notEqual)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
}
sanctionsOk := st.dec.Decrypt(noSanctioned)
st.result = &proofResult{
Diversification: diversificationOk,
Sanctions: sanctionsOk,
Overall: diversificationOk && sanctionsOk,
}
return re.JSON(200, map[string]string{
"message": "proof generated, call GET /v1/proof to see result",
})
})
// GET /v1/proof — get proof result
g.GET("/proof", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if st.result == nil {
return re.JSON(404, map[string]string{"error": "no proof generated yet"})
}
return re.JSON(200, st.result)
})
}
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 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
}
-464
View File
@@ -1,464 +0,0 @@
//go:build demos
// regulated-vault — Private app where operators can't see user data,
// but regulators can force-decrypt via MPC threshold approval.
//
// Architecture:
// User encrypts data client-side with FHE public key.
// Encrypted data stored in Base SQLite — operator sees only ciphertext.
// Decryption requires t-of-n MPC key holders to approve.
// Normal operation: nobody can decrypt. Not the operator, not any single party.
// Regulatory subpoena: regulator requests decrypt → t-of-n holders approve → plaintext revealed.
//
// Run:
// go run . serve --http=0.0.0.0:8090
//
// Endpoints:
// POST /v1/vault/store — store encrypted data (operator never sees plaintext)
// GET /v1/vault/list — list records (all encrypted, no plaintext)
// POST /v1/vault/compute — run computation on encrypted data (homomorphic)
// POST /v1/vault/request — request decryption (creates approval request)
// GET /v1/vault/requests — list pending decryption requests
// POST /v1/vault/approve/{id} — MPC key holder approves decryption
// GET /v1/vault/reveal/{id} — get decrypted result (only after t-of-n approvals)
// GET /v1/vault/audit — audit trail of all decrypt requests + approvals
//
// The key insight: the FHE secret key is Shamir-split across N MPC nodes.
// No single node (including the operator) holds the full key.
// Decryption requires t-of-n shares to be combined — a social/legal process.
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
// state holds FHE parameters and the threshold key management state.
type state struct {
mu sync.RWMutex
params fhe.Parameters
pk *fhe.PublicKey // public — anyone can encrypt
sk *fhe.SecretKey // NEVER stored whole. Split into shares.
bsk *fhe.BootstrapKey // for homomorphic evaluation
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
// Threshold key management
threshold int // t — minimum approvals needed
totalKeys int // n — total key holders
shares [][]byte // Shamir shares of the secret key (in production, distributed to MPC nodes)
records []vaultRecord // encrypted records
requests []decryptRequest
}
type vaultRecord struct {
ID string `json:"id"`
Owner string `json:"owner"` // user who stored it
Label string `json:"label"` // human-readable label
EncryptedData []byte `json:"encrypted_data"` // FHE ciphertext — operator CANNOT read this
DataHash string `json:"data_hash"` // SHA-256 of plaintext (for integrity, not revealing)
CreatedAt time.Time `json:"created_at"`
}
type decryptRequest struct {
ID string `json:"id"`
RecordID string `json:"record_id"`
Requester string `json:"requester"` // who requested (e.g., "SEC", "FINRA", "user")
Reason string `json:"reason"` // legal basis
Status string `json:"status"` // pending, approved, denied, revealed
Approvals []approval `json:"approvals"`
Required int `json:"required"` // t — threshold
Plaintext *string `json:"plaintext,omitempty"` // only populated after threshold met
CreatedAt time.Time `json:"created_at"`
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
}
type approval struct {
KeyHolder string `json:"key_holder"` // which MPC node approved
Share int `json:"share_idx"` // which Shamir share was used
Timestamp time.Time `json:"timestamp"`
}
func main() {
app := base.New()
s := &state{
threshold: 3, // 3-of-5 required to decrypt
totalKeys: 5,
}
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
// Initialize FHE
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "FHE init failed: %v\n", err)
os.Exit(1)
}
s.params = params
keygen := fhe.NewKeyGenerator(params)
sk, pk := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
s.pk = pk
s.sk = sk
s.bsk = bsk
s.enc = fhe.NewEncryptor(params, sk)
s.dec = fhe.NewDecryptor(params, sk)
s.eval = fhe.NewEvaluator(params, bsk)
// WARNING: In this demo, the full FHE secret key is held in memory by a
// single process. Production requires Shamir-splitting sk across N MPC
// nodes with threshold reconstruction — no single party holds the full key.
s.shares = shamirSplit([]byte("simulated-secret-key"), s.totalKeys, s.threshold)
fmt.Printf("\n")
fmt.Printf(" ============================================================\n")
fmt.Printf(" WARNING: DEMO ONLY — operator holds full FHE key in memory.\n")
fmt.Printf(" Production requires distributed threshold decryption via MPC.\n")
fmt.Printf(" DO NOT deploy this binary with real user data.\n")
fmt.Printf(" ============================================================\n")
fmt.Printf("\n")
fmt.Printf(" Regulated Vault (FHE + MPC Threshold Decrypt)\n")
fmt.Printf(" ─────────────────────────────────────────────\n")
fmt.Printf(" Threshold: %d-of-%d required to decrypt\n", s.threshold, s.totalKeys)
fmt.Printf(" Operator: CANNOT see plaintext (only ciphertext)\n")
fmt.Printf(" Regulator: CAN force-decrypt with %d approvals\n", s.threshold)
fmt.Printf(" User: CAN store + compute on encrypted data\n")
fmt.Printf("\n")
registerRoutes(e, s)
return e.Next()
})
app.Start()
}
func registerRoutes(e *core.ServeEvent, s *state) {
// Store encrypted data — operator never sees plaintext
e.Router.POST("/v1/vault/store", func(re *core.RequestEvent) error {
var body struct {
Owner string `json:"owner"`
Label string `json:"label"`
Plaintext string `json:"plaintext"` // sent by user, encrypted immediately, never stored
}
json.NewDecoder(re.Request.Body).Decode(&body)
if body.Plaintext == "" {
return re.JSON(http.StatusBadRequest, map[string]string{"error": "plaintext required"})
}
// Encrypt each byte with FHE — after this, plaintext is GONE from server memory
s.mu.Lock()
encrypted := make([]byte, 0)
for _, b := range []byte(body.Plaintext) {
bits := s.enc.EncryptByte(b) // [8]*Ciphertext, one per bit
for _, ct := range bits {
data, _ := ct.MarshalBinary()
encrypted = append(encrypted, data...)
}
}
// Hash plaintext for integrity verification (hash doesn't reveal content)
hash := sha256.Sum256([]byte(body.Plaintext))
id := generateID()
record := vaultRecord{
ID: id,
Owner: body.Owner,
Label: body.Label,
EncryptedData: encrypted,
DataHash: hex.EncodeToString(hash[:]),
CreatedAt: time.Now().UTC(),
}
s.records = append(s.records, record)
s.mu.Unlock()
// Plaintext is now out of scope — GC will collect it.
// Only the FHE ciphertext persists.
return re.JSON(http.StatusCreated, map[string]any{
"id": id,
"label": body.Label,
"data_hash": record.DataHash,
"encrypted": true,
"message": "Data encrypted with FHE. Operator cannot read it. Decryption requires " + fmt.Sprintf("%d-of-%d", s.threshold, s.totalKeys) + " MPC approvals.",
})
})
// List records — all encrypted, no plaintext ever visible
e.Router.GET("/v1/vault/list", func(re *core.RequestEvent) error {
s.mu.RLock()
defer s.mu.RUnlock()
items := make([]map[string]any, 0, len(s.records))
for _, r := range s.records {
items = append(items, map[string]any{
"id": r.ID,
"owner": r.Owner,
"label": r.Label,
"encrypted_size": len(r.EncryptedData),
"data_hash": r.DataHash,
"created_at": r.CreatedAt,
// NOTE: encrypted_data is NOT returned. Operator sees metadata only.
})
}
return re.JSON(http.StatusOK, map[string]any{
"items": items,
"message": "All data is FHE-encrypted. Plaintext is not available without threshold decryption.",
})
})
// Request decryption — starts the approval process
e.Router.POST("/v1/vault/request", func(re *core.RequestEvent) error {
var body struct {
RecordID string `json:"record_id"`
Requester string `json:"requester"` // "SEC", "FINRA", "user", etc.
Reason string `json:"reason"` // legal basis for decryption
}
json.NewDecoder(re.Request.Body).Decode(&body)
if body.RecordID == "" || body.Requester == "" || body.Reason == "" {
return re.JSON(http.StatusBadRequest, map[string]string{"error": "record_id, requester, and reason required"})
}
s.mu.Lock()
defer s.mu.Unlock()
// Verify record exists
found := false
for _, r := range s.records {
if r.ID == body.RecordID {
found = true
break
}
}
if !found {
return re.JSON(http.StatusNotFound, map[string]string{"error": "record not found"})
}
id := generateID()
req := decryptRequest{
ID: id,
RecordID: body.RecordID,
Requester: body.Requester,
Reason: body.Reason,
Status: "pending",
Required: s.threshold,
CreatedAt: time.Now().UTC(),
}
s.requests = append(s.requests, req)
return re.JSON(http.StatusCreated, map[string]any{
"id": id,
"status": "pending",
"required": s.threshold,
"approvals": 0,
"message": fmt.Sprintf("Decryption request created. Requires %d-of-%d MPC key holder approvals.", s.threshold, s.totalKeys),
})
})
// List pending requests
e.Router.GET("/v1/vault/requests", func(re *core.RequestEvent) error {
s.mu.RLock()
defer s.mu.RUnlock()
return re.JSON(http.StatusOK, map[string]any{"items": s.requests})
})
// Approve decryption — each MPC key holder calls this
e.Router.POST("/v1/vault/approve/{id}", func(re *core.RequestEvent) error {
id := re.Request.PathValue("id")
var body struct {
KeyHolder string `json:"key_holder"` // MPC node identity
ShareIdx int `json:"share_idx"` // which Shamir share they hold
}
json.NewDecoder(re.Request.Body).Decode(&body)
s.mu.Lock()
defer s.mu.Unlock()
for i, req := range s.requests {
if req.ID == id {
if req.Status != "pending" {
return re.JSON(http.StatusConflict, map[string]string{"error": "request already " + req.Status})
}
// Check for duplicate approval from same holder
for _, a := range req.Approvals {
if a.KeyHolder == body.KeyHolder {
return re.JSON(http.StatusConflict, map[string]string{"error": "already approved by this key holder"})
}
}
s.requests[i].Approvals = append(s.requests[i].Approvals, approval{
KeyHolder: body.KeyHolder,
Share: body.ShareIdx,
Timestamp: time.Now().UTC(),
})
remaining := req.Required - len(s.requests[i].Approvals)
if remaining <= 0 {
// Threshold met — decrypt
s.requests[i].Status = "approved"
now := time.Now().UTC()
s.requests[i].ResolvedAt = &now
// Find the record and decrypt
for _, r := range s.records {
if r.ID == req.RecordID {
// In production: combine Shamir shares from MPC nodes to reconstruct sk
// Here we use the full sk (simulated threshold reconstruction)
plaintext := decryptRecord(s, r.EncryptedData)
s.requests[i].Plaintext = &plaintext
break
}
}
return re.JSON(http.StatusOK, map[string]any{
"id": id,
"status": "approved",
"approvals": len(s.requests[i].Approvals),
"message": "Threshold met. Decryption complete. Use GET /v1/vault/reveal/" + id + " to retrieve.",
})
}
return re.JSON(http.StatusOK, map[string]any{
"id": id,
"status": "pending",
"approvals": len(s.requests[i].Approvals),
"remaining": remaining,
"message": fmt.Sprintf("Approval recorded. %d more needed.", remaining),
})
}
}
return re.JSON(http.StatusNotFound, map[string]string{"error": "request not found"})
})
// Reveal decrypted data — only available after threshold approvals
e.Router.GET("/v1/vault/reveal/{id}", func(re *core.RequestEvent) error {
id := re.Request.PathValue("id")
s.mu.RLock()
defer s.mu.RUnlock()
for _, req := range s.requests {
if req.ID == id {
if req.Status != "approved" {
return re.JSON(http.StatusForbidden, map[string]any{
"error": "decryption not yet approved",
"status": req.Status,
"approvals": len(req.Approvals),
"required": req.Required,
})
}
return re.JSON(http.StatusOK, map[string]any{
"warning": "DEMO ONLY — operator holds full FHE key. Production requires MPC threshold decryption.",
"id": id,
"record_id": req.RecordID,
"requester": req.Requester,
"reason": req.Reason,
"plaintext": req.Plaintext,
"approvals": req.Approvals,
"resolved_at": req.ResolvedAt,
})
}
}
return re.JSON(http.StatusNotFound, map[string]string{"error": "request not found"})
})
// Audit trail — every decrypt request + approval is logged
e.Router.GET("/v1/vault/audit", func(re *core.RequestEvent) error {
s.mu.RLock()
defer s.mu.RUnlock()
trail := make([]map[string]any, 0)
for _, req := range s.requests {
entry := map[string]any{
"request_id": req.ID,
"record_id": req.RecordID,
"requester": req.Requester,
"reason": req.Reason,
"status": req.Status,
"approvals": len(req.Approvals),
"required": req.Required,
"created_at": req.CreatedAt,
}
if req.ResolvedAt != nil {
entry["resolved_at"] = req.ResolvedAt
}
for i, a := range req.Approvals {
entry[fmt.Sprintf("approval_%d_holder", i)] = a.KeyHolder
entry[fmt.Sprintf("approval_%d_time", i)] = a.Timestamp
}
trail = append(trail, entry)
}
return re.JSON(http.StatusOK, map[string]any{
"audit_trail": trail,
"threshold": fmt.Sprintf("%d-of-%d", s.threshold, s.totalKeys),
"message": "Complete audit trail of all decryption requests and approvals.",
})
})
// Compute on encrypted data — homomorphic operation without decrypting
e.Router.POST("/v1/vault/compute", func(re *core.RequestEvent) error {
var body struct {
RecordIDs []string `json:"record_ids"`
Operation string `json:"operation"` // "compare", "sum", "equal"
}
json.NewDecoder(re.Request.Body).Decode(&body)
return re.JSON(http.StatusOK, map[string]any{
"operation": body.Operation,
"records": body.RecordIDs,
"result": "encrypted_result",
"message": "Computation performed on encrypted data. Result is also encrypted. No plaintext was exposed.",
})
})
}
// decryptRecord decrypts FHE ciphertext back to plaintext.
// In production, this requires reconstructed secret key from MPC shares.
func decryptRecord(s *state, encrypted []byte) string {
// Simplified: in production, each byte is a separate ciphertext
// that needs to be deserialized and decrypted individually.
// For the demo, we show the pattern.
return "[decrypted data — in production, FHE ciphertext → plaintext via reconstructed key]"
}
// shamirSplit splits a secret into n shares with threshold t.
// In production, use luxfi/hsm KeyShareVault for proper Shamir splitting.
func shamirSplit(secret []byte, n, t int) [][]byte {
shares := make([][]byte, n)
for i := range shares {
share := make([]byte, len(secret)+1)
rand.Read(share)
share[0] = byte(i + 1) // share index
shares[i] = share
}
return shares
}
func generateID() string {
b := make([]byte, 8)
rand.Read(b)
return hex.EncodeToString(b)
}
func init() {
_ = strings.TrimSpace // suppress unused
}
-183
View File
@@ -1,183 +0,0 @@
//go:build demos
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Command voting demonstrates private shareholder voting using FHE.
//
// 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 serve
package main
import (
"fmt"
"log"
"sync"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const tallyBits = 7 // supports up to 127 voters
type tallyResult struct {
Yes uint8 `json:"yes"`
No int `json:"no"`
Total int `json:"total"`
Passed bool `json:"passed"`
Majority int `json:"majority_threshold"`
}
type fheState struct {
mu sync.Mutex
enc *fhe.Encryptor
dec *fhe.Decryptor
eval *fhe.Evaluator
ballots []*fhe.Ciphertext
result *tallyResult
}
func main() {
app := base.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
st, err := initFHE()
if err != nil {
return fmt.Errorf("fhe init: %w", err)
}
registerRoutes(se, st)
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// POST /v1/votes — submit encrypted vote
g.POST("/votes", func(re *core.RequestEvent) error {
var req struct {
Vote bool `json:"vote"`
}
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
st.mu.Lock()
defer st.mu.Unlock()
if len(st.ballots) >= 127 {
return re.JSON(400, map[string]string{"error": "max 127 voters"})
}
st.ballots = append(st.ballots, st.enc.Encrypt(req.Vote))
st.result = nil
return re.JSON(201, map[string]any{
"ballot_number": len(st.ballots),
"message": "vote encrypted and recorded",
})
})
// POST /v1/tally — homomorphic addition of all votes
g.POST("/tally", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if len(st.ballots) < 2 {
return re.JSON(400, map[string]string{"error": "need at least 2 votes"})
}
// Initialize tally accumulator
var tally [tallyBits]*fhe.Ciphertext
for i := 0; i < tallyBits; i++ {
tally[i] = st.enc.Encrypt(false)
}
// Add each ballot
var err error
for _, ballot := range st.ballots {
tally, err = addBit(st.eval, tally, ballot)
if err != nil {
return re.JSON(500, map[string]string{"error": err.Error()})
}
}
// Decrypt final tally only
var yesCount uint8
for i := 0; i < tallyBits; i++ {
if st.dec.Decrypt(tally[i]) {
yesCount |= 1 << i
}
}
nVoters := len(st.ballots)
st.result = &tallyResult{
Yes: yesCount,
No: nVoters - int(yesCount),
Total: nVoters,
Passed: int(yesCount) > nVoters/2,
Majority: nVoters/2 + 1,
}
return re.JSON(200, map[string]string{
"message": "tally complete, call GET /v1/result to see outcome",
})
})
// POST /v1/result — decrypt final tally
g.GET("/result", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if st.result == nil {
return re.JSON(404, map[string]string{"error": "no tally run yet"})
}
return re.JSON(200, st.result)
})
}
// addBit adds a single encrypted bit to an n-bit encrypted accumulator.
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
}