Skip to content
Sealdrop

Sealdrop

Encrypted, self-expiring tip line on Arkiv

Created on 18th October 2026

•

Sealdrop

Sealdrop

Encrypted, self-expiring tip line on Arkiv

The problem Sealdrop solves

A source who wants to tell a newsroom something needs a way to do it without being identified, and the standard answer, SecureDrop, needs a lot of hardware. Its own installation guide requires an application server, a monitor server, a dedicated network firewall, and air-gapped machines that boot to Tails, all as physical machines the organization hosts itself, because cloud servers are "trivially accessible and manipulable by the provider" and a host can be compelled to give access, "potentially with a gag order attached". The public directory lists 24 instances (checked 2026-10-16). A small newsroom, a student paper or a freelancer has no safe channel to offer, so sources fall back to email or direct messages, which tell a provider who wrote to whom. And a tip line that runs on a server is one place to seize, subpoena or switch off.

Sealdrop removes the server. A source opens the newsroom's link, seals a message in the browser, and one Arkiv transaction stores the ciphertext and hands it to the newsroom. The newsroom's open console hears it over a WebSocket and decrypts it locally. Every tip expires on the date its source chose unless the newsroom extends it; an inbox that is no longer renewed lapses after 7 days. What reaches Arkiv is public keys and ciphertext only, so no Sealdrop server holds anything to read or hand over. The source needs no wallet and no account; the newsroom needs a browser.

Why these four pillars. Privacy (main): a source with documents for a newsroom that runs no SecureDrop can send a tip with no account, and the explorer shows only padded ciphertext, three attributes and the protocol's own fields; the source picks the initial lifetime. Censorship Resistance: if the host of a newsroom's tip line is taken down or blocked, its inbox and tips are still Arkiv entities that anyone can query with one JSON-RPC call (arkiv/schema.md, "Read it without Sealdrop"), and there is no Sealdrop backend to shut down. Security: a source who follows a look-alike link or QR code cannot tell a real inbox from a fake when the owner is a field in someone's database; here an inbox loads only if its on-chain $creator is the address in the link, and the link's key check makes the page refuse any other inbox key, even from a lying RPC. A look-alike link has a different address, so the page prints it for the source to compare with the one the newsroom publishes elsewhere. Open Source: a builder who wants a notifier, a relay or a second client does not have to ask us, because arkiv/schema.md is a data contract, and a 43-line reader that shares no code with the app reads a newsroom's tips from it. Full answers: README, "Why these four pillars".

Sources: SecureDrop hardware requirements and the SecureDrop directory.

Challenges I ran into

Items 1 to 4 are from real runs, each with numbers and a repro in arkiv/friction.md (F7, F6, F3, F1); item 5 is a design decision.

  1. The public RPC has an undocumented anonymous query budget. After a few scripted test runs of my own the node answered HTTP 429 ANON_COST_LIMITED for up to 60 minutes (the RPC's Retry-After says when). The budget is 10,000 cost units at 100 per query, and only the 429 says so. The SDK (viem) does not fail on a 429: it waits out Retry-After, so every call hung (I killed one after about 27 minutes). I replaced the fetch with one that fails at once and explains itself, made the app use WebSocket push instead of polling, sweep only on open or refresh and then fetch only what is new, and show the usage in the footer. (F7)
  2. A shared sender key and two senders at once. The docs warn that two writes in flight from one wallet collide on the nonce. I found that their fix, viem's nonce manager, removes the collision, but then the second predictEntityKeys + executeBatch batch reverts on chain (5 of 5 runs) because its predicted key is stale; without the manager, one batch fails before broadcast or both calls report success with the same hash and one write is lost. A shared key used from many browsers is beyond any nonce manager, so sendTip re-predicts the key on every attempt, reads the entity back, compares the bytes and retries. (F6)
  3. Attribute names. The SDK validator, the SDK's own error text and the JSON-RPC doc page allow sentAt; the engine rejects uppercase (only the SDK Mutating Data page says so). I ended up with lowercase names and dropped the timestamp attribute entirely, using the protocol's $createdAt instead. (F3)
  4. $createdAt filters. The node accepts them, the SDK's builder refuses to build them, and the docs disagree, so the incremental refresh uses a raw query string. (F1)
  5. Privacy design under a public ledger. Keeping a tip to three attributes, padding to four size classes, and deciding that the sender field must not exist; and being honest in the README that ciphertext stays in chain history after Entity Expiration.

Tracks Applied (4)

Censorship Resistance

Who is censored today. Sources and newsrooms whose tip line is one server: a hosting provider, a domain or a court can t...Read More

Open Source

Who is locked out today. Other builders. A tip line that runs as one organization's server keeps its inbox in that serve...Read More

Privacy

Who is exposed today. A source with documents for a newsroom. The strong channel, SecureDrop, needs two servers, a firew...Read More

Security

Who is misled today. A source following a link, a QR code or a phishing look-alike to "the newsroom's tip line", with no...Read More

Discussion

Builders also viewed

See more projects on Devfolio