Files
chains/thresholdvm/client.go
T
zeekay f4c7055d45 chains: complete Ringtail → Corona purge across thresholdvm + quantumvm + dexvm
Continuation of dc31d5b (quantumvm consumer-side leak after consensus
repo migration) — the rename was incomplete; this pass closes it.

Identifier renames in chains/{quantumvm,thresholdvm,dexvm}:
  - NTTForwardRingtail / NTTInverseRingtail / BatchNTTForwardRingtail
        → NTTForward/Inverse/BatchNTTForwardCorona (Quasar GPU dispatch)
  - LaneRingtail (CertLane enum)
        → LaneCorona
  - ProtocolRingtail (Protocol enum)
        → ProtocolCorona (string value already "corona")
  - All Ringtail-prefixed local identifiers in quantumvm/{quasar,
    quasar_witness,block,vm,vm_test,signer,stamper,service}.go,
    thresholdvm/{client,protocols,rpc,types/{ceremony,share},
    runtime/m_chain_adapter,cert/lane_test,protocol_executor_test}.go,
    dexvm/factory.go

Directory + file rename:
  thresholdvm/protocol/ringtail_general/ringtail.go
        → thresholdvm/protocol/corona_general/corona.go
  package ringtail_general → package corona_general

evm/main.go: replace single `precompile/ringtail` blank-import with
the canonical LP-4200 unified PQCrypto block (mlkem 0x012201,
mldsa 0x012202, slhdsa 0x012203, pulsar 0x012204, p3q 0x012205,
corona 0x012206) + xwing.

External test that imports threshold's renamed wrapper:
  thresholdvm/protocol_executor_test.go now imports
  "github.com/luxfi/threshold/protocols/corona" (was protocols/ringtail).
  Resolves via the workspace `./threshold` entry pointing at the
  local renamed wrapper; downstream chains/go.mod still pins
  luxfi/threshold v1.5.5 and will tidy clean once threshold cuts the
  next tag.

Build: go build ./... PASS (only harmless duplicate-rpath warnings
from the cevm cgo link).
Tests:
  thresholdvm + subpackages PASS
  quantumvm PASS
2026-05-14 09:33:22 -07:00

