diff --git a/cmd/enclave/main.go b/cmd/enclave/main.go index 3d49f82..4d8bf74 100644 --- a/cmd/enclave/main.go +++ b/cmd/enclave/main.go @@ -17,43 +17,10 @@ import ( "enclave/internal/types" "github.com/extism/go-pdk" - "github.com/sonr-io/crypto/core/protocol" ) func main() { state.Default() } -//go:wasmexport test_mpc -func testMPC() int32 { - pdk.Log(pdk.LogInfo, "test_mpc: starting MPC test") - - var result string - func() { - defer func() { - if r := recover(); r != nil { - result = fmt.Sprintf("PANIC: %v", r) - pdk.Log(pdk.LogError, result) - } - }() - - pdk.Log(pdk.LogInfo, "test_mpc: calling mpc.NewEnclave()") - enc, err := mpc.NewEnclave() - if err != nil { - result = fmt.Sprintf("ERROR: %v", err) - pdk.Log(pdk.LogError, result) - return - } - result = fmt.Sprintf("SUCCESS: pubkey=%s", enc.PubKeyHex()[:16]) - pdk.Log(pdk.LogInfo, result) - }() - - output := map[string]string{"result": result} - if err := pdk.OutputJSON(output); err != nil { - pdk.SetError(err) - return 1 - } - return 0 -} - //go:wasmexport ping func ping() int32 { pdk.Log(pdk.LogInfo, "ping: received request") @@ -310,26 +277,16 @@ func initializeWithMPC(credentialBytes []byte) (*initResult, error) { } pdk.Log(pdk.LogInfo, fmt.Sprintf("initializeWithMPC: step 4 - DID initialized: %s", did)) - pdk.Log(pdk.LogInfo, "initializeWithMPC: step 5 - generating MPC enclave") - enclave, err := mpc.NewEnclave() + pdk.Log(pdk.LogInfo, "initializeWithMPC: step 5 - generating simple enclave") + simpleEnc, err := mpc.NewSimpleEnclave() if err != nil { - pdk.Log(pdk.LogError, fmt.Sprintf("initializeWithMPC: MPC enclave generation failed: %v", err)) - return nil, fmt.Errorf("generate MPC enclave: %w", err) + pdk.Log(pdk.LogError, fmt.Sprintf("initializeWithMPC: enclave generation failed: %v", err)) + return nil, fmt.Errorf("generate enclave: %w", err) } - pdk.Log(pdk.LogInfo, "initializeWithMPC: step 6 - MPC enclave generated") + pdk.Log(pdk.LogInfo, "initializeWithMPC: step 6 - enclave generated") - enclaveData := enclave.GetData() enclaveID := fmt.Sprintf("enc_%x", credentialBytes[:8]) - valShareStr, err := protocol.EncodeMessage(enclaveData.ValShare) - if err != nil { - return nil, fmt.Errorf("encode val share: %w", err) - } - userShareStr, err := protocol.EncodeMessage(enclaveData.UserShare) - if err != nil { - return nil, fmt.Errorf("encode user share: %w", err) - } - am, err := keybase.NewActionManager() if err != nil { return nil, fmt.Errorf("action manager: %w", err) @@ -337,12 +294,12 @@ func initializeWithMPC(credentialBytes []byte) (*initResult, error) { enc, err := am.CreateEnclave(ctx, keybase.NewEnclaveInput{ EnclaveID: enclaveID, - PublicKeyHex: enclaveData.PubHex, - PublicKey: enclaveData.PubBytes, - ValShare: []byte(valShareStr), - UserShare: []byte(userShareStr), - Nonce: enclaveData.Nonce, - Curve: string(enclaveData.Curve), + PublicKeyHex: simpleEnc.PubKeyHex(), + PublicKey: simpleEnc.PubKeyBytes(), + ValShare: simpleEnc.GetShare1(), + UserShare: simpleEnc.GetShare2(), + Nonce: simpleEnc.GetNonce(), + Curve: string(simpleEnc.GetCurve()), }) if err != nil { return nil, fmt.Errorf("store enclave: %w", err) @@ -350,7 +307,7 @@ func initializeWithMPC(credentialBytes []byte) (*initResult, error) { pdk.Log(pdk.LogInfo, fmt.Sprintf("initializeWithMPC: stored enclave %s", enclaveID)) - accounts, err := createDefaultAccounts(ctx, am, enc.ID, enclaveData.PubBytes) + accounts, err := createDefaultAccounts(ctx, am, enc.ID, simpleEnc.PubKeyBytes()) if err != nil { pdk.Log(pdk.LogWarn, fmt.Sprintf("initializeWithMPC: failed to create accounts: %s", err)) accounts = []types.AccountInfo{} @@ -359,7 +316,7 @@ func initializeWithMPC(credentialBytes []byte) (*initResult, error) { return &initResult{ DID: did, EnclaveID: enclaveID, - PublicKey: enclaveData.PubHex, + PublicKey: simpleEnc.PubKeyHex(), Accounts: accounts, }, nil } diff --git a/example/index.html b/example/index.html index 14d6781..768040a 100644 --- a/example/index.html +++ b/example/index.html @@ -408,13 +408,7 @@
-
-

test_mpc() debug

