Every primitive, documented.
Zero Protocol uses nine cryptographic primitives, each chosen for a specific purpose. This page documents the complete stack, wire formats, KDF labels, and key-at-rest protection schemes.
Nine primitives, zero choices left to chance.
ML-KEM-768
ml-kem = "=0.3.0-pre"
PQ Sphinx V2 per-hop handshake (pq_sphinx.rs), HS circuit (hs_circuit.rs), client-guard 0x30/0x31 handshake
Hybrid with X25519: attacker must break both simultaneously. Trial decapsulation fills unused slots with random CTs.
X25519
x25519-dalek = "2"
Sphinx per-hop ECDH (sphinx.rs build_sphinx_packet()), HS circuit sessions (hs_circuit.rs), client-node session establishment
Alpha re-randomization: multiply by blinding scalar derived from k_blind after each hop.
ChaCha20
chacha20 = "0.9"
Sphinx BETA routing layer keystream — xor_stream() in sphinx.rs. Replaces earlier BLAKE2b-CTR (security fix C03-1).
Key is derived per packet from ECDH shared secret, so nonce reuse is safe — each key is cryptographically unique.
XChaCha20-Poly1305
chacha20poly1305 = "0.10"
Node private key encryption at rest (key_protect.rs), encrypted backup archives (node_ops.rs), HS client sessions (hs_client.rs)
C04-1 fix: payload nonce is deterministic, domain-separated — prevents nonce reuse across hops.
BLAKE2b
blake2 = "0.10"
Sphinx KDF tree (derive_hop_keys()), Sphinx MAC (compute_mac()), DHT node IDs, circuit key derivation (pq_kdf.rs)
C04-4 fix: each label is unique and unambiguous — prevents cross-context key confusion. Native keyed mode for MAC.
Ed25519
ed25519-dalek = "2"
Node advertisements (node_ads.rs), DHT node descriptors, hidden service identity (hidden_service.rs), release manifest signing
All node advertisements are self-certified. DHT storage nodes cannot forge records.
Argon2id
argon2 = "0.5"
Unix node private key wrapping (key_protect.rs), encrypted backup archives — same derivation path
Plaintext legacy keys are auto-migrated on first load when ZERO_KEY_PASSPHRASE env var is set.
DPAPI
windows-sys = "0.52"
IPC auth token (ipc.rs CryptProtectData/CryptUnprotectData), node private key wrapping on Windows (key_protect.rs METHOD_DPAPI=0x02)
DPAPI binds the encrypted key to the Windows user account — cannot be decrypted on another machine or as another user.
subtle (constant-time)
subtle = "2"
IPC token comparison, reply MAC verification (sphinx.rs verify_reply_mac()), binary hash comparison in release_manager.rs
All security-sensitive comparisons use subtle to prevent timing side-channel attacks.
BLAKE2b KDF tree labels.
Five independent 32-byte keys derived per hop from the shared ECDH secret. Unambiguous labels prevent cross-context key confusion (C04-4 fix).
zero-sphinx-v1-enc→ k_encChaCha20 BETA routing layer keystreamzero-sphinx-v1-mac→ k_macBLAKE2b keyed header MAC (gamma field)zero-sphinx-v1-blind→ k_blindAlpha re-randomization scalarzero-sphinx-v1-pay→ k_payPayload XChaCha20-Poly1305 AEAD keyzero-sphinx-v1-reply→ k_replyMSG_SPHINX_REPLY authentication keyzero-pq-sphinx-v2-routingPQ hybrid routing secret (pq_sphinx.rs)zero-pq-v1-ck:Circuit key derivation (pq_kdf.rs) — prefix || ss_x || ss_pqzero-pq-sphinx-v2-*Per-hop keys in PQ Sphinx V2 (same structure as V1)Sphinx packet layouts.
V1 Sphinx (sphinx.rs)
SPHINX_LEN=1600 BALPHA(32) + GAMMA(32) + BETA(512) + PAYLOAD(1024) = 1600 B — fits single Ethernet frame, avoids fragmentation
V2 PQ Sphinx (pq_sphinx.rs)
HEADER=4929 BPQ_CT_ARRAY_LEN = 4×1088 = 4352 B. Unused slots filled with random CTs for indistinguishability.
Key protection on disk.
Node private keys are never stored in plaintext. Two methods are available — one per platform.
Argon2id + XChaCha20-Poly1305
[4B magic "ZKP\x01"][1B method=0x01][16B salt][24B nonce][ciphertext+16B tag]DPAPI
[4B magic "ZKP\x01"][1B method=0x02][opaque DPAPI blob]Auto-migration: Plaintext legacy keys are automatically encrypted on first load when ZERO_KEY_PASSPHRASE (Unix) or DPAPI (Windows) is available.