@zkpass/transgate-extension-sdk
v0.1.0
Published
zkPass zkTLS verification SDK for Chrome MV3 extensions. Embed zkPass proof generation directly in your extension's background service worker, with no external extension and no backend required.
Maintainers
Readme
@zkpass/transgate-extension-sdk
zkPass zkTLS verification that runs entirely inside a Chrome MV3 background service worker — no external extension, no backend.
This library is for developers who want to embed zkPass verification directly into their own Chrome MV3 extension. Instead of depending on the external TransGate extension, you bundle proof generation (TLS interception, cryptographic verification, proof assembly) into your own extension and generate zkPass proofs straight from its background service worker. Your extension provides a published schema, AES key material, and triggers the target site's request; the SDK intercepts the matching traffic, builds the zero-knowledge proof, and returns the notary's signature and hashes.
Features
- Self-contained — no dependency on any external extension or backend.
- Built for the background SW — targets Chrome MV3 service workers (not web pages).
- WASM inlined — no separate
.wasmasset to host or path-coordinate. - Reusable & safe — one reusable instance, automatic WASM cleanup, never hangs (default 2-minute timeout), never throws (result-based errors).
- Stage-specific error codes — pinpoint exactly where a run failed.
Contents
- Installation
- Prerequisites
- Quick start
- How a verification run works
- Triggering the target request (
onListening) - API reference
- Runtime constraints
- Bundle size
- Building from source
- License
Installation
npm install @zkpass/transgate-extension-sdkThe package is ESM-only and ships a single bundled entry (dist/index.js) with type declarations (dist/index.d.ts). The WASM is inlined into the bundle — there is no separate asset to host.
Prerequisites
1. Manifest permissions + CSP. The WASM requires wasm-unsafe-eval:
{
"permissions": ["webRequest", "webNavigation", "tabs", "storage"],
"host_permissions": ["<all_urls>"],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}2. Must run in a background service worker. Calling from any other context returns INVALID_CONTEXT (the SDK never throws — it always resolves a result object).
Quick start
import { TransGateExtensionCore } from '@zkpass/transgate-extension-sdk';
const sdk = new TransGateExtensionCore();
const result = await sdk.runZKTLS({
schemaId, // a published zkPass schema id
appId, // a valid zkPass appId (an empty string is rejected by the server)
aesKeys: { aesKey, aesNonce }, // AES key material you provide (Uint8Array)
onStatus: (msg) => console.log('[zkTLS]', msg),
onListening: async () => {
// The request listener is now registered — make the target site emit the request.
await chrome.tabs.create({ url: 'https://example.com/path', active: false });
},
});
if (result.success) {
const { taskId, validatorAddress, validatorSignature, uHash, publicFields } = result;
// …forward the proof downstream (e.g. on-chain verification)
} else {
console.error(result.code, result.error);
}The instance is reusable — call runZKTLS as many times as you like. WASM is released automatically inside each call; you do not need destroy().
How a verification run works
A single runZKTLS call performs these steps in order and short-circuits on the first failure:
- Fetch schema —
GET /schema/{schemaId}(→SCHEMA_FETCH_FAILED) - Verify schema signature (→
SIGNATURE_INVALID) - Allocate a notary node —
POST /sdk/atask(→NODE_UNREACHABLE) - Intercept the matching request — registers a
chrome.webRequestlistener and waits for the target site to emit a request matching the schema's API definition (→INTERCEPT_TIMEOUT) - Generate the proof — 3P-TLS, VOLE, circuit build/evaluation, proof submission (→
SESSION_FAILED/TLS_PROXY_FAILED/PROOF_COMPUTE_FAILED/NODE_VERIFY_FAILED, withPROOF_GENERATION_FAILEDas a fallback)
The SDK only observes traffic and builds the proof — it never navigates, logs in, or triggers requests on your behalf. Causing the target request is the caller's responsibility (see below).
Triggering the target request (onListening)
For step 4 to succeed, the target site must emit the matching request after the listener is registered. Use the onListening callback for that exact moment:
await sdk.runZKTLS({
schemaId, appId, aesKeys,
onListening: async () => {
// Open / navigate so the page issues the schema's target API request.
// Login-gated sites (e.g. social networks) must already be logged in in the same browser profile;
// the opened tab reuses the browser's cookies.
await chrome.tabs.create({ url: targetUrl, active: false });
},
});onListening fires once, right after the chrome.webRequest listeners are registered. Prefer it over matching the onStatus text (STATUS_MSG.WAITING_MATCH) — onListening is a precise hook and won't break if progress wording changes. If the request is emitted before the listener is registered, it will not be captured and the run ends in INTERCEPT_TIMEOUT.
API reference
The public surface is intentionally small. All exports come from the package root:
import {
TransGateExtensionCore,
STATUS_MSG,
VERSION,
type RunZKTLSOptions,
type RunResult,
type ErrorCode,
type AesKeyMaterial,
} from '@zkpass/transgate-extension-sdk';class TransGateExtensionCore
| Member | Signature | Description |
|---|---|---|
| constructor | new TransGateExtensionCore() | Creates a reusable instance. Holds no cross-call state. |
| runZKTLS | (opts: RunZKTLSOptions) => Promise<RunResult> | Runs one full verification. Never throws; all failures are returned as a result. |
| destroy | () => Promise<void> | Optional, idempotent manual WASM release. Usually unnecessary — runZKTLS releases WASM internally on every call (success or failure). |
RunZKTLSOptions
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| schemaId | string | yes | — | A published zkPass schema id. |
| appId | string | yes | — | A valid zkPass appId. An empty string is rejected by the server (HTTP 500). |
| aesKeys | { aesKey: Uint8Array; aesNonce: Uint8Array } | yes | — | AES key material injected by the caller. |
| httpVersion | string | no | '1.1' | Override the HTTP version. /schema does not always return it, so the caller may set it. |
| timeoutMs | number | no | 120000 | Overall timeout. Guarantees the call cannot hang forever. |
| onStatus | (msg: string) => void | no | — | Progress callback. msg is one of the STATUS_MSG values. |
| onListening | () => void \| Promise<void> | no | — | Called once when the request listener is registered — the precise moment to trigger the target request. |
| signal | AbortSignal | no | — | Cancellation. See Runtime constraints for the best-effort semantics. |
RunResult
type RunResult =
| {
success: true;
taskId: string;
allocatorAddress: string;
allocatorSignature: string;
validatorAddress: string;
validatorSignature: string;
uHash: string;
publicFields: unknown[];
}
| { success: false; code: ErrorCode; error: string };On success you get the notary's validatorSignature, the uHash (nullifier hash), publicFields, and the allocator/validator addresses and signatures. On failure, code is a stable ErrorCode and error carries the underlying message.
ErrorCode
Returned in order of the verification steps; the first failing step wins.
| Code | When it happens |
|---|---|
| MISSING_AES_KEY | aesKeys (aesKey / aesNonce) was not provided. |
| INVALID_CONTEXT | Not running inside a Chrome MV3 service worker. |
| SCHEMA_FETCH_FAILED | Failed to fetch the schema, or the response was not a valid schema shape. |
| SIGNATURE_INVALID | Schema signature verification failed. |
| NODE_UNREACHABLE | Failed to allocate a notary node (/sdk/atask; e.g. an invalid appId). |
| INTERCEPT_TIMEOUT | No matching request was observed before timeoutMs. |
| SESSION_FAILED | Connecting to the notary node / establishing the zkTLS session failed. |
| TLS_PROXY_FAILED | 3P-TLS handshake or capture-replay failed. |
| PROOF_COMPUTE_FAILED | Local circuit / proof generation failed (includes WASM initialization). |
| NODE_VERIFY_FAILED | Node interaction failed (VOLE pairing / param upload / signature evaluation). |
| PROOF_GENERATION_FAILED | Other / uncategorized proof-body error (fallback — inspect error). |
| ABORTED | Cancelled by the caller's AbortSignal. |
STATUS_MSG
A frozen record of progress strings reported through onStatus. Use the constants rather than hard-coding the literal text so your logic survives wording changes:
import { STATUS_MSG } from '@zkpass/transgate-extension-sdk';
onStatus: (msg) => {
if (msg === STATUS_MSG.WAITING_MATCH) { /* listener is up */ }
}Keys include WAITING_MATCH, ALL_API_MATCHED, CONNECTING_NODE, SESSION_ESTABLISHED, INIT_WASM, WASM_INITIALIZED, STARTING_3P_TLS, SETTING_VOLE, GENERATING_CIRCUIT, BUILDING_CIRCUIT, EVALUATING_CIRCUIT, SENDING_HTTP2, GENERATING_PROOF, SUBMITTING_PROOF, VERIFICATION_COMPLETE.
VERSION
import { VERSION } from '@zkpass/transgate-extension-sdk'; // e.g. '0.1.0'Runtime constraints
- Notary node network access. During
runZKTLSthe SDK opens a TLS connection toschema.nodeHost; this must be covered byhost_permissions: ["<all_urls>"]. - WASM is inlined into the bundle (no separate asset / path coordination), which increases size (see Bundle size).
AbortSignalis best-effort. WASM computation cannot be interrupted mid-call; cancellation is checked between phases.- No hang without
signal/timeoutMs. The default 2-minute timeout applies; a timeout returnsINTERCEPT_TIMEOUT. chrome.webRequestis observe-only. No blocking is performed under MV3.
Bundle size
dist/index.js is roughly 12 MB (gzip ≈ 5.4 MB). The bulk comes from the zkTLS WASM and DOM-parsing dependencies — an inherent cost of the verification logic, not avoidable overhead.
Building from source
Contributing to or building the SDK itself? See docs/building.md for prerequisites, build, packaging & publishing, regenerating the inline WASM, scripts, and CI.
License
See LICENSE.
