mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
Add threshold signature and ring signature packages
- cggmp21: CGGMP21 threshold ECDSA protocol implementation - mpc: Multi-party computation account management - aggregated: Signature aggregation managers for BLS, Corona, CGGMP21 - corona: Linkable ring signature implementation These packages were previously in node/crypto and are now properly located in the dedicated crypto repository.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package aggregated
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/crypto/bls"
|
||||
"github.com/luxfi/crypto/corona"
|
||||
"github.com/luxfi/crypto/cggmp21"
|
||||
)
|
||||
|
||||
// BLSManager manages BLS signature operations
|
||||
type BLSManager struct {
|
||||
log log.Logger
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewBLSManager creates a new BLS manager
|
||||
func NewBLSManager(log log.Logger) *BLSManager {
|
||||
return &BLSManager{
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateKeyPair generates a new BLS key pair
|
||||
func (m *BLSManager) CreateKeyPair() (*bls.SecretKey, *bls.PublicKey, error) {
|
||||
sk, err := bls.NewSecretKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pk := sk.PublicKey()
|
||||
return sk, pk, nil
|
||||
}
|
||||
|
||||
// Sign creates a BLS signature
|
||||
func (m *BLSManager) Sign(sk *bls.SecretKey, message []byte) (*bls.Signature, error) {
|
||||
sig, err := sk.Sign(message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// CoronaManager manages Corona signature operations
|
||||
type CoronaManager struct {
|
||||
log log.Logger
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCoronaManager creates a new Corona manager
|
||||
func NewCoronaManager(log log.Logger) *CoronaManager {
|
||||
return &CoronaManager{
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateKeyPair generates a new Corona key pair
|
||||
func (m *CoronaManager) CreateKeyPair() (*corona.PrivateKey, *corona.PublicKey, error) {
|
||||
factory := &corona.Factory{}
|
||||
privKey, err := factory.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pubKey, err := factory.ToPublicKey(privKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return privKey, pubKey, nil
|
||||
}
|
||||
|
||||
// Sign creates a Corona ring signature
|
||||
func (m *CoronaManager) Sign(
|
||||
sk *corona.PrivateKey,
|
||||
message []byte,
|
||||
ring []*corona.PublicKey,
|
||||
) (*corona.RingSignature, error) {
|
||||
return sk.Sign(message, ring)
|
||||
}
|
||||
|
||||
// CGGMP21Manager manages CGGMP21 threshold signature operations
|
||||
type CGGMP21Manager struct {
|
||||
log log.Logger
|
||||
parties map[int]*cggmp21.Party
|
||||
config *cggmp21.Config
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCGGMP21Manager creates a new CGGMP21 manager
|
||||
func NewCGGMP21Manager(log log.Logger) *CGGMP21Manager {
|
||||
return &CGGMP21Manager{
|
||||
log: log,
|
||||
parties: make(map[int]*cggmp21.Party),
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeParty creates a new CGGMP21 party
|
||||
func (m *CGGMP21Manager) InitializeParty(
|
||||
partyID ids.NodeID,
|
||||
index int,
|
||||
config *cggmp21.Config,
|
||||
) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
party, err := cggmp21.NewParty(partyID, index, config, m.log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.parties[index] = party
|
||||
m.config = config
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetParty retrieves a CGGMP21 party by index
|
||||
func (m *CGGMP21Manager) GetParty(index int) (*cggmp21.Party, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
party, exists := m.parties[index]
|
||||
if !exists {
|
||||
return nil, errors.New("party not found")
|
||||
}
|
||||
|
||||
return party, nil
|
||||
}
|
||||
|
||||
// FeeCollector manages fee collection for signature operations
|
||||
type FeeCollector struct {
|
||||
collectedFees map[SignatureType]uint64
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewFeeCollector creates a new fee collector
|
||||
func NewFeeCollector() FeeCollector {
|
||||
return FeeCollector{
|
||||
collectedFees: make(map[SignatureType]uint64),
|
||||
}
|
||||
}
|
||||
|
||||
// CollectFee records a fee collection
|
||||
func (fc *FeeCollector) CollectFee(sigType SignatureType, amount uint64) {
|
||||
fc.mu.Lock()
|
||||
defer fc.mu.Unlock()
|
||||
|
||||
fc.collectedFees[sigType] += amount
|
||||
}
|
||||
|
||||
// GetCollectedFees returns total collected fees by type
|
||||
func (fc *FeeCollector) GetCollectedFees() map[SignatureType]uint64 {
|
||||
fc.mu.RLock()
|
||||
defer fc.mu.RUnlock()
|
||||
|
||||
fees := make(map[SignatureType]uint64)
|
||||
for k, v := range fc.collectedFees {
|
||||
fees[k] = v
|
||||
}
|
||||
|
||||
return fees
|
||||
}
|
||||
|
||||
// GetTotalFees returns the total of all collected fees
|
||||
func (fc *FeeCollector) GetTotalFees() uint64 {
|
||||
fc.mu.RLock()
|
||||
defer fc.mu.RUnlock()
|
||||
|
||||
var total uint64
|
||||
for _, amount := range fc.collectedFees {
|
||||
total += amount
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
// ResetFees resets the fee collection
|
||||
func (fc *FeeCollector) ResetFees() map[SignatureType]uint64 {
|
||||
fc.mu.Lock()
|
||||
defer fc.mu.Unlock()
|
||||
|
||||
oldFees := fc.collectedFees
|
||||
fc.collectedFees = make(map[SignatureType]uint64)
|
||||
|
||||
return oldFees
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package aggregated
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/crypto/corona"
|
||||
"github.com/luxfi/crypto/bls"
|
||||
)
|
||||
|
||||
// SignatureType represents the type of aggregated signature
|
||||
type SignatureType uint8
|
||||
|
||||
const (
|
||||
SignatureTypeBLS SignatureType = iota
|
||||
SignatureTypeCorona
|
||||
SignatureTypeCGGMP21
|
||||
)
|
||||
|
||||
// SignatureConfig contains configuration for signature aggregation
|
||||
type SignatureConfig struct {
|
||||
// Network-wide signature type preference
|
||||
PreferredType SignatureType `json:"preferredType"`
|
||||
|
||||
// Enable specific signature types
|
||||
EnableBLS bool `json:"enableBLS"`
|
||||
EnableCorona bool `json:"enableCorona"`
|
||||
EnableCGGMP21 bool `json:"enableCGGMP21"`
|
||||
|
||||
// Fee configuration (in nLUX - nano LUX)
|
||||
BLSFee uint64 `json:"blsFee"` // 0 = free
|
||||
CoronaFee uint64 `json:"coronaFee"` // Premium for enhanced privacy
|
||||
CGGMP21Fee uint64 `json:"cggmp21Fee"` // Premium for threshold signatures
|
||||
|
||||
// Performance settings
|
||||
ParallelAggregation bool `json:"parallelAggregation"`
|
||||
MaxSignersPerRound int `json:"maxSignersPerRound"`
|
||||
|
||||
// Security settings
|
||||
MinSigners int `json:"minSigners"`
|
||||
ThresholdRatio float64 `json:"thresholdRatio"` // e.g., 0.67 for 2/3
|
||||
}
|
||||
|
||||
// AggregatedSignature represents an aggregated signature with metadata
|
||||
type AggregatedSignature struct {
|
||||
Type SignatureType `json:"type"`
|
||||
Signature []byte `json:"signature"`
|
||||
SignerIDs []ids.NodeID `json:"signerIds,omitempty"`
|
||||
SignerCount int `json:"signerCount"`
|
||||
RingPublicKeys []*corona.PublicKey `json:"ringPublicKeys,omitempty"` // For Corona
|
||||
AggregateKey []byte `json:"aggregateKey,omitempty"` // For BLS
|
||||
Threshold int `json:"threshold,omitempty"` // For CGGMP21
|
||||
TotalFee uint64 `json:"totalFee"`
|
||||
}
|
||||
|
||||
// SignatureAggregator manages network-wide signature aggregation
|
||||
type SignatureAggregator struct {
|
||||
config SignatureConfig
|
||||
log log.Logger
|
||||
|
||||
// Signature managers
|
||||
blsManager *BLSManager
|
||||
coronaManager *CoronaManager
|
||||
cggmpManager *CGGMP21Manager
|
||||
|
||||
// Active aggregation sessions
|
||||
sessions map[string]*AggregationSession
|
||||
|
||||
// Fee collector
|
||||
feeCollector FeeCollector
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// AggregationSession represents an active signature aggregation
|
||||
type AggregationSession struct {
|
||||
SessionID string
|
||||
Message []byte
|
||||
SignatureType SignatureType
|
||||
|
||||
// Collected signatures
|
||||
BLSSignatures []*bls.Signature
|
||||
BLSPublicKeys []*bls.PublicKey
|
||||
CoronaSignatures []*corona.RingSignature
|
||||
CoronaRing []*corona.PublicKey
|
||||
|
||||
// Signers
|
||||
Signers map[ids.NodeID]bool
|
||||
SignerCount int
|
||||
|
||||
// Status
|
||||
StartTime int64
|
||||
Completed bool
|
||||
Result *AggregatedSignature
|
||||
}
|
||||
|
||||
// NewSignatureAggregator creates a new signature aggregator
|
||||
func NewSignatureAggregator(config SignatureConfig, log log.Logger) (*SignatureAggregator, error) {
|
||||
sa := &SignatureAggregator{
|
||||
config: config,
|
||||
log: log,
|
||||
sessions: make(map[string]*AggregationSession),
|
||||
}
|
||||
|
||||
// Initialize signature managers based on config
|
||||
if config.EnableBLS {
|
||||
sa.blsManager = NewBLSManager(log)
|
||||
}
|
||||
|
||||
if config.EnableCorona {
|
||||
sa.coronaManager = NewCoronaManager(log)
|
||||
}
|
||||
|
||||
if config.EnableCGGMP21 {
|
||||
sa.cggmpManager = NewCGGMP21Manager(log)
|
||||
}
|
||||
|
||||
sa.feeCollector = NewFeeCollector()
|
||||
|
||||
log.Info("Signature aggregator initialized",
|
||||
log.Uint8("preferredType", uint8(config.PreferredType)),
|
||||
log.Bool("blsEnabled", config.EnableBLS),
|
||||
log.Bool("coronaEnabled", config.EnableCorona),
|
||||
log.Bool("cggmp21Enabled", config.EnableCGGMP21),
|
||||
log.Uint64("blsFee", config.BLSFee),
|
||||
log.Uint64("coronaFee", config.CoronaFee),
|
||||
)
|
||||
|
||||
return sa, nil
|
||||
}
|
||||
|
||||
// StartAggregation starts a new signature aggregation session
|
||||
func (sa *SignatureAggregator) StartAggregation(
|
||||
sessionID string,
|
||||
message []byte,
|
||||
sigType SignatureType,
|
||||
expectedSigners int,
|
||||
) error {
|
||||
sa.mu.Lock()
|
||||
defer sa.mu.Unlock()
|
||||
|
||||
if _, exists := sa.sessions[sessionID]; exists {
|
||||
return errors.New("session already exists")
|
||||
}
|
||||
|
||||
// Validate signature type is enabled
|
||||
switch sigType {
|
||||
case SignatureTypeBLS:
|
||||
if !sa.config.EnableBLS {
|
||||
return errors.New("BLS signatures not enabled")
|
||||
}
|
||||
case SignatureTypeCorona:
|
||||
if !sa.config.EnableCorona {
|
||||
return errors.New("Corona signatures not enabled")
|
||||
}
|
||||
case SignatureTypeCGGMP21:
|
||||
if !sa.config.EnableCGGMP21 {
|
||||
return errors.New("CGGMP21 signatures not enabled")
|
||||
}
|
||||
default:
|
||||
return errors.New("unknown signature type")
|
||||
}
|
||||
|
||||
session := &AggregationSession{
|
||||
SessionID: sessionID,
|
||||
Message: message,
|
||||
SignatureType: sigType,
|
||||
Signers: make(map[ids.NodeID]bool),
|
||||
StartTime: getCurrentTime(),
|
||||
}
|
||||
|
||||
sa.sessions[sessionID] = session
|
||||
|
||||
sa.log.Debug("Started aggregation session",
|
||||
log.String("sessionID", sessionID),
|
||||
log.Uint8("type", uint8(sigType)),
|
||||
log.Int("expectedSigners", expectedSigners),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSignature adds a signature to an aggregation session
|
||||
func (sa *SignatureAggregator) AddSignature(
|
||||
sessionID string,
|
||||
signerID ids.NodeID,
|
||||
signature []byte,
|
||||
publicKey []byte,
|
||||
) error {
|
||||
sa.mu.Lock()
|
||||
defer sa.mu.Unlock()
|
||||
|
||||
session, exists := sa.sessions[sessionID]
|
||||
if !exists {
|
||||
return errors.New("session not found")
|
||||
}
|
||||
|
||||
if session.Completed {
|
||||
return errors.New("session already completed")
|
||||
}
|
||||
|
||||
// Check if signer already contributed
|
||||
if session.Signers[signerID] {
|
||||
return errors.New("signer already contributed")
|
||||
}
|
||||
|
||||
// Add signature based on type
|
||||
switch session.SignatureType {
|
||||
case SignatureTypeBLS:
|
||||
return sa.addBLSSignature(session, signerID, signature, publicKey)
|
||||
|
||||
case SignatureTypeCorona:
|
||||
return sa.addCoronaSignature(session, signerID, signature, publicKey)
|
||||
|
||||
case SignatureTypeCGGMP21:
|
||||
return errors.New("CGGMP21 uses different protocol flow")
|
||||
|
||||
default:
|
||||
return errors.New("unknown signature type")
|
||||
}
|
||||
}
|
||||
|
||||
// addBLSSignature adds a BLS signature to the session
|
||||
func (sa *SignatureAggregator) addBLSSignature(
|
||||
session *AggregationSession,
|
||||
signerID ids.NodeID,
|
||||
signature []byte,
|
||||
publicKey []byte,
|
||||
) error {
|
||||
// Parse BLS signature and public key
|
||||
sig, err := bls.SignatureFromBytes(signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid BLS signature: %w", err)
|
||||
}
|
||||
|
||||
pk, err := bls.PublicKeyFromCompressedBytes(publicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid BLS public key: %w", err)
|
||||
}
|
||||
|
||||
// Verify individual signature
|
||||
valid := bls.Verify(pk, sig, session.Message)
|
||||
if !valid {
|
||||
return fmt.Errorf("BLS signature verification failed")
|
||||
}
|
||||
|
||||
// Add to session
|
||||
session.BLSSignatures = append(session.BLSSignatures, sig)
|
||||
session.BLSPublicKeys = append(session.BLSPublicKeys, pk)
|
||||
session.Signers[signerID] = true
|
||||
session.SignerCount++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addCoronaSignature adds a Corona signature to the session
|
||||
func (sa *SignatureAggregator) addCoronaSignature(
|
||||
session *AggregationSession,
|
||||
signerID ids.NodeID,
|
||||
signature []byte,
|
||||
publicKey []byte,
|
||||
) error {
|
||||
// Parse Corona signature
|
||||
// For now, create a placeholder signature structure
|
||||
// In production, implement proper deserialization
|
||||
ringSig := &corona.RingSignature{
|
||||
C0: new(big.Int).SetBytes(signature[:32]),
|
||||
S: make([]*big.Int, corona.DefaultRingSize),
|
||||
KeyImage: &corona.Point{X: big.NewInt(0), Y: big.NewInt(0)},
|
||||
}
|
||||
|
||||
// Parse public key
|
||||
// For now, create from bytes
|
||||
// In production, implement proper deserialization
|
||||
pk := &corona.PublicKey{
|
||||
Point: &corona.Point{
|
||||
X: new(big.Int).SetBytes(publicKey[:32]),
|
||||
Y: new(big.Int).SetBytes(publicKey[32:64]),
|
||||
},
|
||||
}
|
||||
|
||||
// Add to ring if not already present
|
||||
inRing := false
|
||||
for _, ringPK := range session.CoronaRing {
|
||||
if ringPK.Equal(pk) {
|
||||
inRing = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inRing {
|
||||
session.CoronaRing = append(session.CoronaRing, pk)
|
||||
}
|
||||
|
||||
// Store signature
|
||||
session.CoronaSignatures = append(session.CoronaSignatures, ringSig)
|
||||
session.Signers[signerID] = true
|
||||
session.SignerCount++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FinalizeAggregation completes the aggregation and returns the result
|
||||
func (sa *SignatureAggregator) FinalizeAggregation(
|
||||
sessionID string,
|
||||
requiredSigners int,
|
||||
) (*AggregatedSignature, error) {
|
||||
sa.mu.Lock()
|
||||
defer sa.mu.Unlock()
|
||||
|
||||
session, exists := sa.sessions[sessionID]
|
||||
if !exists {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
|
||||
if session.Completed {
|
||||
return session.Result, nil
|
||||
}
|
||||
|
||||
// Check minimum signers
|
||||
if session.SignerCount < requiredSigners {
|
||||
return nil, fmt.Errorf("insufficient signers: %d < %d", session.SignerCount, requiredSigners)
|
||||
}
|
||||
|
||||
var result *AggregatedSignature
|
||||
var err error
|
||||
|
||||
switch session.SignatureType {
|
||||
case SignatureTypeBLS:
|
||||
result, err = sa.finalizeBLS(session)
|
||||
|
||||
case SignatureTypeCorona:
|
||||
result, err = sa.finalizeCorona(session)
|
||||
|
||||
default:
|
||||
return nil, errors.New("unknown signature type")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Calculate total fee
|
||||
result.TotalFee = sa.calculateFee(session.SignatureType, session.SignerCount)
|
||||
|
||||
// Mark session as completed
|
||||
session.Completed = true
|
||||
session.Result = result
|
||||
|
||||
sa.log.Info("Finalized aggregation",
|
||||
log.String("sessionID", sessionID),
|
||||
log.Uint8("type", uint8(session.SignatureType)),
|
||||
log.Int("signers", session.SignerCount),
|
||||
log.Uint64("totalFee", result.TotalFee),
|
||||
)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// finalizeBLS aggregates BLS signatures
|
||||
func (sa *SignatureAggregator) finalizeBLS(session *AggregationSession) (*AggregatedSignature, error) {
|
||||
if len(session.BLSSignatures) == 0 {
|
||||
return nil, errors.New("no BLS signatures to aggregate")
|
||||
}
|
||||
|
||||
// Aggregate signatures
|
||||
aggSig, err := bls.AggregateSignatures(session.BLSSignatures)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("BLS aggregation failed: %w", err)
|
||||
}
|
||||
|
||||
// Aggregate public keys
|
||||
aggPK, err := bls.AggregatePublicKeys(session.BLSPublicKeys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("BLS public key aggregation failed: %w", err)
|
||||
}
|
||||
|
||||
// Verify aggregate signature
|
||||
valid := bls.Verify(aggPK, aggSig, session.Message)
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("aggregate signature verification failed")
|
||||
}
|
||||
|
||||
sigBytes := bls.SignatureToBytes(aggSig)
|
||||
pkBytes := bls.PublicKeyToCompressedBytes(aggPK)
|
||||
|
||||
// Extract signer IDs
|
||||
signerIDs := make([]ids.NodeID, 0, len(session.Signers))
|
||||
for id := range session.Signers {
|
||||
signerIDs = append(signerIDs, id)
|
||||
}
|
||||
|
||||
return &AggregatedSignature{
|
||||
Type: SignatureTypeBLS,
|
||||
Signature: sigBytes,
|
||||
SignerIDs: signerIDs,
|
||||
SignerCount: session.SignerCount,
|
||||
AggregateKey: pkBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// finalizeCorona creates a linkable ring signature
|
||||
func (sa *SignatureAggregator) finalizeCorona(session *AggregationSession) (*AggregatedSignature, error) {
|
||||
if len(session.CoronaSignatures) == 0 {
|
||||
return nil, errors.New("no Corona signatures collected")
|
||||
}
|
||||
|
||||
// For Corona, we use the first signature as the aggregated result
|
||||
// since ring signatures provide anonymity within the ring
|
||||
ringSig := session.CoronaSignatures[0]
|
||||
|
||||
// Serialize signature (simplified for now)
|
||||
// In production, implement proper serialization
|
||||
sigBytes := make([]byte, 0)
|
||||
sigBytes = append(sigBytes, ringSig.C0.Bytes()...)
|
||||
for _, s := range ringSig.S {
|
||||
if s != nil {
|
||||
sigBytes = append(sigBytes, s.Bytes()...)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify against the full ring
|
||||
valid := ringSig.Verify(session.Message)
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("ring signature verification failed")
|
||||
}
|
||||
|
||||
return &AggregatedSignature{
|
||||
Type: SignatureTypeCorona,
|
||||
Signature: sigBytes,
|
||||
SignerCount: len(session.CoronaRing), // Ring size, not actual signers
|
||||
RingPublicKeys: session.CoronaRing,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// calculateFee calculates the total fee for signature aggregation
|
||||
func (sa *SignatureAggregator) calculateFee(sigType SignatureType, signerCount int) uint64 {
|
||||
var feePerSigner uint64
|
||||
|
||||
switch sigType {
|
||||
case SignatureTypeBLS:
|
||||
feePerSigner = sa.config.BLSFee // 0 for free
|
||||
case SignatureTypeCorona:
|
||||
feePerSigner = sa.config.CoronaFee // Premium fee
|
||||
case SignatureTypeCGGMP21:
|
||||
feePerSigner = sa.config.CGGMP21Fee // Premium fee
|
||||
default:
|
||||
feePerSigner = 0
|
||||
}
|
||||
|
||||
return feePerSigner * uint64(signerCount)
|
||||
}
|
||||
|
||||
// VerifyAggregatedSignature verifies an aggregated signature
|
||||
func (sa *SignatureAggregator) VerifyAggregatedSignature(
|
||||
message []byte,
|
||||
aggSig *AggregatedSignature,
|
||||
) error {
|
||||
switch aggSig.Type {
|
||||
case SignatureTypeBLS:
|
||||
return sa.verifyBLSAggregate(message, aggSig)
|
||||
|
||||
case SignatureTypeCorona:
|
||||
return sa.verifyCoronaAggregate(message, aggSig)
|
||||
|
||||
case SignatureTypeCGGMP21:
|
||||
return errors.New("CGGMP21 verification not implemented")
|
||||
|
||||
default:
|
||||
return errors.New("unknown signature type")
|
||||
}
|
||||
}
|
||||
|
||||
// verifyBLSAggregate verifies a BLS aggregate signature
|
||||
func (sa *SignatureAggregator) verifyBLSAggregate(message []byte, aggSig *AggregatedSignature) error {
|
||||
sig, err := bls.SignatureFromBytes(aggSig.Signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pk, err := bls.PublicKeyFromCompressedBytes(aggSig.AggregateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valid := bls.Verify(pk, sig, message)
|
||||
if !valid {
|
||||
return errors.New("BLS signature verification failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyCoronaAggregate verifies a Corona ring signature
|
||||
func (sa *SignatureAggregator) verifyCoronaAggregate(message []byte, aggSig *AggregatedSignature) error {
|
||||
// For now, simplified verification
|
||||
// In production, deserialize and verify properly
|
||||
if len(aggSig.Signature) < 256 {
|
||||
return errors.New("invalid ring signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSessionStatus returns the status of an aggregation session
|
||||
func (sa *SignatureAggregator) GetSessionStatus(sessionID string) (map[string]interface{}, error) {
|
||||
sa.mu.RLock()
|
||||
defer sa.mu.RUnlock()
|
||||
|
||||
session, exists := sa.sessions[sessionID]
|
||||
if !exists {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
|
||||
status := map[string]interface{}{
|
||||
"sessionID": session.SessionID,
|
||||
"signatureType": session.SignatureType,
|
||||
"signerCount": session.SignerCount,
|
||||
"completed": session.Completed,
|
||||
"startTime": session.StartTime,
|
||||
}
|
||||
|
||||
if session.Completed && session.Result != nil {
|
||||
status["totalFee"] = session.Result.TotalFee
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// Cleanup removes old sessions
|
||||
func (sa *SignatureAggregator) Cleanup(maxAge int64) {
|
||||
sa.mu.Lock()
|
||||
defer sa.mu.Unlock()
|
||||
|
||||
currentTime := getCurrentTime()
|
||||
|
||||
for sessionID, session := range sa.sessions {
|
||||
if currentTime-session.StartTime > maxAge {
|
||||
delete(sa.sessions, sessionID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function
|
||||
func getCurrentTime() int64 {
|
||||
return time.Now().Unix()
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package cggmp21 implements the CGGMP21 threshold ECDSA protocol
|
||||
// Reference: "UC Non-Interactive, Proactive, Threshold ECDSA with Identifiable Aborts"
|
||||
// by Canetti, Gennaro, Goldfeder, Makriyannis, and Peled (2021)
|
||||
package cggmp21
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
)
|
||||
|
||||
// Signature represents an ECDSA signature
|
||||
type Signature struct {
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}
|
||||
|
||||
// Config contains CGGMP21 protocol configuration
|
||||
type Config struct {
|
||||
Threshold int // t: Threshold (t+1 parties needed to sign)
|
||||
TotalParties int // n: Total number of parties
|
||||
Curve elliptic.Curve
|
||||
SessionTimeout int64 // Timeout for protocol rounds
|
||||
}
|
||||
|
||||
// Party represents a participant in the CGGMP21 protocol
|
||||
type Party struct {
|
||||
ID ids.NodeID
|
||||
Index int
|
||||
Config *Config
|
||||
|
||||
// Key material
|
||||
Xi *big.Int // Secret key share
|
||||
PublicKey *ecdsa.PublicKey // Group public key
|
||||
PublicShares map[int]*big.Int // Public key shares from all parties
|
||||
|
||||
// Paillier keys for ZK proofs
|
||||
PaillierSK *PaillierPrivateKey
|
||||
PaillierPKs map[int]*PaillierPublicKey
|
||||
|
||||
// Session state
|
||||
sessions map[string]*SigningSession
|
||||
|
||||
log log.Logger
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// SigningSession represents an active signing session
|
||||
type SigningSession struct {
|
||||
SessionID string
|
||||
Message []byte
|
||||
MessageHash *big.Int
|
||||
|
||||
// Round 1: Commitment
|
||||
Ki *big.Int // k_i (nonce share)
|
||||
Gammai *big.Int // gamma_i (random mask)
|
||||
CommitmentSent bool
|
||||
Commitments map[int][]byte // Received commitments
|
||||
|
||||
// Round 2: Reveal
|
||||
RevealsSent bool
|
||||
GammaShares map[int]*big.Int // gamma_j values
|
||||
BigGammaShares map[int]*ECPoint // [gamma_j]G points
|
||||
|
||||
// Round 3: Multiplication
|
||||
DeltaShare *big.Int // delta_i = k_i * gamma_i
|
||||
ChiShare *big.Int // chi_i = x_i * k_i
|
||||
BigDeltaShares map[int]*ECPoint // [delta_j]G points
|
||||
|
||||
// Round 4: Opening
|
||||
Deltas map[int]*big.Int // delta_j values
|
||||
BigRx *ECPoint // R_x point
|
||||
|
||||
// Final signature
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
|
||||
// Abort handling
|
||||
AbortingParties []int
|
||||
}
|
||||
|
||||
// ECPoint represents an elliptic curve point
|
||||
type ECPoint struct {
|
||||
X, Y *big.Int
|
||||
}
|
||||
|
||||
// NewParty creates a new CGGMP21 party
|
||||
func NewParty(id ids.NodeID, index int, config *Config, log log.Logger) (*Party, error) {
|
||||
if index < 0 || index >= config.TotalParties {
|
||||
return nil, errors.New("invalid party index")
|
||||
}
|
||||
|
||||
// Generate Paillier keypair for ZK proofs
|
||||
paillierSK, paillierPK, err := GeneratePaillierKeyPair(2048)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate Paillier keys: %w", err)
|
||||
}
|
||||
|
||||
return &Party{
|
||||
ID: id,
|
||||
Index: index,
|
||||
Config: config,
|
||||
PublicShares: make(map[int]*big.Int),
|
||||
PaillierSK: paillierSK,
|
||||
PaillierPKs: map[int]*PaillierPublicKey{index: paillierPK},
|
||||
sessions: make(map[string]*SigningSession),
|
||||
log: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// KeyGen performs distributed key generation
|
||||
func (p *Party) KeyGen(parties []int) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
// Generate secret share
|
||||
xi, err := rand.Int(rand.Reader, p.Config.Curve.Params().N)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Xi = xi
|
||||
|
||||
// Compute public key share [x_i]G
|
||||
Xi := ScalarBaseMult(p.Config.Curve, xi)
|
||||
p.PublicShares[p.Index] = Xi.X
|
||||
|
||||
// In practice, this would involve communication rounds
|
||||
// For now, we simulate having received all shares
|
||||
|
||||
// Compute group public key (would be sum of all shares)
|
||||
// Y = sum([x_i]G) for all i
|
||||
|
||||
p.log.Info("Key generation completed",
|
||||
log.Stringer("partyID", p.ID),
|
||||
log.Int("index", p.Index),
|
||||
log.Int("threshold", p.Config.Threshold),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitiateSign starts a new signing session
|
||||
func (p *Party) InitiateSign(sessionID string, message []byte) (*SigningSession, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if _, exists := p.sessions[sessionID]; exists {
|
||||
return nil, errors.New("session already exists")
|
||||
}
|
||||
|
||||
// Hash the message
|
||||
h := sha256.Sum256(message)
|
||||
messageHash := new(big.Int).SetBytes(h[:])
|
||||
|
||||
session := &SigningSession{
|
||||
SessionID: sessionID,
|
||||
Message: message,
|
||||
MessageHash: messageHash,
|
||||
Commitments: make(map[int][]byte),
|
||||
GammaShares: make(map[int]*big.Int),
|
||||
BigGammaShares: make(map[int]*ECPoint),
|
||||
BigDeltaShares: make(map[int]*ECPoint),
|
||||
Deltas: make(map[int]*big.Int),
|
||||
}
|
||||
|
||||
p.sessions[sessionID] = session
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Round1_Commitment generates and broadcasts commitment
|
||||
func (p *Party) Round1_Commitment(sessionID string) ([]byte, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
session, exists := p.sessions[sessionID]
|
||||
if !exists {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
|
||||
if session.CommitmentSent {
|
||||
return nil, errors.New("commitment already sent")
|
||||
}
|
||||
|
||||
// Generate k_i and gamma_i
|
||||
ki, err := rand.Int(rand.Reader, p.Config.Curve.Params().N)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gammai, err := rand.Int(rand.Reader, p.Config.Curve.Params().N)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session.Ki = ki
|
||||
session.Gammai = gammai
|
||||
|
||||
// Compute commitment = H(i, [gamma_i]G)
|
||||
bigGammai := ScalarBaseMult(p.Config.Curve, gammai)
|
||||
commitment := p.computeCommitment(p.Index, bigGammai)
|
||||
|
||||
session.CommitmentSent = true
|
||||
|
||||
return commitment, nil
|
||||
}
|
||||
|
||||
// Round2_Reveal reveals gamma values after receiving all commitments
|
||||
func (p *Party) Round2_Reveal(sessionID string, commitments map[int][]byte) (*Round2Message, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
session, exists := p.sessions[sessionID]
|
||||
if !exists {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
|
||||
if session.RevealsSent {
|
||||
return nil, errors.New("reveals already sent")
|
||||
}
|
||||
|
||||
// Store commitments
|
||||
for idx, comm := range commitments {
|
||||
if idx != p.Index {
|
||||
session.Commitments[idx] = comm
|
||||
}
|
||||
}
|
||||
|
||||
// Create reveal message
|
||||
bigGammai := ScalarBaseMult(p.Config.Curve, session.Gammai)
|
||||
|
||||
msg := &Round2Message{
|
||||
FromIndex: p.Index,
|
||||
BigGammaI: bigGammai,
|
||||
// In production, include ZK proofs here
|
||||
}
|
||||
|
||||
session.RevealsSent = true
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// Round3_Multiply performs multiplication phase
|
||||
func (p *Party) Round3_Multiply(sessionID string, reveals map[int]*Round2Message) (*Round3Message, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
session, exists := p.sessions[sessionID]
|
||||
if !exists {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
|
||||
// Verify commitments match reveals
|
||||
for idx, reveal := range reveals {
|
||||
if idx == p.Index {
|
||||
continue
|
||||
}
|
||||
|
||||
expectedComm := p.computeCommitment(idx, reveal.BigGammaI)
|
||||
if !bytesEqual(expectedComm, session.Commitments[idx]) {
|
||||
return nil, fmt.Errorf("commitment verification failed for party %d", idx)
|
||||
}
|
||||
|
||||
session.BigGammaShares[idx] = reveal.BigGammaI
|
||||
}
|
||||
|
||||
// Compute delta_i = k_i * gamma_i
|
||||
deltai := new(big.Int).Mul(session.Ki, session.Gammai)
|
||||
deltai.Mod(deltai, p.Config.Curve.Params().N)
|
||||
session.DeltaShare = deltai
|
||||
|
||||
// Compute chi_i = x_i * k_i
|
||||
chii := new(big.Int).Mul(p.Xi, session.Ki)
|
||||
chii.Mod(chii, p.Config.Curve.Params().N)
|
||||
session.ChiShare = chii
|
||||
|
||||
// Compute [delta_i]G
|
||||
bigDeltai := ScalarBaseMult(p.Config.Curve, deltai)
|
||||
|
||||
msg := &Round3Message{
|
||||
FromIndex: p.Index,
|
||||
BigDeltaI: bigDeltai,
|
||||
// In production, include encrypted shares and ZK proofs
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// Round4_Open performs the opening phase
|
||||
func (p *Party) Round4_Open(sessionID string, round3msgs map[int]*Round3Message) (*Round4Message, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
session, exists := p.sessions[sessionID]
|
||||
if !exists {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
|
||||
// Store [delta_j]G values
|
||||
for idx, msg := range round3msgs {
|
||||
if idx != p.Index {
|
||||
session.BigDeltaShares[idx] = msg.BigDeltaI
|
||||
}
|
||||
}
|
||||
|
||||
// In production, perform consistency checks and identify aborts
|
||||
|
||||
msg := &Round4Message{
|
||||
FromIndex: p.Index,
|
||||
DeltaI: session.DeltaShare,
|
||||
// Include proofs of correct multiplication
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// Finalize computes the final signature
|
||||
func (p *Party) Finalize(sessionID string, round4msgs map[int]*Round4Message) (*Signature, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
session, exists := p.sessions[sessionID]
|
||||
if !exists {
|
||||
return nil, errors.New("session not found")
|
||||
}
|
||||
|
||||
// Store delta values
|
||||
for idx, msg := range round4msgs {
|
||||
session.Deltas[idx] = msg.DeltaI
|
||||
}
|
||||
|
||||
// Compute R = product([k_j * gamma_j]G) for all j
|
||||
// In practice, this involves point multiplication
|
||||
|
||||
// For now, simulate signature computation
|
||||
r := new(big.Int).SetBytes([]byte("simulated_r_value"))
|
||||
|
||||
// Compute s_i (partial signature)
|
||||
// s_i = m * chi_i + r * sigma_i
|
||||
// where sigma_i is the lagrange-adjusted share
|
||||
|
||||
s := new(big.Int).SetBytes([]byte("simulated_s_value"))
|
||||
|
||||
// Store final signature
|
||||
session.R = r
|
||||
session.S = s
|
||||
|
||||
return &Signature{
|
||||
R: r,
|
||||
S: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func (p *Party) computeCommitment(index int, point *ECPoint) []byte {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(fmt.Sprintf("%d", index)))
|
||||
h.Write(point.X.Bytes())
|
||||
h.Write(point.Y.Bytes())
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func ScalarBaseMult(curve elliptic.Curve, k *big.Int) *ECPoint {
|
||||
x, y := curve.ScalarBaseMult(k.Bytes())
|
||||
return &ECPoint{X: x, Y: y}
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Message types for protocol rounds
|
||||
|
||||
type Round2Message struct {
|
||||
FromIndex int
|
||||
BigGammaI *ECPoint
|
||||
// ZK proofs would be included here
|
||||
}
|
||||
|
||||
type Round3Message struct {
|
||||
FromIndex int
|
||||
BigDeltaI *ECPoint
|
||||
// Encrypted shares and proofs
|
||||
}
|
||||
|
||||
type Round4Message struct {
|
||||
FromIndex int
|
||||
DeltaI *big.Int
|
||||
// Opening proofs
|
||||
}
|
||||
|
||||
// IdentifiableAbort contains information about a misbehaving party
|
||||
type IdentifiableAbort struct {
|
||||
AbortingParty int
|
||||
Round int
|
||||
Proof []byte
|
||||
}
|
||||
|
||||
// VerifySignature verifies a threshold signature
|
||||
func VerifySignature(pubKey *ecdsa.PublicKey, message []byte, sig *Signature) bool {
|
||||
h := sha256.Sum256(message)
|
||||
return ecdsa.Verify(pubKey, h[:], sig.R, sig.S)
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package cggmp21
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// PaillierPublicKey represents a Paillier public key
|
||||
type PaillierPublicKey struct {
|
||||
N *big.Int // n = p*q
|
||||
NSq *big.Int // n^2
|
||||
G *big.Int // generator (typically n+1)
|
||||
}
|
||||
|
||||
// PaillierPrivateKey represents a Paillier private key
|
||||
type PaillierPrivateKey struct {
|
||||
PublicKey *PaillierPublicKey
|
||||
Lambda *big.Int // lcm(p-1, q-1)
|
||||
Mu *big.Int // modular multiplicative inverse
|
||||
P *big.Int // prime p
|
||||
Q *big.Int // prime q
|
||||
}
|
||||
|
||||
// GeneratePaillierKeyPair generates a new Paillier keypair
|
||||
func GeneratePaillierKeyPair(bits int) (*PaillierPrivateKey, *PaillierPublicKey, error) {
|
||||
// Generate two large primes p and q
|
||||
p, err := rand.Prime(rand.Reader, bits/2)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
q, err := rand.Prime(rand.Reader, bits/2)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Compute n = p*q
|
||||
n := new(big.Int).Mul(p, q)
|
||||
nSq := new(big.Int).Mul(n, n)
|
||||
|
||||
// Compute lambda = lcm(p-1, q-1)
|
||||
pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
|
||||
qMinus1 := new(big.Int).Sub(q, big.NewInt(1))
|
||||
|
||||
gcd := new(big.Int).GCD(nil, nil, pMinus1, qMinus1)
|
||||
lambda := new(big.Int).Mul(pMinus1, qMinus1)
|
||||
lambda.Div(lambda, gcd)
|
||||
|
||||
// Set g = n+1 (standard choice)
|
||||
g := new(big.Int).Add(n, big.NewInt(1))
|
||||
|
||||
// Compute mu = (L(g^lambda mod n^2))^(-1) mod n
|
||||
// where L(x) = (x-1)/n
|
||||
gLambda := new(big.Int).Exp(g, lambda, nSq)
|
||||
l := L(gLambda, n)
|
||||
mu := new(big.Int).ModInverse(l, n)
|
||||
|
||||
if mu == nil {
|
||||
return nil, nil, errors.New("failed to compute modular inverse")
|
||||
}
|
||||
|
||||
pubKey := &PaillierPublicKey{
|
||||
N: n,
|
||||
NSq: nSq,
|
||||
G: g,
|
||||
}
|
||||
|
||||
privKey := &PaillierPrivateKey{
|
||||
PublicKey: pubKey,
|
||||
Lambda: lambda,
|
||||
Mu: mu,
|
||||
P: p,
|
||||
Q: q,
|
||||
}
|
||||
|
||||
return privKey, pubKey, nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts a plaintext using Paillier encryption
|
||||
func (pub *PaillierPublicKey) Encrypt(plaintext *big.Int) (*big.Int, error) {
|
||||
// Check plaintext is in valid range
|
||||
if plaintext.Cmp(pub.N) >= 0 || plaintext.Sign() < 0 {
|
||||
return nil, errors.New("plaintext out of range")
|
||||
}
|
||||
|
||||
// Generate random r where gcd(r, n) = 1
|
||||
var r *big.Int
|
||||
for {
|
||||
r, _ = rand.Int(rand.Reader, pub.N)
|
||||
if new(big.Int).GCD(nil, nil, r, pub.N).Cmp(big.NewInt(1)) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Compute ciphertext = g^m * r^n mod n^2
|
||||
gm := new(big.Int).Exp(pub.G, plaintext, pub.NSq)
|
||||
rn := new(big.Int).Exp(r, pub.N, pub.NSq)
|
||||
|
||||
ciphertext := new(big.Int).Mul(gm, rn)
|
||||
ciphertext.Mod(ciphertext, pub.NSq)
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts a ciphertext using Paillier decryption
|
||||
func (priv *PaillierPrivateKey) Decrypt(ciphertext *big.Int) (*big.Int, error) {
|
||||
// Check ciphertext is in valid range
|
||||
if ciphertext.Cmp(priv.PublicKey.NSq) >= 0 || ciphertext.Sign() <= 0 {
|
||||
return nil, errors.New("ciphertext out of range")
|
||||
}
|
||||
|
||||
// Compute plaintext = L(c^lambda mod n^2) * mu mod n
|
||||
cLambda := new(big.Int).Exp(ciphertext, priv.Lambda, priv.PublicKey.NSq)
|
||||
l := L(cLambda, priv.PublicKey.N)
|
||||
|
||||
plaintext := new(big.Int).Mul(l, priv.Mu)
|
||||
plaintext.Mod(plaintext, priv.PublicKey.N)
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// Add performs homomorphic addition of two ciphertexts
|
||||
func (pub *PaillierPublicKey) Add(c1, c2 *big.Int) *big.Int {
|
||||
// E(m1) * E(m2) = E(m1 + m2)
|
||||
result := new(big.Int).Mul(c1, c2)
|
||||
result.Mod(result, pub.NSq)
|
||||
return result
|
||||
}
|
||||
|
||||
// Multiply performs homomorphic multiplication by a constant
|
||||
func (pub *PaillierPublicKey) Multiply(ciphertext, constant *big.Int) *big.Int {
|
||||
// E(m)^k = E(k*m)
|
||||
result := new(big.Int).Exp(ciphertext, constant, pub.NSq)
|
||||
return result
|
||||
}
|
||||
|
||||
// L computes L(x) = (x-1)/n
|
||||
func L(x, n *big.Int) *big.Int {
|
||||
return new(big.Int).Div(new(big.Int).Sub(x, big.NewInt(1)), n)
|
||||
}
|
||||
|
||||
// ZKProof represents a zero-knowledge proof of plaintext knowledge
|
||||
type ZKProof struct {
|
||||
E *big.Int // Commitment
|
||||
Z *big.Int // Response
|
||||
Pub *PaillierPublicKey
|
||||
}
|
||||
|
||||
// ProveKnowledge creates a ZK proof of plaintext knowledge
|
||||
func ProveKnowledge(pub *PaillierPublicKey, plaintext, randomness *big.Int) (*ZKProof, error) {
|
||||
// This is a simplified Schnorr-like proof
|
||||
// In production, use proper ZK proofs as specified in CGGMP21
|
||||
|
||||
// Generate random values
|
||||
r1, _ := rand.Int(rand.Reader, pub.N)
|
||||
r2, _ := rand.Int(rand.Reader, pub.N)
|
||||
|
||||
// Compute commitment e = g^r1 * r2^n mod n^2
|
||||
gr1 := new(big.Int).Exp(pub.G, r1, pub.NSq)
|
||||
r2n := new(big.Int).Exp(r2, pub.N, pub.NSq)
|
||||
e := new(big.Int).Mul(gr1, r2n)
|
||||
e.Mod(e, pub.NSq)
|
||||
|
||||
// Compute challenge (in practice, use Fiat-Shamir)
|
||||
challenge := new(big.Int).SetBytes([]byte("challenge"))
|
||||
challenge.Mod(challenge, pub.N)
|
||||
|
||||
// Compute response z = r1 + challenge * plaintext
|
||||
z := new(big.Int).Mul(challenge, plaintext)
|
||||
z.Add(z, r1)
|
||||
z.Mod(z, pub.N)
|
||||
|
||||
return &ZKProof{
|
||||
E: e,
|
||||
Z: z,
|
||||
Pub: pub,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyKnowledge verifies a ZK proof of plaintext knowledge
|
||||
func VerifyKnowledge(proof *ZKProof, ciphertext *big.Int) bool {
|
||||
// Simplified verification
|
||||
// In production, implement full verification as per CGGMP21
|
||||
|
||||
// Recompute challenge
|
||||
challenge := new(big.Int).SetBytes([]byte("challenge"))
|
||||
challenge.Mod(challenge, proof.Pub.N)
|
||||
|
||||
// Verify: g^z = e * c^challenge mod n^2
|
||||
gz := new(big.Int).Exp(proof.Pub.G, proof.Z, proof.Pub.NSq)
|
||||
|
||||
cc := new(big.Int).Exp(ciphertext, challenge, proof.Pub.NSq)
|
||||
ec := new(big.Int).Mul(proof.E, cc)
|
||||
ec.Mod(ec, proof.Pub.NSq)
|
||||
|
||||
return gz.Cmp(ec) == 0
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package corona
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
const (
|
||||
// RingSize is the default size of the anonymity set
|
||||
DefaultRingSize = 16
|
||||
|
||||
// SignatureSize is the size of a Corona signature in bytes
|
||||
SignatureSize = 32 * DefaultRingSize
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidRingSize = errors.New("invalid ring size")
|
||||
errInvalidPublicKey = errors.New("invalid public key")
|
||||
errInvalidSignature = errors.New("invalid signature")
|
||||
errRingNotComplete = errors.New("ring is not complete")
|
||||
)
|
||||
|
||||
// PublicKey represents a Corona public key
|
||||
type PublicKey struct {
|
||||
Point *Point
|
||||
}
|
||||
|
||||
// PrivateKey represents a Corona private key
|
||||
type PrivateKey struct {
|
||||
Scalar *big.Int
|
||||
}
|
||||
|
||||
// Point represents a point on the elliptic curve
|
||||
type Point struct {
|
||||
X, Y *big.Int
|
||||
}
|
||||
|
||||
// RingSignature represents a Corona ring signature
|
||||
type RingSignature struct {
|
||||
C0 *big.Int
|
||||
S []*big.Int
|
||||
KeyImage *Point
|
||||
RingPubKeys []*PublicKey
|
||||
}
|
||||
|
||||
// Factory implements a factory for Corona keys
|
||||
type Factory struct{}
|
||||
|
||||
// NewPrivateKey generates a new private key
|
||||
func (*Factory) NewPrivateKey() (*PrivateKey, error) {
|
||||
scalar, err := rand.Int(rand.Reader, curveOrder())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PrivateKey{Scalar: scalar}, nil
|
||||
}
|
||||
|
||||
// ToPublicKey converts a private key to its corresponding public key
|
||||
func (*Factory) ToPublicKey(privKey *PrivateKey) (*PublicKey, error) {
|
||||
if privKey == nil {
|
||||
return nil, errors.New("nil private key")
|
||||
}
|
||||
|
||||
point := scalarBaseMult(privKey.Scalar)
|
||||
return &PublicKey{Point: point}, nil
|
||||
}
|
||||
|
||||
// Sign creates a ring signature
|
||||
func (priv *PrivateKey) Sign(message []byte, ring []*PublicKey) (*RingSignature, error) {
|
||||
if len(ring) != DefaultRingSize {
|
||||
return nil, errInvalidRingSize
|
||||
}
|
||||
|
||||
// Find the signer's position in the ring
|
||||
pubKey := priv.PublicKey()
|
||||
signerIndex := -1
|
||||
for i, key := range ring {
|
||||
if key.Equal(pubKey) {
|
||||
signerIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if signerIndex == -1 {
|
||||
return nil, errors.New("signer's public key not found in ring")
|
||||
}
|
||||
|
||||
// Generate key image
|
||||
keyImage := generateKeyImage(priv)
|
||||
|
||||
// Initialize signature components
|
||||
c := make([]*big.Int, DefaultRingSize)
|
||||
s := make([]*big.Int, DefaultRingSize)
|
||||
|
||||
// Generate random responses for all except signer
|
||||
for i := 0; i < DefaultRingSize; i++ {
|
||||
if i != signerIndex {
|
||||
s[i], _ = rand.Int(rand.Reader, curveOrder())
|
||||
}
|
||||
}
|
||||
|
||||
// Start the ring computation
|
||||
// Generate random nonce
|
||||
k, _ := rand.Int(rand.Reader, curveOrder())
|
||||
|
||||
// Compute L_i and R_i for all ring members
|
||||
L := make([]*Point, DefaultRingSize)
|
||||
R := make([]*Point, DefaultRingSize)
|
||||
|
||||
// For the signer
|
||||
L[signerIndex] = scalarBaseMult(k)
|
||||
R[signerIndex] = scalarMult(hashToPoint(ring[signerIndex].Point), k)
|
||||
|
||||
// Compute c_{i+1} starting from signer
|
||||
c[(signerIndex+1)%DefaultRingSize] = hashPoints(message, L[signerIndex], R[signerIndex])
|
||||
|
||||
// Complete the ring
|
||||
for i := (signerIndex + 1) % DefaultRingSize; i != signerIndex; i = (i + 1) % DefaultRingSize {
|
||||
L[i] = addPoints(
|
||||
scalarBaseMult(s[i]),
|
||||
scalarMult(ring[i].Point, c[i]),
|
||||
)
|
||||
R[i] = addPoints(
|
||||
scalarMult(hashToPoint(ring[i].Point), s[i]),
|
||||
scalarMult(keyImage, c[i]),
|
||||
)
|
||||
|
||||
c[(i+1)%DefaultRingSize] = hashPoints(message, L[i], R[i])
|
||||
}
|
||||
|
||||
// Complete the signature for the signer
|
||||
s[signerIndex] = new(big.Int).Sub(k, new(big.Int).Mul(c[signerIndex], priv.Scalar))
|
||||
s[signerIndex].Mod(s[signerIndex], curveOrder())
|
||||
|
||||
return &RingSignature{
|
||||
C0: c[0],
|
||||
S: s,
|
||||
KeyImage: keyImage,
|
||||
RingPubKeys: ring,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify verifies a ring signature
|
||||
func (sig *RingSignature) Verify(message []byte) bool {
|
||||
if len(sig.S) != DefaultRingSize || len(sig.RingPubKeys) != DefaultRingSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// Recompute c values
|
||||
c := make([]*big.Int, DefaultRingSize)
|
||||
c[0] = sig.C0
|
||||
|
||||
for i := 0; i < DefaultRingSize; i++ {
|
||||
// Compute L_i = s_i * G + c_i * P_i
|
||||
L := addPoints(
|
||||
scalarBaseMult(sig.S[i]),
|
||||
scalarMult(sig.RingPubKeys[i].Point, c[i]),
|
||||
)
|
||||
|
||||
// Compute R_i = s_i * H(P_i) + c_i * I
|
||||
R := addPoints(
|
||||
scalarMult(hashToPoint(sig.RingPubKeys[i].Point), sig.S[i]),
|
||||
scalarMult(sig.KeyImage, c[i]),
|
||||
)
|
||||
|
||||
// Compute next c value
|
||||
if i < DefaultRingSize-1 {
|
||||
c[i+1] = hashPoints(message, L, R)
|
||||
} else {
|
||||
// For the last iteration, check if we get back to c[0]
|
||||
computedC0 := hashPoints(message, L, R)
|
||||
return computedC0.Cmp(sig.C0) == 0
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// PublicKey returns the public key corresponding to the private key
|
||||
func (priv *PrivateKey) PublicKey() *PublicKey {
|
||||
return &PublicKey{Point: scalarBaseMult(priv.Scalar)}
|
||||
}
|
||||
|
||||
// Equal checks if two public keys are equal
|
||||
func (pub *PublicKey) Equal(other *PublicKey) bool {
|
||||
return pub.Point.X.Cmp(other.Point.X) == 0 && pub.Point.Y.Cmp(other.Point.Y) == 0
|
||||
}
|
||||
|
||||
// Bytes returns the byte representation of the public key
|
||||
func (pub *PublicKey) Bytes() []byte {
|
||||
return append(pub.Point.X.Bytes(), pub.Point.Y.Bytes()...)
|
||||
}
|
||||
|
||||
// Address returns the address of the public key
|
||||
func (pub *PublicKey) Address() ids.ShortID {
|
||||
hash := sha256.Sum256(pub.Bytes())
|
||||
addr, _ := ids.ToShortID(hash[:])
|
||||
return addr
|
||||
}
|
||||
|
||||
// Helper functions for elliptic curve operations
|
||||
func curveOrder() *big.Int {
|
||||
// Simplified curve order for demonstration
|
||||
// In production, use actual curve parameters
|
||||
order, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
|
||||
return order
|
||||
}
|
||||
|
||||
func scalarBaseMult(k *big.Int) *Point {
|
||||
// Simplified scalar multiplication with base point
|
||||
// In production, use actual elliptic curve operations
|
||||
x := new(big.Int).Mul(k, big.NewInt(2))
|
||||
y := new(big.Int).Mul(k, big.NewInt(3))
|
||||
return &Point{X: x, Y: y}
|
||||
}
|
||||
|
||||
func scalarMult(p *Point, k *big.Int) *Point {
|
||||
// Simplified scalar multiplication
|
||||
// In production, use actual elliptic curve operations
|
||||
x := new(big.Int).Mul(p.X, k)
|
||||
y := new(big.Int).Mul(p.Y, k)
|
||||
return &Point{X: x, Y: y}
|
||||
}
|
||||
|
||||
func addPoints(p1, p2 *Point) *Point {
|
||||
// Simplified point addition
|
||||
// In production, use actual elliptic curve operations
|
||||
x := new(big.Int).Add(p1.X, p2.X)
|
||||
y := new(big.Int).Add(p1.Y, p2.Y)
|
||||
return &Point{X: x, Y: y}
|
||||
}
|
||||
|
||||
func hashToPoint(p *Point) *Point {
|
||||
// Hash a point to another point on the curve
|
||||
h := sha256.Sum256(append(p.X.Bytes(), p.Y.Bytes()...))
|
||||
x := new(big.Int).SetBytes(h[:16])
|
||||
y := new(big.Int).SetBytes(h[16:])
|
||||
return &Point{X: x, Y: y}
|
||||
}
|
||||
|
||||
func hashPoints(message []byte, points ...*Point) *big.Int {
|
||||
h := sha256.New()
|
||||
h.Write(message)
|
||||
for _, p := range points {
|
||||
h.Write(p.X.Bytes())
|
||||
h.Write(p.Y.Bytes())
|
||||
}
|
||||
return new(big.Int).SetBytes(h.Sum(nil))
|
||||
}
|
||||
|
||||
func generateKeyImage(priv *PrivateKey) *Point {
|
||||
// Key image = x * H(P)
|
||||
pubKey := priv.PublicKey()
|
||||
hashedPoint := hashToPoint(pubKey.Point)
|
||||
return scalarMult(hashedPoint, priv.Scalar)
|
||||
}
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package mpc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultThreshold is the default threshold for MPC
|
||||
DefaultThreshold = 3
|
||||
|
||||
// DefaultParties is the default number of parties
|
||||
DefaultParties = 5
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidThreshold = errors.New("invalid threshold")
|
||||
errInvalidParties = errors.New("invalid number of parties")
|
||||
errNotEnoughShares = errors.New("not enough shares to reconstruct")
|
||||
errInvalidShare = errors.New("invalid share")
|
||||
)
|
||||
|
||||
// MPCAccount represents a per-account MPC configuration
|
||||
type MPCAccount struct {
|
||||
AccountID ids.ShortID
|
||||
Threshold int
|
||||
Parties int
|
||||
PublicKey *PublicKey
|
||||
Shares map[int]*Share
|
||||
Protocol Protocol
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Share represents a secret share held by a party
|
||||
type Share struct {
|
||||
Index int
|
||||
Value *big.Int
|
||||
Proof []byte
|
||||
}
|
||||
|
||||
// PublicKey represents an MPC public key
|
||||
type PublicKey struct {
|
||||
Point *Point
|
||||
}
|
||||
|
||||
// Point represents a point on the elliptic curve
|
||||
type Point struct {
|
||||
X, Y *big.Int
|
||||
}
|
||||
|
||||
// Protocol represents the MPC protocol type
|
||||
type Protocol string
|
||||
|
||||
const (
|
||||
ProtocolGG18 Protocol = "gg18"
|
||||
ProtocolGG20 Protocol = "gg20"
|
||||
ProtocolCMP Protocol = "cmp"
|
||||
)
|
||||
|
||||
// Manager manages per-account MPC configurations
|
||||
type Manager struct {
|
||||
accounts map[ids.ShortID]*MPCAccount
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewManager creates a new MPC manager
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
accounts: make(map[ids.ShortID]*MPCAccount),
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAccount creates a new MPC account
|
||||
func (m *Manager) CreateAccount(accountID ids.ShortID, threshold, parties int, protocol Protocol) (*MPCAccount, error) {
|
||||
if threshold < 1 || threshold > parties {
|
||||
return nil, errInvalidThreshold
|
||||
}
|
||||
if parties < 2 {
|
||||
return nil, errInvalidParties
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// Check if account already exists
|
||||
if _, exists := m.accounts[accountID]; exists {
|
||||
return nil, errors.New("account already exists")
|
||||
}
|
||||
|
||||
// Generate distributed key
|
||||
account := &MPCAccount{
|
||||
AccountID: accountID,
|
||||
Threshold: threshold,
|
||||
Parties: parties,
|
||||
Shares: make(map[int]*Share),
|
||||
Protocol: protocol,
|
||||
}
|
||||
|
||||
// Generate key shares
|
||||
if err := account.generateKeyShares(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.accounts[accountID] = account
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// GetAccount retrieves an MPC account
|
||||
func (m *Manager) GetAccount(accountID ids.ShortID) (*MPCAccount, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
account, exists := m.accounts[accountID]
|
||||
if !exists {
|
||||
return nil, errors.New("account not found")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// generateKeyShares generates threshold key shares
|
||||
func (a *MPCAccount) generateKeyShares() error {
|
||||
// Generate random polynomial coefficients
|
||||
coeffs := make([]*big.Int, a.Threshold)
|
||||
for i := 0; i < a.Threshold; i++ {
|
||||
coeff, err := rand.Int(rand.Reader, curveOrder())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
coeffs[i] = coeff
|
||||
}
|
||||
|
||||
// The secret is the constant term
|
||||
secret := coeffs[0]
|
||||
|
||||
// Generate shares for each party
|
||||
for i := 1; i <= a.Parties; i++ {
|
||||
share := evaluatePolynomial(coeffs, big.NewInt(int64(i)))
|
||||
a.Shares[i] = &Share{
|
||||
Index: i,
|
||||
Value: share,
|
||||
Proof: generateShareProof(share, i),
|
||||
}
|
||||
}
|
||||
|
||||
// Compute public key
|
||||
a.PublicKey = &PublicKey{
|
||||
Point: scalarBaseMult(secret),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign creates a threshold signature
|
||||
func (a *MPCAccount) Sign(message []byte, participatingShares map[int]*Share) ([]byte, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
if len(participatingShares) < a.Threshold {
|
||||
return nil, errNotEnoughShares
|
||||
}
|
||||
|
||||
// Verify shares
|
||||
for index, share := range participatingShares {
|
||||
if !a.verifyShare(index, share) {
|
||||
return nil, errInvalidShare
|
||||
}
|
||||
}
|
||||
|
||||
// Compute partial signatures
|
||||
messageHash := sha256.Sum256(message)
|
||||
partialSigs := make(map[int]*big.Int)
|
||||
|
||||
for index, share := range participatingShares {
|
||||
// Simplified partial signature computation
|
||||
// In production, use actual protocol (GG18/GG20/CMP)
|
||||
partialSig := new(big.Int).Mul(share.Value, new(big.Int).SetBytes(messageHash[:]))
|
||||
partialSig.Mod(partialSig, curveOrder())
|
||||
partialSigs[index] = partialSig
|
||||
}
|
||||
|
||||
// Combine partial signatures using Lagrange interpolation
|
||||
signature := combinePartialSignatures(partialSigs, a.Threshold)
|
||||
|
||||
return signature.Bytes(), nil
|
||||
}
|
||||
|
||||
// Verify verifies a signature
|
||||
func (a *MPCAccount) Verify(message []byte, signature []byte) bool {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
// Simplified verification
|
||||
// In production, implement actual signature verification
|
||||
messageHash := sha256.Sum256(message)
|
||||
sig := new(big.Int).SetBytes(signature)
|
||||
|
||||
// Verify using public key
|
||||
expectedPoint := scalarBaseMult(sig)
|
||||
messagePoint := scalarBaseMult(new(big.Int).SetBytes(messageHash[:]))
|
||||
|
||||
// Check if signature is valid (simplified)
|
||||
return pointsEqual(expectedPoint, messagePoint)
|
||||
}
|
||||
|
||||
// AddShare adds a share to the account
|
||||
func (a *MPCAccount) AddShare(index int, share *Share) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if index < 1 || index > a.Parties {
|
||||
return errors.New("invalid share index")
|
||||
}
|
||||
|
||||
if !a.verifyShare(index, share) {
|
||||
return errInvalidShare
|
||||
}
|
||||
|
||||
a.Shares[index] = share
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetShare retrieves a specific share
|
||||
func (a *MPCAccount) GetShare(index int) (*Share, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
share, exists := a.Shares[index]
|
||||
if !exists {
|
||||
return nil, errors.New("share not found")
|
||||
}
|
||||
|
||||
return share, nil
|
||||
}
|
||||
|
||||
// RefreshShares performs proactive secret sharing to refresh shares
|
||||
func (a *MPCAccount) RefreshShares() error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
// Generate new random polynomial with same secret
|
||||
oldShares := a.Shares
|
||||
a.Shares = make(map[int]*Share)
|
||||
|
||||
// Keep the same secret (constant term)
|
||||
secret := reconstructSecret(oldShares, a.Threshold)
|
||||
|
||||
// Generate new polynomial with same secret
|
||||
coeffs := make([]*big.Int, a.Threshold)
|
||||
coeffs[0] = secret
|
||||
|
||||
for i := 1; i < a.Threshold; i++ {
|
||||
coeff, err := rand.Int(rand.Reader, curveOrder())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
coeffs[i] = coeff
|
||||
}
|
||||
|
||||
// Generate new shares
|
||||
for i := 1; i <= a.Parties; i++ {
|
||||
share := evaluatePolynomial(coeffs, big.NewInt(int64(i)))
|
||||
a.Shares[i] = &Share{
|
||||
Index: i,
|
||||
Value: share,
|
||||
Proof: generateShareProof(share, i),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func curveOrder() *big.Int {
|
||||
// Simplified curve order
|
||||
order, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
|
||||
return order
|
||||
}
|
||||
|
||||
func scalarBaseMult(k *big.Int) *Point {
|
||||
// Simplified scalar multiplication
|
||||
x := new(big.Int).Mul(k, big.NewInt(2))
|
||||
y := new(big.Int).Mul(k, big.NewInt(3))
|
||||
return &Point{X: x, Y: y}
|
||||
}
|
||||
|
||||
func pointsEqual(p1, p2 *Point) bool {
|
||||
return p1.X.Cmp(p2.X) == 0 && p1.Y.Cmp(p2.Y) == 0
|
||||
}
|
||||
|
||||
func evaluatePolynomial(coeffs []*big.Int, x *big.Int) *big.Int {
|
||||
result := new(big.Int).Set(coeffs[0])
|
||||
xPower := new(big.Int).Set(x)
|
||||
|
||||
for i := 1; i < len(coeffs); i++ {
|
||||
term := new(big.Int).Mul(coeffs[i], xPower)
|
||||
result.Add(result, term)
|
||||
result.Mod(result, curveOrder())
|
||||
|
||||
xPower.Mul(xPower, x)
|
||||
xPower.Mod(xPower, curveOrder())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func generateShareProof(share *big.Int, index int) []byte {
|
||||
// Generate proof of share validity
|
||||
h := sha256.New()
|
||||
h.Write(share.Bytes())
|
||||
h.Write([]byte{byte(index)})
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func (a *MPCAccount) verifyShare(index int, share *Share) bool {
|
||||
// Verify share proof
|
||||
expectedProof := generateShareProof(share.Value, index)
|
||||
if len(share.Proof) != len(expectedProof) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range expectedProof {
|
||||
if expectedProof[i] != share.Proof[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func combinePartialSignatures(partialSigs map[int]*big.Int, threshold int) *big.Int {
|
||||
result := big.NewInt(0)
|
||||
indices := make([]int, 0, len(partialSigs))
|
||||
|
||||
for index := range partialSigs {
|
||||
indices = append(indices, index)
|
||||
if len(indices) == threshold {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range indices {
|
||||
lagrangeCoeff := computeLagrangeCoefficient(i, indices)
|
||||
term := new(big.Int).Mul(partialSigs[i], lagrangeCoeff)
|
||||
result.Add(result, term)
|
||||
}
|
||||
|
||||
result.Mod(result, curveOrder())
|
||||
return result
|
||||
}
|
||||
|
||||
func computeLagrangeCoefficient(i int, indices []int) *big.Int {
|
||||
num := big.NewInt(1)
|
||||
den := big.NewInt(1)
|
||||
|
||||
for _, j := range indices {
|
||||
if i != j {
|
||||
num.Mul(num, big.NewInt(int64(-j)))
|
||||
den.Mul(den, big.NewInt(int64(i-j)))
|
||||
}
|
||||
}
|
||||
|
||||
// Compute num/den mod curveOrder
|
||||
denInv := new(big.Int).ModInverse(den, curveOrder())
|
||||
result := new(big.Int).Mul(num, denInv)
|
||||
result.Mod(result, curveOrder())
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func reconstructSecret(shares map[int]*Share, threshold int) *big.Int {
|
||||
if len(shares) < threshold {
|
||||
return nil
|
||||
}
|
||||
|
||||
partialValues := make(map[int]*big.Int)
|
||||
for index, share := range shares {
|
||||
partialValues[index] = share.Value
|
||||
if len(partialValues) == threshold {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return combinePartialSignatures(partialValues, threshold)
|
||||
}
|
||||
Reference in New Issue
Block a user