Skip to main content

Verifier Server

The TLSNotary Verifier Server is a Rust-based HTTP/WebSocket server that acts as the verifier party in Multi-Party Computation TLS (MPC-TLS). It validates cryptographic proofs generated by the browser extension without ever seeing the user's private data.

Why a Verifier Server?

TLSNotary uses Multi-Party Computation (MPC) to generate cryptographic proofs of TLS data. This requires two parties:

  1. Prover (Browser Extension) — the user generating the proof
  2. Verifier (This Server) — an independent party that validates the proof

What the Verifier Does

  • Participates in MPC-TLS handshake — co-signs the TLS handshake without seeing plaintext data
  • Validates transcript integrity — ensures the HTTP request/response hasn't been tampered with
  • Generates proof — creates cryptographic proof that data came from a genuine TLS connection
  • Forwards requests — acts as a WebSocket proxy to route TLS traffic to target servers

What the Verifier Does NOT See

  • User credentials — authentication tokens, cookies, and API keys remain encrypted
  • Hidden data — only data marked with action: 'REVEAL' in handlers is visible
  • Committed data — data marked with action: 'PEDERSEN' is sent as a hash commitment, not plaintext

Architecture

Key components:

  1. Session Management — Thread-safe HashMap stores session configuration
  2. WebSocket Endpoints/session, /verifier, and /proxy handle different protocol stages
  3. MPC-TLS Verification — Uses tlsn crate for cryptographic verification
  4. Built-in Proxy — WebSocket-to-TCP bridge forwards requests to target servers
  5. Webhook System — Fire-and-forget POST notifications with redacted transcripts

Built-in WebSocket Proxy

note

The recommended approach is to run a WebSocket-to-TCP proxy on the prover's device for maximum control and privacy. However, this requires setup effort from the user. For convenience, the verifier includes a built-in proxy that works out-of-the-box. Any WebSocket-to-TCP proxy can be used — you can run your own, use a third-party service, or use the verifier's built-in proxy.

The verifier includes a built-in proxy that eliminates the need for external proxy services.

Endpoint: ws[s]://<host>/proxy?token=<target-hostname>

EnvironmentURL
Localws://localhost:7047/proxy?token=api.x.com
Productionwss://demo.tlsnotary.org/proxy?token=api.x.com

The token parameter specifies the target server hostname. The proxy:

  • Parses hostname and port from the token parameter (defaults to port 443)
  • Establishes a TCP connection to the target server
  • Bridges bidirectionally: WebSocket messages ↔ TCP stream
  • Cleans up automatically when either side disconnects
  • Logs total bytes forwarded for debugging

API Endpoints

Health Check

GET /health

curl http://localhost:7047/health
# ok

Create Session

WebSocket /session

Creates a new MPC-TLS verification session.

Message (JSON):

{
"maxRecvData": 16384,
"maxSentData": 4096,
"sessionData": {
"userId": "user_123",
"purpose": "twitter_verification"
}
}
FieldTypeDefaultDescription
maxRecvDatanumber16384Maximum bytes the prover can receive
maxSentDatanumber4096Maximum bytes the prover can send
sessionDataobjectCustom metadata, included in webhook notifications

Response:

{
"sessionId": "550e8400-e29b-41d4-a716-446655440000"
}

Session lifecycle:

  1. Extension opens WebSocket to /session and sends configuration
  2. Server generates UUID, stores config, returns sessionId
  3. Extension opens second WebSocket to /verifier?sessionId=<id>
  4. After verification completes, session is cleaned up

Verifier Connection

WebSocket /verifier?sessionId=<session-id>

Connects to an existing session as the verifier party. Validates the sessionId, retrieves session config, spawns the MPC-TLS verification task, and returns redacted transcripts.

Sessions are single-use and automatically removed after the WebSocket closes.

WebSocket Proxy

WebSocket /proxy?token=<host>

See Built-in WebSocket Proxy above.


Session Flow

Complete flow for generating a TLS proof:


Webhook System

The verifier can send proof data to external services via HTTP POST. This enables backend integration for proof verification, storage, or business logic.

Configuration

Webhooks are configured in config.yaml:

webhooks:
# Per-server webhooks (matched by target hostname)
"api.x.com":
url: "https://your-backend.example.com/webhook/twitter"
headers:
Authorization: "Bearer your-secret-token"
X-Source: "tlsn-verifier"
Content-Type: "application/json"

"api.github.com":
url: "https://your-backend.example.com/webhook/github"
headers:
Authorization: "Bearer another-token"

# Wildcard: catch-all for any unmatched server
"*":
url: "https://your-backend.example.com/webhook/default"
headers:
X-Source: "tlsn-verifier"

