Files
zeekay f6140dfa61 corona: fix Pulsar→Corona naming relics in protocols/corona wrapper
This package wires the github.com/luxfi/corona Module-LWE kernel
(Ringtail/Raccoon, q=0x1000000004A01), but carried stale "Pulsar"
labels from when Corona was historically named Pulsar:

- package doc claimed it wired the Pulsar kernel (github.com/luxfi/pulsar)
  and was "the equivalent of protocols/corona" — self-contradictory; the
  imports are github.com/luxfi/corona/{keyera,threshold}. Rewrote the doc
  to state the scheme accurately and note the Corona-vs-Pulsar distinction
  (both Module-LWE; Pulsar is FIPS-204/ML-DSA q=8380417, Corona is
  Ringtail/Raccoon q=0x1000000004A01).
- error strings "pulsar:" → "corona:" (sentinel identities unchanged;
  substrings asserted by tests preserved).
- exported aliases PulsarKeyEraID/PulsarGroupID → CoronaKeyEraID/
  CoronaGroupID (the corona kernel already uses the Corona* names; these
  wrappers had zero external consumers across ~/work/lux).

No wire format, modulus, or test vector changed. Doc-, string-, and
identifier-only. corona pkg builds + tests green.
2026-06-27 16:29:18 -07:00

