@cofhe/sdk 0.6.0 → 0.7.0

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/acps/acp.ts +411 -0
  3. package/acps/index.ts +70 -0
  4. package/{permits → acps}/onchain-utils.ts +49 -24
  5. package/acps/sealing.ts +90 -0
  6. package/acps/signature.ts +89 -0
  7. package/acps/store.ts +172 -0
  8. package/acps/test/acp.test.ts +615 -0
  9. package/acps/test/localstorage.test.ts +105 -0
  10. package/acps/test/sealing.test.ts +77 -0
  11. package/acps/test/store.test.ts +88 -0
  12. package/acps/test/validation.test.ts +361 -0
  13. package/acps/test-utils.ts +32 -0
  14. package/acps/types.ts +252 -0
  15. package/acps/validation.ts +392 -0
  16. package/adapters/test/ethers5.test.ts +4 -1
  17. package/adapters/test/ethers6.test.ts +4 -1
  18. package/adapters/test/wagmi.test.ts +5 -2
  19. package/chains/chains/stagingCofhe.ts +21 -0
  20. package/chains/index.ts +3 -1
  21. package/chains/test/chains.test.ts +2 -1
  22. package/core/acps.ts +625 -0
  23. package/core/client.ts +136 -39
  24. package/core/clientTypes.ts +52 -41
  25. package/core/config.ts +66 -5
  26. package/core/debug.ts +72 -0
  27. package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
  28. package/core/decrypt/apiError.ts +104 -0
  29. package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
  30. package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
  31. package/core/decrypt/decryptForTxBuilder.ts +102 -102
  32. package/core/decrypt/decryptForViewBuilder.ts +90 -90
  33. package/core/decrypt/submitRetry.ts +38 -30
  34. package/core/decrypt/tnDecryptV1.ts +5 -5
  35. package/core/decrypt/tnDecryptV2.ts +45 -34
  36. package/core/decrypt/tnSealOutputV1.ts +4 -3
  37. package/core/decrypt/tnSealOutputV2.ts +44 -31
  38. package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
  39. package/core/encrypt/encryptInputsBuilder.ts +86 -52
  40. package/core/encrypt/zkPackProveVerify.ts +25 -18
  41. package/core/error.ts +34 -6
  42. package/core/index.ts +14 -14
  43. package/core/test/acpDefaults.test.ts +52 -0
  44. package/core/test/acps.test.ts +596 -0
  45. package/core/test/apiError.test.ts +130 -0
  46. package/core/test/client.test.ts +22 -19
  47. package/core/test/config.test.ts +25 -5
  48. package/core/test/decrypt.test.ts +40 -35
  49. package/core/test/decryptBuilders.test.ts +68 -68
  50. package/core/test/decryptErrorCodes.test.ts +217 -0
  51. package/core/test/encryptInputsBuilder.test.ts +72 -41
  52. package/core/test/pollCallbacks.test.ts +89 -18
  53. package/core/test/stagingRedirect.ts +18 -0
  54. package/core/test/submitRetry.test.ts +182 -0
  55. package/core/types.ts +9 -69
  56. package/dist/acp-Wi6isVQI.d.cts +407 -0
  57. package/dist/acp-Wi6isVQI.d.ts +407 -0
  58. package/dist/acps.cjs +1081 -0
  59. package/dist/acps.d.cts +480 -0
  60. package/dist/acps.d.ts +480 -0
  61. package/dist/acps.js +2 -0
  62. package/dist/chains.cjs +14 -1
  63. package/dist/chains.d.cts +30 -1
  64. package/dist/chains.d.ts +30 -1
  65. package/dist/chains.js +1 -1
  66. package/dist/{chunk-PE5V5CCV.js → chunk-43USWPEH.js} +1000 -604
  67. package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
  68. package/dist/chunk-Q7CBWGQX.js +1029 -0
  69. package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
  70. package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
  71. package/dist/core.cjs +1431 -1026
  72. package/dist/core.d.cts +55 -10
  73. package/dist/core.d.ts +55 -10
  74. package/dist/core.js +3 -3
  75. package/dist/node.cjs +1356 -976
  76. package/dist/node.d.cts +2 -2
  77. package/dist/node.d.ts +2 -2
  78. package/dist/node.js +3 -3
  79. package/dist/web.cjs +1356 -976
  80. package/dist/web.d.cts +2 -2
  81. package/dist/web.d.ts +2 -2
  82. package/dist/web.js +3 -3
  83. package/node/test/inherited.test.ts +75 -65
  84. package/node/test/tfheinit.test.ts +23 -8
  85. package/package.json +6 -6
  86. package/web/test/client.web.test.ts +5 -1
  87. package/web/test/inherited.web.test.ts +75 -65
  88. package/web/test/tfheinit.web.test.ts +14 -5
  89. package/web/test/worker.config.web.test.ts +38 -23
  90. package/web/test/worker.output.web.test.ts +25 -24
  91. package/core/encrypt/encryptUtils.ts +0 -67
  92. package/core/permits.ts +0 -206
  93. package/core/test/permits.test.ts +0 -534
  94. package/dist/chunk-VB62WYPL.js +0 -978
  95. package/dist/permit-DnVMDT5h.d.cts +0 -376
  96. package/dist/permit-DnVMDT5h.d.ts +0 -376
  97. package/dist/permits.cjs +0 -1026
  98. package/dist/permits.d.cts +0 -353
  99. package/dist/permits.d.ts +0 -353
  100. package/dist/permits.js +0 -2
  101. package/permits/index.ts +0 -68
  102. package/permits/permit.ts +0 -385
  103. package/permits/sealing.ts +0 -131
  104. package/permits/signature.ts +0 -79
  105. package/permits/store.ts +0 -157
  106. package/permits/test/localstorage.test.ts +0 -113
  107. package/permits/test/permit.test.ts +0 -557
  108. package/permits/test/sealing.test.ts +0 -84
  109. package/permits/test/store.test.ts +0 -88
  110. package/permits/test/validation.test.ts +0 -361
  111. package/permits/test-utils.ts +0 -28
  112. package/permits/types.ts +0 -204
  113. package/permits/validation.ts +0 -327
  114. /package/{permits → acps}/utils.ts +0 -0