Matching logic: exact hostname match first, then wildcard "*" fallback, then skip (no error).

Payload

{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"sessionData": {
"userId": "user_123",
"requestId": "req_abc",
"purpose": "account_verification"
},
"server_name": "api.x.com",
"redactedTranscript": {
"sent": "<base64-encoded request with redactions>",
"recv": "<base64-encoded response with redactions>"
},
"revealConfig": [
{ "type": "SENT", "part": "START_LINE", "action": "REVEAL" },
{ "type": "RECV", "part": "BODY", "action": "REVEAL", "params": { "type": "json", "path": "screen_name" } }
]
}

The redactedTranscript contains only data marked as revealed in the handlers. Data with action: 'PEDERSEN' or unmarked data is not included.

Example Backend Handler

// Express.js webhook endpoint
app.post('/webhook/twitter', async (req, res) => {
const { sessionId, sessionData, server_name, redactedTranscript, revealConfig } = req.body;

// Decode base64 transcripts
const sentData = Buffer.from(redactedTranscript.sent, 'base64').toString('utf-8');
const recvData = Buffer.from(redactedTranscript.recv, 'base64').toString('utf-8');

console.log('Session:', sessionId);
console.log('User ID:', sessionData.userId);
console.log('Revealed request:', sentData);
console.log('Revealed response:', recvData);

await db.proofs.insert({
sessionId,
userId: sessionData.userId,
server: server_name,
sentData,
recvData,
timestamp: new Date(),
});

res.status(200).json({ received: true });
});

Behavior

Webhooks are fire-and-forget:

  • Sent asynchronously in a separate tokio::spawn task
  • Errors are logged but do not affect the proof result
  • No retry logic (single attempt)
  • Default HTTP client timeout (~30 seconds)

If the webhook endpoint is down, the proof still succeeds. Check verifier logs for webhook errors.


Deployment

Local Development

cd packages/verifier
cargo run
# Server starts on http://0.0.0.0:7047

Production Build

cargo build --release
./target/release/tlsn-verifier-server

Docker

For a complete Docker deployment example with docker-compose, see the official docker-compose.yml in the repository.

Quick start with docker-compose:

git clone https://github.com/tlsnotary/tlsn-extension.git
cd tlsn-extension/packages/demo
docker-compose up verifier
# Server starts on http://0.0.0.0:7047

Configuration

Server Settings

  • Host: 0.0.0.0 (all interfaces)
  • Port: 7047
  • Source: packages/verifier/src/main.rs

Webhook Configuration

Location: packages/verifier/config.yaml

  • Server loads config.yaml from the current working directory
  • If not found, webhooks are disabled (no error)
  • Invalid YAML causes startup failure

CORS

Default: permissive (allows all origins). For production, restrict to specific origins:

let cors = CorsLayer::new()
.allow_origin("https://demo.tlsnotary.org".parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST])
.allow_headers(Any);

Logging

Set via RUST_LOG environment variable:

RUST_LOG=info cargo run     # Recommended for production
RUST_LOG=debug cargo run # Detailed logs
RUST_LOG=trace cargo run # Very verbose

Development

Project Structure

packages/verifier/
├── src/
│ ├── main.rs # HTTP server, routing, WebSocket handlers
│ ├── verifier.rs # MPC-TLS verification logic
│ ├── axum_websocket.rs # WebSocket-to-AsyncRead/Write bridge
│ └── tests/ # Integration tests
├── Cargo.toml
├── Cargo.lock
├── config.yaml # Webhook configuration
└── Dockerfile

Testing

cargo test                      # Run all tests
cargo test -- --nocapture # With output
cargo test test_name # Specific test

Testing with the Extension

  1. Start the verifier:

    cd packages/verifier
    RUST_LOG=debug cargo run
  2. Build and load the extension:

    cd packages/extension
    npm run dev
    # Load from packages/extension/build/ in chrome://extensions/
  3. Open the Developer Console (right-click extension icon), paste plugin code, and click Execute.

  4. Check verifier logs:

    [Session] New session created: 550e8400-...
    [Proxy] New proxy request for host: api.x.com
    [550e8400] Successfully proxied 1234 bytes
    Verification complete

Common Issues

IssueSolution
Address already in use (os error 48)Kill existing process: lsof -ti:7047 | xargs kill -9
failed to load config.yamlCreate config.yaml or run from the directory containing it
WebSocket upgrade failsEnsure client sends Upgrade: websocket and Connection: Upgrade headers
Proxy connection timeoutCheck target server is reachable: nc -zv api.x.com 443