cipherpost

GitHub Repo | Updated: April 2026

A self-sovereign, serverless, accountless CLI for cryptographic material handoff over Mainline DHT via PKARR. Hand off a key, certificate, or secret to someone — end-to-end encrypted, with a signed receipt — without standing up or depending on any server.

Tech Stack: - Rust - PKARR (Mainline DHT identity and rendezvous) - Ed25519 (identity and signing) - age encryption - Argon2id + HKDF-SHA256 (key derivation)

Why I Built This

Moving cryptographic material between parties — SSH keys, X.509 certificates, PGP keys, generic secrets — is a solved problem in theory and a painful one in practice. The common solutions require standing up infrastructure: a shared S3 bucket, an email attachment, a Slack DM, or a secrets manager with accounts, credentials, and a subpoena surface.

The goal was a tool that makes the transfer accountless, serverless, and verifiable without any central operator in the loop. No SaaS. No relay. No logins. The Mainline DHT serves as the rendezvous layer — a globally distributed network with no operator to subpoena. An Ed25519/PKARR keypair is the user's only identity. The recipient publishes a signed receipt to the DHT on pickup, so the sender can independently verify delivery without a central log.

Cipherpost is a fork-and-diverge from cclink, focused on generic cryptographic material handoff rather than Claude Code session continuity. The typed payload schema (GenericSecret, X509Cert, PgpKey, SshKey), explicit acceptance step, and signed receipts are the additions built on top of the shared PKARR/age/Argon2id cryptographic stack.

How It Works

Cipherpost uses a dual-signature model: every share carries an outer PKARR-packet signature and an inner Ed25519 signature over canonical JSON (RFC 8785 / JCS). Tampering at either layer aborts the receive operation before age-decrypt runs. No envelope field is displayed prior to that verification check.

Send / Receive Flow:

Sender → encrypts payload → signs inner JSON → publishes PKARR SignedPacket
                                                          ↓
                                              Mainline DHT (no operator)
                                                          ↓
Recipient → resolves packet → verifies outer PKARR sig → verifies inner Ed25519
         → shown acceptance screen (sender fingerprints, purpose, TTL, payload type)
         → types sender's z-base-32 pubkey to confirm
         → payload decrypted → receipt published to DHT

The passphrase contract is non-interactive-first: credentials come from an environment variable, a file, or a file descriptor. The --passphrase <value> argument is intentionally rejected because it would leak the passphrase via ps output. Single-consumption --burn mode exists — the recipient's acceptance is recorded in a local state ledger before the DHT ciphertext is marked consumed, so a crash between those two steps does not silently lose the payload.

Signature-failure errors are indistinguishable by design: all outer, inner, and canonical-mismatch verification failures share one identical user-facing message and exit code 3, defending against distinguishable-oracle attacks. A full seven-code exit taxonomy covers success, TTL expiry, signature failure, passphrase errors, payload size cap, network errors, and acceptance declined.

What I Learned

The PKARR BEP44 wire budget is a hard constraint that shaped the entire payload design. Realistic X.509 or PGP keys — and even GenericSecret payloads above roughly 550 bytes — exceed the 1000-byte ceiling. Round-trip tests for realistic typed inputs are skipped in CI behind positive Error::WireBudgetExceeded clean-error pins. Two-tier storage or chunking is targeted for a future release; the v1.1 scope shipped the protocol correctly within the constraint rather than papering over it.

Real-DHT cross-identity testing is structurally incompatible with per-commit CI. UDP and NAT variance combined with a 60–90 second DHT propagation tail make per-commit real-DHT testing unworkable. The solution was a release-acceptance workflow that runs the full cross-identity round trip on every version tag push and uploads the output as a 90-day artifact — so each release publishes its own real-DHT evidence alongside the tag. The v1.1.0 evidence run passed against pkarr-default Mainline bootstrap nodes.

The explicit acceptance step — requiring the recipient to type the sender's full z-base-32 pubkey to confirm — was the hardest UX decision. It creates friction. It also prevents a class of attacks where a malicious DHT packet tricks a recipient into auto-accepting material from an unexpected sender.


No server, no account, no operator — just cryptography and a global DHT.