security.txt

Security design · Technical overview

What a breach of our servers
should not reveal

For engineers and cryptographers checking how Krypton Ø protects what members share: the keys, the constructions built on them, what our servers store and observe, and where the design draws its boundaries. The non-technical version is How security works.

Last updated: November 5, 2026

01

Goals and threat model

Krypton Ø is a closed social network for people who know each other. The design assumes our servers can be breached, seized or run by someone untrustworthy, so they should hold nothing that lets anyone read what members share.

In scope

  • Server compromise. Full access to our databases, storage, backups and logs, now or later, doesn't reveal posts, chats, cards limited to Fam, media, files, call audio or notification text, and doesn't open members' encrypted backups or recovery backups.
  • The operator. We hold no key to member content. Staff see only what members choose to show them: items handed over in a report, requests sent to the Krypton Ø team, and what any member can see.
  • A tampering server. Keys pass through the server but are verified on devices against signatures, a certificate chain rooted in the app, and pinned keys. Every item carries its author's signature inside the encryption.
  • The network. End-to-end encryption runs on top of TLS.
  • Harvest now, decrypt later. Key handoffs between devices combine X25519 with ML-KEM-768 (section 9).
  • Stored relationships. The server doesn't keep who is Fam with whom, who sent what to whom, or who is in a chat.

What the design relies on

Like any end-to-end encrypted app, Krypton Ø relies on the member's own phone and on the app as distributed through the App Store and Google Play. Encryption can't control what recipients do with what they are shown. Network metadata, such as IP addresses and timing, is described in section 10.

02

Cryptographic building blocks

Only standard primitives, through open-source Rust implementations. There are no home-made ciphers, curves or hash functions.

PrimitiveSpecificationUsed for
XChaCha20-Poly1305RFC 8439, extended nonceContent, key wraps, notes and backups, with random 192-bit nonces
X25519RFC 7748Classical half of the hybrid KEM; per-item key envelopes
ML-KEM-768FIPS 203Post-quantum half of the hybrid KEM
Ed25519RFC 8032Identity and device signatures, verified strictly
ML-DSA-65FIPS 204A second identity key, bound to the Ed25519 identity
HKDF-SHA256RFC 5869Deriving keys from secrets, with one context label per use
SHA-256, SHA3-256, SHAKE256FIPS 180-4, FIPS 202Hashing, the hybrid combiner and seed expansion
VOPRF, ristretto255-SHA512RFC 9497Anonymous tickets
scryptRFC 7914Stretching the account PIN
BIP39 English word listBIP39Writing the recovery secret as words

Keys and nonces come from the operating system's CSPRNG.

03

Keys and identity

All keys are created on the member's phone. There's no password, phone number or email address: a device signs in by signing a server challenge with its device key.

KeyConstructionRole
Identity seed32 random bytes, expanded with SHAKE256 into an Ed25519 key and an ML-DSA-65 seedThe account's identity. Signs certificates, the encryption key and every item sent
Device keyEd25519, one per deviceSign-in. Certified by the identity key
Encryption seed32 random bytes, expanded with SHAKE256 into ML-KEM-768 and X25519 key pairs (X-Wing key generation)Receiving keys sealed to the member
Content keys256-bit randomOne per post or card; a post's photos, videos and files use the post's key
Pair and chat secrets256-bit random, symmetricShared by two Fam, or by a chat's members (section 5)

Signed statements

The identity key signs fixed-format, domain-separated statements: each device certificate; the full hybrid encryption key, X25519 and ML-KEM-768 parts together, so neither can be swapped alone; and a binding in which the Ed25519 and ML-DSA-65 keys both sign the Ed25519 public key with a SHA3-256 hash of the ML-DSA-65 public key. Each statement type begins with its own domain-separation label, so a signature made for one purpose can't be accepted for another.

Where keys live

Private keys are created and used inside the Rust security core; the JavaScript app layer works with opaque handles rather than private keys. At rest they are protected by the iOS Keychain or the Android Keystore, stay on that device and are left out of cloud backups. Only recovery moves them to a new phone. Our servers hold public keys and device certificates.

Verifying other members

