TLSNotary · Cashu

Prove what a website told you.

This is a TLSNotary notary server and its clients. You fetch a page over HTTPS as you normally would; the notary takes part in the TLS session without seeing it and signs an attestation. You then show anyone the part you choose (one field of a JSON response, a single line of a page) with proof that the server really sent it.

The notary charges each session in Cashu ecash, so its users do not need an account.

You need to run the server yourself. There is no public notary, and the clients on this page do nothing without one. Setting one up takes a Linux server, a domain name and about an hour: see below.

How it works

  1. Pay. Your client asks the notary for a ticket and gets HTTP 402 with a Cashu payment request. It pays with an ecash token from one of the accepted mints and receives a ticket, good for 3 attempts within 30 minutes.
  2. Connect. Your client opens the TLS connection to the website. The TLS keys are split between you and the notary by multi-party computation (MPC-TLS): only you can read the traffic, and you cannot forge it alone. Your client checks the server's certificate as a browser would.
  3. Attest. When the request is done, the notary signs an attestation: commitments to the encrypted transcript, the server's identity and the time. The notary learns neither the request, the response nor the site's name.
  4. Present. From the attestation and your local secrets you build a presentation that reveals only the bytes you choose. Everything else stays hidden and shows as X.
  5. Verify. Anyone who trusts the notary's key verifies the presentation offline: the server name and its certificate chain, the time, and the revealed bytes. No call to the notary or the website is needed.

The notary runs upstream TLSNotary tlsn (a pinned revision after v0.1.0-alpha.15) unchanged, wrapped with payment, limits and an egress policy. Upstream no longer ships a notary server or runs a public one; this is a notary you can run.

Run a notary

The server is tlsnc-notary, a single Rust binary behind nginx. It sells tickets, takes part in the MPC-TLS sessions, signs attestations and keeps the ecash it earns in a wallet of its own. You need:

git clone https://github.com/jooray/tlsnotary-cashu && cd tlsnotary-cashu
cargo zigbuild --release -p tlsnc-notary --target x86_64-unknown-linux-gnu.2.28
npm install && deploy/build-web.sh

export TLSNC_HOST=root@server.example.org TLSNC_DOMAIN=notary.example.org
deploy/deploy.sh      # user, config, key, systemd units, nftables, SELinux, nginx, web app

Then edit /etc/tlsnc/config.toml: the mints you accept, your prices, and the public addresses of your own machines, which the notary must never connect to. The notary connects to hosts its clients name, so it is fenced in three ways (its own address checks, systemd and nftables) and reaches only the public internet on port 443. The full guide, with every step by hand, is DEPLOY.md.

Your notary serves the web app at /app/ and publishes its key at /.well-known/tlsnc-notary.json. Give your users its URL; every client below takes it as a setting.

Notarize a page

Command line: tlsnc

The CLI connects to the website itself, so the notary never touches the target. It is the cheapest option and suits scripts and agents. It has a Cashu wallet built in.

# build it (Rust ≥ 1.95), or take a binary from the latest release
cargo install --locked --git https://github.com/jooray/tlsnotary-cashu tlsnc

export TLSNC_NOTARY=https://notary.example.org     # your notary

tlsnc receive 500                 # Lightning: prints an invoice (and a QR code), waits for payment
tlsnc receive cashuB…             # or receive a Cashu token you already have
tlsnc balance

tlsnc notarize https://httpbin.org/json            # pays the ticket, writes notarized.attestation + .secrets
tlsnc present --json slideshow.author              # reveal one JSON field, writes notarized.presentation
tlsnc verify notarized.presentation                # checks it against the notary's published keys

Installing step by step, including checking a release binary: README. Headers and bodies work like curl: -H 'Authorization: Bearer …', -X POST -d '…'. Authorization, Cookie and similar headers are never revealed unless you pass --allow-credentials. tlsnc backup prints the wallet's 12 words; tlsnc restore "…" brings the funds back on another machine.

In the browser

The web app, served by your notary at /app/, runs the whole prover in WebAssembly in the browser: enter a URL and optional headers (an API token, for example), paste a Cashu token, and download a presentation. A web page cannot reach a website by raw TCP, so the browser's connection goes through the notary's bridge, which forwards only encrypted TLS bytes. That counts as notary egress and costs more.

Pages you are logged in to need your cookies, which a web page cannot read. For those, use the browser extension: it notarizes the tab you are on (with its cookies, if you allow it for that site) and carries its own small Cashu wallet with balance, history and a 12-word backup. Download tlsnc-extension.zip from the latest release (or from your notary's /dl/), unzip it, and load the folder in chrome://extensions with Developer mode on (“Load unpacked”). Then enter your notary's URL in its settings. Details.

From your own code

Rust: tlsnc-client (prover and presentations) and tlsnc-verify. JavaScript: @tlsnc/client (pay, notarize, present in the browser) and @tlsnc/verify (verification only, 600 KB of WebAssembly, works in Node and in any page). See the README.

Paying with Cashu

Each notary sets its own prices, per session in sat, and shows them at /info. The defaults are high on purpose: a notary should not be worth using as a cheap proxy, and a VPN should stay cheaper. Who opens the connection to the website decides the column.

TierRequestResponseYou connect
CLI, MPC
Notary connects
browser, proxy
S≤ 4 KiB≤ 1 MiB42210
M≤ 16 KiB≤ 2 MiB100500
L≤ 32 KiB≤ 2 MiB2001 000

The request limit is what you send (URL, headers, body); responses are cheap to notarize. A ticket allows 3 attempts: a failed session is retried with the same ticket, not refunded. Mint fees are on the payer.

Get ecash from one of the notary's mints (listed in its /info) with tlsnc receive <amount> (pay the Lightning invoice it prints), or with any Cashu wallet (Minibits, cashu.me, …) set to one of those mints, then send yourself a token.

The protocol

Tickets are sold over plain HTTP following NUT-24 (HTTP 402 with an X-Cashu header carrying a NUT-18 payment request):

POST /v1/tickets?tier=S&egress=client
→ 402 Payment Required
  X-Cashu: creqA…            # amount (net of mint fees), unit sat, accepted mints

POST /v1/tickets?tier=S&egress=client
X-Cashu: cashuB…             # a token from one of those mints, at least the amount plus input fees
→ 200 {"ticket":"tk_…","expires_at":…,"attempts":3,"tier":{…},"egress":"client","received":42}

GET /v1/notarize             # WebSocket; the ticket rides in the subprotocol header
Sec-WebSocket-Protocol: tlsnc, tlsnc.tk_…

Over that WebSocket runs an unmodified tlsn session, followed by one length-prefixed frame each way: the attestation request and the signed attestation. A token that is worth too little is returned as a refund token in the 400 response. POST /v1/tickets/status (body {"ticket":"tk_…"}) tells why a session was refused. Full description: DESIGN.md.

Verifying

A presentation is only as good as the notary key you trust. Each notary publishes its keys at /.well-known/tlsnc-notary.json; pin the key rather than fetching it each time.

Verify with tlsnc verify, with @tlsnc/verify in a page or in Node, or in the web app. Verification checks the signature, the server certificate against the Mozilla roots, and that the revealed bytes match the attested transcript.

Limits and privacy