Skip to main content

Encryption model overview

Answers are sealed in the respondent's browser with X-Wing and AES-256-GCM. See what a submission carries, how keys are wrapped, and where recovery fits.

  • For admins
  • 5 min read
  • Updated Oct 29, 2026
  • Reviewed by Security
Note

Heads up

This is the product-level story: keys, ciphertext, and what admins can do. For guarantee mapping, pair it with Zero-knowledge architecture.

When a respondent opens a poll, their browser receives the poll's public key with the poll's questions. The answers are encrypted in that browser before anything is sent, so what reaches us is ciphertext; we can't read answers and can't tell who wrote one.

#How a response is sealed

  1. Receive the poll public key. Served with the poll definition. Each poll has its own X-Wing keypair (X25519 + ML-KEM-768), generated in the admin's browser when the poll is created.
  2. Encapsulate a fresh secret. The respondent's browser runs X-Wing against that public key, producing a one-time shared secret and an encapsulation ciphertext.
  3. Derive a key and seal the answers. HKDF-SHA-256 with a random salt turns the shared secret into an AES-256-GCM key, and the answer set is encrypted under it with a random 12-byte IV.
  4. Submit. The browser posts the sealed answer. Depending on how the poll was shared, the same request also carries a one-vote credential: a hash of the access code, or a blind-signed ballot token and its signature. None of these identifies the respondent, and no name, email or IP address is stored with the answer.

Parameters

Key encapsulationX-Wing (X25519 + ML-KEM-768)
Key derivationHKDF-SHA-256, random 32-byte salt
Payload cipherAES-256-GCM
Poll keysone keypair per poll
Respondent identitynot collected
seal-response.tsILLUSTRATIVE
const { ciphertext: kem, sharedSecret } = xwingEncapsulate(pollPublicKey);
const aesKey = hkdfSha256(sharedSecret, randomSalt, "response-v2"); // AES-256-GCM key
const sealed = aesGcmEncrypt(aesKey, randomIv, encode(answers));

await post(`/polls/${pollId}/responses`, {
  encryptedBlob: { version: 2, salt, xwingCiphertext: kem, iv, ciphertext: sealed },
  // only one of these, and only when the poll uses it:
  codeHash,                          // access-code polls
  ballotToken, ballotSignature,      // personal-invite polls
});

#Where the private keys live

Each poll's private key is wrapped (encrypted) under the key of the folder the poll lives in. The folder key is in turn wrapped once for each person who can open that folder, under that person's own key, and in a workspace it's also wrapped under the workspace's master key so owners keep access when people leave.

  • Poll key: one keypair per poll. The public half is served to respondents; the private half is stored only wrapped.
  • Folder wrap; the poll's private key is wrapped under its folder's key.
  • Personal wraps; the folder key is wrapped once per person with access to the folder, not per device.
  • Removing someone; removing a person drops their wrap. Existing answers are unaffected.

Unwrapping happens in the browser after sign-in. For the unwrap sequence, see Zero-knowledge architecture.

Note

Key backup and recovery

Your key backup is locked in your browser before it reaches us, with your password or your recovery key. Nothing we store can open it. Your recovery key never reaches us at all. Inbox recovery stores your workspace keys encrypted in your browser under your password or recovery key. Verifying your inbox gets you that ciphertext; only your password or recovery key opens it. If a teammate loses both, ask an owner: an owner can restore a colleague's access only after confirming a code with them directly; we can't do it for them. See the threat model.

#Account, folders, and wraps

ConceptRole
Collaboration keyYour personal keypair. Other people's folder keys are wrapped to its public half.
Folder keyWraps the private keys of the polls in a folder
Personal folder wrapOne wrap of the folder key per person with access
Workspace master keyEvery workspace has one. It wraps folder keys so owners keep access, and it also protects the workspace's signing key.
Recovery fileOptional, downloaded by you, protected by a password you choose
Inbox recovery copyYour workspace keys, encrypted under your password or recovery key before they leave the browser

In the browser, key material is kept in IndexedDB, not in easy-to-scrape website storage. New polls publish X-Wing public keys; polls created before the post-quantum upgrade keep the RSA-OAEP keys they were made with. See Post-quantum cryptography.

#What admins see

Answers are decrypted in the admin's browser. Nothing shows below the poll's minimum group size, and never fewer than three. While the poll is open, the first results appear at the minimum group size plus three, later answers arrive in random groups of three to five, and a few answers are always held in reserve until the poll closes, so no one's answer can be picked out. At close, everything shows. How batched release works.

Scores and choices come back as totals. Written answers are shown word for word, shuffled and undated, once results open. Where the poll allows it, anyone who'd rather not be read can send just the topics they pick instead. Details: Aggregate results and anti-inference.

Warning

Free-text needs care

Open-ended answers can identify their author by content, not cryptography. Tell respondents in the question not to include names or details only they would know.

#What the server stores per response

A stored response is the poll it belongs to and its ciphertext. There's no timestamp, no submission order and no identity field on it. When results unlock, admins receive released answers shuffled, so the order says nothing about when each person answered.

A few counters sit beside the answers: how many answers arrived on each day (for the dashboard chart) and which release group each answer belongs to. Neither says anything about what an answer contains or who gave it. Data retention and metadata boundary lists everything we keep.

Downloadable recovery files use a versioned envelope (workspace metadata, integrity check, nested password-protected backup). Details: Recovery, emergency kit, and device handoff.

#What this does not protect against

The threat model covers each residual risk:

  • A compromised admin device with keys unlocked
  • Altered code served from a compromised site
  • Self-identifying content inside a written answer
  • Very small audiences where taking part is itself revealing
Note

Next

Continue with Zero-knowledge architecture for guarantee mapping, Post-quantum cryptography for the algorithms, or the threat model for residual risk.