@zkproofport-app/sdk 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -51,20 +51,26 @@ const DEEP_LINK_HOSTS = {
51
51
  * ```typescript
52
52
  * const metadata = CIRCUIT_METADATA['coinbase_attestation'];
53
53
  * console.log(metadata.name); // "Coinbase KYC"
54
- * console.log(metadata.publicInputsCount); // 2
54
+ * console.log(metadata.publicInputsCount); // 128
55
55
  * ```
56
56
  */
57
57
  const CIRCUIT_METADATA = {
58
58
  coinbase_attestation: {
59
59
  name: 'Coinbase KYC',
60
60
  description: 'Prove Coinbase identity verification',
61
- publicInputsCount: 2,
61
+ // 4 x `pub [u8; 32]` in coinbase-attestation/src/main.nr, flattened to
62
+ // bytes: signal_hash, signer_list_merkle_root, scope, nullifier.
63
+ // Read 2 here until 2026-09-04, which was wrong under any reading —
64
+ // the circuit has never had two public inputs, logical or flattened.
65
+ publicInputsCount: 128,
62
66
  publicInputNames: ['signal_hash', 'signer_list_merkle_root'],
63
67
  },
64
68
  coinbase_country_attestation: {
65
69
  name: 'Coinbase Country',
66
70
  description: 'Prove Coinbase country verification',
67
- publicInputsCount: 14,
71
+ // 32 + 32 + (10 x 2) + 1 + 1 + 32 + 32 = 150, counted off
72
+ // coinbase-country-attestation/src/main.nr. Read 14 until 2026-09-04.
73
+ publicInputsCount: 150,
68
74
  publicInputNames: ['signal_hash', 'signer_list_merkle_root', 'country_list', 'country_list_length', 'is_included'],
69
75
  },
70
76
  oidc_domain_attestation: {
@@ -73,8 +79,22 @@ const CIRCUIT_METADATA = {
73
79
  publicInputsCount: 148,
74
80
  publicInputNames: ['pubkey_modulus_limbs', 'domain', 'scope', 'nullifier', 'provider'],
75
81
  },
76
- // Korea Mobile ID (mDL) circuits. Counts are byte-flattened field totals
77
- // from the compiled circuit ABIs: [u8; 32] arrays contribute 32 inputs each.
82
+ // Planned circuit — the identifier is reserved and the circuit exists, but it
83
+ // is not officially supported yet. See CIRCUIT_SUPPORT_STATUS in ./circuits.
84
+ // Forked from coinbase_attestation and shares its four public inputs, so the
85
+ // count is the byte-flattened total (4 x [u8; 32] = 128), matching the
86
+ // COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT indices below. The
87
+ // coinbase_attestation entry above reports a legacy logical count of 2 and is
88
+ // left as-is for backwards compatibility.
89
+ giwa_attestation: {
90
+ name: 'GIWA Attestation',
91
+ description: 'Prove GIWA identity verification (planned — not officially supported yet)',
92
+ publicInputsCount: 128,
93
+ publicInputNames: ['signal_hash', 'signer_list_merkle_root', 'scope', 'nullifier'],
94
+ },
95
+ // Korea Mobile ID (mDL) circuits. Planned — not officially supported yet.
96
+ // Counts are byte-flattened field totals from the compiled circuit ABIs:
97
+ // [u8; 32] arrays contribute 32 inputs each.
78
98
  mdl_kr_ownership: {
79
99
  name: 'Korea Mobile ID — Ownership',
80
100
  description: "Prove valid Korean mobile driver's license ownership with selective disclosure",
@@ -118,6 +138,13 @@ const VERIFIER_ABI = [
118
138
  * Supported networks:
119
139
  * - 84532: Base Sepolia (testnet)
120
140
  * - 8453: Base Mainnet (production)
141
+ * - 91342: GIWA Sepolia (testnet) — where the GIWA account circuit verifies
142
+ *
143
+ * A chain missing from this map is not a quiet degradation: `getDefaultProvider`
144
+ * throws "No RPC endpoint configured for chain N". That is what a caller saw
145
+ * when verifying a GIWA proof on chain 91342 — the verifier contract had been
146
+ * deployed and the proof was valid, but the SDK had no way to reach the chain
147
+ * to ask. Deploying a verifier to a new network means adding it here too.
121
148
  *
122
149
  * @example
123
150
  * ```typescript
@@ -129,6 +156,7 @@ const VERIFIER_ABI = [
129
156
  const RPC_ENDPOINTS = {
130
157
  84532: 'https://sepolia.base.org', // Base Sepolia
131
158
  8453: 'https://mainnet.base.org', // Base Mainnet
159
+ 91342: 'https://sepolia-rpc.giwa.io', // GIWA Sepolia
132
160
  };
133
161
  /**
134
162
  * Default proof request expiration time in milliseconds.
@@ -4234,6 +4262,383 @@ function reconstructBytes32FromFields(fields) {
4234
4262
  return '0x' + bytes;
4235
4263
  }
4236
4264
 
4265
+ /**
4266
+ * Canonical ZKProofport circuit identifiers.
4267
+ *
4268
+ * This module is the single source of truth for circuit IDs. Every other layer
4269
+ * — the mobile app, the demo, the relay, server-side provers — reads its list
4270
+ * from here instead of keeping a private copy that silently drifts.
4271
+ *
4272
+ * It has **no imports and no dependencies**, so it can be pulled in on its own:
4273
+ *
4274
+ * ```typescript
4275
+ * import { CIRCUIT_IDS, isCircuitId } from '@zkproofport-app/sdk/circuits';
4276
+ * ```
4277
+ *
4278
+ * The same names are also re-exported from the package root
4279
+ * (`@zkproofport-app/sdk`) for callers that already import the SDK.
4280
+ *
4281
+ * ## Identifiers are canonical and case-sensitive
4282
+ *
4283
+ * Each ID is the circuit's `name` in its `Nargo.toml`, verbatim. The format is
4284
+ * lowercase with underscores. Hyphenated spellings (`coinbase-kyc`,
4285
+ * `mdl-kr-age`) are directory or UI route names in other repositories — they
4286
+ * are **not** circuit IDs, and passing one produces a nullifier mismatch or a
4287
+ * failed on-chain lookup rather than a clear error.
4288
+ */
4289
+ /**
4290
+ * Canonical circuit identifiers, keyed by a stable constant name.
4291
+ *
4292
+ * Prefer these constants over string literals so a rename is a compile error
4293
+ * rather than a runtime surprise.
4294
+ *
4295
+ * @example
4296
+ * ```typescript
4297
+ * import { CIRCUIT_IDS } from '@zkproofport-app/sdk/circuits';
4298
+ *
4299
+ * const relay = await sdk.createRelayRequest(CIRCUIT_IDS.COINBASE_ATTESTATION, {
4300
+ * scope: 'myapp.com',
4301
+ * });
4302
+ * ```
4303
+ */
4304
+ const CIRCUIT_IDS = Object.freeze({
4305
+ /** Coinbase KYC attestation. Officially supported. */
4306
+ COINBASE_ATTESTATION: 'coinbase_attestation',
4307
+ /** Coinbase country attestation (inclusion / exclusion). Officially supported. */
4308
+ COINBASE_COUNTRY_ATTESTATION: 'coinbase_country_attestation',
4309
+ /** OIDC email-domain attestation. Officially supported. */
4310
+ OIDC_DOMAIN_ATTESTATION: 'oidc_domain_attestation',
4311
+ /** GIWA attestation. Planned — not officially supported yet. */
4312
+ GIWA_ATTESTATION: 'giwa_attestation',
4313
+ /** Korea Mobile ID ownership. Planned — not officially supported yet. */
4314
+ MDL_KR_OWNERSHIP: 'mdl_kr_ownership',
4315
+ /** Korea Mobile ID age threshold. Planned — not officially supported yet. */
4316
+ MDL_KR_AGE: 'mdl_kr_age',
4317
+ /** Korea Mobile ID si/do region. Planned — not officially supported yet. */
4318
+ MDL_KR_REGION: 'mdl_kr_region',
4319
+ });
4320
+ /**
4321
+ * Support status for every circuit.
4322
+ *
4323
+ * The record is exhaustive over {@link CircuitId}: adding an identifier without
4324
+ * giving it a status is a compile error, so this list cannot drift from
4325
+ * {@link CIRCUIT_IDS}.
4326
+ *
4327
+ * @example
4328
+ * ```typescript
4329
+ * import { CIRCUIT_SUPPORT_STATUS } from '@zkproofport-app/sdk/circuits';
4330
+ *
4331
+ * CIRCUIT_SUPPORT_STATUS.coinbase_attestation; // 'supported'
4332
+ * CIRCUIT_SUPPORT_STATUS.mdl_kr_age; // 'planned'
4333
+ * ```
4334
+ */
4335
+ const CIRCUIT_SUPPORT_STATUS = Object.freeze({
4336
+ coinbase_attestation: 'supported',
4337
+ coinbase_country_attestation: 'supported',
4338
+ oidc_domain_attestation: 'supported',
4339
+ giwa_attestation: 'planned',
4340
+ mdl_kr_ownership: 'planned',
4341
+ mdl_kr_age: 'planned',
4342
+ mdl_kr_region: 'planned',
4343
+ });
4344
+ /**
4345
+ * Where each circuit's verification key lives inside the `zkproofport/circuits`
4346
+ * repository, relative to its root.
4347
+ *
4348
+ * Off-chain verification needs the verification key and nothing else — no
4349
+ * circuit bytecode, no witness. The key is public by nature: it is what anyone
4350
+ * checking a proof must already have, and the Solidity verifiers deployed
4351
+ * on-chain embed the same key.
4352
+ *
4353
+ * Exhaustive over {@link CircuitId} on purpose. A new circuit whose key path is
4354
+ * not listed here fails to compile rather than failing at verification time,
4355
+ * where the only symptom would be a proof that cannot be checked.
4356
+ */
4357
+ const CIRCUIT_VK_PATHS = Object.freeze({
4358
+ coinbase_attestation: 'coinbase-attestation/target/vk',
4359
+ coinbase_country_attestation: 'coinbase-country-attestation/target/vk',
4360
+ oidc_domain_attestation: 'oidc-domain-attestation/target/vk',
4361
+ giwa_attestation: 'giwa-attestation/target/vk',
4362
+ mdl_kr_ownership: 'mdl/kr-ownership/target/vk',
4363
+ mdl_kr_age: 'mdl/kr-age/target/vk',
4364
+ mdl_kr_region: 'mdl/kr-region/target/vk',
4365
+ });
4366
+ /**
4367
+ * Every canonical circuit identifier, in declaration order.
4368
+ *
4369
+ * Includes circuits that are still `planned`. Use {@link SUPPORTED_CIRCUIT_IDS}
4370
+ * when you only want the ones that are officially supported today.
4371
+ */
4372
+ const ALL_CIRCUIT_IDS = Object.freeze(Object.values(CIRCUIT_IDS));
4373
+ /**
4374
+ * Circuits that are officially supported today: the two Coinbase circuits and
4375
+ * OIDC domain attestation.
4376
+ *
4377
+ * Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.
4378
+ */
4379
+ const SUPPORTED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'supported'));
4380
+ /**
4381
+ * Circuits whose identifiers are reserved but which are not officially
4382
+ * supported yet: GIWA attestation and the three Korea Mobile ID circuits.
4383
+ *
4384
+ * Derived from {@link CIRCUIT_SUPPORT_STATUS}, so it cannot fall out of sync.
4385
+ */
4386
+ const PLANNED_CIRCUIT_IDS = Object.freeze(ALL_CIRCUIT_IDS.filter((id) => CIRCUIT_SUPPORT_STATUS[id] === 'planned'));
4387
+ /**
4388
+ * Narrows an unknown value to a canonical circuit identifier.
4389
+ *
4390
+ * Returns `false` for hyphenated route names such as `'coinbase-kyc'`, which
4391
+ * are not circuit IDs.
4392
+ *
4393
+ * @example
4394
+ * ```typescript
4395
+ * isCircuitId('coinbase_attestation'); // true
4396
+ * isCircuitId('coinbase-kyc'); // false
4397
+ * ```
4398
+ */
4399
+ function isCircuitId(value) {
4400
+ return typeof value === 'string' && ALL_CIRCUIT_IDS.includes(value);
4401
+ }
4402
+ /**
4403
+ * Narrows an unknown value to a circuit identifier that is officially supported
4404
+ * today. Planned circuits return `false`.
4405
+ *
4406
+ * @example
4407
+ * ```typescript
4408
+ * isSupportedCircuitId('oidc_domain_attestation'); // true
4409
+ * isSupportedCircuitId('mdl_kr_age'); // false — planned
4410
+ * ```
4411
+ */
4412
+ function isSupportedCircuitId(value) {
4413
+ return isCircuitId(value) && CIRCUIT_SUPPORT_STATUS[value] === 'supported';
4414
+ }
4415
+ /**
4416
+ * Returns the support status of a circuit.
4417
+ *
4418
+ * @param circuit - A canonical circuit identifier.
4419
+ * @throws Error when the value is not a canonical circuit identifier.
4420
+ *
4421
+ * @example
4422
+ * ```typescript
4423
+ * getCircuitSupportStatus('giwa_attestation'); // 'planned'
4424
+ * ```
4425
+ */
4426
+ function getCircuitSupportStatus(circuit) {
4427
+ if (!isCircuitId(circuit)) {
4428
+ throw new Error(`Unknown circuit '${String(circuit)}'. Expected one of: ${ALL_CIRCUIT_IDS.join(', ')}`);
4429
+ }
4430
+ return CIRCUIT_SUPPORT_STATUS[circuit];
4431
+ }
4432
+
4433
+ /**
4434
+ * Off-chain proof verification — checking a proof against its verification key,
4435
+ * with no chain, no RPC, and no gas.
4436
+ *
4437
+ * WHY THIS EXISTS. The SDK had only `verifyProofOnChain`, and every caller that
4438
+ * wanted an off-chain check called that instead and relabelled the result. The
4439
+ * demo page did exactly this from its first commit: pressing "Off-Chain Verify"
4440
+ * ran an on-chain verification and printed "Off-Chain Verification Passed!".
4441
+ * The answer was true and the label was a lie, which is the worst combination —
4442
+ * nothing looked broken. The mobile app has always verified off-chain properly
4443
+ * (through mopro's native Barretenberg); it was only the JavaScript side that
4444
+ * had no such thing.
4445
+ *
4446
+ * HOW IT WORKS. `bb` proofs are checked by `acirVerifyUltraKeccakHonk(proof,
4447
+ * vk)` — the keccak variant, matching how these proofs are produced. Two things
4448
+ * have to line up:
4449
+ *
4450
+ * The version. The verification key format is not stable across bb releases:
4451
+ * the pinned build emits a 1816-byte key where bb 0.87.0 and 1.2.0 emit 1760.
4452
+ * `@aztec/bb.js@1.0.0-nightly.20250723` is the WASM build of the same bb the
4453
+ * circuits were compiled with. A different one will reject valid proofs.
4454
+ *
4455
+ * The oracle hash. Proofs are generated with `--oracle_hash keccak` so the
4456
+ * Solidity verifiers can check them, so the poseidon-flavoured verify — which
4457
+ * is what `BarretenbergVerifier.verifyUltraHonkProof` uses — says false for a
4458
+ * perfectly good proof.
4459
+ *
4460
+ * THE PROOF ARRIVES IN TWO PIECES. The mobile app splits what Barretenberg
4461
+ * produced into `proof` and `publicInputs` before sending it, because the
4462
+ * Solidity verifier takes them separately. Verifying needs them joined back, in
4463
+ * that order, which is what `reconstructHonkProof` does.
4464
+ */
4465
+ /**
4466
+ * Where verification keys are published.
4467
+ *
4468
+ * Not configurable, deliberately: a caller who could point this somewhere else
4469
+ * could point it at a key of their choosing, and "the proof verified" would
4470
+ * then mean nothing. Same repository the mobile app downloads circuit data
4471
+ * from.
4472
+ */
4473
+ const CIRCUITS_VK_BASE = 'https://raw.githubusercontent.com/zkproofport/circuits/main';
4474
+ /** How long a fetched verification key is reused. Keys change only on a recompile. */
4475
+ const VK_CACHE_TTL_MS = 60 * 60 * 1000;
4476
+ const vkCache = new Map();
4477
+ /** @internal Hex string (with or without 0x) to bytes. */
4478
+ function hexToBytes(hex) {
4479
+ const clean = hex.startsWith('0x') || hex.startsWith('0X') ? hex.slice(2) : hex;
4480
+ if (clean.length === 0)
4481
+ return new Uint8Array(0);
4482
+ if (clean.length % 2 !== 0) {
4483
+ throw new Error(`Hex string has an odd length (${clean.length}); it cannot be bytes`);
4484
+ }
4485
+ if (!/^[0-9a-fA-F]*$/.test(clean)) {
4486
+ throw new Error('Hex string contains non-hex characters');
4487
+ }
4488
+ const out = new Uint8Array(clean.length / 2);
4489
+ for (let i = 0; i < out.length; i++) {
4490
+ out[i] = parseInt(clean.substr(i * 2, 2), 16);
4491
+ }
4492
+ return out;
4493
+ }
4494
+ /**
4495
+ * @internal Public inputs as the flat byte run Barretenberg expects: each one a
4496
+ * 32-byte big-endian field element, concatenated in order.
4497
+ *
4498
+ * The relay carries them as hex strings that are USUALLY already 32 bytes, but
4499
+ * a value that happens to have leading zeros can arrive short (`0x01`). Left-
4500
+ * padding here rather than trusting the sender is the difference between a
4501
+ * verification that works and one that fails on roughly one proof in a hundred,
4502
+ * for no reason the caller can see.
4503
+ */
4504
+ function packPublicInputs(publicInputs) {
4505
+ const out = new Uint8Array(publicInputs.length * 32);
4506
+ publicInputs.forEach((input, i) => {
4507
+ const bytes = hexToBytes(input);
4508
+ if (bytes.length > 32) {
4509
+ throw new Error(`Public input ${i} is ${bytes.length} bytes; a field element is at most 32`);
4510
+ }
4511
+ out.set(bytes, i * 32 + (32 - bytes.length));
4512
+ });
4513
+ return out;
4514
+ }
4515
+ /**
4516
+ * Fetch a circuit's verification key, with a small cache.
4517
+ *
4518
+ * @internal
4519
+ */
4520
+ async function fetchVerificationKey(circuit, timeoutMs) {
4521
+ const cached = vkCache.get(circuit);
4522
+ if (cached && Date.now() - cached.at < VK_CACHE_TTL_MS)
4523
+ return cached.vk;
4524
+ const vkPath = CIRCUIT_VK_PATHS[circuit];
4525
+ if (!vkPath) {
4526
+ throw new Error(`No verification key path for circuit '${circuit}'. ` +
4527
+ `Add it to CIRCUIT_VK_PATHS, or pass verificationKey directly.`);
4528
+ }
4529
+ const url = `${CIRCUITS_VK_BASE}/${vkPath}`;
4530
+ const controller = new AbortController();
4531
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
4532
+ try {
4533
+ const resp = await fetch(url, { signal: controller.signal });
4534
+ if (!resp.ok) {
4535
+ throw new Error(`Could not fetch the verification key (HTTP ${resp.status}) from ${url}`);
4536
+ }
4537
+ const vk = new Uint8Array(await resp.arrayBuffer());
4538
+ // A 404 page or an HTML redirect arrives as a successful response full of
4539
+ // text, and would reach Barretenberg as a "verification key" that fails for
4540
+ // reasons no one could guess from the message.
4541
+ if (vk.length < 512) {
4542
+ throw new Error(`The verification key at ${url} is ${vk.length} bytes, far too small to be one. ` +
4543
+ `The path is probably wrong and something else was served.`);
4544
+ }
4545
+ vkCache.set(circuit, { vk, at: Date.now() });
4546
+ return vk;
4547
+ }
4548
+ finally {
4549
+ clearTimeout(timer);
4550
+ }
4551
+ }
4552
+ /**
4553
+ * Verify a proof off-chain, against its verification key.
4554
+ *
4555
+ * Needs `@aztec/bb.js` at the pinned version. It is an optional peer dependency
4556
+ * — the WASM is large and most callers verify on-chain — so the failure to have
4557
+ * it installed says so plainly rather than surfacing as a module-not-found.
4558
+ *
4559
+ * @param circuit - Canonical circuit identifier
4560
+ * @param proof - Proof bytes as hex, WITHOUT public inputs (as the relay sends it)
4561
+ * @param publicInputs - Public inputs as hex strings, in circuit order
4562
+ * @param options - Optional verification key and timeout
4563
+ * @returns `{ valid }`, or `{ valid: false, error }` when the check could not run
4564
+ *
4565
+ * @example
4566
+ * ```typescript
4567
+ * const result = await verifyProofOffChain(
4568
+ * 'coinbase_attestation',
4569
+ * response.proof,
4570
+ * response.publicInputs
4571
+ * );
4572
+ * if (result.valid) console.log('Proof is valid, no chain involved');
4573
+ * ```
4574
+ */
4575
+ async function verifyProofOffChain(circuit, proof, publicInputs, options = {}) {
4576
+ const timeoutMs = options.timeoutMs ?? 30000;
4577
+ let bb;
4578
+ try {
4579
+ bb = await import('@aztec/bb.js');
4580
+ }
4581
+ catch {
4582
+ return {
4583
+ valid: false,
4584
+ error: 'Off-chain verification needs @aztec/bb.js. Install the pinned version: ' +
4585
+ 'npm install @aztec/bb.js@1.0.0-nightly.20250723',
4586
+ };
4587
+ }
4588
+ let api;
4589
+ try {
4590
+ const proofBytes = hexToBytes(proof);
4591
+ if (proofBytes.length === 0) {
4592
+ return { valid: false, error: 'Proof is empty' };
4593
+ }
4594
+ const publicInputBytes = packPublicInputs(publicInputs);
4595
+ const vk = options.verificationKey ?? (await fetchVerificationKey(circuit, timeoutMs));
4596
+ // Public inputs first, then the proof — the order Barretenberg wrote them in
4597
+ // before the mobile app split them apart for the Solidity verifier.
4598
+ const full = bb.reconstructHonkProof(publicInputBytes, proofBytes);
4599
+ api = (await bb.Barretenberg.new({ threads: 1 }));
4600
+ // THE VERIFY MUST MATCH HOW THE PROOF WAS MADE, AND THERE ARE FOUR OF THEM.
4601
+ //
4602
+ // `scripts/build.sh` in the circuits repo proves with `--oracle_hash keccak`
4603
+ // and ZK on — so the only entry point that answers correctly is the one that
4604
+ // is BOTH. Measured against a real proof of the Korea Mobile ID age circuit,
4605
+ // freshly generated against its own key:
4606
+ //
4607
+ // acirVerifyUltraKeccakZkHonk true ← the right one
4608
+ // acirVerifyUltraKeccakHonk TRAPS with a bare "unreachable"
4609
+ // acirVerifyUltraZKHonk false (poseidon: wrong oracle hash)
4610
+ //
4611
+ // The middle row is why this took a while: picking keccak-without-ZK does
4612
+ // not return false, it kills the WebAssembly instance with a message that
4613
+ // names nothing. It looks identical to a malformed proof, to a mismatched
4614
+ // key, and to a missing SRS.
4615
+ //
4616
+ // Two more things that produce that same trap, both required:
4617
+ // initSRSForCircuitSize must be called before the first verify.
4618
+ // The key must be a RawBuffer — a plain Uint8Array is marshalled as a
4619
+ // length-prefixed vector, so its length is read as key material.
4620
+ // Both are what bb.js's own BarretenbergVerifier does before verifying.
4621
+ await api.initSRSForCircuitSize(0);
4622
+ const valid = await api.acirVerifyUltraKeccakZkHonk(full, new bb.RawBuffer(vk));
4623
+ return { valid };
4624
+ }
4625
+ catch (error) {
4626
+ return { valid: false, error: error instanceof Error ? error.message : String(error) };
4627
+ }
4628
+ finally {
4629
+ // The WASM instance holds worker threads; leaving them running keeps a Node
4630
+ // process alive after the check is done.
4631
+ if (api) {
4632
+ try {
4633
+ await api.destroy();
4634
+ }
4635
+ catch {
4636
+ /* nothing useful to do if teardown fails */
4637
+ }
4638
+ }
4639
+ }
4640
+ }
4641
+
4237
4642
  /**
4238
4643
  * Proofport SDK - Main class
4239
4644
  */
@@ -4880,6 +5285,38 @@ class ProofportSDK {
4880
5285
  };
4881
5286
  return verifyProofOnChain(response.circuit, parsedProof, providerOrSigner, customVerifier, responseVerifier);
4882
5287
  }
5288
+ /**
5289
+ * Verifies a proof response off-chain, against the circuit's verification key.
5290
+ *
5291
+ * No chain, no RPC, no gas, and no verifier contract — so this works for a
5292
+ * circuit whose verifier has not been deployed anywhere, and it does not
5293
+ * depend on a network being reachable. What it does not give you is the
5294
+ * chain's own opinion: use {@link verifyResponseOnChain} when the answer has
5295
+ * to be one a contract will agree with.
5296
+ *
5297
+ * Requires the optional peer dependency:
5298
+ * `npm install @aztec/bb.js@1.0.0-nightly.20250723`. The version is exact —
5299
+ * verification keys are not portable across bb releases, so a different build
5300
+ * rejects valid proofs.
5301
+ *
5302
+ * @param response - Proof response from the relay
5303
+ * @param options - Optional verification key (skips the fetch) and timeout
5304
+ * @returns `{ valid }`, or `{ valid: false, error }` if the check could not run
5305
+ *
5306
+ * @example
5307
+ * ```typescript
5308
+ * const result = await sdk.verifyResponseOffChain(response);
5309
+ * if (result.valid) {
5310
+ * console.log('Valid, and nothing was asked of any chain');
5311
+ * }
5312
+ * ```
5313
+ */
5314
+ async verifyResponseOffChain(response, options) {
5315
+ if (response.status !== 'completed' || !response.proof || !response.publicInputs) {
5316
+ return { valid: false, error: 'Invalid or incomplete response' };
5317
+ }
5318
+ return verifyProofOffChain(response.circuit, response.proof, response.publicInputs, options);
5319
+ }
4883
5320
  // ============ Utility Methods ============
4884
5321
  /**
4885
5322
  * Gets the deployed verifier contract address for a circuit.
@@ -5553,5 +5990,5 @@ ProofportSDK.WALLET_SIGNATURE_CIRCUITS = [
5553
5990
  'coinbase_country_attestation',
5554
5991
  ];
5555
5992
 
5556
- export { COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, MDL_KR_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, ProofportSDK, ProofportSDK as default, extractDomainFromPublicInputs, extractNullifierFromPublicInputs, extractScopeFromPublicInputs };
5993
+ export { ALL_CIRCUIT_IDS, CIRCUIT_IDS, CIRCUIT_SUPPORT_STATUS, CIRCUIT_VK_PATHS, COINBASE_ATTESTATION_PUBLIC_INPUT_LAYOUT, COINBASE_COUNTRY_PUBLIC_INPUT_LAYOUT, MDL_KR_PUBLIC_INPUT_LAYOUT, OIDC_DOMAIN_ATTESTATION_PUBLIC_INPUT_LAYOUT, PLANNED_CIRCUIT_IDS, ProofportSDK, SUPPORTED_CIRCUIT_IDS, ProofportSDK as default, extractDomainFromPublicInputs, extractNullifierFromPublicInputs, extractScopeFromPublicInputs, getCircuitSupportStatus, isCircuitId, isSupportedCircuitId, verifyProofOffChain };
5557
5994
  //# sourceMappingURL=index.mjs.map