diff --git a/e2e/setup_test_identities.sh b/e2e/setup_test_identities.sh index 46b62f6..07058e8 100755 --- a/e2e/setup_test_identities.sh +++ b/e2e/setup_test_identities.sh @@ -4,6 +4,18 @@ # This script sets up identities for testing with separate test database paths set -e +# Cross-platform sed in-place function +# macOS requires backup extension, Linux doesn't +sed_inplace() { + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + sed -i '' "$@" + else + # Linux and others + sed -i "$@" + fi +} + # Number of test nodes NUM_NODES=3 BASE_DIR="$(pwd)" @@ -97,14 +109,14 @@ if [ -f "test_event_initiator.identity.json" ]; then # Update all test node config files with the actual public key and password for i in $(seq 0 $((NUM_NODES-1))); do # Update public key using sed with | as delimiter (safer than /) - sed -i "s|event_initiator_pubkey:.*|event_initiator_pubkey: $PUBKEY|g" "$BASE_DIR/test_node$i/config.yaml" + sed_inplace "s|event_initiator_pubkey:.*|event_initiator_pubkey: $PUBKEY|g" "$BASE_DIR/test_node$i/config.yaml" # Update password using sed with | as delimiter and escaped password - sed -i "s|badger_password:.*|badger_password: $ESCAPED_PASSWORD|g" "$BASE_DIR/test_node$i/config.yaml" + sed_inplace "s|badger_password:.*|badger_password: $ESCAPED_PASSWORD|g" "$BASE_DIR/test_node$i/config.yaml" done # Also update the main config.test.yaml - sed -i "s|event_initiator_pubkey:.*|event_initiator_pubkey: $PUBKEY|g" "$BASE_DIR/config.test.yaml" - sed -i "s|badger_password:.*|badger_password: $ESCAPED_PASSWORD|g" "$BASE_DIR/config.test.yaml" + sed_inplace "s|event_initiator_pubkey:.*|event_initiator_pubkey: $PUBKEY|g" "$BASE_DIR/config.test.yaml" + sed_inplace "s|badger_password:.*|badger_password: $ESCAPED_PASSWORD|g" "$BASE_DIR/config.test.yaml" echo "✅ Event initiator public key updated: $PUBKEY" echo "✅ Badger password updated: $BADGER_PASSWORD" diff --git a/e2e/taurus_cmp_test.go b/e2e/taurus_cmp_test.go new file mode 100644 index 0000000..fa6beb3 --- /dev/null +++ b/e2e/taurus_cmp_test.go @@ -0,0 +1,461 @@ +package e2e + +import ( + "testing" + "time" + + "github.com/fystack/mpcium/pkg/event" + "github.com/fystack/mpcium/pkg/logger" + "github.com/fystack/mpcium/pkg/types" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTaurusCMPKeyGeneration(t *testing.T) { + suite := NewE2ETestSuite(".") + logger.Init("dev", true) + + // Comprehensive cleanup before starting tests + t.Log("Performing pre-test cleanup...") + suite.CleanupTestEnvironment(t) + + // Ensure cleanup happens even if test fails + defer func() { + t.Log("Performing post-test cleanup...") + suite.Cleanup(t) + }() + + // Setup infrastructure + t.Run("Setup", func(t *testing.T) { + suite.SetupInfrastructure(t) + suite.SetupTestNodes(t) + suite.LoadConfig() + suite.RegisterPeers(t) + suite.StartNodes(t) + suite.WaitForNodesReady(t) + suite.SetupMPCClient(t) + }) + + // Test Taurus CMP key generation + t.Run("TaurusCMPKeyGeneration", func(t *testing.T) { + testTaurusCMPKeyGeneration(t, suite) + }) + + // Verify key consistency across nodes + t.Run("VerifyTaurusCMPConsistency", func(t *testing.T) { + verifyTaurusCMPKeyConsistency(t, suite) + }) +} + +func TestTaurusCMPSigning(t *testing.T) { + suite := NewE2ETestSuite(".") + logger.Init("dev", true) + + // Comprehensive cleanup before starting tests + t.Log("Performing pre-test cleanup...") + suite.CleanupTestEnvironment(t) + + // Ensure cleanup happens even if test fails + defer func() { + t.Log("Performing post-test cleanup...") + suite.Cleanup(t) + }() + + // Setup infrastructure + t.Run("Setup", func(t *testing.T) { + suite.SetupInfrastructure(t) + suite.SetupTestNodes(t) + suite.LoadConfig() + suite.RegisterPeers(t) + suite.StartNodes(t) + suite.WaitForNodesReady(t) + suite.SetupMPCClient(t) + }) + + // Generate keys first + t.Run("KeyGenerationForSigning", func(t *testing.T) { + testTaurusCMPKeyGeneration(t, suite) + }) + + // Test Taurus CMP signing + t.Run("TaurusCMPSigning", func(t *testing.T) { + testTaurusCMPSigning(t, suite) + }) +} + +func TestTaurusCMPResharing(t *testing.T) { + suite := NewE2ETestSuite(".") + logger.Init("dev", true) + + // Comprehensive cleanup before starting tests + t.Log("Performing pre-test cleanup...") + suite.CleanupTestEnvironment(t) + + // Ensure cleanup happens even if test fails + defer func() { + t.Log("Performing post-test cleanup...") + suite.Cleanup(t) + }() + + // Setup infrastructure + t.Run("Setup", func(t *testing.T) { + suite.SetupInfrastructure(t) + suite.SetupTestNodes(t) + suite.LoadConfig() + suite.RegisterPeers(t) + suite.StartNodes(t) + suite.WaitForNodesReady(t) + suite.SetupMPCClient(t) + }) + + // Generate keys first + t.Run("KeyGenerationForResharing", func(t *testing.T) { + testTaurusCMPKeyGeneration(t, suite) + }) + + // Test Taurus CMP resharing + t.Run("TaurusCMPResharing", func(t *testing.T) { + testTaurusCMPResharing(t, suite) + }) + + // Test signing after resharing + t.Run("SigningAfterResharing", func(t *testing.T) { + testTaurusCMPSigning(t, suite) + }) +} + +func TestMixedProtocolKeyGeneration(t *testing.T) { + suite := NewE2ETestSuite(".") + logger.Init("dev", true) + + // Comprehensive cleanup before starting tests + t.Log("Performing pre-test cleanup...") + suite.CleanupTestEnvironment(t) + + // Ensure cleanup happens even if test fails + defer func() { + t.Log("Performing post-test cleanup...") + suite.Cleanup(t) + }() + + // Setup infrastructure + t.Run("Setup", func(t *testing.T) { + suite.SetupInfrastructure(t) + suite.SetupTestNodes(t) + suite.LoadConfig() + suite.RegisterPeers(t) + suite.StartNodes(t) + suite.WaitForNodesReady(t) + suite.SetupMPCClient(t) + }) + + // Test that all three protocols work in parallel + t.Run("MixedProtocolKeyGeneration", func(t *testing.T) { + testMixedProtocolKeyGeneration(t, suite) + }) + + // Verify all keys are consistent + t.Run("VerifyMixedProtocolConsistency", func(t *testing.T) { + verifyMixedProtocolConsistency(t, suite) + }) +} + +func testTaurusCMPKeyGeneration(t *testing.T, suite *E2ETestSuite) { + t.Log("Testing Taurus CMP key generation...") + + // Ensure MPC client is initialized + if suite.mpcClient == nil { + t.Fatal("MPC client is not initialized. Make sure Setup subtest runs first.") + } + + // Generate 1 wallet ID for Taurus CMP testing + walletID := uuid.New().String() + suite.walletIDs = append(suite.walletIDs, walletID) + + t.Logf("Generated wallet ID for Taurus CMP: %s", walletID) + + // Setup result listener + err := suite.mpcClient.OnWalletCreationResult(func(result event.KeygenResultEvent) { + t.Logf("Received Taurus CMP keygen result for wallet %s: %s", result.WalletID, result.ResultType) + suite.keygenResults[result.WalletID] = &result + + if result.ResultType == event.ResultTypeError { + t.Logf("Taurus CMP keygen failed for wallet %s: %s (%s)", result.WalletID, result.ErrorReason, result.ErrorCode) + } else { + t.Logf("Taurus CMP keygen succeeded for wallet %s", result.WalletID) + } + }) + require.NoError(t, err, "Failed to setup Taurus CMP keygen result listener") + + // Add longer delay to ensure listener is fully established + t.Log("Waiting for result listener to be fully established...") + time.Sleep(15 * time.Second) + + // Trigger key generation + t.Logf("Triggering Taurus CMP key generation for wallet %s", walletID) + err = suite.mpcClient.CreateWallet(walletID) + require.NoError(t, err, "Failed to trigger Taurus CMP key generation for wallet %s", walletID) + + // Wait for key generation to complete + t.Log("Waiting for Taurus CMP key generation to complete...") + + timeout := time.NewTimer(keygenTimeout) + defer timeout.Stop() + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-timeout.C: + t.Fatalf("Timeout waiting for Taurus CMP keygen result for wallet %s", walletID) + case <-ticker.C: + t.Logf("Still waiting for Taurus CMP keygen result for wallet %s...", walletID) + + if result, exists := suite.keygenResults[walletID]; exists { + if result.ResultType == event.ResultTypeError { + t.Errorf("Taurus CMP keygen failed for wallet %s: %s (%s)", walletID, result.ErrorReason, result.ErrorCode) + } else { + t.Logf("Taurus CMP keygen succeeded for wallet %s", result.WalletID) + + // Validate that we have all three key types + assert.NotEmpty(t, result.ECDSAPubKey, "ECDSA public key should not be empty for wallet %s", walletID) + assert.NotEmpty(t, result.EDDSAPubKey, "EdDSA public key should not be empty for wallet %s", walletID) + assert.NotEmpty(t, result.TaurusCMPPubKey, "Taurus CMP public key should not be empty for wallet %s", walletID) + + // Log key sizes for debugging + t.Logf("Key sizes - ECDSA: %d bytes, EdDSA: %d bytes, Taurus CMP: %d bytes", + len(result.ECDSAPubKey), len(result.EDDSAPubKey), len(result.TaurusCMPPubKey)) + } + return + } + } + } +} + +func testTaurusCMPSigning(t *testing.T, suite *E2ETestSuite) { + t.Log("Testing Taurus CMP signing...") + + if len(suite.walletIDs) == 0 { + t.Fatal("No wallets available for Taurus CMP signing. Make sure key generation ran first.") + } + + walletID := suite.walletIDs[0] + t.Logf("Testing Taurus CMP signing for wallet %s", walletID) + + // Setup signing result listener + signingResults := make(map[string]*event.SigningResultEvent) + err := suite.mpcClient.OnSignResult(func(result event.SigningResultEvent) { + t.Logf("Received Taurus CMP signing result for wallet %s (tx: %s): %s", result.WalletID, result.TxID, result.ResultType) + signingResults[result.TxID] = &result + + if result.ResultType == event.ResultTypeError { + t.Logf("Taurus CMP signing failed for wallet %s (tx: %s): %s (%s)", result.WalletID, result.TxID, result.ErrorReason, result.ErrorCode) + } else { + t.Logf("Taurus CMP signing succeeded for wallet %s (tx: %s)", result.WalletID, result.TxID) + } + }) + require.NoError(t, err, "Failed to setup Taurus CMP signing result listener") + + // Wait for listener setup + time.Sleep(2 * time.Second) + + // Test messages to sign + testMessages := []string{ + "Taurus CMP Test Message 1", + "Taurus CMP Test Message 2", + "Taurus CMP Test Message 3", + } + + for i, message := range testMessages { + t.Logf("Testing Taurus CMP signing message %d: %s", i+1, message) + + // Create signing transaction message for Taurus CMP + txID := uuid.New().String() + signTxMsg := &types.SignTxMessage{ + WalletID: walletID, + TxID: txID, + Tx: []byte(message), + KeyType: types.KeyTypeTaurusCmp, + NetworkInternalCode: "test", + } + + // Trigger Taurus CMP signing + err := suite.mpcClient.SignTransaction(signTxMsg) + require.NoError(t, err, "Failed to trigger Taurus CMP signing for wallet %s", walletID) + + // Wait for signing result + timeout := time.NewTimer(signingTimeout) + defer timeout.Stop() + + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + for { + select { + case <-timeout.C: + t.Fatalf("Timeout waiting for Taurus CMP signing result for wallet %s, tx %s", walletID, txID) + case <-ticker.C: + if result, exists := signingResults[txID]; exists { + if result.ResultType == event.ResultTypeError { + t.Errorf("Taurus CMP signing failed for wallet %s (tx: %s): %s (%s)", walletID, txID, result.ErrorReason, result.ErrorCode) + } else { + t.Logf("Taurus CMP signing succeeded for wallet %s (tx: %s)", walletID, txID) + assert.NotEmpty(t, result.Signature, "Taurus CMP signature should not be empty for wallet %s", walletID) + + // Taurus CMP signatures should be 65 bytes (like ECDSA Ethereum format) + t.Logf("Taurus CMP signature length: %d bytes", len(result.Signature)) + if len(result.Signature) > 0 { + assert.Equal(t, 65, len(result.Signature), "Taurus CMP signature should be 65 bytes for wallet %s", walletID) + } + } + goto nextMessage + } + } + } + nextMessage: + } + + t.Log("Taurus CMP signing test completed") +} + +func testTaurusCMPResharing(t *testing.T, suite *E2ETestSuite) { + t.Log("Testing Taurus CMP resharing...") + + if len(suite.walletIDs) == 0 { + t.Fatal("No wallets available for Taurus CMP resharing. Make sure key generation ran first.") + } + + walletID := suite.walletIDs[0] + t.Logf("Testing Taurus CMP resharing for wallet %s", walletID) + + // Get node IDs for resharing + nodeIDs, err := suite.GetNodeIDs() + require.NoError(t, err, "Failed to get node IDs") + require.GreaterOrEqual(t, len(nodeIDs), 2, "Need at least 2 nodes for resharing") + + // Setup resharing result listener + err = suite.mpcClient.OnResharingResult(func(result event.ResharingResultEvent) { + t.Logf("Received Taurus CMP resharing result for wallet %s: %s", result.WalletID, result.ResultType) + suite.resharingResults[result.WalletID] = &result + + if result.ResultType == event.ResultTypeError { + t.Logf("Taurus CMP resharing failed for wallet %s: %s (%s)", result.WalletID, result.ErrorReason, result.ErrorCode) + } else { + t.Logf("Taurus CMP resharing succeeded for wallet %s", result.WalletID) + } + }) + require.NoError(t, err, "Failed to setup Taurus CMP resharing result listener") + + // Wait for listener setup + time.Sleep(10 * time.Second) + + // Create resharing message for Taurus CMP + sessionID := uuid.New().String() + resharingMsg := &types.ResharingMessage{ + SessionID: sessionID, + WalletID: walletID, + NodeIDs: nodeIDs[:2], // Use first 2 nodes for resharing + NewThreshold: 1, // New threshold of 1 + KeyType: types.KeyTypeTaurusCmp, + } + + t.Logf("Sending Taurus CMP resharing message for wallet %s with session ID %s", walletID, sessionID) + t.Logf("New committee: %v, New threshold: %d", resharingMsg.NodeIDs, resharingMsg.NewThreshold) + + // Send resharing message + err = suite.mpcClient.Resharing(resharingMsg) + require.NoError(t, err, "Failed to send Taurus CMP resharing message") + + // Wait for resharing result + t.Log("Waiting for Taurus CMP resharing result...") + + timeout := time.NewTimer(resharingTimeout) + defer timeout.Stop() + + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for { + select { + case <-timeout.C: + t.Fatalf("Timeout waiting for Taurus CMP resharing result for wallet %s", walletID) + case <-ticker.C: + t.Logf("Still waiting for Taurus CMP resharing result for wallet %s...", walletID) + + // Check if we got a result + if result, exists := suite.resharingResults[walletID]; exists { + if result.ResultType == event.ResultTypeError { + t.Fatalf("Taurus CMP resharing failed for wallet %s: %s (%s)", walletID, result.ErrorReason, result.ErrorCode) + } + + t.Logf("Taurus CMP resharing succeeded for wallet %s", walletID) + t.Logf("New public key: %x", result.PubKey) + t.Logf("New threshold: %d", result.NewThreshold) + + // Validate resharing result + assert.NotEmpty(t, result.PubKey, "Taurus CMP public key should not be empty after resharing") + assert.Equal(t, resharingMsg.NewThreshold, result.NewThreshold, "New threshold should match") + return + } + } + } +} + +func testMixedProtocolKeyGeneration(t *testing.T, suite *E2ETestSuite) { + t.Log("Testing mixed protocol key generation (ECDSA + EdDSA + Taurus CMP)...") + + // This test validates that all three protocols work together in the same wallet + // The current implementation generates all three key types simultaneously + testTaurusCMPKeyGeneration(t, suite) + + // Verify that we indeed got all three key types + if len(suite.keygenResults) > 0 { + for walletID, result := range suite.keygenResults { + t.Logf("Validating mixed protocol keys for wallet %s", walletID) + + assert.NotEmpty(t, result.ECDSAPubKey, "ECDSA key missing in mixed protocol") + assert.NotEmpty(t, result.EDDSAPubKey, "EdDSA key missing in mixed protocol") + assert.NotEmpty(t, result.TaurusCMPPubKey, "Taurus CMP key missing in mixed protocol") + + t.Logf("Mixed protocol validation passed for wallet %s", walletID) + } + } +} + +func verifyTaurusCMPKeyConsistency(t *testing.T, suite *E2ETestSuite) { + t.Log("Verifying Taurus CMP key consistency across nodes...") + + // Stop all nodes first to safely access databases + suite.StopNodes(t) + + // Check each wallet's Taurus CMP keys in all node databases + for _, walletID := range suite.walletIDs { + t.Logf("Checking Taurus CMP keys for wallet %s", walletID) + + // Check Taurus CMP keys + suite.CheckKeyInAllNodes(t, walletID, "taurus_cmp", "Taurus CMP") + } + + t.Log("Taurus CMP key consistency verification completed") +} + +func verifyMixedProtocolConsistency(t *testing.T, suite *E2ETestSuite) { + t.Log("Verifying mixed protocol key consistency across nodes...") + + // Stop all nodes first to safely access databases + suite.StopNodes(t) + + // Check each wallet's keys in all node databases + for _, walletID := range suite.walletIDs { + t.Logf("Checking mixed protocol keys for wallet %s", walletID) + + // Check all three key types + suite.CheckKeyInAllNodes(t, walletID, "ecdsa", "ECDSA") + suite.CheckKeyInAllNodes(t, walletID, "eddsa", "EdDSA") + suite.CheckKeyInAllNodes(t, walletID, "taurus_cmp", "Taurus CMP") + } + + t.Log("Mixed protocol key consistency verification completed") +} diff --git a/examples/reshare/main.go b/examples/reshare/main.go index 47c4d85..516b9d4 100644 --- a/examples/reshare/main.go +++ b/examples/reshare/main.go @@ -78,16 +78,28 @@ func main() { logger.Fatal("Failed to subscribe to OnResharingResult", err) } - resharingMsg := &types.ResharingMessage{ - SessionID: uuid.NewString(), - WalletID: "506d2d40-483a-49f1-93c8-27dd4fe9740c", - NodeIDs: []string{ - "c95c340e-5a18-472d-b9b0-5ac68218213a", - "ac37e85f-caca-4bee-8a3a-49a0fe35abff", - }, // new peer IDs + // Example wallet ID - replace with your actual wallet ID + walletID := "506d2d40-483a-49f1-93c8-27dd4fe9740c" + + // Example node IDs - replace with your actual node IDs + nodeIDs := []string{ + "c95c340e-5a18-472d-b9b0-5ac68218213a", + "ac37e85f-caca-4bee-8a3a-49a0fe35abff", + } - NewThreshold: 1, // t+1 <= len(NodeIDs) - KeyType: types.KeyTypeEd25519, + // Choose the key type to reshare + // Options: types.KeyTypeECDSA, types.KeyTypeEd25519, types.KeyTypeTaurusCmp + keyType := types.KeyTypeTaurusCmp // Changed to demonstrate Taurus CMP resharing + + fmt.Printf("Resharing %s keys for wallet %s\n", keyType, walletID) + fmt.Printf("New node committee: %v\n", nodeIDs) + + resharingMsg := &types.ResharingMessage{ + SessionID: uuid.NewString(), + WalletID: walletID, + NodeIDs: nodeIDs, // new peer IDs + NewThreshold: 1, // t+1 <= len(NodeIDs) + KeyType: keyType, } err = mpcClient.Resharing(resharingMsg) if err != nil { diff --git a/examples/taurus/README.md b/examples/taurus/README.md new file mode 100644 index 0000000..c679a03 --- /dev/null +++ b/examples/taurus/README.md @@ -0,0 +1,52 @@ +# Taurus CMP Example + +This example demonstrates how to use Taurus CMP (Cryptographic Multi-Party) protocol for key generation in MPCium. + + +## Running the Example + +1. **Start your MPCium nodes** (see main README for setup instructions) + +2. **Run the Taurus example**: + ```bash + cd examples/taurus + go run main.go + ``` + +## What This Example Does + +1. **Connects to NATS** using your configuration +2. **Generates a new wallet ID** for the demonstration +3. **Triggers key generation** for all protocols (ECDSA, EdDSA, Taurus CMP) +4. **Shows the results** including Taurus CMP public key information + +## Key Features Demonstrated + +- **Taurus CMP Key Generation**: Creates threshold keys using the Taurus protocol +- **Result Handling**: Shows how to receive and process Taurus CMP results +- **Integration**: Demonstrates how Taurus CMP works alongside other protocols + +## Expected Output + +``` +Generated wallet ID: 12345678-1234-1234-1234-123456789012 +Step 1: Generating Taurus CMP keys... +Wallet creation request sent for 12345678-1234-1234-1234-123456789012 +Waiting for key generation to complete... +Note: This generates keys for all protocols (ECDSA, EdDSA, Taurus CMP) +Taurus CMP key generated successfully + Public key size: 64 bytes +``` + +## Next Steps + +- Use the generated wallet ID for signing operations +- Try resharing the keys to refresh the key shares +- Explore the other examples for signing and resharing with different protocols + +## Configuration + +This example uses the same configuration as your MPCium nodes. Make sure: +- Your `config.yaml` is properly configured +- Your `event_initiator.key` exists +- Your MPCium nodes are running and ready diff --git a/examples/taurus/main.go b/examples/taurus/main.go new file mode 100644 index 0000000..3df75f6 --- /dev/null +++ b/examples/taurus/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "slices" + "syscall" + + "github.com/fystack/mpcium/pkg/client" + "github.com/fystack/mpcium/pkg/config" + "github.com/fystack/mpcium/pkg/event" + "github.com/fystack/mpcium/pkg/logger" + "github.com/fystack/mpcium/pkg/types" + "github.com/google/uuid" + "github.com/nats-io/nats.go" + "github.com/spf13/viper" +) + +func main() { + const environment = "development" + config.InitViperConfig("") + logger.Init(environment, true) + + algorithm := viper.GetString("event_initiator_algorithm") + if algorithm == "" { + algorithm = string(types.EventInitiatorKeyTypeEd25519) + } + + // Validate algorithm + if !slices.Contains( + []string{ + string(types.EventInitiatorKeyTypeEd25519), + string(types.EventInitiatorKeyTypeP256), + }, + algorithm, + ) { + logger.Fatal( + fmt.Sprintf( + "invalid algorithm: %s. Must be %s or %s", + algorithm, + types.EventInitiatorKeyTypeEd25519, + types.EventInitiatorKeyTypeP256, + ), + nil, + ) + } + + natsURL := viper.GetString("nats.url") + natsConn, err := nats.Connect(natsURL) + if err != nil { + logger.Fatal("Failed to connect to NATS", err) + } + defer natsConn.Drain() + defer natsConn.Close() + + localSigner, err := client.NewLocalSigner(types.EventInitiatorKeyType(algorithm), client.LocalSignerOptions{ + KeyPath: "./event_initiator.key", + }) + if err != nil { + logger.Fatal("Failed to create local signer", err) + } + + mpcClient := client.NewMPCClient(client.Options{ + NatsConn: natsConn, + Signer: localSigner, + }) + + // Generate a new wallet ID for this demo + walletID := uuid.New().String() + fmt.Printf("Generated wallet ID: %s\n", walletID) + + // Step 1: Key Generation + fmt.Println("Step 1: Generating Taurus CMP keys...") + + err = mpcClient.OnWalletCreationResult(func(evt event.KeygenResultEvent) { + if evt.ResultType == event.ResultTypeSuccess { + logger.Info("Taurus CMP key generation completed successfully", + "walletID", evt.WalletID, + "taurusPubKeySize", len(evt.TaurusCMPPubKey), + ) + fmt.Printf("Taurus CMP key generated successfully\n") + fmt.Printf(" Public key size: %d bytes\n", len(evt.TaurusCMPPubKey)) + } else { + logger.Error("Taurus CMP key generation failed", nil, + "walletID", evt.WalletID, + "error", evt.ErrorReason, + ) + fmt.Printf("Taurus CMP key generation failed: %s\n", evt.ErrorReason) + } + }) + if err != nil { + logger.Fatal("Failed to subscribe to wallet creation results", err) + } + + err = mpcClient.CreateWallet(walletID) + if err != nil { + logger.Fatal("Failed to create wallet", err) + } + + fmt.Printf("Wallet creation request sent for %s\n", walletID) + fmt.Println("Waiting for key generation to complete...") + fmt.Println("Note: This generates keys for all protocols (ECDSA, EdDSA, Taurus CMP)") + + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + <-stop + + fmt.Println("\nShutting down Taurus CMP demo.") +} diff --git a/go.mod b/go.mod index 21eee92..0d2e5c8 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/credentials v1.18.8 github.com/aws/aws-sdk-go-v2/service/kms v1.45.0 github.com/bnb-chain/tss-lib/v2 v2.0.2 + github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/dgraph-io/badger/v4 v4.7.0 @@ -43,7 +44,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.38.1 // indirect github.com/aws/smithy-go v1.23.0 // indirect github.com/btcsuite/btcd v0.24.2 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btcutil v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/pkg/mpc/taurus/cmp_test.go b/pkg/mpc/taurus/cmp_test.go index a338b2b..541d03e 100644 --- a/pkg/mpc/taurus/cmp_test.go +++ b/pkg/mpc/taurus/cmp_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/fystack/mpcium/pkg/logger" + "github.com/fystack/mpcium/pkg/types" "github.com/taurusgroup/multi-party-sig/pkg/party" ) @@ -80,16 +81,22 @@ func TestCmpParty(t *testing.T) { sigs := drain[[]byte](test.results["sign"]) assertAllBytesEqual(t, sigs) - // // --- Reshare --- - // test.runAll(func(p *CmpParty) (any, error) { - // return p.Reshare(context.Background()) - // }, "reshare") + // --- Reshare --- + test.runAll(func(p *CmpParty) (any, error) { + return p.Reshare(context.Background()) + }, "reshare") + + reshareResults := drain[types.ReshareData](test.results["reshare"]) + assertReshareDataConsistent(t, reshareResults) - // // // --- Sign 2 --- - // msg = big.NewInt(2) - // test.runAll(func(p *CmpParty) (any, error) { - // return p.Sign(context.Background(), msg) - // }, "sign") + // --- Sign 2 (after reshare) --- + msg = big.NewInt(2) + test.runAll(func(p *CmpParty) (any, error) { + return p.Sign(context.Background(), msg) + }, "sign") + + sigs2 := drain[[]byte](test.results["sign"]) + assertAllBytesEqual(t, sigs2) } func drain[T any](ch chan any) []T { @@ -112,3 +119,150 @@ func assertAllBytesEqual(t *testing.T, vals [][]byte) { } } } + +func assertReshareDataConsistent(t *testing.T, reshareResults []types.ReshareData) { + if len(reshareResults) == 0 { + t.Fatal("no reshare results to compare") + } + + first := reshareResults[0] + t.Logf("Reshare result - SID: %s, Type: %s, Threshold: %d, PubKey length: %d", + first.SID, first.Type, first.Threshold, len(first.PubKeyBytes)) + + // All parties should have the same public key after reshare + for i, result := range reshareResults[1:] { + if !bytes.Equal(first.PubKeyBytes, result.PubKeyBytes) { + t.Fatalf("public keys not equal after reshare at index %d", i+1) + } + if first.SID != result.SID { + t.Fatalf("session IDs not equal after reshare at index %d", i+1) + } + if first.Type != result.Type { + t.Fatalf("key types not equal after reshare at index %d", i+1) + } + if first.Threshold != result.Threshold { + t.Fatalf("thresholds not equal after reshare at index %d", i+1) + } + } + + // Validate that we have valid public key data + if len(first.PubKeyBytes) == 0 { + t.Fatal("public key bytes should not be empty after reshare") + } + + // Validate key type + if first.Type != "taurus_cmp" { + t.Fatalf("expected key type 'taurus_cmp', got '%s'", first.Type) + } + + t.Logf("Reshare data consistency validation passed for %d parties", len(reshareResults)) +} + +func TestCmpResharing(t *testing.T) { + sid := "test-reshare-session-456" + ids := []party.ID{"node0", "node1", "node2"} + test := newCmpTest(sid, ids) + + // Phase 1: Initial key generation + t.Log("Phase 1: Initial key generation") + test.runAll(func(p *CmpParty) (any, error) { + return p.Keygen(context.Background()) + }, "keygen") + + keygenResults := drain[types.KeyData](test.results["keygen"]) + t.Logf("Generated %d keys", len(keygenResults)) + + // Verify initial keygen results + if len(keygenResults) != len(ids) { + t.Fatalf("expected %d keygen results, got %d", len(ids), len(keygenResults)) + } + + // Phase 2: Initial signing to verify keys work + t.Log("Phase 2: Initial signing verification") + msg1 := big.NewInt(42) + test.runAll(func(p *CmpParty) (any, error) { + return p.Sign(context.Background(), msg1) + }, "sign") + + sigs1 := drain[[]byte](test.results["sign"]) + assertAllBytesEqual(t, sigs1) + t.Logf("Initial signing successful with %d signatures", len(sigs1)) + + // Phase 3: Reshare/refresh keys + t.Log("Phase 3: Resharing/refreshing keys") + test.runAll(func(p *CmpParty) (any, error) { + return p.Reshare(context.Background()) + }, "reshare") + + reshareResults := drain[types.ReshareData](test.results["reshare"]) + assertReshareDataConsistent(t, reshareResults) + t.Logf("Resharing successful with %d results", len(reshareResults)) + + // Phase 4: Signing after reshare to verify keys still work + t.Log("Phase 4: Signing after reshare") + msg2 := big.NewInt(84) + test.runAll(func(p *CmpParty) (any, error) { + return p.Sign(context.Background(), msg2) + }, "sign") + + sigs2 := drain[[]byte](test.results["sign"]) + assertAllBytesEqual(t, sigs2) + t.Logf("Post-reshare signing successful with %d signatures", len(sigs2)) + + // Phase 5: Verify public keys remain the same after reshare + t.Log("Phase 5: Public key consistency verification") + originalPubKey := keygenResults[0].PubKeyBytes + resharedPubKey := reshareResults[0].PubKeyBytes + + if !bytes.Equal(originalPubKey, resharedPubKey) { + t.Fatal("Public key changed after reshare - this should not happen in CMP refresh") + } + + t.Log("All resharing tests passed successfully") +} + +func TestCmpMultipleReshares(t *testing.T) { + sid := "test-multi-reshare-789" + ids := []party.ID{"node0", "node1", "node2"} + test := newCmpTest(sid, ids) + + // Initial keygen + t.Log("Initial key generation") + test.runAll(func(p *CmpParty) (any, error) { + return p.Keygen(context.Background()) + }, "keygen") + + keygenResults := drain[types.KeyData](test.results["keygen"]) + originalPubKey := keygenResults[0].PubKeyBytes + + // Perform multiple reshares + numReshares := 3 + for i := 1; i <= numReshares; i++ { + t.Logf("Reshare iteration %d/%d", i, numReshares) + + // Reshare + test.runAll(func(p *CmpParty) (any, error) { + return p.Reshare(context.Background()) + }, "reshare") + + reshareResults := drain[types.ReshareData](test.results["reshare"]) + assertReshareDataConsistent(t, reshareResults) + + // Verify public key consistency + if !bytes.Equal(originalPubKey, reshareResults[0].PubKeyBytes) { + t.Fatalf("Public key changed after reshare %d", i) + } + + // Test signing after each reshare + testMsg := big.NewInt(int64(100 + i)) + test.runAll(func(p *CmpParty) (any, error) { + return p.Sign(context.Background(), testMsg) + }, "sign") + + sigs := drain[[]byte](test.results["sign"]) + assertAllBytesEqual(t, sigs) + t.Logf("Signing after reshare %d successful", i) + } + + t.Logf("Multiple reshares test passed (%d reshares)", numReshares) +}