# Cifer Sdk

> CIFER SDK - Quantum-Resistant Blockchain Encryption

- Skill: `cifer-security/cifer-sdk` (Agent Skill, multi-file: 16 files)
- Install (CLI): `npx skillmds@latest add cifer-security/cifer-sdk`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cifer-security/cifer-sdk/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: cifer-security (https://skillmd.com/u/cifer-security)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cifer-security/cifer-sdk

---

# CIFER SDK - Quantum-Resistant Blockchain Encryption

> **Skill for AI Agents** | Enable quantum-resistant encryption in blockchain applications using the CIFER SDK (v0.5.3).

## Overview

CIFER (Cryptographic Infrastructure for Encrypted Records) SDK provides quantum-resistant encryption for blockchain applications. This skill enables AI agents to implement secure data encryption, secret management, and on-chain commitments using post-quantum cryptography.

### Key Capabilities

- **Quantum-Resistant Encryption**: ML-KEM-768 (NIST standardized) key encapsulation
- **Multi-Chain Support**: Automatic chain discovery and configuration
- **Wallet Agnostic**: Works with any EIP-1193 provider — zero wallet dependencies, bring your own wallet
- **Web2 Mode**: Email + password registration with session-based auth (no wallet needed)
- **File Encryption**: Async job system for large file encryption/decryption
- **On-Chain Commitments**: Store encrypted data references on-chain with log-based retrieval
- **Transaction Intents**: Non-custodial pattern - you control transaction execution
- **High-Level Flows**: Orchestrated operations for common patterns

### Architecture

- **SecretsController** (on-chain): Manages secret ownership and delegation
- **Blackbox API** (off-chain): Handles encryption/decryption operations
- **Enclave Cluster**: Stores private key shards using threshold cryptography
- **IPFS**: Stores public keys for encryption

## When to Use This Skill

Use the CIFER SDK when you need to:

- Encrypt sensitive data with quantum-resistant algorithms
- Store encrypted records on blockchain
- Manage encryption keys with owner/delegate authorization
- Encrypt files larger than 16KB using the job system
- Build applications requiring post-quantum security
- Build walletless apps with email-based authentication (Web2 mode)

## Installation

```bash
npm install cifer-sdk
# or
yarn add cifer-sdk
# or
pnpm add cifer-sdk
```

**Requirements**: Node.js 18.0+, TypeScript 5.0+ (recommended)

**Runtime Dependencies**:
- `@noble/secp256k1` (for PrivateKeySignerAdapter / Web2 sessions)
- `@noble/hashes` (for keccak256 address derivation)

**ESM Import**:
```typescript
import { createCiferSdk, keyManagement, blackbox, commitments, flows, web2 } from 'cifer-sdk';
```

**CommonJS Import**:
```typescript
const { createCiferSdk, keyManagement, blackbox, web2 } = require('cifer-sdk');
```

**Sub-path Imports**:
```typescript
import * as web2 from 'cifer-sdk/web2';
import { encryptPayload } from 'cifer-sdk/blackbox';
import { Eip1193SignerAdapter } from 'cifer-sdk/adapters';
```

## Quick Start

```typescript
import { createCiferSdk, Eip1193SignerAdapter, blackbox } from 'cifer-sdk';

// 1. Initialize SDK with auto-discovery
const sdk = await createCiferSdk({
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

// 2. Connect wallet (browser)
const signer = new Eip1193SignerAdapter(window.ethereum);

// 3. Encrypt data
const encrypted = await blackbox.payload.encryptPayload({
  chainId: 8453,
  secretId: 123n,
  plaintext: 'My secret message',
  signer,
  readClient: sdk.readClient,
  blackboxUrl: sdk.blackboxUrl,
});

// 4. Decrypt data
const decrypted = await blackbox.payload.decryptPayload({
  chainId: 8453,
  secretId: 123n,
  encryptedMessage: encrypted.encryptedMessage,
  cifer: encrypted.cifer,
  signer,
  readClient: sdk.readClient,
  blackboxUrl: sdk.blackboxUrl,
});

console.log(decrypted.decryptedMessage); // 'My secret message'
```

### Quick Start (Web2 - No Wallet)

```typescript
import { createCiferSdk, web2 } from 'cifer-sdk';
import * as ed from '@noble/ed25519';

// 1. Initialize SDK
const sdk = await createCiferSdk({
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

// 2. Ed25519 key setup
const privateKey = ed.utils.randomPrivateKey();
const publicKey = await ed.getPublicKeyAsync(privateKey);
const ed25519Signer = {
  async sign(message: Uint8Array) { return ed.signAsync(message, privateKey); },
  getPublicKey() { return publicKey; },
};

// 3. Register (one-time)
const reg = await web2.auth.register({
  email: 'user@example.com',
  password: 'securePassword123',
  blackboxUrl: sdk.blackboxUrl,
});
await web2.auth.verifyEmail({ email: 'user@example.com', otp: '123456', blackboxUrl: sdk.blackboxUrl });
await web2.auth.registerKey({ principalId: reg.principalId, password: 'securePassword123', ed25519Signer, blackboxUrl: sdk.blackboxUrl });

// 4. Create session
const session = await web2.session.createManagedSession({
  principalId: reg.principalId,
  ed25519Signer,
  blackboxUrl: sdk.blackboxUrl,
});

// 5. Create secret & encrypt
const secret = await web2.secret.createSecret({ session, blackboxUrl: sdk.blackboxUrl });

const encrypted = await web2.blackbox.payload.encryptPayload({
  session,
  secretId: secret.secretId,
  plaintext: 'Hello from Web2!',
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});

// 6. Decrypt
const decrypted = await web2.blackbox.payload.decryptPayload({
  session,
  secretId: secret.secretId,
  encryptedMessage: encrypted.encryptedMessage,
  cifer: encrypted.cifer,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});

console.log(decrypted.decryptedMessage); // 'Hello from Web2!'
```

---

## Core Concepts

### Secrets

A **secret** is the core primitive in CIFER. Each secret represents an ML-KEM-768 key pair where:
- **Public key**: Stored on IPFS, used for encryption
- **Private key**: Split across enclave cluster using threshold cryptography

| Property | Description |
|----------|-------------|
| `owner` | Address that can transfer, set delegate, and decrypt |
| `delegate` | Address that can decrypt only (zero address if none) |
| `isSyncing` | `true` while key generation is in progress |
| `clusterId` | Which enclave cluster holds the private key shards |
| `secretType` | `1` = ML-KEM-768 (standard) |
| `publicKeyCid` | IPFS CID of public key (empty if syncing) |

**Lifecycle**:
1. **Creation**: User calls `createSecret()` on SecretsController (pays fee)
2. **Syncing**: Enclave cluster generates keys and stores shards (~30-60 seconds)
3. **Ready**: `isSyncing` becomes false, `publicKeyCid` is set
4. **Usage**: Owner/delegate can encrypt and decrypt

### Authorization Model

Encryption requires only a valid signature — **any wallet** can encrypt for any secret. Decryption and management operations are restricted by role:

| Role | Encrypt | Decrypt | Transfer | Set Delegate |
|------|---------|---------|----------|--------------|
| **Owner** | Yes | Yes | Yes | Yes |
| **Delegate** | Yes | Yes | No | No |
| **Any wallet** | Yes | No | No | No |

Setting a delegate:

```typescript
const txIntent = keyManagement.buildSetDelegateTx({
  chainId,
  controllerAddress,
  secretId: 123n,
  newDelegate: '0xDelegateAddress...',
});
```

Removing a delegate:

```typescript
const txIntent = keyManagement.buildRemoveDelegationTx({
  chainId,
  controllerAddress,
  secretId: 123n,
});
```

### Encryption Model

CIFER uses hybrid encryption:
1. **ML-KEM-768**: Post-quantum key encapsulation (1088-byte ciphertext)
2. **AES-256-GCM**: Symmetric encryption for actual data

Output format:

| Field | Size | Description |
|-------|------|-------------|
| `cifer` | 1104 bytes | ML-KEM ciphertext (1088) + tag (16) |
| `encryptedMessage` | Variable | AES-GCM encrypted data (max 16KB) |

### Block Freshness

All blackbox API calls require a recent block number in the signed payload to prevent replay attacks.

- Freshness window: ~100 blocks (~10 minutes)
- SDK automatically retries with fresh block (up to 3 times)
- If you see "block too old" errors, check RPC reliability

### Transaction Intents

The SDK returns transaction intents instead of executing transactions:

```typescript
interface TxIntent {
  chainId: number;
  to: Address;
  data: Hex;
  value?: bigint;
}
```

Execute with any wallet library:

```typescript
// ethers v6
await signer.sendTransaction({ to: txIntent.to, data: txIntent.data, value: txIntent.value });

// wagmi
await sendTransaction({ to: txIntent.to, data: txIntent.data, value: txIntent.value });

// viem
await walletClient.sendTransaction({ to: txIntent.to, data: txIntent.data, value: txIntent.value });
```

### Web2 Mode

CIFER supports Web2 mode for apps that don't use blockchain wallets. Users register with email + password and authenticate via Ed25519-signed sessions.

**`WEB2_CHAIN_ID = -1`**: Sentinel value used for all Web2 operations. When `chainId` is `-1`, the SDK uses `Date.now()` instead of an RPC block number for freshness.

| Feature | Web3 | Web2 |
|---------|------|------|
| Auth | EIP-1193 wallet | Email + password + Ed25519 key |
| Chain ID | Real chain ID (e.g. 8453) | `WEB2_CHAIN_ID = -1` (sentinel) |
| Block freshness | RPC `eth_blockNumber` | `Date.now()` (no RPC needed) |
| Signer | Wallet `personal_sign` | Session EOA `personal_sign` |
| Secret creation | On-chain transaction | `POST /web2/secret` API call |

**How sessions work**:
1. An ephemeral secp256k1 keypair is generated (session key)
2. The session is authenticated with an Ed25519 signature
3. The session key signs blackbox requests (same EIP-191 format as wallets)
4. Sessions expire and can be auto-renewed (managed sessions)

**Ed25519 Signer Interface** (bring-your-own-library):

```typescript
interface Ed25519Signer {
  sign(message: Uint8Array): Promise<Uint8Array>;
  getPublicKey(): Uint8Array;
}
```

Use `@noble/ed25519` or any Ed25519 library:

```typescript
import * as ed from '@noble/ed25519';

const privateKey = ed.utils.randomPrivateKey();
const publicKey = await ed.getPublicKeyAsync(privateKey);

const ed25519Signer: Ed25519Signer = {
  async sign(message) { return ed.signAsync(message, privateKey); },
  getPublicKey() { return publicKey; },
};
```

---

## API Reference

### SDK Initialization

#### With Discovery (Recommended)

```typescript
const sdk = await createCiferSdk({
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

sdk.getSupportedChainIds(); // [8453, 11155111, ...]
sdk.getControllerAddress(8453); // '0x...'
sdk.getRpcUrl(8453); // 'https://...'
```

#### Device Clock Integrity Check

The discovery result exposes an optional `serverTime` (Unix epoch ms) — the blackbox server's own clock at the moment it handled the `/healthz` request. Compare it against `Date.now()` to detect a misconfigured or manipulated device clock before trusting any device-side time-based logic (e.g. a time-lock countdown). It is optional and absent on older blackbox deployments.

```typescript
import { discover } from 'cifer-sdk';

const discovery = await discover({
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

if (discovery.serverTime !== undefined) {
  const skewMs = Math.abs(Date.now() - discovery.serverTime);
  if (skewMs > 10 * 60 * 1000) {
    // device clock is more than 10 minutes off the blackbox — block time-sensitive UI
  }
}
```

#### With Overrides

```typescript
const sdk = await createCiferSdk({
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
  chainOverrides: {
    8453: {
      rpcUrl: 'https://my-private-rpc.example.com',
      secretsControllerAddress: '0x...',
    },
  },
});
```

#### Synchronous (No Discovery)

```typescript
import { createCiferSdkSync, RpcReadClient } from 'cifer-sdk';

const readClient = new RpcReadClient({
  rpcUrlByChainId: {
    8453: 'https://mainnet.base.org',
  },
});

const sdk = createCiferSdkSync({
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
  readClient,
  chainOverrides: {
    8453: {
      rpcUrl: 'https://mainnet.base.org',
      secretsControllerAddress: '0x...',
    },
  },
});
```

---

### Wallet Integration

All wallets must implement the `SignerAdapter` interface:

```typescript
interface SignerAdapter {
  getAddress(): Promise<string>;
  signMessage(message: string): Promise<string>; // EIP-191 personal_sign
  sendTransaction?(txRequest: TxIntent): Promise<TxExecutionResult>; // optional
}
```

#### MetaMask

```typescript
import { Eip1193SignerAdapter } from 'cifer-sdk';

if (typeof window.ethereum === 'undefined') {
  throw new Error('MetaMask is not installed');
}

await window.ethereum.request({ method: 'eth_requestAccounts' });
const signer = new Eip1193SignerAdapter(window.ethereum);
const address = await signer.getAddress();

// Handle account changes
window.ethereum.on('accountsChanged', (accounts) => {
  signer.clearCache();
  console.log('Switched to:', accounts[0]);
});
```

#### WalletConnect v2

```typescript
import { EthereumProvider } from '@walletconnect/ethereum-provider';

const provider = await EthereumProvider.init({
  projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
  chains: [8453],
  showQrModal: true,
  metadata: {
    name: 'My CIFER App',
    description: 'Quantum-resistant encryption',
    url: 'https://myapp.com',
    icons: ['https://myapp.com/icon.png'],
  },
});

await provider.connect();
const signer = new Eip1193SignerAdapter(provider);
```

#### Thirdweb

```typescript
import { createThirdwebClient, defineChain } from 'thirdweb';
import { createWallet, injectedProvider } from 'thirdweb/wallets';

const thirdwebClient = createThirdwebClient({
  clientId: 'YOUR_THIRDWEB_CLIENT_ID',
});

const base = defineChain({
  id: 8453,
  name: 'Base',
  nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
  rpcUrls: { default: { http: ['https://mainnet.base.org'] } },
});

const wallet = createWallet('io.metamask');
await wallet.connect({ client: thirdwebClient, chain: base });

const provider = injectedProvider('io.metamask');
const signer = new Eip1193SignerAdapter(provider);

// For in-app wallets (email/social login):
import { inAppWallet } from 'thirdweb/wallets';

const wallet = inAppWallet();
const account = await wallet.connect({
  client: thirdwebClient,
  chain: base,
  strategy: 'email',
  email: 'user@example.com',
});

const signer = {
  async getAddress() { return account.address; },
  async signMessage(message) { return account.signMessage({ message }); },
};
```

#### Private Key (Server-Side)

> **WARNING**: Never expose private keys in frontend code!

Using ethers.js:

```typescript
import { Wallet } from 'ethers';
import type { SignerAdapter } from 'cifer-sdk';

const wallet = new Wallet(process.env.PRIVATE_KEY);

const signer: SignerAdapter = {
  async getAddress() { return wallet.address; },
  async signMessage(message) { return wallet.signMessage(message); },
};
```

Using viem:

```typescript
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount(process.env.PRIVATE_KEY);

const signer = {
  async getAddress() { return account.address; },
  async signMessage(message) { return account.signMessage({ message }); },
};
```

#### wagmi (React)

```typescript
import { useAccount, useConnectorClient } from 'wagmi';

function useCiferSigner() {
  const { address, isConnected } = useAccount();
  const { data: connectorClient } = useConnectorClient();

  const getSigner = async () => {
    if (!isConnected || !connectorClient) {
      throw new Error('Wallet not connected');
    }
    const provider = await connectorClient.transport;
    return new Eip1193SignerAdapter(provider);
  };

  return { getSigner, address, isConnected };
}
```

#### Coinbase Wallet

```typescript
import { CoinbaseWalletSDK } from '@coinbase/wallet-sdk';

const coinbaseWallet = new CoinbaseWalletSDK({
  appName: 'My CIFER App',
  appLogoUrl: 'https://myapp.com/logo.png',
});

const provider = coinbaseWallet.makeWeb3Provider({ options: 'all' });
await provider.request({ method: 'eth_requestAccounts' });

const signer = new Eip1193SignerAdapter(provider);
```

#### Supporting Multiple Wallets

```typescript
type WalletType = 'metamask' | 'walletconnect' | 'coinbase';

async function createSigner(type: WalletType): Promise<SignerAdapter> {
  switch (type) {
    case 'metamask':
      return new Eip1193SignerAdapter(window.ethereum);
    case 'walletconnect':
      const wcProvider = await EthereumProvider.init({ /* config */ });
      await wcProvider.connect();
      return new Eip1193SignerAdapter(wcProvider);
    case 'coinbase':
      const cbProvider = coinbaseWallet.makeWeb3Provider();
      await cbProvider.request({ method: 'eth_requestAccounts' });
      return new Eip1193SignerAdapter(cbProvider);
  }
}
```

---

### keyManagement Namespace

Interact with the SecretsController contract for secret management.

#### Read Operations

```typescript
// Get secret creation fee
const fee = await keyManagement.getSecretCreationFee({
  chainId: 8453,
  controllerAddress: sdk.getControllerAddress(8453),
  readClient: sdk.readClient,
});

// Get secret state
const state = await keyManagement.getSecret(params, 123n);
// Returns: { owner, delegate, isSyncing, clusterId, secretType, publicKeyCid }

// Check if secret is ready
const ready = await keyManagement.isSecretReady(params, 123n);

// Check authorization
const canDecrypt = await keyManagement.isAuthorized(params, 123n, '0x...');

// Get secrets by wallet
const secrets = await keyManagement.getSecretsByWallet(params, '0xUser...');
// Returns: { owned: bigint[], delegated: bigint[] }

// Get counts of secrets (more gas-efficient than getSecretsByWallet)
const counts = await keyManagement.getSecretsCountByWallet(params, '0xUser...');
// Returns: { ownedCount: bigint, delegatedCount: bigint }
```

#### Transaction Builders

```typescript
// Create a new secret
const fee = await keyManagement.getSecretCreationFee(params);
const txIntent = keyManagement.buildCreateSecretTx({
  chainId: 8453,
  controllerAddress: sdk.getControllerAddress(8453),
  fee,
});

// Set delegate
const txIntent = keyManagement.buildSetDelegateTx({
  chainId: 8453,
  controllerAddress: sdk.getControllerAddress(8453),
  secretId: 123n,
  newDelegate: '0xDelegate...',
});

// Remove delegate
const txIntent = keyManagement.buildRemoveDelegationTx({ ... });

// Transfer ownership (irreversible!)
const txIntent = keyManagement.buildTransferSecretTx({
  chainId: 8453,
  controllerAddress: sdk.getControllerAddress(8453),
  secretId: 123n,
  newOwner: '0xNewOwner...',
});
```

#### Event Parsing

```typescript
const receipt = await provider.waitForTransaction(hash);
const secretId = keyManagement.extractSecretIdFromReceipt(receipt.logs);

// Parse individual event logs
const created = keyManagement.parseSecretCreatedLog(log);
const synced = keyManagement.parseSecretSyncedLog(log);
const delegateUpdated = keyManagement.parseDelegateUpdatedLog(log);
```

---

### blackbox.payload Namespace

Encrypt and decrypt short messages (< 16KB).

#### Encrypt

```typescript
const encrypted = await blackbox.payload.encryptPayload({
  chainId: 8453,
  secretId: 123n,
  plaintext: 'My secret message',
  signer,
  readClient: sdk.readClient,
  blackboxUrl: sdk.blackboxUrl,
  outputFormat: 'hex', // or 'base64'
});

// Returns: { cifer: string, encryptedMessage: string }
```

#### Decrypt

```typescript
const decrypted = await blackbox.payload.decryptPayload({
  chainId: 8453,
  secretId: 123n,
  encryptedMessage: encrypted.encryptedMessage,
  cifer: encrypted.cifer,
  signer, // Must be owner or delegate
  readClient: sdk.readClient,
  blackboxUrl: sdk.blackboxUrl,
  inputFormat: 'hex',
});

// Returns: { decryptedMessage: string }
```

---

### blackbox.files Namespace

Encrypt and decrypt large files using async jobs.

```typescript
// Start encryption job
const job = await blackbox.files.encryptFile({
  chainId: 8453,
  secretId: 123n,
  file: myFile,
  signer,
  readClient: sdk.readClient,
  blackboxUrl: sdk.blackboxUrl,
});
// Returns: { jobId: string, message: string }

// Start decryption job
const job = await blackbox.files.decryptFile({ ... });

// Decrypt from existing encrypt job
const job = await blackbox.files.decryptExistingFile({
  chainId: 8453,
  secretId: 123n,
  encryptJobId: previousJobId,
  signer,
  readClient: sdk.readClient,
  blackboxUrl: sdk.blackboxUrl,
});
```

---

### blackbox.jobs Namespace

Manage async file jobs.

```typescript
// Get job status
const status = await blackbox.jobs.getStatus(jobId, sdk.blackboxUrl);
// Returns: { id, type, status, progress, secretId, chainId, ... }

// Poll until complete
const finalStatus = await blackbox.jobs.pollUntilComplete(
  jobId,
  sdk.blackboxUrl,
  {
    intervalMs: 2000,
    maxAttempts: 120,
    onProgress: (job) => console.log(`Progress: ${job.progress}%`),
  }
);

// Download result (encrypt jobs: no auth, decrypt jobs: auth required)
const blob = await blackbox.jobs.download(jobId, {
  blackboxUrl: sdk.blackboxUrl,
  // For decrypt jobs, also provide:
  chainId: 8453,
  secretId: 123n,
  signer,
  readClient: sdk.readClient,
});

// List jobs for wallet
const result = await blackbox.jobs.list({
  chainId: 8453,
  signer,
  readClient: sdk.readClient,
  blackboxUrl: sdk.blackboxUrl,
});

// Get data consumption stats
const stats = await blackbox.jobs.dataConsumption({ ... });

// Delete a job (mark for cleanup)
await blackbox.jobs.deleteJob(jobId, {
  blackboxUrl: sdk.blackboxUrl,
  chainId: 8453,
  secretId: 123n,
  signer,
  readClient: sdk.readClient,
});
```

---

### commitments Namespace

Store and retrieve encrypted data on-chain.

The commitment pattern:
1. Only hashes stored in contract storage (gas efficient)
2. Full encrypted bytes emitted in events
3. Retrieve from logs using block number from metadata

```typescript
// Check if commitment exists
const exists = await commitments.ciferDataExists(params, dataId);

// Get metadata
const metadata = await commitments.getCIFERMetadata(params, dataId);
// Returns: { secretId, storedAtBlock, ciferHash, encryptedMessageHash }

// Fetch encrypted data from logs
const data = await commitments.fetchCommitmentFromLogs({
  chainId: 8453,
  contractAddress: '0x...',
  dataId: dataKey,
  storedAtBlock: metadata.storedAtBlock,
  readClient: sdk.readClient,
});
// Returns: { cifer, encryptedMessage, ciferHash, encryptedMessageHash }

// Verify integrity
const result = commitments.verifyCommitmentIntegrity(data, metadata);

// Verify and throw on failure
commitments.assertCommitmentIntegrity(data, metadata);

// Validate before storing (throws on invalid sizes)
commitments.validateForStorage(cifer, encryptedMessage);

// Build store transaction
const txIntent = commitments.buildStoreCommitmentTx({
  chainId: 8453,
  contractAddress: '0xYourContract...',
  storeFunction: {
    type: 'function',
    name: 'store',
    inputs: [
      { name: 'key', type: 'bytes32' },
      { name: 'encryptedMessage', type: 'bytes' },
      { name: 'cifer', type: 'bytes' },
    ],
  },
  args: {
    key: dataKey,
    secretId: 123n,
    encryptedMessage: encrypted.encryptedMessage,
    cifer: encrypted.cifer,
  },
  validate: true, // optional, default: true — validate sizes before building tx
});
```

**Constants**:
- `CIFER_ENVELOPE_BYTES = 1104` (fixed cifer size)
- `MAX_PAYLOAD_BYTES = 16384` (16KB max payload)

---

### flows Namespace

High-level orchestrated operations.

Flows support two modes:
- **plan**: Returns a plan describing steps (dry run)
- **execute**: Actually performs the operations

#### Flow Context

```typescript
const ctx = {
  signer: SignerAdapter,
  readClient: ReadClient,
  blackboxUrl: string,
  chainId: number,
  controllerAddress?: Address,
  txExecutor?: (intent: TxIntent) => Promise<TxExecutionResult>,
  pollingStrategy?: { intervalMs: number, maxAttempts: number },
  logger?: (message: string) => void,
  abortSignal?: AbortSignal,
};
```

#### Create Secret and Wait

```typescript
const result = await flows.createSecretAndWaitReady({
  ...ctx,
  controllerAddress: sdk.getControllerAddress(8453),
  txExecutor: async (intent) => {
    const hash = await wallet.sendTransaction(intent);
    return { hash, waitReceipt: () => provider.waitForTransaction(hash) };
  },
});

if (result.success) {
  console.log('Secret ID:', result.data.secretId);
  console.log('Public Key CID:', result.data.state.publicKeyCid);
}
```

#### Encrypt and Prepare Commit

```typescript
const result = await flows.encryptThenPrepareCommitTx(ctx, {
  secretId: 123n,
  plaintext: 'My secret data',
  key: dataKey,
  commitmentContract: '0x...',
  storeFunction: storeAbi, // optional — uses default if omitted
});

if (result.success) {
  await wallet.sendTransaction(result.data.txIntent);
}
```

#### Retrieve and Decrypt from Logs

```typescript
const result = await flows.retrieveFromLogsThenDecrypt(ctx, {
  secretId: 123n,
  dataId: dataKey,
  commitmentContract: '0x...',
  storedAtBlock: metadata.storedAtBlock, // optional — fetched if not provided
  skipIntegrityCheck: false,             // optional
});

if (result.success) {
  console.log('Decrypted:', result.data.decryptedMessage);
}
```

#### File Flows

```typescript
// Encrypt file flow
const result = await flows.encryptFileJobFlow(ctx, {
  secretId: 123n,
  file: myFile,
});
// Returns: { jobId, job, encryptedFile: Blob }

// Decrypt file flow
const result = await flows.decryptFileJobFlow(ctx, {
  secretId: 123n,
  file: ciferFile,
});
// Returns: { jobId, job, decryptedFile: Blob }

// Decrypt from existing encrypt job without re-uploading
const result = await flows.decryptExistingFileJobFlow(ctx, {
  secretId: 123n,
  encryptJobId: previousJobId,
});
// Returns: { jobId, job, decryptedFile: Blob }
```

---

### web2.auth Namespace

Registration and authentication for Web2 mode (two-phase flow).

```typescript
import { web2 } from 'cifer-sdk';

// Phase 1: Register with email + password (sends OTP)
const reg = await web2.auth.register({
  email: 'user@example.com',
  password: 'securePassword123',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
// Returns: { principalId: string, message: string }

// Phase 2: Verify email OTP
const verified = await web2.auth.verifyEmail({
  email: 'user@example.com',
  otp: '123456',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
// Returns: { principalId: string, emailVerified: boolean }

// Phase 3: Register Ed25519 key (propagated to cluster nodes)
const keyResult = await web2.auth.registerKey({
  principalId: reg.principalId,
  password: 'securePassword123',
  ed25519Signer,
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
// Returns: { principalId: string, nodeRegistrationStatus: string }

// If nodeRegistrationStatus !== 'complete', retry:
await web2.auth.retryNodeRegistration({
  principalId: reg.principalId,
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

// Check node registration status
const status = await web2.auth.nodeRegistrationStatus({
  principalId: reg.principalId,
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
```

#### Helper Functions

```typescript
// Resend OTP (60-second cooldown)
await web2.auth.resendOtp({
  email: 'user@example.com',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

// Forgot password (sends OTP)
await web2.auth.forgotPassword({
  email: 'user@example.com',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

// Reset password with OTP
await web2.auth.resetPassword({
  email: 'user@example.com',
  otp: '123456',
  newPassword: 'newSecurePassword456',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
```

#### Account Deletion

Account deletion is a two-step, OTP-confirmed operation. These are stateless `web2.auth` functions; they are **not** methods on the client returned by `web2.createClient()`.

**Step 1 — request a deletion OTP**

`requestAccountDeletion(params): Promise<{ message: string }>` accepts `email`, `password`, `principalId`, `blackboxUrl`, and an optional `fetch` implementation.

```typescript
const deletionRequest = await web2.auth.requestAccountDeletion({
  email: 'user@example.com',
  password: 'securePassword123',
  principalId: 'your-principal-uuid',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

console.log(deletionRequest.message);
```

For anti-enumeration, the Blackbox always returns a generic success message. It sends an OTP only when the email, password, and `principalId` match a verified, active account; a nonexistent or mismatched account does not throw solely for that mismatch.

**Step 2 — confirm account deletion**

`confirmAccountDeletion(params): Promise<ConfirmAccountDeletionResult>` accepts `email`, `otp`, `blackboxUrl`, and an optional `fetch` implementation.

```typescript
const deletionResult = await web2.auth.confirmAccountDeletion({
  email: 'user@example.com',
  otp: '123456',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

console.log(deletionResult.success); // true
```

Confirmation soft-deletes the account: it becomes dormant and hidden from APIs while its records are retained for legal disclosure. Registering again with the same email reactivates the same `principalId` and restores access to its existing secrets. After successful confirmation, clear cached credentials, Ed25519 keys, session keys, and active sessions from local application state.

---

### web2.session Namespace

Create and manage Web2 sessions. Two modes available:

#### Managed Session (Recommended)

SDK manages session lifecycle with auto-renewal:

```typescript
const session = await web2.session.createManagedSession({
  principalId: 'your-uuid',
  ed25519Signer,
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
  ttl: 900, // seconds (default: 900 = 15 minutes)
});

// Session properties:
session.signer;         // SignerAdapter (ephemeral EOA)
session.sessionAddress; // EOA address
session.principalId;    // UUID
session.expiresAt;      // ISO 8601 timestamp
session.isManaged;      // true

// Auto-renew if near expiry (60s skew)
await session.ensureValid();

// Force renewal
await session.renew();
```

#### Existing Session Key (Advanced)

Wrap a pre-existing session private key (e.g. from a TEE web front):

```typescript
const session = web2.session.useExistingSessionKey({
  sessionPrivateKey: '0xabc123...', // hex-encoded secp256k1 private key
  principalId: 'your-uuid',        // optional
});

// session.isManaged === false
// session.renew() throws Web2SessionError
// session.ensureValid() is a no-op
```

---

### web2.secret Namespace

Create and list Web2 secrets.

```typescript
// Create a new secret
const result = await web2.secret.createSecret({
  session,
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
// Returns: { secretId: number }

// List all secrets for the principal
const list = await web2.secret.listSecrets({
  session,
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
// Returns: { secrets: Array<{ secretId, status }> }
```

---

### web2.delegate Namespace

Set or remove delegates on Web2 secrets.

```typescript
// Set a delegate
await web2.delegate.setDelegate({
  session,
  secretId: 42,
  delegatePrincipalId: 'delegate-principal-uuid',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

// Remove a delegate (empty string)
await web2.delegate.setDelegate({
  session,
  secretId: 42,
  delegatePrincipalId: '',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
```

---

### web2.permit Namespace

Request permits for key rotation, ownership transfer, or delegation changes.

```typescript
// Key rotation (email+password, no session needed)
const result = await web2.permit.requestPermit({
  action: 'rotate',
  email: 'user@example.com',
  password: 'securePassword123',
  payload: { newPublicKey: '...' },
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
// Returns: { permitId: string }

// Transfer ownership (session required)
const result = await web2.permit.requestPermit({
  action: 'transfer',
  session,
  secretId: 42,
  payload: { newOwnerPrincipalId: 'new-owner-uuid' },
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});

// Delegate permit (session required)
const result = await web2.permit.requestPermit({
  action: 'delegate',
  session,
  secretId: 42,
  payload: { delegatePrincipalId: 'delegate-uuid' },
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
});
```

---

### web2.principal Namespace

Look up principals by email.

```typescript
const principal = await web2.principal.getByEmail(
  'colleague@example.com',
  'https://blackbox.cifersecurity.com:3010',
);
// Returns: { principalId: string, emailHex: string }
```

---

### web2.blackbox Namespace

Session-first wrappers around the core `blackbox.*` functions. Automatically fills `chainId = -1` and uses the session signer. Calls `session.ensureValid()` before each request.

#### web2.blackbox.payload

```typescript
// Encrypt
const encrypted = await web2.blackbox.payload.encryptPayload({
  session,
  secretId: 42,
  plaintext: 'Hello, Web2!',
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
  readClient: sdk.readClient,
  outputFormat: 'hex', // optional, default: 'hex'
});
// Returns: { cifer: string, encryptedMessage: string }

// Decrypt
const decrypted = await web2.blackbox.payload.decryptPayload({
  session,
  secretId: 42,
  encryptedMessage: encrypted.encryptedMessage,
  cifer: encrypted.cifer,
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
  readClient: sdk.readClient,
  inputFormat: 'hex', // optional
});
// Returns: { decryptedMessage: string }
```

#### web2.blackbox.files

```typescript
// Encrypt file
const job = await web2.blackbox.files.encryptFile({
  session,
  secretId: 42,
  file: myFile,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});
// Returns: { jobId: string, message: string }

// Decrypt file
const job = await web2.blackbox.files.decryptFile({
  session,
  secretId: 42,
  file: encryptedFile,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});

// Decrypt from existing encrypt job
const job = await web2.blackbox.files.decryptExistingFile({
  session,
  secretId: 42,
  encryptJobId: previousJobId,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});
```

#### web2.blackbox.jobs

```typescript
// These are re-exported from core (no session needed):
const status = await web2.blackbox.jobs.getStatus(jobId, sdk.blackboxUrl);
const final = await web2.blackbox.jobs.pollUntilComplete(jobId, sdk.blackboxUrl, {
  onProgress: (job) => console.log(`${job.progress}%`),
});

// These require a session:
const blob = await web2.blackbox.jobs.download(jobId, {
  session,
  secretId: 42,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});

await web2.blackbox.jobs.deleteJob(jobId, {
  session,
  secretId: 42,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});

const jobs = await web2.blackbox.jobs.list({
  session,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
  includeExpired: false,
});

const stats = await web2.blackbox.jobs.dataConsumption({
  session,
  blackboxUrl: sdk.blackboxUrl,
  readClient: sdk.readClient,
});
```

---

### web2.createClient Factory

Stateful client that stores session, `blackboxUrl`, and `readClient` so you don't pass them on every call.

```typescript
import { web2 } from 'cifer-sdk';

const client = web2.createClient({
  blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
  readClient: sdk.readClient,
});

// Session is auto-stored after creation
await client.createManagedSession({
  principalId: 'your-uuid',
  ed25519Signer,
});

// No need to pass session or blackboxUrl!
const secret = await client.createSecret();

const encrypted = await client.payload.encryptPayload({
  secretId: secret.secretId,
  plaintext: 'Hello!',
});

const decrypted = await client.payload.decryptPayload({
  secretId: secret.secretId,
  encryptedMessage: encrypted.encryptedMessage,
  cifer: encrypted.cifer,
});

// Other client methods:
await client.listSecrets();
await client.setDelegate({ secretId: 42, delegatePrincipalId: 'uuid' });
await client.getByEmail('colleague@example.com');
client.setSession(anotherSession); // manually replace stored session
```

The `Web2Client` interface provides: `session`, `blackboxUrl`, `readClient`, `createManagedSession()`, `useExistingSessionKey()`, `setSession()`, `createSecret()`, `listSecrets()`, `setDelegate()`, `requestPermit()`, `getByEmail()`, `payload.*`, `files.*`, `jobs.*`.

---

## Error Handling

All SDK errors extend `CiferError` with typed subclasses:

```
CiferError
├── ConfigError (code: CONFIG_ERROR)
│   ├── DiscoveryError
│   └── ChainNotSupportedError
├── AuthError (code: AUTH_ERROR)
│   ├── SignatureError
│   ├── BlockStaleError
│   └── SignerMismatchError
├── BlackboxError (code: BLACKBOX_ERROR)
│   ├── EncryptionError
│   ├── DecryptionError
│   ├── JobError
│   └── SecretNotReadyError
├── KeyManagementError (code: KEY_MANAGEMENT_ERROR)
│   ├── SecretNotFoundError
│   └── NotAuthorizedError
├── CommitmentsError (code: COMMITMENTS_ERROR)
│   ├── CommitmentNotFoundError
│   ├── IntegrityError
│   ├── InvalidCiferSizeError
│   └── PayloadTooLargeError
├── Web2Error (code: WEB2_ERROR)
│   ├── Web2SessionError (session expired, cannot renew)
│   └── Web2AuthError (registration, OTP, password errors)
└── FlowError (code: FLOW_ERROR)
    ├── FlowAbortedError
    └── FlowTimeoutError
```

### Type Guards

```typescript
import {
  isCiferError,
  isBlockStaleError,
  isSecretNotReadyError,
  isWeb2Error,
  isWeb2SessionError,
  SecretNotFoundError,
  SecretNotReadyError,
  CommitmentNotFoundError,
} from 'cifer-sdk';
```

### Error Handling Example

```typescript
try {
  await blackbox.payload.encryptPayload({ ... });
} catch (error) {
  if (isBlockStaleError(error)) {
    console.log('RPC returning stale blocks');
  } else if (error instanceof SecretNotReadyError) {
    console.log('Wait for secret to sync');
  } else if (error instanceof SecretNotFoundError) {
    console.log('Secret not found:', error.secretId);
  } else if (error instanceof CommitmentNotFoundError) {
    console.log('No data for key:', error.dataId);
  } else if (isWeb2SessionError(error)) {
    console.log('Web2 session expired or cannot renew');
  } else if (isWeb2Error(error)) {
    console.log('Web2 error:', error.message);
  } else if (isCiferError(error)) {
    console.log('CIFER error:', error.code, error.message);
  } else {
    throw error;
  }
}
```

### Common Scenarios

| Error | Cause | Solution |
|-------|-------|----------|
| "Block number is too old" | RPC issues | SDK auto-retries 3x; check RPC reliability; minimize delay between signing and API call |
| "Secret is syncing" | Key generation in progress | Wait 30-60s; use `isSecretReady()` or `createSecretAndWaitReady` flow |
| "Signature verification failed" | Wrong signing method | Use EIP-191 `personal_sign` (not `eth_sign` or typed data) |
| "Not authorized" | Not owner/delegate | Check with `isAuthorized()` |
| "No active Web2 session" | Session not created or expired | Call `createManagedSession()` or pass session explicitly |
| "Web2 session expired" | Existing-key session cannot renew | Recreate session externally |
| "OTP verification failed" | Invalid or expired OTP | Use `resendOtp()` and try again |

---

## Complete Examples

### Browser: Encrypt/Decrypt Message

```typescript
import { createCiferSdk, Eip1193SignerAdapter, blackbox } from 'cifer-sdk';

async function encryptDecryptExample() {
  const sdk = await createCiferSdk({
    blackboxUrl: 'https://blackbox.cifersecurity.com:3010',
  });
  const signer = new Eip1193SignerAdapter(window.ethereum);
  
  const chainId = 8453;
  const secretId = 123n;
  
  // Encrypt
  const encrypted = await blackbox.payload.encryptPayload({
    chainId,
    secretId,
    plaintext: 'Hello, CIFER!',
    signer,
    readClient: sdk.readClient,
    blackboxUrl: sdk.blackboxUrl,
  });
  
  // Decrypt
  const decrypted = await blackbox.payload.decryptPayload({
    chainId,
    secretId,
    encryptedMessage: encrypted.encryptedMessage,
    cifer: encrypted.cifer,
    signer,
    readClient: sdk.readClient,
    blackboxUrl: sdk.blackboxUrl,
  });
  
  console.log('Decrypted:', decrypted.decryptedMe

…(truncated)
