Files
consensus/protocol/quasar/dual_threshold_test.go
zeekay 24b7cd2154 consensus: route corona + pulsar through threshold/protocols alias
Replaces every direct import of luxfi/corona/* and luxfi/pulsar/ref/go
in consensus with the canonical luxfi/threshold/protocols/{corona,pulsar}
alias surface. Pure import swap + go.mod bump; no behaviour change.

Files touched (16):

  bench/pq_modes_bench_test.go
  engine/pq/crypto.go
  engine/pq/engine.go
  pkg/wire/policies.go
  protocol/quasar/cert_size_compare_test.go
  protocol/quasar/corona_gob.go
  protocol/quasar/dual_threshold_test.go
  protocol/quasar/epoch.go
  protocol/quasar/epoch_test.go
  protocol/quasar/gpu_accel.go
  protocol/quasar/grouped_threshold.go
  protocol/quasar/quasar.go
  protocol/quasar/types.go
  protocol/quasar/wave_signer.go
  protocol/quasar/wave_signer_test.go
  protocol/quasar/witness.go
  protocol/quasar/witness_pulsar_test.go

Import path swaps:

  luxfi/corona/threshold        -> luxfi/threshold/protocols/corona
  luxfi/corona/keyera           -> luxfi/threshold/protocols/corona/keyera
  luxfi/corona/primitives       -> luxfi/threshold/protocols/corona/primitives
  luxfi/corona/reshare          -> luxfi/threshold/protocols/corona/reshare
  luxfi/corona/sign             -> luxfi/threshold/protocols/corona/sign
  luxfi/corona/gpu              -> luxfi/threshold/protocols/corona/gpu
  luxfi/pulsar/ref/go/pkg/pulsar -> luxfi/threshold/protocols/pulsar

The aliased package name "coronaThreshold" used by quasar / engine/pq /
pkg/wire identifiers is preserved at the call sites (it now refers to
the threshold/protocols/corona alias package). engine/pq/crypto.go is
the only file where the package was bare-imported as `threshold`; that
identifier was renamed to `corona` to match the new alias path.

go.mod:

  - luxfi/threshold bumped to a branch pseudo-version that carries the
    new alias subpackages; will pin to a stable tag once threshold's
    companion commit lands and a release is cut.
  - luxfi/corona and luxfi/pulsar drop to indirect (consensus now
    reaches them only via threshold/protocols).
  - luxfi/math v1.4.0 -> v1.4.1 and luxfi/metric v1.5.0 -> v1.5.7 are
    transitive minimum-version bumps pulled by the new threshold
    requirement; they are no-op for consensus call sites.

Comment-only references to "github.com/luxfi/corona/..." in bls.go,
round_digest.go, reshare_epoch.go, comprehensive_test.go,
engine_test.go, quasar_bench_test.go and config/pq_mode.go are
documentation pointers to where the underlying implementation lives;
they are accurate either way and are left unchanged (out of refactor
scope).

Layering shape after:

  consensus -> threshold/protocols/{corona,pulsar}  (stable alias)
                 |
                 v
              luxfi/corona, luxfi/pulsar  (primitive implementations)

Closes the 22-file layering violation flagged by the 2026-05-31
integration audit. Tests pass: ./engine/pq/..., ./pkg/wire/...,
./protocol/quasar/... (all key cases — TestEpochManager*,
TestRoundSigner*, TestVerifyPQFinality*, TestBLSThreshold*,
TestDualSigningFlow, TestCoronaCertSize* — green; the pre-existing
TestGroupedEpochManager_ParallelKeygen perf-bound flake on slow
machines was reproduced on unmodified main, so it is not a
regression).
2026-05-31 12:39:47 -07:00

266 lines
9.7 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// Test BLS threshold signing for quantum-safe consensus.
// Note: Full Corona integration requires multi-round protocol coordination.
package quasar
import (
"context"
"testing"
"github.com/luxfi/crypto/threshold"
coronaThreshold "github.com/luxfi/threshold/protocols/corona"
"github.com/stretchr/testify/require"
)
// TestBLSThresholdSigningFlow tests BLS threshold signing via the Quasar signer.
func TestBLSThresholdSigningFlow(t *testing.T) {
// Generate BLS threshold keys for 2-of-3 threshold
shares, groupKey, err := GenerateThresholdKeys(threshold.SchemeBLS, 2, 3)
require.NoError(t, err, "Failed to generate BLS threshold keys")
require.Len(t, shares, 3, "Should have 3 key shares")
require.NotNil(t, groupKey, "Group key should not be nil")
t.Logf("Generated BLS threshold keys: t=2, n=3")
t.Logf("Group key size: %d bytes", len(groupKey.Bytes()))
// Create key shares map
keyShares := make(map[string]threshold.KeyShare)
keyShares["v0"] = shares[0]
keyShares["v1"] = shares[1]
keyShares["v2"] = shares[2]
// Create Quasar signer with BLS threshold signing
config := ThresholdConfig{
SchemeID: threshold.SchemeBLS,
Threshold: 2,
TotalParties: 3,
KeyShares: keyShares,
GroupKey: groupKey,
}
h, err := NewSignerWithThresholdConfig(config)
require.NoError(t, err, "Failed to create Quasar signer")
// Verify threshold mode is enabled
require.True(t, h.IsThresholdMode(), "Should be in threshold mode")
// Sign a message with 2 validators (meeting 2-of-3 threshold)
ctx := context.Background()
message := []byte("consensus block for quantum security")
// Get signature shares from validators
share0, err := h.SignMessageThreshold(ctx, "v0", message)
require.NoError(t, err, "Validator 0 signing failed")
require.NotNil(t, share0, "Signature share should not be nil")
share1, err := h.SignMessageThreshold(ctx, "v1", message)
require.NoError(t, err, "Validator 1 signing failed")
t.Logf("Share 0: %d bytes", len(share0.Bytes()))
t.Logf("Share 1: %d bytes", len(share1.Bytes()))
// Aggregate signatures (need t+1=3 shares for 2-of-3, but API expects >threshold)
// Getting a third share
share2, err := h.SignMessageThreshold(ctx, "v2", message)
require.NoError(t, err, "Validator 2 signing failed")
aggSig, err := h.AggregateThresholdSignatures(ctx, message, []threshold.SignatureShare{share0, share1, share2})
require.NoError(t, err, "Signature aggregation failed")
require.NotNil(t, aggSig, "Aggregated signature should not be nil")
t.Logf("Aggregated signature: %d bytes", len(aggSig.Bytes()))
// Verify the aggregated signature
valid := h.VerifyThresholdSignature(message, aggSig)
require.True(t, valid, "Aggregated signature should verify")
// Also verify via bytes
validBytes := h.VerifyThresholdSignatureBytes(message, aggSig.Bytes())
require.True(t, validBytes, "Serialized signature should verify")
t.Log("✓ BLS threshold signing flow complete")
}
// TestBLSThresholdInsufficientShares tests that aggregation fails without enough shares.
func TestBLSThresholdInsufficientShares(t *testing.T) {
shares, groupKey, err := GenerateThresholdKeys(threshold.SchemeBLS, 2, 3)
require.NoError(t, err)
keyShares := make(map[string]threshold.KeyShare)
keyShares["v0"] = shares[0]
keyShares["v1"] = shares[1]
keyShares["v2"] = shares[2]
h, err := NewSignerWithThresholdConfig(ThresholdConfig{
SchemeID: threshold.SchemeBLS,
Threshold: 2,
TotalParties: 3,
KeyShares: keyShares,
GroupKey: groupKey,
})
require.NoError(t, err)
ctx := context.Background()
message := []byte("need more signatures")
// Sign with only 2 validators (need 3 for t+1 with threshold=2)
share0, _ := h.SignMessageThreshold(ctx, "v0", message)
share1, _ := h.SignMessageThreshold(ctx, "v1", message)
// Aggregation should fail with insufficient signatures
_, err = h.AggregateThresholdSignatures(ctx, message, []threshold.SignatureShare{share0, share1})
require.Error(t, err, "Should fail with insufficient signatures")
}
// TestBLSThresholdWrongMessage tests that verification fails for wrong message.
func TestBLSThresholdWrongMessage(t *testing.T) {
shares, groupKey, err := GenerateThresholdKeys(threshold.SchemeBLS, 2, 3)
require.NoError(t, err)
keyShares := make(map[string]threshold.KeyShare)
keyShares["v0"] = shares[0]
keyShares["v1"] = shares[1]
keyShares["v2"] = shares[2]
h, err := NewSignerWithThresholdConfig(ThresholdConfig{
SchemeID: threshold.SchemeBLS,
Threshold: 2,
TotalParties: 3,
KeyShares: keyShares,
GroupKey: groupKey,
})
require.NoError(t, err)
ctx := context.Background()
message := []byte("original message")
wrongMessage := []byte("tampered message")
share0, _ := h.SignMessageThreshold(ctx, "v0", message)
share1, _ := h.SignMessageThreshold(ctx, "v1", message)
share2, _ := h.SignMessageThreshold(ctx, "v2", message)
aggSig, err := h.AggregateThresholdSignatures(ctx, message, []threshold.SignatureShare{share0, share1, share2})
require.NoError(t, err)
// Verify with wrong message should fail
valid := h.VerifyThresholdSignature(wrongMessage, aggSig)
require.False(t, valid, "Verification should fail for wrong message")
}
// TestDualThresholdKeyGeneration tests that dual key generation works.
func TestDualThresholdKeyGeneration(t *testing.T) {
config, err := GenerateDualThresholdKeys(2, 3)
require.NoError(t, err, "Failed to generate dual threshold keys")
// Verify both BLS and Corona keys were generated
require.NotNil(t, config.BLSGroupKey, "BLS group key should not be nil")
require.NotNil(t, config.CoronaGroupKey, "Corona group key should not be nil")
require.Len(t, config.BLSKeyShares, 3, "Should have 3 BLS key shares")
require.Len(t, config.CoronaShares, 3, "Should have 3 Corona key shares")
t.Logf("BLS group key: %d bytes", len(config.BLSGroupKey.Bytes()))
t.Logf("Corona group key: %d bytes", len(config.CoronaGroupKey.Bytes()))
t.Log("✓ Dual threshold key generation works")
}
// TestDualSigningFlow tests the full BLS + Corona parallel signing flow.
// This is how validators sign blocks in quantum-safe consensus.
func TestDualSigningFlow(t *testing.T) {
// Generate dual keys (would be done at epoch start in production)
config, err := GenerateDualKeys(2, 3)
require.NoError(t, err, "Failed to generate dual keys")
// Create Quasar signer with dual threshold signing
h, err := NewSignerWithDualThreshold(*config)
require.NoError(t, err, "Failed to create Quasar signer")
require.True(t, h.IsDualThresholdMode(), "Should be in dual threshold mode")
ctx := context.Background()
message := []byte("block hash for quantum-safe consensus")
sessionID := 1
prfKey := []byte("prf-key-for-signing-session-!!")
validatorIDs := []string{"v0", "v1", "v2"}
// === ROUND 1: All validators compute BLS share + Corona D+MACs in parallel ===
t.Log("=== Round 1: BLS signing + Corona D matrices ===")
blsSigs := make([]*QuasarSig, 3)
allRound1 := make(map[int]*coronaThreshold.Round1Data)
for i, vid := range validatorIDs {
blsSig, rtRound1, err := h.DualSignRound1(ctx, vid, message, sessionID, prfKey)
require.NoError(t, err, "Round1 failed for %s", vid)
require.NotNil(t, blsSig, "BLS signature should not be nil")
require.NotNil(t, rtRound1, "Corona Round1 data should not be nil")
blsSigs[i] = blsSig
allRound1[rtRound1.PartyID] = rtRound1
t.Logf(" %s: BLS share=%d bytes, Corona D=%dx%d", vid, len(blsSig.BLS), len(rtRound1.D), len(rtRound1.D[0]))
}
// === BLS IS DONE: Can aggregate immediately ===
t.Log("=== BLS Aggregation (single round) ===")
blsAggSig, err := h.AggregateSignatures(message, blsSigs)
require.NoError(t, err, "BLS aggregation failed")
require.NotNil(t, blsAggSig, "BLS aggregated signature should not be nil")
t.Logf(" BLS aggregated: %d bytes", len(blsAggSig.BLSAggregated))
// Verify BLS immediately
blsValid := h.VerifyAggregatedSignature(message, blsAggSig)
require.True(t, blsValid, "BLS signature verification failed")
t.Log(" BLS: ✓ verified")
// === Corona uses the native package directly for full 2-round flow ===
// This demonstrates the protocol but in production would use consensus messages
t.Log("=== Corona 2-Round Protocol (via native package) ===")
// Use the native corona threshold package directly
rtShares := config.CoronaShares
rtGroupKey := config.CoronaGroupKey
signers := make([]*coronaThreshold.Signer, 3)
for i := 0; i < 3; i++ {
signers[i] = coronaThreshold.NewSigner(rtShares[validatorIDs[i]])
}
signerIDs := []int{0, 1, 2}
messageStr := string(message)
// Round 1: All parties compute D + MACs
rtRound1Data := make(map[int]*coronaThreshold.Round1Data)
for _, signer := range signers {
data := signer.Round1(sessionID, prfKey, signerIDs)
rtRound1Data[data.PartyID] = data
}
t.Log(" Round1: D matrices computed")
// Round 2: All parties compute z shares
rtRound2Data := make(map[int]*coronaThreshold.Round2Data)
for _, signer := range signers {
data, err := signer.Round2(sessionID, messageStr, prfKey, signerIDs, rtRound1Data)
require.NoError(t, err, "Corona Round2 failed")
rtRound2Data[data.PartyID] = data
}
t.Log(" Round2: z shares computed")
// Finalize: Any signer aggregates
rtSig, err := signers[0].Finalize(rtRound2Data)
require.NoError(t, err, "Corona finalization failed")
require.NotNil(t, rtSig, "Corona signature should not be nil")
t.Logf(" Finalize: Z=%d, Delta=%d", len(rtSig.Z), len(rtSig.Delta))
// Verify Corona
rtValid := coronaThreshold.Verify(rtGroupKey, messageStr, rtSig)
require.True(t, rtValid, "Corona signature verification failed")
t.Log(" Corona: ✓ verified")
t.Log("✓ Dual BLS + Corona signing flow complete")
t.Log(" - BLS: 1 round, aggregated, verified")
t.Log(" - Corona: 2 rounds, aggregated, verified (post-quantum)")
}