{"version":3,"sources":["../src/prover.ts"],"sourcesContent":["// 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"],"mappings":"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","names":[]}