Files
cli/pkg/key/backend_kchain.go
zeekay 2501973bfa deps: escape four deleted geth tags; 3 API errors remain
This module could not RESOLVE at all — not "failed to compile", but the module
graph itself was unloadable:

    reading github.com/luxfi/geth/go.mod at revision v1.16.99: unknown revision

Two geth tags this repo transitively needed have been DELETED from GitHub
(v1.16.98 AND v1.16.99; the whole v1.16.x line is gone, newest is v1.20.1).
proxy.golang.org still serves them, but GOPRIVATE forces a direct fetch for
luxfi/*, so resolution fails outright.

That created a deadlock: `go get` could not apply the fix because it must load
the graph first, and the graph could not load because of the dead tag. Broken
with `go mod edit -require` (which does not resolve) to pin the escape versions
first, then tidy:

    crypto v1.20.2 · constants v1.6.2 · warp v1.24.1
    ids v1.3.2 · p2p v1.22.1 · sdk v1.18.1 · geth v1.20.1

Also follows the BLS scheme move: github.com/luxfi/crypto/threshold/bls is gone
(from the published module AND the local tree), and now lives at
github.com/luxfi/threshold/scheme/bls. Verified it is the same registration —
that package imports crypto/threshold and its init() calls RegisterScheme.

STATE: dependency resolution now WORKS. The build is not green — 3 compile
errors remain, all genuine API changes needing an owner's intent, not renames:

  1. warp v1.24.1 makes Message and Envelope DISTINCT types.
     SignMessage(*Message) returns (*Envelope, error), so a signed message is
     now a different type that has to be threaded through
     pkg/signatureaggregator and pkg/contract. Not a rename — do not sed it.
  2. keys.LoadMnemonic is gone. The nearest replacements are
     DeriveValidatorFromMnemonic(mnemonic, accountIndex) and
     DeriveValidatorsFromMnemonic(mnemonic, count) — both need an account index
     or count this code does not currently supply, so picking one is a
     behavioural decision.

Committed because it strictly improves the state: "graph will not load" blocks
every tool including the IDE, whereas 3 named compile errors are actionable.
2026-07-26 05:42:27 -07:00

869 lines
23 KiB
Go

// Copyright (C) 2022-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package key
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"net"
"sync"
"time"
"github.com/luxfi/crypto/mlkem"
"github.com/luxfi/crypto/threshold"
_ "github.com/luxfi/threshold/scheme/bls" // register BLS scheme (one registry, crypto/threshold)
)
// BackendKChain is the K-Chain distributed secrets backend type.
const BackendKChain BackendType = "kchain"
// K-Chain errors.
var (
ErrKChainUnavailable = errors.New("kchain: network unavailable")
ErrInvalidShareConfig = errors.New("kchain: invalid share configuration")
ErrInsufficientShares = errors.New("kchain: insufficient shares for reconstruction")
ErrValidatorUnreachable = errors.New("kchain: validator unreachable")
ErrShareStoreFailed = errors.New("kchain: failed to store share")
ErrShareRetrieveFailed = errors.New("kchain: failed to retrieve share")
ErrThresholdSigningFailed = errors.New("kchain: threshold signing failed")
ErrKeyNotDistributed = errors.New("kchain: key not distributed to validators")
)
// ShareConfig configures threshold secret sharing parameters.
type ShareConfig struct {
N int // Total number of shares
K int // Threshold required to reconstruct
ValidatorAddrs []string // Validator network addresses
}
// Validate checks if the share configuration is valid.
func (c *ShareConfig) Validate() error {
if c.N < 2 {
return fmt.Errorf("%w: N must be >= 2", ErrInvalidShareConfig)
}
if c.K < 1 || c.K > c.N {
return fmt.Errorf("%w: K must be 1 <= K <= N", ErrInvalidShareConfig)
}
if len(c.ValidatorAddrs) != c.N {
return fmt.Errorf("%w: validator count must equal N", ErrInvalidShareConfig)
}
return nil
}
// EncryptedShare holds an ML-KEM encrypted key share.
type EncryptedShare struct {
Index int // Share index (1 to N)
Ciphertext []byte // ML-KEM ciphertext
EncryptedKey []byte // AES-GCM encrypted share data
Nonce []byte // AES-GCM nonce
ValidatorID string // Target validator identifier
}
// DistributedKeyInfo holds metadata about a distributed key.
type DistributedKeyInfo struct {
Name string `json:"name"`
GroupPublicKey []byte `json:"group_public_key"`
ShareConfig ShareConfig `json:"share_config"`
CreatedAt int64 `json:"created_at"`
KeyType string `json:"key_type"` // "bls", "ec"
}
// KChainBackend implements distributed key storage using threshold cryptography.
type KChainBackend struct {
mu sync.RWMutex
endpoint string
connected bool
timeout time.Duration
distributedKeys map[string]*DistributedKeyInfo
mlkemKeys map[string]*mlkem.PrivateKey // validator ML-KEM keys
rpcClient *KChainRPCClient // RPC client for K-Chain API
}
// NewKChainBackend creates a new K-Chain distributed secrets backend.
func NewKChainBackend() *KChainBackend {
return &KChainBackend{
timeout: 30 * time.Second,
distributedKeys: make(map[string]*DistributedKeyInfo),
mlkemKeys: make(map[string]*mlkem.PrivateKey),
}
}
// Type returns the backend type identifier.
func (*KChainBackend) Type() BackendType {
return BackendKChain
}
// Name returns a human-readable name.
func (*KChainBackend) Name() string {
return "K-Chain Distributed Secrets"
}
// Available checks if this backend is available (connected to K-Chain).
func (b *KChainBackend) Available() bool {
b.mu.RLock()
defer b.mu.RUnlock()
return b.connected
}
// RequiresPassword returns false; keys are protected by threshold distribution.
func (*KChainBackend) RequiresPassword() bool {
return false
}
// RequiresHardware returns false; uses network validators.
func (*KChainBackend) RequiresHardware() bool {
return false
}
// SupportsRemoteSigning returns true; signing happens on validators.
func (*KChainBackend) SupportsRemoteSigning() bool {
return true
}
// Initialize sets up the backend and attempts K-Chain connection.
func (b *KChainBackend) Initialize(ctx context.Context) error {
b.mu.Lock()
defer b.mu.Unlock()
// Default K-Chain endpoint (963N port range)
if b.endpoint == "" {
b.endpoint = "http://localhost:9630"
}
// Create RPC client
b.rpcClient = NewKChainRPCClient(b.endpoint)
// Check K-Chain connectivity via health endpoint
health, err := b.rpcClient.Health(ctx)
if err != nil {
// Try direct TCP connection as fallback
host := b.endpoint
if len(host) > 7 && host[:7] == "http://" {
host = host[7:]
} else if len(host) > 8 && host[:8] == "https://" {
host = host[8:]
}
conn, err := net.DialTimeout("tcp", host, 5*time.Second)
if err != nil {
b.connected = false
return nil // Not available, but not an error
}
_ = conn.Close()
b.connected = true
return nil
}
b.connected = health.Healthy
return nil
}
// SetEndpoint configures the K-Chain endpoint.
func (b *KChainBackend) SetEndpoint(endpoint string) {
b.mu.Lock()
defer b.mu.Unlock()
b.endpoint = endpoint
}
// Close cleans up resources.
func (b *KChainBackend) Close() error {
b.mu.Lock()
defer b.mu.Unlock()
// Clear sensitive data
for k := range b.mlkemKeys {
b.mlkemKeys[k] = nil
delete(b.mlkemKeys, k)
}
b.connected = false
return nil
}
// CreateKey creates a new distributed key set.
func (b *KChainBackend) CreateKey(ctx context.Context, name string, opts CreateKeyOptions) (*HDKeySet, error) {
if !b.Available() {
return nil, ErrKChainUnavailable
}
// Generate key set locally first
var mnemonic string
if opts.Mnemonic != "" {
if !ValidateMnemonic(opts.Mnemonic) {
return nil, errors.New("invalid mnemonic phrase")
}
mnemonic = opts.Mnemonic
} else {
var err error
mnemonic, err = GenerateMnemonic()
if err != nil {
return nil, fmt.Errorf("failed to generate mnemonic: %w", err)
}
}
keySet, err := DeriveAllKeys(name, mnemonic)
if err != nil {
return nil, fmt.Errorf("failed to derive keys: %w", err)
}
// Note: The key set is created but not yet distributed.
// Call DistributeKey() to split and distribute to validators.
return keySet, nil
}
// DistributeKey splits a key into shares and distributes to validators.
func (b *KChainBackend) DistributeKey(ctx context.Context, name string, keyData []byte, config ShareConfig) error {
if err := config.Validate(); err != nil {
return err
}
// Split secret using Shamir Secret Sharing
shares, err := b.splitSecret(keyData, config.K, config.N)
if err != nil {
return fmt.Errorf("failed to split secret: %w", err)
}
// Generate ML-KEM key pairs for each validator if not cached
validatorPubKeys := make([]*mlkem.PublicKey, config.N)
for i, addr := range config.ValidatorAddrs {
pubKey, err := b.getValidatorPublicKey(ctx, addr)
if err != nil {
return fmt.Errorf("failed to get validator %s public key: %w", addr, err)
}
validatorPubKeys[i] = pubKey
}
// Encrypt and distribute shares
for i, share := range shares {
encShare, err := b.encryptShare(share, i+1, validatorPubKeys[i], config.ValidatorAddrs[i])
if err != nil {
return fmt.Errorf("failed to encrypt share %d: %w", i+1, err)
}
if err := b.storeShareOnValidator(ctx, config.ValidatorAddrs[i], name, encShare); err != nil {
return fmt.Errorf("failed to store share on validator %s: %w", config.ValidatorAddrs[i], err)
}
}
// Store distributed key metadata
b.mu.Lock()
b.distributedKeys[name] = &DistributedKeyInfo{
Name: name,
ShareConfig: config,
CreatedAt: time.Now().Unix(),
KeyType: "generic",
}
b.mu.Unlock()
return nil
}
// DistributeBLSKey distributes a BLS key using threshold BLS scheme.
func (b *KChainBackend) DistributeBLSKey(ctx context.Context, name string, config ShareConfig) (threshold.PublicKey, error) {
if err := config.Validate(); err != nil {
return nil, err
}
// Get the BLS threshold scheme
scheme, err := threshold.GetScheme(threshold.SchemeBLS)
if err != nil {
return nil, fmt.Errorf("BLS scheme not available: %w", err)
}
// Create trusted dealer for key generation
dealer, err := scheme.NewTrustedDealer(threshold.DealerConfig{
Threshold: config.K,
TotalParties: config.N,
})
if err != nil {
return nil, fmt.Errorf("failed to create dealer: %w", err)
}
// Generate key shares
shares, groupKey, err := dealer.GenerateShares(ctx)
if err != nil {
return nil, fmt.Errorf("failed to generate shares: %w", err)
}
// Distribute shares to validators (encrypted with ML-KEM)
for i, share := range shares {
shareBytes := share.Bytes()
pubKey, err := b.getValidatorPublicKey(ctx, config.ValidatorAddrs[i])
if err != nil {
return nil, fmt.Errorf("failed to get validator public key: %w", err)
}
encShare, err := b.encryptShare(shareBytes, i+1, pubKey, config.ValidatorAddrs[i])
if err != nil {
return nil, fmt.Errorf("failed to encrypt share: %w", err)
}
if err := b.storeShareOnValidator(ctx, config.ValidatorAddrs[i], name, encShare); err != nil {
return nil, fmt.Errorf("failed to store share: %w", err)
}
}
// Store metadata
b.mu.Lock()
b.distributedKeys[name] = &DistributedKeyInfo{
Name: name,
GroupPublicKey: groupKey.Bytes(),
ShareConfig: config,
CreatedAt: time.Now().Unix(),
KeyType: "bls",
}
b.mu.Unlock()
return groupKey, nil
}
// ReconstructKey gathers K shares and reconstructs the secret.
func (b *KChainBackend) ReconstructKey(ctx context.Context, name string) ([]byte, error) {
b.mu.RLock()
keyInfo, exists := b.distributedKeys[name]
b.mu.RUnlock()
if !exists {
return nil, ErrKeyNotDistributed
}
config := keyInfo.ShareConfig
// Gather shares from validators
shares := make([][]byte, 0, config.K)
indices := make([]int, 0, config.K)
for i, addr := range config.ValidatorAddrs {
encShare, err := b.retrieveShareFromValidator(ctx, addr, name)
if err != nil {
continue // Try other validators
}
// Decrypt share using our ML-KEM private key
shareData, err := b.decryptShare(encShare)
if err != nil {
continue
}
shares = append(shares, shareData)
indices = append(indices, i+1) // Shamir uses 1-indexed
if len(shares) >= config.K {
break
}
}
if len(shares) < config.K {
return nil, fmt.Errorf("%w: got %d, need %d", ErrInsufficientShares, len(shares), config.K)
}
// Reconstruct secret using Lagrange interpolation
return b.reconstructSecret(shares, indices)
}
// LoadKey loads a distributed key by reconstructing from shares.
func (b *KChainBackend) LoadKey(ctx context.Context, name, password string) (*HDKeySet, error) {
// For K-Chain, password is not used - security comes from threshold distribution
b.mu.RLock()
keyInfo, exists := b.distributedKeys[name]
b.mu.RUnlock()
if !exists {
return nil, ErrKeyNotFound
}
if keyInfo.KeyType == "bls" {
// BLS keys are not fully reconstructed locally for security
// Return a stub with group public key
return &HDKeySet{
Name: name,
BLSPublicKey: keyInfo.GroupPublicKey,
}, nil
}
// Reconstruct generic key
keyData, err := b.ReconstructKey(ctx, name)
if err != nil {
return nil, err
}
// Parse reconstructed key data
var keySet HDKeySet
if err := json.Unmarshal(keyData, &keySet); err != nil {
return nil, fmt.Errorf("failed to parse key data: %w", err)
}
return &keySet, nil
}
// SaveKey distributes a key set to validators.
func (b *KChainBackend) SaveKey(ctx context.Context, keySet *HDKeySet, password string) error {
// Serialize key set
keyData, err := json.Marshal(keySet)
if err != nil {
return fmt.Errorf("failed to serialize key set: %w", err)
}
// Use default share config if not set
config := ShareConfig{
N: 5,
K: 3,
ValidatorAddrs: b.getDefaultValidators(),
}
return b.DistributeKey(ctx, keySet.Name, keyData, config)
}
// DeleteKey removes distributed shares from validators.
func (b *KChainBackend) DeleteKey(ctx context.Context, name string) error {
b.mu.Lock()
keyInfo, exists := b.distributedKeys[name]
if exists {
delete(b.distributedKeys, name)
}
b.mu.Unlock()
if !exists {
return nil
}
// Request deletion from validators
for _, addr := range keyInfo.ShareConfig.ValidatorAddrs {
_ = b.deleteShareFromValidator(ctx, addr, name) // Best effort
}
return nil
}
// ListKeys returns all distributed keys.
func (b *KChainBackend) ListKeys(ctx context.Context) ([]KeyInfo, error) {
b.mu.RLock()
defer b.mu.RUnlock()
keys := make([]KeyInfo, 0, len(b.distributedKeys))
for name, info := range b.distributedKeys {
keys = append(keys, KeyInfo{
Name: name,
Encrypted: true, // Shares are encrypted
Locked: false,
CreatedAt: time.Unix(info.CreatedAt, 0),
})
}
return keys, nil
}
// Lock is a no-op for distributed keys (always protected by threshold).
func (*KChainBackend) Lock(_ context.Context, _ string) error {
return nil
}
// Unlock is a no-op for distributed keys.
func (*KChainBackend) Unlock(_ context.Context, _, _ string) error {
return nil
}
// IsLocked returns false; distributed keys are not locked in traditional sense.
func (*KChainBackend) IsLocked(_ string) bool {
return false
}
// Sign performs threshold BLS signing using validators.
func (b *KChainBackend) Sign(ctx context.Context, name string, request SignRequest) (*SignResponse, error) {
b.mu.RLock()
keyInfo, exists := b.distributedKeys[name]
b.mu.RUnlock()
if !exists {
return nil, ErrKeyNotDistributed
}
if keyInfo.KeyType != "bls" {
return nil, fmt.Errorf("threshold signing only supported for BLS keys")
}
config := keyInfo.ShareConfig
// Request signature shares from validators
sigShares := make([]threshold.SignatureShare, 0, config.K)
scheme, err := threshold.GetScheme(threshold.SchemeBLS)
if err != nil {
return nil, err
}
for _, addr := range config.ValidatorAddrs {
shareData, err := b.requestSignatureShare(ctx, addr, name, request.Data)
if err != nil {
continue
}
sigShare, err := scheme.ParseSignatureShare(shareData)
if err != nil {
continue
}
sigShares = append(sigShares, sigShare)
if len(sigShares) >= config.K {
break
}
}
if len(sigShares) < config.K {
return nil, fmt.Errorf("%w: insufficient signature shares", ErrThresholdSigningFailed)
}
// Parse group public key
groupKey, err := scheme.ParsePublicKey(keyInfo.GroupPublicKey)
if err != nil {
return nil, fmt.Errorf("failed to parse group key: %w", err)
}
// Aggregate signature shares
aggregator, err := scheme.NewAggregator(groupKey)
if err != nil {
return nil, err
}
sig, err := aggregator.Aggregate(ctx, request.Data, sigShares, nil)
if err != nil {
return nil, fmt.Errorf("failed to aggregate signatures: %w", err)
}
return &SignResponse{
Signature: sig.Bytes(),
PublicKey: keyInfo.GroupPublicKey,
}, nil
}
// splitSecret splits a secret using Shamir Secret Sharing.
// Uses GF(2^256) arithmetic for splitting arbitrary byte data.
func (*KChainBackend) splitSecret(secret []byte, k, n int) ([][]byte, error) {
if k < 1 || k > n || n > 255 {
return nil, ErrInvalidShareConfig
}
// Prime for finite field arithmetic (256-bit)
prime := new(big.Int)
prime.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639747", 10)
// Convert secret to big.Int
secretInt := new(big.Int).SetBytes(secret)
if secretInt.Cmp(prime) >= 0 {
// Secret too large - hash it
h := sha256.Sum256(secret)
secretInt = new(big.Int).SetBytes(h[:])
}
// Generate random polynomial coefficients
coeffs := make([]*big.Int, k)
coeffs[0] = secretInt
for i := 1; i < k; i++ {
coeff, err := rand.Int(rand.Reader, prime)
if err != nil {
return nil, err
}
coeffs[i] = coeff
}
// Evaluate polynomial at points 1, 2, ..., n
shares := make([][]byte, n)
for i := 0; i < n; i++ {
x := big.NewInt(int64(i + 1))
y := evaluatePoly(coeffs, x, prime)
// Encode share: index (1 byte) + y value (32 bytes)
share := make([]byte, 33)
share[0] = byte(i + 1)
yBytes := y.Bytes()
copy(share[33-len(yBytes):], yBytes)
shares[i] = share
}
return shares, nil
}
// reconstructSecret reconstructs secret using Lagrange interpolation.
func (*KChainBackend) reconstructSecret(shares [][]byte, indices []int) ([]byte, error) {
prime := new(big.Int)
prime.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639747", 10)
// Parse shares
points := make(map[int]*big.Int)
for i, share := range shares {
if len(share) < 33 {
continue
}
y := new(big.Int).SetBytes(share[1:33])
points[indices[i]] = y
}
// Lagrange interpolation at x=0
secret := big.NewInt(0)
for xi, yi := range points {
// Compute Lagrange basis polynomial at x=0
numerator := big.NewInt(1)
denominator := big.NewInt(1)
for xj := range points {
if xi == xj {
continue
}
// numerator *= -xj = 0 - xj
neg := new(big.Int).Neg(big.NewInt(int64(xj)))
neg.Mod(neg, prime)
numerator.Mul(numerator, neg)
numerator.Mod(numerator, prime)
// denominator *= (xi - xj)
diff := big.NewInt(int64(xi - xj))
diff.Mod(diff, prime)
denominator.Mul(denominator, diff)
denominator.Mod(denominator, prime)
}
// basis = numerator / denominator
denomInv := new(big.Int).ModInverse(denominator, prime)
basis := new(big.Int).Mul(numerator, denomInv)
basis.Mod(basis, prime)
// secret += yi * basis
term := new(big.Int).Mul(yi, basis)
term.Mod(term, prime)
secret.Add(secret, term)
secret.Mod(secret, prime)
}
// Convert back to bytes
result := make([]byte, 32)
secretBytes := secret.Bytes()
copy(result[32-len(secretBytes):], secretBytes)
return result, nil
}
// evaluatePoly evaluates polynomial at point x in finite field.
func evaluatePoly(coeffs []*big.Int, x, prime *big.Int) *big.Int {
result := new(big.Int).Set(coeffs[len(coeffs)-1])
for i := len(coeffs) - 2; i >= 0; i-- {
result.Mul(result, x)
result.Add(result, coeffs[i])
result.Mod(result, prime)
}
return result
}
// encryptShare encrypts a share using ML-KEM hybrid encryption.
func (*KChainBackend) encryptShare(shareData []byte, index int, pubKey *mlkem.PublicKey, validatorID string) (*EncryptedShare, error) {
// ML-KEM encapsulation
ciphertext, sharedSecret, err := pubKey.Encapsulate()
if err != nil {
return nil, fmt.Errorf("ML-KEM encapsulation failed: %w", err)
}
// Derive AES key from shared secret
aesKey := sha256.Sum256(sharedSecret)
// AES-GCM encryption of share data
block, err := aes.NewCipher(aesKey[:])
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
encryptedData := gcm.Seal(nil, nonce, shareData, nil)
return &EncryptedShare{
Index: index,
Ciphertext: ciphertext,
EncryptedKey: encryptedData,
Nonce: nonce,
ValidatorID: validatorID,
}, nil
}
// decryptShare decrypts an encrypted share using local ML-KEM private key.
func (b *KChainBackend) decryptShare(encShare *EncryptedShare) ([]byte, error) {
b.mu.RLock()
privKey, exists := b.mlkemKeys[encShare.ValidatorID]
b.mu.RUnlock()
if !exists {
return nil, fmt.Errorf("no private key for validator %s", encShare.ValidatorID)
}
// ML-KEM decapsulation
sharedSecret, err := privKey.Decapsulate(encShare.Ciphertext)
if err != nil {
return nil, fmt.Errorf("ML-KEM decapsulation failed: %w", err)
}
// Derive AES key
aesKey := sha256.Sum256(sharedSecret)
// AES-GCM decryption
block, err := aes.NewCipher(aesKey[:])
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return gcm.Open(nil, encShare.Nonce, encShare.EncryptedKey, nil)
}
// getValidatorPublicKey retrieves or generates ML-KEM public key for a validator.
func (b *KChainBackend) getValidatorPublicKey(_ context.Context, addr string) (*mlkem.PublicKey, error) {
// In production, this would fetch the validator's public key from the network.
// For now, generate deterministically for testing.
b.mu.Lock()
defer b.mu.Unlock()
if privKey, exists := b.mlkemKeys[addr]; exists {
return privKey.PublicKey(), nil
}
// Generate new ML-KEM key pair (ML-KEM-768 for 192-bit security)
pubKey, privKey, err := mlkem.GenerateKey(mlkem.MLKEM768)
if err != nil {
return nil, err
}
b.mlkemKeys[addr] = privKey
return pubKey, nil
}
// storeShareOnValidator sends an encrypted share to a validator.
func (b *KChainBackend) storeShareOnValidator(ctx context.Context, addr, keyName string, share *EncryptedShare) error {
if b.rpcClient == nil {
return ErrKChainUnavailable
}
// Encode share data
shareData, err := json.Marshal(share)
if err != nil {
return fmt.Errorf("failed to encode share: %w", err)
}
result, err := b.rpcClient.StoreShare(ctx, StoreShareParams{
KeyID: keyName,
ShareIndex: share.Index,
ShareData: string(shareData),
ValidatorID: addr,
})
if err != nil {
return fmt.Errorf("%w: %w", ErrShareStoreFailed, err)
}
if !result.Stored {
return ErrShareStoreFailed
}
return nil
}
// retrieveShareFromValidator retrieves an encrypted share from a validator.
func (b *KChainBackend) retrieveShareFromValidator(ctx context.Context, addr, keyName string) (*EncryptedShare, error) {
if b.rpcClient == nil {
return nil, ErrKChainUnavailable
}
result, err := b.rpcClient.RetrieveShare(ctx, RetrieveShareParams{
KeyID: keyName,
ValidatorID: addr,
})
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrShareRetrieveFailed, err)
}
var share EncryptedShare
if err := json.Unmarshal([]byte(result.ShareData), &share); err != nil {
return nil, fmt.Errorf("failed to decode share: %w", err)
}
return &share, nil
}
// deleteShareFromValidator requests share deletion from a validator.
func (b *KChainBackend) deleteShareFromValidator(ctx context.Context, addr, keyName string) error {
if b.rpcClient == nil {
return ErrKChainUnavailable
}
result, err := b.rpcClient.DeleteShare(ctx, DeleteShareParams{
KeyID: keyName,
ValidatorID: addr,
})
if err != nil {
return err
}
if !result.Deleted {
return fmt.Errorf("failed to delete share: %s", result.Message)
}
return nil
}
// requestSignatureShare requests a signature share from a validator.
func (b *KChainBackend) requestSignatureShare(ctx context.Context, addr, keyName string, message []byte) ([]byte, error) {
if b.rpcClient == nil {
return nil, ErrKChainUnavailable
}
// Get key info to determine algorithm
b.mu.RLock()
keyInfo, exists := b.distributedKeys[keyName]
b.mu.RUnlock()
algorithm := "bls-sig"
if exists && keyInfo.KeyType != "" {
algorithm = keyInfo.KeyType
}
result, err := b.rpcClient.RequestSignatureShare(ctx, RequestSignatureShareParams{
KeyID: keyName,
Message: string(message),
ValidatorID: addr,
Algorithm: algorithm,
})
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrValidatorUnreachable, err)
}
return []byte(result.ShareData), nil
}
// getDefaultValidators returns default validator addresses.
func (*KChainBackend) getDefaultValidators() []string {
return []string{
"validator-1.kchain.lux.network:9630",
"validator-2.kchain.lux.network:9631",
"validator-3.kchain.lux.network:9632",
"validator-4.kchain.lux.network:9633",
"validator-5.kchain.lux.network:9634",
}
}
func init() {
RegisterBackend(NewKChainBackend())
}