@uuaid/plinth
v0.1.0
Published
Plinth — Pseudonymous Link-invariant Network Transit Hops. Unlinkable mix layer beside Pillar: strips identifiers between hops and exposes an OpenRouter-shaped gateway. One operator is not a global-adversary mixnet.
Readme
@uuaid/plinth
Plinth is the stone base a pillar stands on. Authenticated mail still stands on Pillar. When you do not want the column (UUAID) visible between hops, you stand on Plinth.
Pseudonymous Link-invariant Network Transit Hops
This is a sibling of @uuaid/pillar, not a fork. Pillar envelopes keep
sender and recipient in the clear on purpose (inbox auth, F1 sender binding).
Plinth is a new packet that strips those identifiers hop by hop.
What it is
- Mix hops — each hop unwraps one layer, sees only the previous connection and the next hop, forwards a 16 KiB packet. No hop log can contain both endpoints.
- OpenRouter-shaped gateway —
POST /v1/chat/completionsandGET /v1/models. The bearer is an unlinkable mix ticket, not a UUAID.modelisagent/<role>in v0.openai/...goes through the same mix hops whenOPENAI_API_KEYorOPENROUTER_API_KEYis set; the key stays at the exit and never rides the mix wire. OpenRouter:freeslugs andopenrouter/freetake the same path. Without a key, those still return501 model-not-routed.
Conversation continuity is the mixer trick: deposit a request, withdraw a reply to a fresh address. SURBs carry the return path. Session tickets derive the next note so turn N+1 does not reprint turn N’s ticket on the wire.
Honest limit
One operator is not an untraceable mixnet. Three processes on one Mac prove log unlinkability (hop 1 never writes Bob; hop 3 never writes Alice; hop 2 writes neither). A global passive adversary who sees every NIC, or a single operator who runs every hop, can still correlate. Production needs independent operators. Cover traffic is designed, not shipped. Do not claim “untraceable.”
See THREAT-MODEL.md and LAYER.md.
Install
# from the sibling checkout
cd /Users/z/zz-station/pillar/uuaid-plinth
npm installNode.js >= 20. Depends on @uuaid/pillar (file: link to ../uuaid-pillar-node)
for sealPayload / openPayload, jcs, and Keychain. Hop AAD is
uuaid-plinth-hop/1:<packetId>.
Quick start (local demo — one operator)
node bin/plinth.mjs demo --port 8788
# prints gateway URL + a ticket
curl -s http://127.0.0.1:8788/v1/models
curl -s http://127.0.0.1:8788/v1/chat/completions \
-H "Authorization: Bearer <ticket>" \
-H "Content-Type: application/json" \
-d '{"model":"agent/echo","messages":[{"role":"user","content":"hi"}],"stream":false}'stream: true is rejected (400). SSE timing would undo the mix.
Open the same origin in a browser: http://127.0.0.1:8788/ — issue a ticket, send a message. The tab is a client (fetch + /src/client.mjs). Mix hops and the gateway stay on Node.
Browser
Yes, as a client. No, as a public mix hop.
A page can wrap the OpenRouter-shaped API with ordinary fetch. That is how agents will run in a tab, a WebView, or an extension. @uuaid/plinth/client and @uuaid/plinth/ticket-format have no node: imports.
A browser tab cannot be a mix node: it has no stable inbound address, cannot listen on /v1/packet, and would leak the hop through the user’s residential IP. Gateway and hops stay on Node (or another always-on host). Last-mile Pillar crypto still uses Node’s node:crypto via @uuaid/pillar.
import { complete, issueTicket, listModels } from "https://your-gateway/src/client.mjs";CORS is open on the gateway (Access-Control-Allow-Origin: *) so a page on another origin can call it. The gateway still sees the caller’s IP. It does not store Referer or a user field.
Bins
| Bin | Role |
|---|---|
| plinth | init, send, recv, ticket, demo |
| plinth-mix | one mix hop (--port, --delay, --name) |
| plinth-gateway | OpenRouter-shaped HTTP (--directory, --allow-issue, --last-mile) |
Last mile
The mix wire is never a Pillar envelope. An optional exit identity (fresh
key, not Alice) may seal() a Pillar v2 envelope and POST it to the existing
carrier so a pillar-responder can answer. The fleet agent sees the exit
UUAID, not Alice.
Tests
npm test # unit
npm run test:int # three-hop blender proof + gateway
npm run test:allThe integration test is the product: hop logs, SURB reply, ticket unlink, constant size, replay reject, gateway schema.
License
Apache-2.0.
