No commit suggestions generated

This commit is contained in:
Prad Nukala
2025-10-09 15:10:39 -04:00
commit a934caa7d3
323 changed files with 98121 additions and 0 deletions
+499
View File
@@ -0,0 +1,499 @@
# MPC (Multi-Party Computation) Cryptographic Library
![Go](https://img.shields.io/badge/Go-1.24+-green)
![MPC](https://img.shields.io/badge/MPC-Threshold_Signing-blue)
![Encryption](https://img.shields.io/badge/Encryption-AES--GCM-red)
![ECDSA](https://img.shields.io/badge/Curve-secp256k1-yellow)
A comprehensive Go implementation of Multi-Party Computation (MPC) primitives for secure distributed cryptography. This package provides threshold signing, encrypted key management, and secure keyshare operations for decentralized applications.
## Features
-**Threshold Cryptography** - 2-of-2 MPC key generation and signing
-**Secure Enclaves** - Encrypted keyshare storage and management
-**Multiple Curves** - Support for secp256k1, P-256, Ed25519, BLS12-381, and more
-**Key Refresh** - Proactive security through keyshare rotation
-**ECDSA Signing** - Distributed signature generation with SHA3-256
-**Encrypted Export/Import** - Secure enclave serialization with AES-GCM
-**UCAN Integration** - MPC-based JWT signing for User-Controlled Authorization Networks
## Architecture
The package is built around the concept of secure **Enclaves** that manage distributed keyshares:
```
┌─────────────────────────────────────────────────────────┐
│ MPC Enclave │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Alice Share │ │ Bob Share │ ←── Threshold 2/2 │
│ │ (Validator) │ │ (User) │ │
│ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ • Distributed Key Generation (DKG) │
│ • Threshold Signing (2-of-2) │
│ • Key Refresh (Proactive Security) │
│ • Encrypted Storage (AES-GCM) │
└─────────────────────────────────────────────────────────┘
```
## Quick Start
### Installation
```bash
go get github.com/sonr-io/sonr/crypto/mpc
```
### Basic Usage
#### Creating a New MPC Enclave
```go
package main
import (
"fmt"
"github.com/sonr-io/sonr/crypto/mpc"
)
func main() {
// Generate a new MPC enclave with distributed keyshares
enclave, err := mpc.NewEnclave()
if err != nil {
panic(err)
}
// Get the public key
pubKeyHex := enclave.PubKeyHex()
fmt.Printf("Public Key: %s\n", pubKeyHex)
// Verify the enclave is valid
if enclave.IsValid() {
fmt.Println("✅ Enclave successfully created!")
}
}
```
#### Signing and Verification
```go
// Sign data using distributed MPC protocol
message := []byte("Hello, distributed world!")
signature, err := enclave.Sign(message)
if err != nil {
panic(err)
}
// Verify the signature
isValid, err := enclave.Verify(message, signature)
if err != nil {
panic(err)
}
fmt.Printf("Signature valid: %t\n", isValid)
```
#### Secure Export and Import
```go
// Export enclave with encryption
secretKey := []byte("my-super-secret-key-32-bytes-long")
encryptedData, err := enclave.Encrypt(secretKey)
if err != nil {
panic(err)
}
// Import from encrypted data
restoredEnclave, err := mpc.ImportEnclave(
mpc.WithEncryptedData(encryptedData, secretKey),
)
if err != nil {
panic(err)
}
fmt.Printf("Restored public key: %s\n", restoredEnclave.PubKeyHex())
```
## Core Concepts
### Enclaves
An **Enclave** represents a secure MPC keyshare environment that manages distributed cryptographic operations:
```go
type Enclave interface {
// Key Management
PubKeyHex() string // Get public key as hex string
PubKeyBytes() []byte // Get public key as bytes
IsValid() bool // Check if enclave has valid keyshares
// Cryptographic Operations
Sign(data []byte) ([]byte, error) // Threshold signing
Verify(data []byte, sig []byte) (bool, error) // Signature verification
Refresh() (Enclave, error) // Proactive key refresh
// Secure Storage
Encrypt(key []byte) ([]byte, error) // Export encrypted
Decrypt(key []byte, data []byte) ([]byte, error) // Import encrypted
// Serialization
Marshal() ([]byte, error) // JSON serialization
Unmarshal(data []byte) error // JSON deserialization
// Data Access
GetData() *EnclaveData // Access enclave internals
GetEnclave() Enclave // Self-reference
}
```
### Multi-Party Computation Protocol
The package implements a 2-of-2 threshold scheme:
1. **Alice (Validator)** - Server-side keyshare
2. **Bob (User)** - Client-side keyshare
Both parties must participate in:
- **Distributed Key Generation (DKG)** - Creates shared public key
- **Threshold Signing** - Generates valid signatures cooperatively
- **Key Refresh** - Rotates keyshares while preserving public key
### Supported Curves
The package supports multiple elliptic curves:
```go
type CurveName string
const (
K256Name CurveName = "secp256k1" // Bitcoin/Ethereum
P256Name CurveName = "P-256" // NIST P-256
ED25519Name CurveName = "ed25519" // EdDSA
BLS12381G1Name CurveName = "BLS12381G1" // BLS12-381 G1
BLS12381G2Name CurveName = "BLS12381G2" // BLS12-381 G2
// ... more curves supported
)
```
## Advanced Usage
### Custom Import Options
The package provides flexible import mechanisms:
```go
// Import from initial keyshares (after DKG)
enclave, err := mpc.ImportEnclave(
mpc.WithInitialShares(validatorShare, userShare, mpc.K256Name),
)
// Import from existing enclave data
enclave, err := mpc.ImportEnclave(
mpc.WithEnclaveData(enclaveData),
)
// Import from encrypted backup
enclave, err := mpc.ImportEnclave(
mpc.WithEncryptedData(encryptedBytes, secretKey),
)
```
### Key Refresh for Proactive Security
```go
// Refresh keyshares while keeping the same public key
refreshedEnclave, err := enclave.Refresh()
if err != nil {
panic(err)
}
// Public key remains the same
fmt.Printf("Original: %s\n", enclave.PubKeyHex())
fmt.Printf("Refreshed: %s\n", refreshedEnclave.PubKeyHex())
// Both should be identical!
// But the enclave now has fresh keyshares
// This provides forward secrecy against key compromise
```
### Standalone Verification
```go
// Verify signatures without the full enclave
pubKeyBytes := enclave.PubKeyBytes()
isValid, err := mpc.VerifyWithPubKey(pubKeyBytes, message, signature)
if err != nil {
panic(err)
}
```
### UCAN Integration
The package includes MPC-based JWT signing for UCAN tokens:
```go
import "github.com/sonr-io/sonr/crypto/mpc/spec"
// Create MPC-backed UCAN token source
// (Implementation details in spec package)
keyshareSource := spec.KeyshareSource{
// ... MPC enclave integration
}
// Use with UCAN token creation
token, err := keyshareSource.NewOriginToken(
"did:key:audience",
attenuations,
facts,
notBefore,
expires,
)
```
## Security Features
### Encryption
All encrypted operations use **AES-GCM** with **SHA3-256** key derivation:
```go
// Secure key derivation
func GetHashKey(key []byte) []byte {
hash := sha3.New256()
hash.Write(key)
return hash.Sum(nil)[:32] // 256-bit key
}
```
### Threshold Security
- **2-of-2 threshold** - Both parties required for operations
- **No single point of failure** - Neither party alone can sign
- **Proactive refresh** - Regular keyshare rotation without changing public key
- **Forward secrecy** - Old keyshares cannot be used after refresh
### Cryptographic Primitives
- **ECDSA Signing** with **SHA3-256** message hashing
- **AES-GCM** encryption with 12-byte nonces
- **Secure random nonce generation**
- **Multiple curve support** for different use cases
## Public API Reference
### Core Functions
```go
// Generate new MPC enclave
func NewEnclave() (Enclave, error)
// Import enclave from various sources
func ImportEnclave(options ...ImportOption) (Enclave, error)
// Execute distributed signing protocol
func ExecuteSigning(signFuncVal SignFunc, signFuncUser SignFunc) ([]byte, error)
// Execute keyshare refresh protocol
func ExecuteRefresh(refreshFuncVal RefreshFunc, refreshFuncUser RefreshFunc,
curve CurveName) (Enclave, error)
// Standalone signature verification
func VerifyWithPubKey(pubKeyCompressed []byte, data []byte, sig []byte) (bool, error)
```
### Import Options
```go
type ImportOption func(Options) Options
// Create from initial DKG results
func WithInitialShares(valKeyshare Message, userKeyshare Message,
curve CurveName) ImportOption
// Create from encrypted backup
func WithEncryptedData(data []byte, key []byte) ImportOption
// Create from existing data structure
func WithEnclaveData(data *EnclaveData) ImportOption
```
### EnclaveData Structure
```go
type EnclaveData struct {
PubHex string `json:"pub_hex"` // Compressed public key (hex)
PubBytes []byte `json:"pub_bytes"` // Uncompressed public key
ValShare Message `json:"val_share"` // Alice (validator) keyshare
UserShare Message `json:"user_share"`// Bob (user) keyshare
Nonce []byte `json:"nonce"` // Encryption nonce
Curve CurveName `json:"curve"` // Elliptic curve name
}
```
### Protocol Types
```go
type Message *protocol.Message // MPC protocol message
type Signature *curves.EcdsaSignature // ECDSA signature
type RefreshFunc interface{ protocol.Iterator } // Key refresh protocol
type SignFunc interface{ protocol.Iterator } // Signing protocol
type Point curves.Point // Elliptic curve point
```
### Utility Functions
```go
// Cryptographic utilities
func GetHashKey(key []byte) []byte
func SerializeSignature(sig *curves.EcdsaSignature) ([]byte, error)
func DeserializeSignature(sigBytes []byte) (*curves.EcdsaSignature, error)
// Key conversion utilities
func GetECDSAPoint(pubKey []byte) (*curves.EcPoint, error)
// Protocol error handling
func CheckIteratedErrors(aErr, bErr error) error
```
## Error Handling
The package provides comprehensive error handling:
```go
// Common error patterns
enclave, err := mpc.NewEnclave()
if err != nil {
// Handle DKG failure
log.Fatalf("Failed to generate enclave: %v", err)
}
signature, err := enclave.Sign(data)
if err != nil {
// Handle signing protocol failure
log.Fatalf("Failed to sign: %v", err)
}
// Validation errors
if !enclave.IsValid() {
log.Fatal("Enclave has invalid keyshares")
}
```
## Performance Considerations
### Memory Usage
- **Minimal footprint** - Only active keyshares kept in memory
- **Efficient serialization** - JSON-based with compression
- **Secure cleanup** - Sensitive data cleared after use
### Network Communication
- **Minimal rounds** - Optimized protocol with few message exchanges
- **Small messages** - Compact protocol message format
- **Stateless operations** - No persistent connections required
### Cryptographic Performance
- **Hardware acceleration** - Leverages optimized curve implementations
- **Efficient hashing** - SHA3-256 with minimal overhead
- **Fast verification** - Public key operations optimized
## Testing
The package includes comprehensive tests:
```bash
# Run all tests
go test -v ./crypto/mpc
# Run specific test suites
go test -v ./crypto/mpc -run TestEnclaveData
go test -v ./crypto/mpc -run TestKeyShareGeneration
go test -v ./crypto/mpc -run TestEnclaveOperations
# Run with race detection
go test -race ./crypto/mpc
# Generate coverage report
go test -cover ./crypto/mpc
```
## Use Cases
### Decentralized Identity
- **DID key management** - Secure distributed identity keys
- **Threshold signing** - Multi-party authorization for identity operations
- **Key recovery** - Distributed backup and restore mechanisms
### Cryptocurrency Wallets
- **Multi-signature wallets** - True threshold custody solutions
- **Exchange security** - Hot wallet protection with distributed keys
- **Institutional custody** - Compliance-friendly key management
### Blockchain Infrastructure
- **Validator signing** - Secure consensus participation
- **Cross-chain bridges** - Multi-party custody of bridged assets
- **DAO governance** - Distributed decision-making mechanisms
### Enterprise Applications
- **Document signing** - Distributed digital signatures
- **API authentication** - Threshold-based service authentication
- **Secure communication** - End-to-end encrypted messaging
## Dependencies
- **Core Cryptography**: `github.com/sonr-io/sonr/crypto/core/curves`
- **Protocol Framework**: `github.com/sonr-io/sonr/crypto/core/protocol`
- **Threshold ECDSA**: `github.com/sonr-io/sonr/crypto/tecdsa/dklsv1`
- **UCAN Integration**: `github.com/sonr-io/sonr/crypto/ucan`
- **Standard Crypto**: `golang.org/x/crypto/sha3`
- **JWT Support**: `github.com/golang-jwt/jwt`
## Security Considerations
### Threat Model
The package is designed to protect against:
- **Key compromise** - Distributed keyshares prevent single points of failure
- **Insider threats** - No single party can perform operations alone
- **Network attacks** - Protocol messages are cryptographically protected
- **Side-channel attacks** - Secure implementations of cryptographic primitives
### Best Practices
1. **Regular key refresh** - Rotate keyshares periodically
2. **Secure communication** - Use TLS for protocol message exchange
3. **Access controls** - Implement proper authentication for MPC operations
4. **Audit logging** - Log all cryptographic operations
5. **Backup strategies** - Securely store encrypted enclave exports
### Limitations
- **2-of-2 threshold only** - Currently supports only 2-party protocols
- **Network dependency** - Requires communication between parties
- **No byzantine fault tolerance** - Assumes honest-but-curious adversaries
## Contributing
We welcome contributions! Please ensure:
1. **Security first** - All cryptographic code must be carefully reviewed
2. **Comprehensive testing** - Include unit tests and integration tests
3. **Documentation** - Document all public APIs and security assumptions
4. **Performance** - Benchmark critical cryptographic operations
5. **Compatibility** - Maintain backward compatibility with existing enclaves
## License
This project follows the same license as the main Sonr project.
---
**⚠️ Security Notice**: This is cryptographic software. While extensively tested, it should be used with appropriate security measures and understanding of the underlying protocols. For production deployments, consider additional security audits and operational security measures.
+110
View File
@@ -0,0 +1,110 @@
// Package mpc implements the Sonr MPC protocol
package mpc
import (
"crypto/rand"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/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"
)
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
}
+178
View File
@@ -0,0 +1,178 @@
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",
)
})
}
+158
View File
@@ -0,0 +1,158 @@
package mpc
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"encoding/json"
"fmt"
"github.com/sonr-io/sonr/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
}
+307
View File
@@ -0,0 +1,307 @@
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.
}
+140
View File
@@ -0,0 +1,140 @@
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
}
+91
View File
@@ -0,0 +1,91 @@
package mpc
import (
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/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
}
+116
View File
@@ -0,0 +1,116 @@
package spec
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/sonr-io/sonr/crypto/mpc"
)
// MPCSigningMethod implements the SigningMethod interface for MPC-based signing
type MPCSigningMethod struct {
Name string
enclave mpc.Enclave
}
// NewJWTSigningMethod creates a new MPC signing method with the given enclave
func NewJWTSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
return &MPCSigningMethod{
Name: name,
enclave: enclave,
}
}
// WithEnclave sets the enclave for an existing signing method
func (m *MPCSigningMethod) WithEnclave(enclave mpc.Enclave) *MPCSigningMethod {
return &MPCSigningMethod{
Name: m.Name,
enclave: enclave,
}
}
// NewMPCSigningMethod is an alias for NewJWTSigningMethod for compatibility
func NewMPCSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
return NewJWTSigningMethod(name, enclave)
}
// Alg returns the signing method's name
func (m *MPCSigningMethod) Alg() string {
return m.Name
}
// Verify verifies the signature using the MPC public key
func (m *MPCSigningMethod) Verify(signingString string, signature []byte, key any) error {
// Check if enclave is available
if m.enclave == nil {
return fmt.Errorf("MPC enclave not available for signature verification")
}
// Decode the signature
sig, err := base64.RawURLEncoding.DecodeString(string(signature))
if err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}
// Hash the signing string using SHA-256
hasher := sha256.New()
hasher.Write([]byte(signingString))
digest := hasher.Sum(nil)
// Use MPC enclave to verify signature
valid, err := m.enclave.Verify(digest, sig)
if err != nil {
return fmt.Errorf("failed to verify signature: %w", err)
}
if !valid {
return fmt.Errorf("signature verification failed")
}
return nil
}
// Sign signs the data using MPC
func (m *MPCSigningMethod) Sign(signingString string, key any) ([]byte, error) {
// Check if enclave is available
if m.enclave == nil {
return nil, fmt.Errorf("MPC enclave not available for signing")
}
// Hash the signing string using SHA-256
hasher := sha256.New()
hasher.Write([]byte(signingString))
digest := hasher.Sum(nil)
// Use MPC enclave to sign the digest
sig, err := m.enclave.Sign(digest)
if err != nil {
return nil, fmt.Errorf("failed to sign with MPC: %w", err)
}
// Encode the signature as base64url
encoded := base64.RawURLEncoding.EncodeToString(sig)
return []byte(encoded), nil
}
func init() {
// Register the MPC signing method factory
jwt.RegisterSigningMethod("MPC256", func() jwt.SigningMethod {
// This factory creates a new instance without enclave
// The enclave will be provided when creating tokens
return &MPCSigningMethod{
Name: "MPC256",
}
})
}
// RegisterMPCMethod registers an MPC signing method for the given algorithm name
func RegisterMPCMethod(alg string) {
jwt.RegisterSigningMethod(alg, func() jwt.SigningMethod {
return &MPCSigningMethod{
Name: alg,
}
})
}
+305
View File
@@ -0,0 +1,305 @@
package spec
import (
"fmt"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/sonr-io/sonr/crypto/keys"
"github.com/sonr-io/sonr/crypto/mpc"
"lukechampine.com/blake3"
)
// KeyshareSource provides MPC-based UCAN token creation and validation
type KeyshareSource interface {
Address() string
Issuer() string
ChainCode() ([]byte, error)
OriginToken() (*Token, error)
SignData(data []byte) ([]byte, error)
VerifyData(data []byte, sig []byte) (bool, error)
Enclave() mpc.Enclave
// UCAN token creation methods
NewOriginToken(
audienceDID string,
att []Attenuation,
fct []Fact,
notBefore, expires time.Time,
) (*Token, error)
NewAttenuatedToken(
parent *Token,
audienceDID string,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error)
}
// NewSource creates a new MPC-based keyshare source from an enclave
func NewSource(enclave mpc.Enclave) (KeyshareSource, error) {
if !enclave.IsValid() {
return nil, fmt.Errorf("invalid MPC enclave provided")
}
pubKeyBytes := enclave.PubKeyBytes()
issuerDID, addr, err := getIssuerDIDFromBytes(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to derive issuer DID: %w", err)
}
return &mpcKeyshareSource{
enclave: enclave,
issuerDID: issuerDID,
addr: addr,
}, nil
}
// mpcKeyshareSource implements KeyshareSource using MPC enclave
type mpcKeyshareSource struct {
enclave mpc.Enclave
issuerDID string
addr string
}
// Address returns the address derived from the enclave public key
func (k *mpcKeyshareSource) Address() string {
return k.addr
}
// Issuer returns the DID of the issuer derived from the enclave public key
func (k *mpcKeyshareSource) Issuer() string {
return k.issuerDID
}
// Enclave returns the underlying MPC enclave
func (k *mpcKeyshareSource) Enclave() mpc.Enclave {
return k.enclave
}
// ChainCode derives a deterministic chain code from the enclave
func (k *mpcKeyshareSource) ChainCode() ([]byte, error) {
// Sign the address to create a deterministic chain code
sig, err := k.SignData([]byte(k.addr))
if err != nil {
return nil, fmt.Errorf("failed to sign address for chain code: %w", err)
}
// Hash the signature to create a 32-byte chain code
hash := blake3.Sum256(sig)
return hash[:32], nil
}
// OriginToken creates a default origin token with basic capabilities
func (k *mpcKeyshareSource) OriginToken() (*Token, error) {
// Create basic capability for the MPC keyshare
resource := &SimpleResource{
Scheme: "mpc",
Value: k.addr,
URI: fmt.Sprintf("mpc://%s", k.addr),
}
capability := &SimpleCapability{Action: "sign"}
attenuation := Attenuation{
Capability: capability,
Resource: resource,
}
// Create token with no expiration for origin token
zero := time.Time{}
return k.NewOriginToken(k.issuerDID, []Attenuation{attenuation}, nil, zero, zero)
}
// SignData signs data using the MPC enclave
func (k *mpcKeyshareSource) SignData(data []byte) ([]byte, error) {
if !k.enclave.IsValid() {
return nil, fmt.Errorf("enclave is not valid")
}
return k.enclave.Sign(data)
}
// VerifyData verifies a signature using the MPC enclave
func (k *mpcKeyshareSource) VerifyData(data []byte, sig []byte) (bool, error) {
if !k.enclave.IsValid() {
return false, fmt.Errorf("enclave is not valid")
}
return k.enclave.Verify(data, sig)
}
// NewOriginToken creates a new UCAN origin token using MPC signing
func (k *mpcKeyshareSource) NewOriginToken(
audienceDID string,
att []Attenuation,
fct []Fact,
notBefore, expires time.Time,
) (*Token, error) {
return k.newToken(audienceDID, nil, att, fct, notBefore, expires)
}
// NewAttenuatedToken creates a new attenuated UCAN token using MPC signing
func (k *mpcKeyshareSource) NewAttenuatedToken(
parent *Token,
audienceDID string,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error) {
// Validate that new attenuations are more restrictive than parent
if !isAttenuationSubset(att, parent.Attenuations) {
return nil, fmt.Errorf("scope of ucan attenuations must be less than its parent")
}
// Add parent as proof
proofs := []Proof{}
if parent.Raw != "" {
proofs = append(proofs, Proof(parent.Raw))
}
proofs = append(proofs, parent.Proofs...)
return k.newToken(audienceDID, proofs, att, fct, nbf, exp)
}
// newToken creates a new UCAN token with MPC signing
func (k *mpcKeyshareSource) newToken(
audienceDID string,
proofs []Proof,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error) {
// Validate audience DID
if !isValidDID(audienceDID) {
return nil, fmt.Errorf("invalid audience DID: %s", audienceDID)
}
// Create JWT with MPC signing method
t := jwt.New(NewJWTSigningMethod("MPC256", k.enclave))
// Set UCAN version header
t.Header[UCANVersionKey] = UCANVersion
var (
nbfUnix int64
expUnix int64
)
if !nbf.IsZero() {
nbfUnix = nbf.Unix()
}
if !exp.IsZero() {
expUnix = exp.Unix()
}
// Convert attenuations to claim format
attClaims := make([]map[string]any, len(att))
for i, a := range att {
attClaims[i] = map[string]any{
"can": a.Capability.GetActions(),
"with": a.Resource.GetURI(),
}
}
// Convert proofs to strings
proofStrings := make([]string, len(proofs))
for i, proof := range proofs {
proofStrings[i] = string(proof)
}
// Convert facts to any slice
factData := make([]any, len(fct))
for i, fact := range fct {
factData[i] = string(fact.Data)
}
// Set claims
claims := jwt.MapClaims{
"iss": k.issuerDID,
"aud": audienceDID,
"att": attClaims,
}
if nbfUnix > 0 {
claims["nbf"] = nbfUnix
}
if expUnix > 0 {
claims["exp"] = expUnix
}
if len(proofStrings) > 0 {
claims["prf"] = proofStrings
}
if len(factData) > 0 {
claims["fct"] = factData
}
t.Claims = claims
// Sign the token using MPC enclave
tokenString, err := t.SignedString(nil)
if err != nil {
return nil, fmt.Errorf("failed to sign token: %w", err)
}
return &Token{
Raw: tokenString,
Issuer: k.issuerDID,
Audience: audienceDID,
ExpiresAt: expUnix,
NotBefore: nbfUnix,
Attenuations: att,
Proofs: proofs,
Facts: fct,
}, nil
}
// isAttenuationSubset checks if child attenuations are a subset of parent attenuations
func isAttenuationSubset(child, parent []Attenuation) bool {
for _, childAtt := range child {
if !containsAttenuation(parent, childAtt) {
return false
}
}
return true
}
// containsAttenuation checks if the parent list contains an equivalent attenuation
func containsAttenuation(parent []Attenuation, att Attenuation) bool {
for _, parentAtt := range parent {
if parentAtt.Resource.Matches(att.Resource) &&
parentAtt.Capability.Contains(att.Capability) {
return true
}
}
return false
}
// isValidDID validates DID format
func isValidDID(did string) bool {
return did != "" && len(did) > 5 && strings.HasPrefix(did, "did:")
}
// getIssuerDIDFromBytes creates an issuer DID and address from public key bytes
func getIssuerDIDFromBytes(pubKeyBytes []byte) (string, string, error) {
// Convert MPC public key bytes to libp2p crypto.PubKey
pubKey, err := crypto.UnmarshalSecp256k1PublicKey(pubKeyBytes)
if err != nil {
return "", "", fmt.Errorf("failed to unmarshal secp256k1 key: %w", err)
}
// Create DID using the crypto/keys package
did, err := keys.NewDID(pubKey)
if err != nil {
return "", "", fmt.Errorf("failed to create DID: %w", err)
}
didStr := did.String()
// Generate address from DID (simplified implementation)
address := fmt.Sprintf("addr_%x", pubKeyBytes[:8])
return didStr, address, nil
}
+125
View File
@@ -0,0 +1,125 @@
package spec
import (
"encoding/json"
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/types/bech32"
)
// Token represents a UCAN JWT token with parsed claims
type Token struct {
Raw string `json:"raw"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Attenuations []Attenuation `json:"att"`
Proofs []Proof `json:"prf,omitempty"`
Facts []Fact `json:"fct,omitempty"`
}
// Attenuation represents a UCAN capability attenuation
type Attenuation struct {
Capability Capability `json:"can"`
Resource Resource `json:"with"`
}
// Proof represents a UCAN delegation proof (either JWT or CID)
type Proof string
// Fact represents arbitrary facts in UCAN tokens
type Fact struct {
Data json.RawMessage `json:"data"`
}
// Capability defines what actions can be performed
type Capability interface {
GetActions() []string
Grants(abilities []string) bool
Contains(other Capability) bool
String() string
}
// Resource defines what resource the capability applies to
type Resource interface {
GetScheme() string
GetValue() string
GetURI() string
Matches(other Resource) bool
}
// SimpleCapability implements Capability for single actions
type SimpleCapability struct {
Action string `json:"action"`
}
func (c *SimpleCapability) GetActions() []string { return []string{c.Action} }
func (c *SimpleCapability) Grants(abilities []string) bool {
return len(abilities) == 1 && c.Action == abilities[0]
}
func (c *SimpleCapability) Contains(
other Capability,
) bool {
return c.Action == other.GetActions()[0]
}
func (c *SimpleCapability) String() string { return c.Action }
// SimpleResource implements Resource for basic URI resources
type SimpleResource struct {
Scheme string `json:"scheme"`
Value string `json:"value"`
URI string `json:"uri"`
}
func (r *SimpleResource) GetScheme() string { return r.Scheme }
func (r *SimpleResource) GetValue() string { return r.Value }
func (r *SimpleResource) GetURI() string { return r.URI }
func (r *SimpleResource) Matches(other Resource) bool { return r.URI == other.GetURI() }
// UCAN constants
const (
UCANVersion = "0.9.0"
UCANVersionKey = "ucv"
PrfKey = "prf"
FctKey = "fct"
AttKey = "att"
CapKey = "cap"
)
// CreateSimpleAttenuation creates a basic attenuation
func CreateSimpleAttenuation(action, resourceURI string) Attenuation {
return Attenuation{
Capability: &SimpleCapability{Action: action},
Resource: parseResourceURI(resourceURI),
}
}
// parseResourceURI creates a Resource from URI string
func parseResourceURI(uri string) Resource {
parts := strings.SplitN(uri, "://", 2)
if len(parts) != 2 {
return &SimpleResource{
Scheme: "unknown",
Value: uri,
URI: uri,
}
}
return &SimpleResource{
Scheme: parts[0],
Value: parts[1],
URI: uri,
}
}
// getIssuerDIDFromBytes creates an issuer DID and address from public key bytes (alternative implementation)
func getIssuerDIDFromBytesAlt(pubKeyBytes []byte) (string, string, error) {
addr, err := bech32.ConvertAndEncode("idx", pubKeyBytes)
if err != nil {
return "", "", fmt.Errorf("failed to encode address: %w", err)
}
return fmt.Sprintf("did:sonr:%s", addr), addr, nil
}
+160
View File
@@ -0,0 +1,160 @@
package mpc
import (
"crypto/aes"
"crypto/cipher"
"errors"
"fmt"
"math/big"
"github.com/sonr-io/sonr/crypto/core/curves"
"github.com/sonr-io/sonr/crypto/core/protocol"
"github.com/sonr-io/sonr/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])
y := new(big.Int).SetBytes(pubKey[33:])
ecCurve, err := crv.ToEllipticCurve()
if err != nil {
return nil, fmt.Errorf("error converting curve: %v", err)
}
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))
}
r := new(big.Int).SetBytes(sigBytes[:32])
s := new(big.Int).SetBytes(sigBytes[32:])
return &curves.EcdsaSignature{
R: r,
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)
}
+29
View File
@@ -0,0 +1,29 @@
package mpc
import (
"crypto/ecdsa"
"golang.org/x/crypto/sha3"
)
func VerifyWithPubKey(pubKeyCompressed []byte, data []byte, sig []byte) (bool, error) {
edSig, err := DeserializeSignature(sig)
if err != nil {
return false, err
}
ePub, err := GetECDSAPoint(pubKeyCompressed)
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
}