mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
- Update quasar BLS and GPU integration - Improve epoch and threshold management - Remove deprecated corona module - Update integration tests for chain engine - Enhance dynamic and dual threshold tests - CGO_ENABLED=1 default for GPU acceleration
833 lines
23 KiB
Go
833 lines
23 KiB
Go
// Copyright (C) 2025, Lux Industries Inc All rights reserved.
|
|
// Quasar: accretion-powered finality with BLS + Corona signatures
|
|
|
|
package quasar
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/luxfi/crypto/bls"
|
|
"github.com/luxfi/crypto/threshold"
|
|
_ "github.com/luxfi/crypto/threshold/bls" // Register BLS threshold scheme
|
|
|
|
coronaThreshold "github.com/luxfi/corona/threshold"
|
|
)
|
|
|
|
// Buffer pools for hot paths - reduces GC pressure during signing/verification
|
|
var (
|
|
sigPool = sync.Pool{
|
|
New: func() any {
|
|
return &QuasarSig{
|
|
BLS: make([]byte, 0, 96),
|
|
Corona: make([]byte, 0, 4096),
|
|
}
|
|
},
|
|
}
|
|
|
|
blsSigSlicePool = sync.Pool{
|
|
New: func() any {
|
|
s := make([]*bls.Signature, 0, 128)
|
|
return &s
|
|
},
|
|
}
|
|
|
|
pubKeySlicePool = sync.Pool{
|
|
New: func() any {
|
|
s := make([]*bls.PublicKey, 0, 128)
|
|
return &s
|
|
},
|
|
}
|
|
)
|
|
|
|
// Signer implements parallel BLS+Corona threshold signing for PQ-safe consensus.
|
|
// BLS provides fast classical threshold signatures.
|
|
// Corona provides post-quantum threshold signatures based on Ring-LWE.
|
|
// Both run in parallel to provide quantum-safe finality.
|
|
type signer struct {
|
|
mu sync.RWMutex
|
|
|
|
// Classical BLS threshold signing (via crypto/threshold)
|
|
blsScheme threshold.Scheme
|
|
blsGroupKey threshold.PublicKey
|
|
blsSigners map[string]threshold.Signer
|
|
blsAggregator threshold.Aggregator
|
|
blsVerifier threshold.Verifier
|
|
|
|
// Post-quantum Corona threshold signing (native 2-round protocol)
|
|
coronaGroupKey *coronaThreshold.GroupKey
|
|
coronaSigners map[string]*coronaThreshold.Signer
|
|
coronaShares map[string]*coronaThreshold.KeyShare
|
|
|
|
// Legacy BLS direct keys (for backward compatibility)
|
|
blsKeys map[string]*bls.SecretKey
|
|
blsPubKeys map[string]*bls.PublicKey
|
|
|
|
// Consensus state
|
|
validators map[string]*Validator
|
|
threshold int // Number of validators needed for consensus
|
|
}
|
|
|
|
// Validator represents a consensus validator
|
|
type Validator struct {
|
|
ID string
|
|
BLSPubKey *bls.PublicKey
|
|
CoronaPub []byte // Corona group public key contribution
|
|
Weight uint64
|
|
Active bool
|
|
}
|
|
|
|
// SignerConfig configures the dual threshold signing system.
|
|
type SignerConfig struct {
|
|
Threshold int
|
|
TotalParties int
|
|
|
|
// BLS threshold (via crypto/threshold interface)
|
|
BLSKeyShares map[string]threshold.KeyShare
|
|
BLSGroupKey threshold.PublicKey
|
|
|
|
// Corona threshold (native 2-round protocol)
|
|
CoronaShares map[string]*coronaThreshold.KeyShare
|
|
CoronaGroupKey *coronaThreshold.GroupKey
|
|
}
|
|
|
|
// CoronaRound1State holds Round 1 data for all parties in a signing session.
|
|
type CoronaRound1State struct {
|
|
SessionID int
|
|
PRFKey []byte
|
|
SignerIDs []int
|
|
Round1Data map[int]*coronaThreshold.Round1Data
|
|
}
|
|
|
|
// NewSigner creates a new signer engine with basic BLS support.
|
|
func newSigner(thresholdVal int) (*signer, error) {
|
|
if thresholdVal < 1 {
|
|
return nil, errors.New("threshold must be at least 1")
|
|
}
|
|
|
|
return &signer{
|
|
blsKeys: make(map[string]*bls.SecretKey),
|
|
blsPubKeys: make(map[string]*bls.PublicKey),
|
|
blsSigners: make(map[string]threshold.Signer),
|
|
coronaSigners: make(map[string]*coronaThreshold.Signer),
|
|
coronaShares: make(map[string]*coronaThreshold.KeyShare),
|
|
validators: make(map[string]*Validator),
|
|
threshold: thresholdVal,
|
|
}, nil
|
|
}
|
|
|
|
// NewSignerWithDualThreshold creates a signer with full dual threshold signing.
|
|
func newSignerWithDualThreshold(config SignerConfig) (*signer, error) {
|
|
if config.Threshold < 1 {
|
|
return nil, errors.New("threshold must be at least 1")
|
|
}
|
|
|
|
h := &signer{
|
|
blsKeys: make(map[string]*bls.SecretKey),
|
|
blsPubKeys: make(map[string]*bls.PublicKey),
|
|
blsSigners: make(map[string]threshold.Signer),
|
|
coronaSigners: make(map[string]*coronaThreshold.Signer),
|
|
coronaShares: make(map[string]*coronaThreshold.KeyShare),
|
|
validators: make(map[string]*Validator),
|
|
threshold: config.Threshold,
|
|
}
|
|
|
|
// Initialize BLS threshold scheme
|
|
blsScheme, err := threshold.GetScheme(threshold.SchemeBLS)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get BLS scheme: %w", err)
|
|
}
|
|
h.blsScheme = blsScheme
|
|
h.blsGroupKey = config.BLSGroupKey
|
|
|
|
if config.BLSGroupKey != nil {
|
|
h.blsAggregator, err = blsScheme.NewAggregator(config.BLSGroupKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create BLS aggregator: %w", err)
|
|
}
|
|
h.blsVerifier, err = blsScheme.NewVerifier(config.BLSGroupKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create BLS verifier: %w", err)
|
|
}
|
|
}
|
|
|
|
for id, share := range config.BLSKeyShares {
|
|
signer, err := blsScheme.NewSigner(share)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create BLS signer for %s: %w", id, err)
|
|
}
|
|
h.blsSigners[id] = signer
|
|
}
|
|
|
|
// Initialize Corona signers (native 2-round protocol)
|
|
h.coronaGroupKey = config.CoronaGroupKey
|
|
for id, share := range config.CoronaShares {
|
|
h.coronaShares[id] = share
|
|
h.coronaSigners[id] = coronaThreshold.NewSigner(share)
|
|
}
|
|
|
|
return h, nil
|
|
}
|
|
|
|
// GenerateDualKeys generates both BLS and Corona threshold keys for an epoch.
|
|
// Call this when the validator set changes.
|
|
func GenerateDualKeys(t, n int) (*SignerConfig, error) {
|
|
ctx := context.Background()
|
|
|
|
// Generate BLS threshold keys
|
|
blsScheme, err := threshold.GetScheme(threshold.SchemeBLS)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get BLS scheme: %w", err)
|
|
}
|
|
|
|
blsDealer, err := blsScheme.NewTrustedDealer(threshold.DealerConfig{
|
|
Threshold: t,
|
|
TotalParties: n,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create BLS dealer: %w", err)
|
|
}
|
|
|
|
blsShares, blsGroupKey, err := blsDealer.GenerateShares(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate BLS shares: %w", err)
|
|
}
|
|
|
|
// Generate Corona threshold keys (native)
|
|
coronaShares, coronaGroupKey, err := coronaThreshold.GenerateKeys(t, n, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate Corona shares: %w", err)
|
|
}
|
|
|
|
// Convert to maps keyed by validator ID
|
|
blsShareMap := make(map[string]threshold.KeyShare)
|
|
coronaShareMap := make(map[string]*coronaThreshold.KeyShare)
|
|
|
|
for i := 0; i < n; i++ {
|
|
id := fmt.Sprintf("v%d", i)
|
|
blsShareMap[id] = blsShares[i]
|
|
coronaShareMap[id] = coronaShares[i]
|
|
}
|
|
|
|
return &SignerConfig{
|
|
Threshold: t,
|
|
TotalParties: n,
|
|
BLSKeyShares: blsShareMap,
|
|
BLSGroupKey: blsGroupKey,
|
|
CoronaShares: coronaShareMap,
|
|
CoronaGroupKey: coronaGroupKey,
|
|
}, nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// Corona 2-Round Protocol
|
|
// ============================================================================
|
|
|
|
// CoronaRound1 performs Round 1 of Corona signing for a validator.
|
|
// Returns Round1Data to broadcast to other validators.
|
|
func (s *signer) CoronaRound1(validatorID string, sessionID int, prfKey []byte) (*coronaThreshold.Round1Data, error) {
|
|
s.mu.RLock()
|
|
signer, exists := s.coronaSigners[validatorID]
|
|
s.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return nil, fmt.Errorf("validator %s not found in Corona signers", validatorID)
|
|
}
|
|
|
|
// Get all signer indices
|
|
signerIDs := make([]int, 0, len(s.coronaSigners))
|
|
for _, s := range s.coronaShares {
|
|
signerIDs = append(signerIDs, s.Index)
|
|
}
|
|
|
|
return signer.Round1(sessionID, prfKey, signerIDs), nil
|
|
}
|
|
|
|
// CoronaRound2 performs Round 2 of Corona signing for a validator.
|
|
// Requires collected Round 1 data from all signers.
|
|
// Returns Round2Data to broadcast.
|
|
func (s *signer) CoronaRound2(validatorID string, sessionID int, message string, prfKey []byte, round1Data map[int]*coronaThreshold.Round1Data) (*coronaThreshold.Round2Data, error) {
|
|
s.mu.RLock()
|
|
signer, exists := s.coronaSigners[validatorID]
|
|
s.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return nil, fmt.Errorf("validator %s not found in Corona signers", validatorID)
|
|
}
|
|
|
|
signerIDs := make([]int, 0, len(s.coronaSigners))
|
|
for _, s := range s.coronaShares {
|
|
signerIDs = append(signerIDs, s.Index)
|
|
}
|
|
|
|
return signer.Round2(sessionID, message, prfKey, signerIDs, round1Data)
|
|
}
|
|
|
|
// CoronaFinalize aggregates Round 2 data into the final signature.
|
|
// Any validator can call this.
|
|
func (s *signer) CoronaFinalize(validatorID string, round2Data map[int]*coronaThreshold.Round2Data) (*coronaThreshold.Signature, error) {
|
|
s.mu.RLock()
|
|
signer, exists := s.coronaSigners[validatorID]
|
|
s.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return nil, fmt.Errorf("validator %s not found in Corona signers", validatorID)
|
|
}
|
|
|
|
return signer.Finalize(round2Data)
|
|
}
|
|
|
|
// VerifyCoronaSignature verifies a Corona threshold signature.
|
|
func (s *signer) VerifyCoronaSignature(message string, sig *coronaThreshold.Signature) bool {
|
|
if s.coronaGroupKey == nil || sig == nil {
|
|
return false
|
|
}
|
|
return coronaThreshold.Verify(s.coronaGroupKey, message, sig)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Parallel BLS + Corona Signing
|
|
// ============================================================================
|
|
|
|
// DualSignRound1 performs Round 1 of both BLS and Corona in parallel.
|
|
// BLS: Computes signature share (single round)
|
|
// Corona: Computes D matrix + MACs (Round 1 of 2)
|
|
func (s *signer) DualSignRound1(ctx context.Context, validatorID string, message []byte, sessionID int, prfKey []byte) (*QuasarSig, *coronaThreshold.Round1Data, error) {
|
|
s.mu.RLock()
|
|
blsSigner, hasBLS := s.blsSigners[validatorID]
|
|
_, hasCorona := s.coronaSigners[validatorID]
|
|
s.mu.RUnlock()
|
|
|
|
if !hasBLS || !hasCorona {
|
|
return nil, nil, errors.New("validator not configured for dual signing")
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
var blsErr error
|
|
var blsShare threshold.SignatureShare
|
|
var round1Data *coronaThreshold.Round1Data
|
|
|
|
// Get BLS signer indices
|
|
blsIndices := make([]int, 0, len(s.blsSigners))
|
|
for _, s := range s.blsSigners {
|
|
blsIndices = append(blsIndices, s.Index())
|
|
}
|
|
|
|
wg.Add(2)
|
|
|
|
// BLS signing (single round)
|
|
go func() {
|
|
defer wg.Done()
|
|
blsShare, blsErr = blsSigner.SignShare(ctx, message, blsIndices, nil)
|
|
}()
|
|
|
|
// Corona Round 1
|
|
go func() {
|
|
defer wg.Done()
|
|
round1Data, _ = s.CoronaRound1(validatorID, sessionID, prfKey)
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
if blsErr != nil {
|
|
return nil, nil, fmt.Errorf("BLS signing failed: %w", blsErr)
|
|
}
|
|
|
|
sig := &QuasarSig{
|
|
BLS: blsShare.Bytes(),
|
|
ValidatorID: validatorID,
|
|
IsThreshold: true,
|
|
SignerIndex: blsSigner.Index(),
|
|
}
|
|
|
|
return sig, round1Data, nil
|
|
}
|
|
|
|
// DualSignRound2 performs Round 2 of Corona (BLS is already done in Round1).
|
|
func (s *signer) DualSignRound2(validatorID string, sessionID int, message string, prfKey []byte, round1Data map[int]*coronaThreshold.Round1Data) (*coronaThreshold.Round2Data, error) {
|
|
return s.CoronaRound2(validatorID, sessionID, message, prfKey, round1Data)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Legacy & Backward Compatibility
|
|
// ============================================================================
|
|
|
|
// AddValidator adds a validator with legacy BLS key generation.
|
|
func (s *signer) AddValidator(id string, weight uint64) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
blsSK, err := bls.NewSecretKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate BLS key: %w", err)
|
|
}
|
|
blsPK := blsSK.PublicKey()
|
|
|
|
s.blsKeys[id] = blsSK
|
|
s.blsPubKeys[id] = blsPK
|
|
|
|
s.validators[id] = &Validator{
|
|
ID: id,
|
|
BLSPubKey: blsPK,
|
|
Weight: weight,
|
|
Active: true,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SignMessage signs a message with BLS (legacy mode or threshold).
|
|
func (s *signer) SignMessage(validatorID string, message []byte) (*QuasarSig, error) {
|
|
return s.SignMessageWithContext(context.Background(), validatorID, message)
|
|
}
|
|
|
|
// SignMessageWithContext signs a message.
|
|
func (s *signer) SignMessageWithContext(ctx context.Context, validatorID string, message []byte) (*QuasarSig, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
sig := sigPool.Get().(*QuasarSig)
|
|
sig.BLS = sig.BLS[:0]
|
|
sig.Corona = sig.Corona[:0]
|
|
sig.ValidatorID = validatorID
|
|
|
|
// Check for threshold signer
|
|
blsSigner, hasBLSSigner := s.blsSigners[validatorID]
|
|
if hasBLSSigner {
|
|
blsIndices := make([]int, 0, len(s.blsSigners))
|
|
for _, s := range s.blsSigners {
|
|
blsIndices = append(blsIndices, s.Index())
|
|
}
|
|
|
|
share, err := blsSigner.SignShare(ctx, message, blsIndices, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BLS threshold sign failed: %w", err)
|
|
}
|
|
|
|
sig.BLS = append(sig.BLS, share.Bytes()...)
|
|
sig.IsThreshold = true
|
|
sig.SignerIndex = blsSigner.Index()
|
|
return sig, nil
|
|
}
|
|
|
|
// Fall back to legacy BLS
|
|
blsSK, exists := s.blsKeys[validatorID]
|
|
if !exists {
|
|
return nil, errors.New("validator not found")
|
|
}
|
|
|
|
blsSig, err := blsSK.Sign(message)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BLS sign failed: %w", err)
|
|
}
|
|
|
|
sig.BLS = append(sig.BLS, bls.SignatureToBytes(blsSig)...)
|
|
sig.IsThreshold = false
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
// ReleaseQuasarSig returns a QuasarSig to the pool.
|
|
func ReleaseQuasarSig(sig *QuasarSig) {
|
|
if sig == nil {
|
|
return
|
|
}
|
|
sig.ValidatorID = ""
|
|
sig.IsThreshold = false
|
|
sig.SignerIndex = 0
|
|
sigPool.Put(sig)
|
|
}
|
|
|
|
// VerifyQuasarSig verifies a signature.
|
|
func (s *signer) VerifyQuasarSig(message []byte, sig *QuasarSig) bool {
|
|
return s.VerifyQuasarSigWithContext(context.Background(), message, sig)
|
|
}
|
|
|
|
// VerifyQuasarSigWithContext verifies a signature.
|
|
func (s *signer) VerifyQuasarSigWithContext(ctx context.Context, message []byte, sig *QuasarSig) bool {
|
|
if ctx.Err() != nil {
|
|
return false
|
|
}
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
if sig.IsThreshold {
|
|
return true // Shares verified during aggregation
|
|
}
|
|
|
|
validator, exists := s.validators[sig.ValidatorID]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
blsSig, err := bls.SignatureFromBytes(sig.BLS)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return bls.Verify(validator.BLSPubKey, blsSig, message)
|
|
}
|
|
|
|
// AggregateSignatures aggregates BLS threshold signature shares.
|
|
func (s *signer) AggregateSignatures(message []byte, signatures []*QuasarSig) (*AggregatedSignature, error) {
|
|
return s.AggregateSignaturesWithContext(context.Background(), message, signatures)
|
|
}
|
|
|
|
// AggregateSignaturesWithContext aggregates signatures.
|
|
func (s *signer) AggregateSignaturesWithContext(ctx context.Context, message []byte, signatures []*QuasarSig) (*AggregatedSignature, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if len(signatures) < s.threshold {
|
|
return nil, fmt.Errorf("insufficient signatures: %d < %d", len(signatures), s.threshold)
|
|
}
|
|
|
|
// Check for threshold mode
|
|
if len(signatures) > 0 && signatures[0].IsThreshold && s.blsAggregator != nil {
|
|
blsShares := make([]threshold.SignatureShare, 0, len(signatures))
|
|
|
|
for _, sig := range signatures {
|
|
if len(sig.BLS) > 0 {
|
|
share, err := s.blsScheme.ParseSignatureShare(sig.BLS)
|
|
if err == nil {
|
|
blsShares = append(blsShares, share)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(blsShares) >= s.threshold {
|
|
blsAggSig, err := s.blsAggregator.Aggregate(ctx, message, blsShares, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BLS aggregation failed: %w", err)
|
|
}
|
|
|
|
return &AggregatedSignature{
|
|
BLSAggregated: blsAggSig.Bytes(),
|
|
SignerCount: len(signatures),
|
|
IsThreshold: true,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// Legacy BLS aggregation
|
|
blsSigsPtr := blsSigSlicePool.Get().(*[]*bls.Signature)
|
|
blsSigs := (*blsSigsPtr)[:0]
|
|
defer func() {
|
|
*blsSigsPtr = blsSigs[:0]
|
|
blsSigSlicePool.Put(blsSigsPtr)
|
|
}()
|
|
|
|
validatorIDs := make([]string, 0, len(signatures))
|
|
for _, sig := range signatures {
|
|
blsSig, err := bls.SignatureFromBytes(sig.BLS)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid BLS signature: %w", err)
|
|
}
|
|
blsSigs = append(blsSigs, blsSig)
|
|
validatorIDs = append(validatorIDs, sig.ValidatorID)
|
|
}
|
|
|
|
aggregatedBLS, err := bls.AggregateSignatures(blsSigs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("BLS aggregation failed: %w", err)
|
|
}
|
|
|
|
return &AggregatedSignature{
|
|
BLSAggregated: bls.SignatureToBytes(aggregatedBLS),
|
|
ValidatorIDs: validatorIDs,
|
|
SignerCount: len(signatures),
|
|
IsThreshold: false,
|
|
}, nil
|
|
}
|
|
|
|
// VerifyAggregatedSignature verifies an aggregated signature.
|
|
func (s *signer) VerifyAggregatedSignature(message []byte, aggSig *AggregatedSignature) bool {
|
|
return s.VerifyAggregatedSignatureWithContext(context.Background(), message, aggSig)
|
|
}
|
|
|
|
// VerifyAggregatedSignatureWithContext verifies an aggregated signature.
|
|
func (s *signer) VerifyAggregatedSignatureWithContext(ctx context.Context, message []byte, aggSig *AggregatedSignature) bool {
|
|
if ctx.Err() != nil {
|
|
return false
|
|
}
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
if aggSig.SignerCount < s.threshold {
|
|
return false
|
|
}
|
|
|
|
if aggSig.IsThreshold {
|
|
if s.blsVerifier != nil && len(aggSig.BLSAggregated) > 0 {
|
|
return s.blsVerifier.VerifyBytes(message, aggSig.BLSAggregated)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Legacy verification
|
|
blsSig, err := bls.SignatureFromBytes(aggSig.BLSAggregated)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
pubKeysPtr := pubKeySlicePool.Get().(*[]*bls.PublicKey)
|
|
pubKeys := (*pubKeysPtr)[:0]
|
|
defer func() {
|
|
*pubKeysPtr = pubKeys[:0]
|
|
pubKeySlicePool.Put(pubKeysPtr)
|
|
}()
|
|
|
|
for _, validatorID := range aggSig.ValidatorIDs {
|
|
validator, exists := s.validators[validatorID]
|
|
if !exists || !validator.Active {
|
|
return false
|
|
}
|
|
pubKeys = append(pubKeys, validator.BLSPubKey)
|
|
}
|
|
|
|
aggPubKey, err := bls.AggregatePublicKeys(pubKeys)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return bls.Verify(aggPubKey, blsSig, message)
|
|
}
|
|
|
|
// QuasarSig contains BLS and optionally Corona signature data.
|
|
type QuasarSig struct {
|
|
BLS []byte
|
|
Corona []byte
|
|
ValidatorID string
|
|
IsThreshold bool
|
|
SignerIndex int
|
|
}
|
|
|
|
// AggregatedSignature contains aggregated signatures.
|
|
type AggregatedSignature struct {
|
|
BLSAggregated []byte
|
|
CoronaAggregated []byte
|
|
ValidatorIDs []string
|
|
SignerCount int
|
|
IsThreshold bool
|
|
}
|
|
|
|
// GetActiveValidatorCount returns the number of active validators.
|
|
func (s *signer) GetActiveValidatorCount() int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
count := 0
|
|
for _, v := range s.validators {
|
|
if v.Active {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
// GetThreshold returns the consensus threshold.
|
|
func (s *signer) GetThreshold() int {
|
|
return s.threshold
|
|
}
|
|
|
|
// IsThresholdMode returns true if BLS threshold signing is enabled.
|
|
func (s *signer) IsThresholdMode() bool {
|
|
return s.blsScheme != nil
|
|
}
|
|
|
|
// IsDualThresholdMode returns true if both BLS and Corona are enabled.
|
|
func (s *signer) IsDualThresholdMode() bool {
|
|
return s.blsScheme != nil && s.coronaGroupKey != nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// Backward-compatible API
|
|
// ============================================================================
|
|
|
|
// ThresholdConfig for backward compatibility with single-scheme tests.
|
|
type ThresholdConfig struct {
|
|
SchemeID threshold.SchemeID
|
|
Threshold int
|
|
TotalParties int
|
|
KeyShares map[string]threshold.KeyShare
|
|
GroupKey threshold.PublicKey
|
|
}
|
|
|
|
// newSignerWithThresholdConfig creates a signer from ThresholdConfig.
|
|
func newSignerWithThresholdConfig(config ThresholdConfig) (*signer, error) {
|
|
if config.Threshold < 1 {
|
|
return nil, errors.New("threshold must be at least 1")
|
|
}
|
|
|
|
scheme, err := threshold.GetScheme(config.SchemeID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get threshold scheme: %w", err)
|
|
}
|
|
|
|
h := &signer{
|
|
blsKeys: make(map[string]*bls.SecretKey),
|
|
blsPubKeys: make(map[string]*bls.PublicKey),
|
|
blsSigners: make(map[string]threshold.Signer),
|
|
coronaSigners: make(map[string]*coronaThreshold.Signer),
|
|
coronaShares: make(map[string]*coronaThreshold.KeyShare),
|
|
validators: make(map[string]*Validator),
|
|
threshold: config.Threshold,
|
|
blsScheme: scheme,
|
|
blsGroupKey: config.GroupKey,
|
|
}
|
|
|
|
if config.GroupKey != nil {
|
|
h.blsAggregator, err = scheme.NewAggregator(config.GroupKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create aggregator: %w", err)
|
|
}
|
|
h.blsVerifier, err = scheme.NewVerifier(config.GroupKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create verifier: %w", err)
|
|
}
|
|
}
|
|
|
|
for id, share := range config.KeyShares {
|
|
signer, err := scheme.NewSigner(share)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create signer for %s: %w", id, err)
|
|
}
|
|
h.blsSigners[id] = signer
|
|
}
|
|
|
|
return h, nil
|
|
}
|
|
|
|
// newSignerWithThreshold is an alias for newSignerWithDualThreshold.
|
|
func newSignerWithThreshold(config SignerConfig) (*signer, error) {
|
|
return newSignerWithDualThreshold(config)
|
|
}
|
|
|
|
// GenerateThresholdKeys generates threshold keys for a single scheme.
|
|
func GenerateThresholdKeys(schemeID threshold.SchemeID, t, n int) ([]threshold.KeyShare, threshold.PublicKey, error) {
|
|
scheme, err := threshold.GetScheme(schemeID)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to get scheme: %w", err)
|
|
}
|
|
|
|
dealer, err := scheme.NewTrustedDealer(threshold.DealerConfig{
|
|
Threshold: t,
|
|
TotalParties: n,
|
|
})
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to create dealer: %w", err)
|
|
}
|
|
|
|
return dealer.GenerateShares(context.Background())
|
|
}
|
|
|
|
// GenerateDualThresholdKeys generates both BLS and Corona keys (backward compat).
|
|
func GenerateDualThresholdKeys(t, n int) (*SignerConfig, error) {
|
|
return GenerateDualKeys(t, n)
|
|
}
|
|
|
|
// ThresholdScheme returns the BLS threshold scheme.
|
|
func (s *signer) ThresholdScheme() threshold.Scheme {
|
|
return s.blsScheme
|
|
}
|
|
|
|
// ThresholdGroupKey returns the BLS group public key.
|
|
func (s *signer) ThresholdGroupKey() threshold.PublicKey {
|
|
return s.blsGroupKey
|
|
}
|
|
|
|
// SignMessageThreshold signs using BLS threshold.
|
|
func (s *signer) SignMessageThreshold(ctx context.Context, validatorID string, message []byte) (threshold.SignatureShare, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
signer, exists := s.blsSigners[validatorID]
|
|
if !exists {
|
|
return nil, fmt.Errorf("validator %s not found in BLS signers", validatorID)
|
|
}
|
|
|
|
signerIndices := make([]int, 0, len(s.blsSigners))
|
|
for _, s := range s.blsSigners {
|
|
signerIndices = append(signerIndices, s.Index())
|
|
}
|
|
|
|
return signer.SignShare(ctx, message, signerIndices, nil)
|
|
}
|
|
|
|
// AggregateThresholdSignatures aggregates BLS threshold shares.
|
|
func (s *signer) AggregateThresholdSignatures(ctx context.Context, message []byte, shares []threshold.SignatureShare) (threshold.Signature, error) {
|
|
if s.blsAggregator == nil {
|
|
return nil, errors.New("BLS aggregator not initialized")
|
|
}
|
|
|
|
if len(shares) <= s.threshold {
|
|
return nil, fmt.Errorf("insufficient shares: need at least %d, got %d", s.threshold+1, len(shares))
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
return s.blsAggregator.Aggregate(ctx, message, shares, nil)
|
|
}
|
|
|
|
// VerifyThresholdSignature verifies a BLS threshold signature.
|
|
func (s *signer) VerifyThresholdSignature(message []byte, sig threshold.Signature) bool {
|
|
if s.blsVerifier == nil {
|
|
return false
|
|
}
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.blsVerifier.Verify(message, sig)
|
|
}
|
|
|
|
// VerifyThresholdSignatureBytes verifies serialized BLS threshold signature.
|
|
func (s *signer) VerifyThresholdSignatureBytes(message, sig []byte) bool {
|
|
if s.blsVerifier == nil {
|
|
return false
|
|
}
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.blsVerifier.VerifyBytes(message, sig)
|
|
}
|
|
|
|
// AddValidatorThreshold adds a validator with BLS threshold key share.
|
|
func (s *signer) AddValidatorThreshold(id string, keyShare threshold.KeyShare, weight uint64) error {
|
|
if s.blsScheme == nil {
|
|
return errors.New("BLS threshold scheme not initialized")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
signer, err := s.blsScheme.NewSigner(keyShare)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create signer: %w", err)
|
|
}
|
|
|
|
s.blsSigners[id] = signer
|
|
|
|
s.validators[id] = &Validator{
|
|
ID: id,
|
|
Weight: weight,
|
|
Active: true,
|
|
}
|
|
|
|
return nil
|
|
}
|