mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
423 lines
9.9 KiB
Go
423 lines
9.9 KiB
Go
// 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)
|
|
}
|