From dc279eccedaa56af94f24564512e429168a0def4 Mon Sep 17 00:00:00 2001 From: Hanzo AI Date: Thu, 21 May 2026 18:46:28 -0700 Subject: [PATCH] =?UTF-8?q?rip(demos):=20delete=20cmd/demos/=20=E2=80=94?= =?UTF-8?q?=20kill=20-tags=20demos,=20one=20way=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- cmd/demos/README.md | 102 ------- cmd/demos/auction/main.go | 221 -------------- cmd/demos/compliance/main.go | 248 ---------------- cmd/demos/darkpool/main.go | 253 ---------------- cmd/demos/marketmaker/main.go | 231 --------------- cmd/demos/nav/main.go | 221 -------------- cmd/demos/proof/main.go | 242 ---------------- cmd/demos/regulated-vault/main.go | 464 ------------------------------ cmd/demos/voting/main.go | 183 ------------ 9 files changed, 2165 deletions(-) delete mode 100644 cmd/demos/README.md delete mode 100644 cmd/demos/auction/main.go delete mode 100644 cmd/demos/compliance/main.go delete mode 100644 cmd/demos/darkpool/main.go delete mode 100644 cmd/demos/marketmaker/main.go delete mode 100644 cmd/demos/nav/main.go delete mode 100644 cmd/demos/proof/main.go delete mode 100644 cmd/demos/regulated-vault/main.go delete mode 100644 cmd/demos/voting/main.go diff --git a/cmd/demos/README.md b/cmd/demos/README.md deleted file mode 100644 index 9bda746..0000000 --- a/cmd/demos/README.md +++ /dev/null @@ -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) diff --git a/cmd/demos/auction/main.go b/cmd/demos/auction/main.go deleted file mode 100644 index 5f9c3d9..0000000 --- a/cmd/demos/auction/main.go +++ /dev/null @@ -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) -} diff --git a/cmd/demos/compliance/main.go b/cmd/demos/compliance/main.go deleted file mode 100644 index 1c1c977..0000000 --- a/cmd/demos/compliance/main.go +++ /dev/null @@ -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 -} diff --git a/cmd/demos/darkpool/main.go b/cmd/demos/darkpool/main.go deleted file mode 100644 index 68b3b40..0000000 --- a/cmd/demos/darkpool/main.go +++ /dev/null @@ -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 -} diff --git a/cmd/demos/marketmaker/main.go b/cmd/demos/marketmaker/main.go deleted file mode 100644 index 3c0608c..0000000 --- a/cmd/demos/marketmaker/main.go +++ /dev/null @@ -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) -} diff --git a/cmd/demos/nav/main.go b/cmd/demos/nav/main.go deleted file mode 100644 index 7800668..0000000 --- a/cmd/demos/nav/main.go +++ /dev/null @@ -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 -} diff --git a/cmd/demos/proof/main.go b/cmd/demos/proof/main.go deleted file mode 100644 index 668c614..0000000 --- a/cmd/demos/proof/main.go +++ /dev/null @@ -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 -} diff --git a/cmd/demos/regulated-vault/main.go b/cmd/demos/regulated-vault/main.go deleted file mode 100644 index ed19879..0000000 --- a/cmd/demos/regulated-vault/main.go +++ /dev/null @@ -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 -} diff --git a/cmd/demos/voting/main.go b/cmd/demos/voting/main.go deleted file mode 100644 index 319bbc6..0000000 --- a/cmd/demos/voting/main.go +++ /dev/null @@ -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 -}