mirror of
https://github.com/luxfi/threshold.git
synced 2026-10-30 15:15:53 +00:00
New curve types: - Ed25519 (pkg/math/curve/ed25519.go) — 17 tests - Ristretto255 (pkg/math/curve/ristretto255.go) — 18 tests Includes FromUniformBytes for Merlin challenge scalar reduction FROST sr25519 (Schnorrkel) threshold signing: - Merlin transcript challenge path in sign/round2.go - KeygenSR25519(), SignSR25519() entry points - SR25519Signature type with Verify() - Cross-verified against ChainSafe/go-schnorrkel — 6 tests - First-of-kind threshold sr25519 implementation Chain adapter fixes: - Solana, TON, Sui, Cardano, NEAR, XRPL adapters now use real Ed25519 curve instead of secp256k1 placeholder - CLI: ed25519 curve selection now works (was returning error)
844 lines
24 KiB
Go
844 lines
24 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/luxfi/threshold/internal/test"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/party"
|
|
"github.com/luxfi/threshold/pkg/pool"
|
|
"github.com/luxfi/threshold/protocols/cmp"
|
|
"github.com/luxfi/threshold/protocols/frost"
|
|
"github.com/luxfi/threshold/protocols/lss"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
protocolLSS = "lss"
|
|
protocolCMP = "cmp"
|
|
protocolFROST = "frost"
|
|
operationAll = "all"
|
|
suiteFunctional = "functional"
|
|
suiteSecurity = "security"
|
|
suiteProperty = "property"
|
|
suiteFuzz = "fuzz"
|
|
)
|
|
|
|
var (
|
|
// Global flags
|
|
configDir string
|
|
protocolName string
|
|
curveType string
|
|
networkAddr string
|
|
verbose bool
|
|
|
|
// Protocol options
|
|
threshold int
|
|
parties int
|
|
partyID string
|
|
outputFile string
|
|
inputFile string
|
|
|
|
// Root command
|
|
rootCmd = &cobra.Command{
|
|
Use: "threshold-cli",
|
|
Short: "CLI tool for threshold signature protocols",
|
|
Long: `A comprehensive CLI tool for testing and using threshold signature protocols
|
|
including LSS-MPC, CGG21 (CMP), and FROST protocols.`,
|
|
}
|
|
|
|
// Subcommands
|
|
keygenCmd = &cobra.Command{
|
|
Use: "keygen",
|
|
Short: "Generate threshold keys",
|
|
Long: `Generate threshold keys for the specified protocol and parameters`,
|
|
RunE: runKeygen,
|
|
}
|
|
|
|
signCmd = &cobra.Command{
|
|
Use: "sign",
|
|
Short: "Create threshold signature",
|
|
Long: `Create a threshold signature using the specified protocol`,
|
|
RunE: runSign,
|
|
}
|
|
|
|
reshareCmd = &cobra.Command{
|
|
Use: "reshare",
|
|
Short: "Reshare keys with new parties",
|
|
Long: `Dynamically reshare keys to add/remove parties or change threshold`,
|
|
RunE: runReshare,
|
|
}
|
|
|
|
verifyCmd = &cobra.Command{
|
|
Use: "verify",
|
|
Short: "Verify a signature",
|
|
Long: `Verify a threshold signature against a public key and message`,
|
|
RunE: runVerify,
|
|
}
|
|
|
|
benchCmd = &cobra.Command{
|
|
Use: "bench",
|
|
Short: "Run performance benchmarks",
|
|
Long: `Run performance benchmarks for the specified protocol`,
|
|
RunE: runBenchmark,
|
|
}
|
|
|
|
testCmd = &cobra.Command{
|
|
Use: "test",
|
|
Short: "Run protocol tests",
|
|
Long: `Run comprehensive tests for the specified protocol`,
|
|
RunE: runTests,
|
|
}
|
|
|
|
simulateCmd = &cobra.Command{
|
|
Use: "simulate",
|
|
Short: "Simulate protocol execution",
|
|
Long: `Simulate protocol execution with various scenarios`,
|
|
RunE: runSimulation,
|
|
}
|
|
|
|
exportCmd = &cobra.Command{
|
|
Use: "export",
|
|
Short: "Export keys in various formats",
|
|
Long: `Export threshold keys in different formats (PEM, JWK, etc.)`,
|
|
RunE: runExport,
|
|
}
|
|
|
|
importCmd = &cobra.Command{
|
|
Use: "import",
|
|
Short: "Import keys from various formats",
|
|
Long: `Import threshold keys from different formats`,
|
|
RunE: runImport,
|
|
}
|
|
|
|
infoCmd = &cobra.Command{
|
|
Use: "info",
|
|
Short: "Display protocol information",
|
|
Long: `Display detailed information about protocols and configurations`,
|
|
RunE: runInfo,
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
// Global flags
|
|
rootCmd.PersistentFlags().StringVarP(&configDir, "config-dir", "d", "./threshold-data", "Configuration directory")
|
|
rootCmd.PersistentFlags().StringVarP(&protocolName, "protocol", "p", protocolLSS, "Protocol to use: lss, cmp, frost")
|
|
rootCmd.PersistentFlags().StringVarP(&curveType, "curve", "c", "secp256k1", "Elliptic curve: secp256k1, p256, ed25519")
|
|
rootCmd.PersistentFlags().StringVarP(&networkAddr, "network", "n", "", "Network address for distributed mode")
|
|
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output")
|
|
|
|
// Keygen flags
|
|
keygenCmd.Flags().IntVarP(&threshold, "threshold", "t", 0, "Threshold value (required)")
|
|
keygenCmd.Flags().IntVarP(&parties, "parties", "N", 0, "Total number of parties (required)")
|
|
keygenCmd.Flags().StringVarP(&partyID, "id", "i", "", "Party ID (required)")
|
|
keygenCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file for config")
|
|
_ = keygenCmd.MarkFlagRequired("threshold") //nolint:errcheck
|
|
_ = keygenCmd.MarkFlagRequired("parties") //nolint:errcheck
|
|
_ = keygenCmd.MarkFlagRequired("id") //nolint:errcheck
|
|
|
|
// Sign flags
|
|
signCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input config file (required)")
|
|
signCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output signature file")
|
|
signCmd.Flags().StringSliceP("signers", "s", nil, "List of signer IDs")
|
|
signCmd.Flags().String("message", "", "Message to sign (hex encoded)")
|
|
signCmd.Flags().String("message-file", "", "File containing message to sign")
|
|
_ = signCmd.MarkFlagRequired("input") //nolint:errcheck
|
|
|
|
// Reshare flags
|
|
reshareCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input config file (required)")
|
|
reshareCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output config file")
|
|
reshareCmd.Flags().IntVar(&threshold, "new-threshold", 0, "New threshold value")
|
|
reshareCmd.Flags().StringSlice("add-parties", nil, "Parties to add")
|
|
reshareCmd.Flags().StringSlice("remove-parties", nil, "Parties to remove")
|
|
_ = reshareCmd.MarkFlagRequired("input") //nolint:errcheck
|
|
|
|
// Verify flags
|
|
verifyCmd.Flags().String("signature", "", "Signature file (required)")
|
|
verifyCmd.Flags().String("public-key", "", "Public key file (required)")
|
|
verifyCmd.Flags().String("message", "", "Message (hex encoded)")
|
|
verifyCmd.Flags().String("message-file", "", "File containing message")
|
|
_ = verifyCmd.MarkFlagRequired("signature") //nolint:errcheck
|
|
_ = verifyCmd.MarkFlagRequired("public-key") //nolint:errcheck
|
|
|
|
// Benchmark flags
|
|
benchCmd.Flags().Int("iterations", 10, "Number of benchmark iterations")
|
|
benchCmd.Flags().String("operation", operationAll, "Operation to benchmark: keygen, sign, reshare, all")
|
|
benchCmd.Flags().Bool("profile", false, "Enable CPU profiling")
|
|
|
|
// Test flags
|
|
testCmd.Flags().String("suite", operationAll, "Test suite to run: functional, security, property, fuzz, all")
|
|
testCmd.Flags().Bool("ginkgo", false, "Run Ginkgo tests")
|
|
testCmd.Flags().Duration("timeout", 0, "Test timeout (0 = no timeout)")
|
|
|
|
// Simulate flags
|
|
simulateCmd.Flags().String("scenario", "", "Scenario to simulate: byzantine, network-failure, etc.")
|
|
simulateCmd.Flags().Int("rounds", 100, "Number of simulation rounds")
|
|
simulateCmd.Flags().Float64("failure-rate", 0.1, "Failure rate for fault simulation")
|
|
|
|
// Export/Import flags
|
|
exportCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input config file (required)")
|
|
exportCmd.Flags().String("format", "pem", "Export format: pem, jwk, der")
|
|
exportCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file")
|
|
_ = exportCmd.MarkFlagRequired("input") //nolint:errcheck
|
|
|
|
importCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input file (required)")
|
|
importCmd.Flags().String("format", "pem", "Import format: pem, jwk, der")
|
|
importCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output config file")
|
|
_ = importCmd.MarkFlagRequired("input") //nolint:errcheck
|
|
|
|
// Add subcommands
|
|
rootCmd.AddCommand(keygenCmd, signCmd, reshareCmd, verifyCmd, benchCmd,
|
|
testCmd, simulateCmd, exportCmd, importCmd, infoCmd)
|
|
}
|
|
|
|
func main() {
|
|
if err := rootCmd.Execute(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runKeygen(cmd *cobra.Command, args []string) error {
|
|
// Create config directory
|
|
if err := os.MkdirAll(configDir, 0750); err != nil {
|
|
return fmt.Errorf("failed to create config directory: %w", err)
|
|
}
|
|
|
|
// Get curve
|
|
group, err := getCurve(curveType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create party IDs
|
|
partyIDs := make([]party.ID, parties)
|
|
for i := 0; i < parties; i++ {
|
|
partyIDs[i] = party.ID(fmt.Sprintf("party-%d", i+1))
|
|
}
|
|
|
|
// Find our index
|
|
var ourIndex int
|
|
found := false
|
|
for i, id := range partyIDs {
|
|
if string(id) == partyID {
|
|
ourIndex = i
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("party ID %s not in party list", partyID)
|
|
}
|
|
|
|
// Setup network
|
|
var network *test.Network
|
|
if networkAddr == "" {
|
|
// Local simulation mode
|
|
network = test.NewNetwork(partyIDs)
|
|
fmt.Println("Running in local simulation mode...")
|
|
} else {
|
|
// Distributed mode
|
|
return fmt.Errorf("distributed mode not yet implemented")
|
|
}
|
|
|
|
// Run protocol
|
|
pl := pool.NewPool(0)
|
|
defer pl.TearDown()
|
|
|
|
var config interface{}
|
|
|
|
switch protocolName {
|
|
case protocolLSS:
|
|
config, err = runLSSKeygen(group, partyIDs[ourIndex], partyIDs, threshold, pl, network)
|
|
case protocolCMP:
|
|
config, err = runCMPKeygen(group, partyIDs[ourIndex], partyIDs, threshold, pl, network)
|
|
case protocolFROST:
|
|
config, err = runFROSTKeygen(group, partyIDs[ourIndex], partyIDs, threshold, pl, network)
|
|
default:
|
|
return fmt.Errorf("unknown protocol: %s", protocolName)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("keygen failed: %w", err)
|
|
}
|
|
|
|
// Save config
|
|
if outputFile == "" {
|
|
outputFile = filepath.Join(configDir, fmt.Sprintf("%s-%s.json", protocolName, partyID))
|
|
}
|
|
|
|
data, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(outputFile, data, 0600); err != nil {
|
|
return fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Key generation complete. Config saved to: %s\n", outputFile)
|
|
|
|
// Display public key
|
|
switch c := config.(type) {
|
|
case *lss.Config:
|
|
pubKey, err := c.PublicKey()
|
|
if err == nil {
|
|
pkBytes, err := pubKey.MarshalBinary()
|
|
if err == nil {
|
|
fmt.Printf("Public key: %s\n", hex.EncodeToString(pkBytes))
|
|
}
|
|
}
|
|
case *cmp.Config:
|
|
if pk := c.PublicPoint(); pk != nil {
|
|
pkBytes, err := pk.MarshalBinary()
|
|
if err == nil {
|
|
fmt.Printf("Public key: %s\n", hex.EncodeToString(pkBytes))
|
|
}
|
|
}
|
|
case *frost.Config:
|
|
if c.PublicKey != nil {
|
|
pkBytes, err := c.PublicKey.MarshalBinary()
|
|
if err == nil {
|
|
fmt.Printf("Public key: %s\n", hex.EncodeToString(pkBytes))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runSign(cmd *cobra.Command, args []string) error {
|
|
// Load config
|
|
configData, err := os.ReadFile(inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
|
|
// Get message
|
|
var message []byte
|
|
msgFile, msgFileErr := cmd.Flags().GetString("message-file")
|
|
if msgFileErr != nil {
|
|
return fmt.Errorf("failed to get message-file flag: %w", msgFileErr)
|
|
}
|
|
if msgFile != "" {
|
|
message, err = os.ReadFile(msgFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read message file: %w", err)
|
|
}
|
|
} else {
|
|
msgHex, msgHexErr := cmd.Flags().GetString("message")
|
|
if msgHexErr != nil {
|
|
return fmt.Errorf("failed to get message flag: %w", msgHexErr)
|
|
}
|
|
if msgHex != "" {
|
|
message, err = hex.DecodeString(msgHex)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode message: %w", err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("either --message or --message-file must be specified")
|
|
}
|
|
}
|
|
|
|
// Get signers
|
|
signerStrs, signerErr := cmd.Flags().GetStringSlice("signers")
|
|
if signerErr != nil {
|
|
return fmt.Errorf("failed to get signers flag: %w", signerErr)
|
|
}
|
|
signers := make([]party.ID, len(signerStrs))
|
|
for i, s := range signerStrs {
|
|
signers[i] = party.ID(s)
|
|
}
|
|
|
|
// Setup network
|
|
pl := pool.NewPool(0)
|
|
defer pl.TearDown()
|
|
|
|
var signature interface{}
|
|
|
|
switch protocolName {
|
|
case protocolLSS:
|
|
var config lss.Config
|
|
if err = json.Unmarshal(configData, &config); err != nil {
|
|
return fmt.Errorf("failed to unmarshal LSS config: %w", err)
|
|
}
|
|
|
|
network := test.NewNetwork(signers)
|
|
signature, err = runLSSSign(&config, signers, message, pl, network)
|
|
|
|
case protocolCMP:
|
|
var config cmp.Config
|
|
if err = json.Unmarshal(configData, &config); err != nil {
|
|
return fmt.Errorf("failed to unmarshal CMP config: %w", err)
|
|
}
|
|
|
|
network := test.NewNetwork(signers)
|
|
signature, err = runCMPSign(&config, signers, message, pl, network)
|
|
|
|
case protocolFROST:
|
|
var config frost.Config
|
|
if err = json.Unmarshal(configData, &config); err != nil {
|
|
return fmt.Errorf("failed to unmarshal FROST config: %w", err)
|
|
}
|
|
|
|
network := test.NewNetwork(signers)
|
|
signature, err = runFROSTSign(&config, signers, message, pl, network)
|
|
|
|
default:
|
|
return fmt.Errorf("unknown protocol: %s", protocolName)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("signing failed: %w", err)
|
|
}
|
|
|
|
// Save signature
|
|
if outputFile == "" {
|
|
outputFile = "signature.json"
|
|
}
|
|
|
|
sigData, err := json.MarshalIndent(signature, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal signature: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(outputFile, sigData, 0600); err != nil {
|
|
return fmt.Errorf("failed to write signature: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Signature created and saved to: %s\n", outputFile)
|
|
return nil
|
|
}
|
|
|
|
func runReshare(cmd *cobra.Command, args []string) error {
|
|
// Load current config
|
|
configData, err := os.ReadFile(inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
|
|
// Get parameters
|
|
addParties, addErr := cmd.Flags().GetStringSlice("add-parties")
|
|
if addErr != nil {
|
|
return fmt.Errorf("failed to get add-parties flag: %w", addErr)
|
|
}
|
|
removeParties, removeErr := cmd.Flags().GetStringSlice("remove-parties")
|
|
if removeErr != nil {
|
|
return fmt.Errorf("failed to get remove-parties flag: %w", removeErr)
|
|
}
|
|
|
|
if threshold == 0 && len(addParties) == 0 && len(removeParties) == 0 {
|
|
return fmt.Errorf("must specify new threshold, parties to add, or parties to remove")
|
|
}
|
|
|
|
// Currently only LSS supports resharing
|
|
if protocolName != protocolLSS {
|
|
return fmt.Errorf("resharing is currently only supported for LSS protocol")
|
|
}
|
|
|
|
var config lss.Config
|
|
if err = json.Unmarshal(configData, &config); err != nil {
|
|
return fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
|
|
// Setup network
|
|
pl := pool.NewPool(0)
|
|
defer pl.TearDown()
|
|
|
|
// Convert string IDs to party.ID
|
|
newPartyIDs := make([]party.ID, len(addParties))
|
|
for i, p := range addParties {
|
|
newPartyIDs[i] = party.ID(p)
|
|
}
|
|
|
|
// Create network with all parties
|
|
allParties := append(config.PartyIDs(), newPartyIDs...)
|
|
network := test.NewNetwork(allParties)
|
|
|
|
// Run resharing
|
|
newConfig, err := runLSSReshare(&config, threshold, newPartyIDs, pl, network)
|
|
if err != nil {
|
|
return fmt.Errorf("resharing failed: %w", err)
|
|
}
|
|
|
|
// Save new config
|
|
if outputFile == "" {
|
|
outputFile = filepath.Join(configDir, fmt.Sprintf("%s-%s-reshared.json", protocolName, config.ID))
|
|
}
|
|
|
|
data, err := json.MarshalIndent(newConfig, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(outputFile, data, 0600); err != nil {
|
|
return fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Resharing complete. New config saved to: %s\n", outputFile)
|
|
fmt.Printf("New threshold: %d, Total parties: %d\n", newConfig.Threshold, len(newConfig.PartyIDs()))
|
|
|
|
return nil
|
|
}
|
|
|
|
func runVerify(cmd *cobra.Command, args []string) error {
|
|
// Load signature
|
|
sigFile, sigErr := cmd.Flags().GetString("signature")
|
|
if sigErr != nil {
|
|
return fmt.Errorf("failed to get signature flag: %w", sigErr)
|
|
}
|
|
sigData, err := os.ReadFile(sigFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read signature: %w", err)
|
|
}
|
|
|
|
// Load public key
|
|
pkFile, pkErr := cmd.Flags().GetString("public-key")
|
|
if pkErr != nil {
|
|
return fmt.Errorf("failed to get public-key flag: %w", pkErr)
|
|
}
|
|
pkData, err := os.ReadFile(pkFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read public key: %w", err)
|
|
}
|
|
|
|
// Get message
|
|
var message []byte
|
|
msgFile, msgFileErr := cmd.Flags().GetString("message-file")
|
|
if msgFileErr != nil {
|
|
return fmt.Errorf("failed to get message-file flag: %w", msgFileErr)
|
|
}
|
|
if msgFile != "" {
|
|
message, err = os.ReadFile(msgFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read message file: %w", err)
|
|
}
|
|
} else {
|
|
msgHex, msgHexErr := cmd.Flags().GetString("message")
|
|
if msgHexErr != nil {
|
|
return fmt.Errorf("failed to get message flag: %w", msgHexErr)
|
|
}
|
|
if msgHex != "" {
|
|
message, err = hex.DecodeString(msgHex)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode message: %w", err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("either --message or --message-file must be specified")
|
|
}
|
|
}
|
|
|
|
// Verify based on protocol
|
|
valid := false
|
|
switch protocolName {
|
|
case protocolLSS, protocolCMP:
|
|
// ECDSA verification
|
|
valid, err = verifyECDSA(sigData, pkData, message)
|
|
case protocolFROST:
|
|
// Schnorr verification
|
|
valid, err = verifySchnorr(sigData, pkData, message)
|
|
default:
|
|
return fmt.Errorf("unknown protocol: %s", protocolName)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("verification failed: %w", err)
|
|
}
|
|
|
|
if valid {
|
|
fmt.Println("✓ Signature is VALID")
|
|
return nil
|
|
} else {
|
|
fmt.Println("✗ Signature is INVALID")
|
|
return fmt.Errorf("invalid signature")
|
|
}
|
|
}
|
|
|
|
func runBenchmark(cmd *cobra.Command, args []string) error {
|
|
iterations, iterErr := cmd.Flags().GetInt("iterations")
|
|
if iterErr != nil {
|
|
return fmt.Errorf("failed to get iterations flag: %w", iterErr)
|
|
}
|
|
operation, opErr := cmd.Flags().GetString("operation")
|
|
if opErr != nil {
|
|
return fmt.Errorf("failed to get operation flag: %w", opErr)
|
|
}
|
|
enableProfile, profileErr := cmd.Flags().GetBool("profile")
|
|
if profileErr != nil {
|
|
return fmt.Errorf("failed to get profile flag: %w", profileErr)
|
|
}
|
|
|
|
fmt.Printf("Running %s benchmarks for %s protocol...\n", operation, protocolName)
|
|
fmt.Printf("Iterations: %d\n", iterations)
|
|
|
|
if enableProfile {
|
|
// Setup CPU profiling
|
|
fmt.Println("CPU profiling enabled")
|
|
}
|
|
|
|
// Run benchmarks based on operation
|
|
switch operation {
|
|
case "keygen":
|
|
return benchmarkKeygen(protocolName, iterations)
|
|
case "sign":
|
|
return benchmarkSign(protocolName, iterations)
|
|
case "reshare":
|
|
if protocolName != protocolLSS {
|
|
return fmt.Errorf("reshare benchmark only available for LSS protocol")
|
|
}
|
|
return benchmarkReshare(iterations)
|
|
case operationAll:
|
|
if err := benchmarkKeygen(protocolName, iterations); err != nil {
|
|
return err
|
|
}
|
|
if err := benchmarkSign(protocolName, iterations); err != nil {
|
|
return err
|
|
}
|
|
if protocolName == protocolLSS {
|
|
if err := benchmarkReshare(iterations); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown operation: %s", operation)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runTests(cmd *cobra.Command, args []string) error {
|
|
suite, suiteErr := cmd.Flags().GetString("suite")
|
|
if suiteErr != nil {
|
|
return fmt.Errorf("failed to get suite flag: %w", suiteErr)
|
|
}
|
|
useGinkgo, ginkgoErr := cmd.Flags().GetBool("ginkgo")
|
|
if ginkgoErr != nil {
|
|
return fmt.Errorf("failed to get ginkgo flag: %w", ginkgoErr)
|
|
}
|
|
timeout, timeoutErr := cmd.Flags().GetDuration("timeout")
|
|
if timeoutErr != nil {
|
|
return fmt.Errorf("failed to get timeout flag: %w", timeoutErr)
|
|
}
|
|
|
|
fmt.Printf("Running %s test suite for %s protocol...\n", suite, protocolName)
|
|
|
|
if useGinkgo {
|
|
fmt.Println("Using Ginkgo test runner")
|
|
// Run Ginkgo tests
|
|
return runGinkgoTests(protocolName, suite, timeout)
|
|
}
|
|
|
|
// Run standard Go tests
|
|
switch suite {
|
|
case suiteFunctional:
|
|
return runFunctionalTests(protocolName)
|
|
case suiteSecurity:
|
|
return runSecurityTests(protocolName)
|
|
case suiteProperty:
|
|
return runPropertyTests(protocolName)
|
|
case suiteFuzz:
|
|
return runFuzzTests(protocolName)
|
|
case operationAll:
|
|
if err := runFunctionalTests(protocolName); err != nil {
|
|
return err
|
|
}
|
|
if err := runSecurityTests(protocolName); err != nil {
|
|
return err
|
|
}
|
|
if err := runPropertyTests(protocolName); err != nil {
|
|
return err
|
|
}
|
|
if err := runFuzzTests(protocolName); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown test suite: %s", suite)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runSimulation(cmd *cobra.Command, args []string) error {
|
|
scenario, scenarioErr := cmd.Flags().GetString("scenario")
|
|
if scenarioErr != nil {
|
|
return fmt.Errorf("failed to get scenario flag: %w", scenarioErr)
|
|
}
|
|
rounds, roundsErr := cmd.Flags().GetInt("rounds")
|
|
if roundsErr != nil {
|
|
return fmt.Errorf("failed to get rounds flag: %w", roundsErr)
|
|
}
|
|
failureRate, rateErr := cmd.Flags().GetFloat64("failure-rate")
|
|
if rateErr != nil {
|
|
return fmt.Errorf("failed to get failure-rate flag: %w", rateErr)
|
|
}
|
|
|
|
fmt.Printf("Running %s simulation for %s protocol...\n", scenario, protocolName)
|
|
fmt.Printf("Rounds: %d, Failure rate: %.2f%%\n", rounds, failureRate*100)
|
|
|
|
switch scenario {
|
|
case "byzantine":
|
|
return simulateByzantine(protocolName, rounds, failureRate)
|
|
case "network-failure":
|
|
return simulateNetworkFailure(protocolName, rounds, failureRate)
|
|
case "concurrent-signing":
|
|
return simulateConcurrentSigning(protocolName, rounds)
|
|
case "large-scale":
|
|
return simulateLargeScale(protocolName, rounds)
|
|
default:
|
|
return fmt.Errorf("unknown scenario: %s", scenario)
|
|
}
|
|
}
|
|
|
|
func runExport(cmd *cobra.Command, args []string) error {
|
|
format, formatErr := cmd.Flags().GetString("format")
|
|
if formatErr != nil {
|
|
return fmt.Errorf("failed to get format flag: %w", formatErr)
|
|
}
|
|
|
|
// Load config
|
|
configData, err := os.ReadFile(inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
|
|
var exported []byte
|
|
|
|
switch protocolName {
|
|
case protocolLSS:
|
|
var config lss.Config
|
|
if err = json.Unmarshal(configData, &config); err != nil {
|
|
return fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
exported, err = exportLSSConfig(&config, format)
|
|
case protocolCMP:
|
|
var config cmp.Config
|
|
if err = json.Unmarshal(configData, &config); err != nil {
|
|
return fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
exported, err = exportCMPConfig(&config, format)
|
|
case protocolFROST:
|
|
var config frost.Config
|
|
if unmarshalErr := json.Unmarshal(configData, &config); unmarshalErr != nil {
|
|
return fmt.Errorf("failed to unmarshal config: %w", unmarshalErr)
|
|
}
|
|
exported, err = exportFROSTConfig(&config, format)
|
|
default:
|
|
return fmt.Errorf("unknown protocol: %s", protocolName)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("export failed: %w", err)
|
|
}
|
|
|
|
// Save exported data
|
|
if outputFile == "" {
|
|
outputFile = fmt.Sprintf("exported.%s", format)
|
|
}
|
|
|
|
if err := os.WriteFile(outputFile, exported, 0600); err != nil {
|
|
return fmt.Errorf("failed to write exported data: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Config exported to: %s\n", outputFile)
|
|
return nil
|
|
}
|
|
|
|
func runImport(cmd *cobra.Command, args []string) error {
|
|
format, formatErr := cmd.Flags().GetString("format")
|
|
if formatErr != nil {
|
|
return fmt.Errorf("failed to get format flag: %w", formatErr)
|
|
}
|
|
|
|
// Read input file
|
|
data, err := os.ReadFile(inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read input file: %w", err)
|
|
}
|
|
|
|
var config interface{}
|
|
|
|
switch protocolName {
|
|
case protocolLSS:
|
|
config, err = importLSSConfig(data, format)
|
|
case protocolCMP:
|
|
config, err = importCMPConfig(data, format)
|
|
case protocolFROST:
|
|
config, err = importFROSTConfig(data, format)
|
|
default:
|
|
return fmt.Errorf("unknown protocol: %s", protocolName)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("import failed: %w", err)
|
|
}
|
|
|
|
// Save config
|
|
if outputFile == "" {
|
|
outputFile = fmt.Sprintf("%s-imported.json", protocolName)
|
|
}
|
|
|
|
configData, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(outputFile, configData, 0600); err != nil {
|
|
return fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Config imported and saved to: %s\n", outputFile)
|
|
return nil
|
|
}
|
|
|
|
func runInfo(cmd *cobra.Command, args []string) error {
|
|
fmt.Printf("Threshold Signature CLI v1.0.0\n\n")
|
|
|
|
fmt.Printf("Supported Protocols:\n")
|
|
fmt.Printf(" - LSS (Lindell-Shamir-Shmalo): Dynamic resharing, ECDSA signatures\n")
|
|
fmt.Printf(" - CMP (CGG21): State-of-the-art ECDSA threshold signatures\n")
|
|
fmt.Printf(" - FROST: Flexible Round-Optimized Schnorr Threshold signatures\n\n")
|
|
|
|
fmt.Printf("Supported Curves:\n")
|
|
fmt.Printf(" - secp256k1: Bitcoin/Ethereum compatible\n")
|
|
fmt.Printf(" - p256: NIST P-256\n")
|
|
fmt.Printf(" - ed25519: EdDSA signatures (FROST only)\n\n")
|
|
|
|
fmt.Printf("Features:\n")
|
|
fmt.Printf(" - Key generation with configurable threshold\n")
|
|
fmt.Printf(" - Threshold signing\n")
|
|
fmt.Printf(" - Dynamic resharing (LSS only)\n")
|
|
fmt.Printf(" - Signature verification\n")
|
|
fmt.Printf(" - Performance benchmarking\n")
|
|
fmt.Printf(" - Comprehensive testing\n")
|
|
fmt.Printf(" - Protocol simulation\n")
|
|
fmt.Printf(" - Key import/export\n\n")
|
|
|
|
if verbose {
|
|
fmt.Printf("Configuration Directory: %s\n", configDir)
|
|
fmt.Printf("Current Protocol: %s\n", protocolName)
|
|
fmt.Printf("Current Curve: %s\n", curveType)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func getCurve(curveType string) (curve.Curve, error) {
|
|
switch strings.ToLower(curveType) {
|
|
case "secp256k1":
|
|
return curve.Secp256k1{}, nil
|
|
case "p256":
|
|
return nil, fmt.Errorf("p256 not yet supported")
|
|
case "ed25519":
|
|
return curve.Ed25519{}, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown curve: %s", curveType)
|
|
}
|
|
}
|