555 lines
16 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package thresholdvm
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
)
// Client provides access to thresholdvm services. Per LP-134, this serves
// M-Chain (MPC mode) and F-Chain (FHE mode); legacy T-Chain MPC routes here.
type Client struct {
endpoint string
chainID string // Requesting chain's ID
httpClient *http.Client
}
// NewClient creates a new T-Chain client
func NewClient(endpoint, chainID string) *Client {
return &Client{
endpoint: endpoint,
chainID: chainID,
httpClient: &http.Client{
Timeout: 60 * time.Second,
},
}
}
// RPCClient wraps the underlying transport for JSON-RPC calls
type rpcRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params"`
}
type rpcResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result json.RawMessage `json:"result,omitempty"`
Error *rpcError `json:"error,omitempty"`
}
type rpcError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func (c *Client) call(ctx context.Context, method string, params interface{}, result interface{}) error {
reqBody := rpcRequest{
JSONRPC: "2.0",
ID: 1,
Method: method,
Params: params,
}
body, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", c.endpoint+"/rpc", bytes.NewReader(body))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
var rpcResp rpcResponse
if err := json.Unmarshal(respBody, &rpcResp); err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
if rpcResp.Error != nil {
return fmt.Errorf("RPC error %d: %s", rpcResp.Error.Code, rpcResp.Error.Message)
}
if result != nil && len(rpcResp.Result) > 0 {
if err := json.Unmarshal(rpcResp.Result, result); err != nil {
return fmt.Errorf("failed to unmarshal result: %w", err)
}
}
return nil
}
// =============================================================================
// Key Generation
// =============================================================================
// KeygenRequest contains parameters for key generation
type KeygenRequest struct {
KeyID string `json:"keyId"`
Protocol string `json:"protocol"` // lss, cggmp21, bls, corona
Threshold int `json:"threshold"` // Optional
TotalParties int `json:"totalParties"` // Optional
}
// KeygenResponse contains the keygen result
type KeygenResponse struct {
SessionID string `json:"sessionId"`
KeyID string `json:"keyId"`
Protocol string `json:"protocol"`
Status string `json:"status"`
Threshold int `json:"threshold"`
TotalParties int `json:"totalParties"`
StartedAt int64 `json:"startedAt"`
}
// Keygen initiates key generation on T-Chain
func (c *Client) Keygen(ctx context.Context, req KeygenRequest) (*KeygenResponse, error) {
params := map[string]interface{}{
"keyId": req.KeyID,
"protocol": req.Protocol,
"requestedBy": c.chainID,
}
if req.Threshold > 0 {
params["threshold"] = req.Threshold
}
if req.TotalParties > 0 {
params["totalParties"] = req.TotalParties
}
var result KeygenResponse
if err := c.call(ctx, "threshold_keygen", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetKeygenStatus retrieves the status of a keygen session
func (c *Client) GetKeygenStatus(ctx context.Context, sessionID string) (*KeygenResponse, error) {
params := map[string]string{
"sessionId": sessionID,
}
var result KeygenResponse
if err := c.call(ctx, "threshold_getKeygenStatus", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// WaitForKeygen waits for keygen to complete
func (c *Client) WaitForKeygen(ctx context.Context, sessionID string, timeout time.Duration) (*KeygenResponse, error) {
deadline := time.Now().Add(timeout)
pollInterval := 500 * time.Millisecond
for time.Now().Before(deadline) {
status, err := c.GetKeygenStatus(ctx, sessionID)
if err != nil {
return nil, err
}
switch status.Status {
case "completed":
return status, nil
case "failed":
return nil, fmt.Errorf("keygen failed: session %s", sessionID)
default:
// Still running, wait and retry
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(pollInterval):
}
}
}
return nil, errors.New("keygen timed out")
}
// =============================================================================
// Signing
// =============================================================================
// SignRequest contains parameters for signing
type SignRequest struct {
KeyID string `json:"keyId"`
MessageHash []byte `json:"messageHash"`
MessageType string `json:"messageType"` // raw, eth_sign, typed_data
}
// SignResponse contains the signing session info
type SignResponse struct {
SessionID string `json:"sessionId"`
KeyID string `json:"keyId"`
Status string `json:"status"`
CreatedAt int64 `json:"createdAt"`
ExpiresAt int64 `json:"expiresAt"`
}
// SignatureResponse contains a completed signature
type SignatureResponse struct {
SessionID string `json:"sessionId"`
Status string `json:"status"`
Signature string `json:"signature,omitempty"`
R string `json:"r,omitempty"`
S string `json:"s,omitempty"`
V int `json:"v,omitempty"`
SignerParties []string `json:"signerParties,omitempty"`
CompletedAt int64 `json:"completedAt,omitempty"`
Error string `json:"error,omitempty"`
}
// Sign requests a signature from T-Chain
func (c *Client) Sign(ctx context.Context, req SignRequest) (*SignResponse, error) {
params := map[string]interface{}{
"keyId": req.KeyID,
"messageHash": hex.EncodeToString(req.MessageHash),
"messageType": req.MessageType,
"requestingChain": c.chainID,
}
var result SignResponse
if err := c.call(ctx, "threshold_sign", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetSignature retrieves a signature from T-Chain
func (c *Client) GetSignature(ctx context.Context, sessionID string) (*SignatureResponse, error) {
params := map[string]string{
"sessionId": sessionID,
}
var result SignatureResponse
if err := c.call(ctx, "threshold_getSignature", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// WaitForSignature waits for signature to complete
func (c *Client) WaitForSignature(ctx context.Context, sessionID string, timeout time.Duration) (*SignatureResponse, error) {
deadline := time.Now().Add(timeout)
pollInterval := 100 * time.Millisecond
for time.Now().Before(deadline) {
sig, err := c.GetSignature(ctx, sessionID)
if err != nil {
return nil, err
}
switch sig.Status {
case "completed":
return sig, nil
case "failed":
return nil, fmt.Errorf("signing failed: %s", sig.Error)
default:
// Still signing, wait and retry
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(pollInterval):
}
}
}
return nil, errors.New("signing timed out")
}
// SignAndWait signs a message and waits for completion
func (c *Client) SignAndWait(ctx context.Context, req SignRequest, timeout time.Duration) (*SignatureResponse, error) {
// Start signing
resp, err := c.Sign(ctx, req)
if err != nil {
return nil, err
}
// Wait for completion
return c.WaitForSignature(ctx, resp.SessionID, timeout)
}
// BatchSign requests multiple signatures
func (c *Client) BatchSign(ctx context.Context, keyID string, messageHashes [][]byte) ([]string, error) {
hashes := make([]string, len(messageHashes))
for i, h := range messageHashes {
hashes[i] = hex.EncodeToString(h)
}
params := map[string]interface{}{
"keyId": keyID,
"messageHashes": hashes,
"requestingChain": c.chainID,
}
var result struct {
SessionIDs []string `json:"sessionIds"`
}
if err := c.call(ctx, "threshold_batchSign", params, &result); err != nil {
return nil, err
}
return result.SessionIDs, nil
}
// =============================================================================
// Key Management
// =============================================================================
// KeyInfo contains key information
type KeyInfo struct {
KeyID string `json:"keyId"`
Protocol string `json:"protocol"`
PublicKey string `json:"publicKey"`
Address string `json:"address,omitempty"`
Threshold int `json:"threshold"`
TotalParties int `json:"totalParties"`
Generation uint64 `json:"generation"`
Status string `json:"status"`
SignCount uint64 `json:"signCount"`
CreatedAt int64 `json:"createdAt"`
LastUsedAt int64 `json:"lastUsedAt,omitempty"`
PartyIDs []string `json:"partyIds"`
}
// ListKeys lists all keys on T-Chain
func (c *Client) ListKeys(ctx context.Context) ([]KeyInfo, error) {
var result []KeyInfo
if err := c.call(ctx, "threshold_listKeys", nil, &result); err != nil {
return nil, err
}
return result, nil
}
// GetKey retrieves key information
func (c *Client) GetKey(ctx context.Context, keyID string) (*KeyInfo, error) {
params := map[string]string{
"keyId": keyID,
}
var result KeyInfo
if err := c.call(ctx, "threshold_getKey", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetPublicKey retrieves the public key for a key ID
func (c *Client) GetPublicKey(ctx context.Context, keyID string) ([]byte, error) {
params := map[string]string{
"keyId": keyID,
}
var result map[string]string
if err := c.call(ctx, "threshold_getPublicKey", params, &result); err != nil {
return nil, err
}
pubKeyHex := result["publicKey"]
if len(pubKeyHex) >= 2 && pubKeyHex[:2] == "0x" {
pubKeyHex = pubKeyHex[2:]
}
return hex.DecodeString(pubKeyHex)
}
// GetAddress retrieves the address for a key ID
func (c *Client) GetAddress(ctx context.Context, keyID string) ([]byte, error) {
params := map[string]string{
"keyId": keyID,
}
var result map[string]string
if err := c.call(ctx, "threshold_getAddress", params, &result); err != nil {
return nil, err
}
addrHex := result["address"]
if len(addrHex) >= 2 && addrHex[:2] == "0x" {
addrHex = addrHex[2:]
}
return hex.DecodeString(addrHex)
}
// Reshare triggers key resharing
func (c *Client) Reshare(ctx context.Context, keyID string, newPartyIDs []string, newThreshold int) (*KeygenResponse, error) {
params := map[string]interface{}{
"keyId": keyID,
"newPartyIds": newPartyIDs,
"newThreshold": newThreshold,
"requestedBy": c.chainID,
}
var result KeygenResponse
if err := c.call(ctx, "threshold_reshare", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// Refresh triggers key refresh
func (c *Client) Refresh(ctx context.Context, keyID string) (*KeygenResponse, error) {
params := map[string]interface{}{
"keyId": keyID,
"requestedBy": c.chainID,
}
var result KeygenResponse
if err := c.call(ctx, "threshold_refresh", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// =============================================================================
// Protocol Information
// =============================================================================
// ProtocolInfo contains protocol information
type ProtocolInfo struct {
Name string `json:"name"`
Description string `json:"description"`
SupportedCurves []string `json:"supportedCurves"`
KeySize int `json:"keySize"`
SignatureSize int `json:"signatureSize"`
IsPostQuantum bool `json:"isPostQuantum"`
SupportsReshare bool `json:"supportsReshare"`
SupportsRefresh bool `json:"supportsRefresh"`
}
// GetProtocols retrieves all supported protocols
func (c *Client) GetProtocols(ctx context.Context) ([]ProtocolInfo, error) {
var result []ProtocolInfo
if err := c.call(ctx, "threshold_getProtocols", nil, &result); err != nil {
return nil, err
}
return result, nil
}
// GetProtocolInfo retrieves info for a specific protocol
func (c *Client) GetProtocolInfo(ctx context.Context, protocol string) (*ProtocolInfo, error) {
params := map[string]string{
"protocol": protocol,
}
var result ProtocolInfo
if err := c.call(ctx, "threshold_getProtocolInfo", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// =============================================================================
// Network Information
// =============================================================================
// ThresholdInfo contains T-Chain information
type ThresholdInfo struct {
Version string `json:"version"`
NodeID string `json:"nodeId"`
ChainID string `json:"chainId"`
MPCReady bool `json:"mpcReady"`
ActiveKeyID string `json:"activeKeyId,omitempty"`
Threshold int `json:"threshold"`
TotalParties int `json:"totalParties"`
SupportedProtocols []string `json:"supportedProtocols"`
AuthorizedChains []string `json:"authorizedChains"`
TotalKeys int `json:"totalKeys"`
ActiveSessions int `json:"activeSessions"`
}
// GetInfo retrieves T-Chain information
func (c *Client) GetInfo(ctx context.Context) (*ThresholdInfo, error) {
var result ThresholdInfo
if err := c.call(ctx, "threshold_getInfo", nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// NetworkStats contains network statistics
type NetworkStats struct {
TotalSignatures uint64 `json:"totalSignatures"`
TotalKeygens uint64 `json:"totalKeygens"`
ActiveSessions int `json:"activeSessions"`
SignaturesByChain map[string]uint64 `json:"signaturesByChain"`
AverageSigningTime int64 `json:"averageSigningTime"` // nanoseconds
SuccessRate float64 `json:"successRate"`
}
// GetStats retrieves T-Chain statistics
func (c *Client) GetStats(ctx context.Context) (*NetworkStats, error) {
var result NetworkStats
if err := c.call(ctx, "threshold_getStats", nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// QuotaInfo contains quota information
type QuotaInfo struct {
ChainID string `json:"chainId"`
DailyLimit uint64 `json:"dailyLimit"`
UsedToday uint64 `json:"usedToday"`
Remaining uint64 `json:"remaining"`
ResetTime int64 `json:"resetTime"`
}
// GetQuota retrieves quota information for this chain
func (c *Client) GetQuota(ctx context.Context) (*QuotaInfo, error) {
params := map[string]string{
"chainId": c.chainID,
}
var result QuotaInfo
if err := c.call(ctx, "threshold_getQuota", params, &result); err != nil {
return nil, err
}
return &result, nil
}
// =============================================================================
// Health
// =============================================================================
// Health retrieves T-Chain health status
func (c *Client) Health(ctx context.Context) (map[string]interface{}, error) {
var result map[string]interface{}
if err := c.call(ctx, "threshold_health", nil, &result); err != nil {
return nil, err
}
return result, nil
}
// IsReady checks if thresholdvm (M-Chain MPC mode per LP-134) is ready
func (c *Client) IsReady(ctx context.Context) (bool, error) {
info, err := c.GetInfo(ctx)
if err != nil {
return false, err
}
return info.MPCReady, nil
}