Crate map
audience: contributors
Workspace at /Users/karim/dev/flashbots/zipnet/. Edition 2024, MSRV
1.93. Mosaik pinned to =0.3.17 (see CLAUDE.md
for rationale).
zipnet-proto (pure: no mosaik, no tokio, no I/O)
▲
│
zipnet-core (pure: no mosaik, no tokio, no I/O)
▲
│
zipnet-node ── mosaik 0.3.17 ── iroh 0.97 (QUIC)
▲ ▲
│ └──────────────────────────┐
│ │
zipnet (SDK facade) ├── zipnet-client
├── zipnet-aggregator
└── zipnet-server
The split between -proto, -core, and -node is load-bearing,
not cosmetic. Anything that touches tokio, mosaik, or I/O must
live in -node (or higher). Anything that could be reused by an
alternative transport lives in -proto / -core. If you find
yourself reaching for tokio::spawn or mosaik:: inside -proto or
-core, you are in the wrong crate.
zipnet-proto
Pure wire types and crypto primitives. No mosaik, no async.
| Module | Role |
|---|---|
wire | ClientEnvelope, AggregateEnvelope, PartialUnblind, BroadcastRecord, ClientId, ServerId, RoundId |
crypto | HKDF-SHA256 salt composition, AES-128-CTR pad generator, blake3 falsification tag |
keys | DhSecret (X25519 StaticSecret), ClientKeyPair, ServerKeyPair, public ClientBundle / ServerBundle |
params | RoundParams (broadcast shape) |
xor | xor_into, xor_many_into over equal-length buffers |
WIRE_VERSION is bumped any time a wire or params shape changes.
CommitteeMachine::signature() in zipnet-node mixes this in so
nodes with different wire versions will never form a group.
zipnet-core
Paper’s algorithms as pure functions over -proto types. No async.
| Module | Role |
|---|---|
client::seal | Algorithm 1 — TEE-side sealing of one envelope |
aggregator::RoundFold | Algorithm 2 — stateful XOR fold of envelopes for one round |
server::partial_unblind | Algorithm 3 — per-server partial computation |
server::finalize | Committee combine — aggregate + partials → broadcast |
slot | Deterministic slot assignment + slot layout helpers |
The full round trip is exercised by
server::tests::e2e_two_servers_three_clients, which constructs a
3-server / 4-client setup (2 talkers + 2 cover) and asserts that the
final BroadcastRecord contains each talker’s plaintext at the
expected slot with a valid falsification tag. No transport is
involved — this is the pure-algebra proof.
zipnet-node
The only non-SDK crate that imports mosaik. Hosts the
declare! items, the committee state machine, and the role event
loops.
| Module | Role |
|---|---|
protocol | declare::stream! + declare::collection! items, tag constants, ticket class constants |
committee | CommitteeMachine: StateMachine, Command, Query, QueryResult, LiveRound, CommitteeConfig |
tickets | BundleValidator<K>: TicketValidator for client / server bundle tickets |
roles::common | NetworkBoot helper that wraps iroh secret, tags, tickets, and mDNS setup |
roles::client | client event loop |
roles::aggregator | aggregator event loop |
roles::server | committee server event loop (single tokio::select! over three event sources) |
The role modules are reusable as a library — the three binaries are
thin CLI wrappers around them. Test code in
crates/zipnet-node/tests/e2e.rs reuses the same primitives but
inlines the server loop so it can inject a pre-built Arc<Network>
and cross-sync_with all peers before anything starts (same pattern
as mosaik’s examples/orderbook).
protocol.rs today vs target
protocol.rs currently declares its StreamId / StoreId literals
as flat strings ("zipnet.stream.client-to-aggregator", etc.). The
target per design-intro
is DEPLOYMENT.derive("submit") / .derive("broadcasts") / …
chained off the per-deployment content + intent addressed root so
multiple deployments can coexist on one mosaik universe without
colliding. The migration removes the ZIPNET_SALT.derive(shard)
NetworkId scoping in favour of the shared zipnet::UNIVERSE
constant.
zipnet (SDK facade)
Public surface for consumers. Wraps zipnet-node and hides all
mosaik types (StreamId, StoreId, GroupId) from callers.
| Module | Role |
|---|---|
environments | UNIVERSE constant |
config | Config, ShuffleWindow (deployment fingerprint) |
datum | ShuffleDatum trait, DecodeError |
client | Zipnet::<D>::{submit, receipts, read, deployment_id}, Submitter<D>, Receipts<D>, Reader<D> |
error | Error { WrongUniverse, ConnectTimeout, Attestation, Shutdown, Protocol } |
types | Receipt, SubmissionId, Outcome |
Re-exports from mosaik that the SDK intentionally surfaces:
UniqueId, NetworkId, Tag, unique_id!. Nothing else is
re-exported — callers that need raw mosaik types have fallen off the
supported path and should drop to zipnet-node directly.
Zipnet::<D>::deployment_id(&Config) is the pure-function derivation
that produces the on-wire identity — use it on both sides of the
handshake for diagnostic parity. The canonical form is
blake3("zipnet|" || name || "|type=" || TYPE_TAG || "|size=" || WIRE_SIZE || "|window=" || window || "|init=" || init); any change
to that encoding is a wire-breaking change and must be versioned.
Binaries
Thin CLI wrappers around zipnet-node::roles::*. In v1 they still
take a ZIPNET_SHARD flag and scope to ZIPNET_SALT.derive(shard);
this predates the UNIVERSE + deployment-fingerprint design and will
be retired as the binaries migrate to the Zipnet::<D>::*
constructors on UNIVERSE.
| Crate | Flags of note |
|---|---|
zipnet-client | ZIPNET_MESSAGE, ZIPNET_CADENCE |
zipnet-aggregator | ZIPNET_FOLD_DEADLINE |
zipnet-server | ZIPNET_COMMITTEE_SECRET, ZIPNET_MIN_PARTICIPANTS, ZIPNET_ROUND_PERIOD, ZIPNET_ROUND_DEADLINE |
Each binary also takes the common ZIPNET_SHARD, ZIPNET_SECRET,
ZIPNET_BOOTSTRAP, ZIPNET_METRICS — see Environment
variables.
Feature flags
zipnet-node/tee-tdx(off by default) — foldsmosaik::tickets::Tdx::new().require_own_mrtd()?into the committee’s admission validators. Requires mosaik’stdxfeature (on by default) and TDX hardware.zipnet-client/tee-tdx,zipnet-server/tee-tdx— re-export flips of the node crate’s flag.
Mock TEE is the default path (// SIMPLIFICATION: in source); TDX is
opt-in for v1 and the critical-path enforcement lands in v2 (see
Roadmap).
Dependency choices worth knowing
x25519-dalek2.0 pinsrand_core0.6 (not workspace rand 0.9). We break workspace coherence inzipnet-proto/Cargo.tomlby pullingrand_core = "0.6"explicitly forOsRngcompatibility withStaticSecret::random_from_rng. The crate-proper rand dep is workspace-pinned.mosaik = "=0.3.17"— the API we developed against. Upgrades are expected to break compile; thedeclare::stream!/declare::collection!macros are stable-ish, the ticket and group APIs have shifted across minor versions.