{"version":3,"sources":["../src/client.ts","../src/witness.ts","../src/prover.ts","../src/keys.ts","../src/encryption.ts"],"sourcesContent":["// Private Pool V2 — high-level orchestration helper.\n//\n// Glues together the foundational libs into the user-facing flows:\n//   - `prepareDepositTransaction(...)` — build witness, prove, build ix,\n//     return a versioned tx ready for the wallet adapter to sign+send\n//   - `prepareWithdrawTransaction(...)` — same, but consumes a note\n//   - `prepareJoinSplitTransaction(...)` — same, 2 inputs / 2 outputs\n//   - `loadConfig(network)` — fetch + cache pool addresses from\n//     `/api/private-pool/v2/config`\n//\n// The wallet adapter takes care of signing + sending (`wallet.sendTransaction`).\n// Backend orkiestracji przyjdzie w v1.1 (sponsored fee payer); MVP is\n// fully client-driven.\n\nimport {\n  AddressLookupTableProgram,\n  ComputeBudgetProgram,\n  Connection,\n  PublicKey,\n  SystemProgram,\n  TransactionInstruction,\n  TransactionMessage,\n  VersionedTransaction,\n} from '@solana/web3.js';\n\nimport { deriveCommitment, deriveNullifier as deriveNullifierAsync, merkleParent } from './witness';\nimport { proveDeposit, proveWithdraw, proveJoinSplit, proveCashoutProof } from './prover';\nimport { parsePaymentAddress, deriveCodeKeys, type CodeKeyMaterial } from './keys';\n// ADR-008 (stealth addresses) was withdrawn 2026-05-08 — see the ADR\n// markdown postmortem. All notes are now legacy ADR-003 (master_pk\n// owner_pk). Future stealth needs a curve-based scheme + circuit\n// upgrade.\nimport {\n  fieldToBytes32BE as keysFieldToBytes32BE,\n  type PrivatePoolKeyMaterial,\n} from './keys';\nimport {\n  encryptNote,\n  tryDecryptNote,\n  type DecryptedNote,\n  PRIVATE_POOL_NOTE_BYTES,\n} from './encryption';\nimport type { OwnedNote } from './discovery';\n\n// ── Config fetcher ───────────────────────────────────────────────────────\n\nexport type PrivatePoolV2Config = {\n  network: string;\n  cluster: 'devnet' | 'mainnet';\n  programIds: {\n    pool: string;\n    depositVerifier: string;\n    withdrawVerifier: string;\n    joinsplitVerifier: string;\n  };\n  poolPda: string;\n  vaultAta: string;\n  usdcMint: string;\n  aspAuthority: string;\n  tokenProgramId: string;\n  associatedTokenProgramId: string;\n  /// Backend's sponsored-fee-payer pubkey (`null` when the backend\n  /// doesn't have a keypair configured — fall back to user-pays-fees).\n  /// When set, frontend builds v0 messages with `payerKey: feePayer`\n  /// so the wallet popup never asks the user for SOL.\n  feePayer: string | null;\n  constants: {\n    poolDepth: number;\n    aspDepth: number;\n    rootHistorySize: number;\n    aspRootHistorySize: number;\n    encryptedNoteBytes: number;\n    encryptedNoteWithMemoBytes: number;\n    usdcDecimals: number;\n  };\n};\n\nconst configCache = new Map<string, PrivatePoolV2Config>();\n\nexport async function loadConfig(args: {\n  apiBaseUrl: string;\n  network: string;\n}): Promise<PrivatePoolV2Config> {\n  const cacheKey = `${args.apiBaseUrl}|${args.network}`;\n  const cached = configCache.get(cacheKey);\n  if (cached) return cached;\n  const url = new URL('api/private-pool/v2/config', args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/');\n  url.searchParams.set('network', args.network);\n  const r = await fetch(url.toString());\n  if (!r.ok) throw new Error(`loadConfig: ${r.status} ${await r.text()}`);\n  const cfg = (await r.json()) as PrivatePoolV2Config;\n  configCache.set(cacheKey, cfg);\n  return cfg;\n}\n\n// ── PDA helpers (mirror server `pdas.js` shape so the client can derive\n//    the same addresses without round-tripping the backend) ─────────────\n\nexport function fieldToBytes32BE(value: bigint): Buffer {\n  return Buffer.from(keysFieldToBytes32BE(value));\n}\n\nfunction deriveLeafPda(programId: PublicKey, poolPda: PublicKey, commitmentBytes: Buffer): [PublicKey, number] {\n  return PublicKey.findProgramAddressSync(\n    [Buffer.from('ppv2-leaf'), poolPda.toBuffer(), commitmentBytes],\n    programId,\n  );\n}\nfunction deriveBlobPda(programId: PublicKey, poolPda: PublicKey, commitmentBytes: Buffer): [PublicKey, number] {\n  return PublicKey.findProgramAddressSync(\n    [Buffer.from('ppv2-enc'), poolPda.toBuffer(), commitmentBytes],\n    programId,\n  );\n}\nfunction deriveNullifierPda(programId: PublicKey, poolPda: PublicKey, nullifierBytes: Buffer): [PublicKey, number] {\n  return PublicKey.findProgramAddressSync(\n    [Buffer.from('ppv2-nullifier'), poolPda.toBuffer(), nullifierBytes],\n    programId,\n  );\n}\n\nfunction deriveAta(mint: PublicKey, owner: PublicKey, tokenProgram: PublicKey, atokenProgram: PublicKey): PublicKey {\n  return PublicKey.findProgramAddressSync(\n    [owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()],\n    atokenProgram,\n  )[0];\n}\n\n// ── Anchor + borsh primitives (frontend mirror) ──────────────────────────\n\nasync function anchorDiscriminator(name: string): Promise<Buffer> {\n  // Use Web Crypto SHA-256 — same output as Node `crypto.createHash`.\n  const enc = new TextEncoder().encode(`global:${name}`);\n  const digest = await crypto.subtle.digest('SHA-256', enc);\n  return Buffer.from(new Uint8Array(digest).slice(0, 8));\n}\n\nfunction writeU64LE(value: bigint | number): Buffer {\n  // The browser polyfill for `Buffer` (`buffer` npm package) doesn't\n  // always implement BigInt-flavoured methods like writeBigUInt64LE\n  // — older versions ship without them. Drop down to DataView, which\n  // is part of the ES standard and supports `setBigUint64` everywhere\n  // we run (Node + every modern browser).\n  const buf = Buffer.alloc(8);\n  const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);\n  view.setBigUint64(0, BigInt(value), true /* littleEndian */);\n  return buf;\n}\n\nfunction writeAnchorVecU8(bytes: Uint8Array): Buffer {\n  const out = Buffer.alloc(4 + bytes.length);\n  out.writeUInt32LE(bytes.length, 0);\n  Buffer.from(bytes).copy(out, 4);\n  return out;\n}\n\n// ── poseidon_addr_hash mirror — match the on-chain handler ──────────────\n\nimport { buildPoseidon } from 'circomlibjs';\n\nconst FIELD_MODULUS =\n  21888242871839275222246405745257275088548364400416034343698204186575808495617n;\n\nlet cachedPoseidon: any = null;\nasync function getPoseidon(): Promise<any> {\n  if (!cachedPoseidon) cachedPoseidon = await buildPoseidon();\n  return cachedPoseidon;\n}\n\n/// Match the Rust `poseidon_addr_hash`: reduce the SPL pubkey\n/// (interpreted as a 256-bit BE big-int) modulo the BN254 scalar\n/// field, then Poseidon-1.\n///\n/// Both sides do PROPER `bytes mod p` reduction (audit H-3). The\n/// previous `& 0x1f` mask threw away 3 bits of pre-image entropy;\n/// this version preserves full ~254-bit field uniformity.\n///\n/// On-chain side: `reduce_into_bn254_fr` in\n/// `programs/solana-private-pool-v2/src/lib.rs` — hand-rolled\n/// 8-bound subtraction loop against a hard-coded BN254 modulus\n/// constant (avoids pulling `ark_bn254` for one mod op). Both\n/// implementations are covered by 6 unit tests including a Python\n/// cross-reference for the worst-case input.\nasync function poseidonAddrHash(pubkey: PublicKey): Promise<bigint> {\n  const bytes = Buffer.from(pubkey.toBytes());\n  let acc = 0n;\n  for (const b of bytes) acc = (acc << 8n) + BigInt(b);\n  acc = ((acc % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n  const p = await getPoseidon();\n  const h = p([acc]);\n  return ((BigInt(p.F.toString(h)) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n// ── Deposit flow ─────────────────────────────────────────────────────────\n\nexport type PrepareDepositArgs = {\n  cfg: PrivatePoolV2Config;\n  /// Wallet pubkey of the depositor (user signing the tx). The proof\n  /// binds `depositor_addr_hash` to this exact pubkey; the on-chain\n  /// handler re-hashes the signer at runtime and rejects on mismatch.\n  depositor: PublicKey;\n  /// Recipient identity. For self-deposit (typical onboarding) pass the\n  /// caller's own derived `spendingPkField`. For depositing to someone\n  /// else, parse their payment address and use that `spendingPkField`.\n  recipientSpendingPkField: bigint;\n  /// 32-byte X25519 viewing pubkey of the recipient. Used to encrypt the\n  /// note for them; the on-chain blob lets them trial-decrypt later.\n  recipientViewingPk: Uint8Array;\n  /// micro-USDC amount entering the pool (full, fee will be subtracted\n  /// inside the circuit to compute the note's value).\n  publicValue: bigint;\n  /// Relayer fee in micro-USDC. Most MVP flows set this to 0.\n  fee: bigint;\n  /// Optional 32-byte memo. `null` for memo-less.\n  memo: Uint8Array | null;\n  /// Leave on-chain ciphertext or send `null` to skip blob storage and\n  /// rely on off-chain delivery (e.g. self-deposit where the depositor\n  /// already has the plaintext locally).\n  storeBlobOnChain?: boolean;\n  // (ADR-008 `useStealthAddress` flag removed — see ADR markdown\n  // postmortem. All notes are now master_pk legacy.)\n};\n\nexport type PreparedDepositTx = {\n  /// Versioned tx ready for `wallet.sendTransaction(tx, conn)`.\n  transaction: VersionedTransaction;\n  /// Computed commitment (hex + field) — useful for showing the user a\n  /// \"this is your note ID\" preview before signing.\n  commitmentHex: string;\n  commitmentField: bigint;\n  /// PDA addresses the handler is going to allocate. Frontend can poll\n  /// these post-confirmation to detect when the indexer has indexed the\n  /// fresh leaf.\n  leafPda: PublicKey;\n  blobPda: PublicKey;\n  /// Local copies of the note material — caller stashes these in\n  /// IndexedDB / localStorage so the recipient can recover the note\n  /// from local state even if the on-chain blob is skipped.\n  ownedNoteSecret: {\n    blinding: Uint8Array;\n    ownerPk: Uint8Array;\n    memo: Uint8Array | null;\n    valueMicroUsdc: bigint;\n  };\n  /// Time spent on Groth16 prove (for UX progress feedback).\n  proveMs: number;\n};\n\n/// Build a deposit tx end-to-end. Caller's responsibility to sign+send\n/// via the wallet adapter.\nexport async function prepareDepositTransaction(\n  args: PrepareDepositArgs,\n  options: { connection: Connection; recentBlockhash?: string } = {} as any,\n): Promise<PreparedDepositTx> {\n  const conn = options.connection;\n  if (!conn) throw new Error('prepareDepositTransaction: connection required');\n\n  // 1. Witness — note value = publicValue - fee.\n  const noteValue = args.publicValue - args.fee;\n  if (noteValue <= 0n) throw new Error('prepareDepositTransaction: noteValue must be positive');\n\n  // Fresh per-note blinding. Real wallets pull from a CSPRNG.\n  const blinding = crypto.getRandomValues(new Uint8Array(32));\n  // Reduce blinding to a field element (just clear top 2 bits).\n  blinding[0] = blinding[0] & 0x3f;\n  const blindingField = bytesToField(blinding);\n\n  // Legacy ADR-003 only: owner_pk = recipient's master spending_pk.\n  const ownerPkField = args.recipientSpendingPkField;\n\n  const memoField = args.memo ? bytesToField(args.memo) : 0n;\n  const commitmentField = await deriveCommitment({\n    value: noteValue,\n    ownerPk: ownerPkField,\n    blinding: blindingField,\n    memo: memoField,\n  });\n\n  const depositorAddrHashField = await poseidonAddrHash(args.depositor);\n\n  // Witness shape mirrors `Deposit.circom` signal declaration order.\n  const witness = {\n    ownerPk: ownerPkField.toString(10),\n    blinding: blindingField.toString(10),\n    memo: memoField.toString(10),\n    publicValue: args.publicValue.toString(10),\n    depositorAddrHash: depositorAddrHashField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').DepositWitness;\n\n  // 2. Prove.\n  const { proofBytes, proveMs } = await proveDeposit(witness);\n\n  // 3. Optional: encrypt the note for the recipient.\n  const commitmentBytes = fieldToBytes32BE(commitmentField);\n  let encryptedNote = new Uint8Array(0);\n  let ephemeralPk = new Uint8Array(32);\n  if (args.storeBlobOnChain !== false) {\n    // ownerPk inside the plaintext mirrors the on-chain commitment's\n    // stealth pubkey when stealth is on (so the recipient's\n    // self-recovery sanity check matches), or the master pubkey for\n    // legacy notes. Either way, recipient does the same Poseidon4\n    // commitment recompute and verifies match.\n    const note: DecryptedNote = {\n      value: noteValue,\n      blinding,\n      ownerPk: fieldToBytes32BE(ownerPkField),\n      memo: args.memo,\n      stealthNonce: null,\n      layout: 'legacy',\n    };\n    const blob = encryptNote({\n      note,\n      recipientViewingPk: args.recipientViewingPk,\n      commitment: commitmentBytes,\n    });\n    encryptedNote = blob.ciphertext;\n    ephemeralPk = blob.ephemeralPk;\n  }\n\n  // 4. Build deposit_note ix.\n  const programId = new PublicKey(args.cfg.programIds.pool);\n  const usdcMint = new PublicKey(args.cfg.usdcMint);\n  const poolPda = new PublicKey(args.cfg.poolPda);\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId);\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId);\n  const vaultAta = new PublicKey(args.cfg.vaultAta);\n  const depositorAta = deriveAta(usdcMint, args.depositor, tokenProgram, atokenProgram);\n\n  const [leafPda] = deriveLeafPda(programId, poolPda, commitmentBytes);\n  const [blobPda] = deriveBlobPda(programId, poolPda, commitmentBytes);\n\n  const disc = await anchorDiscriminator('deposit_note');\n  const ixData = Buffer.concat([\n    disc,\n    commitmentBytes,\n    fieldToBytes32BE(depositorAddrHashField),\n    writeU64LE(args.publicValue),\n    writeU64LE(args.fee),\n    writeAnchorVecU8(encryptedNote),\n    Buffer.from(ephemeralPk),\n    writeAnchorVecU8(proofBytes),\n  ]);\n\n  const verifierProgram = new PublicKey(args.cfg.programIds.depositVerifier);\n\n  const depositIx = new TransactionInstruction({\n    programId,\n    keys: [\n      { pubkey: args.depositor, isSigner: true, isWritable: true },\n      { pubkey: usdcMint, isSigner: false, isWritable: false },\n      { pubkey: poolPda, isSigner: false, isWritable: true },\n      { pubkey: leafPda, isSigner: false, isWritable: true },\n      { pubkey: blobPda, isSigner: false, isWritable: true },\n      { pubkey: vaultAta, isSigner: false, isWritable: true },\n      { pubkey: depositorAta, isSigner: false, isWritable: true },\n      { pubkey: verifierProgram, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n    ],\n    data: ixData,\n  });\n\n  // Pre-flight: vault ATA + depositor ATA must exist. Add `CreateIdempotent`\n  // ixs for any missing ones — cheaper than a full pre-tx round-trip.\n  const preIxs: TransactionInstruction[] = [];\n  const vaultExists = !!(await conn.getAccountInfo(vaultAta));\n  const depositorAtaExists = !!(await conn.getAccountInfo(depositorAta));\n  if (!vaultExists) {\n    preIxs.push(buildCreateIdempotentAtaIx(args.depositor, vaultAta, poolPda, usdcMint, tokenProgram, atokenProgram));\n  }\n  if (!depositorAtaExists) {\n    preIxs.push(buildCreateIdempotentAtaIx(args.depositor, depositorAta, args.depositor, usdcMint, tokenProgram, atokenProgram));\n  }\n\n  // Compute budget — deposit verifier is cheap (~50k CU), but stay safe.\n  const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });\n\n  // Recent blockhash.\n  const blockhash = options.recentBlockhash\n    ?? (await conn.getLatestBlockhash('confirmed')).blockhash;\n\n  // Sponsored fee payer when backend exposed `feePayer`; otherwise user\n  // pays SOL fees themselves. The wallet popup wording differs noticeably\n  // — sponsored case shows ~0 SOL deduction, fallback shows ~5k lamports.\n  const payerKey = args.cfg.feePayer ? new PublicKey(args.cfg.feePayer) : args.depositor;\n  const message = new TransactionMessage({\n    payerKey,\n    recentBlockhash: blockhash,\n    instructions: [cuIx, ...preIxs, depositIx],\n  }).compileToV0Message();\n  const tx = new VersionedTransaction(message);\n\n  return {\n    transaction: tx,\n    commitmentHex: '0x' + commitmentBytes.toString('hex'),\n    commitmentField,\n    leafPda,\n    blobPda,\n    ownedNoteSecret: {\n      blinding,\n      ownerPk: fieldToBytes32BE(args.recipientSpendingPkField),\n      memo: args.memo,\n      valueMicroUsdc: noteValue,\n    },\n    proveMs,\n  };\n}\n\n// ── shared helpers ───────────────────────────────────────────────────────\n\nfunction bytesToField(bytes: Uint8Array): bigint {\n  if (bytes.length === 0) return 0n;\n  let v = 0n;\n  for (const b of bytes) v = (v << 8n) + BigInt(b);\n  return ((v % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\nfunction buildCreateIdempotentAtaIx(\n  payer: PublicKey,\n  ata: PublicKey,\n  owner: PublicKey,\n  mint: PublicKey,\n  tokenProgram: PublicKey,\n  atokenProgram: PublicKey,\n): TransactionInstruction {\n  return new TransactionInstruction({\n    programId: atokenProgram,\n    keys: [\n      { pubkey: payer, isSigner: true, isWritable: true },\n      { pubkey: ata, isSigner: false, isWritable: true },\n      { pubkey: owner, isSigner: false, isWritable: false },\n      { pubkey: mint, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n    ],\n    data: Buffer.from([1]), // CreateIdempotent discriminator\n  });\n}\n\n// ── Merkle reconstruction (frontend mirror) ─────────────────────────────\n\n/// Build the path for `leafIndex` in a depth-`depth` sparse tree whose\n/// occupied leaves are `leaves` (insertion order).\nasync function buildMerkleTreeFromLeaves(\n  leaves: bigint[],\n  leafIndex: number,\n  depth: number,\n): Promise<{ pathElements: bigint[]; pathIndices: number[]; root: bigint }> {\n  if (leafIndex < 0 || leafIndex >= leaves.length) {\n    throw new Error(`merkle path: leafIndex ${leafIndex} out of range [0, ${leaves.length})`);\n  }\n  const zeroHashes: bigint[] = [0n];\n  for (let i = 0; i < depth; i++) {\n    zeroHashes.push(await merkleParent(zeroHashes[i], zeroHashes[i]));\n  }\n  const pathElements: bigint[] = [];\n  const pathIndices: number[] = [];\n  let layer = [...leaves];\n  let cur = leafIndex;\n  for (let level = 0; level < depth; level++) {\n    const isRight = cur % 2;\n    const sibling =\n      isRight === 0\n        ? layer[cur + 1] !== undefined\n          ? layer[cur + 1]\n          : zeroHashes[level]\n        : layer[cur - 1] !== undefined\n        ? layer[cur - 1]\n        : zeroHashes[level];\n    pathElements.push(sibling);\n    pathIndices.push(isRight);\n    const next: bigint[] = [];\n    for (let i = 0; i < layer.length; i += 2) {\n      const l = layer[i];\n      const r = i + 1 < layer.length ? layer[i + 1] : zeroHashes[level];\n      next.push(await merkleParent(l, r));\n    }\n    layer = next;\n    cur = Math.floor(cur / 2);\n  }\n  // Compute root.\n  let rootLayer = [...leaves];\n  for (let level = 0; level < depth; level++) {\n    const next: bigint[] = [];\n    for (let i = 0; i < rootLayer.length; i += 2) {\n      const l = rootLayer[i];\n      const r = i + 1 < rootLayer.length ? rootLayer[i + 1] : zeroHashes[level];\n      next.push(await merkleParent(l, r));\n    }\n    if (rootLayer.length === 0) {\n      rootLayer = [zeroHashes[depth]];\n      break;\n    }\n    rootLayer = next.length > 0 ? next : [zeroHashes[level + 1]];\n  }\n  return { pathElements, pathIndices, root: rootLayer[0] };\n}\n\n// ── Withdraw flow ────────────────────────────────────────────────────────\n\nexport type PrepareWithdrawArgs = {\n  cfg: PrivatePoolV2Config;\n  /// User's master spending_sk (the one derived from the wallet sig).\n  /// For ADR-008 stealth notes the circuit needs the per-note\n  /// `noteStealthSpendingSkField` instead — pass that via the field\n  /// below; this `spendingSkField` is the fallback used for legacy\n  spendingSkField: bigint;\n  nullifierSkField: bigint;\n  /// The note to consume (one OwnedNote-shaped value; we accept just the\n  /// fields the witness needs so callers can pass a slim subset).\n  note: {\n    value: bigint;\n    blinding: bigint;\n    memo: bigint;\n    commitmentField: bigint;\n    leafIndex: number;\n  };\n  /// All commitments currently in the pool, in insertion order. Caller\n  /// fetches via `GET /api/private-pool/v2/leaves`.\n  allLeaves: bigint[];\n  /// Pool root the proof will be built against. Must match what the\n  /// reconstructed merkle root yields for `allLeaves`. Caller pulls this\n  /// from `pool.latest_root` (or any of the last 64 root_history entries).\n  poolRoot: bigint;\n  /// Active ASP root that contains `note.commitmentField`. Caller is\n  /// responsible for ensuring the ASP authority has published this root.\n  aspRoot: bigint;\n  /// ASP merkle path for the note's commitment (also caller's responsibility).\n  aspPath: { pathElements: bigint[]; pathIndices: number[] };\n  /// Recipient SPL pubkey — Poseidon-1 hashed for the proof.\n  recipient: PublicKey;\n  /// micro-USDC paid out to recipient (full note value − fee).\n  publicValue: bigint;\n  fee: bigint;\n};\n\nexport type PreparedWithdrawTx = {\n  transaction: VersionedTransaction;\n  nullifierField: bigint;\n  nullifierPda: PublicKey;\n  proveMs: number;\n};\n\nexport async function prepareWithdrawTransaction(\n  args: PrepareWithdrawArgs,\n  options: { connection: Connection; payer: PublicKey; recentBlockhash?: string },\n): Promise<PreparedWithdrawTx> {\n  // 1. Reconstruct the pool merkle path for the note's leaf.\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  );\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareWithdrawTransaction: reconstructed pool root ${poolPath.root.toString(16)} does not match supplied ${args.poolRoot.toString(16)} — likely stale leaves array`,\n    );\n  }\n\n  // 2. Derive the public address binding (Poseidon-1 of recipient pubkey).\n  const publicAddressField = await poseidonAddrHash(args.recipient);\n\n  // Legacy ADR-003: owner_pk = poseidon1(master_spending_sk). The\n  // commitment on chain was minted with this exact owner_pk, so the\n  // circuit's `poseidon1(spendingSk) == ownerPk` check tautologically\n  // holds when both sides come from the same master sk.\n  const p = await getPoseidon();\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n\n  // Witness shape — mirrors `Withdraw.circom` signal order. Same rationale\n  // as deposit: we build the field-string map directly, bypassing the\n  // builder that expects Uint8Array shapes.\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness;\n\n  // 4. Prove + pack.\n  const { proofBytes, proveMs } = await proveWithdraw(witness);\n\n  // 5. Compute the nullifier (matches what the circuit emits).\n  const nullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  });\n  const nullifierBytes = fieldToBytes32BE(nullifierField);\n\n  // 6. Build the ix.\n  const programId = new PublicKey(args.cfg.programIds.pool);\n  const usdcMint = new PublicKey(args.cfg.usdcMint);\n  const poolPda = new PublicKey(args.cfg.poolPda);\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId);\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId);\n  const vaultAta = new PublicKey(args.cfg.vaultAta);\n  const recipientAta = deriveAta(usdcMint, args.recipient, tokenProgram, atokenProgram);\n  const verifier = new PublicKey(args.cfg.programIds.withdrawVerifier);\n\n  const [nullifierPda] = deriveNullifierPda(programId, poolPda, nullifierBytes);\n\n  const disc = await anchorDiscriminator('withdraw');\n  const ixData = Buffer.concat([\n    disc,\n    nullifierBytes,\n    fieldToBytes32BE(args.poolRoot),\n    fieldToBytes32BE(args.aspRoot),\n    fieldToBytes32BE(publicAddressField),\n    writeU64LE(args.publicValue),\n    writeU64LE(args.fee),\n    writeAnchorVecU8(proofBytes),\n  ]);\n\n  const withdrawIx = new TransactionInstruction({\n    programId,\n    keys: [\n      { pubkey: options.payer, isSigner: true, isWritable: true },\n      { pubkey: usdcMint, isSigner: false, isWritable: false },\n      { pubkey: poolPda, isSigner: false, isWritable: true },\n      { pubkey: nullifierPda, isSigner: false, isWritable: true },\n      { pubkey: args.recipient, isSigner: false, isWritable: false },\n      { pubkey: vaultAta, isSigner: false, isWritable: true },\n      { pubkey: recipientAta, isSigner: false, isWritable: true },\n      { pubkey: verifier, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n    ],\n    data: ixData,\n  });\n\n  // Pre-flight: recipient ATA may not exist yet on a fresh wallet.\n  const preIxs: TransactionInstruction[] = [];\n  const recipientAtaExists = !!(await options.connection.getAccountInfo(recipientAta));\n  if (!recipientAtaExists) {\n    preIxs.push(buildCreateIdempotentAtaIx(options.payer, recipientAta, args.recipient, usdcMint, tokenProgram, atokenProgram));\n  }\n  const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });\n\n  const blockhash = options.recentBlockhash\n    ?? (await options.connection.getLatestBlockhash('confirmed')).blockhash;\n  const payerKey = args.cfg.feePayer ? new PublicKey(args.cfg.feePayer) : options.payer;\n  const message = new TransactionMessage({\n    payerKey,\n    recentBlockhash: blockhash,\n    instructions: [cuIx, ...preIxs, withdrawIx],\n  }).compileToV0Message();\n  const tx = new VersionedTransaction(message);\n  return { transaction: tx, nullifierField, nullifierPda, proveMs };\n}\n\n// ── Relayer-signed withdraw (ADR-010) ────────────────────────────────────\n//\n// Companion to `prepareWithdrawTransaction`: instead of returning a\n// VersionedTransaction the caller has to sign + broadcast (which puts\n// the user's wallet pubkey into the on-chain `signers` array),\n// `prepareWithdrawRelayPayload` returns a typed bundle the SDK ships\n// to `POST /api/private-pool/v2/relay/withdraw`. The backend wraps it\n// in a tx with `caller = relayer_keypair` and broadcasts.\n//\n// Net effect on chain: the user's wallet never appears as a signer of\n// the withdraw tx. The proof is the only authorisation primitive\n// (recipient is bound via `public_address = Poseidon(recipient)`;\n// amount + fee + roots are public inputs the verifier cross-checks).\n// See ADR-010 §Threat model for what the relayer can / cannot do.\n\nexport type WithdrawRelayPayload = {\n  network: string\n  nullifier_hex: string\n  root_hex: string\n  asp_root_hex: string\n  public_address_hex: string\n  public_value: string\n  fee: string\n  recipient: string\n  recipient_ata: string\n  proof_b64: string\n}\n\nexport type PreparedWithdrawRelay = {\n  payload: WithdrawRelayPayload\n  nullifierField: bigint\n  proveMs: number\n}\n\nexport async function prepareWithdrawRelayPayload(\n  args: PrepareWithdrawArgs & { network: string },\n): Promise<PreparedWithdrawRelay> {\n  // The proof + public-input derivation is identical to\n  // `prepareWithdrawTransaction`; only the output shape differs.\n  // We inline the steps rather than refactoring the existing function\n  // to share a helper because every step inside that function is\n  // tightly couple to the on-chain ix layout (witness shape, account\n  // ordering) — duplicating a few lines keeps each path independently\n  // auditable.\n\n  // 1. Pool merkle path.\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  )\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareWithdrawRelayPayload: reconstructed pool root ${poolPath.root.toString(16)} != supplied ${args.poolRoot.toString(16)} — likely stale leaves array`,\n    )\n  }\n\n  // 2. Recipient binding.\n  const publicAddressField = await poseidonAddrHash(args.recipient)\n\n  // 3. Witness (same shape as the tx flow; see `prepareWithdrawTransaction`).\n  // CRITICAL: this must match `Withdraw.circom` signal inputs EXACTLY.\n  // The circuit derives poolRoot/aspRoot internally from the path elements\n  // and verifies leafIndex via `poolPathIndices` — adding those as separate\n  // signals fails witness gen with \"Signal X not found\" under strict snarkjs.\n  const p = await getPoseidon()\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness\n\n  const { proofBytes, proveMs } = await proveWithdraw(witness)\n\n  // 4. Nullifier (matches the circuit's emission).\n  const nullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n  const nullifierBytes = fieldToBytes32BE(nullifierField)\n\n  // 5. Pre-derive the recipient ATA so the server doesn't re-do the\n  //    derivation work + we can cross-check the proof's binding before\n  //    burning relayer SOL.\n  const usdcMint = new PublicKey(args.cfg.usdcMint)\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId)\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId)\n  const recipientAta = deriveAta(usdcMint, args.recipient, tokenProgram, atokenProgram)\n\n  // 6. Build the JSON payload. All bigint values serialised as\n  //    decimal strings — the server parses them with `BigInt(…)`,\n  //    bypassing JS Number precision loss above 2^53.\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n\n  const payload: WithdrawRelayPayload = {\n    network: args.network,\n    nullifier_hex: toHex32(nullifierField),\n    root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    public_address_hex: toHex32(publicAddressField),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    recipient: args.recipient.toBase58(),\n    recipient_ata: recipientAta.toBase58(),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return { payload, nullifierField, proveMs }\n}\n\n/**\n * Submit a prepared relay payload. Returns the on-chain tx signature\n * + an explorer URL when confirmation succeeds.\n *\n * Network-error handling: if the POST itself fails (server down,\n * timeout, etc.), the error is rethrown for the caller to retry —\n * the on-chain state is unchanged because no tx was built. If the\n * POST returns a structured error (e.g. `already_consumed`,\n * `fee_below_min`), it's surfaced as a typed exception with the\n * server's error code on `cause.error`.\n */\nexport async function submitWithdrawViaRelay(args: {\n  apiBaseUrl: string\n  payload: WithdrawRelayPayload\n}): Promise<{ signature: string; confirmed: boolean; explorerUrl: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/withdraw',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  const body = (await res.json().catch(() => ({}))) as Record<string, unknown>\n  if (!res.ok) {\n    const err = new Error(\n      `relay/withdraw rejected (HTTP ${res.status}): ${body.error ?? 'unknown'} — ${body.detail ?? ''}`,\n    )\n    ;(err as Error & { cause?: unknown }).cause = body\n    throw err\n  }\n  return {\n    signature: String(body.signature ?? ''),\n    confirmed: Boolean(body.confirmed),\n    explorerUrl: String(body.explorerUrl ?? ''),\n  }\n}\n\n// ── JoinSplit flow ───────────────────────────────────────────────────────\n//\n// Solana's 1232-byte tx limit forces a couple of compromises here:\n//   - Use a v0 tx with an Address Lookup Table (caller manages the LUT,\n//     normally created once per pool and reused).\n//   - Encrypted-note blobs go off-chain (per ADR-004 amendment); caller\n//     POSTs them to `/api/private-pool/v2/notes/relay-blob` AFTER the tx\n//     confirms.\n//\n// Caller is responsible for:\n//   - publishing an ASP root containing both input commitments (separate\n//     `add_asp_root` tx, gated by ASP authority signer)\n//   - allocating + extending the LUT and waiting for it to land\n//   - encrypting the recipient + change ciphertexts and uploading them\n//     to the relay (we return the cipher material so the caller can do\n//     this in parallel with confirmation polling)\n\nexport type JoinSplitNoteRecipient = {\n  /// Recipient's public spending key field (Poseidon1 of their spending_sk).\n  spendingPkField: bigint;\n  /// Recipient's X25519 viewing pubkey (32 bytes) — used for blob encryption.\n  viewingPk: Uint8Array;\n};\n\nexport type PrepareJoinSplitArgs = {\n  cfg: PrivatePoolV2Config;\n  /// Sender's keys (must be the owner of both input notes).\n  senderSpendingSkField: bigint;\n  senderSpendingPkField: bigint; // Poseidon1(senderSpendingSkField) — mirror for owner_pk equality\n  senderNullifierSkField: bigint;\n  senderViewingPk: Uint8Array; // for self-encrypting the change note\n  /// Two input notes the sender owns + their merkle paths in the pool tree.\n  /// All notes are legacy ADR-003 (master_pk owner_pk) in V1.\n  inputNotes: [\n    { value: bigint; blinding: bigint; memo: bigint; commitmentField: bigint; leafIndex: number },\n    { value: bigint; blinding: bigint; memo: bigint; commitmentField: bigint; leafIndex: number },\n  ];\n  allLeaves: bigint[];\n  poolRoot: bigint;\n  aspRoot: bigint;\n  aspPaths: [\n    { pathElements: bigint[]; pathIndices: number[] },\n    { pathElements: bigint[]; pathIndices: number[] },\n  ];\n  /// Recipient identity. Output 1 goes here; output 2 is sender's change.\n  recipient: JoinSplitNoteRecipient;\n  /// Amount transferred to recipient (micro-USDC). Must satisfy\n  /// `recipientAmount + changeAmount + fee == in1.value + in2.value`.\n  recipientAmount: bigint;\n  fee: bigint;\n  /// Optional 32-byte memo attached to the recipient's output note —\n  /// the recipient sees it after decrypting their blob. Use cases:\n  /// invoice IDs (binds the JoinSplit to a specific invoice), payment\n  /// reason codes, free-text labels (UTF-8 truncated). Pass `null` for\n  /// memo-less notes (default).\n  recipientMemo?: Uint8Array | null;\n  /// Lookup table to fold constant accounts into. Caller created earlier.\n  lookupTable: import('@solana/web3.js').AddressLookupTableAccount;\n};\n\nexport type PreparedJoinSplitTx = {\n  transaction: VersionedTransaction;\n  nullifiers: [bigint, bigint];\n  outputCommitments: [bigint, bigint];\n  /// Materials the caller MUST relay to the backend after confirmation,\n  /// otherwise the recipient never sees the new note.\n  blobs: [\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n  ];\n  proveMs: number;\n};\n\nexport async function prepareJoinSplitTransaction(\n  args: PrepareJoinSplitArgs,\n  options: { connection: Connection; payer: PublicKey; recentBlockhash?: string },\n): Promise<PreparedJoinSplitTx> {\n  // 1. Balance check before anything expensive.\n  const totalIn = args.inputNotes[0].value + args.inputNotes[1].value;\n  const changeAmount = totalIn - args.recipientAmount - args.fee;\n  if (changeAmount < 0n) {\n    throw new Error(\n      `prepareJoinSplitTransaction: insufficient input value (have ${totalIn}, need ${args.recipientAmount + args.fee})`,\n    );\n  }\n\n  // All notes are legacy ADR-003 in V1 (stealth removed — see ADR-008\n  // postmortem). Both inputs spend under the master spending_sk, both\n  // outputs land under the recipient's / sender's master spending_pk.\n  const inputSpendingSkField = args.senderSpendingSkField;\n  const inputOwnerPkField = args.senderSpendingPkField;\n\n  // 2. Reconstruct merkle paths for both inputs.\n  const path1 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[0].leafIndex, args.cfg.constants.poolDepth);\n  const path2 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[1].leafIndex, args.cfg.constants.poolDepth);\n  if (path1.root !== args.poolRoot || path2.root !== args.poolRoot) {\n    throw new Error('prepareJoinSplitTransaction: pool root mismatch on reconstruction');\n  }\n\n  // 3. Generate fresh blindings for the two output notes.\n  const out1Blinding = randomFieldBytes();\n  const out2Blinding = randomFieldBytes();\n  const out1BlindingField = bytesToField(out1Blinding);\n  const out2BlindingField = bytesToField(out2Blinding);\n\n  // Recipient memo (optional 32-byte tag). The change note has no memo.\n  const recipientMemo = args.recipientMemo ?? null;\n  if (recipientMemo && recipientMemo.length !== 32) {\n    throw new Error(`prepareJoinSplitTransaction: recipientMemo must be 32 bytes, got ${recipientMemo.length}`);\n  }\n  const recipientMemoField = recipientMemo ? bytesToField(recipientMemo) : 0n;\n\n  // Legacy ADR-003 outputs — recipient + change notes carry the master\n  // `spending_pk` of their respective owners. Per-recipient\n  // unlinkability is weaker (two transfers to the same pubkey share\n  // the same `owner_pk`) but amounts + sender stay hidden via the\n  // anonymity set — V1 trade-off until a curve-based stealth scheme\n  // ships (see ADR-008 postmortem).\n  const recipientOwnerPk = args.recipient.spendingPkField;\n  const changeOwnerPk = args.senderSpendingPkField;\n\n  // Witness shape mirrors `JoinSplit2x2.circom` signal declaration order.\n  // Built directly from bigint inputs (cheaper than going through the\n  // Uint8Array-shaped builder API).\n  //\n  // `inputOwnerPkN` and `spendingSk` are the stealth-aware values\n  // (= master pair when both inputs are non-stealth, = stealth pair\n  // when both inputs share the same stealth nonce). Outputs always use\n  // the per-payment stealth `owner_pk` — the recipient/sender unwrap\n  // their nonce from the blob during discovery.\n  const witness = {\n    inputValue1: args.inputNotes[0].value.toString(10),\n    inputOwnerPk1: inputOwnerPkField.toString(10),\n    inputBlinding1: args.inputNotes[0].blinding.toString(10),\n    inputMemo1: args.inputNotes[0].memo.toString(10),\n    inputPoolPath1: path1.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices1: path1.pathIndices,\n    inputAspPath1: args.aspPaths[0].pathElements.map((e) => e.toString(10)),\n    inputAspIndices1: args.aspPaths[0].pathIndices,\n\n    inputValue2: args.inputNotes[1].value.toString(10),\n    inputOwnerPk2: inputOwnerPkField.toString(10),\n    inputBlinding2: args.inputNotes[1].blinding.toString(10),\n    inputMemo2: args.inputNotes[1].memo.toString(10),\n    inputPoolPath2: path2.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices2: path2.pathIndices,\n    inputAspPath2: args.aspPaths[1].pathElements.map((e) => e.toString(10)),\n    inputAspIndices2: args.aspPaths[1].pathIndices,\n\n    spendingSk: inputSpendingSkField.toString(10),\n    nullifierSk: args.senderNullifierSkField.toString(10),\n\n    outputValue1: args.recipientAmount.toString(10),\n    outputOwnerPk1: recipientOwnerPk.toString(10),\n    outputBlinding1: out1BlindingField.toString(10),\n    outputMemo1: recipientMemoField.toString(10),\n\n    outputValue2: changeAmount.toString(10),\n    outputOwnerPk2: changeOwnerPk.toString(10),\n    outputBlinding2: out2BlindingField.toString(10),\n    outputMemo2: '0',\n\n    publicValueIn: '0',\n    publicValueOut: '0',\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').JoinSplitWitness;\n\n  // 5. Prove.\n  const { proofBytes, proveMs } = await proveJoinSplit(witness);\n\n  // 6. Compute output commitments + nullifiers (witness builder didn't\n  //    surface them as bigints, so re-derive from the public signals — or\n  //    cleaner: recompute via Poseidon4).\n  //\n  //    Output commitments use the stealth `owner_pk` so they match the\n  //    on-chain leaf the verifier emits from the public-input `outputOwnerPkN`.\n  const out1Commitment = await poseidonHashFour(\n    args.recipientAmount,\n    recipientOwnerPk,\n    out1BlindingField,\n    recipientMemoField,\n  );\n  const out2Commitment = await poseidonHashFour(\n    changeAmount,\n    changeOwnerPk,\n    out2BlindingField,\n    0n,\n  );\n  const n1 = await deriveNullifierAsync({\n    commitment: args.inputNotes[0].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  });\n  const n2 = await deriveNullifierAsync({\n    commitment: args.inputNotes[1].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  });\n\n  // 7. Encrypt blobs (recipient note + sender's change note).\n  //\n  //    Plaintext carries:\n  //      - `ownerPk`: the stealth `owner_pk` so the recipient's\n  //        Poseidon4 commitment recompute (during decrypt sanity-check)\n  //        matches the on-chain leaf\n  //      - `stealthNonce`: lets the recipient derive\n  //        `stealth_spending_sk = poseidon2(master_sk, nonce)`\n  //        so they can spend later\n  //      - `layout: 'stealth'`: triggers the 112B/144B serializer path\n  const oc1Bytes = fieldToBytes32BE(out1Commitment);\n  const oc2Bytes = fieldToBytes32BE(out2Commitment);\n  const recipientBlob = encryptNote({\n    note: {\n      value: args.recipientAmount,\n      blinding: out1Blinding,\n      ownerPk: fieldToBytes32BE(recipientOwnerPk),\n      memo: recipientMemo,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.recipient.viewingPk,\n    commitment: oc1Bytes,\n  });\n  const changeBlob = encryptNote({\n    note: {\n      value: changeAmount,\n      blinding: out2Blinding,\n      ownerPk: fieldToBytes32BE(changeOwnerPk),\n      memo: null,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.senderViewingPk,\n    commitment: oc2Bytes,\n  });\n\n  // 8. Build the ix. Empty `encrypted_note` per output (ADR-004 amendment:\n  //    JoinSplit blobs go off-chain via the relay).\n  const programId = new PublicKey(args.cfg.programIds.pool);\n  const usdcMint = new PublicKey(args.cfg.usdcMint);\n  const poolPda = new PublicKey(args.cfg.poolPda);\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId);\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId);\n  const vaultAta = new PublicKey(args.cfg.vaultAta);\n  const verifier = new PublicKey(args.cfg.programIds.joinsplitVerifier);\n\n  const n1Bytes = fieldToBytes32BE(n1);\n  const n2Bytes = fieldToBytes32BE(n2);\n  const [nMarker1] = deriveNullifierPda(programId, poolPda, n1Bytes);\n  const [nMarker2] = deriveNullifierPda(programId, poolPda, n2Bytes);\n  const [leaf1] = deriveLeafPda(programId, poolPda, oc1Bytes);\n  const [leaf2] = deriveLeafPda(programId, poolPda, oc2Bytes);\n  const [blob1] = deriveBlobPda(programId, poolPda, oc1Bytes);\n  const [blob2] = deriveBlobPda(programId, poolPda, oc2Bytes);\n\n  const disc = await anchorDiscriminator('joinsplit');\n  const ixData = Buffer.concat([\n    disc,\n    n1Bytes, n2Bytes,\n    oc1Bytes, oc2Bytes,\n    fieldToBytes32BE(args.poolRoot),\n    fieldToBytes32BE(args.aspRoot),\n    fieldToBytes32BE(0n), // public_address (pure internal)\n    writeU64LE(0n),  // public_value_in\n    writeU64LE(0n),  // public_value_out\n    writeU64LE(args.fee),\n    writeAnchorVecU8(new Uint8Array(0)), // encrypted_note_1 — empty (off-chain)\n    writeAnchorVecU8(new Uint8Array(0)), // encrypted_note_2 — empty (off-chain)\n    Buffer.alloc(32),                    // ephemeral_pk_1 — zero (unused)\n    Buffer.alloc(32),                    // ephemeral_pk_2 — zero (unused)\n    writeAnchorVecU8(proofBytes),\n  ]);\n\n  const joinsplitIx = new TransactionInstruction({\n    programId,\n    keys: [\n      { pubkey: options.payer, isSigner: true, isWritable: true },\n      { pubkey: usdcMint, isSigner: false, isWritable: false },\n      { pubkey: poolPda, isSigner: false, isWritable: true },\n      { pubkey: nMarker1, isSigner: false, isWritable: true },\n      { pubkey: nMarker2, isSigner: false, isWritable: true },\n      { pubkey: leaf1, isSigner: false, isWritable: true },\n      { pubkey: leaf2, isSigner: false, isWritable: true },\n      { pubkey: blob1, isSigner: false, isWritable: true },\n      { pubkey: blob2, isSigner: false, isWritable: true },\n      { pubkey: vaultAta, isSigner: false, isWritable: true },\n      // pure internal: pass payer as spl_counterparty + payer ATA\n      { pubkey: options.payer, isSigner: false, isWritable: false },\n      { pubkey: deriveAta(usdcMint, options.payer, tokenProgram, atokenProgram), isSigner: false, isWritable: true },\n      { pubkey: verifier, isSigner: false, isWritable: false },\n      { pubkey: tokenProgram, isSigner: false, isWritable: false },\n      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },\n    ],\n    data: ixData,\n  });\n\n  const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 800_000 });\n  const blockhash = options.recentBlockhash\n    ?? (await options.connection.getLatestBlockhash('confirmed')).blockhash;\n  const payerKey = args.cfg.feePayer ? new PublicKey(args.cfg.feePayer) : options.payer;\n  const message = new TransactionMessage({\n    payerKey,\n    recentBlockhash: blockhash,\n    instructions: [cuIx, joinsplitIx],\n  }).compileToV0Message([args.lookupTable]);\n  const tx = new VersionedTransaction(message);\n\n  return {\n    transaction: tx,\n    nullifiers: [n1, n2],\n    outputCommitments: [out1Commitment, out2Commitment],\n    blobs: [\n      {\n        commitmentHex: '0x' + Buffer.from(oc1Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(recipientBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(recipientBlob.ciphertext).toString('base64'),\n        ciphertextLen: recipientBlob.ciphertext.length,\n      },\n      {\n        commitmentHex: '0x' + Buffer.from(oc2Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(changeBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(changeBlob.ciphertext).toString('base64'),\n        ciphertextLen: changeBlob.ciphertext.length,\n      },\n    ],\n    proveMs,\n  };\n}\n\n// ── Relayer-signed joinsplit (ADR-010) ───────────────────────────────────\n//\n// Sibling of `prepareWithdrawRelayPayload`. Same trade-offs and same\n// \"caller is unbound\" trick the Anchor program already supports. Top-up\n// (`public_value_in > 0`) is rejected server-side because the SPL\n// transfer in that branch requires the counterparty's signing\n// authority; this function still accepts pure-internal and mid-flow\n// withdraw shapes.\n\nexport type JoinSplitRelayPayload = {\n  network: string\n  root_hex: string\n  asp_root_hex: string\n  public_address_hex: string\n  input_nullifiers_hex: [string, string]\n  output_commitments_hex: [string, string]\n  ephemeral_pks_b64: [string, string]\n  encrypted_notes_b64: [string, string]\n  spl_counterparty: string\n  spl_counterparty_ata: string\n  public_value_in: string\n  public_value_out: string\n  fee: string\n  proof_b64: string\n}\n\nexport type PreparedJoinSplitRelay = {\n  payload: JoinSplitRelayPayload\n  nullifiers: [bigint, bigint]\n  outputCommitments: [bigint, bigint]\n  /// Same shape as `PreparedJoinSplitTx.blobs` — the SDK still POSTs\n  /// these to /notes/relay-blob AFTER the relay tx confirms, so the\n  /// recipient can trial-decrypt. The relay tx ITSELF carries empty\n  /// `encrypted_note_*` Vec<u8> (ADR-004 amendment).\n  blobs: [\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n    { commitmentHex: string; ephemeralPkHex: string; ciphertextB64: string; ciphertextLen: number },\n  ]\n  proveMs: number\n}\n\n/// Args for the relay flow. Same as `PrepareJoinSplitArgs` minus the\n/// `lookupTable` (the server builds a legacy Transaction without LUT\n/// since with only one signer the joinsplit ix fits comfortably under\n/// the 1232-byte tx size limit).\nexport type PrepareJoinSplitRelayArgs = Omit<PrepareJoinSplitArgs, 'lookupTable'> & {\n  network: string\n  /// Sender's wallet pubkey — used to derive the depositor's own ATA\n  /// which the on-chain ix struct requires as `spl_counterparty` even\n  /// for pure-internal flows (handler short-circuits the SPL movement\n  /// when both public values are zero).\n  payer: PublicKey\n}\n\nexport async function prepareJoinSplitRelayPayload(\n  args: PrepareJoinSplitRelayArgs,\n): Promise<PreparedJoinSplitRelay> {\n  // Balance + path reconstruction, mirroring `prepareJoinSplitTransaction`\n  // (no shared helper for the same reason as withdraw — keep each\n  // path independently auditable).\n  const totalIn = args.inputNotes[0].value + args.inputNotes[1].value\n  const changeAmount = totalIn - args.recipientAmount - args.fee\n  if (changeAmount < 0n) {\n    throw new Error(\n      `prepareJoinSplitRelayPayload: insufficient input value (have ${totalIn}, need ${args.recipientAmount + args.fee})`,\n    )\n  }\n\n  const path1 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[0].leafIndex, args.cfg.constants.poolDepth)\n  const path2 = await buildMerkleTreeFromLeaves(args.allLeaves, args.inputNotes[1].leafIndex, args.cfg.constants.poolDepth)\n  if (path1.root !== args.poolRoot || path2.root !== args.poolRoot) {\n    throw new Error('prepareJoinSplitRelayPayload: pool root mismatch on reconstruction')\n  }\n\n  // Fresh blindings for the two outputs.\n  const out1Blinding = randomFieldBytes()\n  const out2Blinding = randomFieldBytes()\n  const out1BlindingField = bytesToField(out1Blinding)\n  const out2BlindingField = bytesToField(out2Blinding)\n\n  // Recipient memo (optional 32-byte tag); change note has no memo.\n  const recipientMemo = args.recipientMemo ?? null\n  if (recipientMemo && recipientMemo.length !== 32) {\n    throw new Error(`prepareJoinSplitRelayPayload: recipientMemo must be 32 bytes, got ${recipientMemo.length}`)\n  }\n  const recipientMemoField = recipientMemo ? bytesToField(recipientMemo) : 0n\n\n  // Owner_pk per output. Same legacy ADR-003 rule as the tx flow.\n  const recipientOwnerPk = args.recipient.spendingPkField\n  const changeOwnerPk = args.senderSpendingPkField\n\n  // Witness — identical shape to the tx path; the proof bytes carry\n  // the same public-input commitments.\n  const witness = {\n    inputValue1: args.inputNotes[0].value.toString(10),\n    inputOwnerPk1: args.senderSpendingPkField.toString(10),\n    inputBlinding1: args.inputNotes[0].blinding.toString(10),\n    inputMemo1: args.inputNotes[0].memo.toString(10),\n    inputPoolPath1: path1.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices1: path1.pathIndices,\n    inputAspPath1: args.aspPaths[0].pathElements.map((e) => e.toString(10)),\n    inputAspIndices1: args.aspPaths[0].pathIndices,\n\n    inputValue2: args.inputNotes[1].value.toString(10),\n    inputOwnerPk2: args.senderSpendingPkField.toString(10),\n    inputBlinding2: args.inputNotes[1].blinding.toString(10),\n    inputMemo2: args.inputNotes[1].memo.toString(10),\n    inputPoolPath2: path2.pathElements.map((e) => e.toString(10)),\n    inputPoolIndices2: path2.pathIndices,\n    inputAspPath2: args.aspPaths[1].pathElements.map((e) => e.toString(10)),\n    inputAspIndices2: args.aspPaths[1].pathIndices,\n\n    spendingSk: args.senderSpendingSkField.toString(10),\n    nullifierSk: args.senderNullifierSkField.toString(10),\n\n    outputValue1: args.recipientAmount.toString(10),\n    outputOwnerPk1: recipientOwnerPk.toString(10),\n    outputBlinding1: out1BlindingField.toString(10),\n    outputMemo1: recipientMemoField.toString(10),\n\n    outputValue2: changeAmount.toString(10),\n    outputOwnerPk2: changeOwnerPk.toString(10),\n    outputBlinding2: out2BlindingField.toString(10),\n    outputMemo2: '0',\n\n    publicValueIn: '0',\n    publicValueOut: '0',\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').JoinSplitWitness\n\n  const { proofBytes, proveMs } = await proveJoinSplit(witness)\n\n  // Output commitments + nullifiers (derived post-prove so we don't\n  // need a separate witness pass).\n  const out1Commitment = await poseidonHashFour(args.recipientAmount, recipientOwnerPk, out1BlindingField, recipientMemoField)\n  const out2Commitment = await poseidonHashFour(changeAmount, changeOwnerPk, out2BlindingField, 0n)\n  const n1 = await deriveNullifierAsync({\n    commitment: args.inputNotes[0].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  })\n  const n2 = await deriveNullifierAsync({\n    commitment: args.inputNotes[1].commitmentField,\n    nullifierSk: args.senderNullifierSkField,\n  })\n\n  // Encrypt both blobs (recipient + change). Plaintext same shape as\n  // the tx path so the recipient's decrypt code is identical.\n  const oc1Bytes = fieldToBytes32BE(out1Commitment)\n  const oc2Bytes = fieldToBytes32BE(out2Commitment)\n  const recipientBlob = encryptNote({\n    note: {\n      value: args.recipientAmount,\n      blinding: out1Blinding,\n      ownerPk: fieldToBytes32BE(recipientOwnerPk),\n      memo: recipientMemo,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.recipient.viewingPk,\n    commitment: oc1Bytes,\n  })\n  const changeBlob = encryptNote({\n    note: {\n      value: changeAmount,\n      blinding: out2Blinding,\n      ownerPk: fieldToBytes32BE(changeOwnerPk),\n      memo: null,\n      stealthNonce: null,\n      layout: 'legacy',\n    },\n    recipientViewingPk: args.senderViewingPk,\n    commitment: oc2Bytes,\n  })\n\n  // Pure-internal sub-case: pass payer as spl_counterparty + their\n  // ATA. The handler short-circuits SPL movement when both public\n  // values are zero, so these accounts are unused on chain but the\n  // ix struct requires them.\n  const usdcMint = new PublicKey(args.cfg.usdcMint)\n  const tokenProgram = new PublicKey(args.cfg.tokenProgramId)\n  const atokenProgram = new PublicKey(args.cfg.associatedTokenProgramId)\n  const payerAta = deriveAta(usdcMint, args.payer, tokenProgram, atokenProgram)\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n\n  const payload: JoinSplitRelayPayload = {\n    network: args.network,\n    root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    public_address_hex: toHex32(0n),\n    input_nullifiers_hex: [toHex32(n1), toHex32(n2)],\n    output_commitments_hex: [toHex32(out1Commitment), toHex32(out2Commitment)],\n    // The on-chain ix struct includes ephemeral_pk_* even though the\n    // Anchor handler ignores them when encrypted_note_* is empty.\n    // We pass zeros (matches `prepareJoinSplitTransaction`).\n    ephemeral_pks_b64: [\n      Buffer.from(new Uint8Array(32)).toString('base64'),\n      Buffer.from(new Uint8Array(32)).toString('base64'),\n    ],\n    encrypted_notes_b64: ['', ''], // off-chain via /notes/relay-blob\n    spl_counterparty: args.payer.toBase58(),\n    spl_counterparty_ata: payerAta.toBase58(),\n    public_value_in: '0',\n    public_value_out: '0',\n    fee: args.fee.toString(10),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return {\n    payload,\n    nullifiers: [n1, n2],\n    outputCommitments: [out1Commitment, out2Commitment],\n    blobs: [\n      {\n        commitmentHex: '0x' + Buffer.from(oc1Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(recipientBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(recipientBlob.ciphertext).toString('base64'),\n        ciphertextLen: recipientBlob.ciphertext.length,\n      },\n      {\n        commitmentHex: '0x' + Buffer.from(oc2Bytes).toString('hex'),\n        ephemeralPkHex: '0x' + Buffer.from(changeBlob.ephemeralPk).toString('hex'),\n        ciphertextB64: Buffer.from(changeBlob.ciphertext).toString('base64'),\n        ciphertextLen: changeBlob.ciphertext.length,\n      },\n    ],\n    proveMs,\n  }\n}\n\n/**\n * Submit a prepared joinsplit relay payload. Same shape as\n * `submitWithdrawViaRelay`. Returns the on-chain tx signature on\n * success; throws with `cause.error` on structured server errors\n * (e.g. `not_relayable`, `already_consumed`).\n */\nexport async function submitJoinSplitViaRelay(args: {\n  apiBaseUrl: string\n  payload: JoinSplitRelayPayload\n}): Promise<{ signature: string; confirmed: boolean; explorerUrl: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/joinsplit',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  const body = (await res.json().catch(() => ({}))) as Record<string, unknown>\n  if (!res.ok) {\n    const err = new Error(\n      `relay/joinsplit rejected (HTTP ${res.status}): ${body.error ?? 'unknown'} — ${body.detail ?? ''}`,\n    )\n    ;(err as Error & { cause?: unknown }).cause = body\n    throw err\n  }\n  return {\n    signature: String(body.signature ?? ''),\n    confirmed: Boolean(body.confirmed),\n    explorerUrl: String(body.explorerUrl ?? ''),\n  }\n}\n\n// ── Internal helpers ────────────────────────────────────────────────────\n\nfunction randomFieldBytes(): Uint8Array {\n  const out = crypto.getRandomValues(new Uint8Array(32));\n  out[0] = out[0] & 0x3f; // keep within BN254 field\n  return out;\n}\n\nasync function poseidonHashFour(a: bigint, b: bigint, c: bigint, d: bigint): Promise<bigint> {\n  const p = await getPoseidon();\n  const h = p([a, b, c, d]);\n  return ((BigInt(p.F.toString(h)) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n// Re-export the parser so callers don't need to pull `private-pool-keys`\n// directly when they're only translating a payment-address string.\nexport { parsePaymentAddress };\n\n/// Submit a user-signed (sponsored-fee-payer) tx to the backend for\n/// fee-payer signing + RPC relay. Returns the cluster signature once\n/// the tx confirms (or unconfirmed signature with a `confirmed: false`\n/// flag if the cluster lagged on confirmation).\n///\n/// `signedTx` is the result of `wallet.signTransaction(prepared.transaction)` —\n/// user provides their own signature, backend adds fee-payer sig + sends.\nexport async function relaySponsoredTransaction(args: {\n  apiBaseUrl: string;\n  network: string;\n  signedTx: VersionedTransaction;\n}): Promise<{ signature: string; confirmed: boolean }> {\n  const url = new URL('api/private-pool/v2/relay-tx', args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/');\n  url.searchParams.set('network', args.network);\n  const r = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify({\n      tx_b64: Buffer.from(args.signedTx.serialize()).toString('base64'),\n    }),\n  });\n  if (!r.ok) {\n    const err = await r.text();\n    throw new Error(`relay-tx failed: ${r.status} ${err}`);\n  }\n  const j = await r.json() as { signature: string; confirmed?: boolean };\n  return { signature: j.signature, confirmed: j.confirmed !== false };\n}\n\n// ── ADR-012 V3 — unlinkable cashout via credit pool tree ────────────────\n//\n// V3 differs from ADR-011 v2 in shape:\n//   - Park outputs a fresh commitment to the credit pool merkle tree\n//     (NOT a per-recipient PDA). The on-chain claim_credit_v3 tx has\n//     NO recipient account in its account list — recipient is bound\n//     only through the publicAddress hash + the ownerPk inside the\n//     encrypted commitment.\n//   - Cashout is a ZK proof of membership in the credit pool tree\n//     against a fresh credit_nullifier (Poseidon2(commitment,\n//     nullifier_sk), like main-pool spends). Recipient pubkey appears\n//     only as `recipient_ata` owner check at cashout time, decoupled\n//     from any specific Park.\n//   - Encrypted blob is keyed by credit_commitment (not by PDA),\n//     mirroring main-pool deposit blobs. Discovery is trial-decrypt\n//     against the recipient's viewing_sk, just like Send notes.\n\nexport type ClaimCreditV3RelayPayload = {\n  network: string\n  // ── Source-side (Withdraw proof on the pool note being spent) ────\n  source_nullifier_hex: string\n  source_root_hex: string\n  asp_root_hex: string\n  public_address_hex: string\n  public_value: string\n  fee: string\n  source_proof_b64: string\n  // ── Credit-pool side (new commitment + encrypted blob) ───────────\n  credit_commitment_hex: string\n  /// Base64 of the encrypted blob — server stores it on chain at the\n  /// credit-pool blob PDA `[b\"ppv2-enc\", pool, credit_commitment]`.\n  encrypted_note_b64: string\n}\n\nexport type PreparedClaimCreditV3Relay = {\n  payload: ClaimCreditV3RelayPayload\n  /// Source note's nullifier (for indexer correlation + dedup).\n  sourceNullifierField: bigint\n  /// Credit-pool commitment (for the recipient's cashout flow).\n  creditCommitmentField: bigint\n  /// Plaintext credit-pool note details (the recipient gets these\n  /// by decrypting `encrypted_note_b64` with their viewing_sk — we\n  /// expose them here for testing + side-channel persistence).\n  creditNote: {\n    value: bigint\n    blinding: Uint8Array\n    ownerPk: Uint8Array\n  }\n  proveMs: number\n}\n\nexport async function prepareClaimCreditV3RelayPayload(args: {\n  cfg: PrivatePoolV2Config\n  /// Source pool note being spent.\n  note: {\n    value: bigint\n    blinding: bigint\n    memo: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  spendingSkField: bigint\n  nullifierSkField: bigint\n  /// Recipient's spending_pk field (becomes ownerPk in the new credit\n  /// commitment). From payment_address or self-derivation.\n  recipientSpendingPkField: bigint\n  /// Recipient's viewing_pk (32B) for encrypting the note.\n  recipientViewingPk: Uint8Array\n  /// Recipient's wallet pubkey — binds the source proof's publicAddress.\n  /// Same wallet must show up at cashout time to satisfy ATA owner check.\n  recipient: PublicKey\n  allLeaves: bigint[]\n  poolRoot: bigint\n  aspRoot: bigint\n  aspPath: { pathElements: bigint[]; pathIndices: number[] }\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedClaimCreditV3Relay> {\n  // 1. Build source merkle path (same as v2 claim_credit).\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  )\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareClaimCreditV3RelayPayload: reconstructed pool root ${poolPath.root.toString(16)} != supplied ${args.poolRoot.toString(16)} — stale leaves`,\n    )\n  }\n\n  // 2. Recipient binding for source proof's publicAddress.\n  const publicAddressField = await poseidonAddrHash(args.recipient)\n\n  // 3. Source-side Withdraw proof witness (identical shape to atomic\n  // withdraw + v2 claim_credit — no extra fields).\n  const p = await getPoseidon()\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const sourceWitness = {\n    value: args.note.value.toString(10),\n    ownerPk: ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness\n  const { proofBytes: sourceProofBytes, proveMs } = await proveWithdraw(sourceWitness)\n\n  // 4. Source-side nullifier — same Poseidon2(commitment, nullifierSk).\n  const sourceNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n\n  // 5. Build the FRESH credit-pool note.\n  //   value = recipient amount (after relayer fee carved off)\n  //   ownerPk = recipient's spending_pk_field — recipient proves\n  //             knowledge at cashout via SpendingPkFromSk(spendingSk)\n  //   blinding = fresh random, modular-reduced into BN254 field\n  //   memo = 0 (V3 doesn't use the memo slot)\n  const creditValue = args.publicValue - args.fee\n  if (creditValue <= 0n) {\n    throw new Error('prepareClaimCreditV3RelayPayload: creditValue <= 0 (publicValue must exceed fee)')\n  }\n  const randomFieldBytes32 = (): Uint8Array => {\n    const raw = crypto.getRandomValues(new Uint8Array(32))\n    let acc = 0n\n    for (const b of raw) acc = (acc << 8n) + BigInt(b)\n    const reduced = ((acc % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    return new Uint8Array(fieldToBytes32BE(reduced))\n  }\n  const blindingBytes = randomFieldBytes32()\n  let blindingField = 0n\n  for (const b of blindingBytes) blindingField = (blindingField << 8n) + BigInt(b)\n  blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const recipientOwnerPkBytes = fieldToBytes32BE(args.recipientSpendingPkField)\n\n  const creditCommitmentField = await deriveCommitment({\n    value: creditValue,\n    ownerPk: args.recipientSpendingPkField,\n    blinding: blindingField,\n    memo: 0n,\n  })\n  const commitmentBytes = fieldToBytes32BE(creditCommitmentField)\n\n  // 6. Encrypt the fresh credit-pool note for the recipient.\n  // Layout matches the main-pool legacy ADR-003 path: 96B ciphertext\n  // (no memo, no stealth). On-chain handler accepts this via the\n  // existing is_valid_ciphertext_len check.\n  const noteForEnc: DecryptedNote = {\n    value: creditValue,\n    blinding: blindingBytes,\n    ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    memo: null,\n    stealthNonce: null,\n    layout: 'legacy',\n  }\n  const encryptedBlob = encryptNote({\n    note: noteForEnc,\n    recipientViewingPk: args.recipientViewingPk,\n    commitment: new Uint8Array(commitmentBytes),\n  })\n  // Server expects `ephemeral_pk || ciphertext` packed contiguously\n  // for the on-chain `encrypted_note` arg (matches main-pool blob layout).\n  const encryptedNoteFull = new Uint8Array(32 + encryptedBlob.ciphertext.length)\n  encryptedNoteFull.set(encryptedBlob.ephemeralPk, 0)\n  encryptedNoteFull.set(encryptedBlob.ciphertext, 32)\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: ClaimCreditV3RelayPayload = {\n    network: args.network,\n    source_nullifier_hex: toHex32(sourceNullifierField),\n    source_root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    public_address_hex: toHex32(publicAddressField),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    source_proof_b64: Buffer.from(sourceProofBytes).toString('base64'),\n    credit_commitment_hex: toHex32(creditCommitmentField),\n    encrypted_note_b64: Buffer.from(encryptedNoteFull).toString('base64'),\n  }\n\n  return {\n    payload,\n    sourceNullifierField,\n    creditCommitmentField,\n    creditNote: {\n      value: creditValue,\n      blinding: blindingBytes,\n      ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    },\n    proveMs,\n  }\n}\n\nexport async function submitClaimCreditV3ViaRelay(args: {\n  apiBaseUrl: string\n  payload: ClaimCreditV3RelayPayload\n}): Promise<{ signature: string; credit_commitment_hex: string; credit_root_hex: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/claim-credit-v3',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.payload.network)\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  if (!res.ok) {\n    const body = await res.json().catch(() => ({}))\n    throw new Error(\n      `relay/claim-credit-v3 rejected (HTTP ${res.status}): ${(body as any).error ?? 'unknown'} — ${(body as any).detail ?? ''}`,\n    )\n  }\n  return res.json() as Promise<{ signature: string; credit_commitment_hex: string; credit_root_hex: string }>\n}\n\nexport type CashOutV3RelayPayload = {\n  network: string\n  credit_nullifier_hex: string\n  credit_pool_root_hex: string\n  public_address_hex: string\n  public_value: string\n  fee: string\n  recipient: string\n  recipient_ata: string\n  proof_b64: string\n}\n\nexport type PreparedCashOutV3Relay = {\n  payload: CashOutV3RelayPayload\n  creditNullifierField: bigint\n  proveMs: number\n}\n\nexport async function prepareCashOutV3RelayPayload(args: {\n  cfg: PrivatePoolV2Config\n  /// Credit-pool note being cashed out — recipient decrypted it from\n  /// the encrypted blob on the credit-pool tree leaf.\n  note: {\n    value: bigint\n    blinding: bigint\n    ownerPkField: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  /// Recipient's keys (proves knowledge of spendingSk that matches\n  /// note.ownerPk, and derives the credit-pool nullifier).\n  spendingSkField: bigint\n  nullifierSkField: bigint\n  /// Recipient SPL accounts.\n  recipient: PublicKey\n  recipientAta: PublicKey\n  /// Snapshot of all credit-pool leaves for merkle-path reconstruction.\n  /// Server's `/credit-pool/leaves` endpoint returns this.\n  allCreditLeaves: bigint[]\n  creditPoolRoot: bigint\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedCashOutV3Relay> {\n  // 1. Build credit-pool merkle path.\n  const creditPath = await buildMerkleTreeFromLeaves(\n    args.allCreditLeaves,\n    args.note.leafIndex,\n    24, // CREDIT_POOL_TREE_DEPTH from ADR-012\n  )\n  if (creditPath.root !== args.creditPoolRoot) {\n    throw new Error(\n      `prepareCashOutV3RelayPayload: reconstructed credit root ${creditPath.root.toString(16)} != supplied ${args.creditPoolRoot.toString(16)} — stale credit leaves`,\n    )\n  }\n\n  // 2. Recipient binding.\n  const publicAddressField = await poseidonAddrHash(args.recipient)\n\n  // 3. CashoutProof witness — mirrors Withdraw witness shape minus ASP.\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: args.note.ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: '0',\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    creditPoolPathElements: creditPath.pathElements.map((e) => e.toString(10)),\n    creditPoolPathIndices: creditPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: publicAddressField.toString(10),\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').CashoutProofWitness\n  const { proofBytes, proveMs } = await proveCashoutProof(witness)\n\n  // 4. Credit-pool nullifier.\n  const creditNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: CashOutV3RelayPayload = {\n    network: args.network,\n    credit_nullifier_hex: toHex32(creditNullifierField),\n    credit_pool_root_hex: toHex32(args.creditPoolRoot),\n    public_address_hex: toHex32(publicAddressField),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    recipient: args.recipient.toBase58(),\n    recipient_ata: args.recipientAta.toBase58(),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return { payload, creditNullifierField, proveMs }\n}\n\n// ── F2 — Bearer-mode park + cashout (private payment codes) ────────────\n//\n// Bearer mode parks a credit-pool leaf without binding it to a specific\n// recipient wallet. The leaf's `owner_pk` is derived from a one-time\n// `code_seed`; whoever ends up with the seed can derive the matching\n// `spending_sk_field` and cash out to any wallet they choose. The\n// `public_address` field in the proof witness is set to 0, signalling\n// the on-chain handler to skip the recipient-binding check (per the\n// ADR-012 addendum + Anchor change deployed alongside this code).\n//\n// Why this is safe: the leaf's commitment still binds value + owner_pk\n// + blinding. The proof still verifies the prover knew the\n// spending_sk that hashes to owner_pk. The credit_nullifier still\n// prevents double-spend. The only thing different from a normal park\n// is that any wallet — not a specific bound one — can be the\n// `recipient` on the cashout side.\n//\n// This is the primitive behind F2 (private payment codes) and F3\n// (public tip jars) in CREDIT_POOL_FEATURE_ROADMAP.md.\n\n/// Park a pool note into a code-bound credit-pool leaf. Returns the\n/// prepared relay payload PLUS the `codeKeys` (so the caller can\n/// encode `codeKeys.codeSeed` into a redemption URL or QR).\n///\n/// The caller should NOT show `codeKeys.codeSeed` anywhere except\n/// directly to the issuer — once the seed is shared, anyone with it\n/// can cash out the leaf. Treat it like the seed phrase of a hot\n/// wallet: high blast radius, single-purpose, ephemeral.\nexport async function prepareClaimCreditV3RelayPayloadAsBearer(args: {\n  cfg: PrivatePoolV2Config\n  /// Source pool note being spent by the issuer.\n  note: {\n    value: bigint\n    blinding: bigint\n    memo: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  /// Issuer's keys — proves ownership of the source note.\n  spendingSkField: bigint\n  nullifierSkField: bigint\n  /// Either pass an externally-generated `codeKeys` (when the caller\n  /// wants to control the seed lifecycle / persist it for cancel) OR\n  /// pass `codeSeed` and let us derive. One must be set.\n  codeKeys?: CodeKeyMaterial\n  codeSeed?: Uint8Array\n  allLeaves: bigint[]\n  poolRoot: bigint\n  aspRoot: bigint\n  aspPath: { pathElements: bigint[]; pathIndices: number[] }\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedClaimCreditV3Relay & { codeKeys: CodeKeyMaterial }> {\n  // 0. Resolve code keys — caller-supplied wins; fall back to deriving\n  // from a passed-in seed. Throws if neither is set.\n  let codeKeys: CodeKeyMaterial\n  if (args.codeKeys) {\n    codeKeys = args.codeKeys\n  } else if (args.codeSeed) {\n    codeKeys = await deriveCodeKeys(args.codeSeed)\n  } else {\n    throw new Error(\n      'prepareClaimCreditV3RelayPayloadAsBearer: must pass codeKeys OR codeSeed',\n    )\n  }\n\n  // 1. Build source merkle path — identical to the non-bearer path.\n  const poolPath = await buildMerkleTreeFromLeaves(\n    args.allLeaves,\n    args.note.leafIndex,\n    args.cfg.constants.poolDepth,\n  )\n  if (poolPath.root !== args.poolRoot) {\n    throw new Error(\n      `prepareClaimCreditV3RelayPayloadAsBearer: reconstructed pool root ${poolPath.root.toString(16)} != supplied ${args.poolRoot.toString(16)} — stale leaves`,\n    )\n  }\n\n  // 2. Source-side Withdraw proof witness. Crucially, `publicAddress`\n  // is set to 0 instead of `poseidon_addr_hash(recipient)` — that's\n  // what flags this leaf as bearer-mode for the on-chain cashout.\n  const sourceWitness = {\n    value: args.note.value.toString(10),\n    ownerPk: ((await getPoseidon())\n      ? '0' // placeholder; reassigned below\n      : '0'),\n    blinding: args.note.blinding.toString(10),\n    memo: args.note.memo.toString(10),\n    spendingSk: args.spendingSkField.toString(10),\n    nullifierSk: args.nullifierSkField.toString(10),\n    poolPathElements: poolPath.pathElements.map((e) => e.toString(10)),\n    poolPathIndices: poolPath.pathIndices,\n    aspPathElements: args.aspPath.pathElements.map((e) => e.toString(10)),\n    aspPathIndices: args.aspPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').WithdrawWitness\n  // Recompute ownerPk properly (the placeholder above keeps TS happy\n  // during literal init; the real value is the issuer's spendingPk).\n  const p = await getPoseidon()\n  const ownerPkField = ((BigInt(p.F.toString(p([args.spendingSkField]))) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  ;(sourceWitness as unknown as { ownerPk: string }).ownerPk = ownerPkField.toString(10)\n  const { proofBytes: sourceProofBytes, proveMs } = await proveWithdraw(sourceWitness)\n\n  // 3. Source-side nullifier.\n  const sourceNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.nullifierSkField,\n  })\n\n  // 4. Build the fresh credit-pool leaf — same as cross-wallet park,\n  // except the recipient's `spending_pk_field` is the CODE-derived\n  // public, not a wallet-derived one. Whoever has `codeSeed` can later\n  // derive the matching `spending_sk_field` to satisfy ownership.\n  const creditValue = args.publicValue - args.fee\n  if (creditValue <= 0n) {\n    throw new Error('prepareClaimCreditV3RelayPayloadAsBearer: creditValue <= 0 (publicValue must exceed fee)')\n  }\n  const randomFieldBytes32 = (): Uint8Array => {\n    const raw = crypto.getRandomValues(new Uint8Array(32))\n    let acc = 0n\n    for (const b of raw) acc = (acc << 8n) + BigInt(b)\n    const reduced = ((acc % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    return new Uint8Array(fieldToBytes32BE(reduced))\n  }\n  const blindingBytes = randomFieldBytes32()\n  let blindingField = 0n\n  for (const b of blindingBytes) blindingField = (blindingField << 8n) + BigInt(b)\n  blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  const recipientOwnerPkBytes = fieldToBytes32BE(codeKeys.spendingPkField)\n\n  const creditCommitmentField = await deriveCommitment({\n    value: creditValue,\n    ownerPk: codeKeys.spendingPkField,\n    blinding: blindingField,\n    memo: 0n,\n  })\n  const commitmentBytes = fieldToBytes32BE(creditCommitmentField)\n\n  // 5. Encrypt the credit-pool note for the code's viewing key. The\n  // redeemer derives the same viewing_sk from the seed and decrypts.\n  const noteForEnc: DecryptedNote = {\n    value: creditValue,\n    blinding: blindingBytes,\n    ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    memo: null,\n    stealthNonce: null,\n    layout: 'legacy',\n  }\n  const encryptedBlob = encryptNote({\n    note: noteForEnc,\n    recipientViewingPk: codeKeys.viewingPk,\n    commitment: new Uint8Array(commitmentBytes),\n  })\n  const encryptedNoteFull = new Uint8Array(32 + encryptedBlob.ciphertext.length)\n  encryptedNoteFull.set(encryptedBlob.ephemeralPk, 0)\n  encryptedNoteFull.set(encryptedBlob.ciphertext, 32)\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: ClaimCreditV3RelayPayload = {\n    network: args.network,\n    source_nullifier_hex: toHex32(sourceNullifierField),\n    source_root_hex: toHex32(args.poolRoot),\n    asp_root_hex: toHex32(args.aspRoot),\n    // Bearer mode signal — on-chain cash_out_v3 skips the recipient\n    // binding check when this is all-zero (see Anchor addendum).\n    public_address_hex: toHex32(0n),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    source_proof_b64: Buffer.from(sourceProofBytes).toString('base64'),\n    credit_commitment_hex: toHex32(creditCommitmentField),\n    encrypted_note_b64: Buffer.from(encryptedNoteFull).toString('base64'),\n  }\n\n  return {\n    payload,\n    sourceNullifierField,\n    creditCommitmentField,\n    creditNote: {\n      value: creditValue,\n      blinding: blindingBytes,\n      ownerPk: new Uint8Array(recipientOwnerPkBytes),\n    },\n    proveMs,\n    codeKeys,\n  }\n}\n\n/// Cash out a code-bound credit-pool leaf to an arbitrary wallet.\n/// `args.note` must come from `findCreditPoolLeafByCommitment` (or\n/// equivalent — the caller has to know value/blinding/ownerPkField\n/// of the leaf, typically obtained by decrypting the on-chain blob\n/// with `codeKeys.viewingSk`).\n///\n/// The proof uses `publicAddress = 0`, matching how the leaf was\n/// parked. On-chain cash_out_v3 sees the zero binding and skips the\n/// `Poseidon(recipient) == publicAddress` check, allowing any wallet\n/// to be the destination.\nexport async function prepareCashOutV3RelayPayloadAsBearer(args: {\n  cfg: PrivatePoolV2Config\n  /// Credit-pool leaf being cashed out. Decrypted client-side from\n  /// the on-chain blob using the seed-derived viewing_sk.\n  note: {\n    value: bigint\n    blinding: bigint\n    ownerPkField: bigint\n    commitmentField: bigint\n    leafIndex: number\n  }\n  /// Code keys — derive these once from the seed and reuse for\n  /// discovery + cashout. We need `spendingSkField` (for the proof)\n  /// and `nullifierSkField` (for the credit_nullifier).\n  codeKeys: CodeKeyMaterial\n  /// Destination wallet — can be ANY wallet, not bound to anything\n  /// committed at park time. SPL transfer lands at this wallet's ATA.\n  recipient: PublicKey\n  recipientAta: PublicKey\n  /// Snapshot of all credit-pool leaves for the merkle-path proof.\n  allCreditLeaves: bigint[]\n  creditPoolRoot: bigint\n  publicValue: bigint\n  fee: bigint\n  network: string\n}): Promise<PreparedCashOutV3Relay> {\n  // 1. Reconstruct the credit-pool merkle path.\n  const creditPath = await buildMerkleTreeFromLeaves(\n    args.allCreditLeaves,\n    args.note.leafIndex,\n    24, // CREDIT_POOL_TREE_DEPTH from ADR-012\n  )\n  if (creditPath.root !== args.creditPoolRoot) {\n    throw new Error(\n      `prepareCashOutV3RelayPayloadAsBearer: reconstructed credit root ${creditPath.root.toString(16)} != supplied ${args.creditPoolRoot.toString(16)} — stale credit leaves`,\n    )\n  }\n\n  // 2. Cashout proof witness — same shape as the non-bearer path,\n  // EXCEPT publicAddress = 0 (mirrors the bearer-mode park witness).\n  const witness = {\n    value: args.note.value.toString(10),\n    ownerPk: args.note.ownerPkField.toString(10),\n    blinding: args.note.blinding.toString(10),\n    memo: '0',\n    spendingSk: args.codeKeys.spendingSkField.toString(10),\n    nullifierSk: args.codeKeys.nullifierSkField.toString(10),\n    creditPoolPathElements: creditPath.pathElements.map((e) => e.toString(10)),\n    creditPoolPathIndices: creditPath.pathIndices,\n    publicValue: args.publicValue.toString(10),\n    publicAddress: '0',\n    fee: args.fee.toString(10),\n  } as unknown as import('./types').CashoutProofWitness\n  const { proofBytes, proveMs } = await proveCashoutProof(witness)\n\n  // 3. Credit-pool nullifier (Poseidon2(commitment, nullifier_sk),\n  // where nullifier_sk is the CODE's, not a wallet's).\n  const creditNullifierField = await deriveNullifierAsync({\n    commitment: args.note.commitmentField,\n    nullifierSk: args.codeKeys.nullifierSkField,\n  })\n\n  const toHex32 = (n: bigint) => '0x' + n.toString(16).padStart(64, '0')\n  const payload: CashOutV3RelayPayload = {\n    network: args.network,\n    credit_nullifier_hex: toHex32(creditNullifierField),\n    credit_pool_root_hex: toHex32(args.creditPoolRoot),\n    // Bearer mode — zero public address. On-chain handler skips\n    // the recipient binding check; `args.recipient` is whatever\n    // wallet the redeemer wants paid out to.\n    public_address_hex: toHex32(0n),\n    public_value: args.publicValue.toString(10),\n    fee: args.fee.toString(10),\n    recipient: args.recipient.toBase58(),\n    recipient_ata: args.recipientAta.toBase58(),\n    proof_b64: Buffer.from(proofBytes).toString('base64'),\n  }\n\n  return { payload, creditNullifierField, proveMs }\n}\n\n/// Find a single credit-pool leaf by its commitment hex. Used at\n/// redeem time: the issuer's URL carries `commitment=…` alongside\n/// `seed=…`, and we look the leaf up directly rather than trial-\n/// decrypting every leaf in the tree.\n///\n/// Returns the leaf's plaintext fields (value, blinding, ownerPk,\n/// leafIndex) after decrypting the on-chain blob with the code-\n/// derived viewing_sk. Returns null if the leaf doesn't exist or\n/// the blob can't be decrypted with the supplied viewing key (= the\n/// seed doesn't actually correspond to this leaf).\nexport async function fetchCreditPoolLeafForBearerRedeem(args: {\n  apiBaseUrl: string\n  network: string\n  commitmentHex: string\n  codeKeys: CodeKeyMaterial\n}): Promise<{\n  value: bigint\n  blinding: bigint\n  ownerPkField: bigint\n  commitmentField: bigint\n  leafIndex: number\n  alreadySpent: boolean\n} | null> {\n  const target = args.commitmentHex.toLowerCase().startsWith('0x')\n    ? args.commitmentHex.toLowerCase()\n    : '0x' + args.commitmentHex.toLowerCase()\n\n  // 1. Pull the credit-pool leaves snapshot + check spent state for\n  // each candidate nullifier we can derive from this seed.\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const r = await fetch(url.toString(), { cache: 'no-store' })\n  if (!r.ok) return null\n  const body = (await r.json()) as {\n    leaves: Array<{\n      commitment_hex: string\n      leaf_index: number\n      // The /credit-pool/leaves response joins blob fields inline\n      // (not nested under a sub-object). See\n      // server/src/routes/private-pool-v2.js:3084-3100.\n      ephemeral_pk_hex: string | null\n      ciphertext_b64: string | null\n      ciphertext_len: number | null\n    }>\n  }\n  const match = body.leaves.find(\n    (l) => l.commitment_hex.toLowerCase() === target,\n  )\n  if (!match) return null\n  if (!match.ephemeral_pk_hex || !match.ciphertext_b64) return null\n\n  // 2. Decrypt the blob with the code's viewing_sk.\n  const ephPk = Uint8Array.from(Buffer.from(match.ephemeral_pk_hex.replace(/^0x/, ''), 'hex'))\n  let ciphertext = Uint8Array.from(Buffer.from(match.ciphertext_b64, 'base64'))\n  // V3 blobs may be stored as `ephPk || ciphertext` (128B / 160B); strip\n  // the prefix if it matches the blob's ephemeral pk.\n  if ((ciphertext.length === 128 || ciphertext.length === 160) && ephPk.length === 32) {\n    let matchesPrefix = true\n    for (let i = 0; i < 32; i++) {\n      if (ciphertext[i] !== ephPk[i]) { matchesPrefix = false; break }\n    }\n    if (matchesPrefix) ciphertext = ciphertext.slice(32)\n  }\n  const decrypted = tryDecryptNote({\n    blob: {\n      ephemeralPk: ephPk,\n      ciphertext,\n      commitment: Uint8Array.from(Buffer.from(target.replace(/^0x/, ''), 'hex')),\n    },\n    viewingSk: args.codeKeys.viewingSk,\n  })\n  if (!decrypted) return null\n\n  // 3. Convert blinding bytes → bigint field. Same reduction the\n  // park flow used when generating the blinding.\n  let blindingField = 0n\n  for (const b of decrypted.blinding) blindingField = (blindingField << 8n) + BigInt(b)\n  blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  let ownerPkField = 0n\n  for (const b of decrypted.ownerPk) ownerPkField = (ownerPkField << 8n) + BigInt(b)\n  ownerPkField = ((ownerPkField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n\n  // 4. Spent-state check. Compute the credit_nullifier using the\n  // code's nullifier_sk and ask the indexer if it's already used.\n  const commitmentField = BigInt(target)\n  const credNull = await deriveNullifierAsync({\n    commitment: commitmentField,\n    nullifierSk: args.codeKeys.nullifierSkField,\n  })\n  let alreadySpent = false\n  try {\n    const nullUrl = new URL(\n      'api/private-pool/v2/nullifiers/all',\n      args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n    )\n    nullUrl.searchParams.set('network', args.network)\n    const nr = await fetch(nullUrl.toString(), { cache: 'no-store' })\n    if (nr.ok) {\n      const nb = (await nr.json()) as { nullifiers?: string[] }\n      const credNullHex = '0x' + credNull.toString(16).padStart(64, '0')\n      alreadySpent = (nb.nullifiers ?? []).some(\n        (n) => n.toLowerCase() === credNullHex.toLowerCase(),\n      )\n    }\n  } catch {\n    // Best-effort. If we can't reach the indexer, leave alreadySpent\n    // = false; the on-chain cash_out_v3 will fail loudly if it's\n    // actually already spent (duplicate nullifier account).\n  }\n\n  return {\n    value: decrypted.value,\n    blinding: blindingField,\n    ownerPkField,\n    commitmentField,\n    leafIndex: match.leaf_index,\n    alreadySpent,\n  }\n}\n\n/// Code-only discovery: given just the code-derived keys (no\n/// commitment hint), scan every leaf in the credit-pool tree and\n/// trial-decrypt its blob with `codeKeys.viewingSk`. The first leaf\n/// that decrypts successfully — and whose `owner_pk` matches our\n/// derived `spendingPkField` — is \"ours\" by construction (the leaf's\n/// commitment binds to `Poseidon4(value, owner_pk, blinding, memo)`,\n/// and we put exactly this `owner_pk` in at park time).\n///\n/// Used by the redeem page when the URL fragment carries only `code=`\n/// without `commitment=`, or when the user pastes a raw code into the\n/// text-input form. Same end result as `fetchCreditPoolLeafForBearerRedeem`,\n/// just slower on big trees (scan cost = N × ChaCha20-Poly1305 attempt,\n/// ≈ 1–2 ms per leaf in practice).\n///\n/// Returns `null` when no leaf decrypts — meaning either the code\n/// doesn't belong to this network's credit pool, or the park hasn't\n/// confirmed yet.\nexport async function discoverCreditPoolLeafByCodeKeys(args: {\n  apiBaseUrl: string\n  network: string\n  codeKeys: CodeKeyMaterial\n}): Promise<{\n  value: bigint\n  blinding: bigint\n  ownerPkField: bigint\n  commitmentField: bigint\n  commitmentHex: string\n  leafIndex: number\n  alreadySpent: boolean\n} | null> {\n  // 1. Pull the credit-pool leaves + blobs in one round-trip.\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const r = await fetch(url.toString(), { cache: 'no-store' })\n  if (!r.ok) return null\n  const body = (await r.json()) as {\n    leaves: Array<{\n      commitment_hex: string\n      leaf_index: number\n      ephemeral_pk_hex: string | null\n      ciphertext_b64: string | null\n      ciphertext_len: number | null\n    }>\n  }\n\n  // 2. Pre-compute the spent-nullifier set so we can flag a redeemed\n  // leaf in a single response, without a per-row spent check after\n  // we find the match.\n  let spentSet: Set<string> = new Set()\n  try {\n    const nullUrl = new URL(\n      'api/private-pool/v2/nullifiers/all',\n      args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n    )\n    nullUrl.searchParams.set('network', args.network)\n    const nr = await fetch(nullUrl.toString(), { cache: 'no-store' })\n    if (nr.ok) {\n      const nj = (await nr.json()) as { nullifiers?: string[] }\n      spentSet = new Set((nj.nullifiers ?? []).map((n) => n.toLowerCase()))\n    }\n  } catch {\n    // Best-effort: if we can't fetch nullifiers, the cashout tx\n    // itself will fail loudly on duplicate-nullifier; the UI just\n    // won't show the \"already spent\" hint upfront.\n  }\n\n  // 3. Walk every leaf. Trial-decrypt is cheap; we can afford the\n  // full scan. First hit wins — by construction a code can only\n  // belong to one leaf (the issuer's claim_credit_v3 created\n  // exactly one).\n  for (const leaf of body.leaves) {\n    if (!leaf.ephemeral_pk_hex || !leaf.ciphertext_b64) continue\n\n    const ephPk = Uint8Array.from(\n      Buffer.from(leaf.ephemeral_pk_hex.replace(/^0x/, ''), 'hex'),\n    )\n    let ciphertext = Uint8Array.from(Buffer.from(leaf.ciphertext_b64, 'base64'))\n    // V3 blobs may be stored as `ephPk || ciphertext` (128B / 160B);\n    // strip the prefix if it matches the blob's ephemeral pk. Same\n    // normalisation `fetchCreditPoolLeafForBearerRedeem` applies.\n    if ((ciphertext.length === 128 || ciphertext.length === 160) && ephPk.length === 32) {\n      let matchesPrefix = true\n      for (let i = 0; i < 32; i++) {\n        if (ciphertext[i] !== ephPk[i]) { matchesPrefix = false; break }\n      }\n      if (matchesPrefix) ciphertext = ciphertext.slice(32)\n    }\n\n    const target = leaf.commitment_hex.toLowerCase().startsWith('0x')\n      ? leaf.commitment_hex.toLowerCase()\n      : '0x' + leaf.commitment_hex.toLowerCase()\n\n    const decrypted = tryDecryptNote({\n      blob: {\n        ephemeralPk: ephPk,\n        ciphertext,\n        commitment: Uint8Array.from(Buffer.from(target.replace(/^0x/, ''), 'hex')),\n      },\n      viewingSk: args.codeKeys.viewingSk,\n    })\n    if (!decrypted) continue\n\n    // Decrypted — but a SUCCESSFUL trial-decrypt only means the blob\n    // was encrypted with our viewing key. We still need to check\n    // owner_pk binds to our spending_pk_field, otherwise the code\n    // belongs to a different recipient who happens to share viewing\n    // keys (impossible in practice — both derive deterministically\n    // from the same seed — but worth asserting for safety).\n    let ownerPkField = 0n\n    for (const b of decrypted.ownerPk) ownerPkField = (ownerPkField << 8n) + BigInt(b)\n    ownerPkField = ((ownerPkField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    if (ownerPkField !== args.codeKeys.spendingPkField) continue\n\n    // Convert blinding bytes → field. Same reduction the park flow\n    // used when generating the blinding.\n    let blindingField = 0n\n    for (const b of decrypted.blinding) blindingField = (blindingField << 8n) + BigInt(b)\n    blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n\n    const commitmentField = BigInt(target)\n\n    // Spent-check via the pre-fetched nullifier set.\n    const credNull = await deriveNullifierAsync({\n      commitment: commitmentField,\n      nullifierSk: args.codeKeys.nullifierSkField,\n    })\n    const credNullHex = '0x' + credNull.toString(16).padStart(64, '0')\n    const alreadySpent = spentSet.has(credNullHex.toLowerCase())\n\n    return {\n      value: decrypted.value,\n      blinding: blindingField,\n      ownerPkField,\n      commitmentField,\n      commitmentHex: target,\n      leafIndex: leaf.leaf_index,\n      alreadySpent,\n    }\n  }\n  return null\n}\n\nexport async function submitCashOutV3ViaRelay(args: {\n  apiBaseUrl: string\n  payload: CashOutV3RelayPayload\n}): Promise<{ signature: string }> {\n  const url = new URL(\n    'api/private-pool/v2/relay/cash-out-v3',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.payload.network)\n  const res = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify(args.payload),\n  })\n  if (!res.ok) {\n    const body = await res.json().catch(() => ({}))\n    throw new Error(\n      `relay/cash-out-v3 rejected (HTTP ${res.status}): ${(body as any).error ?? 'unknown'} — ${(body as any).detail ?? ''}`,\n    )\n  }\n  return res.json() as Promise<{ signature: string }>\n}\n\n/// Fetch the current credit-pool merkle leaves for path reconstruction.\n/// Returns leaves indexed in insertion order (leaf_index = array index).\nexport async function fetchCreditPoolLeaves(args: {\n  apiBaseUrl: string\n  network: string\n}): Promise<{ commitmentField: bigint; leafIndex: number }[]> {\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const res = await fetch(url.toString())\n  if (!res.ok) {\n    throw new Error(`fetchCreditPoolLeaves failed: HTTP ${res.status}`)\n  }\n  const body = (await res.json()) as {\n    leaves: { commitment_hex: string; leaf_index: number }[]\n  }\n  return body.leaves.map((l) => ({\n    commitmentField: BigInt(l.commitment_hex),\n    leafIndex: l.leaf_index,\n  }))\n}\n\n/// Fetch the latest credit-pool merkle root (caller uses it as the\n/// `creditPoolRoot` witness input). Server returns the on-chain\n/// `latest_root` from the CreditPoolStateAccount.\nexport async function fetchCreditPoolLatestRoot(args: {\n  apiBaseUrl: string\n  network: string\n}): Promise<bigint> {\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/root',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const res = await fetch(url.toString())\n  if (!res.ok) {\n    throw new Error(`fetchCreditPoolLatestRoot failed: HTTP ${res.status}`)\n  }\n  const body = (await res.json()) as { root_hex: string }\n  return BigInt(body.root_hex)\n}\n\nexport type V3CashableCreditPoolNote = {\n  /// recipient-amount (post-fee) stored in the note\n  value: bigint\n  blindingField: bigint\n  /// owner_pk field — recipient's spending_pk_field, embedded in commitment\n  ownerPkField: bigint\n  commitmentField: bigint\n  commitmentHex: string\n  leafIndex: number\n  claimedAtSlot: number\n  /// Poseidon2(commitment, nullifier_sk) — for spent-set membership check\n  nullifierField: bigint\n  spent: boolean\n}\n\n/// Discover credit-pool notes this device can cash out. Pulls\n/// `/credit-pool/leaves` (which now joins encrypted blobs server-side),\n/// trial-decrypts each blob with `viewingSk`, then filters to entries\n/// where the decrypted `ownerPk` matches the caller's `spendingPkField`\n/// — that's how we tell \"this note is mine\" without on-chain marker.\n///\n/// Spent check uses `/nullifiers/all` (same shared spent-set as main\n/// pool — every credit_credit_v3 / cash_out_v3 nullifier is in there).\nexport async function discoverCashableCreditPoolNotesV3(args: {\n  apiBaseUrl: string\n  network: string\n  viewingSk: Uint8Array\n  /// Caller's spending_pk_field (Poseidon-1 of spending_sk).\n  spendingPkField: bigint\n  nullifierSkField: bigint\n}): Promise<V3CashableCreditPoolNote[]> {\n  // 1. Pull leaves + inline blobs in one round trip.\n  const url = new URL(\n    'api/private-pool/v2/credit-pool/leaves',\n    args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n  )\n  url.searchParams.set('network', args.network)\n  const res = await fetch(url.toString())\n  if (!res.ok) throw new Error(`discoverCashableCreditPoolNotesV3: HTTP ${res.status}`)\n  const body = (await res.json()) as {\n    warming_up: boolean\n    initialized: boolean\n    leaves: Array<{\n      commitment_hex: string\n      leaf_index: number\n      claimed_at_slot: number\n      ephemeral_pk_hex: string | null\n      ciphertext_b64: string | null\n    }>\n  }\n  if (body.warming_up || !body.initialized) return []\n\n  // 2. Trial-decrypt each blob; filter to those that decode AND whose\n  // ownerPk matches our spendingPkField.\n  const candidates: V3CashableCreditPoolNote[] = []\n  for (const leaf of body.leaves) {\n    if (!leaf.ephemeral_pk_hex || !leaf.ciphertext_b64) {\n      console.warn('[discoverV3] leaf skipped: no blob', { leaf_index: leaf.leaf_index, commit: leaf.commitment_hex })\n      continue\n    }\n    const ephHex = leaf.ephemeral_pk_hex.startsWith('0x') ? leaf.ephemeral_pk_hex.slice(2) : leaf.ephemeral_pk_hex\n    const commitHex = leaf.commitment_hex.startsWith('0x') ? leaf.commitment_hex.slice(2) : leaf.commitment_hex\n    const ephPk = Uint8Array.from(Buffer.from(ephHex, 'hex'))\n    const commitment = Uint8Array.from(Buffer.from(commitHex, 'hex'))\n    let ciphertext = Uint8Array.from(Buffer.from(leaf.ciphertext_b64, 'base64'))\n    // V3 quirk: claim_credit_v3 handler stores `encrypted_note` (the full\n    // ephPk || ciphertext package, 128B) into the blob's `ciphertext`\n    // field — unlike deposit_note which gets ephPk as a separate\n    // instruction arg and stores only the pure ciphertext (96B).\n    // The frontend tryDecryptNote expects pure ciphertext, so we\n    // strip the 32-byte ephPk prefix here when present.\n    // Detection: ciphertext_len in (CIPHERTEXT_NO_MEMO + 32,\n    // CIPHERTEXT_WITH_MEMO + 32) and the first 32 bytes match the\n    // separately-stored ephPk field.\n    if ((ciphertext.length === 128 || ciphertext.length === 160) && ephPk.length === 32) {\n      const prefix = ciphertext.slice(0, 32)\n      let matches = true\n      for (let i = 0; i < 32; i++) {\n        if (prefix[i] !== ephPk[i]) { matches = false; break }\n      }\n      if (matches) {\n        ciphertext = ciphertext.slice(32)\n      }\n    }\n    const note = tryDecryptNote({\n      blob: { ephemeralPk: ephPk, commitment, ciphertext },\n      viewingSk: args.viewingSk,\n    })\n    if (!note) {\n      console.warn('[discoverV3] tryDecryptNote returned null', {\n        leaf_index: leaf.leaf_index,\n        commit: leaf.commitment_hex.slice(0, 18),\n        ciphertext_len: ciphertext.length,\n      })\n      continue\n    }\n    // Filter: note.ownerPk must match caller's spending_pk. We compare\n    // the bigint forms (decoded from BE bytes) because the field\n    // element is what the circuit + commitment recompute use.\n    let ownerPkField = 0n\n    for (const b of note.ownerPk) ownerPkField = (ownerPkField << 8n) + BigInt(b)\n    ownerPkField = ((ownerPkField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    if (ownerPkField !== args.spendingPkField) {\n      console.warn('[discoverV3] ownerPk mismatch', {\n        leaf_index: leaf.leaf_index,\n        note_ownerPk: '0x' + ownerPkField.toString(16),\n        caller_spendingPk: '0x' + args.spendingPkField.toString(16),\n      })\n      continue\n    }\n    // blinding field\n    let blindingField = 0n\n    for (const b of note.blinding) blindingField = (blindingField << 8n) + BigInt(b)\n    blindingField = ((blindingField % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    // Commitment field — recompute from plaintext to make sure the\n    // on-chain leaf actually binds the decrypted (value, ownerPk,\n    // blinding, 0) tuple. Saves us from acting on tampered storage.\n    const recomputedCommitmentField = await deriveCommitment({\n      value: note.value,\n      ownerPk: ownerPkField,\n      blinding: blindingField,\n      memo: 0n,\n    })\n    let commitmentFieldFromHex = 0n\n    for (const b of commitment) commitmentFieldFromHex = (commitmentFieldFromHex << 8n) + BigInt(b)\n    commitmentFieldFromHex = ((commitmentFieldFromHex % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n    if (recomputedCommitmentField !== commitmentFieldFromHex) {\n      console.warn('[discoverV3] commitment recompute mismatch', {\n        leaf_index: leaf.leaf_index,\n        recomputed: '0x' + recomputedCommitmentField.toString(16),\n        on_chain: '0x' + commitmentFieldFromHex.toString(16),\n        note_value: note.value.toString(),\n        memo_in_note: note.memo ? `bytes[${note.memo.length}]` : 'null',\n      })\n      continue\n    }\n    // Nullifier — Poseidon2(commitment, nullifier_sk).\n    const nullifierField = await deriveNullifierAsync({\n      commitment: recomputedCommitmentField,\n      nullifierSk: args.nullifierSkField,\n    })\n    candidates.push({\n      value: note.value,\n      blindingField,\n      ownerPkField,\n      commitmentField: recomputedCommitmentField,\n      commitmentHex: '0x' + commitHex,\n      leafIndex: leaf.leaf_index,\n      claimedAtSlot: leaf.claimed_at_slot,\n      nullifierField,\n      spent: false,\n    })\n  }\n\n  // 3. Mark spent based on the shared spent-set.\n  if (candidates.length === 0) return []\n  try {\n    const consumed = await (async () => {\n      const u = new URL(\n        'api/private-pool/v2/nullifiers/all',\n        args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/',\n      )\n      u.searchParams.set('network', args.network)\n      const r = await fetch(u.toString())\n      if (!r.ok) return new Set<string>()\n      const b = (await r.json()) as { nullifiers: string[] }\n      return new Set(b.nullifiers.map((n) => n.toLowerCase()))\n    })()\n    for (const c of candidates) {\n      const nHex = '0x' + c.nullifierField.toString(16).padStart(64, '0')\n      if (consumed.has(nHex.toLowerCase())) c.spent = true\n    }\n  } catch {\n    // Spent-check best-effort. Leave spent=false on failure; cashout\n    // will hit on-chain `already_consumed` if the user tries one.\n  }\n  return candidates\n}\n\n/// POST a JoinSplit blob to the off-chain relay. Caller invokes this AFTER\n/// the on-chain tx confirms (otherwise the relay points at a commitment\n/// that doesn't exist yet).\nexport async function relayJoinSplitBlob(args: {\n  apiBaseUrl: string;\n  network: string;\n  blob: PreparedJoinSplitTx['blobs'][number];\n  sourceTx?: string;\n  senderPubkey?: string;\n}): Promise<void> {\n  const url = new URL('api/private-pool/v2/notes/relay-blob', args.apiBaseUrl.endsWith('/') ? args.apiBaseUrl : args.apiBaseUrl + '/');\n  url.searchParams.set('network', args.network);\n  const r = await fetch(url.toString(), {\n    method: 'POST',\n    headers: { 'content-type': 'application/json' },\n    body: JSON.stringify({\n      commitment_hex: args.blob.commitmentHex,\n      ephemeral_pk_hex: args.blob.ephemeralPkHex,\n      ciphertext_b64: args.blob.ciphertextB64,\n      source_tx: args.sourceTx ?? null,\n      sender_pubkey: args.senderPubkey ?? null,\n    }),\n  });\n  if (!r.ok) throw new Error(`relayJoinSplitBlob: ${r.status} ${await r.text()}`);\n}\n","// Private Pool V2 — circuit witness builders + crypto helpers.\n//\n// Off-circuit mirrors of the in-circuit primitives + assembly logic\n// that converts (user intent, on-chain state, owned notes) into the\n// witness shape that snarkjs.fullProve() expects.\n//\n// Every primitive here MUST produce the same field element as its\n// circom counterpart in `contracts/circuits/private-pool-v2/lib.circom`.\n// Conformance is checked by Node-side smoke tests in\n// `contracts/scripts/private-pool-v2/`.\n//\n// Poseidon implementation: `circomlibjs` (already a dep, used by V4).\n// Async-init via `loadPoseidonBuilder()` — first call awaits the\n// builder, subsequent calls reuse the cached instance.\n//\n// Reference: ADR-001 (commitment), ADR-002 (key derivation), ADR-003\n// (encryption — separate file `private-pool-notes.ts`).\n\nimport {\n  type DepositWitness,\n  type WithdrawWitness,\n  type JoinSplitWitness,\n  type DecryptedNote,\n  type OwnedNote,\n  type MerklePath,\n  type PaymentAddress,\n  POOL_DEPTH,\n  ASP_DEPTH,\n} from './types'\n\n// ─────────────────────────────────────────────────────────────────────\n// Poseidon loader — same pattern as `shielded-private-links.ts`.\n// `circomlibjs.buildPoseidon()` returns a callable that takes an array\n// of field elements (bigint) and returns a Uint8Array which we lift\n// back to bigint via `F.toObject`. We cache the builder promise so\n// every call after the first is sync-ish.\n// ─────────────────────────────────────────────────────────────────────\n\ninterface PoseidonField {\n  toObject: (value: unknown) => bigint | string | number\n}\ninterface PoseidonBuilder {\n  (inputs: bigint[]): unknown\n  F: PoseidonField\n}\n\nlet poseidonBuilderPromise: Promise<PoseidonBuilder> | null = null\n\nasync function loadPoseidonBuilder(): Promise<PoseidonBuilder> {\n  if (!poseidonBuilderPromise) {\n    poseidonBuilderPromise = import('circomlibjs').then(\n      (mod) => (mod as { buildPoseidon: () => Promise<PoseidonBuilder> }).buildPoseidon(),\n    )\n  }\n  return await poseidonBuilderPromise\n}\n\nfunction liftPoseidonResult(builder: PoseidonBuilder, value: unknown): bigint {\n  const lifted = builder.F && typeof builder.F.toObject === 'function'\n    ? builder.F.toObject(value)\n    : value\n  const big = BigInt(String(lifted))\n  return ((big % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n}\n\nasync function poseidonHash(inputs: bigint[]): Promise<bigint> {\n  const builder = await loadPoseidonBuilder()\n  return liftPoseidonResult(builder, builder(inputs))\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Field-element helpers — all circuit signals are BN254 scalar field\n// elements. snarkjs accepts them as decimal strings; the bigint API\n// keeps the rest of our code reasonable.\n// ─────────────────────────────────────────────────────────────────────\n\n/** BN254 scalar field modulus. */\nexport const FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n\n\n/** Convert 32 little-endian bytes (or fewer; right-padded) to a field element. */\nexport function bytesToField(bytes: Uint8Array): bigint {\n  if (bytes.length > 32) throw new Error('bytesToField: input > 32 bytes')\n  let n = 0n\n  for (let i = bytes.length - 1; i >= 0; i--) {\n    n = (n << 8n) | BigInt(bytes[i] ?? 0)\n  }\n  return n % FIELD_MODULUS\n}\n\n/** Convert a field element to 32 little-endian bytes. */\nexport function fieldToBytes(n: bigint): Uint8Array {\n  const out = new Uint8Array(32)\n  let x = ((n % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS\n  for (let i = 0; i < 32; i++) {\n    out[i] = Number(x & 0xffn)\n    x >>= 8n\n  }\n  return out\n}\n\n/** Field element → snarkjs witness string (decimal). */\nexport function fieldStr(n: bigint): string {\n  return n.toString(10)\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Commitment & nullifier — off-circuit mirrors of lib.circom.\n// ─────────────────────────────────────────────────────────────────────\n\n/**\n * Commitment per ADR-001:\n *   commitment = Poseidon4(value, owner_pk, blinding, memo_or_zero)\n */\nexport async function deriveCommitment(args: {\n  value: bigint\n  ownerPk: bigint\n  blinding: bigint\n  memo: bigint | null\n}): Promise<bigint> {\n  const memo = args.memo ?? 0n\n  return poseidonHash([args.value, args.ownerPk, args.blinding, memo])\n}\n\n/**\n * Spending pubkey per ADR-002:\n *   spending_pk = Poseidon1(spending_sk)\n */\nexport async function deriveSpendingPk(spendingSk: bigint): Promise<bigint> {\n  return poseidonHash([spendingSk])\n}\n\n/**\n * Nullifier per ADR-002:\n *   nullifier = Poseidon2(commitment, nullifier_sk)\n */\nexport async function deriveNullifier(args: {\n  commitment: bigint\n  nullifierSk: bigint\n}): Promise<bigint> {\n  return poseidonHash([args.commitment, args.nullifierSk])\n}\n\n/**\n * Merkle node hash per lib.circom MerkleRoot template:\n *   parent = Poseidon2(left, right)\n *\n * Used by witness builders that need to verify their own merkle path\n * against a published root before submitting a proof (catches\n * stale-witness bugs early).\n */\nexport async function merkleParent(left: bigint, right: bigint): Promise<bigint> {\n  return poseidonHash([left, right])\n}\n\n/**\n * Recompute a merkle root from a leaf + path. Useful for client-side\n * sanity check (\"does my path actually verify against the published\n * root?\") before burning proof time.\n */\nexport async function recomputeMerkleRoot(args: {\n  leaf: bigint\n  pathElements: bigint[]\n  pathIndices: number[]\n}): Promise<bigint> {\n  if (args.pathElements.length !== args.pathIndices.length) {\n    throw new Error('recomputeMerkleRoot: pathElements / pathIndices length mismatch')\n  }\n  let hash = args.leaf\n  for (let i = 0; i < args.pathElements.length; i++) {\n    const sibling = args.pathElements[i]!\n    const idx = args.pathIndices[i]!\n    if (idx !== 0 && idx !== 1) {\n      throw new Error(`recomputeMerkleRoot: pathIndices[${i}] must be 0 or 1, got ${idx}`)\n    }\n    if (idx === 0) {\n      hash = await merkleParent(hash, sibling)\n    } else {\n      hash = await merkleParent(sibling, hash)\n    }\n  }\n  return hash\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Owned note enrichment — given a DecryptedNote, fill in the cached\n// commitment + nullifier so the rest of the pipeline can reuse them.\n// ─────────────────────────────────────────────────────────────────────\n\nexport async function enrichOwnedNote(args: {\n  note: DecryptedNote\n  leafIndex: number\n  receivedAtSlot: number\n  spent: boolean\n  nullifierSk: bigint\n}): Promise<OwnedNote> {\n  const { note, leafIndex, receivedAtSlot, spent, nullifierSk } = args\n  const commitment = await deriveCommitment({\n    value: note.value,\n    ownerPk: bytesToField(note.ownerPk),\n    blinding: bytesToField(note.blinding),\n    memo: note.memo ? bytesToField(note.memo) : null,\n  })\n  const nullifier = await deriveNullifier({ commitment, nullifierSk })\n  return {\n    ...note,\n    leafIndex,\n    receivedAtSlot,\n    spent,\n    commitment: fieldToBytes(commitment),\n    nullifier: fieldToBytes(nullifier),\n  }\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Deposit witness builder.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface DepositInputs {\n  /** Output note being created. */\n  recipient: PaymentAddress\n  /** Random 32-byte blinding for the new note. */\n  blinding: Uint8Array\n  /** Optional 32-byte memo. */\n  memo: Uint8Array | null\n  /** USDC value entering the pool, in micro-USDC. */\n  publicValue: bigint\n  /** Hash of the SPL source account (binds proof to caller). */\n  depositorAddrHash: bigint\n  /** Relayer fee, micro-USDC. */\n  fee: bigint\n}\n\nexport function buildDepositWitness(inputs: DepositInputs): DepositWitness {\n  // Note: Deposit witness needs no Poseidon hashing — the circuit\n  // computes the commitment from inputs. So this builder stays sync.\n\n  if (inputs.publicValue < 0n) throw new Error('publicValue must be non-negative')\n  if (inputs.fee < 0n) throw new Error('fee must be non-negative')\n  if (inputs.fee > inputs.publicValue) throw new Error('fee exceeds publicValue')\n\n  return {\n    ownerPk: fieldStr(bytesToField(inputs.recipient.spendingPk)),\n    blinding: fieldStr(bytesToField(inputs.blinding)),\n    memo: fieldStr(inputs.memo ? bytesToField(inputs.memo) : 0n),\n    publicValue: fieldStr(inputs.publicValue),\n    depositorAddrHash: fieldStr(inputs.depositorAddrHash),\n    fee: fieldStr(inputs.fee),\n  }\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Withdraw witness builder.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface WithdrawInputs {\n  /** The note being spent (must have spent: false). */\n  note: OwnedNote\n  /** Caller's spending key (to prove authority). */\n  spendingSk: bigint\n  /** Caller's nullifier key (to derive `note.nullifier`). */\n  nullifierSk: bigint\n  /** Pool merkle inclusion proof for `note.commitment`. */\n  poolPath: MerklePath\n  /** ASP merkle inclusion proof for `note.commitment`. */\n  aspPath: MerklePath\n  /** Recipient address hash (binds the proof). */\n  publicAddress: bigint\n  /** USDC paid out to recipient (== note.value − fee). */\n  publicValue: bigint\n  /** Relayer fee, micro-USDC. */\n  fee: bigint\n}\n\nexport async function buildWithdrawWitness(inputs: WithdrawInputs): Promise<WithdrawWitness> {\n  // Sanity checks — fail fast before the prover spends 30s on garbage.\n  if (inputs.note.spent) throw new Error('cannot withdraw an already-spent note')\n  if (inputs.publicValue + inputs.fee !== inputs.note.value) {\n    throw new Error(\n      `balance mismatch: publicValue (${inputs.publicValue}) + fee (${inputs.fee}) ` +\n      `≠ note.value (${inputs.note.value})`,\n    )\n  }\n  if (inputs.poolPath.pathElements.length !== POOL_DEPTH) {\n    throw new Error(`pool path depth ${inputs.poolPath.pathElements.length} ≠ ${POOL_DEPTH}`)\n  }\n  if (inputs.aspPath.pathElements.length !== ASP_DEPTH) {\n    throw new Error(`asp path depth ${inputs.aspPath.pathElements.length} ≠ ${ASP_DEPTH}`)\n  }\n\n  // Verify the witness reconstructs to the published root before\n  // submitting — catches stale paths or bad indexer data early.\n  const leaf = bytesToField(inputs.note.commitment)\n  const recomputedPool = await recomputeMerkleRoot({\n    leaf,\n    pathElements: inputs.poolPath.pathElements.map(bytesToField),\n    pathIndices: inputs.poolPath.pathIndices,\n  })\n  if (recomputedPool !== bytesToField(inputs.poolPath.root)) {\n    throw new Error('pool path does not verify against published root')\n  }\n\n  return {\n    value: fieldStr(inputs.note.value),\n    ownerPk: fieldStr(bytesToField(inputs.note.ownerPk)),\n    blinding: fieldStr(bytesToField(inputs.note.blinding)),\n    memo: fieldStr(inputs.note.memo ? bytesToField(inputs.note.memo) : 0n),\n    spendingSk: fieldStr(inputs.spendingSk),\n    nullifierSk: fieldStr(inputs.nullifierSk),\n    poolPathElements: inputs.poolPath.pathElements.map((b) => fieldStr(bytesToField(b))),\n    poolPathIndices: inputs.poolPath.pathIndices,\n    aspPathElements: inputs.aspPath.pathElements.map((b) => fieldStr(bytesToField(b))),\n    aspPathIndices: inputs.aspPath.pathIndices,\n    publicValue: fieldStr(inputs.publicValue),\n    publicAddress: fieldStr(inputs.publicAddress),\n    fee: fieldStr(inputs.fee),\n  }\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// JoinSplit2x2 witness builder.\n// ─────────────────────────────────────────────────────────────────────\n\nexport interface JoinSplitInputs {\n  /** Two input notes being spent. */\n  inputs: [OwnedNote, OwnedNote]\n  /** Merkle paths for each input (pool + ASP). */\n  inputPoolPaths: [MerklePath, MerklePath]\n  inputAspPaths: [MerklePath, MerklePath]\n\n  /** Caller's spending key (must derive to both inputs' ownerPk). */\n  spendingSk: bigint\n  /** Caller's nullifier key. */\n  nullifierSk: bigint\n\n  /** Two output notes being created. */\n  outputs: [DecryptedNote, DecryptedNote]\n\n  /** Public-side flow. Set ONE of in/out non-zero, the other to 0n. */\n  publicValueIn: bigint\n  publicValueOut: bigint\n  /** Counterparty hash if publicValueIn or publicValueOut > 0; else 0n. */\n  publicAddress: bigint\n  /** Relayer fee. */\n  fee: bigint\n}\n\nexport async function buildJoinSplitWitness(inputs: JoinSplitInputs): Promise<JoinSplitWitness> {\n  // Hard assertions — fail fast.\n  if (inputs.inputs[0].spent || inputs.inputs[1].spent) {\n    throw new Error('cannot spend an already-spent note')\n  }\n  if (\n    inputs.inputs[0].commitment.every((b, i) => b === inputs.inputs[1].commitment[i])\n  ) {\n    throw new Error(\n      'both inputs share the same commitment — would produce identical nullifiers; ' +\n      'use distinct dummy notes (DUMMY_A vs DUMMY_B from pool config) for padding',\n    )\n  }\n  if (inputs.publicValueIn > 0n && inputs.publicValueOut > 0n) {\n    throw new Error('only one of publicValueIn / publicValueOut may be non-zero')\n  }\n\n  // Balance equation:\n  //   sum(inputValues) + publicValueIn === sum(outputValues) + publicValueOut + fee\n  const inputSum = inputs.inputs[0].value + inputs.inputs[1].value\n  const outputSum = inputs.outputs[0].value + inputs.outputs[1].value\n  const lhs = inputSum + inputs.publicValueIn\n  const rhs = outputSum + inputs.publicValueOut + inputs.fee\n  if (lhs !== rhs) {\n    throw new Error(\n      `balance mismatch: ${inputSum} + ${inputs.publicValueIn} (in) ≠ ${outputSum} + ` +\n      `${inputs.publicValueOut} (out) + ${inputs.fee} (fee)`,\n    )\n  }\n\n  // Verify both pool paths against same root (the circuit asserts this).\n  const root0 = bytesToField(inputs.inputPoolPaths[0].root)\n  const root1 = bytesToField(inputs.inputPoolPaths[1].root)\n  if (root0 !== root1) {\n    throw new Error('both inputs must verify against the same pool root snapshot')\n  }\n\n  // ASP roots same check.\n  const asp0 = bytesToField(inputs.inputAspPaths[0].root)\n  const asp1 = bytesToField(inputs.inputAspPaths[1].root)\n  if (asp0 !== asp1) {\n    throw new Error('both inputs must verify against the same ASP root snapshot')\n  }\n\n  // Verify each input's witness path against its claimed root —\n  // rejects stale or wrong indexer data before we burn 30-60s of CPU.\n  for (let i = 0; i < 2; i++) {\n    const note = inputs.inputs[i]!\n    const poolPath = inputs.inputPoolPaths[i]!\n    const aspPath = inputs.inputAspPaths[i]!\n    const leaf = bytesToField(note.commitment)\n    const recomputedPool = await recomputeMerkleRoot({\n      leaf,\n      pathElements: poolPath.pathElements.map(bytesToField),\n      pathIndices: poolPath.pathIndices,\n    })\n    if (recomputedPool !== bytesToField(poolPath.root)) {\n      throw new Error(`input ${i} pool path does not verify against root`)\n    }\n    const recomputedAsp = await recomputeMerkleRoot({\n      leaf,\n      pathElements: aspPath.pathElements.map(bytesToField),\n      pathIndices: aspPath.pathIndices,\n    })\n    if (recomputedAsp !== bytesToField(aspPath.root)) {\n      throw new Error(`input ${i} ASP path does not verify against root`)\n    }\n  }\n\n  // Spend authority sanity — derived spending_pk should match BOTH\n  // input notes' ownerPk. Circuit constraint G enforces this; we\n  // catch wallet-mixup bugs before the prover.\n  const spendingPk = await deriveSpendingPk(inputs.spendingSk)\n  for (let i = 0; i < 2; i++) {\n    const note = inputs.inputs[i]!\n    const ownerField = bytesToField(note.ownerPk)\n    if (spendingPk !== ownerField) {\n      throw new Error(\n        `input ${i} ownerPk does not match the provided spending key — ` +\n        `did you switch wallets? expected ${spendingPk}, got ${ownerField}`,\n      )\n    }\n  }\n\n  return {\n    inputValue1:       fieldStr(inputs.inputs[0].value),\n    inputOwnerPk1:     fieldStr(bytesToField(inputs.inputs[0].ownerPk)),\n    inputBlinding1:    fieldStr(bytesToField(inputs.inputs[0].blinding)),\n    inputMemo1:        fieldStr(inputs.inputs[0].memo ? bytesToField(inputs.inputs[0].memo) : 0n),\n    inputPoolPath1:    inputs.inputPoolPaths[0].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputPoolIndices1: inputs.inputPoolPaths[0].pathIndices,\n    inputAspPath1:     inputs.inputAspPaths[0].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputAspIndices1:  inputs.inputAspPaths[0].pathIndices,\n\n    inputValue2:       fieldStr(inputs.inputs[1].value),\n    inputOwnerPk2:     fieldStr(bytesToField(inputs.inputs[1].ownerPk)),\n    inputBlinding2:    fieldStr(bytesToField(inputs.inputs[1].blinding)),\n    inputMemo2:        fieldStr(inputs.inputs[1].memo ? bytesToField(inputs.inputs[1].memo) : 0n),\n    inputPoolPath2:    inputs.inputPoolPaths[1].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputPoolIndices2: inputs.inputPoolPaths[1].pathIndices,\n    inputAspPath2:     inputs.inputAspPaths[1].pathElements.map((b) => fieldStr(bytesToField(b))),\n    inputAspIndices2:  inputs.inputAspPaths[1].pathIndices,\n\n    spendingSk:  fieldStr(inputs.spendingSk),\n    nullifierSk: fieldStr(inputs.nullifierSk),\n\n    outputValue1:    fieldStr(inputs.outputs[0].value),\n    outputOwnerPk1:  fieldStr(bytesToField(inputs.outputs[0].ownerPk)),\n    outputBlinding1: fieldStr(bytesToField(inputs.outputs[0].blinding)),\n    outputMemo1:     fieldStr(inputs.outputs[0].memo ? bytesToField(inputs.outputs[0].memo) : 0n),\n\n    outputValue2:    fieldStr(inputs.outputs[1].value),\n    outputOwnerPk2:  fieldStr(bytesToField(inputs.outputs[1].ownerPk)),\n    outputBlinding2: fieldStr(bytesToField(inputs.outputs[1].blinding)),\n    outputMemo2:     fieldStr(inputs.outputs[1].memo ? bytesToField(inputs.outputs[1].memo) : 0n),\n\n    publicValueIn:  fieldStr(inputs.publicValueIn),\n    publicValueOut: fieldStr(inputs.publicValueOut),\n    publicAddress:  fieldStr(inputs.publicAddress),\n    fee:            fieldStr(inputs.fee),\n  }\n}\n","// Private Pool V2 — browser-side Groth16 prover wrapper.\n//\n// One callable per circuit. Each callable:\n//   1. Lazy-loads snarkjs (esm side-import; ~700 KB once cached)\n//   2. Calls `groth16.fullProve(witness, wasmUrl, zkeyUrl)`\n//   3. Packs the proof into the 256-byte layout the on-chain verifier\n//      expects (matches V4 `encodeGroth16Proof` rationale; see\n//      `shielded-browser-prover.ts` for the EVM-style G2 swap details)\n//   4. Returns `{ proofBytes, publicSignals, proveMs }`\n//\n// The wrapper is intentionally NOT a Web Worker (yet). snarkjs runs the\n// WASM witness generator on the main thread; for joinsplit (~33k constraints)\n// fullProve takes ~1s on an M1, ~3-5s on a 5-yr-old laptop. UI freeze is\n// noticeable but tolerable as a v1 — Faza 6 promotes this to a worker\n// once we have UX progress callbacks.\n//\n// Wasm/zkey URLs default to the staged artifacts under `/zk/private-pool-v2/`\n// (the `zk:build-private-pool-v2` script copies `Deposit.wasm`,\n// `deposit_final.zkey`, etc. into `frontend/public/zk/...`).\n\nimport type {\n  DepositWitness,\n  WithdrawWitness,\n  JoinSplitWitness,\n  CashoutProofWitness,\n} from './types';\n\ntype SnarkJsModule = typeof import('snarkjs');\ntype Groth16Proof = {\n  pi_a: [string, string, string];\n  pi_b: [[string, string], [string, string], [string, string]];\n  pi_c: [string, string, string];\n  protocol?: string;\n  curve?: string;\n};\n\nconst DEFAULT_ARTIFACT_BASE = '/zk/private-pool-v2';\n\nconst ARTIFACTS = {\n  deposit: {\n    wasm: 'deposit.wasm',\n    zkey: 'deposit.zkey',\n  },\n  withdraw: {\n    wasm: 'withdraw.wasm',\n    zkey: 'withdraw.zkey',\n  },\n  joinsplit: {\n    wasm: 'joinsplit2x2.wasm',\n    zkey: 'joinsplit2x2.zkey',\n  },\n  /// ADR-012 V3 — cashout from credit pool tree. Smaller circuit\n  /// (~7k constraints, depth-24 tree, no ASP layer) so this prover\n  /// runs noticeably faster than Withdraw on mid-range hardware.\n  cashoutproof: {\n    wasm: 'cashoutproof.wasm',\n    zkey: 'cashoutproof.zkey',\n  },\n} as const;\n\nexport type CircuitName = keyof typeof ARTIFACTS;\n\n// ── snarkjs loader ───────────────────────────────────────────────────────\n\nlet snarkjsModulePromise: Promise<SnarkJsModule> | null = null;\nasync function loadSnarkJsModule(): Promise<SnarkJsModule> {\n  if (!snarkjsModulePromise) {\n    // Dynamic import keeps the ~700 KB snarkjs bundle out of the\n    // initial JS payload — only loaded when the user actually starts\n    // a deposit / withdraw / joinsplit flow.\n    snarkjsModulePromise = import('snarkjs') as Promise<SnarkJsModule>;\n  }\n  return snarkjsModulePromise;\n}\n\n// ── Proof byte packing ──────────────────────────────────────────────────\n//\n// `groth16.exportSolidityCallData` produces the EVM-style ordering with\n// G2 Fp2 swap. Solana's alt_bn128_pairing syscall uses the same\n// convention, so we forward the swapped output as-is. (Verified end-to-end\n// by V4 ASP integration AND by the V2 E2E scripts in\n// `contracts/scripts/private-pool-v2/`.)\n\nfunction fieldToBytes32BE(value: bigint): Uint8Array {\n  const hex = value.toString(16).padStart(64, '0');\n  return Uint8Array.from(Buffer.from(hex, 'hex'));\n}\n\nasync function packProofForSolana(\n  snarkjs: SnarkJsModule,\n  proof: Groth16Proof,\n  publicSignals: string[],\n): Promise<Uint8Array> {\n  if (typeof snarkjs.groth16.exportSolidityCallData !== 'function') {\n    throw new Error('snarkjs.groth16.exportSolidityCallData unavailable');\n  }\n  const rawCalldata = await snarkjs.groth16.exportSolidityCallData(proof, publicSignals);\n  const parsed = JSON.parse(`[${rawCalldata}]`) as [\n    [string, string],\n    [[string, string], [string, string]],\n    [string, string],\n    string[],\n  ];\n  const [a, b, c] = parsed;\n  const out = new Uint8Array(256);\n  out.set(fieldToBytes32BE(BigInt(a[0])), 0);\n  out.set(fieldToBytes32BE(BigInt(a[1])), 32);\n  out.set(fieldToBytes32BE(BigInt(b[0][0])), 64);\n  out.set(fieldToBytes32BE(BigInt(b[0][1])), 96);\n  out.set(fieldToBytes32BE(BigInt(b[1][0])), 128);\n  out.set(fieldToBytes32BE(BigInt(b[1][1])), 160);\n  out.set(fieldToBytes32BE(BigInt(c[0])), 192);\n  out.set(fieldToBytes32BE(BigInt(c[1])), 224);\n  return out;\n}\n\n// ── Generic prove ────────────────────────────────────────────────────────\n\nexport type ProveResult = {\n  proofBytes: Uint8Array; // 256-byte Solana layout\n  publicSignals: string[]; // decimal field-element strings, in circuit order\n  proveMs: number;\n};\n\nexport type ProveOptions = {\n  /// Override the default `/zk/private-pool-v2/` artifact base. Useful\n  /// for hosting artifacts on a CDN or for swapping in dev artifacts.\n  artifactBaseUrl?: string;\n};\n\nasync function proveCircuit(\n  circuit: CircuitName,\n  witness: Record<string, unknown>,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  const base = (options.artifactBaseUrl ?? DEFAULT_ARTIFACT_BASE).replace(/\\/+$/, '');\n  const wasm = `${base}/${ARTIFACTS[circuit].wasm}`;\n  const zkey = `${base}/${ARTIFACTS[circuit].zkey}`;\n  const snarkjs = await loadSnarkJsModule();\n  const t0 = (typeof performance !== 'undefined' ? performance : Date).now();\n  const { proof, publicSignals } = (await snarkjs.groth16.fullProve(witness, wasm, zkey)) as {\n    proof: Groth16Proof;\n    publicSignals: string[];\n  };\n  const proofBytes = await packProofForSolana(snarkjs, proof, publicSignals);\n  const proveMs = ((typeof performance !== 'undefined' ? performance : Date).now()) - t0;\n  return { proofBytes, publicSignals, proveMs };\n}\n\n// ── Per-circuit thin wrappers (typed witnesses) ──────────────────────────\n\nexport async function proveDeposit(\n  witness: DepositWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('deposit', witness as unknown as Record<string, unknown>, options);\n}\n\nexport async function proveWithdraw(\n  witness: WithdrawWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('withdraw', witness as unknown as Record<string, unknown>, options);\n}\n\nexport async function proveJoinSplit(\n  witness: JoinSplitWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('joinsplit', witness as unknown as Record<string, unknown>, options);\n}\n\n/// ADR-012 V3 — cashout from credit pool tree.\nexport async function proveCashoutProof(\n  witness: CashoutProofWitness,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit('cashoutproof', witness as unknown as Record<string, unknown>, options);\n}\n\n/// Untyped escape hatch — useful for benchmarks or for circuits not yet\n/// type-modelled in `private-pool-types.ts`.\nexport async function proveAny(\n  circuit: CircuitName,\n  witness: Record<string, unknown>,\n  options: ProveOptions = {},\n): Promise<ProveResult> {\n  return proveCircuit(circuit, witness, options);\n}\n\n/// Hex preview helper for the proof bytes — handy for logging without\n/// dumping all 256 bytes.\nexport function proofPreview(bytes: Uint8Array): string {\n  if (bytes.length === 0) return '0x';\n  const head = Buffer.from(bytes.slice(0, 8)).toString('hex');\n  const tail = Buffer.from(bytes.slice(-8)).toString('hex');\n  return `0x${head}…${tail} (${bytes.length} bytes)`;\n}\n","// Private Pool V2 — wallet-derived key tree (ADR-002).\n//\n//   root_sk      = blake2b256(walletSig)[:32]\n//   spending_sk  = blake2b256(root_sk || ASCII(\"relai-pool-v2/spend\"))\n//   viewing_sk   = blake2b256(root_sk || ASCII(\"relai-pool-v2/view\"))\n//   nullifier_sk = blake2b256(root_sk || ASCII(\"relai-pool-v2/null\"))\n//\n//   spending_pk  = Poseidon1(spending_sk_field) mod p   // field element used in commitments\n//   viewing_pk   = X25519.public_from(viewing_sk)        // 32-byte X25519 pubkey\n//\n//   PaymentAddress = base58( spending_pk_bytes_BE || viewing_pk_bytes )\n//\n// `*_sk` is the raw 32-byte blake2b output. `*_sk_field` is the same\n// bytes reduced mod the BN254 scalar field modulus, then re-emitted as\n// a bigint suitable for Poseidon / circuit witness inputs. The two\n// representations are kept side by side because:\n//   - the encryption layer (ADR-003) wants raw bytes for X25519\n//   - the circuit witness (Faza 1) wants field elements\n\nimport { blake2b } from '@noble/hashes/blake2b';\nimport { x25519 } from '@noble/curves/ed25519';\nimport { buildPoseidon } from 'circomlibjs';\nimport bs58 from 'bs58';\n\nconst FIELD_MODULUS =\n  21888242871839275222246405745257275088548364400416034343698204186575808495617n;\n\nconst TEXT_ENCODER = new TextEncoder();\nconst LABELS = {\n  spending: TEXT_ENCODER.encode('relai-pool-v2/spend'),\n  viewing: TEXT_ENCODER.encode('relai-pool-v2/view'),\n  nullifier: TEXT_ENCODER.encode('relai-pool-v2/null'),\n} as const;\n\nconst PAYMENT_ADDRESS_BYTES = 64; // spending_pk(32) + viewing_pk(32)\n\n// ── Hash helpers ─────────────────────────────────────────────────────────\n\nfunction concatBytes(...parts: Uint8Array[]): Uint8Array {\n  const total = parts.reduce((sum, p) => sum + p.length, 0);\n  const out = new Uint8Array(total);\n  let off = 0;\n  for (const p of parts) {\n    out.set(p, off);\n    off += p.length;\n  }\n  return out;\n}\n\nfunction ensureBytes(label: string, bytes: Uint8Array, expected: number): void {\n  if (bytes.length !== expected) {\n    throw new Error(`${label}: expected ${expected} bytes, got ${bytes.length}`);\n  }\n}\n\n/// 32-byte BE → bigint (reduced mod BN254 scalar field).\nexport function bytesToFieldBE(bytes: Uint8Array): bigint {\n  if (bytes.length === 0) return 0n;\n  let v = 0n;\n  for (const b of bytes) v = (v << 8n) + BigInt(b);\n  return ((v % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n/// bigint → 32-byte BE buffer. Throws if the bigint exceeds 256 bits\n/// (shouldn't happen for field elements, but worth being explicit).\nexport function fieldToBytes32BE(value: bigint): Uint8Array {\n  if (value < 0n) {\n    value = ((value % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n  }\n  const hex = value.toString(16).padStart(64, '0');\n  if (hex.length > 64) {\n    throw new Error(`fieldToBytes32BE: value exceeds 32 bytes (${hex.length / 2})`);\n  }\n  return Uint8Array.from(Buffer.from(hex, 'hex'));\n}\n\n// ── Poseidon singleton ───────────────────────────────────────────────────\n\nlet poseidonInstance: ReturnType<typeof buildPoseidon> extends Promise<infer T> ? T | null : never = null;\nasync function getPoseidon() {\n  if (!poseidonInstance) {\n    poseidonInstance = await buildPoseidon();\n  }\n  return poseidonInstance!;\n}\n\n/// Poseidon-Bn254X5 of one field input → bigint mod p.\nexport async function poseidon1(input: bigint): Promise<bigint> {\n  // circomlibjs has no published .d.ts so the `buildPoseidon` return is\n  // typed as `unknown` upstream. The runtime shape is well-known and\n  // stable; cast to `any` for the call sites.\n  const p = (await getPoseidon()) as any;\n  const h = p([input]);\n  return ((BigInt(p.F.toString(h)) % FIELD_MODULUS) + FIELD_MODULUS) % FIELD_MODULUS;\n}\n\n// ── Sub-key derivation ───────────────────────────────────────────────────\n\n/// `root_sk = blake2b256(walletSig)[:32]`. The wallet signature is\n/// produced off-screen by signing the canonical challenge message\n/// (`PRIVATE_POOL_V2_KEY_CHALLENGE`) — the caller is expected to handle\n/// the wallet popup itself.\nexport function deriveRootSk(walletSignature: Uint8Array): Uint8Array {\n  if (!walletSignature || walletSignature.length === 0) {\n    throw new Error('deriveRootSk: walletSignature must be non-empty');\n  }\n  return blake2b(walletSignature, { dkLen: 32 });\n}\n\n/// `<sub_sk> = blake2b256(root_sk || label)`.\nexport function deriveSubkey(rootSk: Uint8Array, label: Uint8Array): Uint8Array {\n  ensureBytes('rootSk', rootSk, 32);\n  return blake2b(concatBytes(rootSk, label), { dkLen: 32 });\n}\n\nexport type PrivatePoolKeyMaterial = {\n  /// Raw 32-byte blake2b sub-keys.\n  rootSk: Uint8Array;\n  spendingSk: Uint8Array;\n  viewingSk: Uint8Array;\n  nullifierSk: Uint8Array;\n  /// Field-element views of the spending/nullifier secrets — what the\n  /// circuit witness consumes. spending_sk_field / nullifier_sk_field\n  /// are `bytesToFieldBE(spendingSk)` etc.\n  spendingSkField: bigint;\n  nullifierSkField: bigint;\n  /// Public counterparts.\n  spendingPkField: bigint;        // Poseidon1(spendingSkField)\n  viewingPk: Uint8Array;          // X25519.public(viewingSk)\n  /// Encoded payment address, base58( spending_pk_bytes || viewing_pk_bytes ).\n  paymentAddress: string;\n};\n\n/// One-shot key derivation. Returns the full sub-key tree + payment\n/// address. Idempotent; safe to re-run on every page load.\nexport async function derivePrivatePoolKeys(\n  walletSignature: Uint8Array,\n): Promise<PrivatePoolKeyMaterial> {\n  const rootSk = deriveRootSk(walletSignature);\n  const spendingSk = deriveSubkey(rootSk, LABELS.spending);\n  const viewingSk = deriveSubkey(rootSk, LABELS.viewing);\n  const nullifierSk = deriveSubkey(rootSk, LABELS.nullifier);\n\n  const spendingSkField = bytesToFieldBE(spendingSk);\n  const nullifierSkField = bytesToFieldBE(nullifierSk);\n  const spendingPkField = await poseidon1(spendingSkField);\n  const viewingPk = x25519.getPublicKey(viewingSk);\n\n  const paymentAddress = encodePaymentAddress(spendingPkField, viewingPk);\n\n  return {\n    rootSk,\n    spendingSk,\n    viewingSk,\n    nullifierSk,\n    spendingSkField,\n    nullifierSkField,\n    spendingPkField,\n    viewingPk,\n    paymentAddress,\n  };\n}\n\n// ── F2: code-derived key material (private payment codes) ──────────────\n//\n// A one-time `code_seed` (32 bytes) deterministically generates the\n// same key tree as a wallet signature would. The seed plays exactly\n// the role of `walletSignature` in `derivePrivatePoolKeys` — same\n// blake2b labels, same Poseidon derivation, same X25519 viewing key.\n//\n// Why we reuse the existing tree shape:\n//   - The credit-pool leaf binds `owner_pk = spending_pk_field`.\n//     Whatever generates `spending_sk_field` and lets us compute its\n//     Poseidon-1 image works as a \"recipient identity.\" The wallet\n//     case derives via signature; the bearer-code case derives via\n//     seed. Same circuit, same prover, same on-chain verifier.\n//   - The encrypted note blob is keyed by `viewing_pk` (X25519). The\n//     redeemer needs to derive the matching `viewing_sk` from the\n//     same seed at redeem time.\n//\n// Why this is safe:\n//   - The seed is generated client-side and never leaves the issuer's\n//     device (except via the URL/QR they hand to the redeemer).\n//   - Anyone with the seed can re-derive all keys and cash out the\n//     parked leaf. That's exactly the bearer-token semantics we want.\n//   - The seed is large enough (32 bytes = 256 bits) that brute-force\n//     guessing the seed of a specific leaf is not feasible.\n//\n// Important difference from `derivePrivatePoolKeys`:\n//   - No `paymentAddress` is computed — the seed itself encodes both\n//     halves of the addressing, and we don't want to leak a payment\n//     address that ties the code to a long-lived identity.\n\n/// Two seed formats, picked by the issuer per code:\n///\n///   • `long` (default; 32B / 256-bit)\n///     - Encoded as base58 in URL fragment (~44 chars).\n///     - Astronomically brute-force resistant. Industry-standard for\n///       \"fire and forget\" bearer tokens.\n///     - Hard to dictate by voice/SMS — must be shared as URL/QR.\n///\n///   • `short` (16-char Wi-Fi-style base32, 80-bit)\n///     - Encoded as `XXXX-XXXX-XXXX-XXXX` for easy reading.\n///     - ~80 bits of entropy. At 10⁹ hashes/s a single specific code\n///       takes ≈ 38,000 years to brute-force; the on-chain leaf set\n///       gates each guess by Poseidon-derived owner_pk lookup, so\n///       practical attack cost is much higher. Comfortable for typical\n///       consumer amounts; we'd recommend `long` for institutional /\n///       very high value.\n///     - Can be dictated, written on paper, typed into a separate\n///       device — same UX feel as a Wi-Fi password.\n///\n/// Both share the SAME key-derivation algorithm — we just hand\n/// `deriveRootSk` a different number of entropy bytes. The on-chain\n/// proof is identical for both formats; format choice is purely an\n/// issuer-side UX dial.\nconst LONG_SEED_BYTES = 32;\nconst SHORT_SEED_BYTES = 10; // 10 × 8 = 80 bits, encodes as 16 base32 chars\n\nexport type CodeSeedFormat = 'short' | 'long';\n\nexport type CodeKeyMaterial = {\n  /// Raw seed; the only thing the issuer must guard + later share.\n  /// 10 bytes for `short`, 32 bytes for `long`. The on-chain derivation\n  /// works identically either way (blake2b is variable-input).\n  codeSeed: Uint8Array;\n  /// Which format the seed was generated as. Lets callers re-encode\n  /// the same seed back to the right surface (base32 vs base58)\n  /// without recomputing entropy.\n  format: CodeSeedFormat;\n  /// Key tree, identical shape to wallet-derived material.\n  rootSk: Uint8Array;\n  spendingSk: Uint8Array;\n  viewingSk: Uint8Array;\n  nullifierSk: Uint8Array;\n  spendingSkField: bigint;\n  nullifierSkField: bigint;\n  spendingPkField: bigint;\n  viewingPk: Uint8Array;\n};\n\n/// Generate a fresh code seed using a CSPRNG. The issuer holds this\n/// locally (in IndexedDB if they want cancel capability) and shares\n/// it out-of-band — usually as part of a URL fragment so it never\n/// touches the server.\n///\n/// `format` defaults to `'long'` for safety. Callers wanting the\n/// dictatable Wi-Fi-style format pass `'short'` explicitly.\nexport function generateCodeSeed(format: CodeSeedFormat = 'long'): Uint8Array {\n  const bytes = format === 'short' ? SHORT_SEED_BYTES : LONG_SEED_BYTES;\n  const out = new Uint8Array(bytes);\n  // `crypto` is the same global the rest of the SDK uses for\n  // blinding randomness; available in both browser and Node ≥ 19.\n  crypto.getRandomValues(out);\n  return out;\n}\n\n/// Detect the format of an existing seed by its length. Useful when a\n/// caller has the bytes but not the format flag (e.g. after re-deriving\n/// from a decoded code).\nexport function detectCodeSeedFormat(codeSeed: Uint8Array): CodeSeedFormat {\n  if (codeSeed.length === SHORT_SEED_BYTES) return 'short';\n  if (codeSeed.length === LONG_SEED_BYTES) return 'long';\n  throw new Error(\n    `detectCodeSeedFormat: unsupported seed length ${codeSeed.length} (expected ${SHORT_SEED_BYTES} or ${LONG_SEED_BYTES})`,\n  );\n}\n\n/// Deterministic key derivation from a code seed. Same algorithm as\n/// the wallet-derived path; the only difference is the entropy source\n/// (seed vs wallet signature). Idempotent — calling it twice with the\n/// same seed yields exactly the same key material.\n///\n/// Accepts both short (10-byte) and long (32-byte) seeds; the\n/// `blake2b(seed)` rooting step doesn't care about input length, and\n/// downstream derivation is identical.\nexport async function deriveCodeKeys(\n  codeSeed: Uint8Array,\n): Promise<CodeKeyMaterial> {\n  if (!codeSeed) {\n    throw new Error('deriveCodeKeys: codeSeed required');\n  }\n  if (codeSeed.length !== SHORT_SEED_BYTES && codeSeed.length !== LONG_SEED_BYTES) {\n    throw new Error(\n      `deriveCodeKeys: codeSeed must be ${SHORT_SEED_BYTES} or ${LONG_SEED_BYTES} bytes (got ${codeSeed.length})`,\n    );\n  }\n  const format: CodeSeedFormat = codeSeed.length === SHORT_SEED_BYTES ? 'short' : 'long';\n  const rootSk = deriveRootSk(codeSeed);\n  const spendingSk = deriveSubkey(rootSk, LABELS.spending);\n  const viewingSk = deriveSubkey(rootSk, LABELS.viewing);\n  const nullifierSk = deriveSubkey(rootSk, LABELS.nullifier);\n\n  const spendingSkField = bytesToFieldBE(spendingSk);\n  const nullifierSkField = bytesToFieldBE(nullifierSk);\n  const spendingPkField = await poseidon1(spendingSkField);\n  const viewingPk = x25519.getPublicKey(viewingSk);\n\n  return {\n    codeSeed,\n    format,\n    rootSk,\n    spendingSk,\n    viewingSk,\n    nullifierSk,\n    spendingSkField,\n    nullifierSkField,\n    spendingPkField,\n    viewingPk,\n  };\n}\n\n// ── F2: code-seed codec for URLs (Bech32-style) ────────────────────────\n//\n// We encode the 32B seed as base58 — same alphabet the SDK already uses\n// for the payment address, no new dependencies, ~44 ASCII chars. The\n// short fixed length lets us validate decoded seeds with a single\n// length check. A Bech32 checksum is overkill at this size (a typo\n// would already fail the field-element derivation at park-redeem time,\n// just less helpfully) — kept simple to reduce dependency surface.\n\n/// Encode a 32B \"long\" seed for URL / QR transport. Result is base58\n/// of the raw seed bytes — no version byte, no checksum. The caller\n/// is expected to put this in the URL fragment (`#seed=…`) so it never\n/// hits server logs.\nexport function encodeCodeSeed(seed: Uint8Array): string {\n  if (!seed || seed.length !== LONG_SEED_BYTES) {\n    throw new Error(\n      `encodeCodeSeed: seed must be exactly ${LONG_SEED_BYTES} bytes (got ${seed?.length ?? 0})`,\n    );\n  }\n  const encoder = (bs58 as { encode?: (b: Uint8Array) => string }).encode\n    ?? (bs58 as { default?: { encode: (b: Uint8Array) => string } }).default?.encode;\n  if (!encoder) {\n    throw new Error('encodeCodeSeed: bs58 encoder missing — incompatible bs58 version');\n  }\n  return encoder(seed);\n}\n\n/// Inverse of `encodeCodeSeed`. Throws on malformed input (wrong\n/// length, non-base58 chars). The caller may want to wrap this in a\n/// try/catch to surface a user-friendly \"invalid code\" error.\nexport function decodeCodeSeed(encoded: string): Uint8Array {\n  if (typeof encoded !== 'string' || encoded.length === 0) {\n    throw new Error('decodeCodeSeed: input must be a non-empty string');\n  }\n  const decoder = (bs58 as { decode?: (s: string) => Uint8Array }).decode\n    ?? (bs58 as { default?: { decode: (s: string) => Uint8Array } }).default?.decode;\n  if (!decoder) {\n    throw new Error('decodeCodeSeed: bs58 decoder missing — incompatible bs58 version');\n  }\n  const out = decoder(encoded);\n  if (out.length !== LONG_SEED_BYTES) {\n    throw new Error(\n      `decodeCodeSeed: decoded ${out.length} bytes, expected ${LONG_SEED_BYTES}`,\n    );\n  }\n  return out;\n}\n\n// ── Short-code (Wi-Fi-style base32) codec ──────────────────────────────\n//\n// 10 bytes → 16 base32 characters → rendered as `XXXX-XXXX-XXXX-XXXX`.\n// RFC 4648 base32 alphabet (`A-Z2-7`) is deliberately confusion-free —\n// no 0/O, 1/I/l ambiguity. Issuer can dictate the code over the phone\n// without losing 25 minutes to \"is that an O or zero\" rounds. Dashes\n// are decorative; the decoder strips them.\n//\n// Why base32 over base58 here:\n//   • 5 bits per character → fixed 16-char output for 80 bits, with\n//     no awkward \"≈ 14 chars\" rounding.\n//   • Voice-friendly alphabet.\n//   • Dashes optional in the input — we tolerate either form.\n\nconst BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';\n\nfunction encodeBase32(bytes: Uint8Array): string {\n  let bits = 0;\n  let value = 0;\n  let out = '';\n  for (const b of bytes) {\n    value = (value << 8) | b;\n    bits += 8;\n    while (bits >= 5) {\n      out += BASE32_ALPHABET[(value >>> (bits - 5)) & 0x1f];\n      bits -= 5;\n    }\n  }\n  if (bits > 0) {\n    out += BASE32_ALPHABET[(value << (5 - bits)) & 0x1f];\n  }\n  return out;\n}\n\nfunction decodeBase32(s: string): Uint8Array {\n  // Normalise: uppercase, drop dashes/whitespace, validate alphabet.\n  const cleaned = s.toUpperCase().replace(/[\\s-]/g, '');\n  if (cleaned.length === 0) throw new Error('decodeBase32: empty input');\n  for (const ch of cleaned) {\n    if (BASE32_ALPHABET.indexOf(ch) === -1) {\n      throw new Error(`decodeBase32: illegal character '${ch}' (allowed: A-Z, 2-7)`);\n    }\n  }\n  let bits = 0;\n  let value = 0;\n  const out: number[] = [];\n  for (const ch of cleaned) {\n    value = (value << 5) | BASE32_ALPHABET.indexOf(ch);\n    bits += 5;\n    if (bits >= 8) {\n      out.push((value >>> (bits - 8)) & 0xff);\n      bits -= 8;\n    }\n  }\n  return Uint8Array.from(out);\n}\n\n/// Format a raw 10-byte seed into the user-facing\n/// `XXXX-XXXX-XXXX-XXXX` representation. Round-trip with\n/// `decodeShortCode` is exact.\nexport function encodeShortCode(seed: Uint8Array): string {\n  if (!seed || seed.length !== SHORT_SEED_BYTES) {\n    throw new Error(\n      `encodeShortCode: seed must be exactly ${SHORT_SEED_BYTES} bytes (got ${seed?.length ?? 0})`,\n    );\n  }\n  const flat = encodeBase32(seed); // 16 chars\n  // Insert a hyphen every 4 chars for readability. `XXXX-XXXX-XXXX-XXXX`.\n  return flat.match(/.{1,4}/g)!.join('-');\n}\n\n/// Inverse of `encodeShortCode`. Accepts the canonical\n/// `XXXX-XXXX-XXXX-XXXX` form, plain `XXXXXXXXXXXXXXXX`, lower-case,\n/// or any combination with extra whitespace — anything a user might\n/// dictate or paste with mild messiness. Throws on illegal characters\n/// or wrong length.\nexport function decodeShortCode(encoded: string): Uint8Array {\n  if (typeof encoded !== 'string') {\n    throw new Error('decodeShortCode: input must be a string');\n  }\n  const out = decodeBase32(encoded);\n  if (out.length !== SHORT_SEED_BYTES) {\n    throw new Error(\n      `decodeShortCode: decoded ${out.length} bytes, expected ${SHORT_SEED_BYTES} (16 base32 chars)`,\n    );\n  }\n  return out;\n}\n\n/// Auto-detect the format of an encoded code string and return the raw\n/// seed bytes. Convenience for redeem flows that accept either format\n/// from a single text input.\n///\n/// Detection rules:\n///   • Strip whitespace + dashes. If the result is exactly 16 chars in\n///     the base32 alphabet → short code, decoded as such.\n///   • Otherwise try base58 → 32-byte long seed.\n///   • Anything else → throw with a hint.\nexport function decodeCodeAuto(encoded: string): {\n  seed: Uint8Array;\n  format: CodeSeedFormat;\n} {\n  if (typeof encoded !== 'string' || encoded.trim().length === 0) {\n    throw new Error('decodeCodeAuto: empty input');\n  }\n  const cleaned = encoded.trim();\n  const base32Cleaned = cleaned.toUpperCase().replace(/[\\s-]/g, '');\n  // Short code: exactly 16 base32 chars.\n  if (\n    base32Cleaned.length === 16\n    && [...base32Cleaned].every((c) => BASE32_ALPHABET.indexOf(c) !== -1)\n  ) {\n    return { seed: decodeShortCode(cleaned), format: 'short' };\n  }\n  // Otherwise assume base58 long. The base58 decoder will throw if\n  // the alphabet doesn't match.\n  try {\n    return { seed: decodeCodeSeed(cleaned), format: 'long' };\n  } catch (err) {\n    throw new Error(\n      `decodeCodeAuto: not a valid short (16 char) or long (~44 char) code: ${err instanceof Error ? err.message : String(err)}`,\n    );\n  }\n}\n\n// ── Payment address codec ────────────────────────────────────────────────\n\n/// `base58(spending_pk_bytes_BE || viewing_pk_bytes)` — the human-shareable\n/// \"send notes to me\" handle. 64 bytes encoded → ~88 base58 chars.\nexport function encodePaymentAddress(spendingPkField: bigint, viewingPk: Uint8Array): string {\n  ensureBytes('viewingPk', viewingPk, 32);\n  const spendingPkBytes = fieldToBytes32BE(spendingPkField);\n  const concat = concatBytes(spendingPkBytes, viewingPk);\n  // bs58@4: bs58.encode(buffer)\n  // bs58@5+: bs58.default.encode(buffer)\n  const encoder = (bs58 as { encode?: (b: Uint8Array) => string }).encode\n    ?? (bs58 as { default?: { encode: (b: Uint8Array) => string } }).default?.encode;\n  if (!encoder) throw new Error('encodePaymentAddress: bs58.encode not found');\n  return encoder(concat);\n}\n\nexport function parsePaymentAddress(address: string): {\n  spendingPkField: bigint;\n  viewingPk: Uint8Array;\n} {\n  const decoder = (bs58 as { decode?: (s: string) => Uint8Array }).decode\n    ?? (bs58 as { default?: { decode: (s: string) => Uint8Array } }).default?.decode;\n  if (!decoder) throw new Error('parsePaymentAddress: bs58.decode not found');\n  const decoded = decoder(address);\n  if (decoded.length !== PAYMENT_ADDRESS_BYTES) {\n    throw new Error(\n      `parsePaymentAddress: decoded length ${decoded.length} != ${PAYMENT_ADDRESS_BYTES}`,\n    );\n  }\n  return {\n    spendingPkField: bytesToFieldBE(decoded.slice(0, 32)),\n    viewingPk: decoded.slice(32, 64),\n  };\n}\n\n// ── Wallet challenge ─────────────────────────────────────────────────────\n\n/// Canonical challenge message the wallet signs to derive the root_sk.\n/// Versioned + namespaced so a sig made for V2 can never be reused for\n/// any other RelAI feature (or any other app's \"sign this\" flow).\nexport const PRIVATE_POOL_V2_KEY_CHALLENGE =\n  'RelAI Private Pool V2 — derive private balance keys.\\n\\nThis signature unlocks your hidden-amount balance under the /app/private surface. It does NOT authorize any spend on its own.';\n\n/// Build the wallet challenge bytes for `signMessage` (UTF-8 of the\n/// human-readable challenge above; matches what the on-screen prompt\n/// will display).\nexport function buildKeyChallengeMessage(): Uint8Array {\n  return TEXT_ENCODER.encode(PRIVATE_POOL_V2_KEY_CHALLENGE);\n}\n","// Private Pool V2 — encrypted note format (ADR-003).\n//\n//   ChaCha20-Poly1305 AEAD\n//   X25519 ephemeral key agreement → HKDF-SHA256(shared, salt, info=commitment)\n//   nonce = blake2b256(eph_pk || commitment)[:12]\n//   AAD   = commitment (32 bytes)\n//\n// Plaintext layout (memo-less, 80 bytes):\n//   0..8    value     (u64 LE, micro-USDC)\n//   8..40   blinding  (32 bytes BE)\n//   40..72  owner_pk  (32 bytes BE)\n//   72      memo flag (0x00)\n//   73..80  zero pad\n//\n// Plaintext layout (with memo, 112 bytes): same as above but\n//   72      memo flag (0x01)\n//   80..112 memo (32 bytes)\n//\n// On-chain `EncryptedNoteBlobAccount`:\n//   - ephemeral_pk: 32 bytes (separate field)\n//   - ciphertext:   96 or 128 bytes (Vec<u8>) — plaintext + 16-byte Poly1305 tag\n//\n// Conformance vectors: see `frontend/src/__tests__/private-pool-encryption.test.ts`.\n\nimport { chacha20poly1305 } from '@noble/ciphers/chacha';\nimport { x25519 } from '@noble/curves/ed25519';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { blake2b } from '@noble/hashes/blake2b';\nimport { sha256 } from '@noble/hashes/sha2';\n\nconst HKDF_SALT = new TextEncoder().encode('RelAI PrivatePool v2 note key');\nconst NONCE_LEN = 12;\nconst TAG_LEN = 16;\n\n// Plaintext layouts (legacy ADR-003 only — stealth was withdrawn,\n// see ADR-008 postmortem):\n//   80B  no memo:   value(8) + blinding(32) + owner_pk(32) + flag=0(1) + 7B pad\n//   112B with memo: above + 32B memo at offset 80, flag=1\nconst PLAINTEXT_NO_MEMO = 80;\nconst PLAINTEXT_WITH_MEMO = 112;\nconst CIPHERTEXT_NO_MEMO = PLAINTEXT_NO_MEMO + TAG_LEN; // 96\nconst CIPHERTEXT_WITH_MEMO = PLAINTEXT_WITH_MEMO + TAG_LEN; // 128\n\nconst EPHEMERAL_PK_LEN = 32;\nconst COMMITMENT_LEN = 32;\n\nexport const PRIVATE_POOL_NOTE_BYTES = {\n  PLAINTEXT_NO_MEMO,\n  PLAINTEXT_WITH_MEMO,\n  CIPHERTEXT_NO_MEMO,\n  CIPHERTEXT_WITH_MEMO,\n  EPHEMERAL_PK_LEN,\n  COMMITMENT_LEN,\n  NONCE_LEN,\n  TAG_LEN,\n} as const;\n\nexport type NoteLayout = 'legacy';\n\nexport type DecryptedNote = {\n  value: bigint;\n  blinding: Uint8Array; // 32 bytes BE\n  ownerPk: Uint8Array; // 32 bytes BE — recipient's master spending_pk\n  memo: Uint8Array | null; // 32 bytes when present, null otherwise\n  /// Always `null` in V1 (stealth withdrawn — see ADR-008 postmortem).\n  /// Field kept on the type so callers don't have to branch on a\n  /// missing property; the encryption layer enforces null.\n  stealthNonce: null;\n  /// Always `'legacy'` in V1.\n  layout: NoteLayout;\n};\n\n// ── Plaintext (de)serialisation ──────────────────────────────────────────\n\nfunction ensureLen(label: string, bytes: Uint8Array, expected: number): void {\n  if (bytes.length !== expected) {\n    throw new Error(`${label}: expected ${expected} bytes, got ${bytes.length}`);\n  }\n}\n\n/// Pack a `DecryptedNote` to its on-chain plaintext bytes.\n///\n/// Layout selection (auto):\n///   - `stealthNonce: null, memo: null` → 80B legacy (ADR-003 v1)\n///   - `memo: null` → 80B (no memo)\n///   - `memo: Uint8Array` → 112B (with memo)\nexport function serializeNotePlaintext(note: DecryptedNote): Uint8Array {\n  ensureLen('blinding', note.blinding, 32);\n  ensureLen('ownerPk', note.ownerPk, 32);\n  if (note.memo) ensureLen('memo', note.memo, 32);\n\n  const isWithMemo = !!note.memo;\n  const len = isWithMemo ? PLAINTEXT_WITH_MEMO : PLAINTEXT_NO_MEMO;\n  const out = new Uint8Array(len);\n\n  const view = new DataView(out.buffer, out.byteOffset, 8);\n  view.setBigUint64(0, note.value, true);\n  out.set(note.blinding, 8);\n  out.set(note.ownerPk, 40);\n  out[72] = isWithMemo ? 0x01 : 0x00;\n  if (isWithMemo) out.set(note.memo!, 80);\n  return out;\n}\n\n/// Inverse of `serializeNotePlaintext`. Throws on malformed inputs.\nexport function parseNotePlaintext(plaintext: Uint8Array): DecryptedNote {\n  const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);\n  const flag = plaintext.length >= 73 ? plaintext[72] : 0xff;\n\n  if (plaintext.length === PLAINTEXT_NO_MEMO && flag === 0x00) {\n    return {\n      value: view.getBigUint64(0, true),\n      blinding: plaintext.slice(8, 40),\n      ownerPk: plaintext.slice(40, 72),\n      memo: null,\n      stealthNonce: null,\n      layout: 'legacy',\n    };\n  }\n  if (plaintext.length === PLAINTEXT_WITH_MEMO && flag === 0x01) {\n    return {\n      value: view.getBigUint64(0, true),\n      blinding: plaintext.slice(8, 40),\n      ownerPk: plaintext.slice(40, 72),\n      memo: plaintext.slice(80, 112),\n      stealthNonce: null,\n      layout: 'legacy',\n    };\n  }\n  throw new Error(\n    `note plaintext: unrecognized layout (length ${plaintext.length}, memo flag ${flag.toString(16)})`,\n  );\n}\n\n// ── Key agreement ────────────────────────────────────────────────────────\n\n/// HKDF-SHA256 with ADR-003 salt + commitment-as-info → 32-byte key.\nfunction deriveSymmetricKey(sharedSecret: Uint8Array, commitment: Uint8Array): Uint8Array {\n  return hkdf(sha256, sharedSecret, HKDF_SALT, commitment, 32);\n}\n\n/// `nonce = blake2b256(eph_pk || commitment)[:12]`.\nfunction deriveNonce(ephemeralPk: Uint8Array, commitment: Uint8Array): Uint8Array {\n  ensureLen('ephemeralPk', ephemeralPk, EPHEMERAL_PK_LEN);\n  ensureLen('commitment', commitment, COMMITMENT_LEN);\n  const concat = new Uint8Array(ephemeralPk.length + commitment.length);\n  concat.set(ephemeralPk, 0);\n  concat.set(commitment, ephemeralPk.length);\n  // dkLen 32 default; we slice the first 12 bytes for the AEAD nonce.\n  return blake2b(concat, { dkLen: 32 }).slice(0, NONCE_LEN);\n}\n\n// ── Encrypt / decrypt ────────────────────────────────────────────────────\n\nexport type EncryptedNoteBlob = {\n  /// X25519 ephemeral pubkey published with the blob. Pure scalar mult,\n  /// reveals nothing about the recipient.\n  ephemeralPk: Uint8Array; // 32 bytes\n  /// Plaintext + Poly1305 tag (96 or 128 bytes total).\n  ciphertext: Uint8Array;\n  /// Mirrors the on-chain commitment for AAD binding.\n  commitment: Uint8Array; // 32 bytes\n};\n\nexport type EncryptArgs = {\n  /// Plaintext note to encrypt — recipient's ownerPk + (optional)\n  /// `stealthNonce` are committed to inside.\n  note: DecryptedNote;\n  /// Recipient's X25519 viewing-key pubkey (per ADR-002).\n  recipientViewingPk: Uint8Array; // 32 bytes\n  /// Note commitment (Poseidon4 of value/owner_pk/blinding/memo). Used as\n  /// AEAD AAD + HKDF info — binds the ciphertext to a single commitment.\n  commitment: Uint8Array; // 32 bytes\n  /// Optional ephemeral keypair override (for deterministic test vectors).\n  /// Production code generates fresh randomness per note.\n  ephemeralKeypairOverride?: { sk: Uint8Array; pk: Uint8Array };\n};\n\n/// Encrypt a note for a recipient. Returns the publishable blob fields.\n///\n/// Domain-separation guarantees:\n///   - HKDF info=commitment makes the AEAD key unique per note (a leak\n///     of one key doesn't cascade)\n///   - AAD=commitment makes the ciphertext non-portable (can't be moved\n///     to a different commitment without breaking decryption)\n///   - Deterministic nonce (eph_pk + commitment) avoids the catastrophic\n///     \"weak browser entropy\" path; uniqueness is by construction since\n///     ephemeral_pk is fresh per call.\nexport function encryptNote(args: EncryptArgs): EncryptedNoteBlob {\n  ensureLen('recipientViewingPk', args.recipientViewingPk, 32);\n  ensureLen('commitment', args.commitment, COMMITMENT_LEN);\n\n  let ephSk: Uint8Array;\n  let ephPk: Uint8Array;\n  if (args.ephemeralKeypairOverride) {\n    ephSk = args.ephemeralKeypairOverride.sk;\n    ephPk = args.ephemeralKeypairOverride.pk;\n    ensureLen('ephemeralKeypairOverride.sk', ephSk, 32);\n    ensureLen('ephemeralKeypairOverride.pk', ephPk, 32);\n  } else {\n    ephSk = x25519.utils.randomPrivateKey();\n    ephPk = x25519.getPublicKey(ephSk);\n  }\n\n  const sharedSecret = x25519.getSharedSecret(ephSk, args.recipientViewingPk);\n  const key = deriveSymmetricKey(sharedSecret, args.commitment);\n  const nonce = deriveNonce(ephPk, args.commitment);\n\n  const plaintext = serializeNotePlaintext(args.note);\n  const cipher = chacha20poly1305(key, nonce, args.commitment);\n  const ciphertext = cipher.encrypt(plaintext);\n\n  if (\n    ciphertext.length !== CIPHERTEXT_NO_MEMO\n    && ciphertext.length !== CIPHERTEXT_WITH_MEMO\n  ) {\n    throw new Error(`encryptNote: unexpected ciphertext length ${ciphertext.length}`);\n  }\n  return {\n    ephemeralPk: ephPk,\n    ciphertext,\n    commitment: args.commitment,\n  };\n}\n\n/// Try to decrypt a blob with the given viewing secret. Returns the note\n/// when the auth tag verifies, or `null` when it doesn't (i.e. \"this\n/// blob isn't mine\"). Never throws on tag-mismatch — that's the\n/// expected hot path during note discovery.\nexport function tryDecryptNote(args: {\n  blob: EncryptedNoteBlob;\n  viewingSk: Uint8Array; // 32 bytes\n}): DecryptedNote | null {\n  ensureLen('viewingSk', args.viewingSk, 32);\n  ensureLen('blob.ephemeralPk', args.blob.ephemeralPk, 32);\n  ensureLen('blob.commitment', args.blob.commitment, COMMITMENT_LEN);\n  const len = args.blob.ciphertext.length;\n  if (len !== CIPHERTEXT_NO_MEMO && len !== CIPHERTEXT_WITH_MEMO) {\n    return null;\n  }\n\n  const sharedSecret = x25519.getSharedSecret(args.viewingSk, args.blob.ephemeralPk);\n  const key = deriveSymmetricKey(sharedSecret, args.blob.commitment);\n  const nonce = deriveNonce(args.blob.ephemeralPk, args.blob.commitment);\n  const cipher = chacha20poly1305(key, nonce, args.blob.commitment);\n  let plaintext: Uint8Array;\n  try {\n    plaintext = cipher.decrypt(args.blob.ciphertext);\n  } catch {\n    // Tag verification failed — not our blob, or corrupted.\n    return null;\n  }\n  try {\n    return parseNotePlaintext(plaintext);\n  } catch {\n    // Decrypted but plaintext layout is bad — bail rather than return\n    // garbage. Should never happen on a well-formed blob.\n    return null;\n  }\n}\n\n/// Strict variant: throws on auth-tag mismatch. Use this when the\n/// caller asserts the blob is theirs (e.g. after a successful trial-decrypt\n/// on a different blob, this one MUST match too).\nexport function decryptNote(args: { blob: EncryptedNoteBlob; viewingSk: Uint8Array }): DecryptedNote {\n  const out = tryDecryptNote(args);\n  if (!out) throw new Error('decryptNote: ciphertext failed Poly1305 verification');\n  return out;\n}\n\n// ── X25519 helpers (re-exported so callers don't import @noble directly) ─\n\n/// Generate a fresh X25519 keypair. Used for per-note ephemerals AND\n/// for the user's long-lived viewing key when initialising private balance.\nexport function generateX25519Keypair(): { sk: Uint8Array; pk: Uint8Array } {\n  const sk = x25519.utils.randomPrivateKey();\n  const pk = x25519.getPublicKey(sk);\n  return { sk, pk };\n}\n\n/// Derive an X25519 viewing pubkey from a 32-byte secret. The secret\n/// itself is produced by ADR-002's HKDF-from-wallet-sig path (see\n/// `private-pool-keys.ts`).\nexport function x25519PublicFromSecret(viewingSk: Uint8Array): Uint8Array {\n  ensureLen('viewingSk', viewingSk, 32);\n  return x25519.getPublicKey(viewingSk);\n}\n\n"],"mappings":"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","names":["fieldToBytes32BE","FIELD_MODULUS","fieldToBytes32BE","bs58","import_ed25519","import_blake2b","import_circomlibjs","fieldToBytes32BE","FIELD_MODULUS","getPoseidon","getPoseidon","FIELD_MODULUS","fieldToBytes32BE"]}