VEILED CONTACTBeta

How the crypto works

Every message and every block of voice is locked on the device that sends it, and the server passes it on without being able to open it.

The cryptography has not had an independent audit. Tests with recorded values run on every change. They are not a security review. The specification lists the claims and their limits.

The whole design is five ideas
  1. Identity is a key on the member's own device

    The first time a client runs, it makes two keys. One signs what it sends (Ed25519). The other agrees on a shared secret with other people (X25519). The private halves of both stay on the device and are never sent. The server knows a member by the public half and gives out a small user number. A name and its keys keep working when that person reconnects from a different address. There is no account to create and no email address to give.

    The client shows a short fingerprint, eight groups of four characters. Compare it with the fingerprint the host published.

  2. A channel has one key, and every member gets their own copy of it

    Whoever creates a channel makes a random 32-byte key for it. To hand that key around, each member's copy is put inside a locked packet addressed to that member's public key. Only that member can open it, and the server cannot, even though it stores the packets and passes them on. It hands each member the ones addressed to them.

  3. Messages are locked and signed before they leave the device

    A message is locked with XChaCha20-Poly1305, a standard modern encryption method, using a key worked out from the channel key. The lock is tied to the message's context: which channel, which key generation, which sender, which message id. Move a locked message into another channel, or pass it off as someone else's, and the check fails. On top of that, the sender signs the message id together with the locked text. The server cannot read a message, invent one, or change their order.

  4. Voice blocks are locked the same way, one packet at a time

    Sound is cut into blocks of 10 ms and each block is locked with a voice key worked out from the channel key. Every block travels in a fixed envelope of 28 bytes. The envelope names the channel, the sender, the key generation and a sequence number. The server reads that envelope to know where to pass the block and sends the rest on unchanged. A listener keeps a note of the last 64 sequence numbers from each sender. A block that arrives twice is dropped and counted as loss, rather than played again.

  5. Removing someone rotates the key

    When a member leaves or is removed, one of the remaining members makes a fresh channel key. It is handed to everyone still there, along with a signature proving it came from somebody who held the previous key. New messages use the new key. Messages sent before the change stay readable to anyone who already had that round of the key. Any scheme that rekeys on removal has the same limit.

The building blocks

Standard, well-reviewed pieces. None of this is invented here.

What is used, and what for
JobWhat does it
Locking messages and mediaXChaCha20-Poly1305, 24-byte nonce, 16-byte authentication tag
Handing a channel key to one memberSealed box over X25519 and XSalsa20-Poly1305
IdentityEd25519 for signing, X25519 for key agreement
Turning the join password into a keyArgon2id, 64 MiB memory, 3 passes, 1 lane, 32-byte output
Proving the password without sending itHMAC-SHA256 over a nonce the server chose
Fingerprints people compare by eyeBLAKE2b-256, printed as 8 groups of 4 hex characters

The client uses libsodium, which is bundled with it and served by the server it belongs to. It is never fetched from an outside service. Nothing in the key handling depends on an outside service staying up.

How the claims are checked

Every check here runs on a recorded value.

Recorded test values run automatically
A set of recorded values fixes the exact bytes for working out a key, locking a message and locking a media block. The same file fixes the bytes for handing over a channel key and for turning a password into a key. It also holds a handful of deliberately damaged inputs, which must fail. The Rust code and the browser client both check themselves against the same file, so the two cannot drift apart.
Tampering has to fail loudly
Flipping a single byte has to break the check. That applies in the message's context, in the locked text and in the one-time random value that goes with it. Those cases are part of the recorded set, so the guarantee is tested on every change.
The specifications can be read
The crypto design states what is protected and what is not, in the project's own words. The wire protocol fixes every field number, including the 28-byte media envelope. Both are frozen. A change to either gets a version bump.
No independent audit yet
The building blocks are libsodium's, and the design is fixed by a frozen specification. Everything the client claims is checked against recorded values on every change, and none of it has been reviewed by anyone outside the project. Treat this release as something to test with. Do not use it to protect anything you care about.
What is deliberately not claimed
Resistance to traffic analysis, cover traffic and deniability are not delivered by this version. Neither is protection from a host who replaces the web client with an edited copy, nor identical rebuilds from source. The specification lists all of them as out of scope.