@cofhe/sdk 0.6.1 → 0.7.1
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/CHANGELOG.md +45 -0
- package/acps/acp.ts +411 -0
- package/acps/index.ts +70 -0
- package/{permits → acps}/onchain-utils.ts +49 -24
- package/acps/sealing.ts +90 -0
- package/acps/signature.ts +89 -0
- package/acps/store.ts +172 -0
- package/acps/test/acp.test.ts +615 -0
- package/acps/test/localstorage.test.ts +105 -0
- package/acps/test/sealing.test.ts +77 -0
- package/acps/test/store.test.ts +88 -0
- package/acps/test/validation.test.ts +361 -0
- package/acps/test-utils.ts +32 -0
- package/acps/types.ts +252 -0
- package/acps/validation.ts +392 -0
- package/adapters/test/ethers5.test.ts +4 -1
- package/adapters/test/ethers6.test.ts +4 -1
- package/adapters/test/wagmi.test.ts +5 -2
- package/chains/chains/stagingCofhe.ts +21 -0
- package/chains/index.ts +3 -1
- package/chains/test/chains.test.ts +2 -1
- package/core/acps.ts +625 -0
- package/core/client.ts +136 -39
- package/core/clientTypes.ts +52 -41
- package/core/config.ts +66 -5
- package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
- package/core/decrypt/apiError.ts +104 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
- package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
- package/core/decrypt/decryptForTxBuilder.ts +102 -102
- package/core/decrypt/decryptForViewBuilder.ts +90 -90
- package/core/decrypt/submitRetry.ts +38 -30
- package/core/decrypt/tnDecryptV1.ts +5 -5
- package/core/decrypt/tnDecryptV2.ts +25 -21
- package/core/decrypt/tnSealOutputV1.ts +4 -3
- package/core/decrypt/tnSealOutputV2.ts +24 -18
- package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
- package/core/encrypt/encryptInputsBuilder.ts +86 -52
- package/core/encrypt/zkPackProveVerify.ts +25 -18
- package/core/error.ts +34 -6
- package/core/index.ts +4 -14
- package/core/test/acpDefaults.test.ts +52 -0
- package/core/test/acps.test.ts +596 -0
- package/core/test/apiError.test.ts +130 -0
- package/core/test/client.test.ts +22 -19
- package/core/test/config.test.ts +25 -5
- package/core/test/decrypt.test.ts +40 -35
- package/core/test/decryptBuilders.test.ts +68 -68
- package/core/test/decryptErrorCodes.test.ts +217 -0
- package/core/test/encryptInputsBuilder.test.ts +72 -41
- package/core/test/pollCallbacks.test.ts +89 -18
- package/core/test/stagingRedirect.ts +18 -0
- package/core/test/submitRetry.test.ts +182 -0
- package/core/types.ts +9 -69
- package/dist/acp-Wi6isVQI.d.cts +407 -0
- package/dist/acp-Wi6isVQI.d.ts +407 -0
- package/dist/acps.cjs +1081 -0
- package/dist/acps.d.cts +480 -0
- package/dist/acps.d.ts +480 -0
- package/dist/acps.js +2 -0
- package/dist/chains.cjs +14 -1
- package/dist/chains.d.cts +30 -1
- package/dist/chains.d.ts +30 -1
- package/dist/chains.js +1 -1
- package/dist/{chunk-NOC3PYB7.js → chunk-43USWPEH.js} +930 -580
- package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
- package/dist/chunk-Q7CBWGQX.js +1029 -0
- package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
- package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
- package/dist/core.cjs +1358 -1002
- package/dist/core.d.cts +31 -9
- package/dist/core.d.ts +31 -9
- package/dist/core.js +3 -3
- package/dist/node.cjs +1293 -952
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -3
- package/dist/web.cjs +1293 -952
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +3 -3
- package/node/test/inherited.test.ts +75 -65
- package/node/test/tfheinit.test.ts +23 -8
- package/package.json +6 -6
- package/web/test/client.web.test.ts +5 -1
- package/web/test/inherited.web.test.ts +75 -65
- package/web/test/tfheinit.web.test.ts +14 -5
- package/web/test/worker.config.web.test.ts +38 -23
- package/web/test/worker.output.web.test.ts +25 -24
- package/core/encrypt/encryptUtils.ts +0 -67
- package/core/permits.ts +0 -216
- package/core/test/permits.test.ts +0 -596
- package/dist/chunk-VB62WYPL.js +0 -978
- package/dist/permit-DnVMDT5h.d.cts +0 -376
- package/dist/permit-DnVMDT5h.d.ts +0 -376
- package/dist/permits.cjs +0 -1026
- package/dist/permits.d.cts +0 -353
- package/dist/permits.d.ts +0 -353
- package/dist/permits.js +0 -2
- package/permits/index.ts +0 -68
- package/permits/permit.ts +0 -385
- package/permits/sealing.ts +0 -131
- package/permits/signature.ts +0 -79
- package/permits/store.ts +0 -157
- package/permits/test/localstorage.test.ts +0 -113
- package/permits/test/permit.test.ts +0 -557
- package/permits/test/sealing.test.ts +0 -84
- package/permits/test/store.test.ts +0 -88
- package/permits/test/validation.test.ts +0 -361
- package/permits/test-utils.ts +0 -28
- package/permits/types.ts +0 -204
- package/permits/validation.ts +0 -327
- /package/{permits → acps}/utils.ts +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ACPPublic, type EthEncryptedData } from '@/acps';
|
|
2
2
|
import { cofheFetch } from '../debug.js';
|
|
3
3
|
|
|
4
4
|
import { CofheError, CofheErrorCode } from '../error.js';
|
|
5
5
|
import { type DecryptPollCallbackFunction } from '../types.js';
|
|
6
6
|
import { computeMinuteRampPollIntervalMs } from './polling.js';
|
|
7
|
+
import { mapApiErrorCodeToCofheErrorCode, parseApiErrorResponseBody } from './apiError.js';
|
|
7
8
|
import { classifySubmitResponse, normalize404RetryTimeoutMs, throwIfSubmitRetryTimedOut } from './submitRetry.js';
|
|
9
|
+
import {} from './tnDecryptUtils.js';
|
|
8
10
|
|
|
9
11
|
// Polling configuration
|
|
10
12
|
const POLL_INTERVAL_MS = 1000; // 1 second
|
|
@@ -136,7 +138,7 @@ async function submitSealOutputRequest(
|
|
|
136
138
|
thresholdNetworkUrl: string,
|
|
137
139
|
ctHash: bigint | string,
|
|
138
140
|
chainId: number,
|
|
139
|
-
|
|
141
|
+
acp: ACPPublic,
|
|
140
142
|
overallStartTime: number,
|
|
141
143
|
retry404TimeoutMs: number,
|
|
142
144
|
onPoll?: DecryptPollCallbackFunction
|
|
@@ -144,9 +146,10 @@ async function submitSealOutputRequest(
|
|
|
144
146
|
const body = {
|
|
145
147
|
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
|
|
146
148
|
host_chain_id: chainId,
|
|
147
|
-
|
|
149
|
+
acp,
|
|
148
150
|
};
|
|
149
151
|
let attemptIndex = 0;
|
|
152
|
+
let last404ApiErrorMessage: string | undefined;
|
|
150
153
|
|
|
151
154
|
for (;;) {
|
|
152
155
|
let response: Response;
|
|
@@ -175,17 +178,20 @@ async function submitSealOutputRequest(
|
|
|
175
178
|
});
|
|
176
179
|
}
|
|
177
180
|
|
|
178
|
-
const responseClassification = await classifySubmitResponse({
|
|
181
|
+
const responseClassification = await classifySubmitResponse({
|
|
182
|
+
response,
|
|
183
|
+
fallbackErrorCode: CofheErrorCode.SealOutputFailed,
|
|
184
|
+
});
|
|
179
185
|
|
|
180
186
|
if (responseClassification.kind === 'fatal-http') {
|
|
181
187
|
throw new CofheError({
|
|
182
|
-
code:
|
|
188
|
+
code: responseClassification.cofheErrorCode,
|
|
189
|
+
apiErrorCode: responseClassification.apiErrorCode,
|
|
183
190
|
message: `sealOutput request failed: ${responseClassification.errorMessage}`,
|
|
184
191
|
hint: 'Check the threshold network URL and request parameters.',
|
|
185
192
|
context: {
|
|
186
193
|
thresholdNetworkUrl,
|
|
187
|
-
status:
|
|
188
|
-
statusText: response.statusText,
|
|
194
|
+
status: responseClassification.status,
|
|
189
195
|
body,
|
|
190
196
|
attemptIndex,
|
|
191
197
|
},
|
|
@@ -236,6 +242,10 @@ async function submitSealOutputRequest(
|
|
|
236
242
|
});
|
|
237
243
|
}
|
|
238
244
|
|
|
245
|
+
if (responseClassification.status === 404) {
|
|
246
|
+
last404ApiErrorMessage = responseClassification.apiErrorMessage;
|
|
247
|
+
}
|
|
248
|
+
|
|
239
249
|
const elapsedMs = Date.now() - overallStartTime;
|
|
240
250
|
|
|
241
251
|
throwIfSubmitRetryTimedOut({
|
|
@@ -248,6 +258,7 @@ async function submitSealOutputRequest(
|
|
|
248
258
|
thresholdNetworkUrl,
|
|
249
259
|
body,
|
|
250
260
|
attemptIndex,
|
|
261
|
+
lastKnownErrorMessage: last404ApiErrorMessage,
|
|
251
262
|
});
|
|
252
263
|
|
|
253
264
|
onPoll?.({
|
|
@@ -344,16 +355,11 @@ async function pollSealOutputStatus(
|
|
|
344
355
|
|
|
345
356
|
// Handle other non-200 status codes
|
|
346
357
|
if (!response.ok) {
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
const errorBody = await response.json();
|
|
350
|
-
errorMessage = errorBody.error_message || errorBody.message || errorMessage;
|
|
351
|
-
} catch {
|
|
352
|
-
errorMessage = response.statusText || errorMessage;
|
|
353
|
-
}
|
|
358
|
+
const { apiErrorCode, errorMessage } = await parseApiErrorResponseBody(response);
|
|
354
359
|
|
|
355
360
|
throw new CofheError({
|
|
356
|
-
code: CofheErrorCode.SealOutputFailed,
|
|
361
|
+
code: mapApiErrorCodeToCofheErrorCode(apiErrorCode, CofheErrorCode.SealOutputFailed),
|
|
362
|
+
apiErrorCode,
|
|
357
363
|
message: `sealOutput status poll failed: ${errorMessage}`,
|
|
358
364
|
context: {
|
|
359
365
|
thresholdNetworkUrl,
|
|
@@ -407,12 +413,12 @@ async function pollSealOutputStatus(
|
|
|
407
413
|
export async function tnSealOutputV2(params: {
|
|
408
414
|
ctHash: bigint | string;
|
|
409
415
|
chainId: number;
|
|
410
|
-
|
|
416
|
+
acp: ACPPublic;
|
|
411
417
|
thresholdNetworkUrl: string;
|
|
412
418
|
retry404TimeoutMs?: number;
|
|
413
419
|
onPoll?: DecryptPollCallbackFunction;
|
|
414
420
|
}): Promise<EthEncryptedData> {
|
|
415
|
-
const { thresholdNetworkUrl, ctHash, chainId,
|
|
421
|
+
const { thresholdNetworkUrl, ctHash, chainId, acp, retry404TimeoutMs, onPoll } = params;
|
|
416
422
|
const normalized404RetryTimeoutMs = normalize404RetryTimeoutMs({
|
|
417
423
|
timeoutMs: retry404TimeoutMs,
|
|
418
424
|
operationLabel: 'sealOutput',
|
|
@@ -425,7 +431,7 @@ export async function tnSealOutputV2(params: {
|
|
|
425
431
|
thresholdNetworkUrl,
|
|
426
432
|
ctHash,
|
|
427
433
|
chainId,
|
|
428
|
-
|
|
434
|
+
acp,
|
|
429
435
|
overallStartTime,
|
|
430
436
|
normalized404RetryTimeoutMs,
|
|
431
437
|
onPoll
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { type EncryptableItem, FheTypes } from '../types.js';
|
|
2
|
-
import { type
|
|
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
|
|
182
|
-
* Locally, we create the
|
|
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
|
|
180
|
+
async function createBatchProofSignature(
|
|
185
181
|
items: EncryptableItemWithCtHash[],
|
|
186
182
|
securityZone: number,
|
|
187
|
-
account: string
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
254
|
-
*
|
|
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<
|
|
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
|
|
274
|
-
const
|
|
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
|
|
277
|
-
return
|
|
278
|
-
|
|
279
|
-
|
|
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
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ZkBuilderAndCrsGenerator,
|
|
3
3
|
type ZkProveWorkerFunction,
|
|
4
|
+
type VerifyBatchResult,
|
|
4
5
|
zkPack,
|
|
5
6
|
zkProve,
|
|
6
7
|
zkProveWithWorker,
|
|
7
|
-
|
|
8
|
+
zkVerifyBatch,
|
|
8
9
|
constructZkPoKMetadata,
|
|
9
10
|
} from './zkPackProveVerify.js';
|
|
10
11
|
import { CofheError, CofheErrorCode } from '../error.js';
|
|
@@ -12,8 +13,6 @@ import {
|
|
|
12
13
|
type EncryptStepCallbackFunction,
|
|
13
14
|
EncryptStep,
|
|
14
15
|
type EncryptableItem,
|
|
15
|
-
type EncryptedItemInput,
|
|
16
|
-
type EncryptedItemInputs,
|
|
17
16
|
type TfheInitializer,
|
|
18
17
|
type EncryptStepCallbackContext,
|
|
19
18
|
type HashPlusProofResult,
|
|
@@ -48,11 +47,20 @@ type EncryptInputsBuilderParams<T extends EncryptableItem[]> = BaseBuilderParams
|
|
|
48
47
|
* config, tfhePublicKeyDeserializer, compactPkeCrsDeserializer, and zkBuilderAndCrsGenerator are required to be set in the builder.
|
|
49
48
|
*/
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The builder as returned by `client.encryptInputs(...)`, before a consuming contract has been set.
|
|
52
|
+
*
|
|
53
|
+
* `execute()` is intentionally absent: the verifier binds the consuming contract into the signed
|
|
54
|
+
* digest, so no batch can be produced until the caller commits to the contract that will consume it.
|
|
55
|
+
* `setConsumingContract(...)` returns the full {@link EncryptInputsBuilder}, unlocking `execute()`.
|
|
56
|
+
*/
|
|
57
|
+
export type EncryptInputsBuilderUnset<T extends EncryptableItem[]> = Omit<EncryptInputsBuilder<T>, 'execute'>;
|
|
58
|
+
|
|
59
|
+
export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuilder {
|
|
52
60
|
private securityZone: number;
|
|
61
|
+
protected consumingContract: string | undefined;
|
|
53
62
|
private stepCallback?: EncryptStepCallbackFunction;
|
|
54
63
|
private inputItems: [...T];
|
|
55
|
-
private hpp: boolean = false;
|
|
56
64
|
|
|
57
65
|
private zkvWalletClient: WalletClient | undefined;
|
|
58
66
|
|
|
@@ -156,7 +164,9 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
156
164
|
*
|
|
157
165
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
158
166
|
*/
|
|
159
|
-
setAccount(account: string): EncryptInputsBuilder<T
|
|
167
|
+
setAccount(this: EncryptInputsBuilder<T>, account: string): EncryptInputsBuilder<T>;
|
|
168
|
+
setAccount(this: EncryptInputsBuilderUnset<T>, account: string): EncryptInputsBuilderUnset<T>;
|
|
169
|
+
setAccount(account: string): EncryptInputsBuilder<T> {
|
|
160
170
|
this.account = account;
|
|
161
171
|
return this;
|
|
162
172
|
}
|
|
@@ -179,7 +189,9 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
179
189
|
*
|
|
180
190
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
181
191
|
*/
|
|
182
|
-
setChainId(chainId: number): EncryptInputsBuilder<T
|
|
192
|
+
setChainId(this: EncryptInputsBuilder<T>, chainId: number): EncryptInputsBuilder<T>;
|
|
193
|
+
setChainId(this: EncryptInputsBuilderUnset<T>, chainId: number): EncryptInputsBuilderUnset<T>;
|
|
194
|
+
setChainId(chainId: number): EncryptInputsBuilder<T> {
|
|
183
195
|
this.chainId = chainId;
|
|
184
196
|
return this;
|
|
185
197
|
}
|
|
@@ -202,7 +214,9 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
202
214
|
*
|
|
203
215
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
204
216
|
*/
|
|
205
|
-
setSecurityZone(securityZone: number): EncryptInputsBuilder<T
|
|
217
|
+
setSecurityZone(this: EncryptInputsBuilder<T>, securityZone: number): EncryptInputsBuilder<T>;
|
|
218
|
+
setSecurityZone(this: EncryptInputsBuilderUnset<T>, securityZone: number): EncryptInputsBuilderUnset<T>;
|
|
219
|
+
setSecurityZone(securityZone: number): EncryptInputsBuilder<T> {
|
|
206
220
|
this.securityZone = securityZone;
|
|
207
221
|
return this;
|
|
208
222
|
}
|
|
@@ -212,18 +226,44 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
212
226
|
}
|
|
213
227
|
|
|
214
228
|
/**
|
|
229
|
+
* @param address - The contract that will consume the resulting hashes+signature (i.e. the
|
|
230
|
+
* contract that will call `FHE.asEuint*`/`FHE.asEuint*s` with them).
|
|
231
|
+
*
|
|
232
|
+
* Required before `execute()` - the verifier binds this address into the signed digest, so a
|
|
233
|
+
* batch signed for one contract cannot be replayed into another.
|
|
234
|
+
*
|
|
215
235
|
* Example:
|
|
216
236
|
* ```typescript
|
|
217
237
|
* const encrypted = await encryptInputs([Encryptable.uint128(10n)])
|
|
218
|
-
* .
|
|
238
|
+
* .setConsumingContract("0x123...890")
|
|
219
239
|
* .execute();
|
|
220
240
|
* ```
|
|
221
241
|
*
|
|
222
|
-
* @returns
|
|
242
|
+
* @returns The chainable EncryptInputsBuilder instance.
|
|
223
243
|
*/
|
|
224
|
-
|
|
225
|
-
this.
|
|
226
|
-
return this
|
|
244
|
+
setConsumingContract(address: string): EncryptInputsBuilder<T> {
|
|
245
|
+
this.consumingContract = address;
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
getConsumingContract(): string | undefined {
|
|
250
|
+
return this.consumingContract;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Asserts that this.consumingContract is populated
|
|
255
|
+
* @throws {CofheError} If consumingContract is not set
|
|
256
|
+
*/
|
|
257
|
+
private assertConsumingContract(): asserts this is this & { consumingContract: string } {
|
|
258
|
+
if (this.consumingContract) return;
|
|
259
|
+
throw new CofheError({
|
|
260
|
+
code: CofheErrorCode.ConsumingContractUninitialized,
|
|
261
|
+
message: 'Consuming contract is not set',
|
|
262
|
+
hint: 'Use setConsumingContract(...) to set the contract that will consume the encrypted inputs.',
|
|
263
|
+
context: {
|
|
264
|
+
consumingContract: this.consumingContract,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
227
267
|
}
|
|
228
268
|
|
|
229
269
|
/**
|
|
@@ -240,7 +280,9 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
240
280
|
*
|
|
241
281
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
242
282
|
*/
|
|
243
|
-
setUseWorker(useWorker: boolean): EncryptInputsBuilder<T
|
|
283
|
+
setUseWorker(this: EncryptInputsBuilder<T>, useWorker: boolean): EncryptInputsBuilder<T>;
|
|
284
|
+
setUseWorker(this: EncryptInputsBuilderUnset<T>, useWorker: boolean): EncryptInputsBuilderUnset<T>;
|
|
285
|
+
setUseWorker(useWorker: boolean): EncryptInputsBuilder<T> {
|
|
244
286
|
this.useWorker = useWorker;
|
|
245
287
|
return this;
|
|
246
288
|
}
|
|
@@ -277,7 +319,9 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
277
319
|
*
|
|
278
320
|
* @returns The EncryptInputsBuilder instance.
|
|
279
321
|
*/
|
|
280
|
-
onStep(callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T
|
|
322
|
+
onStep(this: EncryptInputsBuilder<T>, callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T>;
|
|
323
|
+
onStep(this: EncryptInputsBuilderUnset<T>, callback: EncryptStepCallbackFunction): EncryptInputsBuilderUnset<T>;
|
|
324
|
+
onStep(callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T> {
|
|
281
325
|
this.stepCallback = callback;
|
|
282
326
|
return this;
|
|
283
327
|
}
|
|
@@ -433,10 +477,11 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
433
477
|
* cofheMocksInsertPackedHashes - stores the ctHashes and their plaintext values for on-chain mocking of FHE operations.
|
|
434
478
|
* cofheMocksZkCreateProofSignatures - creates signatures to be included in the encrypted inputs. The signers address is known and verified in the mock contracts.
|
|
435
479
|
*/
|
|
436
|
-
private async mocksExecute(): Promise<
|
|
480
|
+
private async mocksExecute(): Promise<VerifyBatchResult> {
|
|
437
481
|
this.assertAccount();
|
|
438
482
|
this.assertPublicClient();
|
|
439
483
|
this.assertWalletClient();
|
|
484
|
+
this.assertConsumingContract();
|
|
440
485
|
|
|
441
486
|
const [initTfheDelay, fetchKeysDelay, packDelay, proveDelay, verifyDelay] = this.resolveEncryptDelays();
|
|
442
487
|
|
|
@@ -468,31 +513,27 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
468
513
|
|
|
469
514
|
this.fireStepStart(EncryptStep.Verify);
|
|
470
515
|
await sleep(verifyDelay);
|
|
471
|
-
const
|
|
516
|
+
const result = await cofheMocksZkVerifySign(
|
|
472
517
|
this.inputItems,
|
|
473
518
|
this.account,
|
|
474
519
|
this.securityZone,
|
|
520
|
+
this.consumingContract,
|
|
475
521
|
this.publicClient,
|
|
476
522
|
this.walletClient,
|
|
477
523
|
this.zkvWalletClient
|
|
478
524
|
);
|
|
479
|
-
const encryptedInputs: EncryptedItemInput[] = signedResults.map(({ ct_hash, signature }, index) => ({
|
|
480
|
-
ctHash: BigInt(ct_hash),
|
|
481
|
-
securityZone: this.securityZone,
|
|
482
|
-
utype: this.inputItems[index].utype,
|
|
483
|
-
signature,
|
|
484
|
-
}));
|
|
485
525
|
this.fireStepEnd(EncryptStep.Verify, { isMocks: true, mockSleep: verifyDelay });
|
|
486
526
|
|
|
487
|
-
return
|
|
527
|
+
return result;
|
|
488
528
|
}
|
|
489
529
|
|
|
490
530
|
/**
|
|
491
531
|
* In the production context, perform a true encryption with the CoFHE coprocessor.
|
|
492
532
|
*/
|
|
493
|
-
private async productionExecute(): Promise<
|
|
533
|
+
private async productionExecute(): Promise<VerifyBatchResult> {
|
|
494
534
|
this.assertAccount();
|
|
495
535
|
this.assertChainId();
|
|
536
|
+
this.assertConsumingContract();
|
|
496
537
|
|
|
497
538
|
this.fireStepStart(EncryptStep.InitTfhe);
|
|
498
539
|
|
|
@@ -555,32 +596,28 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
555
596
|
|
|
556
597
|
const zkVerifierUrl = await this.getZkVerifierUrl();
|
|
557
598
|
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
signature,
|
|
566
|
-
})
|
|
599
|
+
const result = await zkVerifyBatch(
|
|
600
|
+
zkVerifierUrl,
|
|
601
|
+
proof,
|
|
602
|
+
this.account,
|
|
603
|
+
this.securityZone,
|
|
604
|
+
this.chainId,
|
|
605
|
+
this.consumingContract
|
|
567
606
|
);
|
|
568
607
|
|
|
569
608
|
this.fireStepEnd(EncryptStep.Verify);
|
|
570
609
|
|
|
571
|
-
return
|
|
610
|
+
return result;
|
|
572
611
|
}
|
|
573
612
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
proof += item.signature;
|
|
581
|
-
}
|
|
613
|
+
/**
|
|
614
|
+
* Converts a batch-verified result (per-item ctHash/ctType + one shared signature) into the
|
|
615
|
+
* tuple returned by `execute()`: per-item hashes in input order, followed by the single signature.
|
|
616
|
+
*/
|
|
617
|
+
private buildBatchResult(result: VerifyBatchResult): HashPlusProofResult<T> {
|
|
618
|
+
const hashes = result.outputs.map((output) => '0x' + BigInt(output.ct_hash).toString(16).padStart(64, '0'));
|
|
582
619
|
|
|
583
|
-
return [...hashes,
|
|
620
|
+
return [...hashes, result.signature] as unknown as HashPlusProofResult<T>;
|
|
584
621
|
}
|
|
585
622
|
|
|
586
623
|
/**
|
|
@@ -602,17 +639,14 @@ export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boole
|
|
|
602
639
|
*
|
|
603
640
|
* @returns The encrypted inputs.
|
|
604
641
|
*/
|
|
605
|
-
async execute(): Promise<
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
let items: [...EncryptedItemInputs<T>];
|
|
642
|
+
async execute(): Promise<HashPlusProofResult<T>> {
|
|
643
|
+
let result: VerifyBatchResult;
|
|
609
644
|
|
|
610
645
|
// On hardhat chain, interact with MockZkVerifier contract instead of CoFHE
|
|
611
|
-
if (this.chainId === hardhat.id)
|
|
646
|
+
if (this.chainId === hardhat.id) result = await this.mocksExecute();
|
|
612
647
|
// On other chains, interact with CoFHE coprocessor
|
|
613
|
-
else
|
|
648
|
+
else result = await this.productionExecute();
|
|
614
649
|
|
|
615
|
-
|
|
616
|
-
return items as unknown as Result;
|
|
650
|
+
return this.buildBatchResult(result);
|
|
617
651
|
}
|
|
618
652
|
}
|
|
@@ -41,14 +41,19 @@ export interface ZkProveWorkerResponse {
|
|
|
41
41
|
error?: string;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export type
|
|
44
|
+
export type VerifyBatchOutputRaw = {
|
|
45
45
|
ct_hash: string;
|
|
46
|
+
ct_type: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type VerifyBatchResultRaw = {
|
|
50
|
+
ciphertexts: VerifyBatchOutputRaw[];
|
|
46
51
|
signature: string;
|
|
47
52
|
recid: number;
|
|
48
53
|
};
|
|
49
54
|
|
|
50
|
-
export type
|
|
51
|
-
|
|
55
|
+
export type VerifyBatchResult = {
|
|
56
|
+
outputs: VerifyBatchOutputRaw[];
|
|
52
57
|
signature: `0x${string}`;
|
|
53
58
|
};
|
|
54
59
|
|
|
@@ -256,13 +261,14 @@ export const constructZkPoKMetadata = (accountAddr: string, securityZone: number
|
|
|
256
261
|
return metadata;
|
|
257
262
|
};
|
|
258
263
|
|
|
259
|
-
export const
|
|
264
|
+
export const zkVerifyBatch = async (
|
|
260
265
|
verifierUrl: string,
|
|
261
266
|
serializedBytes: Uint8Array,
|
|
262
267
|
address: string,
|
|
263
268
|
securityZone: number,
|
|
264
|
-
chainId: number
|
|
265
|
-
|
|
269
|
+
chainId: number,
|
|
270
|
+
consumingContract: string
|
|
271
|
+
): Promise<VerifyBatchResult> => {
|
|
266
272
|
// Convert bytearray to hex string
|
|
267
273
|
const packed_list = toHexString(serializedBytes);
|
|
268
274
|
|
|
@@ -274,13 +280,14 @@ export const zkVerify = async (
|
|
|
274
280
|
account_addr: address,
|
|
275
281
|
security_zone: sz_byte[0],
|
|
276
282
|
chain_id: chainId,
|
|
283
|
+
contract_address: consumingContract,
|
|
277
284
|
};
|
|
278
285
|
|
|
279
286
|
const body = JSON.stringify(payload);
|
|
280
287
|
|
|
281
288
|
// Send request to verification server
|
|
282
289
|
try {
|
|
283
|
-
const response = await fetch(`${verifierUrl}/
|
|
290
|
+
const response = await fetch(`${verifierUrl}/verifyBatch`, {
|
|
284
291
|
method: 'POST',
|
|
285
292
|
headers: {
|
|
286
293
|
'Content-Type': 'application/json',
|
|
@@ -293,34 +300,34 @@ export const zkVerify = async (
|
|
|
293
300
|
const errorBody = await response.text();
|
|
294
301
|
throw new CofheError({
|
|
295
302
|
code: CofheErrorCode.ZkVerifyFailed,
|
|
296
|
-
message: `HTTP error! ZK proof verification failed - ${errorBody}`,
|
|
303
|
+
message: `HTTP error! ZK batch proof verification failed - ${errorBody}`,
|
|
297
304
|
});
|
|
298
305
|
}
|
|
299
306
|
|
|
300
|
-
const json = (await response.json()) as { status: string; data:
|
|
307
|
+
const json = (await response.json()) as { status: string; data: VerifyBatchResultRaw; error: string };
|
|
301
308
|
|
|
302
309
|
if (json.status !== 'success') {
|
|
303
310
|
throw new CofheError({
|
|
304
311
|
code: CofheErrorCode.ZkVerifyFailed,
|
|
305
|
-
message: `ZK proof verification response malformed - ${json.error}`,
|
|
312
|
+
message: `ZK batch proof verification response malformed - ${json.error}`,
|
|
306
313
|
});
|
|
307
314
|
}
|
|
308
315
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
316
|
+
const { ciphertexts, signature, recid } = json.data;
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
outputs: ciphertexts,
|
|
320
|
+
signature: concatSigRecid(signature, recid),
|
|
321
|
+
};
|
|
315
322
|
} catch (e) {
|
|
316
323
|
throw new CofheError({
|
|
317
324
|
code: CofheErrorCode.ZkVerifyFailed,
|
|
318
|
-
message: `ZK proof verification failed`,
|
|
325
|
+
message: `ZK batch proof verification failed`,
|
|
319
326
|
cause: e instanceof Error ? e : undefined,
|
|
320
327
|
});
|
|
321
328
|
}
|
|
322
329
|
};
|
|
323
330
|
|
|
324
|
-
const concatSigRecid = (signature: string, recid: number): `0x${string}` => {
|
|
331
|
+
export const concatSigRecid = (signature: string, recid: number): `0x${string}` => {
|
|
325
332
|
return `${signature}${(recid + 27).toString(16).padStart(2, '0')}` as `0x${string}`;
|
|
326
333
|
};
|