Files
corona/reshare/quasar_integration.go
zeekay 30abcdf1b1 threshold: make trusted-dealer keygen explicit, drop dead ReconstructSecret, pin sub-quorum soundness
Part of the Lux threshold-crypto security rip (killing trusted-dealer /
full-key-reconstruction footguns).

- Rename threshold.GenerateKeys -> threshold.GenerateKeysTrustedDealer.
  The function samples the full secret and sets sign.K / sign.Threshold in
  one process: it IS the trusted dealer. The new name mirrors
  keyera.BootstrapTrustedDealer so the trust model is explicit and
  greppable. Doc comment now states it is for test/KAT/CT/CLI/oracle use
  only, and that production chain keygen uses keyera.Bootstrap (dealerless
  Pedersen DKG). Stays exported; every caller updated (cli, ct/dudect,
  same-package tests) plus stale comment refs in cmd, keyera, wire,
  reshare.

- Delete utils.ReconstructSecret. It Lagrange-interpolates the full secret
  from shares and had zero callers anywhere in the repo: a
  reconstruction-shaped footgun left dead. CompareSecrets and the section
  retained.

- Include threshold/minority_soundness_test.go: an adversarial negative
  pinning that a strict sub-quorum cannot assemble a signature that
  verifies under the group key (uncancelled PRF mask fails the L2-norm
  gate). A sub-quorum forgery would be a catastrophic finality break.
2026-06-26 21:13:45 -07:00