Joining is by invitation or through the Krypton Ø team. Each identity key is certified by the member or team account that let that person in, and certificates chain back to root keys built into the app. Devices check that chain before accepting a Fam request, and before sending posts, chat invitations or calls to an account, then pin the verified key and reject a different one for that account later. Becoming Fam yields a statement signed by both identity keys. Chains travel from device to device; the server vouches for no one.

Invitation links and Fam QR codes carry, after the #, a one-time secret and a 128-bit fingerprint of the owner's identity key and hybrid key, which the opening device checks. The fragment isn't sent to our servers.

04

Encrypting content

  1. The core creates a fresh 256-bit content key for each post or card. A post's photos, videos and files are encrypted under the same key.
  2. The body and the author's signature are padded to a size class and encrypted with XChaCha20-Poly1305. Post text is padded to 256 bytes, doubling up to 64 KiB, then to multiples of 64 KiB.
  3. The content key is sealed for each recipient and delivered through their pair mailbox (section 5). The server stores the ciphertext under a random id, with no author or recipient field. Chat lines are encrypted under the chat's secret instead (section 5).

Signatures

The identity key signs a domain tag, the item's kind and context (the post or chat it belongs to), the author's account number, the item id, a timestamp and the SHA-256 hash of the body. The signature sits inside the ciphertext, hidden from the server. Recipients show an item only if it verifies against a key they verified, and the bound kind and context prevent replay elsewhere.

Key envelopes

  • Outer, 1,201 bytes for a 32-byte key: used wherever an envelope is stored on the server or travels on its own, such as Fam requests, invitation bundles, encrypted backups and reports. It uses the hybrid KEM in section 9.
  • Inner, 104 bytes: used only inside a pair-mailbox note, which is already encrypted under the pair's 256-bit secret. It holds an ephemeral X25519 public key, a nonce, and the content key under XChaCha20-Poly1305, keyed by HKDF-SHA256 of the shared secret salted with both public keys.

Each opener accepts exactly one length, so an outer layer can't be downgraded to the inner format. Non-canonical and small-order X25519 keys and all-zero shared secrets are rejected.

Media and files

Photos are encrypted whole. Videos and files use 64 KiB chunks whose nonces include the chunk index and whose associated data covers the header and a final-chunk flag, so truncation, reordering, extension and splicing fail; nothing is written out until every chunk authenticates. Files sit under random names, need a secret (hashed on the server) for every action, and aren't linked to an account or a post.

Cards

A member's name, bio, photo and Backers form an encrypted card. Its key goes only to the member's Fam through pair mailboxes, unless the member chooses Everyone; then it is also wrapped with a member key that every member holds. The server knows which setting was chosen.

05

Delivery without a social graph

The server stores no relationships. Devices deliver to mailboxes that aren't tied to accounts.

Pair secrets

When two members become Fam, the requesting phone creates a 256-bit pair secret and sends it sealed to the other member's hybrid key and signed by its identity key. Requests reach a request mailbox whose coordinates appear only in the recipient's encrypted card or link, and Fam requests and replies are all padded to the same size. Each phone keeps its own Fam list, backed up encrypted to its own key.

Mailboxes

period     30-day window; its start day is derived from the pair secret
read key   = HKDF-SHA256(pair secret, salt = label ‖ period, info = "read"  ‖ direction)
write key  = HKDF-SHA256(pair secret, salt = label ‖ period, info = "write" ‖ direction)
mailbox    = SHA-256(read key)
note       = nonce ‖ XChaCha20-Poly1305(message key, plaintext, AAD = label ‖ mailbox)
The message key is derived from the pair secret under its own label, separately from the mailbox keys.
  • Each pair has one mailbox per direction. Mailbox numbers change every 30 days, on a day derived from the pair secret, so pairs don't all change together.
  • The server stores the mailbox number and the SHA-256 of the write key. It accepts notes only with the write key and releases them only for the read key. No sign-in is involved.
  • The read and write keys the server sees in requests don't open notes, and the mailbox number is bound as associated data, so a note can't be moved to another direction or period. The sender's identity and signature are inside.

Chats

