feat: all 7 FHE demos now Base apps — single binary, admin UI, REST API

Each demo is a sovereign app: go run ./cmd/demos/<name> serve

darkpool:     /v1/orders, /v1/match, /v1/reveal — encrypted order matching
compliance:   /v1/portfolio, /v1/check — SEC diversification on encrypted holdings
marketmaker:  /v1/quotes, /v1/best — private market making
auction:      /v1/bids, /v1/determine-winner, /v1/reveal — sealed-bid auction
voting:       /v1/votes, /v1/tally, /v1/result — encrypted shareholder voting
nav:          /v1/holdings, /v1/compute — confidential fund valuation
proof:        /v1/positions, /v1/prove — compliance proofs without revealing data

All use Base embedded SQLite + admin UI at /_/. FHE key material
stored in Base's encrypted collections. Original FHE logic preserved.
This commit is contained in:
Hanzo AI
2026-04-07 19:00:09 -07:00
parent ccec005cde
commit a409594e84
9 changed files with 1221 additions and 713 deletions
+129 -71
View File
@@ -3,104 +3,164 @@
// Command auction demonstrates an encrypted sealed-bid auction using FHE.
//
// Five participants submit encrypted sealed bids. The system determines the
// winner via boolean circuit max computation. Only the winning bid is decrypted.
// 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
// go run ./cmd/demos/auction serve
package main
import (
"fmt"
"os"
"time"
"log"
"sync"
"github.com/google/uuid"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const bidBits = 8
type bid struct {
participant string
amount uint8
encAmount [8]*fhe.Ciphertext
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() {
fmt.Println("=== FHE Encrypted Sealed-Bid Auction Demo ===")
fmt.Println("Determine winner without revealing losing bids")
fmt.Println()
app := base.New()
// Setup
fmt.Print("Initializing FHE... ")
t0 := time.Now()
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 {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
fmt.Printf("done (%v)\n\n", time.Since(t0))
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
// Five sealed bids
bids := []bid{
{participant: "Participant 1", amount: 84},
{participant: "Participant 2", amount: 92},
{participant: "Participant 3", amount: 97},
{participant: "Participant 4", amount: 83},
{participant: "Participant 5", amount: 88},
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
fmt.Println("Sealed bids (shown for demo verification only):")
for _, b := range bids {
fmt.Printf(" %s: $%d\n", b.participant, b.amount)
}
fmt.Println()
// Encrypt
fmt.Println("Encrypting sealed bids...")
t0 = time.Now()
for i := range bids {
bids[i].encAmount = encryptByte(enc, bids[i].amount)
}
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
// Tournament: find max via pairwise comparison
fmt.Println("Determining winner via encrypted comparisons...")
t0 = time.Now()
winnerIdx := 0
for i := 1; i < len(bids); i++ {
t1 := time.Now()
gtResult, err := gtEncrypted(eval, bids[i].encAmount, bids[winnerIdx].encAmount)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
// 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"`
}
isGreater := dec.Decrypt(gtResult)
elapsed := time.Since(t1)
if isGreater {
fmt.Printf(" %s > %s: true (%v) -> new leader\n",
bids[i].participant, bids[winnerIdx].participant, elapsed)
winnerIdx = i
} else {
fmt.Printf(" %s > %s: false (%v)\n",
bids[i].participant, bids[winnerIdx].participant, elapsed)
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"})
}
}
fmt.Printf("Tournament done (%v)\n\n", time.Since(t0))
// Decrypt winner only
winningAmount := decryptByte(dec, bids[winnerIdx].encAmount)
st.mu.Lock()
defer st.mu.Unlock()
fmt.Println("--- Result ---")
fmt.Printf("Winner: %s @ $%d\n", bids[winnerIdx].participant, winningAmount)
fmt.Println("\nNote: losing bid amounts were never decrypted.")
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 {
@@ -121,7 +181,6 @@ func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
return v
}
// ltEncrypted computes a < b on 8-bit encrypted values (MSB-first comparison).
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
var isLess, isEqual *fhe.Ciphertext
for i := bidBits - 1; i >= 0; i-- {
@@ -155,7 +214,6 @@ func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext,
return isLess, nil
}
// gtEncrypted computes a > b: same as b < a.
func gtEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
return ltEncrypted(eval, b, a)
}
+137 -110
View File
@@ -3,134 +3,176 @@
// Command compliance demonstrates programmable compliance checks on encrypted portfolios.
//
// A portfolio of 5 positions is encrypted. The system verifies SEC diversification
// 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
// go run ./cmd/demos/compliance serve
package main
import (
"fmt"
"os"
"time"
"log"
"sync"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const valueBits = 8 // 8-bit values (0-255)
const valueBits = 8
type position struct {
name string
weight uint8 // percentage 0-100
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() {
fmt.Println("=== FHE Programmable Compliance Demo ===")
fmt.Println("Verify SEC diversification on encrypted portfolio")
fmt.Println()
app := base.New()
// Setup
fmt.Print("Initializing FHE... ")
t0 := time.Now()
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 {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
fmt.Printf("done (%v)\n\n", time.Since(t0))
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
// Portfolio
portfolio := []position{
{name: "AAPL", weight: 20},
{name: "MSFT", weight: 18},
{name: "NVDA", weight: 15},
{name: "GOOG", weight: 12},
{name: "AMZN", weight: 10},
}
var totalWeight uint8
for _, p := range portfolio {
totalWeight += p.weight
fmt.Printf(" %s: %d%%\n", p.name, p.weight)
}
fmt.Printf(" Total exposure: %d%%\n\n", totalWeight)
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// Encrypt positions
fmt.Println("Encrypting portfolio positions...")
t0 = time.Now()
encWeights := make([][8]*fhe.Ciphertext, len(portfolio))
for i, p := range portfolio {
encWeights[i] = encryptByte(enc, p.weight)
}
// Encrypt threshold (25%) as constant for comparison
encThreshold := encryptByte(enc, 25)
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
// Check 1: no single position > 25%
fmt.Println("Check 1: Each position <= 25%...")
t0 = time.Now()
allCompliant := enc.Encrypt(true) // accumulator
for i, p := range portfolio {
t1 := time.Now()
// leEncrypted returns 1 if weight <= 25
leResult, err := leEncrypted(eval, encWeights[i], encThreshold)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
// 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"`
}
// AND into accumulator
allCompliant, err = eval.AND(allCompliant, leResult)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
fmt.Printf(" %s checked (%v)\n", p.name, time.Since(t1))
}
check1 := dec.Decrypt(allCompliant)
fmt.Printf(" Diversification: %v (%v)\n\n", check1, time.Since(t0))
// Check 2: total exposure < 80%
fmt.Println("Check 2: Total exposure < 80%...")
t0 = time.Now()
// Sum weights using ripple-carry adder
encTotal := encWeights[0]
for i := 1; i < len(encWeights); i++ {
t1 := time.Now()
encTotal, err = addBytes(eval, enc, encTotal, encWeights[i])
if err != nil {
fmt.Fprintf(os.Stderr, "error adding: %v\n", err)
os.Exit(1)
if len(req.Positions) == 0 {
return re.JSON(400, map[string]string{"error": "positions required"})
}
fmt.Printf(" Added position %d (%v)\n", i+1, time.Since(t1))
}
encMaxExposure := encryptByte(enc, 80)
t1 := time.Now()
ltResult, err := ltEncrypted(eval, encTotal, encMaxExposure)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
check2 := dec.Decrypt(ltResult)
fmt.Printf(" Total < 80%%: %v (%v)\n\n", check2, time.Since(t1))
st.mu.Lock()
defer st.mu.Unlock()
// Verify
decTotal := decryptByte(dec, encTotal)
fmt.Printf(" (Verification: encrypted total decrypts to %d%%)\n\n", decTotal)
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),
})
}
// Final
fmt.Println("--- Result ---")
fmt.Printf("Portfolio compliant: %v\n", check1 && check2)
fmt.Println("(Holdings were never revealed to the compliance engine)")
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 {
@@ -141,22 +183,10 @@ func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
return bits
}
func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
var v uint8
for i := 0; i < 8; i++ {
if dec.Decrypt(bits[i]) {
v |= 1 << i
}
}
return v
}
// addBytes adds two encrypted 8-bit values using ripple-carry.
func addBytes(eval *fhe.Evaluator, enc *fhe.Encryptor, a, b [8]*fhe.Ciphertext) ([8]*fhe.Ciphertext, error) {
carry := enc.Encrypt(false)
var result [8]*fhe.Ciphertext
for i := 0; i < 8; i++ {
// sum = a XOR b XOR carry (full adder)
abXor, err := eval.XOR(a[i], b[i])
if err != nil {
return result, err
@@ -165,7 +195,6 @@ func addBytes(eval *fhe.Evaluator, enc *fhe.Encryptor, a, b [8]*fhe.Ciphertext)
if err != nil {
return result, err
}
// carry = MAJORITY(a, b, carry)
carry, err = eval.MAJORITY(a[i], b[i], carry)
if err != nil {
return result, err
@@ -175,7 +204,6 @@ func addBytes(eval *fhe.Evaluator, enc *fhe.Encryptor, a, b [8]*fhe.Ciphertext)
return result, nil
}
// ltEncrypted computes a < b on encrypted 8-bit values.
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
var isLess, isEqual *fhe.Ciphertext
for i := valueBits - 1; i >= 0; i-- {
@@ -209,7 +237,6 @@ func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext,
return isLess, nil
}
// leEncrypted computes a <= b: NOT(b < a).
func leEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
bLtA, err := ltEncrypted(eval, b, a)
if err != nil {
+154 -90
View File
@@ -9,118 +9,190 @@
//
// Usage:
//
// go run ./cmd/demos/darkpool
// go run ./cmd/demos/darkpool serve
package main
import (
"fmt"
"os"
"time"
"log"
"sync"
"github.com/google/uuid"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const priceBits = 8 // 8-bit prices (0-255)
const priceBits = 8
type order struct {
id int
isBid bool
price uint8
qty uint8
type encOrder struct {
ID string
Side string // "bid" or "ask"
EncPrice [8]*fhe.Ciphertext
EncQty [8]*fhe.Ciphertext
}
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() {
fmt.Println("=== FHE Dark Pool Demo ===")
fmt.Println("Encrypted order matching without revealing prices")
fmt.Println()
app := base.New()
// Setup
fmt.Print("Initializing FHE... ")
t0 := time.Now()
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 {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
fmt.Printf("done (%v)\n\n", time.Since(t0))
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
}
// Orders: 3 bids, 3 asks
bids := []order{
{id: 1, isBid: true, price: 85, qty: 100},
{id: 2, isBid: true, price: 84, qty: 200},
{id: 3, isBid: true, price: 86, qty: 75},
}
asks := []order{
{id: 4, isBid: false, price: 84, qty: 100},
{id: 5, isBid: false, price: 87, qty: 250},
{id: 6, isBid: false, price: 85, qty: 90},
}
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// Encrypt all orders
fmt.Println("Encrypting 6 limit orders...")
t0 = time.Now()
for i := range bids {
bids[i].encPrice = encryptByte(enc, bids[i].price)
bids[i].encQty = encryptByte(enc, bids[i].qty)
}
for i := range asks {
asks[i].encPrice = encryptByte(enc, asks[i].price)
asks[i].encQty = encryptByte(enc, asks[i].qty)
}
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
// 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"})
}
// Match bids vs asks: bid.price >= ask.price
fmt.Println("Matching encrypted orders (bid.price >= ask.price)...")
type match struct {
bid, ask order
}
var matches []match
st.mu.Lock()
defer st.mu.Unlock()
for _, bid := range bids {
for _, ask := range asks {
t1 := time.Now()
isGe, err := geEncrypted(eval, bid.encPrice, ask.encPrice)
if err != nil {
fmt.Fprintf(os.Stderr, "error comparing: %v\n", err)
os.Exit(1)
}
isMatch := dec.Decrypt(isGe)
elapsed := time.Since(t1)
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
if isMatch {
matches = append(matches, match{bid: bid, ask: ask})
fmt.Printf(" Order %d vs %d: MATCH (%v)\n", bid.id, ask.id, elapsed)
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 {
fmt.Printf(" Order %d vs %d: no match (%v)\n", bid.id, ask.id, elapsed)
asks = append(asks, o)
}
}
}
// Decrypt matched fills
fmt.Printf("\n--- Matched Fills (%d) ---\n", len(matches))
for _, m := range matches {
bidPrice := decryptByte(dec, m.bid.encPrice)
askPrice := decryptByte(dec, m.ask.encPrice)
bidQty := decryptByte(dec, m.bid.encQty)
askQty := decryptByte(dec, m.ask.encQty)
fillQty := bidQty
if askQty < fillQty {
fillQty = askQty
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,
})
}
}
}
fmt.Printf(" Matched: Buy %d @ $%d <-> Sell %d @ $%d (fill %d shares)\n",
bidQty, bidPrice, askQty, askPrice, fillQty)
}
fmt.Println("\nNote: unmatched order prices were never revealed.")
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 {
@@ -142,19 +214,14 @@ func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
}
// geEncrypted computes a >= b on encrypted 8-bit values using MSB-first comparison.
// Returns an encrypted bit: 1 if a >= b, 0 otherwise.
func geEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
// Compare from MSB to LSB: a >= b iff NOT (a < b)
// a < b: find first bit where they differ (MSB to LSB); at that bit a=0, b=1
var isLess, isEqual *fhe.Ciphertext
for i := priceBits - 1; i >= 0; i-- {
// bitLt = NOT(a[i]) AND b[i] -- this bit says a < b at position i
bitLt, err := eval.ANDNY(a[i], b[i])
if err != nil {
return nil, fmt.Errorf("bit %d ANDNY: %w", i, err)
}
// bitEq = NOT(a[i] XOR b[i]) -- bits are equal
bitXor, err := eval.XOR(a[i], b[i])
if err != nil {
return nil, fmt.Errorf("bit %d XOR: %w", i, err)
@@ -165,7 +232,6 @@ func geEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext,
isLess = bitLt
isEqual = bitEq
} else {
// isLess = isLess OR (isEqual AND bitLt)
eqAndLt, err := eval.AND(isEqual, bitLt)
if err != nil {
return nil, err
@@ -174,7 +240,6 @@ func geEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext,
if err != nil {
return nil, err
}
// isEqual = isEqual AND bitEq
isEqual, err = eval.AND(isEqual, bitEq)
if err != nil {
return nil, err
@@ -182,6 +247,5 @@ func geEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext,
}
}
// a >= b is NOT(a < b)
return eval.NOT(isLess), nil
}
+137 -92
View File
@@ -3,127 +3,174 @@
// Command marketmaker demonstrates private market making with FHE.
//
// Three market makers submit encrypted bid/ask quotes. The system selects
// the best bid (highest) and best ask (lowest) via boolean circuit comparisons.
// 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
// go run ./cmd/demos/marketmaker serve
package main
import (
"fmt"
"os"
"time"
"log"
"sync"
"github.com/google/uuid"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const priceBits = 8
type quote struct {
maker string
bid uint8
ask uint8
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() {
fmt.Println("=== FHE Private Market Making Demo ===")
fmt.Println("Select best bid/ask from encrypted quotes")
fmt.Println()
app := base.New()
// Setup
fmt.Print("Initializing FHE... ")
t0 := time.Now()
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 {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
fmt.Printf("done (%v)\n\n", time.Since(t0))
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
// Three market makers
quotes := []quote{
{maker: "MM1", bid: 84, ask: 86},
{maker: "MM2", bid: 85, ask: 87},
{maker: "MM3", bid: 83, ask: 85},
}
for _, q := range quotes {
fmt.Printf(" %s: bid $%d / ask $%d\n", q.maker, q.bid, q.ask)
}
fmt.Println()
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
// Encrypt
fmt.Println("Encrypting quotes...")
t0 = time.Now()
type encQuote struct {
maker string
encBid [8]*fhe.Ciphertext
encAsk [8]*fhe.Ciphertext
}
encQuotes := make([]encQuote, len(quotes))
for i, q := range quotes {
encQuotes[i].maker = q.maker
encQuotes[i].encBid = encryptByte(enc, q.bid)
encQuotes[i].encAsk = encryptByte(enc, q.ask)
}
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
// Best bid (highest)
fmt.Println("Finding best bid (highest)...")
t0 = time.Now()
bestBidIdx := 0
for i := 1; i < len(encQuotes); i++ {
t1 := time.Now()
// is encQuotes[i].bid > encQuotes[bestBidIdx].bid?
gtResult, err := gtEncrypted(eval, encQuotes[i].encBid, encQuotes[bestBidIdx].encBid)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
// 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"`
}
isGreater := dec.Decrypt(gtResult)
fmt.Printf(" %s > %s: %v (%v)\n", encQuotes[i].maker, encQuotes[bestBidIdx].maker, isGreater, time.Since(t1))
if isGreater {
bestBidIdx = i
if err := re.BindBody(&req); err != nil {
return re.JSON(400, map[string]string{"error": "invalid body"})
}
}
fmt.Printf("Best bid found (%v)\n\n", time.Since(t0))
// Best ask (lowest)
fmt.Println("Finding best ask (lowest)...")
t0 = time.Now()
bestAskIdx := 0
for i := 1; i < len(encQuotes); i++ {
t1 := time.Now()
ltResult, err := ltEncrypted(eval, encQuotes[i].encAsk, encQuotes[bestAskIdx].encAsk)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
if req.Maker == "" {
return re.JSON(400, map[string]string{"error": "maker required"})
}
isLess := dec.Decrypt(ltResult)
fmt.Printf(" %s < %s: %v (%v)\n", encQuotes[i].maker, encQuotes[bestAskIdx].maker, isLess, time.Since(t1))
if isLess {
bestAskIdx = i
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),
}
}
fmt.Printf("Best ask found (%v)\n\n", time.Since(t0))
st.quotes = append(st.quotes, q)
st.spread = nil // invalidate cached spread
// Decrypt winners
bestBidPrice := decryptByte(dec, encQuotes[bestBidIdx].encBid)
bestAskPrice := decryptByte(dec, encQuotes[bestAskIdx].encAsk)
return re.JSON(201, map[string]string{"id": q.ID, "maker": q.Maker})
})
fmt.Println("--- Result ---")
fmt.Printf("Best bid: %s @ $%d\n", encQuotes[bestBidIdx].maker, bestBidPrice)
fmt.Printf("Best ask: %s @ $%d\n", encQuotes[bestAskIdx].maker, bestAskPrice)
fmt.Printf("Spread: $%d\n", bestAskPrice-bestBidPrice)
fmt.Println("\nNote: losing quotes were never decrypted.")
// 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 {
@@ -144,11 +191,10 @@ func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
return v
}
// ltEncrypted computes a < b on encrypted 8-bit values (MSB-first).
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
var isLess, isEqual *fhe.Ciphertext
for i := priceBits - 1; i >= 0; i-- {
bitLt, err := eval.ANDNY(a[i], b[i]) // NOT(a) AND b
bitLt, err := eval.ANDNY(a[i], b[i])
if err != nil {
return nil, err
}
@@ -178,7 +224,6 @@ func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext,
return isLess, nil
}
// gtEncrypted computes a > b: same as b < a.
func gtEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
return ltEncrypted(eval, b, a)
}
+138 -92
View File
@@ -3,133 +3,180 @@
// Command nav demonstrates confidential Net Asset Value (NAV) computation using FHE.
//
// An ETF's holdings (10 positions with share counts and prices) are encrypted.
// 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
// go run ./cmd/demos/nav serve
package main
import (
"fmt"
"os"
"time"
"log"
"sync"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
type holding struct {
ticker string
shares uint8 // number of shares held (small for demo)
price uint8 // price per share in dollars
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() {
fmt.Println("=== FHE Confidential NAV Computation Demo ===")
fmt.Println("Compute ETF NAV on encrypted holdings")
fmt.Println()
app := base.New()
// Setup
fmt.Print("Initializing FHE... ")
t0 := time.Now()
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 {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
fmt.Printf("done (%v)\n\n", time.Since(t0))
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
// ETF holdings: small values so sums stay in 8-bit range
// Total value = 2*19 + 2*21 + 1*14 + 2*17 + 1*18 + 2*25 + 1*12 + 1*20 + 2*10 + 1*14
// = 38 + 42 + 14 + 34 + 18 + 50 + 12 + 20 + 20 + 14 = 262
// But 262 > 255, so let's reduce: use 5 holdings to stay under 255.
holdings := []holding{
{ticker: "AAPL", shares: 2, price: 19},
{ticker: "MSFT", shares: 2, price: 21},
{ticker: "NVDA", shares: 1, price: 14},
{ticker: "GOOG", shares: 2, price: 17},
{ticker: "AMZN", shares: 1, price: 18},
}
totalShares := uint64(5) // total ETF shares outstanding
func registerRoutes(se *core.ServeEvent, st *fheState) {
g := se.Router.Group("/v1")
var expectedTotal uint64
fmt.Println("ETF Holdings (shown for verification):")
for _, h := range holdings {
value := uint64(h.shares) * uint64(h.price)
expectedTotal += value
fmt.Printf(" %6s: %d shares @ $%d = $%d\n", h.ticker, h.shares, h.price, value)
}
expectedNAV := expectedTotal / totalShares
fmt.Printf(" Total value: $%d\n", expectedTotal)
fmt.Printf(" Shares outstanding: %d\n", totalShares)
fmt.Printf(" Expected NAV: $%d/share\n\n", expectedNAV)
// 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"})
}
// Encrypt prices
fmt.Println("Encrypting holdings...")
t0 = time.Now()
encPrices := make([][8]*fhe.Ciphertext, len(holdings))
for i, h := range holdings {
encPrices[i] = encryptByte(enc, h.price)
}
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
st.mu.Lock()
defer st.mu.Unlock()
// Compute NAV: for each position, add price to itself (shares-1) times, then sum all
fmt.Println("Computing NAV on encrypted data...")
fmt.Println(" Strategy: repeated addition (shares * price)")
t0 = time.Now()
st.holdings = nil
st.result = nil
st.sharesOutstanding = req.SharesOutstanding
var encTotal [8]*fhe.Ciphertext
initialized := false
for _, h := range req.Holdings {
st.holdings = append(st.holdings, encHolding{
Ticker: h.Ticker,
Shares: h.Shares,
EncPrice: encryptByte(st.enc, h.Price),
})
}
for idx, h := range holdings {
t1 := time.Now()
// shares * price via repeated addition
posValue := encPrices[idx]
for s := uint8(1); s < h.shares; s++ {
posValue, err = addBytes(eval, enc, posValue, encPrices[idx])
if err != nil {
fmt.Fprintf(os.Stderr, "error multiplying %s: %v\n", h.ticker, err)
os.Exit(1)
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()})
}
}
}
if !initialized {
encTotal = posValue
initialized = true
} else {
encTotal, err = addBytes(eval, enc, encTotal, posValue)
if err != nil {
fmt.Fprintf(os.Stderr, "error accumulating %s: %v\n", h.ticker, err)
os.Exit(1)
}
decTotal := uint64(decryptByte(st.dec, encTotal))
st.result = &navResult{
TotalValue: decTotal,
SharesOutstanding: st.sharesOutstanding,
NAVPerShare: decTotal / st.sharesOutstanding,
}
fmt.Printf(" %6s: %d additions (%v)\n", h.ticker, h.shares, time.Since(t1))
}
fmt.Printf("Total computation: %v\n\n", time.Since(t0))
// Decrypt and compute NAV (division by public totalShares is plaintext)
decTotal := uint64(decryptByte(dec, encTotal))
navPerShare := decTotal / totalShares
return re.JSON(200, map[string]string{
"message": "NAV computed, call GET /v1/nav to see result",
})
})
fmt.Println("--- Result ---")
fmt.Printf("IBIT NAV: $%d per share (computed on encrypted data)\n", navPerShare)
fmt.Printf("Total fund value: $%d (decrypted)\n", decTotal)
fmt.Printf("Shares outstanding: %d (public)\n", totalShares)
// GET /v1/nav — get encrypted NAV value
g.GET("/nav", func(re *core.RequestEvent) error {
st.mu.Lock()
defer st.mu.Unlock()
if navPerShare == expectedNAV {
fmt.Println("PASS: NAV matches expected value")
} else {
fmt.Printf("NOTE: NAV=%d vs expected=%d (FHE arithmetic precision)\n", navPerShare, expectedNAV)
}
fmt.Println("\nNote: individual holdings were never revealed to the NAV calculator.")
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 {
@@ -150,7 +197,6 @@ func decryptByte(dec *fhe.Decryptor, bits [8]*fhe.Ciphertext) uint8 {
return v
}
// addBytes adds two encrypted 8-bit values using ripple-carry full adder.
func addBytes(eval *fhe.Evaluator, enc *fhe.Encryptor, a, b [8]*fhe.Ciphertext) ([8]*fhe.Ciphertext, error) {
carry := enc.Encrypt(false)
var result [8]*fhe.Ciphertext
+163 -123
View File
@@ -11,150 +11,191 @@
//
// Usage:
//
// go run ./cmd/demos/proof
// go run ./cmd/demos/proof serve
package main
import (
"fmt"
"os"
"time"
"log"
"sync"
"github.com/hanzoai/base"
"github.com/hanzoai/base/core"
"github.com/luxfi/fhe"
)
const valueBits = 8
type position struct {
name string
weight uint8 // percentage
entityID uint8 // counterparty ID
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() {
fmt.Println("=== FHE Compliance Proof Demo ===")
fmt.Println("Prove compliance without revealing portfolio data")
fmt.Println()
app := base.New()
// Setup
fmt.Print("Initializing FHE... ")
t0 := time.Now()
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 {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
fmt.Printf("done (%v)\n\n", time.Since(t0))
// Portfolio
portfolio := []position{
{name: "AAPL", weight: 20, entityID: 11},
{name: "MSFT", weight: 18, entityID: 22},
{name: "NVDA", weight: 15, entityID: 33},
{name: "GOOG", weight: 12, entityID: 44},
{name: "AMZN", weight: 10, entityID: 55},
}
sanctionedIDs := []uint8{99, 88, 77}
fmt.Println("Portfolio (shown for verification):")
for _, p := range portfolio {
fmt.Printf(" %s: %d%% (entity %d)\n", p.name, p.weight, p.entityID)
}
fmt.Printf("Sanctioned entities: %v\n\n", sanctionedIDs)
// Encrypt
fmt.Println("Encrypting portfolio...")
t0 = time.Now()
encWeights := make([][8]*fhe.Ciphertext, len(portfolio))
encEntities := make([][8]*fhe.Ciphertext, len(portfolio))
for i, p := range portfolio {
encWeights[i] = encryptByte(enc, p.weight)
encEntities[i] = encryptByte(enc, p.entityID)
}
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
// Proof 1: max position < 25%
fmt.Println("Proof 1: No position exceeds 25%...")
t0 = time.Now()
encThreshold := encryptByte(enc, 25)
allUnder := enc.Encrypt(true)
for i, p := range portfolio {
t1 := time.Now()
// weight <= 25: NOT(25 < weight)
threshLtWeight, err := ltEncrypted(eval, encThreshold, encWeights[i])
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
withinLimit := eval.NOT(threshLtWeight) // weight <= 25
allUnder, err = eval.AND(allUnder, withinLimit)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf(" %s checked (%v)\n", p.name, time.Since(t1))
}
diversificationOk := dec.Decrypt(allUnder)
fmt.Printf(" Result: %v (%v)\n\n", diversificationOk, time.Since(t0))
// Proof 2: no sanctioned counterparty
fmt.Println("Proof 2: No sanctioned counterparty...")
t0 = time.Now()
noSanctioned := enc.Encrypt(true)
for _, sid := range sanctionedIDs {
encSanctioned := encryptByte(enc, sid)
for i, p := range portfolio {
t1 := time.Now()
// Check entity != sanctioned using XOR: if any bit differs, they are not equal
isEqual := enc.Encrypt(true)
for bit := 0; bit < valueBits; bit++ {
bitXor, err := eval.XOR(encEntities[i][bit], encSanctioned[bit])
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
bitsMatch := eval.NOT(bitXor) // bits match if XOR is 0
isEqual, err = eval.AND(isEqual, bitsMatch)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
// notEqual = NOT(isEqual)
notEqual := eval.NOT(isEqual)
noSanctioned, err = eval.AND(noSanctioned, notEqual)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf(" %s vs entity %d (%v)\n", p.name, sid, time.Since(t1))
}
}
sanctionsOk := dec.Decrypt(noSanctioned)
fmt.Printf(" Result: %v (%v)\n\n", sanctionsOk, time.Since(t0))
// Final
overallPass := diversificationOk && sanctionsOk
fmt.Println("--- Result ---")
fmt.Printf("Compliance proof: %s (portfolio data remained encrypted)\n", label(overallPass))
fmt.Printf(" Diversification (max < 25%%): %s\n", label(diversificationOk))
fmt.Printf(" Sanctions screening: %s\n", label(sanctionsOk))
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
func label(ok bool) string {
if ok {
return "PASS"
}
return "FAIL"
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 {
@@ -165,7 +206,6 @@ func encryptByte(enc *fhe.Encryptor, v uint8) [8]*fhe.Ciphertext {
return bits
}
// ltEncrypted computes a < b on 8-bit encrypted values (MSB-first).
func ltEncrypted(eval *fhe.Evaluator, a, b [8]*fhe.Ciphertext) (*fhe.Ciphertext, error) {
var isLess, isEqual *fhe.Ciphertext
for i := valueBits - 1; i >= 0; i-- {
+131 -109
View File
@@ -3,142 +3,164 @@
// Command voting demonstrates private shareholder voting using FHE.
//
// 100 shareholders vote encrypted yes/no. Votes are tallied homomorphically
// Shareholders vote encrypted yes/no. Votes are tallied homomorphically
// using a ripple-carry adder on encrypted bits. Only the final count is
// decrypted -- individual votes are never revealed.
//
// Usage:
//
// go run ./cmd/demos/voting
// go run ./cmd/demos/voting -voters 50 -yes 30
// go run ./cmd/demos/voting serve
package main
import (
"flag"
"fmt"
"math/rand/v2"
"os"
"time"
"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() {
nVoters := flag.Int("voters", 100, "number of shareholders (max 127)")
nYes := flag.Int("yes", -1, "number of yes votes (-1 = random ~67%)")
flag.Parse()
app := base.New()
if *nVoters < 2 || *nVoters > 127 {
fmt.Fprintln(os.Stderr, "error: -voters must be 2..127")
os.Exit(1)
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)
}
}
fmt.Println("=== FHE Private Shareholder Voting Demo ===")
fmt.Println("Homomorphic tally without revealing individual votes")
fmt.Println()
// Generate votes
votes := make([]bool, *nVoters)
if *nYes >= 0 {
if *nYes > *nVoters {
fmt.Fprintln(os.Stderr, "error: -yes cannot exceed -voters")
os.Exit(1)
}
for i := 0; i < *nYes; i++ {
votes[i] = true
}
} else {
for i := range votes {
votes[i] = rand.IntN(3) != 0 // ~67% yes
}
}
expectedYes := 0
for _, v := range votes {
if v {
expectedYes++
}
}
fmt.Printf("Shareholders: %d | Expected: %d yes, %d no\n\n", *nVoters, expectedYes, *nVoters-expectedYes)
// Setup FHE
fmt.Print("Initializing FHE parameters... ")
t0 := time.Now()
func initFHE() (*fheState, error) {
params, err := fhe.NewParametersFromLiteral(fhe.PN10QP27)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return nil, err
}
keygen := fhe.NewKeyGenerator(params)
sk, _ := keygen.GenKeyPair()
bsk := keygen.GenBootstrapKey(sk)
enc := fhe.NewEncryptor(params, sk)
dec := fhe.NewDecryptor(params, sk)
eval := fhe.NewEvaluator(params, bsk)
fmt.Printf("done (%v)\n\n", time.Since(t0))
// Encrypt ballots
fmt.Printf("Encrypting %d ballots...\n", *nVoters)
t0 = time.Now()
ballots := make([]*fhe.Ciphertext, *nVoters)
for i, v := range votes {
ballots[i] = enc.Encrypt(v)
}
fmt.Printf("Encryption done (%v)\n\n", time.Since(t0))
// Initialize tally accumulator (7-bit: supports 0-127)
tally := [tallyBits]*fhe.Ciphertext{}
for i := 0; i < tallyBits; i++ {
tally[i] = enc.Encrypt(false)
}
// Tally votes homomorphically
fmt.Printf("Tallying %d ballots homomorphically (%d-bit accumulator)...\n", *nVoters, tallyBits)
t0 = time.Now()
for i, ballot := range ballots {
if (i+1)%10 == 0 || i == 0 {
fmt.Printf(" Processing ballot %d/%d...\n", i+1, *nVoters)
}
tally, err = addBit(eval, tally, ballot)
if err != nil {
fmt.Fprintf(os.Stderr, "error adding ballot %d: %v\n", i+1, err)
os.Exit(1)
}
}
tallyElapsed := time.Since(t0)
fmt.Printf("Tally done (%v, %.1fms per ballot)\n\n",
tallyElapsed, float64(tallyElapsed.Milliseconds())/float64(*nVoters))
// Decrypt only the final tally
var yesCount uint8
for i := 0; i < tallyBits; i++ {
if dec.Decrypt(tally[i]) {
yesCount |= 1 << i
}
}
noCount := *nVoters - int(yesCount)
fmt.Println("--- Result ---")
passed := int(yesCount) > *nVoters/2
if passed {
fmt.Printf("Vote passed: %d yes, %d no\n", yesCount, noCount)
} else {
fmt.Printf("Vote failed: %d yes, %d no\n", yesCount, noCount)
}
fmt.Printf("Threshold: >%d (simple majority)\n", *nVoters/2)
if int(yesCount) == expectedYes {
fmt.Println("PASS: tally matches expected count")
} else {
fmt.Println("FAIL: tally mismatch!")
}
fmt.Println("\nNote: individual ballots were never decrypted.")
return &fheState{
enc: fhe.NewEncryptor(params, sk),
dec: fhe.NewDecryptor(params, sk),
eval: fhe.NewEvaluator(params, bsk),
}, nil
}
// addBit adds a single encrypted bit to an n-bit encrypted accumulator
// using a ripple-carry adder (XOR for sum, AND for carry).
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
+50 -8
View File
@@ -4,6 +4,7 @@ go 1.26.1
require (
github.com/google/uuid v1.6.0
github.com/hanzoai/base v0.39.1
github.com/luxfi/database v1.17.44
github.com/luxfi/lattice/v7 v7.0.0
github.com/luxfi/mdns v0.1.0
@@ -13,23 +14,43 @@ require (
require (
github.com/ALTree/bigfloat v0.2.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/gorilla/rpc v1.2.1 // indirect
github.com/grandcat/zeroconf v1.0.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hanzoai/dbx v1.13.0 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.9.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/luxfi/cache v1.2.1 // indirect
github.com/luxfi/codec v1.1.3 // indirect
github.com/luxfi/compress v0.0.5 // indirect
github.com/luxfi/concurrent v0.0.3 // indirect
github.com/luxfi/crypto v1.17.39 // indirect
github.com/luxfi/crypto v1.17.45 // indirect
github.com/luxfi/ids v1.2.9 // indirect
github.com/luxfi/log v1.4.1 // indirect
github.com/luxfi/math v1.2.3 // indirect
@@ -38,23 +59,44 @@ require (
github.com/luxfi/zapdb/v4 v4.9.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.66 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nexus-rpc/sdk-go v0.6.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/stretchr/testify v1.11.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.temporal.io/api v1.62.6 // indirect
go.temporal.io/sdk v1.41.1 // indirect
go.uber.org/mock v0.6.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.org/x/image v0.38.0 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.48.0 // indirect
)
+182 -18
View File
@@ -2,10 +2,15 @@ github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a81 h1:TBzelXBdnzDy+HCrBMcomEnhrmigkWOI1/mIPCi2u4M=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260215031811-a0ab0b218a81/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
@@ -14,25 +19,76 @@ github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuM
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hanzoai/base v0.39.1 h1:UEXbz5m0VF85QOb++ShHt4FKSCChqtwCigw6oItyNeI=
github.com/hanzoai/base v0.39.1/go.mod h1:gGQrufNjkkX4/4NDPpl2aizAJLTYJ3VcyV4TDE4MANc=
github.com/hanzoai/dbx v1.13.0 h1:IVCfz4NMKJ1EfAOEo3Nyfs67qj8v5Kx6aIjAja/9ANA=
github.com/hanzoai/dbx v1.13.0/go.mod h1:WIRkg1f2Mc+vXo2gpWk+hgPsJPFZBNUFCkSFJ4M5s4k=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -51,8 +107,8 @@ github.com/luxfi/constants v1.4.3 h1:bcqUO8twMOhHWCgN/RbjfWulVPbvUQcxDkssaRrCx4g
github.com/luxfi/constants v1.4.3/go.mod h1:ENkJ121cmDEkwQPDiKK4QhnTnW9u37PGpepbrdVcAmc=
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
github.com/luxfi/container v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
github.com/luxfi/crypto v1.17.39 h1:dDmktYOD/sU6WjIpitIfuHp7mRbc3izOsyJrQ1c5eOQ=
github.com/luxfi/crypto v1.17.39/go.mod h1:mChLWmW4CLR1wAN6CeJTveCzUv0DTzGQnYgq01x3W0U=
github.com/luxfi/crypto v1.17.45 h1:uGK0y4+aLipE/M0YIQ5hcsWv0ZG0E4cPv03a94K/eLE=
github.com/luxfi/crypto v1.17.45/go.mod h1:GnAkhQ7HNs3X0Tzx5nOONS3kl0yRmWHbDcRO5ffILsg=
github.com/luxfi/database v1.17.44 h1:hfiTls7sqbweW+o4iaZqB8P997paC+vpgWmhN6v5MJ0=
github.com/luxfi/database v1.17.44/go.mod h1:6Ey5y3I0WNLHbxIlIdFqUuKfBg+b0fAgTA8FgRgQ8zg=
github.com/luxfi/geth v1.16.69 h1:CHO6xTZ+A+3itk94ts4uyVRJajNVP3RxWTjJp5qGOlk=
@@ -84,61 +140,139 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo=
github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/sdk v1.41.1 h1:yOpvsHyDD1lNuwlGBv/SUodCPhjv9nDeC9lLHW/fJUA=
go.temporal.io/sdk v1.41.1/go.mod h1:/InXQT5guZ6AizYzpmzr5avQ/GMgq1ZObcKlKE2AhTc=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -146,5 +280,35 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4=
modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=