234 lines
9.7 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package reshare
// Quasar consensus integration for the Reshare / Refresh primitives.
//
// This file is the integration sketch that the consensus engineer wires
// into `~/work/lux/consensus/protocol/quasar/epoch.go`. It does NOT
// import the consensus package (to avoid a circular dependency: the
// consensus layer depends on pulsar, not the other way around). Instead
// it documents the API surface and provides helper types the caller
// can adapt directly.
//
// # Renaming (Correction 1, addressed in this PR)
//
// The current `protocol/quasar/epoch.go` uses a vocabulary that
// pre-dates Reshare and is now misleading:
//
// OLD NAME NEW NAME (this PR)
// ──────── ──────────────────
// RotateEpochKeys ReshareEpoch (or RotateEpochShares)
// EpochKeys (struct) EpochShareState
// RotateEpoch(newVals) RotateEpoch(newVals) calls Reshare(...)
// → calls GenerateKeysTrustedDealer instead of GenerateKeysTrustedDealer
//
// Concretely the existing `EpochManager.RotateEpoch` re-runs
// `coronaThreshold.GenerateKeysTrustedDealer(t, n, nil)` which generates a FRESH
// secret and a NEW group public key per epoch. With Reshare, the
// master secret and group public key are PERSISTENT across epochs;
// only the share distribution rotates. So:
//
// - At genesis: EpochManager.InitializeEpoch creates the persistent
// GroupKey via DKG (or trusted-dealer Gen for bootstrap).
// - At each epoch boundary: RotateEpoch calls Reshare under the
// hood, producing new EpochShareState that signs under the
// SAME GroupKey.
// - BetweenEpochs (optional): the same set of validators may run
// Refresh periodically to defeat mobile adversaries.
//
// # Activation circuit-breaker
//
// Before the chain commits a new EpochShareState, the new committee
// MUST emit a valid ActivationCert (activation.go) over the resharing
// transcript. The chain calls VerifyActivation; if it fails, the chain
// stays at the old epoch. See ActivationMessage for the canonical
// signed bytes.
//
// # Slashing evidence
//
// Each Complaint that disqualifies a sender is admissible as slashing
// evidence at the consensus layer. The Quasar slashing module
// (slasher TBD: ~/work/lux/consensus/protocol/quasar/slashing.go,
// not yet implemented) consumes:
//
// Evidence_Equivocation = (sender_id, complaint, commits_a, commits_b)
// Evidence_BadDelivery = (sender_id, complaint, share, blind, commits)
//
// The slasher re-runs CommitDigest equality (for equivocation) or
// VerifyShareAgainstCommits (for bad delivery) under the recorded
// transcript_hash. Successful re-verification triggers a stake
// slashing transaction signed by the slasher's authority key.
// EpochShareState is the renamed analogue of Quasar's `EpochKeys`
// struct. It carries the per-validator shares for one epoch, the
// (unchanged) group public key, and the metadata the consensus layer
// needs to rotate.
//
// Distinction from `EpochKeys`:
// - GroupKey is a POINTER, shared across epochs. The pointer is
// established at genesis and persists for the entire group lineage.
// - ValidatorSet is the new committee. Old shares from the previous
// epoch are NOT carried here — they are erased after activation.
// - Threshold may differ from previous epoch (Reshare allows
// t_old → t_new transitions). Refresh keeps t unchanged.
type EpochShareState struct {
Epoch uint64
ValidatorSet []string // ordered validator wire-identity strings
Threshold int // new threshold t_new (= t_old for Refresh)
Shares map[string]*PartyKeyShare // validator-ID → share
GroupKey *PartyGroupKey // POINTER to persistent group key
TranscriptHash [32]byte // bind to TranscriptInputs.Hash()
ActivationCert *ActivationCert // proof the chain accepted this epoch
}
// ReshareEpochInputs is the parameter bundle for one resharing round.
// The Quasar `EpochManager.ReshareEpoch` consumes it to produce the
// next EpochShareState.
//
// OldShares and NewValidators are the substantive inputs. The other
// fields are protocol context: chain ID, group ID, epoch IDs (mainly
// for transcript binding), and the threshold parameters.
//
// The caller is responsible for ensuring NewValidators is correctly
// ordered (the order determines the 0-indexed Index field of each
// PartyKeyShare). Sorted-by-public-key is the canonical choice; see
// ValidatorSetHash in transcript.go.
type ReshareEpochInputs struct {
ChainID []byte
GroupID []byte
OldEpochID uint64
NewEpochID uint64
OldValidators []string
NewValidators []string
OldShares map[int]Share // 1-indexed party ID → share
OldThreshold int
NewThreshold int
GroupKeyHash [32]byte
}
// RefreshEpochInputs is the parameter bundle for one same-committee
// Refresh round. The committee and threshold are unchanged, only the
// share distribution rotates.
type RefreshEpochInputs struct {
ChainID []byte
GroupID []byte
OldEpochID uint64
NewEpochID uint64
Validators []string // unchanged committee
Shares map[int]Share // 1-indexed party ID → share
Threshold int // unchanged threshold
GroupKeyHash [32]byte
}
// BuildTranscript converts ReshareEpochInputs into the
// TranscriptInputs ready for transcript-binding.
func (i *ReshareEpochInputs) BuildTranscript() TranscriptInputs {
oldKeys := make([][]byte, len(i.OldValidators))
for k, v := range i.OldValidators {
oldKeys[k] = []byte(v)
}
newKeys := make([][]byte, len(i.NewValidators))
for k, v := range i.NewValidators {
newKeys[k] = []byte(v)
}
return TranscriptInputs{
ChainID: i.ChainID,
GroupID: i.GroupID,
OldEpochID: i.OldEpochID,
NewEpochID: i.NewEpochID,
OldSetHash: ValidatorSetHash(oldKeys, nil),
NewSetHash: ValidatorSetHash(newKeys, nil),
ThresholdOld: uint32(i.OldThreshold),
ThresholdNew: uint32(i.NewThreshold),
GroupPublicKeyHash: i.GroupKeyHash,
Variant: "reshare",
}
}
// BuildTranscript for Refresh. Old/new sets are equal; old/new
// thresholds are equal.
func (i *RefreshEpochInputs) BuildTranscript() TranscriptInputs {
keys := make([][]byte, len(i.Validators))
for k, v := range i.Validators {
keys[k] = []byte(v)
}
hash := ValidatorSetHash(keys, nil)
return TranscriptInputs{
ChainID: i.ChainID,
GroupID: i.GroupID,
OldEpochID: i.OldEpochID,
NewEpochID: i.NewEpochID,
OldSetHash: hash,
NewSetHash: hash,
ThresholdOld: uint32(i.Threshold),
ThresholdNew: uint32(i.Threshold),
GroupPublicKeyHash: i.GroupKeyHash,
Variant: "refresh",
}
}
// QuasarIntegrationChecklist is the development punch-list for wiring
// this package into `protocol/quasar/epoch.go`. Each item is
// individually verifiable:
//
// [1] Rename EpochManager.RotateEpoch → ReshareEpoch (or keep the
// name but change the body). Caller code in quasar.go and the
// `validator-rotation` consensus path need updates.
//
// [2] Replace `coronaThreshold.GenerateKeysTrustedDealer` call with a Reshare
// invocation: extract OldShares from current EpochShareState,
// feed into Reshare(r, oldShares, tOld, newSet, tNew, randSource).
//
// [3] Wire transport: the resharing exchange happens over the
// Quasar p2p mesh. The p2p layer must expose authenticated
// point-to-point channels OLD → NEW (an existing facility for
// BLS aggregation; reuse it). Do NOT introduce a new transport.
//
// [4] Add commit broadcast: each old party commits to its g_i
// polynomial via CommitToPoly and broadcasts the digest via
// CommitDigest.
//
// [5] Add complaint workflow: each new party verifies received
// shares via VerifyShareAgainstCommits; on failure emits a
// signed Complaint. Complaints aggregate at the chain layer
// until DisqualificationThreshold is met or the round timer
// expires.
//
// [6] Compute disqualified set deterministically via
// ComputeDisqualifiedSet, then re-shape the qualified quorum
// via FilterQualifiedQuorum.
//
// [7] If the qualified quorum drops below tOld, the round FAILS;
// the chain emits a "reshare-failed" event and stays at the old
// epoch.
//
// [8] On success, every new validator computes its PartyKeyShare
// via PartyKeyShareFromShare. The committee then jointly produces
// an ActivationCert by signing the activation message under the
// UNCHANGED GroupKey.
//
// [9] The chain runs VerifyActivation on the activation cert;
// success commits the new EpochShareState.
//
// [10] Old shares are erased via EraseShare; the old EpochShareState
// is removed from epochHistory after a configurable grace
// period (e.g. 6 epochs of cross-epoch verification window).
//
// [11] Slashing evidence (Complaints + commit pairs for
// equivocation) is forwarded to the slashing module. The
// slasher reverifies on-chain and emits the slashing tx.
//
// [12] Backwards compatibility: `EpochManager.GenerateKeysTrustedDealer` is
// KEPT as the genesis path (one-time bootstrap). Subsequent
// epoch rotations go through ReshareEpoch only.
//
// Realistic effort estimate: 2-3 weeks for an engineer familiar with
// Quasar's consensus internals to land items [1]-[10] with integration
// tests. Items [11]-[12] are an additional 1-2 weeks each (slashing
// evidence ingestion is more complex than this checklist suggests
// because the evidence format must round-trip through the chain's
// transaction encoding).
type QuasarIntegrationChecklist struct{}