@@ -1,9 +1,11 @@
1
- import { type Permission } from '@/permits';
1
+ import { type ACPPublic } from '@/acps';
2
+ import { cofheFetch } from '../debug.js';
2
3
 
3
4
  import { CofheError, CofheErrorCode } from '../error';
4
5
  import { type DecryptPollCallbackFunction } from '../types';
5
6
  import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
6
7
  import { computeMinuteRampPollIntervalMs } from './polling.js';
8
+ import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
7
9
  import { classifySubmitResponse, normalize404RetryTimeoutMs, throwIfSubmitRetryTimedOut } from './submitRetry.js';
8
10
 
9
11
  // Polling configuration
@@ -177,7 +179,7 @@ async function submitDecryptRequestV2(
177
179
  thresholdNetworkUrl: string,
178
180
  ctHash: bigint | string,
179
181
  chainId: number,
180
- permission: Permission | null,
182
+ acp: ACPPublic | null,
181
183
  overallStartTime: number,
182
184
  retry404TimeoutMs: number,
183
185
  onPoll?: DecryptPollCallbackFunction
@@ -185,28 +187,32 @@ async function submitDecryptRequestV2(
185
187
  const body: {
186
188
  ct_tempkey: string;
187
189
  host_chain_id: number;
188
- permit?: Permission;
190
+ acp?: ACPPublic;
189
191
  } = {
190
192
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
191
193
  host_chain_id: chainId,
192
194
  };
193
195
 
194
- if (permission) {
195
- body.permit = permission;
196
+ if (acp) {
197
+ body.acp = acp;
196
198
  }
197
199
 
198
200
  let attemptIndex = 0;
201
+ let last404ApiErrorMessage: string | undefined;
199
202
 
200
203
  for (;;) {
201
204
  let response: Response;
202
205
  try {
203
- response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
204
- method: 'POST',
205
- headers: {
206
- 'Content-Type': 'application/json',
207
- },
208
- body: JSON.stringify(body),
209
- });
206
+ response = await cofheFetch(
207
+ `${thresholdNetworkUrl}/v2/decrypt`,
208
+ /*op:decrypt*/ {
209
+ method: 'POST',
210
+ headers: {
211
+ 'Content-Type': 'application/json',
212
+ },
213
+ body: JSON.stringify(body),
214
+ }
215
+ );
210
216
  } catch (e) {
211
217
  throw new CofheError({
212
218
  code: CofheErrorCode.DecryptFailed,
@@ -221,17 +227,20 @@ async function submitDecryptRequestV2(
221
227
  });
222
228
  }
223
229
 
224
- const responseClassification = await classifySubmitResponse({ response });
230
+ const responseClassification = await classifySubmitResponse({
231
+ response,
232
+ fallbackErrorCode: CofheErrorCode.DecryptFailed,
233
+ });
225
234
 
226
235
  if (responseClassification.kind === 'fatal-http') {
227
236
  throw new CofheError({
228
- code: CofheErrorCode.DecryptFailed,
237
+ code: responseClassification.cofheErrorCode,
238
+ apiErrorCode: responseClassification.apiErrorCode,
229
239
  message: `decrypt request failed: ${responseClassification.errorMessage}`,
230
240
  hint: 'Check the threshold network URL and request parameters.',
231
241
  context: {
232
242
  thresholdNetworkUrl,
233
- status: response.status,
234
- statusText: response.statusText,
243
+ status: responseClassification.status,
235
244
  body,
236
245
  attemptIndex,
237
246
  },
@@ -285,6 +294,10 @@ async function submitDecryptRequestV2(
285
294
  });
286
295
  }
287
296
 
297
+ if (responseClassification.status === 404) {
298
+ last404ApiErrorMessage = responseClassification.apiErrorMessage;
299
+ }
300
+
288
301
  const elapsedMs = Date.now() - overallStartTime;
289
302
 
290
303
  throwIfSubmitRetryTimedOut({
@@ -297,6 +310,7 @@ async function submitDecryptRequestV2(
297
310
  thresholdNetworkUrl,
298
311
  body,
299
312
  attemptIndex,
313
+ lastKnownErrorMessage: last404ApiErrorMessage,
300
314
  });
301
315
 
302
316
  onPoll?.({
@@ -352,12 +366,15 @@ async function pollDecryptStatusV2(
352
366
 
353
367
  let response: Response;
354
368
  try {
355
- response = await fetch(`${thresholdNetworkUrl}/v2/decrypt/${requestId}`, {
356
- method: 'GET',
357
- headers: {
358
- 'Content-Type': 'application/json',
359
- },
360
- });
369
+ response = await cofheFetch(
370
+ `${thresholdNetworkUrl}/v2/decrypt/${requestId}`,
371
+ /*op:decrypt-poll*/ {
372
+ method: 'GET',
373
+ headers: {
374
+ 'Content-Type': 'application/json',
375
+ },
376
+ }
377
+ );
361
378
  } catch (e) {
362
379
  throw new CofheError({
363
380
  code: CofheErrorCode.DecryptFailed,
@@ -384,17 +401,11 @@ async function pollDecryptStatusV2(
384
401
  }
385
402
 
386
403
  if (!response.ok) {
387
- let errorMessage = `HTTP ${response.status}`;
388
- try {
389
- const errorBody = (await response.json()) as Record<string, unknown>;
390
- const maybeMessage = (errorBody.error_message || errorBody.message) as unknown;
391
- if (typeof maybeMessage === 'string' && maybeMessage.length > 0) errorMessage = maybeMessage;
392
- } catch {
393
- errorMessage = response.statusText || errorMessage;
394
- }
404
+ const { apiErrorCode, errorMessage } = await parseApiErrorResponseBody(response);
395
405
 
396
406
  throw new CofheError({
397
- code: CofheErrorCode.DecryptFailed,
407
+ code: mapApiErrorCodeToCofheErrorCode(apiErrorCode, CofheErrorCode.DecryptFailed),
408
+ apiErrorCode,
398
409
  message: `decrypt status poll failed: ${errorMessage}`,
399
410
  context: {
400
411
  thresholdNetworkUrl,
@@ -448,12 +459,12 @@ async function pollDecryptStatusV2(
448
459
  export async function tnDecryptV2(params: {
449
460
  ctHash: bigint | string;
450
461
  chainId: number;
451
- permission: Permission | null;
462
+ acp: ACPPublic | null;
452
463
  thresholdNetworkUrl: string;
453
464
  retry404TimeoutMs?: number;
454
465
  onPoll?: DecryptPollCallbackFunction;
455
466
  }): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
456
- const { thresholdNetworkUrl, ctHash, chainId, permission, retry404TimeoutMs, onPoll } = params;
467
+ const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
457
468
  const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
458
469
  timeoutMs: retry404TimeoutMs,
459
470
  operationLabel: 'decrypt',
@@ -464,7 +475,7 @@ export async function tnDecryptV2(params: {
464
475
  thresholdNetworkUrl,
465
476
  ctHash,
466
477
  chainId,
467
- permission,
478
+ acp,
468
479
  overallStartTime,
469
480
  normalized404RetryTimeoutMs,
470
481
  onPoll
@@ -1,11 +1,12 @@
1
- import { type Permission, type EthEncryptedData } from '@/permits';
1
+ import { type ACPPublic, type EthEncryptedData } from '@/acps';
2
2
 
3
3
  import { CofheError, CofheErrorCode } from '../error.js';
4
+ import {} from './tnDecryptUtils.js';
4
5
 
5
6
  export async function tnSealOutputV1(
6
7
  ctHash: bigint,
7
8
  chainId: number,
8
- permission: Permission,
9
+ acp: ACPPublic,
9
10
  thresholdNetworkUrl: string
10
11
  ): Promise<EthEncryptedData> {
11
12
  let sealed: EthEncryptedData | undefined;
@@ -15,7 +16,7 @@ export async function tnSealOutputV1(
15
16
  const body = {
16
17
  ct_tempkey: ctHash.toString(16).padStart(64, '0'),
17
18
  host_chain_id: chainId,
18
- permit: permission,
19
+ acp: acp,
19
20
  };
20
21
 
21
22
  try {
@@ -1,9 +1,12 @@
1
- import { type Permission, type EthEncryptedData } from '@/permits';
1
+ import { type ACPPublic, type EthEncryptedData } from '@/acps';
2
+ import { cofheFetch } from '../debug.js';
2
3
 
3
4
  import { CofheError, CofheErrorCode } from '../error.js';
4
5
  import { type DecryptPollCallbackFunction } from '../types.js';
5
6
  import { computeMinuteRampPollIntervalMs } from './polling.js';
7
+ import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
6
8
  import { classifySubmitResponse, normalize404RetryTimeoutMs, throwIfSubmitRetryTimedOut } from './submitRetry.js';
9
+ import {} from './tnDecryptUtils.js';
7
10
 
8
11
  // Polling configuration
9
12
  const POLL_INTERVAL_MS = 1000; // 1 second
@@ -135,7 +138,7 @@ async function submitSealOutputRequest(
135
138
  thresholdNetworkUrl: string,
136
139
  ctHash: bigint | string,
137
140
  chainId: number,
138
- permission: Permission,
141
+ acp: ACPPublic,
139
142
  overallStartTime: number,
140
143
  retry404TimeoutMs: number,
141
144
  onPoll?: DecryptPollCallbackFunction
@@ -143,20 +146,24 @@ async function submitSealOutputRequest(
143
146
  const body = {
144
147
  ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
145
148
  host_chain_id: chainId,
146
- permit: permission,
149
+ acp,
147
150
  };
148
151
  let attemptIndex = 0;
152
+ let last404ApiErrorMessage: string | undefined;
149
153
 
150
154
  for (;;) {
151
155
  let response: Response;
152
156
  try {
153
- response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
154
- method: 'POST',
155
- headers: {
156
- 'Content-Type': 'application/json',
157
- },
158
- body: JSON.stringify(body),
159
- });
157
+ response = await cofheFetch(
158
+ `${thresholdNetworkUrl}/v2/sealoutput`,
159
+ /*op:sealoutput*/ {
160
+ method: 'POST',
161
+ headers: {
162
+ 'Content-Type': 'application/json',
163
+ },
164
+ body: JSON.stringify(body),
165
+ }
166
+ );
160
167
  } catch (e) {
161
168
  throw new CofheError({
162
169
  code: CofheErrorCode.SealOutputFailed,
@@ -171,17 +178,20 @@ async function submitSealOutputRequest(
171
178
  });
172
179
  }
173
180
 
174
- const responseClassification = await classifySubmitResponse({ response });
181
+ const responseClassification = await classifySubmitResponse({
182
+ response,
183
+ fallbackErrorCode: CofheErrorCode.SealOutputFailed,
184
+ });
175
185
 
176
186
  if (responseClassification.kind === 'fatal-http') {
177
187
  throw new CofheError({
178
- code: CofheErrorCode.SealOutputFailed,
188
+ code: responseClassification.cofheErrorCode,
189
+ apiErrorCode: responseClassification.apiErrorCode,
179
190
  message: `sealOutput request failed: ${responseClassification.errorMessage}`,
180
191
  hint: 'Check the threshold network URL and request parameters.',
181
192
  context: {
182
193
  thresholdNetworkUrl,
183
- status: response.status,
184
- statusText: response.statusText,
194
+ status: responseClassification.status,
185
195
  body,
186
196
  attemptIndex,
187
197
  },
@@ -232,6 +242,10 @@ async function submitSealOutputRequest(
232
242
  });
233
243
  }
234
244
 
245
+ if (responseClassification.status === 404) {
246
+ last404ApiErrorMessage = responseClassification.apiErrorMessage;
247
+ }
248
+
235
249
  const elapsedMs = Date.now() - overallStartTime;
236
250
 
237
251
  throwIfSubmitRetryTimedOut({
@@ -244,6 +258,7 @@ async function submitSealOutputRequest(
244
258
  thresholdNetworkUrl,
245
259
  body,
246
260
  attemptIndex,
261
+ lastKnownErrorMessage: last404ApiErrorMessage,
247
262
  });
248
263
 
249
264
  onPoll?.({
@@ -303,12 +318,15 @@ async function pollSealOutputStatus(
303
318
 
304
319
  let response: Response;
305
320
  try {
306
- response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput/${requestId}`, {
307
- method: 'GET',
308
- headers: {
309
- 'Content-Type': 'application/json',
310
- },
311
- });
321
+ response = await cofheFetch(
322
+ `${thresholdNetworkUrl}/v2/sealoutput/${requestId}`,
323
+ /*op:sealoutput-poll*/ {
324
+ method: 'GET',
325
+ headers: {
326
+ 'Content-Type': 'application/json',
327
+ },
328
+ }
329
+ );
312
330
  } catch (e) {
313
331
  throw new CofheError({
314
332
  code: CofheErrorCode.SealOutputFailed,
@@ -337,16 +355,11 @@ async function pollSealOutputStatus(
337
355
 
338
356
  // Handle other non-200 status codes
339
357
  if (!response.ok) {
340
- let errorMessage = `HTTP ${response.status}`;
341
- try {
342
- const errorBody = await response.json();
343
- errorMessage = errorBody.error_message || errorBody.message || errorMessage;
344
- } catch {
345
- errorMessage = response.statusText || errorMessage;
346
- }
358
+ const { apiErrorCode, errorMessage } = await parseApiErrorResponseBody(response);
347
359
 
348
360
  throw new CofheError({
349
- code: CofheErrorCode.SealOutputFailed,
361
+ code: mapApiErrorCodeToCofheErrorCode(apiErrorCode, CofheErrorCode.SealOutputFailed),
362
+ apiErrorCode,
350
363
  message: `sealOutput status poll failed: ${errorMessage}`,
351
364
  context: {
352
365
  thresholdNetworkUrl,
@@ -400,12 +413,12 @@ async function pollSealOutputStatus(
400
413
  export async function tnSealOutputV2(params: {
401
414
  ctHash: bigint | string;
402
415
  chainId: number;
403
- permission: Permission;
416
+ acp: ACPPublic;
404
417
  thresholdNetworkUrl: string;
405
418
  retry404TimeoutMs?: number;
406
419
  onPoll?: DecryptPollCallbackFunction;
407
420
  }): Promise<EthEncryptedData> {
408
- const { thresholdNetworkUrl, ctHash, chainId, permission, retry404TimeoutMs, onPoll } = params;
421
+ const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
409
422
  const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
410
423
  timeoutMs: retry404TimeoutMs,
411
424
  operationLabel: 'sealOutput',
@@ -418,7 +431,7 @@ export async function tnSealOutputV2(params: {
418
431
  thresholdNetworkUrl,
419
432
  ctHash,
420
433
  chainId,
421
- permission,
434
+ acp,
422
435
  overallStartTime,
423
436
  normalized404RetryTimeoutMs,
424
437
  onPoll
@@ -1,15 +1,6 @@
1
1
  import { type EncryptableItem, FheTypes } from '../types.js';
2
- import { type VerifyResult } from './zkPackProveVerify.js';
3
- import {
4
- createWalletClient,
5
- http,
6
- encodePacked,
7
- keccak256,
8
- hashMessage,
9
- toBytes,
10
- type PublicClient,
11
- type WalletClient,
12
- } from 'viem';
2
+ import { type VerifyBatchResult } from './zkPackProveVerify.js';
3
+ import { createWalletClient, http, encodePacked, keccak256, type PublicClient, type WalletClient } from 'viem';
13
4
  import { MockZkVerifierAbi } from './MockZkVerifierAbi.js';
14
5
  import { hardhat } from 'viem/chains';
15
6
  import { CofheError, CofheErrorCode } from '../error.js';
@@ -178,89 +169,80 @@ async function insertCtHashes(items: EncryptableItemWithCtHash[], walletClient:
178
169
  }
179
170
 
180
171
  /**
181
- * The mocks verify the EncryptedInputs' signature against the known proof signer account.
182
- * Locally, we create the proof signatures from the known proof signer account.
172
+ * The mocks verify a batch's signature against the known verifier signer account.
173
+ * Locally, we create the single batch signature from the known signer account, over
174
+ * keccak256(h_0 || h_1 || ... || h_n), where each h_i is the same per-item message hash
175
+ * used by the on-chain digest (`ctHash || utype || securityZone || sender || chainId ||
176
+ * consumingContract`, per cofhe-contracts#77's contract-binding fix). This is the one
177
+ * canonical signer implementation used by the mocks - there is no separate per-item signing
178
+ * path.
183
179
  */
184
- async function createProofSignatures(
180
+ async function createBatchProofSignature(
185
181
  items: EncryptableItemWithCtHash[],
186
182
  securityZone: number,
187
- account: string
188
- ): Promise<`0x${string}`[]> {
189
- let signatures: `0x${string}`[] = [];
190
-
191
- // Create wallet client for the encrypted input signer
192
- // This wallet won't send a transaction, so gas isn't needed
193
- // This wallet doesn't need to be connected to the network
194
- let encInputSignerClient: WalletClient | undefined;
195
-
196
- try {
197
- encInputSignerClient = createMockZkVerifierSigner();
198
- } catch (err) {
199
- throw new CofheError({
200
- code: CofheErrorCode.ZkMocksCreateProofSignatureFailed,
201
- message: `mockZkVerifySign createProofSignatures failed while creating wallet client`,
202
- cause: err instanceof Error ? err : undefined,
203
- context: {
204
- MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
205
- },
206
- });
207
- }
208
-
183
+ account: string,
184
+ consumingContract: string
185
+ ): Promise<`0x${string}`> {
209
186
  try {
210
- for (const item of items) {
211
- // Pack the data into bytes and hash it
212
- const packedData = encodePacked(
213
- ['uint256', 'uint8', 'uint8', 'address', 'uint256'],
214
- [BigInt(item.ctHash), item.utype, securityZone, account as `0x${string}`, BigInt(hardhat.id)]
215
- );
216
- const messageHash = keccak256(packedData);
187
+ // Compute each item's per-item message hash h_i
188
+ const itemHashes = items.map((item) =>
189
+ keccak256(
190
+ encodePacked(
191
+ ['uint256', 'uint8', 'uint8', 'address', 'uint256', 'address'],
192
+ [
193
+ BigInt(item.ctHash),
194
+ item.utype,
195
+ securityZone,
196
+ account as `0x${string}`,
197
+ BigInt(hardhat.id),
198
+ consumingContract as `0x${string}`,
199
+ ]
200
+ )
201
+ )
202
+ );
217
203
 
218
- const signature = await sign({
219
- hash: messageHash,
220
- privateKey: MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
221
- to: 'hex',
222
- });
204
+ // Fold into one batch digest: keccak256(h_0 || h_1 || ... || h_n)
205
+ const batchDigest = keccak256(
206
+ encodePacked(
207
+ itemHashes.map(() => 'bytes32' as const),
208
+ itemHashes
209
+ )
210
+ );
223
211
 
224
- signatures.push(signature);
225
- }
212
+ // Sign once for the whole batch
213
+ return await sign({
214
+ hash: batchDigest,
215
+ privateKey: MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
216
+ to: 'hex',
217
+ });
226
218
  } catch (err) {
227
219
  throw new CofheError({
228
220
  code: CofheErrorCode.ZkMocksCreateProofSignatureFailed,
229
- message: `mockZkVerifySign createProofSignatures failed while calling signMessage`,
221
+ message: `mockZkVerifySign createBatchProofSignature failed while signing the batch digest`,
230
222
  cause: err instanceof Error ? err : undefined,
231
223
  context: {
232
224
  items,
233
225
  securityZone,
226
+ consumingContract,
234
227
  },
235
228
  });
236
229
  }
237
-
238
- if (signatures.length !== items.length) {
239
- throw new CofheError({
240
- code: CofheErrorCode.ZkMocksCreateProofSignatureFailed,
241
- message: `mockZkVerifySign createProofSignatures returned incorrect number of signatures`,
242
- context: {
243
- items,
244
- securityZone,
245
- },
246
- });
247
- }
248
-
249
- return signatures;
250
230
  }
251
231
 
252
232
  /**
253
- * Transforms the encryptable items into EncryptedInputs ready to be used in a transaction on the hardhat chain.
254
- * The EncryptedInputs are returned in the same format as from CoFHE, and include on-chain verifiable signatures.
233
+ * Transforms the encryptable items into a batch-verified result ready to be used in a
234
+ * transaction on the hardhat chain. Mirrors the shape returned by CoFHE's `/verifyBatch`:
235
+ * per-item ctHash/ctType, plus one shared on-chain verifiable signature for the whole batch.
255
236
  */
256
237
  export async function cofheMocksZkVerifySign(
257
238
  items: EncryptableItem[],
258
239
  account: string,
259
240
  securityZone: number,
241
+ consumingContract: string,
260
242
  publicClient: PublicClient,
261
243
  walletClient: WalletClient,
262
244
  zkvWalletClient: WalletClient | undefined
263
- ): Promise<VerifyResult[]> {
245
+ ): Promise<VerifyBatchResult> {
264
246
  // Use config._internal?.zkvWalletClient if provided, otherwise use a mock zk verifier signer
265
247
  const _walletClient = zkvWalletClient ?? createMockZkVerifierSigner();
266
248
 
@@ -270,12 +252,15 @@ export async function cofheMocksZkVerifySign(
270
252
  // Insert the ctHashes into the MockZkVerifier contract
271
253
  await insertCtHashes(encryptableItems, _walletClient);
272
254
 
273
- // Locally create the proof signatures from the known proof signer account
274
- const signatures = await createProofSignatures(encryptableItems, securityZone, account);
255
+ // Locally create the single batch signature from the known signer account
256
+ const signature = await createBatchProofSignature(encryptableItems, securityZone, account, consumingContract);
275
257
 
276
- // Return the ctHashes and signatures in the same format as CoFHE
277
- return encryptableItems.map((item, index) => ({
278
- ct_hash: item.ctHash.toString(),
279
- signature: signatures[index],
280
- }));
258
+ // Return the ctHashes/ctTypes and the batch signature, in the same shape as CoFHE's /verifyBatch
259
+ return {
260
+ outputs: encryptableItems.map((item) => ({
261
+ ct_hash: item.ctHash.toString(),
262
+ ct_type: item.utype,
263
+ })),
264
+ signature,
265
+ };
281
266
  }