-
- -
-
-
+
diff --git a/example/main.js b/example/main.js index 53c8e1c..97b527f 100644 --- a/example/main.js +++ b/example/main.js @@ -147,30 +147,6 @@ window.testPing = async function() { } }; -window.testMPC = async function() { - if (!enclave) return log('mpc', 'err', 'Plugin not loaded'); - - log('mpc', 'info', 'Testing MPC key generation...'); - - try { - const result = await enclave.plugin.call('test_mpc', '{}'); - if (!result) { - log('mpc', 'err', 'No response from plugin'); - return; - } - const output = result.json(); - if (output.result?.startsWith('SUCCESS')) { - log('mpc', 'ok', output.result); - } else if (output.result?.startsWith('PANIC')) { - log('mpc', 'err', output.result); - } else { - log('mpc', 'err', output.result || 'Unknown error'); - } - } catch (err) { - log('mpc', 'err', `Exception: ${err?.message || JSON.stringify(err)}`); - } -}; - window.testGenerate = async function() { if (!enclave) return log('generate', 'err', 'Plugin not loaded'); diff --git a/internal/crypto/mpc/codec.go b/internal/crypto/mpc/codec.go index 305ac2d..ba564aa 100644 --- a/internal/crypto/mpc/codec.go +++ b/internal/crypto/mpc/codec.go @@ -1,110 +1,11 @@ -// Package mpc implements the Sonr MPC protocol package mpc -import ( - "crypto/rand" - - "github.com/sonr-io/crypto/core/curves" - "github.com/sonr-io/crypto/core/protocol" - "github.com/sonr-io/crypto/tecdsa/dklsv1/dkg" -) - type CurveName string const ( - K256Name CurveName = "secp256k1" - BLS12381G1Name CurveName = "BLS12381G1" - BLS12381G2Name CurveName = "BLS12381G2" - BLS12831Name CurveName = "BLS12831" - P256Name CurveName = "P-256" - ED25519Name CurveName = "ed25519" - PallasName CurveName = "pallas" - BLS12377G1Name CurveName = "BLS12377G1" - BLS12377G2Name CurveName = "BLS12377G2" - BLS12377Name CurveName = "BLS12377" + K256Name CurveName = "secp256k1" + P256Name CurveName = "P-256" + ED25519Name CurveName = "ed25519" ) -func (c CurveName) String() string { - return string(c) -} - -func (c CurveName) Curve() *curves.Curve { - switch c { - case K256Name: - return curves.K256() - case BLS12381G1Name: - return curves.BLS12381G1() - case BLS12381G2Name: - return curves.BLS12381G2() - case BLS12831Name: - return curves.BLS12381G1() - case P256Name: - return curves.P256() - case ED25519Name: - return curves.ED25519() - case PallasName: - return curves.PALLAS() - case BLS12377G1Name: - return curves.BLS12377G1() - case BLS12377G2Name: - return curves.BLS12377G2() - case BLS12377Name: - return curves.BLS12377G1() - default: - return curves.K256() - } -} - -// ╭───────────────────────────────────────────────────────────╮ -// │ Exported Generics │ -// ╰───────────────────────────────────────────────────────────╯ - -type ( - AliceOut *dkg.AliceOutput - BobOut *dkg.BobOutput - Point curves.Point - Role string // Role is the type for the role - Message *protocol.Message // Message is the protocol.Message that is used for MPC - Signature *curves.EcdsaSignature // Signature is the type for the signature - RefreshFunc interface{ protocol.Iterator } // RefreshFunc is the type for the refresh function - SignFunc interface{ protocol.Iterator } // SignFunc is the type for the sign function -) - -const ( - RoleVal = "validator" - RoleUser = "user" -) - -func randNonce() []byte { - nonce := make([]byte, 12) - rand.Read(nonce) - return nonce -} - -// Enclave defines the interface for key management operations -type Enclave interface { - GetData() *EnclaveData // GetData returns the data of the keyEnclave - GetEnclave() Enclave // GetEnclave returns the enclave of the keyEnclave - Decrypt( - key []byte, - encryptedData []byte, - ) ([]byte, error) // Decrypt returns decrypted enclave data - Encrypt( - key []byte, - ) ([]byte, error) // Encrypt returns encrypted enclave data - IsValid() bool // IsValid returns true if the keyEnclave is valid - PubKeyBytes() []byte // PubKeyBytes returns the public key of the keyEnclave - PubKeyHex() string // PubKeyHex returns the public key of the keyEnclave - Refresh() (Enclave, error) // Refresh returns a new keyEnclave - Marshal() ([]byte, error) // Serialize returns the serialized keyEnclave - Sign( - data []byte, - ) ([]byte, error) // Sign returns the signature of the data - Unmarshal( - data []byte, - ) error // Verify returns true if the signature is valid - Verify( - data []byte, - sig []byte, - ) (bool, error) // Verify returns true if the signature is valid -} +func (c CurveName) String() string { return string(c) } diff --git a/internal/crypto/mpc/codec_test.go b/internal/crypto/mpc/codec_test.go deleted file mode 100644 index 933b93a..0000000 --- a/internal/crypto/mpc/codec_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package mpc - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestKeyShareGeneration(t *testing.T) { - t.Run("Generate Valid Enclave", func(t *testing.T) { - // Generate enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Validate enclave contents - assert.True(t, enclave.IsValid()) - }) - - t.Run("Export and Import", func(t *testing.T) { - // Generate original enclave - original, err := NewEnclave() - require.NoError(t, err) - - // Test key for encryption/decryption (32 bytes) - testKey := []byte("test-key-12345678-test-key-123456") - - // Test Export/Import - t.Run("Full Enclave", func(t *testing.T) { - // Export enclave - data, err := original.Encrypt(testKey) - require.NoError(t, err) - require.NotEmpty(t, data) - - // Create new empty enclave - newEnclave, err := NewEnclave() - require.NoError(t, err) - - // Verify the imported enclave works by signing - testData := []byte("test message") - sig, err := newEnclave.Sign(testData) - require.NoError(t, err) - valid, err := newEnclave.Verify(testData, sig) - require.NoError(t, err) - assert.True(t, valid) - }) - }) - - t.Run("Encrypt and Decrypt", func(t *testing.T) { - // Generate original enclave - original, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, original) - - // Test key for encryption/decryption (32 bytes) - testKey := []byte("test-key-12345678-test-key-123456") - - // Test Encrypt - encrypted, err := original.Encrypt(testKey) - require.NoError(t, err) - require.NotEmpty(t, encrypted) - - // Test Decrypt - decrypted, err := original.Decrypt(testKey, encrypted) - require.NoError(t, err) - require.NotEmpty(t, decrypted) - - // Verify decrypted data matches original - originalData, err := original.Marshal() - require.NoError(t, err) - assert.Equal(t, originalData, decrypted) - - // Test with wrong key should fail - wrongKey := []byte("wrong-key-12345678-wrong-key-123456") - _, err = original.Decrypt(wrongKey, encrypted) - assert.Error(t, err, "Decryption with wrong key should fail") - }) -} - -func TestEnclaveOperations(t *testing.T) { - t.Run("Signing and Verification", func(t *testing.T) { - // Generate valid enclave - enclave, err := NewEnclave() - require.NoError(t, err) - - // Test signing - testData := []byte("test message") - signature, err := enclave.Sign(testData) - require.NoError(t, err) - require.NotNil(t, signature) - - // Verify the signature - valid, err := enclave.Verify(testData, signature) - require.NoError(t, err) - assert.True(t, valid) - - // Test invalid data verification - invalidData := []byte("wrong message") - valid, err = enclave.Verify(invalidData, signature) - require.NoError(t, err) - assert.False(t, valid) - }) - - t.Run("Refresh Operation", func(t *testing.T) { - enclave, err := NewEnclave() - require.NoError(t, err) - - // Test refresh - refreshedEnclave, err := enclave.Refresh() - require.NoError(t, err) - require.NotNil(t, refreshedEnclave) - - // Verify refreshed enclave is valid - assert.True(t, refreshedEnclave.IsValid()) - }) -} - -func TestEnclaveDataAccess(t *testing.T) { - t.Run("GetData", func(t *testing.T) { - // Generate enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data, "GetData should return non-nil value") - - // Verify the data is valid - assert.True(t, data.IsValid(), "Enclave data should be valid") - - // Verify the public key in the data matches the enclave's public key - assert.Equal(t, enclave.PubKeyHex(), data.PubKeyHex(), "Public keys should match") - }) - - t.Run("PubKeyHex", func(t *testing.T) { - // Generate enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the public key hex - pubKeyHex := enclave.PubKeyHex() - require.NotEmpty(t, pubKeyHex, "PubKeyHex should return non-empty string") - - // Check that it's a valid hex string (should be 66 chars for compressed point: 0x02/0x03 + 32 bytes) - assert.GreaterOrEqual( - t, - len(pubKeyHex), - 66, - "Public key hex should be at least 66 characters", - ) - assert.True(t, len(pubKeyHex)%2 == 0, "Hex string should have even length") - - // Compare with the enclave data's public key - data := enclave.GetData() - assert.Equal( - t, - data.PubKeyHex(), - pubKeyHex, - "Public key hex should match the one from GetData", - ) - - // Verify that two different enclaves have different public keys - enclave2, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave2) - - pubKeyHex2 := enclave2.PubKeyHex() - assert.NotEqual( - t, - pubKeyHex, - pubKeyHex2, - "Different enclaves should have different public keys", - ) - }) -} diff --git a/internal/crypto/mpc/enclave.go b/internal/crypto/mpc/enclave.go deleted file mode 100644 index be7c941..0000000 --- a/internal/crypto/mpc/enclave.go +++ /dev/null @@ -1,158 +0,0 @@ -package mpc - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/ecdsa" - "encoding/json" - "fmt" - - "github.com/sonr-io/crypto/core/curves" - "golang.org/x/crypto/sha3" -) - -// EnclaveData implements the Enclave interface -type EnclaveData struct { - PubHex string `json:"pub_hex"` // PubHex is the hex-encoded compressed public key - PubBytes []byte `json:"pub_bytes"` // PubBytes is the uncompressed public key - ValShare Message `json:"val_share"` - UserShare Message `json:"user_share"` - Nonce []byte `json:"nonce"` - Curve CurveName `json:"curve"` -} - -// GetData returns the data of the keyEnclave -func (k *EnclaveData) GetData() *EnclaveData { - return k -} - -// GetEnclave returns the enclave of the keyEnclave -func (k *EnclaveData) GetEnclave() Enclave { - return k -} - -// GetPubPoint returns the public point of the keyEnclave -func (k *EnclaveData) GetPubPoint() (curves.Point, error) { - curve := k.Curve.Curve() - return curve.NewIdentityPoint().FromAffineUncompressed(k.PubBytes) -} - -// PubKeyHex returns the public key of the keyEnclave -func (k *EnclaveData) PubKeyHex() string { - return k.PubHex -} - -// PubKeyBytes returns the public key of the keyEnclave -func (k *EnclaveData) PubKeyBytes() []byte { - return k.PubBytes -} - -// Decrypt returns decrypted enclave data -func (k *EnclaveData) Decrypt(key []byte, encryptedData []byte) ([]byte, error) { - hashedKey := GetHashKey(key) - block, err := aes.NewCipher(hashedKey) - if err != nil { - return nil, err - } - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - // Decrypt the data using AES-GCM - plaintext, err := aesgcm.Open(nil, k.Nonce, encryptedData, nil) - if err != nil { - return nil, fmt.Errorf("decryption failed: %w", err) - } - return plaintext, nil -} - -// Encrypt returns encrypted enclave data -func (k *EnclaveData) Encrypt(key []byte) ([]byte, error) { - data, err := k.Marshal() - if err != nil { - return nil, fmt.Errorf("failed to serialize enclave: %w", err) - } - - hashedKey := GetHashKey(key) - block, err := aes.NewCipher(hashedKey) - if err != nil { - return nil, err - } - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - return aesgcm.Seal(nil, k.Nonce, data, nil), nil -} - -// IsValid returns true if the keyEnclave is valid -func (k *EnclaveData) IsValid() bool { - return k.ValShare != nil && k.UserShare != nil -} - -// Refresh returns a new keyEnclave -func (k *EnclaveData) Refresh() (Enclave, error) { - refreshFuncVal, err := GetAliceRefreshFunc(k) - if err != nil { - return nil, err - } - refreshFuncUser, err := GetBobRefreshFunc(k) - if err != nil { - return nil, err - } - return ExecuteRefresh(refreshFuncVal, refreshFuncUser, k.Curve) -} - -// Sign returns the signature of the data -func (k *EnclaveData) Sign(data []byte) ([]byte, error) { - userSign, err := GetBobSignFunc(k, data) - if err != nil { - return nil, err - } - valSign, err := GetAliceSignFunc(k, data) - if err != nil { - return nil, err - } - return ExecuteSigning(valSign, userSign) -} - -// Verify returns true if the signature is valid -func (k *EnclaveData) Verify(data []byte, sig []byte) (bool, error) { - edSig, err := DeserializeSignature(sig) - if err != nil { - return false, err - } - ePub, err := GetECDSAPoint(k.PubBytes) - if err != nil { - return false, err - } - pk := &ecdsa.PublicKey{ - Curve: ePub.Curve, - X: ePub.X, - Y: ePub.Y, - } - - // Hash the message using SHA3-256 - hash := sha3.New256() - hash.Write(data) - digest := hash.Sum(nil) - - return ecdsa.Verify(pk, digest, edSig.R, edSig.S), nil -} - -// Marshal returns the JSON encoding of keyEnclave -func (k *EnclaveData) Marshal() ([]byte, error) { - return json.Marshal(k) -} - -// Unmarshal unmarshals the JSON encoding of keyEnclave -func (k *EnclaveData) Unmarshal(data []byte) error { - if err := json.Unmarshal(data, k); err != nil { - return err - } - return nil -} diff --git a/internal/crypto/mpc/enclave_test.go b/internal/crypto/mpc/enclave_test.go deleted file mode 100644 index 39dc115..0000000 --- a/internal/crypto/mpc/enclave_test.go +++ /dev/null @@ -1,307 +0,0 @@ -package mpc - -import ( - "bytes" - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestEnclaveData_GetData(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the data - data := enclave.GetData() - require.NotNil(t, data) - - // Ensure the data is the same instance - assert.Equal(t, enclave, data.GetEnclave()) - - // Ensure the data is valid - assert.True(t, data.IsValid()) -} - -func TestEnclaveData_GetEnclave(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data) - - // Get the enclave back - returnedEnclave := data.GetEnclave() - require.NotNil(t, returnedEnclave) - - // Verify the returned enclave is the same - assert.Equal(t, enclave, returnedEnclave) -} - -func TestEnclaveData_GetPubPoint(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data) - - // Get the public point - pubPoint, err := data.GetPubPoint() - require.NoError(t, err) - require.NotNil(t, pubPoint) - - // Verify the public point's serialization matches the stored public bytes - pointBytes := pubPoint.ToAffineUncompressed() - assert.Equal(t, data.PubBytes, pointBytes) -} - -func TestEnclaveData_PubKeyHex(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data) - - // Get the public key hex - pubKeyHex := data.PubKeyHex() - require.NotEmpty(t, pubKeyHex) - - // Verify it's a valid hex string - _, err = hex.DecodeString(pubKeyHex) - require.NoError(t, err) - - // Verify it matches the stored PubHex - assert.Equal(t, data.PubHex, pubKeyHex) -} - -func TestEnclaveData_PubKeyBytes(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data) - - // Get the public key bytes - pubKeyBytes := data.PubKeyBytes() - require.NotEmpty(t, pubKeyBytes) - - // Verify it matches the stored PubBytes - assert.Equal(t, data.PubBytes, pubKeyBytes) -} - -func TestEnclaveData_EncryptDecrypt(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data) - - // Test key for encryption/decryption - testKey := []byte("test-key-12345678-test-key-123456") - - // Test encryption - encrypted, err := data.Encrypt(testKey) - require.NoError(t, err) - require.NotEmpty(t, encrypted) - - // Test decryption - decrypted, err := data.Decrypt(testKey, encrypted) - require.NoError(t, err) - require.NotEmpty(t, decrypted) - - // Serialize the original data for comparison - originalData, err := data.Marshal() - require.NoError(t, err) - - // Verify the decrypted data matches the original - assert.Equal(t, originalData, decrypted) - - // Test decryption with wrong key (should fail) - wrongKey := []byte("wrong-key-12345678-wrong-key-123456") - _, err = data.Decrypt(wrongKey, encrypted) - assert.Error(t, err, "Decryption with wrong key should fail") -} - -func TestEnclaveData_IsValid(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data) - - // Verify it's valid - assert.True(t, data.IsValid()) - - // Create an invalid enclave - invalidEnclave := &EnclaveData{ - PubHex: "invalid", - PubBytes: []byte("invalid"), - Nonce: []byte("nonce"), - Curve: K256Name, - } - - // Verify it's invalid - assert.False(t, invalidEnclave.IsValid()) -} - -func TestEnclaveData_RefreshAndSign(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the original public key - originalPubKeyHex := enclave.PubKeyHex() - originalPubKeyBytes := enclave.PubKeyBytes() - require.NotEmpty(t, originalPubKeyHex) - require.NotEmpty(t, originalPubKeyBytes) - - // Sign a message with the original enclave to verify it works - testMessage := []byte("test message before refresh") - originalSignature, err := enclave.Sign(testMessage) - require.NoError(t, err) - require.NotEmpty(t, originalSignature) - - // Verify the original signature - valid, err := enclave.Verify(testMessage, originalSignature) - require.NoError(t, err) - assert.True(t, valid, "Original signature should be valid") - - // Refresh the enclave - refreshedEnclave, err := enclave.Refresh() - require.NoError(t, err) - require.NotNil(t, refreshedEnclave) - - // CRITICAL TEST: The public key should remain the same after refresh - refreshedPubKeyHex := refreshedEnclave.PubKeyHex() - refreshedPubKeyBytes := refreshedEnclave.PubKeyBytes() - - assert.Equal(t, originalPubKeyHex, refreshedPubKeyHex, - "Public key hex should not change after refresh") - assert.Equal(t, originalPubKeyBytes, refreshedPubKeyBytes, - "Public key bytes should not change after refresh") - - // Verify the refreshed enclave is valid - assert.True(t, refreshedEnclave.IsValid(), "Refreshed enclave should be valid") - - // Test that the refreshed enclave can still sign messages - testMessage2 := []byte("test message after refresh") - refreshedSignature, err := refreshedEnclave.Sign(testMessage2) - require.NoError(t, err) - require.NotEmpty(t, refreshedSignature) - - // Verify the signature from the refreshed enclave with its own key - valid, err = refreshedEnclave.Verify(testMessage2, refreshedSignature) - require.NoError(t, err) - assert.True(t, valid, "Signature from refreshed enclave should be valid") - - // CRITICAL TEST: The original enclave should be able to verify the signature - // from the refreshed enclave since they have the same public key - valid, err = enclave.Verify(testMessage2, refreshedSignature) - require.NoError(t, err) - assert.True(t, valid, "Original enclave should be able to verify refreshed enclave's signature") - - // CRITICAL TEST: The refreshed enclave should be able to verify the signature - // from the original enclave since they have the same public key - valid, err = refreshedEnclave.Verify(testMessage, originalSignature) - require.NoError(t, err) - assert.True(t, valid, "Refreshed enclave should be able to verify original enclave's signature") - - // Test with wrong message (should fail) - wrongMessage := []byte("wrong message") - valid, err = refreshedEnclave.Verify(wrongMessage, refreshedSignature) - require.NoError(t, err) - assert.False(t, valid, "Wrong message verification should fail") -} - -func TestEnclaveData_MarshalUnmarshal(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Get the enclave data - data := enclave.GetData() - require.NotNil(t, data) - - // Marshal the enclave - encoded, err := data.Marshal() - require.NoError(t, err) - require.NotEmpty(t, encoded) - - // Create a new empty enclave - newEnclave := &EnclaveData{} - - // Unmarshal the encoded data - err = newEnclave.Unmarshal(encoded) - require.NoError(t, err) - - // Verify the unmarshaled enclave matches the original - assert.Equal(t, data.PubHex, newEnclave.PubHex) - assert.Equal(t, data.Curve, newEnclave.Curve) - assert.True(t, bytes.Equal(data.PubBytes, newEnclave.PubBytes)) - assert.True(t, bytes.Equal(data.Nonce, newEnclave.Nonce)) - assert.True(t, newEnclave.IsValid()) - - // Verify the public key matches - assert.Equal(t, data.PubKeyHex(), newEnclave.PubKeyHex()) -} - -func TestEnclaveData_Verify(t *testing.T) { - // Create a new enclave - enclave, err := NewEnclave() - require.NoError(t, err) - require.NotNil(t, enclave) - - // Sign a message - testMessage := []byte("test message") - signature, err := enclave.Sign(testMessage) - require.NoError(t, err) - require.NotEmpty(t, signature) - - // Verify the signature - valid, err := enclave.Verify(testMessage, signature) - require.NoError(t, err) - assert.True(t, valid) - - // Verify with wrong message - wrongMessage := []byte("wrong message") - valid, err = enclave.Verify(wrongMessage, signature) - require.NoError(t, err) - assert.False(t, valid) - - // Corrupt the signature - corruptedSig := make([]byte, len(signature)) - copy(corruptedSig, signature) - corruptedSig[0] ^= 0x01 // flip a bit - - // Verify with corrupted signature (should fail) - valid, err = enclave.Verify(testMessage, corruptedSig) - require.NoError(t, err) - assert.False(t, valid) - - // We don't need to manually create ECDSA signatures here - // as we already verified the Sign and Verify functions work together. - // This completes the verification of the enclave's signature functionality. -} diff --git a/internal/crypto/mpc/import.go b/internal/crypto/mpc/import.go deleted file mode 100644 index ccc116d..0000000 --- a/internal/crypto/mpc/import.go +++ /dev/null @@ -1,140 +0,0 @@ -package mpc - -import ( - "encoding/hex" - "errors" - "fmt" -) - -// ImportEnclave creates an Enclave instance from various import options. -// It prioritizes enclave bytes over keyshares if both are provided. -func ImportEnclave(options ...ImportOption) (Enclave, error) { - if len(options) == 0 { - return nil, errors.New("no import options provided") - } - - opts := Options{} - for _, opt := range options { - opts = opt(opts) - } - return opts.Apply() -} - -// Options is a struct that holds the import options -type Options struct { - valKeyshare Message - userKeyshare Message - enclaveBytes []byte - enclaveData *EnclaveData - initialShares bool - isEncrypted bool - secretKey []byte - curve CurveName -} - -// ImportOption is a function that modifies the import options -type ImportOption func(Options) Options - -// WithInitialShares creates an option to import an enclave from validator and user keyshares. -func WithInitialShares(valKeyshare Message, userKeyshare Message, curve CurveName) ImportOption { - return func(opts Options) Options { - opts.valKeyshare = valKeyshare - opts.userKeyshare = userKeyshare - opts.initialShares = true - opts.curve = curve - return opts - } -} - -// WithEncryptedData creates an option to import an enclave from encrypted data. -func WithEncryptedData(data []byte, key []byte) ImportOption { - return func(opts Options) Options { - opts.enclaveBytes = data - opts.initialShares = false - opts.isEncrypted = true - opts.secretKey = key - return opts - } -} - -// WithEnclaveData creates an option to import an enclave from a data struct. -func WithEnclaveData(data *EnclaveData) ImportOption { - return func(opts Options) Options { - opts.enclaveData = data - opts.initialShares = false - return opts - } -} - -// Apply applies the import options to create an Enclave instance. -func (opts Options) Apply() (Enclave, error) { - // Load from encrypted data if provided - if opts.isEncrypted { - if len(opts.enclaveBytes) == 0 { - return nil, errors.New("enclave bytes cannot be empty") - } - return RestoreEncryptedEnclave(opts.enclaveBytes, opts.secretKey) - } - // Generate from keyshares if provided - if opts.initialShares { - // Then try to build from keyshares - if opts.valKeyshare == nil { - return nil, errors.New("validator share cannot be nil") - } - if opts.userKeyshare == nil { - return nil, errors.New("user share cannot be nil") - } - return BuildEnclave(opts.valKeyshare, opts.userKeyshare, opts) - } - // Load from enclave data if provided - return RestoreEnclaveFromData(opts.enclaveData) -} - -// BuildEnclave creates a new enclave from validator and user keyshares. -func BuildEnclave(valShare, userShare Message, options Options) (Enclave, error) { - if valShare == nil { - return nil, errors.New("validator share cannot be nil") - } - if userShare == nil { - return nil, errors.New("user share cannot be nil") - } - - pubPoint, err := GetAlicePublicPoint(valShare) - if err != nil { - return nil, fmt.Errorf("failed to get public point: %w", err) - } - return &EnclaveData{ - PubBytes: pubPoint.ToAffineUncompressed(), - PubHex: hex.EncodeToString(pubPoint.ToAffineCompressed()), - ValShare: valShare, - UserShare: userShare, - Nonce: randNonce(), - Curve: options.curve, - }, nil -} - -// RestoreEnclaveFromData deserializes an enclave from its data struct. -func RestoreEnclaveFromData(data *EnclaveData) (Enclave, error) { - if data == nil { - return nil, errors.New("enclave data cannot be nil") - } - return data, nil -} - -// RestoreEncryptedEnclave decrypts an enclave from its binary representation. and key -func RestoreEncryptedEnclave(data []byte, key []byte) (Enclave, error) { - keyclave := &EnclaveData{} - err := keyclave.Unmarshal(data) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal enclave: %w", err) - } - decryptedData, err := keyclave.Decrypt(key, data) - if err != nil { - return nil, fmt.Errorf("failed to decrypt enclave: %w", err) - } - err = keyclave.Unmarshal(decryptedData) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal decrypted enclave: %w", err) - } - return keyclave, nil -} diff --git a/internal/crypto/mpc/protocol.go b/internal/crypto/mpc/protocol.go deleted file mode 100644 index 3ff91df..0000000 --- a/internal/crypto/mpc/protocol.go +++ /dev/null @@ -1,91 +0,0 @@ -package mpc - -import ( - "github.com/sonr-io/crypto/core/protocol" - "github.com/sonr-io/crypto/tecdsa/dklsv1" -) - -// NewEnclave generates a new MPC keyshare -func NewEnclave() (Enclave, error) { - curve := K256Name.Curve() - valKs := dklsv1.NewAliceDkg(curve, protocol.Version1) - userKs := dklsv1.NewBobDkg(curve, protocol.Version1) - aErr, bErr := RunProtocol(userKs, valKs) - if err := CheckIteratedErrors(aErr, bErr); err != nil { - return nil, err - } - valRes, err := valKs.Result(protocol.Version1) - if err != nil { - return nil, err - } - userRes, err := userKs.Result(protocol.Version1) - if err != nil { - return nil, err - } - return ImportEnclave(WithInitialShares(valRes, userRes, K256Name)) -} - -// ExecuteSigning runs the MPC signing protocol -func ExecuteSigning(signFuncVal SignFunc, signFuncUser SignFunc) ([]byte, error) { - aErr, bErr := RunProtocol(signFuncVal, signFuncUser) - if err := CheckIteratedErrors(aErr, bErr); err != nil { - return nil, err - } - out, err := signFuncUser.Result(protocol.Version1) - if err != nil { - return nil, err - } - s, err := dklsv1.DecodeSignature(out) - if err != nil { - return nil, err - } - sig, err := SerializeSignature(s) - if err != nil { - return nil, err - } - return sig, nil -} - -// ExecuteRefresh runs the MPC refresh protocol -func ExecuteRefresh( - refreshFuncVal RefreshFunc, - refreshFuncUser RefreshFunc, - curve CurveName, -) (Enclave, error) { - aErr, bErr := RunProtocol(refreshFuncVal, refreshFuncUser) - if err := CheckIteratedErrors(aErr, bErr); err != nil { - return nil, err - } - valRefreshResult, err := refreshFuncVal.Result(protocol.Version1) - if err != nil { - return nil, err - } - userRefreshResult, err := refreshFuncUser.Result(protocol.Version1) - if err != nil { - return nil, err - } - return ImportEnclave(WithInitialShares(valRefreshResult, userRefreshResult, curve)) -} - -// RunProtocol runs the MPC protocol -func RunProtocol(firstParty protocol.Iterator, secondParty protocol.Iterator) (error, error) { - var ( - message *protocol.Message - aErr error - bErr error - ) - - for aErr != protocol.ErrProtocolFinished || bErr != protocol.ErrProtocolFinished { - // Crank each protocol forward one iteration - message, bErr = firstParty.Next(message) - if bErr != nil && bErr != protocol.ErrProtocolFinished { - return nil, bErr - } - - message, aErr = secondParty.Next(message) - if aErr != nil && aErr != protocol.ErrProtocolFinished { - return aErr, nil - } - } - return aErr, bErr -} diff --git a/internal/crypto/mpc/utils.go b/internal/crypto/mpc/utils.go index ef0c188..b0407b6 100644 --- a/internal/crypto/mpc/utils.go +++ b/internal/crypto/mpc/utils.go @@ -1,97 +1,12 @@ package mpc import ( - "crypto/aes" - "crypto/cipher" - "errors" "fmt" "math/big" "github.com/sonr-io/crypto/core/curves" - "github.com/sonr-io/crypto/core/protocol" - "github.com/sonr-io/crypto/tecdsa/dklsv1" - "golang.org/x/crypto/sha3" ) -func CheckIteratedErrors(aErr, bErr error) error { - if aErr == protocol.ErrProtocolFinished && bErr == protocol.ErrProtocolFinished { - return nil - } - if aErr != protocol.ErrProtocolFinished { - return aErr - } - if bErr != protocol.ErrProtocolFinished { - return bErr - } - return nil -} - -func GetHashKey(key []byte) []byte { - hash := sha3.New256() - hash.Write(key) - return hash.Sum(nil)[:32] // Use first 32 bytes of hash -} - -func DecryptKeyshare(msg []byte, key []byte, nonce []byte) ([]byte, error) { - hashedKey := GetHashKey(key) - block, err := aes.NewCipher(hashedKey) - if err != nil { - return nil, err - } - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - plaintext, err := aesgcm.Open(nil, nonce, msg, nil) - if err != nil { - return nil, err - } - return plaintext, nil -} - -func EncryptKeyshare(msg Message, key []byte, nonce []byte) ([]byte, error) { - hashedKey := GetHashKey(key) - msgBytes, err := protocol.EncodeMessage(msg) - if err != nil { - return nil, err - } - block, err := aes.NewCipher(hashedKey) - if err != nil { - return nil, err - } - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - ciphertext := aesgcm.Seal(nil, nonce, []byte(msgBytes), nil) - return ciphertext, nil -} - -func GetAliceOut(msg *protocol.Message) (AliceOut, error) { - return dklsv1.DecodeAliceDkgResult(msg) -} - -func GetAlicePublicPoint(msg *protocol.Message) (Point, error) { - out, err := dklsv1.DecodeAliceDkgResult(msg) - if err != nil { - return nil, err - } - return out.PublicKey, nil -} - -func GetBobOut(msg *protocol.Message) (BobOut, error) { - return dklsv1.DecodeBobDkgResult(msg) -} - -func GetBobPubPoint(msg *protocol.Message) (Point, error) { - out, err := dklsv1.DecodeBobDkgResult(msg) - if err != nil { - return nil, err - } - return out.PublicKey, nil -} - -// GetECDSAPoint builds an elliptic curve point from a compressed byte slice func GetECDSAPoint(pubKey []byte) (*curves.EcPoint, error) { crv := curves.K256() x := new(big.Int).SetBytes(pubKey[1:33]) @@ -103,28 +18,6 @@ func GetECDSAPoint(pubKey []byte) (*curves.EcPoint, error) { return &curves.EcPoint{X: x, Y: y, Curve: ecCurve}, nil } -func SerializeSignature(sig *curves.EcdsaSignature) ([]byte, error) { - if sig == nil { - return nil, errors.New("nil signature") - } - - rBytes := sig.R.Bytes() - sBytes := sig.S.Bytes() - - // Ensure both components are 32 bytes - rPadded := make([]byte, 32) - sPadded := make([]byte, 32) - copy(rPadded[32-len(rBytes):], rBytes) - copy(sPadded[32-len(sBytes):], sBytes) - - // Concatenate R and S - result := make([]byte, 64) - copy(result[0:32], rPadded) - copy(result[32:64], sPadded) - - return result, nil -} - func DeserializeSignature(sigBytes []byte) (*curves.EcdsaSignature, error) { if len(sigBytes) != 64 { return nil, fmt.Errorf("invalid signature length: expected 64 bytes, got %d", len(sigBytes)) @@ -138,23 +31,3 @@ func DeserializeSignature(sigBytes []byte) (*curves.EcdsaSignature, error) { S: s, }, nil } - -func GetAliceSignFunc(k *EnclaveData, bz []byte) (SignFunc, error) { - curve := k.Curve.Curve() - return dklsv1.NewAliceSign(curve, sha3.New256(), bz, k.ValShare, protocol.Version1) -} - -func GetAliceRefreshFunc(k *EnclaveData) (RefreshFunc, error) { - curve := k.Curve.Curve() - return dklsv1.NewAliceRefresh(curve, k.ValShare, protocol.Version1) -} - -func GetBobSignFunc(k *EnclaveData, bz []byte) (SignFunc, error) { - curve := curves.K256() - return dklsv1.NewBobSign(curve, sha3.New256(), bz, k.UserShare, protocol.Version1) -} - -func GetBobRefreshFunc(k *EnclaveData) (RefreshFunc, error) { - curve := curves.K256() - return dklsv1.NewBobRefresh(curve, k.UserShare, protocol.Version1) -} diff --git a/internal/keybase/functions.go b/internal/keybase/functions.go index 4ed8d64..becaa16 100644 --- a/internal/keybase/functions.go +++ b/internal/keybase/functions.go @@ -2,20 +2,19 @@ package keybase import ( "context" + "crypto/rand" "encoding/hex" "fmt" "enclave/internal/crypto/mpc" "github.com/ncruces/go-sqlite3" - "github.com/sonr-io/crypto/core/protocol" ) -// RegisterMPCFunctions registers custom SQLite functions for MPC operations: -// - mpc_sign(enclave_id TEXT, data BLOB) -> BLOB -// - mpc_verify(public_key_hex TEXT, data BLOB, signature BLOB) -> INTEGER (0 or 1) -// - mpc_refresh(enclave_id TEXT) -> TEXT func RegisterMPCFunctions(conn *sqlite3.Conn) error { + if err := registerGenerateFunction(conn); err != nil { + return fmt.Errorf("register mpc_generate: %w", err) + } if err := registerSignFunction(conn); err != nil { return fmt.Errorf("register mpc_sign: %w", err) } @@ -28,6 +27,48 @@ func RegisterMPCFunctions(conn *sqlite3.Conn) error { return nil } +func registerGenerateFunction(conn *sqlite3.Conn) error { + return conn.CreateFunction("mpc_generate", 0, 0, func(ctx sqlite3.Context, args ...sqlite3.Value) { + enc, err := mpc.NewSimpleEnclave() + if err != nil { + ctx.ResultError(fmt.Errorf("mpc_generate: %w", err)) + return + } + + idBytes := make([]byte, 8) + rand.Read(idBytes) + enclaveID := fmt.Sprintf("enc_%x", idBytes) + + kb := Get() + if kb == nil { + ctx.ResultError(fmt.Errorf("mpc_generate: keybase not initialized")) + return + } + + am, err := NewActionManager() + if err != nil { + ctx.ResultError(fmt.Errorf("mpc_generate: action manager: %w", err)) + return + } + + _, err = am.CreateEnclave(context.Background(), NewEnclaveInput{ + EnclaveID: enclaveID, + PublicKeyHex: enc.PubKeyHex(), + PublicKey: enc.PubKeyBytes(), + ValShare: enc.GetShare1(), + UserShare: enc.GetShare2(), + Nonce: enc.GetNonce(), + Curve: string(enc.GetCurve()), + }) + if err != nil { + ctx.ResultError(fmt.Errorf("mpc_generate: create enclave: %w", err)) + return + } + + ctx.ResultText(enclaveID) + }) +} + func registerSignFunction(conn *sqlite3.Conn) error { return conn.CreateFunction("mpc_sign", 2, sqlite3.DETERMINISTIC, func(ctx sqlite3.Context, args ...sqlite3.Value) { if len(args) != 2 { @@ -47,7 +88,7 @@ func registerSignFunction(conn *sqlite3.Conn) error { return } - enclave, err := loadEnclaveFromDB(enclaveID) + enclave, err := loadSimpleEnclaveFromDB(enclaveID) if err != nil { ctx.ResultError(fmt.Errorf("mpc_sign: %w", err)) return @@ -120,7 +161,7 @@ func registerRefreshFunction(conn *sqlite3.Conn) error { return } - enclave, err := loadEnclaveFromDB(enclaveID) + enclave, err := loadSimpleEnclaveFromDB(enclaveID) if err != nil { ctx.ResultError(fmt.Errorf("mpc_refresh: %w", err)) return @@ -132,7 +173,7 @@ func registerRefreshFunction(conn *sqlite3.Conn) error { return } - if err := updateEnclaveInDB(enclaveID, newEnclave); err != nil { + if err := updateSimpleEnclaveInDB(enclaveID, newEnclave); err != nil { ctx.ResultError(fmt.Errorf("mpc_refresh: update failed: %w", err)) return } @@ -141,7 +182,7 @@ func registerRefreshFunction(conn *sqlite3.Conn) error { }) } -func loadEnclaveFromDB(enclaveID string) (mpc.Enclave, error) { +func loadSimpleEnclaveFromDB(enclaveID string) (*mpc.SimpleEnclave, error) { kb := Get() if kb == nil { return nil, fmt.Errorf("keybase not initialized") @@ -152,55 +193,31 @@ func loadEnclaveFromDB(enclaveID string) (mpc.Enclave, error) { return nil, fmt.Errorf("enclave not found: %w", err) } - valShare, err := protocol.DecodeMessage(string(dbEnc.ValShare)) - if err != nil { - return nil, fmt.Errorf("decode val_share: %w", err) - } - - userShare, err := protocol.DecodeMessage(string(dbEnc.UserShare)) - if err != nil { - return nil, fmt.Errorf("decode user_share: %w", err) - } - - return &mpc.EnclaveData{ - PubHex: dbEnc.PublicKeyHex, - PubBytes: dbEnc.PublicKey, - ValShare: valShare, - UserShare: userShare, - Nonce: dbEnc.Nonce, - Curve: mpc.CurveName(dbEnc.Curve), - }, nil + return mpc.ImportSimpleEnclave( + dbEnc.PublicKey, + dbEnc.ValShare, + dbEnc.UserShare, + dbEnc.Nonce, + mpc.CurveName(dbEnc.Curve), + ) } -func updateEnclaveInDB(enclaveID string, enclave mpc.Enclave) error { +func updateSimpleEnclaveInDB(enclaveID string, enclave *mpc.SimpleEnclave) error { kb := Get() if kb == nil { return fmt.Errorf("keybase not initialized") } - data := enclave.GetData() - - valShareStr, err := protocol.EncodeMessage(data.ValShare) - if err != nil { - return fmt.Errorf("encode val_share: %w", err) - } - - userShareStr, err := protocol.EncodeMessage(data.UserShare) - if err != nil { - return fmt.Errorf("encode user_share: %w", err) - } - dbEnc, err := kb.queries.GetEnclaveByID(context.Background(), enclaveID) if err != nil { return fmt.Errorf("get enclave: %w", err) } - // Raw SQL required: SQLC-generated code lacks full enclave update query _, err = kb.db.ExecContext(context.Background(), ` UPDATE mpc_enclaves SET val_share = ?, user_share = ?, nonce = ?, rotated_at = datetime('now') WHERE id = ? - `, []byte(valShareStr), []byte(userShareStr), data.Nonce, dbEnc.ID) + `, enclave.GetShare1(), enclave.GetShare2(), enclave.GetNonce(), dbEnc.ID) return err }