mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 15:31:38 +00:00
No commit suggestions generated
This commit is contained in:
+499
@@ -0,0 +1,499 @@
|
||||
# MPC (Multi-Party Computation) Cryptographic Library
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user