224 lines
7.3 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package corona
import (
"bytes"
"crypto/rand"
"errors"
"strings"
"testing"
"github.com/luxfi/threshold/pkg/party"
)
// The corona package is a thin alias surface over luxfi/corona/{keyera,threshold}.
// These tests pin the argument-validation contracts in Bootstrap / Reshare /
// Reanchor / ShareForParty and the alias-surface round-trip for the trusted-
// dealer keygen + Verify / VerifyBatch / VerifyBatchAll. They are package
// tests (not _test) so unexported helpers (validatorIDs) can be reached.
// -----------------------------------------------------------------------------
// Bootstrap — argument validation
// -----------------------------------------------------------------------------
func TestBootstrap_RejectsEmptyValidators(t *testing.T) {
era, err := Bootstrap(1, nil, CoronaGroupID(0), 0, rand.Reader)
if era != nil {
t.Fatalf("era should be nil on error, got %v", era)
}
if !errors.Is(err, ErrEmptyValidators) {
t.Fatalf("want ErrEmptyValidators, got %v", err)
}
}
func TestBootstrap_RejectsThresholdZero(t *testing.T) {
_, err := Bootstrap(0, []party.ID{"a", "b"}, CoronaGroupID(0), 0, rand.Reader)
if !errors.Is(err, ErrInvalidThreshold) {
t.Fatalf("want ErrInvalidThreshold, got %v", err)
}
}
func TestBootstrap_RejectsThresholdAboveN(t *testing.T) {
_, err := Bootstrap(3, []party.ID{"a", "b"}, CoronaGroupID(0), 0, rand.Reader)
if !errors.Is(err, ErrInvalidThreshold) {
t.Fatalf("want ErrInvalidThreshold, got %v", err)
}
}
func TestBootstrap_NilEntropyDefaultsToCryptoRand(t *testing.T) {
// Passing nil entropy must NOT panic and must not return an entropy-related
// error — Bootstrap is documented to fall back to crypto/rand.Reader.
era, err := Bootstrap(1, []party.ID{"only"}, CoronaGroupID(0), 0, nil)
if err != nil {
// Bootstrap may still fail for kernel reasons in a constrained env;
// the contract we are pinning here is "no panic from nil entropy".
// We accept either a non-nil era or a non-entropy error.
t.Logf("kernel returned err=%v (acceptable so long as no panic)", err)
}
_ = era
}
// -----------------------------------------------------------------------------
// Reshare — argument validation
// -----------------------------------------------------------------------------
func TestReshare_RejectsNilKeyEra(t *testing.T) {
_, err := Reshare(nil, []party.ID{"a"}, 1, rand.Reader)
if err == nil || !strings.Contains(err.Error(), "nil key era") {
t.Fatalf("want nil-key-era error, got %v", err)
}
}
func TestReshare_RejectsEmptyValidators(t *testing.T) {
// Even with a nil era we expect the empty-validators path to short-circuit
// only when era is non-nil — confirm the nil-era guard wins first.
_, err := Reshare(nil, nil, 1, rand.Reader)
if err == nil {
t.Fatal("expected error, got nil")
}
// And confirm a synthesized empty-validators path on a non-nil era hits
// ErrEmptyValidators if reachable. (Constructing a real *KeyEra here is
// out of scope; the nil-era guard above covers the validation surface.)
}
func TestReshare_RejectsThresholdZeroOrAboveN(t *testing.T) {
// We test through Bootstrap-then-Reshare to exercise the t guard, but
// can also build a synthetic era. The nil-era path is the most reliable
// here without a full Bootstrap. The threshold guard is tested via
// Reanchor below which shares the same shape.
_, err := Reshare(nil, []party.ID{"a"}, 2, rand.Reader)
if err == nil {
t.Fatal("expected error, got nil")
}
}
// -----------------------------------------------------------------------------
// Reanchor — argument validation
// -----------------------------------------------------------------------------
func TestReanchor_RejectsEmptyValidators(t *testing.T) {
_, err := Reanchor(nil, 1, nil, CoronaGroupID(0), rand.Reader)
if !errors.Is(err, ErrEmptyValidators) {
t.Fatalf("want ErrEmptyValidators, got %v", err)
}
}
func TestReanchor_RejectsThresholdZero(t *testing.T) {
_, err := Reanchor(nil, 0, []party.ID{"a"}, CoronaGroupID(0), rand.Reader)
if !errors.Is(err, ErrInvalidThreshold) {
t.Fatalf("want ErrInvalidThreshold, got %v", err)
}
}
func TestReanchor_RejectsThresholdAboveN(t *testing.T) {
_, err := Reanchor(nil, 2, []party.ID{"a"}, CoronaGroupID(0), rand.Reader)
if !errors.Is(err, ErrInvalidThreshold) {
t.Fatalf("want ErrInvalidThreshold, got %v", err)
}
}
// -----------------------------------------------------------------------------
// ShareForParty
// -----------------------------------------------------------------------------
func TestShareForParty_NilState(t *testing.T) {
share, err := ShareForParty(nil, "anyone")
if share != nil {
t.Fatalf("share should be nil, got %v", share)
}
if err == nil || !strings.Contains(err.Error(), "nil share state") {
t.Fatalf("want nil-state error, got %v", err)
}
}
func TestShareForParty_PartyNotInSet(t *testing.T) {
state := &EpochShareState{Shares: map[string]*KeyShare{
"alice": {},
}}
_, err := ShareForParty(state, party.ID("eve"))
if !errors.Is(err, ErrPartyNotInSet) {
t.Fatalf("want ErrPartyNotInSet, got %v", err)
}
if !strings.Contains(err.Error(), "eve") {
t.Fatalf("error should include the missing party id, got %v", err)
}
}
func TestShareForParty_HappyPath(t *testing.T) {
want := &KeyShare{}
state := &EpochShareState{Shares: map[string]*KeyShare{
"alice": want,
}}
got, err := ShareForParty(state, party.ID("alice"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != want {
t.Fatalf("share pointer mismatch: got %p want %p", got, want)
}
}
// -----------------------------------------------------------------------------
// Alias surface — NewParams + trusted-dealer GenerateKeys + Verify roundtrip
// -----------------------------------------------------------------------------
func TestNewParams_NonNil(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatalf("NewParams: %v", err)
}
if p == nil {
t.Fatal("NewParams returned nil with no error")
}
}
func TestGenerateKeys_TrustedDealer_SmokeRoundtrip(t *testing.T) {
// Smallest committee that exercises the threshold path: t-of-n = 2-of-3.
const tThreshold, n = 2, 3
seed := bytes.NewReader(bytes.Repeat([]byte{0xA5}, 4096))
shares, gk, err := GenerateKeys(tThreshold, n, seed)
if err != nil {
t.Fatalf("GenerateKeys: %v", err)
}
if gk == nil {
t.Fatal("GroupKey should be non-nil on success")
}
if got := len(shares); got != n {
t.Fatalf("want %d shares, got %d", n, got)
}
for i, sh := range shares {
if sh == nil {
t.Fatalf("shares[%d] is nil", i)
}
}
}
// -----------------------------------------------------------------------------
// validatorIDs (unexported helper)
// -----------------------------------------------------------------------------
func TestValidatorIDs_PreservesOrderAndLength(t *testing.T) {
in := []party.ID{"alice", "bob", "carol"}
out := validatorIDs(in)
if len(out) != len(in) {
t.Fatalf("len mismatch: in=%d out=%d", len(in), len(out))
}
for i := range in {
if string(in[i]) != out[i] {
t.Fatalf("element %d mismatch: in=%q out=%q", i, in[i], out[i])
}
}
}
func TestValidatorIDs_EmptyInput(t *testing.T) {
out := validatorIDs(nil)
if out == nil {
t.Fatal("validatorIDs(nil) should return a non-nil empty slice, not nil")
}
if len(out) != 0 {
t.Fatalf("want empty slice, got len=%d", len(out))
}
}