A chat is a random 256-bit secret shared by its members. The server knows a chat only by a tag derived from it, and stores lines encrypted under a key derived from the current secret, each signed by its author inside the encryption. The member list is a sequence of signed lines in the chat itself. When someone joins, leaves or is removed, the chat moves to a new secret, sealed to each member's encryption key. Typing signals use a channel named from the secret and encrypted with a key derived from it.

06

Joining

Joining goes through an existing member or the Krypton Ø team. Members obtain invitation tickets through an RFC 9497 VOPRF. The server evaluates blinded inputs and returns a DLEQ proof that the device checks against server keys built into the app, so no member can be singled out with a separate key. On redemption the server checks that a ticket is valid and unused, but can't tell whom it was issued to. The same mechanism meters mailbox creation, posts and uploads; a spent ticket isn't linked to the account it was issued to.

The inviting member's device passes an invitation bundle (a certificate over the new member's identity key, its own certificate chain, the member key and a ticket) through a one-time mailbox derived from the link secret. For people the Krypton Ø team lets in directly, the server knows the team brought them in.

07

Calls and notifications

Voice calls between two Fam use WebRTC, with audio encrypted between the two phones by DTLS-SRTP.

  • Ringing and signalling. Calls ring through the pair's mailboxes. Offers, answers and candidates travel on a channel named from the pair secret and call id, encrypted under a per-call key derived the same way, so the server can't read the session descriptions.
  • Authentication. Each phone signs its session description, including its DTLS fingerprint, and any later candidates with its identity key, bound to the call id, direction, sender and recipient. The other phone checks this against the key it verified when the two became Fam.
  • Relay. Call audio can pass through a TURN relay, which forwards SRTP packets it can't decrypt.
  • Records. The server doesn't record who calls whom. Call history stays on each phone.

Notifications

Push payloads carry no names or content. A short line encrypted under a key derived from the pair or chat secret is decrypted on the receiving phone, so Apple and Google see only ciphertext. Mailbox wake-ups are registered by topic, without an account number.

08

Account recovery

Recovery needs the recovery phrase the member wrote down, the account PIN and a share held by our server. Our server can't restore an account on its own, because it never has the phrase.

R        192 random bits, shown once as 18 BIP39 words (no PBKDF2 seed derivation)
U        256 random bits, held by the server
S        = HKDF-SHA256(R ‖ U)
backup   = version ‖ nonce ‖ XChaCha20-Poly1305(S, seeds, AAD = version)
seeds    = identity seed ‖ encryption seed
locator  = HKDF-SHA256(R)            the server stores only SHA-256(locator)
S and the locator use different HKDF labels, so the locator says nothing about S.
  • Setup. The core creates R and U, encrypts the two seeds into the backup and shows R once as 18 words for the member to write down. R isn't uploaded or included in cloud backups. Creating a new phrase replaces U and the backup.
  • Recovery. A new phone uses the phrase to find the account and proves the PIN, which is stretched on the phone with scrypt. Our server releases U and the backup only after a waiting period, and the member's existing devices are notified and can cancel.
  • Completion. The core checks the recovered identity key against the account before loading anything, and the new phone's device certificate must be signed by that recovered key. The Fam list and received keys then return from encrypted backups.
  • The server alone. U, the backup and the PIN check value open nothing without R, whose 192 bits leave about 296 work even under Grover's algorithm.

09

Post-quantum protection

Wherever one device hands a key to another across the server, Krypton Ø uses a hybrid KEM, so that ciphertext collected today stays closed if large quantum computers arrive.

