libID does not implement notarization itself. It relies on
TLSNotary and its upstream
tlsn implementation for the
notarization protocol and cryptography. This repository is a thin integration
wrapper that retains completed attestations for retrieval and exposes the
HTTP/WebSocket server used by browser clients, including ProxyMode support. We
are grateful to the TLSNotary contributors for building and sharing the
excellent protocol that makes this service possible.
The libID notary service. One binary, one signing identity, two duties:
- Platform session notarization — the notary acts as the MPC-TLS or ProxyMode (zkTLS) verifier for a prover's HTTPS session with a platform API (X, GitHub, …) and signs what was proven: tlsn attestations, EVM-ready transcript proofs, and on-demand token / user-identity attestations whose digests on-chain verifiers recover.
- Notarized JWKS readings — the notary co-fetches Google's OIDC signing
keys (
https://www.googleapis.com/oauth2/v3/certs) over MPC-TLS and signs aJwksRotationProofthat aJwksOraclecontract accepts, so an on-chain OIDC verifier can rotate Google's keys without trusting the submitter.
Both duties share one TCP listener: the notary verifies the MPC-TLS session
first, then dispatches on the TLS-certificate-verified server name. A session
with www.googleapis.com is answered with the JWKS proof shape; every other
session with the platform proof shape. The notary's signature alone registers
nothing — on-chain verifiers recover it against the notary public key served
at /info.
TCP wire protocol on NOTARY_PORT (default 7047) — length-prefixed JSON
after MPC-TLS, for Rust backend provers.
HTTP / WebSocket on NOTARY_WS_PORT (default 7048) — tlsn-js compatible:
| Route | What it does |
|---|---|
GET /info |
{version, publicKey} — compressed SEC1 notary public key, hex |
POST /session |
Create a session → {sessionId} (503 above NOTARY_MAX_SESSIONS) |
WS /notarize-proxy?sessionId= |
ProxyMode (zkTLS) verifier session — the primary browser path |
WS /notarize?sessionId= |
MPC-TLS verifier session (tlsn-js fallback path) |
GET /evm-proof/{sessionId} |
Long-poll the finished session's NotaryResponse |
GET /zk/proxy/attestation/{sessionId}?session_type=token|me&… |
On-demand signed token / user-identity attestation |
WS /proxy?token=<host> |
Raw WS↔TCP relay for the tlsn-js MPC path |
WS /notary |
Legacy TCP-over-WS path |
Sessions live at most 30 minutes; a background sweep evicts anything older, fetched or not.
Flags or environment variables:
| Flag | Env | Default | Meaning |
|---|---|---|---|
--host |
NOTARY_HOST |
127.0.0.1 |
Bind address |
--port |
NOTARY_PORT |
7047 |
TCP wire port |
--ws-port |
NOTARY_WS_PORT |
7048 |
HTTP/WS port (0 disables) |
--signing-key |
SIGNING_KEY |
— | Hex secp256k1 key, or kms:<key-id-or-alias> for AWS KMS |
--chain-id |
CHAIN_ID |
31337 |
Chain the attestations target (digest domain separator) |
--x-zk-verifier-address |
X_ZK_VERIFIER_ADDRESS |
— | ZK verifier contract recovering token/me attestations |
--verifying-contract |
VERIFYING_CONTRACT_ADDRESS |
zero address | Contract recovering the MPC-TLS digest (Registry, or GitHubIdentityVerifier in identity deployments). Legacy alias: --registry-contract-address / REGISTRY_CONTRACT_ADDRESS |
--platform-name |
NOTARY_PLATFORM_NAME |
api.x.com |
TLS server identity ProxyMode sessions must present |
--max-sessions |
NOTARY_MAX_SESSIONS |
1024 |
Concurrent-session cap |
--jwks-enabled |
NOTARY_JWKS_ENABLED |
true |
Serve JWKS notarization sessions on the TCP listener |
With a KMS key the private material never enters the process: every signature
is a kms:Sign call, including tlsn attestations (the sign step is lifted out
of tlsn and applied asynchronously, byte-identical to tlsn's own signer).
Released images are published to GitHub Container Registry:
docker run --rm \
-p 7047:7047 -p 7048:7048 \
-e NOTARY_HOST=0.0.0.0 \
-e SIGNING_KEY=<hex-or-kms:…> \
-e CHAIN_ID=1 \
-e X_ZK_VERIFIER_ADDRESS=0x… \
-e VERIFYING_CONTRACT_ADDRESS=0x… \
ghcr.io/libid-org/notary:latestTags: <version> and latest on every release; custom-<suffix> images can
be built from any ref via the Custom Docker image workflow (inputs: ref,
tag-suffix).
Each release also ships tlsn-wasm-<version>.tar.gz as a release asset:
tlsn_wasm.js, tlsn_wasm_bg.wasm and spawn.js, built from upstream
TLSNotary's crates/wasm at the exact tlsn revision this server pins —
prover and notary can never drift onto different protocol versions. Serve the
three files side by side (with a rewrite of /:path+/spawn.js to the root
copy) and the bundle is a drop-in for tlsn-js-style workers; unlike the npm
tlsn-js build it includes set_progress_callback.
To build locally: ./scripts/build-tlsn-wasm.sh --out <dir> (needs rustup,
wasm-pack 0.15.0, and a clang with a wasm32 backend — the script explains
exactly what is missing if something is).
The crate also builds as a library. notary::jwks exposes the prover-side
helpers a backend rotation listener needs:
jwks::prover::notarize_jwks(socket)— run the MPC-TLS JWKS prover against a notary's TCP port and get back the signedJwksRotationProof.jwks::mock::MockProver— build a structurally identical proof without MPC (zeroed handshake fields, real signature) for contract testing.
Shared primitives (digests, wire protocol, transcript math, signers) come from libID-rs.
MIT or Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.