mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
The dealerless RSS Pulsar (ML-DSA) leg is only stock-FIPS-204-signable for a small committee (C(N,N-T+1) replicated shares grow combinatorially). Adds the consensus-side bound — delegated to luxfi/dkg v0.3.0 rss.ValidateCommittee for ONE definition — plus ClampMithrilCommitteeSize and the grouped-epoch sortition cap, with the full security argument: consensus quorum != signing committee, so broad security comes from Avalanche subsampling + rotating committees + 2/3 group quorum + dual-PQ AND-mode, never from committee size. Default group size (3) already complies; the cap only constrains over-large configs. quasar suite green (77s). NOTE: pre-existing luxfi/[email protected] go.sum mismatch (force-moved tag) breaks the whole-repo 'go build ./...' on main independent of this change (proven on clean HEAD); the quasar package and its full test suite are green.
68 lines
2.4 KiB
Go
68 lines
2.4 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package quasar
|
|
|
|
import "testing"
|
|
|
|
// TestValidateMithrilSigningCommittee pins the consensus-side Pulsar dealerless-
|
|
// RSS committee bound 2 ≤ T ≤ N ≤ 6, delegated to luxfi/dkg/rss.
|
|
func TestValidateMithrilSigningCommittee(t *testing.T) {
|
|
valid := [][2]int{
|
|
{2, 2}, {2, 3}, {3, 3}, {2, 4}, {3, 4}, {4, 4},
|
|
{2, 5}, {3, 5}, {4, 5}, {5, 5},
|
|
{2, 6}, {3, 6}, {4, 6}, {5, 6}, {6, 6},
|
|
}
|
|
for _, tn := range valid {
|
|
if err := ValidateMithrilSigningCommittee(tn[0], tn[1]); err != nil {
|
|
t.Fatalf("rejected viable Pulsar committee (T=%d,N=%d): %v", tn[0], tn[1], err)
|
|
}
|
|
}
|
|
bad := [][2]int{{1, 2}, {3, 2}, {2, 7}, {7, 7}, {4, 100}}
|
|
for _, tn := range bad {
|
|
if err := ValidateMithrilSigningCommittee(tn[0], tn[1]); err == nil {
|
|
t.Fatalf("admitted non-viable Pulsar committee (T=%d,N=%d)", tn[0], tn[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRecommendedMithrilCommitteeIsViable(t *testing.T) {
|
|
tt, n := RecommendedMithrilCommittee()
|
|
if tt != 4 || n != 6 {
|
|
t.Fatalf("RecommendedMithrilCommittee()=(%d,%d), want (4,6)", tt, n)
|
|
}
|
|
if err := ValidateMithrilSigningCommittee(tt, n); err != nil {
|
|
t.Fatalf("recommended committee is not viable: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClampMithrilCommitteeSize(t *testing.T) {
|
|
cases := []struct{ in, want int }{
|
|
{100, MithrilMaxCommittee}, {7, MithrilMaxCommittee}, {6, 6},
|
|
{3, 3}, {2, 3}, {0, 3},
|
|
}
|
|
for _, c := range cases {
|
|
if got := ClampMithrilCommitteeSize(c.in); got != c.want {
|
|
t.Errorf("ClampMithrilCommitteeSize(%d)=%d, want %d", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestGroupedEpochManagerCapsAtMithrilBound proves the sortition cap: an
|
|
// over-large group request is clamped to the Mithril viability bound so every
|
|
// signing group can host a dealerless-RSS Pulsar key.
|
|
func TestGroupedEpochManagerCapsAtMithrilBound(t *testing.T) {
|
|
gem := NewGroupedEpochManager(10, 7, 3)
|
|
if gem.groupSize > MithrilMaxCommittee {
|
|
t.Fatalf("group size %d exceeds Mithril bound %d", gem.groupSize, MithrilMaxCommittee)
|
|
}
|
|
if gem.groupSize != MithrilMaxCommittee {
|
|
t.Fatalf("group size %d, want clamp to %d", gem.groupSize, MithrilMaxCommittee)
|
|
}
|
|
// Default (3) must be unchanged — the cap only constrains over-large configs.
|
|
gem3 := NewGroupedEpochManager(3, 2, 3)
|
|
if gem3.groupSize != 3 {
|
|
t.Fatalf("default group size changed to %d, want 3", gem3.groupSize)
|
|
}
|
|
}
|