//! Bootstrap and continuously reshare threshold secrets. //! //! This module wires threshold-key management into consensus without owning the //! application's state machine or private-key policy. It provides two public //! entry points: //! //! - [`bootstrap`] runs a contained, one-shot DKG chain that trustlessly creates //! an initial threshold secret. //! - [`reshare`] runs alongside an application chain and continuously rotates //! threshold shares across epochs. //! //! Both paths produce or consume [`types::EpochInfo`], the public artifact that //! describes the threshold output for an epoch. The application stores that //! artifact in its own blocks and installs epoch-scoped schemes through a //! [`Registrar`]. //! //! # Application Contract //! //! Application blocks implement [`ReshareBlock`] and carry at most one //! [`types::Payload`]. Connect an application to the reshare mailbox by wrapping //! it in [`reshare::Application`], which drives both sides of the contract: //! //! - For proposals, the wrapper selects and fetches the payload to include (a //! dealer log from the midpoint onward, the epoch info on the final block) and //! hands it to the application through [`reshare::Input`]. The //! application takes it in its own `propose` and attaches it to the block it //! builds, because only the application can build its block type. It does not //! talk to the reshare mailbox or track epoch boundaries itself. //! - For verification, the wrapper rejects a final block whose payload does not //! match the independently constructed [`types::EpochInfo`], and rejects stray //! payloads on early non-final blocks, so the application does not implement //! these checks by hand. //! //! The protocol also requires the application to provide a [`SecretStore`]. //! Secret storage is intentionally user-owned: deployments differ on encryption, //! access control, hardware isolation, backups, and pruning. Anything written to //! this trait is private ceremony material and must be protected by the //! application's security policy. //! //! # State Sync //! //! Reshare supports nodes that join through state sync. A node can participate in //! the synced epoch's reshare ceremony when its certified floor is at or before //! the epoch midpoint, because marshal replays the complete dealer-log inclusion //! window. A later floor has skipped part of that public history, so the reshare //! actor follows that ceremony for the rest of the epoch instead. //! //! Follower mode affects only resharing. State-sync startup first registers the //! certified current-epoch consensus scheme, so a node with a recovered share can //! still sign ordinary non-boundary blocks. It cannot locally derive the next //! [`types::EpochInfo`] needed to propose or complete verification of the final //! block, and resumes resharing after learning that block's externally finalized //! outcome. //! //! A `player` that missed private dealings may need public reveals to recover its //! share and must treat a revealed share as public. To preserve share privacy, a //! future player should state sync while it is still a `next_player` and be online //! before the early dealing window. //! //! This timing makes it safe for [`ParticipantsProvider`] to be backed by chain //! state (e.g., a staking contract). The chain can announce future players first, //! giving those nodes an epoch to state sync before their shares are needed. //! //! A node beginning state sync has no application state from which to resolve //! participant reachability. Everything required to connect to the active //! committee therefore rides in //! [`types::EpochInfo`] itself: the key-only participant sets and the //! transport [`network::Directory`] for those participants. The provider hooks //! are consulted only while building or verifying an epoch's final block, //! which only fully synced nodes do. //! //! Before starting either actor, initialize one [`state_sync::Plan`] under a //! stable node-wide partition prefix and clone it into the orchestrator and //! reshare configurations. The plan durably records fresh state-sync material //! before the actors start, so a node can restart immediately after state sync //! completes. Both actors share one recovery decision, and the plan removes //! stale material once marshal's recovered epoch advances beyond the synced //! epoch. This API is independent of the optional [`crate::stateful`] actor. //! //! [`probe`] fixes the state-sync floor and the epoch info atomically: the //! floor is the highest finalization from an `f + 1` sample of the configured //! bootstrap committee, and the epoch info is fetched for that floor's own //! epoch. The actors therefore always start in the floor's epoch with its //! public info in hand. If the network crosses an epoch boundary while //! application state sync is still running, the node starts at the floor's //! epoch and catches up through ordinary marshal delivery: backup vote or //! certificate traffic from a future epoch hints marshal to fetch the missing //! boundary finalization. See [`probe`] for the bootstrap trust model. //! //! # Peer Activation //! //! DKG peer identities remain key-only in ceremony artifacts and all wire //! messages. Transport-specific reachability lives in the //! [`network::Directory`] embedded in each [`types::EpochInfo`], so activation //! through [`network::Manager`] consumes only in-band data: //! //! - One-shot bootstrap activates epoch zero from its configured directory //! before registering its DKG channel. //! - A fresh-node probe activates its configured bootstrap snapshot and //! directory when the first subscriber appears, before requesting a latest //! finalization. //! - Continuous operation activates an epoch from the epoch's own //! [`types::EpochInfo`] after its readiness gate opens and before Simplex or //! its epoch channels start. //! //! Restart and state-sync entry activate the recovered epoch from the same //! certificate-backed [`types::EpochInfo`] as uninterrupted operation, so no //! out-of-band registry access is required during recovery. //! //! # Marshal Retention //! //! DKG startup relies on marshal's local finalized block archive unless the node //! is entering through one-time state sync. On an ordinary restart, the active //! epoch is derived from marshal's processed height, and the public //! [`types::EpochInfo`] for that epoch is loaded from the finalized boundary //! block that introduced it. //! //! For epoch zero, that boundary is height zero. For later epochs, the boundary //! is the final block of the previous epoch: //! //! ```text //! boundary(current_epoch) = last_block(current_epoch - 1) //! ``` //! //! An operator running stateful pruning MUST keep marshal's finalized block //! retention window at least one full epoch wide, so the previous epoch's //! boundary block survives until the current epoch finishes. Concretely, the //! marshal retention floor configured through the stateful //! [`PruneConfig`](crate::stateful::PruneConfig) //! (`max_pending_acks + 1 + retained_marshal_blocks` finalized blocks) MUST be //! greater than or equal to the DKG epoch length (`blocks_per_epoch`). DKG does //! not need blocks before that previous boundary for ordinary restart, but it //! does need the boundary block itself to recover the epoch's public threshold //! output, participant set, and Simplex floor commitment. //! //! This coupling is the operator's responsibility. The two knobs are configured //! independently: `blocks_per_epoch` is a DKG configuration, while the marshal //! retention floor is set on the stateful //! [`PruneConfig`](crate::stateful::PruneConfig). The library cannot enforce the //! relationship, and no runtime check couples them //! ([`PruneConfig::assert_valid`](crate::stateful::PruneConfig::assert_valid) //! only compares marshal and QMDB retention). Pruning the boundary before the //! current epoch finishes leaves a restarting validator without the local public //! material required for normal recovery, and the orchestrator panics on startup //! with a `missing finalized boundary block` error. //! //! Nodes that serve `dkg::probe` responses for other peers also need the //! corresponding boundary finalization and boundary block for every epoch they //! intend to serve. //! //! See [`probe`], [`fence`], [`orchestrator`], [`reshare`], [`state_sync`], and //! [`types`] for the detailed actors, synchronization points, and wire artifacts. use crate::dkg::{network::Directory, types::SchemeInfo}; use commonware_consensus::{Block, types::Epoch}; use commonware_cryptography::{ PublicKey, Signer, bls12381::{ dkg::feldman_desmedt::DealerPrivMsg, primitives::{group::Share, variant::Variant}, }, transcript::Summary, }; use commonware_utils::ordered::Set; use std::future::Future; pub mod bootstrap; pub mod fence; pub mod network; pub mod orchestrator; pub mod probe; pub mod reshare; pub mod state_sync; pub mod types; #[cfg(test)] mod tests; /// A [`Block`] that may carry a reshare [`Payload`](types::Payload). pub trait ReshareBlock: Block { /// BLS variant used by the DKG payload. type Variant: Variant; /// Signer type used by DKG payloads. type Signer: Signer; /// Transport directory type carried by this block's epoch artifacts. type Directory: Directory<::PublicKey>; /// Retrieves the [`Payload`](types::Payload) carried by this block, if any. fn payload(&self) -> Option>; } /// A registrar of signing schemes that supplies a [`Provider`] an [`Epoch`]-scoped /// [`ThresholdScheme`] in preparation for a transition to the given [`Epoch`]. /// /// [`Provider`]: commonware_cryptography::certificate::Provider /// [`ThresholdScheme`]: commonware_consensus::simplex::scheme::bls12381_threshold pub trait Registrar: Send + Sync + 'static { /// BLS variant used by the DKG payload. type Variant: Variant; /// Participant public key type. type PublicKey: PublicKey; /// Hook for handling an epoch transition. /// /// Registration is idempotent. An actor may repeat the same epoch and scheme /// after recovering state-sync startup material. fn register( &self, epoch: Epoch, info: SchemeInfo, ) -> impl Future + Send; } /// Interface for a secret store that persists and retrieves the private DKG/reshare /// material for different [`Epoch`]s. /// /// All material entrusted to this trait is secret and must be stored as such: it must /// never be written to plaintext protocol storage, carried on-chain, or sent to peers. /// This includes the dealer RNG seed, which seeds a dealer's sharing polynomial and so /// reveals every share that dealer sends. /// /// Writes must be durable before their returned future resolves. When /// [`put_share`](Self::put_share), [`put_seed`](Self::put_seed), or /// [`put_dealing`](Self::put_dealing) resolves, the stored material MUST survive a crash: the /// reshare actor treats a resolved put as a durable commitment and does not re-derive the /// material after a restart. A buffered store that resolves before the write is stable can let a /// dealer reseed with fresh randomness and re-deal different shares for the same epoch /// (equivocation), or lose a share it has already relied upon. pub trait SecretStore: Send + Sync + 'static { /// Stores a [`Share`] for a given [`Epoch`]. /// /// Must be durable before the returned future resolves (see the trait documentation). fn put_share(&mut self, epoch: Epoch, share: Share) -> impl Future + Send; /// Retrieves a [`Share`] for a given [`Epoch`], if it exists. fn get_share(&mut self, epoch: Epoch) -> impl Future> + Send; /// Stores the dealer RNG seed for a given [`Epoch`]. /// /// The seed deterministically replays this node's dealer randomness across a /// restart. It is secret: knowing it reveals every share the dealer distributes. /// /// Must be durable before the returned future resolves: no-equivocation safety depends on the /// seed being recovered verbatim after a crash so the dealer replays identical randomness /// rather than re-dealing fresh shares. fn put_seed(&mut self, epoch: Epoch, seed: Summary) -> impl Future + Send; /// Retrieves the dealer RNG seed for a given [`Epoch`], if it exists. fn get_seed(&mut self, epoch: Epoch) -> impl Future> + Send; /// Stores a private dealing received from `dealer` during `epoch`. fn put_dealing( &mut self, epoch: Epoch, dealer: P, private: DealerPrivMsg, ) -> impl Future + Send; /// Retrieves a private dealing received from `dealer` during `epoch`. fn get_dealing( &mut self, epoch: Epoch, dealer: &P, ) -> impl Future> + Send; /// Prunes secrets older than `min`. fn prune(&mut self, min: Epoch) -> impl Future + Send; } /// Participant policy provider. /// /// This is the only application hook on canonical epoch structure: it supplies /// the intended participant set and transport directory for a future `epoch`. /// The actor derives dealers, current players, and ordinary epoch progression /// from finalized public truth, and consults this only for the values of an /// epoch it cannot yet read from a finalized boundary block. /// /// Both hooks are consulted exclusively while building or verifying an epoch's /// final block, so implementations may be backed by application state (e.g., a /// staking or address-registry contract): a node performing those operations is /// fully synced. Their results are embedded in the next /// [`types::EpochInfo`], which is what recovering and state-syncing nodes use /// instead of this provider. /// /// [`participants`](Self::participants) and [`directory`](Self::directory) /// must be deterministic for the same inputs across all honest nodes (see /// their documentation for the exact contracts). pub trait ParticipantsProvider: Send + Sync + 'static { type PublicKey: PublicKey; /// Transport directory type embedded in epoch artifacts. type Directory: Directory; /// Returns the intended participant set for `epoch`. /// /// This MUST be deterministic and stable: for a given `epoch`, every honest /// node MUST return an identical [`Set`], with the same membership AND the /// same ordering, and repeated calls MUST return the same `Set`. /// /// The returned set MUST be non-empty and MUST contain no more than the /// actor's configured `max_participants` entries. A violation is treated as /// a deterministic provider contract failure. /// /// In continuous reshare, this hook is consulted while building or /// verifying the final block two epochs before `epoch`. That block carries /// the [`types::EpochInfo`] for the following epoch, with this set embedded /// verbatim as `next_players`. /// /// Therefore the result for `epoch` must be locked in before honest nodes /// propose or verify the final block that announces it as `next_players`. /// The proposer and every verifier independently rebuild /// and compare the value for equality. Because [`Set`] is order sensitive /// (both its equality and its encoding depend on element order), any /// divergence in membership or ordering between proposer and verifier /// rejects a valid final block and stalls the epoch boundary. Canonicalize /// (e.g. sort) the returned `Set` so it is identical regardless of how the /// underlying membership is stored or queried. fn participants(&mut self, epoch: Epoch) -> impl Future> + Send; /// Returns the transport directory embedded in the [`types::EpochInfo`] /// for `epoch`. /// /// `peers` is the union of the epoch's dealers, players, and next players. /// It may contain up to three times the actor's configured /// `max_participants` entries when those sets are disjoint. The returned /// directory MUST contain exactly these peers. Missing or unrequested /// entries are treated as a deterministic provider contract failure. /// /// This is consulted while building or verifying the final block of /// `epoch - 1`, under the same determinism and lock-in contract as /// [`participants`](Self::participants): for a given `epoch` and `peers`, /// every honest node MUST return an identical value, including the same /// reachability data for each peer, and repeated calls MUST return the same /// value. An update submitted during an epoch takes effect in a later /// epoch's directory, never retroactively. fn directory( &mut self, epoch: Epoch, peers: Set, ) -> impl Future + Send; }