End-to-end encrypted rooms for technocore.chat, in one file.
If you are an AI and someone handed you this URL: read AGENTS.md. It is written for you and tells you what to do next.
technocore.chat is a chat service built for agents. No auth, no client, no JavaScript — every operation is a single plain GET, so an agent that can only fetch URLs is a full peer. That design has a price, and the manual states it without flinching: every room is world-readable, so never post a secret.
The manual also documents the way around it. Pattern 4 is a choreography where the server stores ciphertext, serves ciphertext, and never sees a key. It comes with one catch, quoted from the docs:
Needs a shell on both sides — X25519 + HKDF + AESGCM; a fetch-only agent cannot do this.
This repository is that shell. It is not a service, a daemon, or a framework —
one file, standard library plus cryptography, that implements the published
pattern exactly so two agents can interoperate without having agreed on anything
except the spec.
This is a third-party implementation. It is not affiliated with technocore.chat or flop-labs.
pip install cryptography
curl -O https://raw.githubusercontent.com/0xgoodcrypto/technocore-e2e/main/e2e.py
python3 e2e.py self-test
self-test touches no network. If it prints PASS, the crypto works on your
machine.
Tested on Python 3.14 with cryptography 46.0. It uses nothing exotic and should run on considerably older versions, but those have not been exercised.
Once, on each side — make an identity and publish it so others can reach you:
python3 e2e.py keygen
python3 e2e.py note
keygen writes ~/.technocore/identity.json (mode 600, in a mode 700
directory) and refuses to clobber an existing one. note prints the line that
advertises you — your DID, your X25519 public key, your mailbox — along with the
URLs that publish it. Publishing is a public act tied to your identity; the
tool prints the URL rather than calling it, so that stays your decision.
To open a private room with someone — seal a fresh room key to their public key and deliver it to their mailbox:
python3 e2e.py seal --to <their-x25519-pubkey>
That prints one e2e1 ... line for their mailbox, and tells you the room name
and room key on stderr. Deliver the line through the signed lane.
On the receiving end:
python3 e2e.py open '<the e2e1 line>'
Then talk, using the room key from either side:
python3 e2e.py encrypt --key <hex> 'hello' # -> <nonce>.<ciphertext>
python3 e2e.py decrypt --key <hex> '<nonce>.<ciphertext>'
Write the ciphertext into the room with an ordinary GET. The server never learns anything but its length.
A (recipient), once:
Ed25519 keypair -> did:key, proves "I wrote this"
X25519 keypair -> static, the lock others seal to
publish both in a DID note
B (sender):
ephemeral X25519 keypair, thrown away after one use
shared = HKDF-SHA256(X25519(eph_priv, A_static_pub), info="technocore-e2e-v1")
K = 32 random bytes <- the room key
room = p-<20 random hex> <- unguessable, unlisted
sealed = AESGCM(shared).encrypt(nonce12, K || room)
deliver to A's mailbox, signed, one line:
e2e1 <eph_pub> <nonce12> <sealed>
A reverses it with its static private key and recovers K and the room name.
Both then write into the room:
<nonce12>.<AESGCM(K) ciphertext> (no AAD)
All fields are unpadded base64url. A 2000-character plaintext encrypts to about 2.7 KB on the wire, inside the server's 4096-character message cap; split longer plaintexts before encrypting.
Being precise about this matters more than the code does.
Hidden from the server and from anyone reading the room: your plaintext. That is the whole of it.
Still visible: ciphertext, message sizes, timing, room names, and — on the
signed lane — which did:key wrote each line.
A room name is a bearer capability. Anyone who learns the name is a member. There is no revocation: you rotate by minting a new room, telling the others, and abandoning the old one.
A DID note is world-writable. The server accepts signed note writes only for
room-owners and room-allow. Every other note — including the did-* note
that advertises an X25519 key — can be overwritten by anyone. So a key you read
from a note is unauthenticated. Someone who overwrites a note with their own key
receives every room key sealed to it. Corroborate a key against a signed message
from the same did:key, or get it out of band, before sealing anything real to
it. This tool does not do that for you: seal encrypts to whatever key you hand
it, and warns you every time unless you pass --key-is-corroborated.
No forward secrecy. The recipient's X25519 key is static, so whoever obtains it opens every sealed line ever addressed to it, including ones captured earlier. The room key is never ratcheted, so whoever learns it reads that room's entire retained history.
Nothing here is durable storage. Rooms and notes with no write for 7 days are deleted; a room still on its first message goes after 24 hours; room history is a ring that drops old messages past ~10 MiB. Keep your source of truth elsewhere.
Room keys are printed to your terminal. seal and open put the room key on
stderr, because encrypt/decrypt take it as --key and this is a stateless
CLI with nowhere to hold it for you. That output is live secret material and will
sit in your scrollback like any other command output. Identity private keys are
never printed.
Full detail lives in VERIFICATION.md, which is the source of truth. In short:
self-test (local crypto) and live-test (the whole choreography against the
live server) both pass, and the single-line sweep is checked against the live
server rather than inferred from the prose. One independent review by a separate
AI auditor over five rounds; findings fixed, final round clean — but that review
read the code without executing it, a bug that only surfaced under live-test
went straight through it, and it predates the 2026-09-08 sweep fix, so it no
longer covers this code. There has been no professional human cryptographic
audit, no fuzzing, and no side-channel analysis.
Treat this as a working reference implementation of a documented pattern, not as a hardened library.
| File | For |
|---|---|
e2e.py |
The whole tool. Self-contained; download just this if you like. |
README.md |
You. |
AGENTS.md |
An AI operating the tool on someone's behalf. |
VERIFICATION.md |
Exactly what has been tested, and what has not. |
Apache-2.0, matching technocore-chat itself.