Files
zeekayandHanzo Dev 41d57d3c1e Threshold FHE moves to luxfi/threshold/protocols/tfhe
pkg/keycommit keeps what stays: a Shamir split, over the RFC 3526 Group-14
prime, of the SHA-256 commitment to a key. Its shares cannot decrypt, so
CommitKey and KeyCommitment replace GenerateSharedKey and KeyShare. fhed's
threshold mode follows the rename.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 13:26:44 -07:00

1193 lines
32 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"log/slog"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"sync"
"syscall"
"time"
"github.com/google/uuid"
"github.com/urfave/cli/v3"
"github.com/luxfi/fhe"
"github.com/luxfi/fhe/pkg/membership"
"github.com/luxfi/fhe/pkg/store"
"github.com/luxfi/fhe/pkg/keycommit"
"github.com/luxfi/mdns"
)
const Version = "0.3.0"
func main() {
app := &cli.Command{
Name: "fhed",
Usage: "FHE daemon for fully homomorphic encryption operations",
Version: Version,
Commands: []*cli.Command{
{
Name: "start",
Usage: "Start the FHE daemon",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "node-id",
Usage: "Node identifier (auto-generated if not set)",
Value: "",
},
&cli.StringFlag{
Name: "mode",
Aliases: []string{"m"},
Usage: "Operation mode: 'standard' (single-key) or 'threshold' (t-of-n)",
Value: "standard",
},
&cli.StringFlag{
Name: "http",
Usage: "HTTP API listen address",
Value: ":8448",
},
&cli.StringFlag{
Name: "data",
Aliases: []string{"d"},
Usage: "Data directory for keys and state",
Value: "",
},
&cli.IntFlag{
Name: "threshold",
Aliases: []string{"t"},
Usage: "Decryption threshold (threshold mode)",
Value: 2,
},
&cli.BoolFlag{
Name: "discover",
Usage: "Enable mDNS peer discovery (threshold mode)",
Value: true,
},
&cli.StringFlag{
Name: "params",
Usage: "Parameter set: 'PN10QP27' (default), 'PN11QP54' (high precision), 'STD128' (OpenFHE compatible)",
Value: "PN10QP27",
},
&cli.StringFlag{
Name: "log-level",
Usage: "Log level: debug, info, warn, error",
Value: "info",
},
&cli.StringFlag{
Name: "password",
Usage: "ZapDB encryption password (or set FHED_PASSWORD env)",
Sources: cli.EnvVars("FHED_PASSWORD"),
Value: "",
},
},
Action: runDaemon,
},
{
Name: "keygen",
Usage: "Generate FHE keys",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output directory for keys",
Value: ".",
},
&cli.StringFlag{
Name: "params",
Usage: "Parameter set: 'PN10QP27', 'PN11QP54', 'STD128'",
Value: "PN10QP27",
},
&cli.BoolFlag{
Name: "threshold",
Usage: "Generate threshold keys",
Value: false,
},
&cli.IntFlag{
Name: "t",
Usage: "Threshold (t in t-of-n)",
Value: 2,
},
&cli.IntFlag{
Name: "n",
Usage: "Number of parties (n in t-of-n)",
Value: 3,
},
},
Action: runKeygen,
},
{
Name: "reshare",
Usage: "Reshare keys with new threshold or parties",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "input",
Aliases: []string{"i"},
Usage: "Input directory with existing key shares",
Value: ".",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output directory for new key shares",
Value: "./reshared",
},
&cli.IntFlag{
Name: "new-t",
Usage: "New threshold",
Value: 2,
},
&cli.IntFlag{
Name: "new-n",
Usage: "New number of parties",
Value: 3,
},
},
Action: runReshare,
},
{
Name: "version",
Usage: "Display version information",
Action: func(ctx context.Context, c *cli.Command) error {
fmt.Printf("fhed version %s\n", Version)
return nil
},
},
},
}
if err := app.Run(context.Background(), os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// FHEDaemon holds the daemon state
type FHEDaemon struct {
nodeID string
params fhe.Parameters
// Standard mode
secretKey *fhe.SecretKey
publicKey *fhe.PublicKey
bootstrapKey *fhe.BootstrapKey
evaluator *fhe.Evaluator
encryptor *fhe.Encryptor
decryptor *fhe.Decryptor
// Threshold mode
thresholdMode bool
thresholdT int
keyShare *keycommit.KeyCommitment
membership *membership.Manager
discovery *mdns.Discovery
store *store.Store
dataDir string
logger *slog.Logger
mu sync.RWMutex
}
func runDaemon(ctx context.Context, c *cli.Command) error {
nodeID := c.String("node-id")
if nodeID == "" {
nodeID = fmt.Sprintf("fhed-%s", uuid.New().String()[:8])
}
mode := c.String("mode")
httpAddr := c.String("http")
dataDir := c.String("data")
thresholdT := int(c.Int("threshold"))
enableDiscovery := c.Bool("discover")
paramsName := c.String("params")
logLevel := c.String("log-level")
// Setup logger
var level slog.Level
switch logLevel {
case "debug":
level = slog.LevelDebug
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
default:
level = slog.LevelInfo
}
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
// Default data directory
if dataDir == "" {
home, _ := os.UserHomeDir()
dataDir = filepath.Join(home, ".fhed")
}
if err := os.MkdirAll(dataDir, 0750); err != nil {
return fmt.Errorf("failed to create data directory: %w", err)
}
logger.Info("Starting FHE daemon",
"nodeID", nodeID,
"mode", mode,
"http", httpAddr,
"dataDir", dataDir,
"params", paramsName,
)
// Get parameters
params, err := getParameters(paramsName)
if err != nil {
return err
}
// Parse HTTP port for discovery
_, portStr, _ := net.SplitHostPort(httpAddr)
var httpPort int
fmt.Sscanf(portStr, "%d", &httpPort)
if httpPort == 0 {
httpPort = 8448
}
// Open encrypted ZapDB for key material and ciphertext storage
password := c.String("password")
if password == "" {
password = "fhed-dev-" + nodeID // Dev default; production must set FHED_PASSWORD
logger.Warn("No password set — using dev default. Set FHED_PASSWORD for production.")
}
db, err := store.Open(filepath.Join(dataDir, "zapdb"), password)
if err != nil {
return fmt.Errorf("failed to open zapdb: %w", err)
}
defer db.Close()
daemon := &FHEDaemon{
nodeID: nodeID,
params: params,
thresholdMode: mode == "threshold",
thresholdT: thresholdT,
store: db,
dataDir: dataDir,
logger: logger,
}
if daemon.thresholdMode {
// Initialize membership manager
daemon.membership = membership.NewManager(membership.ManagerConfig{
NodeID: nodeID,
Threshold: thresholdT,
Logger: logger,
})
// Initialize mDNS discovery if enabled
if enableDiscovery {
disc := mdns.New("_fhed._tcp", nodeID, httpPort,
mdns.WithLogger(logger),
mdns.WithMetadata(map[string]string{
"version": Version,
}),
)
daemon.discovery = disc
// Handle peer discovery events
disc.OnPeer(func(peer *mdns.Peer, joined bool) {
if joined {
daemon.membership.AddMember(&membership.Member{
NodeID: peer.NodeID,
Addr: peer.Addr,
Port: peer.Port,
Ready: true,
})
} else {
daemon.membership.RemoveMember(peer.NodeID)
}
})
if err := disc.Start(); err != nil {
return fmt.Errorf("failed to start discovery: %w", err)
}
defer disc.Stop()
logger.Info("mDNS discovery enabled", "service", "_fhed._tcp")
}
// Handle membership changes (trigger reshare if needed)
daemon.membership.OnChange(func(change *membership.MembershipChange, newState *membership.ClusterState) {
logger.Info("Membership changed",
"type", change.Type,
"members", len(newState.Members),
"threshold", newState.Threshold,
)
// In a full implementation, trigger distributed keygen/reshare here
if change.Type == membership.ChangeJoin || change.Type == membership.ChangeLeave {
if len(newState.Members) >= newState.Threshold {
logger.Info("Quorum reached, ready for threshold operations")
}
}
})
// Load or generate threshold key share
if err := daemon.loadOrGenerateThresholdKey(); err != nil {
return fmt.Errorf("failed to load/generate threshold key: %w", err)
}
} else {
// Standard mode: single-key FHE
if err := daemon.loadOrGenerateKeys(); err != nil {
return fmt.Errorf("failed to load/generate keys: %w", err)
}
}
// Start HTTP server
httpServer := daemon.createHTTPServer()
httpListener, err := net.Listen("tcp", httpAddr)
if err != nil {
return fmt.Errorf("failed to listen on HTTP address: %w", err)
}
go func() {
logger.Info("HTTP server listening", "addr", httpAddr)
if err := httpServer.Serve(httpListener); err != nil && err != http.ErrServerClosed {
logger.Error("HTTP server error", "error", err)
}
}()
logger.Info("[READY] FHE daemon is ready",
"nodeID", nodeID,
"mode", mode,
"http", httpAddr,
)
// Wait for shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
logger.Info("Shutdown signal received, stopping...")
// Graceful shutdown
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
httpServer.Shutdown(shutdownCtx)
return nil
}
func getParameters(name string) (fhe.Parameters, error) {
var paramsLit fhe.ParametersLiteral
switch name {
case "PN11QP54":
paramsLit = fhe.PN11QP54
case "STD128":
paramsLit = fhe.PN9QP28_STD128
default:
paramsLit = fhe.PN10QP27
}
return fhe.NewParametersFromLiteral(paramsLit)
}
func (d *FHEDaemon) loadOrGenerateKeys() error {
keyID := "default"
// Try to load existing keys from ZapDB
if d.store.HasSecretKey(keyID) {
d.logger.Info("Loading existing keys from ZapDB")
skData, err := d.store.GetSecretKey(keyID)
if err != nil {
return fmt.Errorf("failed to read secret key: %w", err)
}
pkData, err := d.store.GetPublicKey(keyID)
if err != nil {
return fmt.Errorf("failed to read public key: %w", err)
}
bskData, err := d.store.GetBootstrapKey(keyID)
if err != nil {
return fmt.Errorf("failed to read bootstrap key: %w", err)
}
d.secretKey = new(fhe.SecretKey)
if err := d.secretKey.UnmarshalBinary(skData); err != nil {
return fmt.Errorf("failed to unmarshal secret key: %w", err)
}
d.publicKey = new(fhe.PublicKey)
if err := d.publicKey.UnmarshalBinary(pkData); err != nil {
return fmt.Errorf("failed to unmarshal public key: %w", err)
}
d.bootstrapKey = new(fhe.BootstrapKey)
if err := d.bootstrapKey.UnmarshalBinary(bskData); err != nil {
return fmt.Errorf("failed to unmarshal bootstrap key: %w", err)
}
d.encryptor = fhe.NewEncryptor(d.params, d.secretKey)
d.decryptor = fhe.NewDecryptor(d.params, d.secretKey)
d.evaluator = fhe.NewEvaluator(d.params, d.bootstrapKey)
return nil
}
// Generate new keys and store in ZapDB
d.logger.Info("Generating new FHE keys (this may take a moment)...")
keygen := fhe.NewKeyGenerator(d.params)
d.secretKey, d.publicKey = keygen.GenKeyPair()
d.bootstrapKey = keygen.GenBootstrapKey(d.secretKey)
d.encryptor = fhe.NewEncryptor(d.params, d.secretKey)
d.decryptor = fhe.NewDecryptor(d.params, d.secretKey)
d.evaluator = fhe.NewEvaluator(d.params, d.bootstrapKey)
// Persist to encrypted ZapDB
skData, err := d.secretKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal secret key: %w", err)
}
if err := d.store.PutSecretKey(keyID, skData); err != nil {
return fmt.Errorf("failed to store secret key: %w", err)
}
pkData, err := d.publicKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal public key: %w", err)
}
if err := d.store.PutPublicKey(keyID, pkData); err != nil {
return fmt.Errorf("failed to write public key: %w", err)
}
bskData, err := d.bootstrapKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal bootstrap key: %w", err)
}
if err := d.store.PutBootstrapKey(keyID, bskData); err != nil {
return fmt.Errorf("failed to store bootstrap key: %w", err)
}
d.logger.Info("Keys generated and saved to ZapDB")
return nil
}
func (d *FHEDaemon) loadOrGenerateThresholdKey() error {
keyID := "threshold-default"
// Try to load existing key share from ZapDB
if ksData, err := d.store.GetSecretKey(keyID); err == nil {
d.logger.Info("Loading existing key share from ZapDB")
ks, err := keycommit.UnmarshalCommitment(ksData)
if err != nil {
return fmt.Errorf("failed to unmarshal key share: %w", err)
}
d.keyShare = ks
pkData, err := d.store.GetPublicKey(keyID)
if err != nil {
return fmt.Errorf("failed to read public key: %w", err)
}
d.publicKey = new(fhe.PublicKey)
if err := d.publicKey.UnmarshalBinary(pkData); err != nil {
return fmt.Errorf("failed to unmarshal public key: %w", err)
}
if bskData, err := d.store.GetBootstrapKey(keyID); err == nil {
d.bootstrapKey = new(fhe.BootstrapKey)
if err := d.bootstrapKey.UnmarshalBinary(bskData); err != nil {
d.logger.Warn("Failed to load bootstrap key, evaluation will not work", "error", err)
} else {
d.evaluator = fhe.NewEvaluator(d.params, d.bootstrapKey)
}
}
d.logger.Info("Threshold key share loaded",
"partyIndex", ks.PartyIndex,
"threshold", ks.Threshold,
"sessionID", ks.SessionID,
)
return nil
}
d.logger.Warn("Generating single-party threshold keys for demo (NOT secure for production)")
sessionID := uuid.New().String()
result, err := keycommit.CommitKey(d.params, d.thresholdT, d.thresholdT, sessionID)
if err != nil {
return fmt.Errorf("failed to generate threshold keys: %w", err)
}
d.keyShare = result.Shares[0]
d.publicKey = result.PublicKey
d.bootstrapKey = result.BootstrapKey
if d.bootstrapKey != nil {
d.evaluator = fhe.NewEvaluator(d.params, d.bootstrapKey)
}
// Persist to ZapDB
ksData, err := keycommit.MarshalCommitment(d.keyShare)
if err != nil {
return fmt.Errorf("failed to marshal key share: %w", err)
}
if err := d.store.PutSecretKey(keyID, ksData); err != nil {
return fmt.Errorf("failed to store key share: %w", err)
}
pkData, err := d.publicKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal public key: %w", err)
}
if err := d.store.PutPublicKey(keyID, pkData); err != nil {
return fmt.Errorf("failed to store public key: %w", err)
}
if d.bootstrapKey != nil {
bskData, err := d.bootstrapKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal bootstrap key: %w", err)
}
if err := d.store.PutBootstrapKey(keyID, bskData); err != nil {
return fmt.Errorf("failed to store bootstrap key: %w", err)
}
}
d.logger.Info("Threshold keys generated",
"sessionID", sessionID,
"threshold", d.thresholdT,
)
return nil
}
// HTTP API
func (d *FHEDaemon) createHTTPServer() *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/v1/fhe/health", d.handleHealth)
mux.HandleFunc("/v1/fhe/publickey", d.handlePublicKey)
mux.HandleFunc("/v1/fhe/encrypt", d.handleEncrypt)
mux.HandleFunc("/v1/fhe/decrypt", d.handleDecrypt)
mux.HandleFunc("/v1/fhe/evaluate", d.handleEvaluate)
// Cluster management endpoints (threshold mode)
mux.HandleFunc("/v1/fhe/cluster/status", d.handleClusterStatus)
mux.HandleFunc("/v1/fhe/cluster/peers", d.handleClusterPeers)
mux.HandleFunc("/v1/fhe/cluster/reshare", d.handleClusterReshare)
return &http.Server{Handler: mux}
}
func (d *FHEDaemon) handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
status := map[string]interface{}{
"status": "healthy",
"version": Version,
"nodeId": d.nodeID,
"mode": map[bool]string{true: "threshold", false: "standard"}[d.thresholdMode],
"params": map[string]interface{}{
"N": d.params.N(),
"NBR": d.params.NBR(),
"QLWE": d.params.QLWE(),
"QBR": d.params.QBR(),
},
}
if d.thresholdMode && d.membership != nil {
status["threshold"] = d.thresholdT
status["members"] = d.membership.MemberCount()
status["ready"] = d.membership.ReadyCount()
status["canDecrypt"] = d.membership.CanDecrypt()
}
json.NewEncoder(w).Encode(status)
}
func (d *FHEDaemon) handleClusterStatus(w http.ResponseWriter, r *http.Request) {
if !d.thresholdMode {
http.Error(w, "not in threshold mode", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(d.membership.GetState())
}
func (d *FHEDaemon) handleClusterPeers(w http.ResponseWriter, r *http.Request) {
if !d.thresholdMode || d.discovery == nil {
http.Error(w, "discovery not enabled", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(d.discovery.Peers())
}
func (d *FHEDaemon) handleClusterReshare(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if !d.thresholdMode {
http.Error(w, "not in threshold mode", http.StatusBadRequest)
return
}
var req struct {
NewThreshold int `json:"newThreshold"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if req.NewThreshold < 1 {
http.Error(w, "threshold must be at least 1", http.StatusBadRequest)
return
}
if err := d.membership.SetThreshold(req.NewThreshold); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
d.thresholdT = req.NewThreshold
d.logger.Info("Threshold updated, reshare triggered", "newThreshold", req.NewThreshold)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"newThreshold": req.NewThreshold,
"message": "reshare initiated",
})
}
func (d *FHEDaemon) handlePublicKey(w http.ResponseWriter, r *http.Request) {
d.mu.RLock()
defer d.mu.RUnlock()
if d.publicKey == nil {
http.Error(w, "no public key available", http.StatusServiceUnavailable)
return
}
pkData, err := d.publicKey.MarshalBinary()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"publicKey": hex.EncodeToString(pkData),
})
}
type EncryptRequest struct {
Bit *bool `json:"bit,omitempty"`
Uint32 *uint32 `json:"uint32,omitempty"`
Uint64 *uint64 `json:"uint64,omitempty"`
}
type EncryptResponse struct {
Ciphertext string `json:"ciphertext,omitempty"`
Ciphertexts []string `json:"ciphertexts,omitempty"`
NumBits int `json:"numBits"`
}
func (d *FHEDaemon) handleEncrypt(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req EncryptRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
d.mu.RLock()
defer d.mu.RUnlock()
if d.encryptor == nil {
http.Error(w, "encryption not available", http.StatusServiceUnavailable)
return
}
var resp EncryptResponse
if req.Bit != nil {
ct := d.encryptor.Encrypt(*req.Bit)
ctData, err := ct.MarshalBinary()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp.Ciphertext = hex.EncodeToString(ctData)
resp.NumBits = 1
} else if req.Uint32 != nil {
cts := d.encryptor.EncryptUint32(*req.Uint32)
resp.Ciphertexts = make([]string, 32)
for i, ct := range cts {
ctData, err := ct.MarshalBinary()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp.Ciphertexts[i] = hex.EncodeToString(ctData)
}
resp.NumBits = 32
} else if req.Uint64 != nil {
cts := d.encryptor.EncryptUint64(*req.Uint64)
resp.Ciphertexts = make([]string, 64)
for i, ct := range cts {
ctData, err := ct.MarshalBinary()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp.Ciphertexts[i] = hex.EncodeToString(ctData)
}
resp.NumBits = 64
} else {
http.Error(w, "must provide 'bit', 'uint32', or 'uint64'", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
type DecryptRequest struct {
Ciphertext string `json:"ciphertext,omitempty"`
Ciphertexts []string `json:"ciphertexts,omitempty"`
}
type DecryptResponse struct {
Bit *bool `json:"bit,omitempty"`
Uint32 *uint32 `json:"uint32,omitempty"`
Uint64 *uint64 `json:"uint64,omitempty"`
}
func (d *FHEDaemon) handleDecrypt(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// In threshold mode, check if we have quorum
if d.thresholdMode && d.membership != nil && !d.membership.CanDecrypt() {
http.Error(w, fmt.Sprintf("not enough parties for decryption (need %d, have %d ready)",
d.thresholdT, d.membership.ReadyCount()), http.StatusServiceUnavailable)
return
}
var req DecryptRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
d.mu.RLock()
defer d.mu.RUnlock()
if d.decryptor == nil {
http.Error(w, "decryption not available", http.StatusServiceUnavailable)
return
}
var resp DecryptResponse
if req.Ciphertext != "" {
ctData, err := hex.DecodeString(req.Ciphertext)
if err != nil {
http.Error(w, "invalid ciphertext hex", http.StatusBadRequest)
return
}
ct := new(fhe.Ciphertext)
if err := ct.UnmarshalBinary(ctData); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
result := d.decryptor.Decrypt(ct)
resp.Bit = &result
} else if len(req.Ciphertexts) > 0 {
numBits := len(req.Ciphertexts)
if numBits == 32 {
var cts [32]*fhe.Ciphertext
for i, ctHex := range req.Ciphertexts {
ctData, err := hex.DecodeString(ctHex)
if err != nil {
http.Error(w, fmt.Sprintf("invalid ciphertext %d hex", i), http.StatusBadRequest)
return
}
cts[i] = new(fhe.Ciphertext)
if err := cts[i].UnmarshalBinary(ctData); err != nil {
http.Error(w, fmt.Sprintf("failed to parse ciphertext %d", i), http.StatusBadRequest)
return
}
}
result := d.decryptor.DecryptUint32(cts)
resp.Uint32 = &result
} else if numBits == 64 {
var cts [64]*fhe.Ciphertext
for i, ctHex := range req.Ciphertexts {
ctData, err := hex.DecodeString(ctHex)
if err != nil {
http.Error(w, fmt.Sprintf("invalid ciphertext %d hex", i), http.StatusBadRequest)
return
}
cts[i] = new(fhe.Ciphertext)
if err := cts[i].UnmarshalBinary(ctData); err != nil {
http.Error(w, fmt.Sprintf("failed to parse ciphertext %d", i), http.StatusBadRequest)
return
}
}
result := d.decryptor.DecryptUint64(cts)
resp.Uint64 = &result
} else {
http.Error(w, "ciphertexts array must have 32 or 64 elements", http.StatusBadRequest)
return
}
} else {
http.Error(w, "must provide 'ciphertext' or 'ciphertexts'", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
type EvaluateRequest struct {
Operation string `json:"operation"`
Operands []string `json:"operands"`
}
type EvaluateResponse struct {
Result string `json:"result"`
}
func (d *FHEDaemon) handleEvaluate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req EvaluateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if len(req.Operands) < 1 {
http.Error(w, "at least one operand required", http.StatusBadRequest)
return
}
operands := make([]*fhe.Ciphertext, len(req.Operands))
for i, opHex := range req.Operands {
data, err := hex.DecodeString(opHex)
if err != nil {
http.Error(w, fmt.Sprintf("invalid operand %d", i), http.StatusBadRequest)
return
}
operands[i] = new(fhe.Ciphertext)
if err := operands[i].UnmarshalBinary(data); err != nil {
http.Error(w, fmt.Sprintf("failed to parse operand %d", i), http.StatusBadRequest)
return
}
}
d.mu.RLock()
defer d.mu.RUnlock()
if d.evaluator == nil {
http.Error(w, "evaluation not available", http.StatusServiceUnavailable)
return
}
var result *fhe.Ciphertext
var err error
switch req.Operation {
case "not":
result = d.evaluator.NOT(operands[0])
case "and":
if len(operands) < 2 {
http.Error(w, "and requires 2 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.AND(operands[0], operands[1])
case "or":
if len(operands) < 2 {
http.Error(w, "or requires 2 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.OR(operands[0], operands[1])
case "xor":
if len(operands) < 2 {
http.Error(w, "xor requires 2 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.XOR(operands[0], operands[1])
case "nand":
if len(operands) < 2 {
http.Error(w, "nand requires 2 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.NAND(operands[0], operands[1])
case "nor":
if len(operands) < 2 {
http.Error(w, "nor requires 2 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.NOR(operands[0], operands[1])
case "xnor":
if len(operands) < 2 {
http.Error(w, "xnor requires 2 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.XNOR(operands[0], operands[1])
case "mux":
if len(operands) < 3 {
http.Error(w, "mux requires 3 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.MUX(operands[0], operands[1], operands[2])
case "refresh":
result, err = d.evaluator.Refresh(operands[0])
case "majority":
if len(operands) < 3 {
http.Error(w, "majority requires 3 operands", http.StatusBadRequest)
return
}
result, err = d.evaluator.MAJORITY(operands[0], operands[1], operands[2])
default:
http.Error(w, fmt.Sprintf("unsupported operation: %s", req.Operation), http.StatusBadRequest)
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resultData, err := result.MarshalBinary()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(EvaluateResponse{
Result: hex.EncodeToString(resultData),
})
}
func runKeygen(ctx context.Context, c *cli.Command) error {
outputDir := c.String("output")
paramsName := c.String("params")
isThreshold := c.Bool("threshold")
t := int(c.Int("t"))
n := int(c.Int("n"))
params, err := getParameters(paramsName)
if err != nil {
return fmt.Errorf("failed to create parameters: %w", err)
}
if err := os.MkdirAll(outputDir, 0750); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
if isThreshold {
fmt.Printf("Generating threshold keys (%d-of-%d)...\n", t, n)
sessionID := uuid.New().String()
result, err := keycommit.CommitKey(params, t, n, sessionID)
if err != nil {
return fmt.Errorf("failed to generate threshold keys: %w", err)
}
// Save each key share
for i, share := range result.Shares {
shareDir := filepath.Join(outputDir, fmt.Sprintf("party%d", i+1))
if err := os.MkdirAll(shareDir, 0750); err != nil {
return fmt.Errorf("failed to create party directory: %w", err)
}
ksData, err := keycommit.MarshalCommitment(share)
if err != nil {
return fmt.Errorf("failed to marshal key share: %w", err)
}
if err := os.WriteFile(filepath.Join(shareDir, "keyshare.bin"), ksData, 0600); err != nil {
return fmt.Errorf("failed to write key share: %w", err)
}
// Save public key for each party
pkData, err := result.PublicKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal public key: %w", err)
}
if err := os.WriteFile(filepath.Join(shareDir, "public.key"), pkData, 0644); err != nil {
return fmt.Errorf("failed to write public key: %w", err)
}
fmt.Printf(" Party %d key share saved to: %s\n", i+1, shareDir)
}
// Save bootstrap key (shared)
if result.BootstrapKey != nil {
bskData, err := result.BootstrapKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal bootstrap key: %w", err)
}
bskPath := filepath.Join(outputDir, "bootstrap.key")
if err := os.WriteFile(bskPath, bskData, 0644); err != nil {
return fmt.Errorf("failed to write bootstrap key: %w", err)
}
fmt.Printf("Bootstrap key saved to: %s\n", bskPath)
}
fmt.Printf("\nThreshold keygen complete!\n")
fmt.Printf(" Session ID: %s\n", sessionID)
fmt.Printf(" Threshold: %d-of-%d\n", t, n)
return nil
}
// Standard keygen
fmt.Println("Generating standard FHE keys...")
keygen := fhe.NewKeyGenerator(params)
sk, pk := keygen.GenKeyPair()
skData, err := sk.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal secret key: %w", err)
}
if err := os.WriteFile(filepath.Join(outputDir, "secret.key"), skData, 0600); err != nil {
return fmt.Errorf("failed to write secret key: %w", err)
}
fmt.Printf("Secret key saved to: %s/secret.key\n", outputDir)
pkData, err := pk.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal public key: %w", err)
}
if err := os.WriteFile(filepath.Join(outputDir, "public.key"), pkData, 0644); err != nil {
return fmt.Errorf("failed to write public key: %w", err)
}
fmt.Printf("Public key saved to: %s/public.key\n", outputDir)
fmt.Println("Generating bootstrap key...")
bsk := keygen.GenBootstrapKey(sk)
bskData, err := bsk.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal bootstrap key: %w", err)
}
if err := os.WriteFile(filepath.Join(outputDir, "bootstrap.key"), bskData, 0644); err != nil {
return fmt.Errorf("failed to write bootstrap key: %w", err)
}
fmt.Printf("Bootstrap key saved to: %s/bootstrap.key\n", outputDir)
fmt.Println("Key generation complete!")
return nil
}
func runReshare(ctx context.Context, c *cli.Command) error {
inputDir := c.String("input")
outputDir := c.String("output")
newT := int(c.Int("new-t"))
newN := int(c.Int("new-n"))
// Load existing key shares
entries, err := os.ReadDir(inputDir)
if err != nil {
return fmt.Errorf("failed to read input directory: %w", err)
}
var oldShares []*keycommit.KeyCommitment
for _, entry := range entries {
if !entry.IsDir() {
continue
}
ksPath := filepath.Join(inputDir, entry.Name(), "keyshare.bin")
if _, err := os.Stat(ksPath); err != nil {
continue
}
ksData, err := os.ReadFile(ksPath)
if err != nil {
return fmt.Errorf("failed to read key share %s: %w", entry.Name(), err)
}
ks, err := keycommit.UnmarshalCommitment(ksData)
if err != nil {
return fmt.Errorf("failed to unmarshal key share %s: %w", entry.Name(), err)
}
oldShares = append(oldShares, ks)
}
if len(oldShares) == 0 {
return fmt.Errorf("no key shares found in %s", inputDir)
}
fmt.Printf("Found %d key shares (threshold %d-of-%d)\n",
len(oldShares), oldShares[0].Threshold, oldShares[0].Total)
fmt.Printf("Resharing to %d-of-%d...\n", newT, newN)
newSessionID := uuid.New().String()
result, err := keycommit.ReshareCommitment(oldShares, newT, newN, newSessionID)
if err != nil {
return fmt.Errorf("failed to reshare: %w", err)
}
if err := os.MkdirAll(outputDir, 0750); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
for i, share := range result.Shares {
shareDir := filepath.Join(outputDir, fmt.Sprintf("party%d", i+1))
if err := os.MkdirAll(shareDir, 0750); err != nil {
return fmt.Errorf("failed to create party directory: %w", err)
}
ksData, err := keycommit.MarshalCommitment(share)
if err != nil {
return fmt.Errorf("failed to marshal key share: %w", err)
}
if err := os.WriteFile(filepath.Join(shareDir, "keyshare.bin"), ksData, 0600); err != nil {
return fmt.Errorf("failed to write key share: %w", err)
}
pkData, err := result.PublicKey.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal public key: %w", err)
}
if err := os.WriteFile(filepath.Join(shareDir, "public.key"), pkData, 0644); err != nil {
return fmt.Errorf("failed to write public key: %w", err)
}
fmt.Printf(" Party %d key share saved to: %s\n", i+1, shareDir)
}
fmt.Printf("\nReshare complete!\n")
fmt.Printf(" New session ID: %s\n", newSessionID)
fmt.Printf(" New threshold: %d-of-%d\n", newT, newN)
return nil
}