commonware-glue 2026.9.0

Default constructions that span multiple primitives.
Documentation
//! 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<<Self::Signer as Signer>::PublicKey>;

    /// Retrieves the [`Payload`](types::Payload) carried by this block, if any.
    fn payload(&self) -> Option<types::Payload<Self::Variant, Self::Signer, Self::Directory>>;
}

/// 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<Self::Variant, Self::PublicKey>,
    ) -> impl Future<Output = ()> + 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<Output = ()> + Send;

    /// Retrieves a [`Share`] for a given [`Epoch`], if it exists.
    fn get_share(&mut self, epoch: Epoch) -> impl Future<Output = Option<Share>> + 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<Output = ()> + Send;

    /// Retrieves the dealer RNG seed for a given [`Epoch`], if it exists.
    fn get_seed(&mut self, epoch: Epoch) -> impl Future<Output = Option<Summary>> + Send;

    /// Stores a private dealing received from `dealer` during `epoch`.
    fn put_dealing<P: PublicKey>(
        &mut self,
        epoch: Epoch,
        dealer: P,
        private: DealerPrivMsg,
    ) -> impl Future<Output = ()> + Send;

    /// Retrieves a private dealing received from `dealer` during `epoch`.
    fn get_dealing<P: PublicKey>(
        &mut self,
        epoch: Epoch,
        dealer: &P,
    ) -> impl Future<Output = Option<DealerPrivMsg>> + Send;

    /// Prunes secrets older than `min`.
    fn prune(&mut self, min: Epoch) -> impl Future<Output = ()> + 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<Self::PublicKey>;

    /// 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<Output = Set<Self::PublicKey>> + 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<Self::PublicKey>,
    ) -> impl Future<Output = Self::Directory> + Send;
}