mirror of
https://github.com/luxfi/fhe.git
synced 2026-07-26 23:16:08 +00:00
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:
+129
-71
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user