Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

ModuleRole
wireClientEnvelope, AggregateEnvelope, PartialUnblind, BroadcastRecord, ClientId, ServerId, RoundId
cryptoHKDF-SHA256 salt composition, AES-128-CTR pad generator, blake3 falsification tag
keysDhSecret (X25519 StaticSecret), ClientKeyPair, ServerKeyPair, public ClientBundle / ServerBundle
paramsRoundParams (broadcast shape)
xorxor_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.

ModuleRole
client::sealAlgorithm 1 — TEE-side sealing of one envelope
aggregator::RoundFoldAlgorithm 2 — stateful XOR fold of envelopes for one round
server::partial_unblindAlgorithm 3 — per-server partial computation
server::finalizeCommittee combine — aggregate + partials → broadcast
slotDeterministic 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.

ModuleRole
protocoldeclare::stream! + declare::collection! items, tag constants, ticket class constants
committeeCommitteeMachine: StateMachine, Command, Query, QueryResult, LiveRound, CommitteeConfig
ticketsBundleValidator<K>: TicketValidator for client / server bundle tickets
roles::commonNetworkBoot helper that wraps iroh secret, tags, tickets, and mDNS setup
roles::clientclient event loop
roles::aggregatoraggregator event loop
roles::servercommittee 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.

ModuleRole
environmentsUNIVERSE constant
configConfig, ShuffleWindow (deployment fingerprint)
datumShuffleDatum trait, DecodeError
clientZipnet::<D>::{submit, receipts, read, deployment_id}, Submitter<D>, Receipts<D>, Reader<D>
errorError { WrongUniverse, ConnectTimeout, Attestation, Shutdown, Protocol }
typesReceipt, 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.

CrateFlags of note
zipnet-clientZIPNET_MESSAGE, ZIPNET_CADENCE
zipnet-aggregatorZIPNET_FOLD_DEADLINE
zipnet-serverZIPNET_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) — folds mosaik::tickets::Tdx::new().require_own_mrtd()? into the committee’s admission validators. Requires mosaik’s tdx feature (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-dalek 2.0 pins rand_core 0.6 (not workspace rand 0.9). We break workspace coherence in zipnet-proto/Cargo.toml by pulling rand_core = "0.6" explicitly for OsRng compatibility with StaticSecret::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; the declare::stream! / declare::collection! macros are stable-ish, the ticket and group APIs have shifted across minor versions.