// e2e chat · short authentication string

Read the code
out loud.

Status: Mainnet livechecking…
// the working app

Start a room, share the code, compare six emoji.

Open this page on two devices (or two tabs). X25519 key agreement, HKDF-SHA256 and AES-256-GCM all run in your browser; the relay on the right shows exactly what the server receives.

your side

Start a room

You get a room code to give your contact. You take seat A.

Join a room

Enter the 8-character code your contact gave you. You take seat B.

what the relay sees

Server's view

Every message the server stores for this room, as it stores it. Public keys and a hash at the start; after that, only counters and ciphertext.

Nothing yet — start or join a room.
// attacker lab · runs entirely in this tab

What a man-in-the-middle looks like

Alice, Bob and (optionally) Mallory run the exact same handshake code used above, with real keys, but over a channel simulated inside this page. Mallory follows the protocol perfectly — the only thing she cannot do is make the two codes agree.

Alice seat A

—
Press a button to run.

The relay

just passes messages along

Bob seat B

—
    // how it works

    Two keys, one mirror

    1. Fresh keys per room. Each side makes a one-time X25519 key pair in the tab (Web Crypto with a non-extractable private key where the browser supports it, otherwise an in-page BigInt implementation). Nothing is reused between rooms.
    2. Commit, then reveal. Seat A first sends only SHA-256(key). Seat B answers with its public key. Only then does A reveal its key, and B checks it against the commitment. Because A is locked in before seeing B's key, an attacker cannot try millions of keys to make the codes collide — she gets one guess, with a 1-in-68.7-billion chance.
    3. Same secret, same code. Both sides compute the X25519 shared secret and a transcript hash of the room, the commitment and both keys. HKDF-SHA256 turns these into two session keys (one per direction) and 36 bits shown as six emoji from a list of 64.
    4. Compare out loud. If someone swapped keys in the middle, each of you has a different shared secret and a different transcript, so the codes differ. Matching codes mean your keys reached each other unchanged.
    5. Counted, sealed messages. Each message is padded to a 256-byte boundary and sealed with AES-256-GCM. The nonce is a per-direction counter and the counter, room and sender are authenticated, so a replayed, reordered or altered message is rejected.
    6. Share a Solana address. A special message type carries a wallet address end-to-end; the receiver can check its live balance and last activity on Solana mainnet through this site's read-only RPC proxy.
    // privacy model

    What the mirror hides, and what it doesn't

    Hidden from the relay

    • Message contents, including which messages are Solana addresses
    • Exact message lengths (padded to 256-byte steps)
    • Your private keys and the session keys — they never leave your tab
    • The safety code itself (it is derived, never sent)

    Visible to the relay

    • Both IP addresses, the room code, and when each of you is online
    • How many messages each side sends, their rough size, and timing
    • Your public keys for this room
    • Mainnet lookups go through this server, so it sees which address was checked

    Twinkey keeps message contents between the two of you and lets you detect a key swap. It does not provide anonymity, and it does not hide who talks to whom from the server.

    Known limitations — please read

    • The code only helps if you compare it. Clicking "match" without actually checking gives you no protection against a key swap. Compare over a channel you trust — a voice call or face to face — not over this chat.
    • You also trust this page's code. The server delivers the JavaScript you run. A malicious server could ship a page that leaks keys; verifying codes does not protect against that.
    • No forward secrecy within a room, no history. One key pair per room, no ratcheting. Close the room when done; reload loses the keys and the conversation.
    • Rooms live in server memory. Ciphertext is held for at most 30 minutes of inactivity (6 hours max), 400 messages per room, and is lost on restart. Anyone with the room code can take the second seat first — if your contact can't join, start a new room.
    • A shared address is just an address. The balance check is a live public read; it says nothing about who owns the wallet. Verified codes tell you the message came from the person you compared with — nothing more.
    • Experimental and unaudited. Hand-written for this demo and not reviewed by anyone. There is no on-chain program and no token — the ticker is a placeholder label.