> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.umbraprivacy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ZK Provers

> Custom Groth16 provers in V18: provers ship inside @umbra-privacy/sdk/zk-prover (no separate web-zk-prover package), CDN asset provider, web worker setup with comlink, 2-8s browser / 1-3s Node.js proof generation.

## Overview

Stealth Pool Note creation and burning require client-side generation of [Groth16](https://eprint.iacr.org/2016/260.pdf) zero-knowledge proofs. The SDK does not bundle a default prover — you provide one as a dependency injection.

**The prover ships inside `@umbra-privacy/sdk/zk-prover`.** The standalone `@umbra-privacy/web-zk-prover` package has been deleted. All factories listed below wrap [snarkjs](https://github.com/iden3/snarkjs) and handle proving-key loading from Umbra's CDN by default.

## Quick start

Per-circuit factories are exported from `@umbra-privacy/sdk/zk-prover`. Pair each one with the matching high-level factory in `/deposit` or `/burn`.

```typescript theme={null}
import {
  getETAIntoStealthPoolNoteCreatorProver,
  getClaimReceiverClaimableUtxoIntoEncryptedBalanceProver,
} from "@umbra-privacy/sdk/zk-prover";
import { getETAIntoReceiverBurnableStealthPoolNoteCreatorFunction } from "@umbra-privacy/sdk/deposit";
import { getReceiverBurnableStealthPoolNoteIntoETABurnerFunction } from "@umbra-privacy/sdk/burn";
import { getUmbraRelayer } from "@umbra-privacy/sdk";

// 1. Create the provers (uses CDN asset provider by default).
const createProver = getETAIntoStealthPoolNoteCreatorProver();
const burnProver   = getClaimReceiverClaimableUtxoIntoEncryptedBalanceProver();

// 2. Pass them to the SDK factories.
const createNote = getETAIntoReceiverBurnableStealthPoolNoteCreatorFunction(
  { client },
  { zkProver: createProver },
);

const r = getUmbraRelayer({ apiEndpoint: "https://relayer.api.umbraprivacy.com" });
const burn = getReceiverBurnableStealthPoolNoteIntoETABurnerFunction(
  { client },
  {
    fetchBatchMerkleProof: client.fetchBatchMerkleProof!,
    zkProver: burnProver,
    relayer: {
      submitBurn:        r.submitClaim,
      pollBurnStatus:    r.pollClaimStatus,
      getRelayerAddress: r.getRelayerAddress,
    },
  },
);
```

## Available factory functions

All factories accept an optional `provider` parameter of type `IZkAssetProvider`. When omitted, the default CDN provider is used.

**Creator provers (renamed in V5):**

* `getETAIntoStealthPoolNoteCreatorProver(provider?)` → `IZkProverForETAIntoStealthPoolNote`. Shared by both self-burnable and receiver-burnable ETA-source creators.
* `getATAIntoStealthPoolNoteCreatorProver(provider?)` → `IZkProverForATAIntoStealthPoolNote`. Shared by both self-burnable and receiver-burnable ATA-source creators.

**Burner provers:**

* `getClaimSelfClaimableUtxoIntoEncryptedBalanceProver(provider?)` → `IZkProverForClaimSelfClaimableUtxoIntoEncryptedBalance`.
* `getClaimReceiverClaimableUtxoIntoEncryptedBalanceProver(provider?)` → `IZkProverForClaimReceiverClaimableUtxoIntoEncryptedBalance`.
* `getClaimSelfClaimableUtxoIntoPublicBalanceProver(provider?)` → `IZkProverForClaimSelfClaimableUtxoIntoPublicBalance`.

**Registration:**

* `getUserRegistrationProver(provider?)` → `IZkProverForUserRegistration`.

<Note>
  The **burner** prover factory names use the `getClaim…ClaimableUtxo…` spelling, while the **creator** prover factories use the Stealth Pool Note vocabulary (`getETAIntoStealthPoolNoteCreatorProver`, `getATAIntoStealthPoolNoteCreatorProver`). Both sets coexist — the naming difference is by design to match the high-level factory vocabulary.
</Note>

## Custom asset provider

By default, all prover factories use `getCdnZkAssetProvider()` to fetch proving keys from Umbra's CDN — no configuration required. For advanced use cases, customise the asset provider.

### Custom CDN URL

To load assets from a different CDN or self-hosted location, use `getCdnZkAssetProvider` with a custom `baseUrl`:

```typescript theme={null}
import { getCdnZkAssetProvider } from "@umbra-privacy/sdk/zk-prover/cdn";
import { getUserRegistrationProver } from "@umbra-privacy/sdk/zk-prover";

const myProvider = getCdnZkAssetProvider({
  baseUrl: "https://my-cdn.example.com/zk-assets",
});

const prover = getUserRegistrationProver(myProvider);
```

### Fully custom provider

Implement the `IZkAssetProvider` interface directly:

```typescript theme={null}
import type {
  IZkAssetProvider,
  ZkAssetUrls,
  ZKeyType,
  ClaimVariant,
} from "@umbra-privacy/sdk/zk-prover";
import { getUserRegistrationProver } from "@umbra-privacy/sdk/zk-prover";

function createMyAssetProvider(storageBaseUrl: string): IZkAssetProvider {
  return {
    async getAssetUrls(type: ZKeyType, variant?: ClaimVariant): Promise<ZkAssetUrls> {
      const name = variant ? `${type}-${variant}` : type;
      return {
        zkeyUrl: `${storageBaseUrl}/${name}.zkey`,
        wasmUrl: `${storageBaseUrl}/${name}.wasm`,
      };
    },
  };
}

const myProvider = createMyAssetProvider("https://my-storage.example.com/zk-assets");
const prover = getUserRegistrationProver(myProvider);
```

## The ZK prover interface

The prover-interface and result types ship at `@umbra-privacy/sdk/zk-prover`. See [ZK Prover Interfaces](/sdk/understanding-the-sdk/zk-provers) for the full list. All provers return the same proof shape:

```typescript theme={null}
interface Groth16Proof {
  a: Groth16ProofA; // G1 point — 64 bytes (x, y uncompressed)
  b: Groth16ProofB; // G2 point — 128 bytes
  c: Groth16ProofC; // G1 point — 64 bytes
}
```

## Web Worker pattern

Proof generation is CPU-intensive — generating a Groth16 proof takes 2-8 seconds in the browser and 1-3 seconds in Node.js. In browser applications, run the prover in a Web Worker to avoid blocking the main thread:

```typescript theme={null}
// prover-worker.ts
import { expose } from "comlink";
import { getETAIntoStealthPoolNoteCreatorProver } from "@umbra-privacy/sdk/zk-prover";

const prover = getETAIntoStealthPoolNoteCreatorProver();

expose(prover);
```

```typescript theme={null}
// main.ts
import { wrap } from "comlink";
import type { IZkProverForETAIntoStealthPoolNote } from "@umbra-privacy/sdk/zk-prover";

const worker   = new Worker(new URL("./prover-worker.ts", import.meta.url));
const zkProver = wrap<IZkProverForETAIntoStealthPoolNote>(worker);

const createNote = getATAIntoReceiverBurnableStealthPoolNoteCreatorFunction(
  { client },
  { zkProver },
);
```

<Note>
  [comlink](https://github.com/GoogleChromeLabs/comlink) is a convenient library for wrapping Web Workers with a promise-based RPC interface. Any similar mechanism works.
</Note>

## Custom prover implementation

If the bundled prover does not fit your environment, you can implement the prover interface directly:

```typescript theme={null}
import type { IZkProverForETAIntoStealthPoolNote, ETAIntoStealthPoolNoteCircuitInputs } from "@umbra-privacy/sdk/zk-prover";
import type { Groth16ProofA, Groth16ProofB, Groth16ProofC } from "@umbra-privacy/sdk/types";

function createRemoteZkProver(endpoint: string): IZkProverForETAIntoStealthPoolNote {
  return {
    prove: async (inputs: ETAIntoStealthPoolNoteCircuitInputs): Promise<{
      a: Groth16ProofA;
      b: Groth16ProofB;
      c: Groth16ProofC;
    }> => {
      const response = await fetch(`${endpoint}/prove`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(inputs),
      });
      if (!response.ok) throw new Error(`Proving server returned ${response.status}`);
      return response.json();
    },
  };
}
```

<Warning>
  A remote prover receives the circuit inputs, which include private data such as the note amount and recipient. Only use a remote prover if you fully trust the operator of the proving service.
</Warning>

## Proof generation timing

Proof generation time varies by device and prover implementation:

* WebAssembly in browser: 2–8 seconds.
* Native binary (Node.js): 1–3 seconds.
* Remote proving service: depends on network latency and server capacity.

Show a loading indicator while proof generation is in progress.

## Circuit details

The Umbra circuits are written in [Circom](https://docs.circom.io/). The self-burnable Stealth Pool Note circuit proves:

* Knowledge of the secret inputs `(amount, recipient, nonce, blinding_factor)` whose Poseidon hash equals a commitment in the Merkle tree.
* A valid Merkle inclusion proof from that commitment to the current tree root.
* The nullifier is correctly computed as `Poseidon(poseidon_private_key, commitment)`.
* The destination address matches the one embedded in the commitment.

All four facts are proven simultaneously in a single Groth16 proof, which is then verified on-chain in a single transaction.
