> For the complete documentation index, see [llms.txt](https://docs.zama.org/protocol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/protocol/sdk/concepts/security-model.md).

# Security model

This page describes what the SDK protects, what it exposes, and the trust assumptions underlying its design. Understanding these boundaries helps you make informed decisions about deploying confidential tokens.

## What is encrypted

Confidential tokens encrypt **balances** and **confidential transfer amounts**. When a user transfers 500 tokens privately, the plaintext amount is FHE-encrypted client-side before the transaction reaches the blockchain, and the on-chain contract only ever sees the ciphertext.

Shielding and unshielding are the public boundary: they convert tokens between a public ERC-20 and its confidential form, so the **shield/unshield amount is visible on-chain** — it is an ordinary public ERC-20 movement. Privacy begins once tokens are in confidential form: the resulting balance is encrypted, and later confidential transfers hide their amounts.

The on-chain contract stores FHE ciphertexts instead of `uint256` values. Only the balance owner (via their FHE private key and the relayer KMS) can decrypt their own balance.

## What is visible

FHE protects values, not metadata. The following remain publicly observable on-chain:

* **Transaction existence** — that a transaction occurred is visible in the block.
* **Participant addresses** — sender and receiver addresses are part of the transaction.
* **Token contract address** — which confidential token is involved.
* **Transaction type** — whether the call is a shield, transfer, unshield, or approval.
* **Shield and unshield amounts** — converting between public ERC-20 and confidential form is a public ERC-20 transfer, so the converted amount is visible. Only confidential transfers hide their amounts.
* **Gas costs** — standard Ethereum gas accounting.
* **Timing** — when transactions occur.

An observer can see that address A sent a confidential transfer to address B on token contract C. They cannot see how much was sent.

{% hint style="info" %}
This is a value-privacy model, not a full-privacy model. It protects amounts while preserving the public verifiability that makes Ethereum useful. For transaction-graph privacy, additional measures (like mixing services or stealth addresses) would be needed on top of FHE.
{% endhint %}

## Trust assumptions

### The relayer and KMS

The relayer provides the FHE infrastructure: encryption, decryption coordination, and transport key pair generation. The Key Management Service (KMS) holds the network's FHE master key and performs re-encryption.

The critical trust property: **the KMS re-encrypts ciphertexts without learning plaintext values.** When a user requests their balance, the KMS transforms the on-chain ciphertext from the network key to the user's public key. The KMS sees ciphertexts in and ciphertexts out — never plaintext.

This is a cryptographic property of the re-encryption scheme, not a policy promise. The KMS cannot extract plaintext from the ciphertexts it processes, assuming the underlying TFHE scheme is secure.

{% hint style="warning" %}
The KMS must be available for decryption to work. If the relayer is down, users cannot read their balances or finalize unshield operations. The on-chain encrypted data remains safe — it is inaccessible without the FHE infrastructure, but also unreadable until the relayer returns.
{% endhint %}

### The blockchain

The on-chain FHE coprocessor (FHEVM) executes homomorphic operations. It must correctly perform encrypted arithmetic for transfers and balance updates. This is part of the blockchain's consensus — nodes verify FHE operations as part of block validation.

### The user's wallet

The wallet signs EIP-712 typed data to authorize FHE operations. The SDK trusts that the wallet correctly implements `eth_signTypedData_v4` and that the signing key is under the user's control. A compromised wallet compromises the FHE session — the attacker could sign authorization requests and decrypt the user's balances.

## Credential storage

### Transport key pair storage

The transport private key is stored in plaintext in the configured storage backend (typically IndexedDB in browsers). There is no encryption-at-rest layer.

| Parameter  | Value                                                            |
| ---------- | ---------------------------------------------------------------- |
| Storage    | IndexedDB (browser), memory (tests), AsyncLocalStorage (Node.js) |
| Key format | Plaintext ML-KEM key pair                                        |
| Scope      | One transport key pair per signer address (chain-independent)    |

The security model relies on same-origin isolation: only JavaScript running on the same origin can read IndexedDB. See [Permit Model](/protocol/sdk/concepts/permit-model.md) for the full lifecycle.

### Limitations

<details>

<summary>What same-origin isolation does NOT protect against</summary>

* **Same-origin scripts** — any JavaScript running on the same origin can read IndexedDB. A cross-site scripting (XSS) vulnerability could access the transport private key directly. Reducing XSS surface is essential.
* **Physical device access** — someone with access to the device's file system can read the IndexedDB contents.
* **Malicious browser extensions** — extensions with broad permissions can access IndexedDB. Users should audit their installed extensions.

</details>

## WASM bundle integrity

The `web()` relayer transport loads the TFHE WASM bundle from Zama's CDN (`cdn.zama.org`). Before execution, the SDK computes a SHA-384 digest of the fetched payload and compares it to a hash pinned in the library's source code. If the hashes do not match, initialization fails with a clear error.

![WASM Bundle Integrity Check](/files/4wUsChvDSmjt6Nkr5970)

This protects against CDN compromise or man-in-the-middle injection of modified WASM.

Integrity checking is enabled by default. Disable it only in test environments:

```ts
const config = createConfig({
  chains: [sepolia],
  publicClient,
  walletClient,
  relayers: { [sepolia.id]: web({ security: { integrityCheck: false } }) },
});
```

{% hint style="warning" %}
Disabling integrity checks in production removes a critical defense layer. A compromised WASM bundle could exfiltrate transport private keys or manipulate encrypted values.
{% endhint %}

## Browser security headers

### COOP/COEP headers

Multi-threaded FHE requires `SharedArrayBuffer`, which browsers restrict to cross-origin isolated contexts. Your server must send these headers:

```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```

Without these headers, `SharedArrayBuffer` is unavailable. The SDK falls back to single-threaded WASM execution, which is slower but functional.

{% hint style="info" %}
Single-threaded mode works without COOP/COEP headers. Only enable cross-origin isolation if you need the performance benefit of multi-threaded FHE.
{% endhint %}

### Content Security Policy (CSP)

The Web Worker loads and executes WASM from a CDN. Your CSP must allow:

| Directive     | Value                  | Reason                                        |
| ------------- | ---------------------- | --------------------------------------------- |
| `worker-src`  | `blob:`                | Workers are created from blob URLs            |
| `script-src`  | `'wasm-unsafe-eval'`   | Required for WASM execution inside the worker |
| `connect-src` | `https://cdn.zama.org` | CDN fetch for the WASM bundle                 |

Example CSP header:

```
Content-Security-Policy: worker-src blob:; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self' https://cdn.zama.org https://your-relayer-proxy.com;
```

<details>

<summary>Why wasm-unsafe-eval?</summary>

The `wasm-unsafe-eval` directive allows WASM compilation and execution without requiring `unsafe-eval`. It is narrower than `unsafe-eval` — it permits only WebAssembly instantiation, not arbitrary JavaScript `eval()`. All major browsers support it as of 2024.

</details>

## Permit security

### Time-bounded signatures

EIP-712 permit signatures include a start timestamp and duration (in days). The relayer rejects permits outside their validity window. This limits the damage from a leaked permit — it becomes useless after expiry.

Two TTL controls are available:

* `transportKeyPairTTL` — how long the transport key pair remains valid (default: 30 days).
* `permitTTL` — how long signed permits remain valid, in days (default: 30).

### Address-scoped authorization

The EIP-712 typed data includes the wallet address. A permit signed by address A cannot authorize decryption for address B. Combined with contract-scoped authorization (the signed message lists specific contract addresses), each permit is tightly bound to a specific user and set of contracts.

### Revocation

Permits can be revoked programmatically via `sdk.permits.revokePermits()` or automatically via wallet lifecycle events (disconnect, account switch). Revocation removes permits from storage immediately.

After revoking permits, the transport key pair remains in storage. Use `sdk.permits.clear()` to also wipe the key pair.

## CSRF protection

For browser apps, the `web()` transport supports CSRF tokens injected into all mutating HTTP requests to the relayer proxy:

```ts
const config = createConfig({
  chains: [sepolia],
  publicClient,
  walletClient,
  relayers: {
    [sepolia.id]: web({
      security: { getCsrfToken: () => document.cookie.match(/csrf=(\w+)/)?.[1] ?? "" },
    }),
  },
});
```

The token is refreshed before each encrypt/decrypt call. Only POST, PUT, DELETE, and PATCH requests to the relayer URL include the CSRF header. GET requests and non-relayer URLs pass through without modification.

## Summary of cryptographic algorithms

| Operation        | Algorithm       | Key size    | Source                        |
| ---------------- | --------------- | ----------- | ----------------------------- |
| CDN integrity    | SHA-384         | --          | Web Crypto API                |
| FHE encryption   | TFHE            | Network key | WASM (`@zama-fhe/sdk (WASM)`) |
| ZK proofs        | WASM prover     | --          | WASM (`@zama-fhe/sdk (WASM)`) |
| Wallet signing   | ECDSA secp256k1 | 256-bit     | User wallet                   |
| Request tracking | UUID v4         | 128-bit     | `crypto.randomUUID()`         |

## Reporting vulnerabilities

If you discover a security vulnerability in the SDK, report it to **<security@zama.ai>**. Do not open a public GitHub issue for security reports. See the [Security Policy](https://github.com/zama-ai/sdk/blob/main/SECURITY.md) for full details.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zama.org/protocol/sdk/concepts/security-model.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
