Running a client
audience: operators
The typical zipnet publisher is an external user running their own
TDX-attested agent — you don’t operate those. This page is about the
reference zipnet client subcommand you ship to publishers (or
run yourself for a bundled wallet, a cover-traffic filler, or a
smoke-test participant).
A client generates an X25519 keypair, publishes its public bundle via gossip, and seals one envelope per round. In production every client runs inside a TDX guest whose MR_TD matches the value your committee pinned; see Quickstart TDX section.
One-shot command
ZIPNET_INSTANCE="acme.mainnet" \
ZIPNET_MESSAGE="payload-to-broadcast" \
zipnet client --bootstrap <peer_id_of_aggregator_or_server>
Omit ZIPNET_MESSAGE to run a cover-traffic client that participates
in every round with a zero payload. Cover traffic is the operator’s
tool for raising the effective anonymity set size when real
publishers are sparse.
Environment variables
| Variable | Meaning | Notes |
|---|---|---|
ZIPNET_INSTANCE | Instance name to bind to | Required. Same string the committee uses; typos show up as ConnectTimeout. |
ZIPNET_UNIVERSE | Universe override | Optional; leave unset to use the shared universe. |
ZIPNET_BOOTSTRAP | Peer IDs to dial on startup | Aggregator’s PeerId or any committee server’s. Needed only on cold networks. |
ZIPNET_MESSAGE | UTF-8 message to seal per round | Truncate yourself to fit slot_bytes − tag_len. Default slot width is 240 bytes of user payload. |
ZIPNET_CADENCE | Talk every Nth round | Default 1. Useful for dialing your own talk/cover ratio. |
ZIPNET_METRICS | Prometheus bind address | Optional. |
Building the TDX image you ship to publishers
Publishers to a TDX-gated instance need to run your image (not their own ad-hoc build), because the committee will reject any client whose quote doesn’t match the pinned MR_TD.
Under the consolidated-binary layout, the TDX build lives on the
single zipnet crate and is gated by a Cargo feature:
cargo build --release --features tdx-builder-alpine
Alpine is the usual choice for clients — ~5 MB versus Ubuntu’s
~25 MB — unless your agent has a specific glibc dependency. The
artifacts land under target/release/tdx-artifacts/zipnet/alpine/:
| Artifact | What it’s for |
|---|---|
zipnet-run-qemu.sh | Self-extracting launcher publishers invoke on a TDX host. |
zipnet-mrtd.hex | The 48-byte measurement. You pin this in the committee and publish it to readers. |
zipnet-vmlinuz | Raw kernel, for repackaging. |
zipnet-initramfs.cpio.gz | Raw initramfs. |
zipnet-ovmf.fd | Raw OVMF firmware. |
The image bakes the zipnet binary only; the role (server /
aggregator / client) is picked at launch via the subcommand or via
ZIPNET_* env vars set by the guest’s entrypoint. MR_TD is
identical for every role because the image is identical. If you
need cryptographically separable committee and client images, build
twice with different default entrypoints baked in — see
Quickstart — TDX.
Publish zipnet-mrtd.hex alongside your release notes. It goes
into the committee’s Tdx::require_mrtd(...) configuration and
into readers’ verification code. See
Rotations and upgrades
for rolling a new MR_TD without downtime.
What a healthy client log looks like
INFO zipnet: spawning zipnet client client=550fda1ffa
INFO zipnet::node::roles::common: zipnet up: network=<universe> instance=acme.mainnet peer=c2e9aeee0e... role=a8b7ed5911...
INFO zipnet::node::roles::client: client booting; waiting for rosters
After boot, every sealed envelope is a DEBUG event. Raise
RUST_LOG to debug,zipnet_node=debug to see them.
Why a client’s envelope might get dropped
- The client bundle hasn’t replicated yet. The first few rounds
after a client connects may not include it in
ClientRegistry. Wait forzipnet_client_registeredto flip to 1 before relying on anonymity guarantees. - Slot collision with another client. v1’s slot assignment is a deterministic hash — two clients occasionally pick the same slot and XOR their messages into garbage. Neither falsification tag verifies, the committee still publishes the broadcast, the messages are lost, the clients retry next round. A 4x-oversized scheduling vector in v2 makes this rare.
- Message is longer than
slot_bytes − tag_len. The client exits withMessageTooLong. Shorten, or raiseslot_bytesat the instance level (which retires the instance — see Rotations and upgrades).
Identity lifetime
In the mock path (TDX disabled), each process run generates a fresh X25519 identity — run-to-run unlinkability is free. In the TDX path, the identity lives in sealed storage inside the enclave so a restart preserves it; useful for reputation systems, but means the same enclave is recognizable across runs. Design accordingly when you pick a cover-traffic cadence.
See also
- Running a committee server
- Rotations and upgrades — rebuilding the client image and rolling a new MR_TD.
- Security posture checklist — client-host hygiene, TDX expectations.