public key      = pk_M (ML-KEM-768, 1,184 B) ‖ pk_X (X25519, 32 B)
encapsulation   = ct_M (1,088 B) ‖ ct_X (fresh ephemeral X25519 public key, 32 B)
shared secret   = SHA3-256(ss_M ‖ ss_X ‖ ct_X ‖ pk_X ‖ X-Wing label)
k               = HKDF-SHA256(shared secret, info = purpose ‖ 0x00 ‖ 0x02)
outer envelope  = 0x02 ‖ ct (1,120 B) ‖ nonce (24 B) ‖ body
body            = XChaCha20-Poly1305(k, key id (8 B) ‖ payload, AAD = 0x02 ‖ ct)
  • Construction. Key generation, encapsulation and the combiner follow X-Wing (draft-connolly-cfrg-xwing-kem-06) and are tested against that draft's vectors and NIST ACVP vectors for ML-KEM-768. An attacker has to break both X25519 and ML-KEM-768.
  • No visible recipient. The recipient's key id is inside the AEAD, so an envelope doesn't show whom it is for. Each purpose has its own HKDF context.
  • Coverage. Fam requests and the notes that set up pair secrets, invitation bundles, encrypted backups, the member key and reports. Mailbox notes, chat lines and notification lines are protected by 256-bit pair or chat secrets exchanged through this layer, and the per-item X25519 envelopes inside pair notes sit beneath it. Recovery is symmetric.
  • No substitution. The identity signature covers the ML-KEM-768 key together with the X25519 key, and devices pin what they verified.
  • Identity. Protocol signatures use Ed25519. Each account also has an ML-DSA-65 key from the same identity seed, bound at creation by signatures from both keys, with its hash in pinned keys and the built-in roots.

10

What our servers store and see

Encryption hides content, not all metadata. Section 2 of the privacy policy is the complete list.

Stored, not encrypted

  • A random account number, public keys and device certificates, membership status, role and joining period, and KryptonØ+ status and purchases.
  • Mailbox numbers, notes padded to fixed size classes, the hour each note arrived, the weeks a mailbox was made and last used, and its write-key hash, with no account attached.
  • Posts and chat lines as ciphertext without author or recipient, with size class, coarse times and counts needed for plan limits; which post a chat belongs to and its line order; media and files under random names, with their size.
  • Each card's setting (Fam only or Everyone), encrypted backups, recovery records, ticket counts per account and period, reports and push registrations.

Not stored

Who is Fam with whom, who sent what to whom, who is in a chat, blocks, Fam groups, and who invited or backed whom, except that the server knows when the Krypton Ø team let someone in.

Observable as it happens

  • The IP address and time of each request. Requests made close together from one address can be linked, including to an account that signed in from it. Hosting providers' access logs keep IP addresses for a limited period.
  • Mailboxes, posts or files collected in one request belong to one recipient (not who).
  • Uploads are made while signed in, so the server sees an account uploading something of a given size.
  • During a call, which two accounts are on the same live channel. Calls aren't recorded.
  • Apple and Google know which device a push token belongs to.

Reports

A reporting device re-encrypts the item, exactly as its author signed it, so that only our moderation staff can open it; the server can't. Unreported items stay closed.

11

Implementation

  • One core. Private-key and content-key operations run in a single Rust library shared by the iOS and Android apps. The JavaScript layer works with public keys, ciphertext and opaque handles.
  • Strict inputs. Ed25519 keys must be canonical, not of small order and torsion-free, with strict signature verification. X25519 keys must be canonical, on the curve and not of small order. ML-KEM-768 keys must pass the FIPS 203 modulus check. Envelopes, backups and statements have fixed lengths and version bytes.
  • Constant time. Comparisons of secret values run in constant time.
  • Tests. Known-answer vectors from NIST ACVP (ML-KEM-768, ML-DSA-65), the X-Wing draft, RFC 9497 and RFC 7914, and an adversarial suite of malformed keys, tampered envelopes and files, and cross-purpose signature reuse.

12

Reporting a vulnerability

If you find a security problem in the Krypton Ø apps, our websites or this design, please email security@krypton-0.com with [security] at the start of the subject line. Tell us what you found, how to reproduce it, and what it would let an attacker do. Please describe the issue in plain text; if you need to share files or a proof of concept, tell us and we'll arrange a secure way to receive them.

You can encrypt sensitive details to our PGP key, fingerprint 460D DBDD 84F1 9E52 94FA C4BF A6EE DF5D A0B7 57F2.

  • We'll confirm that we received your report and keep you informed while we work on a fix.
  • Please give us 90 days before publishing details, and let's agree on timing together. If a fix ships sooner, we can coordinate an earlier disclosure.
  • Test only with accounts you own. Don't access other members' data, disrupt the service or use social engineering.
  • We don't run a paid bug bounty. If you'd like, we'll credit you when the fix is published.

Machine-readable contact details: /.well-known/security.txt (RFC 9116).