@cofhe/sdk 0.1.1 → 0.2.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 +22 -0
- package/adapters/ethers6.ts +28 -28
- package/adapters/hardhat.ts +0 -1
- package/adapters/index.test.ts +14 -19
- package/adapters/smartWallet.ts +81 -73
- package/adapters/test-utils.ts +45 -45
- package/adapters/types.ts +3 -3
- package/chains/chains/localcofhe.ts +14 -0
- package/chains/chains.test.ts +2 -1
- package/chains/defineChain.ts +2 -2
- package/chains/index.ts +3 -1
- package/chains/types.ts +3 -3
- package/core/baseBuilder.ts +30 -49
- package/core/client.test.ts +200 -72
- package/core/client.ts +152 -148
- package/core/clientTypes.ts +114 -0
- package/core/config.test.ts +30 -11
- package/core/config.ts +26 -13
- package/core/consts.ts +18 -0
- package/core/decrypt/cofheMocksSealOutput.ts +2 -4
- package/core/decrypt/decryptHandleBuilder.ts +51 -45
- package/core/decrypt/{tnSealOutput.ts → tnSealOutputV1.ts} +1 -1
- package/core/decrypt/tnSealOutputV2.ts +298 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +15 -16
- package/core/encrypt/encryptInputsBuilder.test.ts +132 -116
- package/core/encrypt/encryptInputsBuilder.ts +159 -111
- package/core/encrypt/encryptUtils.ts +6 -3
- package/core/encrypt/zkPackProveVerify.ts +70 -8
- package/core/error.ts +0 -2
- package/core/fetchKeys.test.ts +1 -18
- package/core/fetchKeys.ts +0 -26
- package/core/index.ts +37 -17
- package/core/keyStore.ts +65 -38
- package/core/permits.test.ts +255 -4
- package/core/permits.ts +83 -18
- package/core/types.ts +198 -152
- package/core/utils.ts +43 -1
- package/dist/adapters.d.cts +38 -20
- package/dist/adapters.d.ts +38 -20
- package/dist/chains.cjs +18 -8
- package/dist/chains.d.cts +31 -9
- package/dist/chains.d.ts +31 -9
- package/dist/chains.js +1 -1
- package/dist/{chunk-KFGPTJ6X.js → chunk-I5WFEYXX.js} +1768 -1526
- package/dist/{chunk-LU7BMUUT.js → chunk-R3B5TMVX.js} +330 -197
- package/dist/{chunk-GZCQQYVI.js → chunk-TBLR7NNE.js} +18 -9
- package/dist/{types-PhwGgQvs.d.ts → clientTypes-RqkgkV2i.d.ts} +331 -429
- package/dist/{types-bB7wLj0q.d.cts → clientTypes-e4filDzK.d.cts} +331 -429
- package/dist/core.cjs +3000 -2625
- package/dist/core.d.cts +113 -7
- package/dist/core.d.ts +113 -7
- package/dist/core.js +3 -3
- package/dist/node.cjs +2851 -2526
- package/dist/node.d.cts +4 -4
- package/dist/node.d.ts +4 -4
- package/dist/node.js +4 -3
- package/dist/{permit-S9CnI6MF.d.cts → permit-MZ502UBl.d.cts} +54 -41
- package/dist/{permit-S9CnI6MF.d.ts → permit-MZ502UBl.d.ts} +54 -41
- package/dist/permits.cjs +328 -195
- package/dist/permits.d.cts +113 -825
- package/dist/permits.d.ts +113 -825
- package/dist/permits.js +1 -1
- package/dist/types-YiAC4gig.d.cts +33 -0
- package/dist/types-YiAC4gig.d.ts +33 -0
- package/dist/web.cjs +3067 -2527
- package/dist/web.d.cts +22 -6
- package/dist/web.d.ts +22 -6
- package/dist/web.js +185 -9
- package/dist/zkProve.worker.cjs +93 -0
- package/dist/zkProve.worker.d.cts +2 -0
- package/dist/zkProve.worker.d.ts +2 -0
- package/dist/zkProve.worker.js +91 -0
- package/node/client.test.ts +20 -25
- package/node/encryptInputs.test.ts +18 -38
- package/node/index.ts +1 -0
- package/package.json +15 -15
- package/permits/index.ts +1 -0
- package/permits/localstorage.test.ts +9 -14
- package/permits/onchain-utils.ts +221 -0
- package/permits/permit.test.ts +76 -27
- package/permits/permit.ts +58 -95
- package/permits/sealing.test.ts +3 -3
- package/permits/sealing.ts +2 -2
- package/permits/store.test.ts +10 -50
- package/permits/store.ts +9 -21
- package/permits/test-utils.ts +11 -3
- package/permits/types.ts +39 -9
- package/permits/utils.ts +0 -5
- package/permits/validation.test.ts +29 -32
- package/permits/validation.ts +114 -176
- package/web/client.web.test.ts +20 -25
- package/web/config.web.test.ts +0 -2
- package/web/encryptInputs.web.test.ts +31 -54
- package/web/index.ts +65 -1
- package/web/storage.ts +19 -5
- package/web/worker.builder.web.test.ts +148 -0
- package/web/worker.config.web.test.ts +329 -0
- package/web/worker.output.web.test.ts +84 -0
- package/web/workerManager.test.ts +80 -0
- package/web/workerManager.ts +214 -0
- package/web/workerManager.web.test.ts +114 -0
- package/web/zkProve.worker.ts +133 -0
- package/core/result.test.ts +0 -180
- package/core/result.ts +0 -67
- package/core/test-utils.ts +0 -45
- package/dist/types-KImPrEIe.d.cts +0 -48
- package/dist/types-KImPrEIe.d.ts +0 -48
package/core/config.ts
CHANGED
|
@@ -5,18 +5,16 @@ import { type WalletClient } from 'viem';
|
|
|
5
5
|
import { CofhesdkError, CofhesdkErrorCode } from './error.js';
|
|
6
6
|
import { type IStorage } from './types.js';
|
|
7
7
|
|
|
8
|
+
export type CofhesdkEnvironment = 'node' | 'hardhat' | 'web' | 'react';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Usable config type inferred from the schema
|
|
10
12
|
*/
|
|
11
13
|
export type CofhesdkConfig = {
|
|
14
|
+
/** Environment that the SDK is running in */
|
|
15
|
+
environment: 'node' | 'hardhat' | 'web' | 'react';
|
|
16
|
+
/** List of supported chains */
|
|
12
17
|
supportedChains: CofheChain[];
|
|
13
|
-
/**
|
|
14
|
-
* Strategy for fetching FHE keys
|
|
15
|
-
* - CONNECTED_CHAIN: Fetch keys for the connected chain (provided by the publicClient)
|
|
16
|
-
* - SUPPORTED_CHAINS: Fetch keys for all supported chains (provided by the supportedChains config)
|
|
17
|
-
* - OFF: Do not fetch keys (fetching occurs during encryptInputs)
|
|
18
|
-
* */
|
|
19
|
-
fheKeysPrefetching: 'CONNECTED_CHAIN' | 'SUPPORTED_CHAINS' | 'OFF';
|
|
20
18
|
/**
|
|
21
19
|
* How permits are generated
|
|
22
20
|
* - ON_CONNECT: Generate a permit when client.connect() is called
|
|
@@ -32,6 +30,12 @@ export type CofhesdkConfig = {
|
|
|
32
30
|
* (defaults to indexedDB on web, filesystem on node)
|
|
33
31
|
*/
|
|
34
32
|
fheKeyStorage: IStorage | null;
|
|
33
|
+
/**
|
|
34
|
+
* Whether to use Web Workers for ZK proof generation (web platform only)
|
|
35
|
+
* When enabled, heavy WASM computation is offloaded to prevent UI freezing
|
|
36
|
+
* Default: true
|
|
37
|
+
*/
|
|
38
|
+
useWorkers: boolean;
|
|
35
39
|
/** Mocks configs */
|
|
36
40
|
mocks: {
|
|
37
41
|
/**
|
|
@@ -52,10 +56,10 @@ export type CofhesdkInternalConfig = {
|
|
|
52
56
|
* Zod schema for configuration validation
|
|
53
57
|
*/
|
|
54
58
|
export const CofhesdkConfigSchema = z.object({
|
|
59
|
+
/** Environment that the SDK is running in */
|
|
60
|
+
environment: z.enum(['node', 'hardhat', 'web', 'react']).optional().default('node'),
|
|
55
61
|
/** List of supported chain configurations */
|
|
56
62
|
supportedChains: z.array(z.custom<CofheChain>()),
|
|
57
|
-
/** Strategy for fetching FHE keys */
|
|
58
|
-
fheKeysPrefetching: z.enum(['CONNECTED_CHAIN', 'SUPPORTED_CHAINS', 'OFF']).optional().default('OFF'),
|
|
59
63
|
/** How permits are generated */
|
|
60
64
|
permitGeneration: z.enum(['ON_CONNECT', 'ON_DECRYPT_HANDLES', 'MANUAL']).optional().default('ON_CONNECT'),
|
|
61
65
|
/** Default permit expiration in seconds, default is 30 days */
|
|
@@ -66,12 +70,20 @@ export const CofhesdkConfigSchema = z.object({
|
|
|
66
70
|
/** Storage method for fhe keys (defaults to indexedDB on web, filesystem on node) */
|
|
67
71
|
fheKeyStorage: z
|
|
68
72
|
.object({
|
|
69
|
-
getItem: z.
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
getItem: z.custom<IStorage['getItem']>((val) => typeof val === 'function', {
|
|
74
|
+
message: 'getItem must be a function',
|
|
75
|
+
}),
|
|
76
|
+
setItem: z.custom<IStorage['setItem']>((val) => typeof val === 'function', {
|
|
77
|
+
message: 'setItem must be a function',
|
|
78
|
+
}),
|
|
79
|
+
removeItem: z.custom<IStorage['removeItem']>((val) => typeof val === 'function', {
|
|
80
|
+
message: 'removeItem must be a function',
|
|
81
|
+
}),
|
|
72
82
|
})
|
|
73
83
|
.or(z.null())
|
|
74
84
|
.default(null),
|
|
85
|
+
/** Whether to use Web Workers for ZK proof generation (web platform only) */
|
|
86
|
+
useWorkers: z.boolean().optional().default(true),
|
|
75
87
|
/** Mocks configs */
|
|
76
88
|
mocks: z
|
|
77
89
|
.object({
|
|
@@ -91,6 +103,7 @@ export const CofhesdkConfigSchema = z.object({
|
|
|
91
103
|
* Input config type inferred from the schema
|
|
92
104
|
*/
|
|
93
105
|
export type CofhesdkInputConfig = z.input<typeof CofhesdkConfigSchema>;
|
|
106
|
+
|
|
94
107
|
/**
|
|
95
108
|
* Creates and validates a cofhesdk configuration (base implementation)
|
|
96
109
|
* @param config - The configuration object to validate
|
|
@@ -101,7 +114,7 @@ export function createCofhesdkConfigBase(config: CofhesdkInputConfig): CofhesdkC
|
|
|
101
114
|
const result = CofhesdkConfigSchema.safeParse(config);
|
|
102
115
|
|
|
103
116
|
if (!result.success) {
|
|
104
|
-
throw new Error(`Invalid cofhesdk configuration: ${result.error.
|
|
117
|
+
throw new Error(`Invalid cofhesdk configuration: ${z.prettifyError(result.error)}`, { cause: result.error });
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
return result.data;
|
package/core/consts.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Main Task Manager contract address */
|
|
2
|
+
export const TASK_MANAGER_ADDRESS = '0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9' as const;
|
|
3
|
+
|
|
4
|
+
/** Mock ZK Verifier contract address (used for testing) */
|
|
5
|
+
export const MOCKS_ZK_VERIFIER_ADDRESS = '0x0000000000000000000000000000000000005001' as const;
|
|
6
|
+
|
|
7
|
+
/** Mock Query Decrypter contract address (used for testing) */
|
|
8
|
+
export const MOCKS_QUERY_DECRYPTER_ADDRESS = '0x0000000000000000000000000000000000005002' as const;
|
|
9
|
+
|
|
10
|
+
/** Test Bed contract address (used for testing) */
|
|
11
|
+
export const TEST_BED_ADDRESS = '0x0000000000000000000000000000000000005003' as const;
|
|
12
|
+
|
|
13
|
+
/** Private key for the Mock ZK Verifier signer account */
|
|
14
|
+
export const MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY =
|
|
15
|
+
'0x6C8D7F768A6BB4AAFE85E8A2F5A9680355239C7E14646ED62B044E39DE154512' as const;
|
|
16
|
+
|
|
17
|
+
/** Address for the Mock ZK Verifier signer account */
|
|
18
|
+
export const MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = '0x6E12D8C87503D4287c294f2Fdef96ACd9DFf6bd2' as const;
|
|
@@ -5,9 +5,7 @@ import { sleep } from '../utils.js';
|
|
|
5
5
|
import { MockQueryDecrypterAbi } from './MockQueryDecrypterAbi.js';
|
|
6
6
|
import { FheTypes } from '../types.js';
|
|
7
7
|
import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
|
|
8
|
-
|
|
9
|
-
// Address the Mock Query Decrypter contract is deployed to on the Hardhat chain
|
|
10
|
-
export const MockQueryDecrypterAddress = '0x0000000000000000000000000000000000000200';
|
|
8
|
+
import { MOCKS_QUERY_DECRYPTER_ADDRESS } from '../consts.js';
|
|
11
9
|
|
|
12
10
|
export async function cofheMocksSealOutput(
|
|
13
11
|
ctHash: bigint,
|
|
@@ -29,7 +27,7 @@ export async function cofheMocksSealOutput(
|
|
|
29
27
|
};
|
|
30
28
|
|
|
31
29
|
const [allowed, error, result] = await publicClient.readContract({
|
|
32
|
-
address:
|
|
30
|
+
address: MOCKS_QUERY_DECRYPTER_ADDRESS,
|
|
33
31
|
abi: MockQueryDecrypterAbi,
|
|
34
32
|
functionName: 'querySealOutput',
|
|
35
33
|
args: [ctHash, BigInt(utype), permissionWithBigInts],
|
|
@@ -3,13 +3,13 @@ import { type Permit, PermitUtils } from '@/permits';
|
|
|
3
3
|
|
|
4
4
|
import { FheTypes, type UnsealedItem } from '../types.js';
|
|
5
5
|
import { getThresholdNetworkUrlOrThrow } from '../config.js';
|
|
6
|
-
import { type Result, resultWrapper } from '../result.js';
|
|
7
6
|
import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
|
|
8
7
|
import { permits } from '../permits.js';
|
|
9
8
|
import { isValidUtype, convertViaUtype } from './decryptUtils.js';
|
|
10
9
|
import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
|
|
11
10
|
import { cofheMocksSealOutput } from './cofheMocksSealOutput.js';
|
|
12
|
-
import {
|
|
11
|
+
// import { tnSealOutputV1 } from './tnSealOutputV1.js';
|
|
12
|
+
import { tnSealOutputV2 } from './tnSealOutputV2.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* API
|
|
@@ -26,7 +26,7 @@ import { tnSealOutput } from './tnSealOutput.js';
|
|
|
26
26
|
* If permitHash not set, uses chainId and account to get active permit
|
|
27
27
|
* If permit is set, uses permit to decrypt regardless of chainId, account, or permitHash
|
|
28
28
|
*
|
|
29
|
-
* Returns
|
|
29
|
+
* Returns the unsealed item.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
type DecryptHandlesBuilderParams<U extends FheTypes> = BaseBuilderParams & {
|
|
@@ -151,9 +151,9 @@ export class DecryptHandlesBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
151
151
|
return this.permit;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
private getThresholdNetworkUrl(
|
|
155
|
-
|
|
156
|
-
return getThresholdNetworkUrlOrThrow(config, chainId);
|
|
154
|
+
private async getThresholdNetworkUrl(): Promise<string> {
|
|
155
|
+
this.assertChainId();
|
|
156
|
+
return getThresholdNetworkUrlOrThrow(this.config, this.chainId);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
private validateUtypeOrThrow(): void {
|
|
@@ -170,20 +170,20 @@ export class DecryptHandlesBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
170
170
|
private async getResolvedPermit(): Promise<Permit> {
|
|
171
171
|
if (this.permit) return this.permit;
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
this.assertChainId();
|
|
174
|
+
this.assertAccount();
|
|
175
175
|
|
|
176
176
|
// Fetch with permit hash
|
|
177
177
|
if (this.permitHash) {
|
|
178
|
-
const permit = await permits.getPermit(chainId, account, this.permitHash);
|
|
178
|
+
const permit = await permits.getPermit(this.chainId, this.account, this.permitHash);
|
|
179
179
|
if (!permit) {
|
|
180
180
|
throw new CofhesdkError({
|
|
181
181
|
code: CofhesdkErrorCode.PermitNotFound,
|
|
182
|
-
message: `Permit with hash <${this.permitHash}> not found for account <${account}> and chainId <${chainId}>`,
|
|
182
|
+
message: `Permit with hash <${this.permitHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
|
|
183
183
|
hint: 'Ensure the permit exists and is valid.',
|
|
184
184
|
context: {
|
|
185
|
-
chainId,
|
|
186
|
-
account,
|
|
185
|
+
chainId: this.chainId,
|
|
186
|
+
account: this.account,
|
|
187
187
|
permitHash: this.permitHash,
|
|
188
188
|
},
|
|
189
189
|
});
|
|
@@ -192,15 +192,15 @@ export class DecryptHandlesBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Fetch with active permit
|
|
195
|
-
const permit = await permits.getActivePermit(chainId, account);
|
|
195
|
+
const permit = await permits.getActivePermit(this.chainId, this.account);
|
|
196
196
|
if (!permit) {
|
|
197
197
|
throw new CofhesdkError({
|
|
198
198
|
code: CofhesdkErrorCode.PermitNotFound,
|
|
199
|
-
message: `Active permit not found for chainId <${chainId}> and account <${account}>`,
|
|
199
|
+
message: `Active permit not found for chainId <${this.chainId}> and account <${this.account}>`,
|
|
200
200
|
hint: 'Ensure a permit exists for this account on this chain.',
|
|
201
201
|
context: {
|
|
202
|
-
chainId,
|
|
203
|
-
account,
|
|
202
|
+
chainId: this.chainId,
|
|
203
|
+
account: this.account,
|
|
204
204
|
},
|
|
205
205
|
});
|
|
206
206
|
}
|
|
@@ -211,18 +211,23 @@ export class DecryptHandlesBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
211
211
|
* On hardhat, interact with MockZkVerifier contract instead of CoFHE
|
|
212
212
|
*/
|
|
213
213
|
private async mocksSealOutput(permit: Permit): Promise<bigint> {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
this.assertPublicClient();
|
|
215
|
+
|
|
216
|
+
const mocksSealOutputDelay = this.config.mocks.sealOutputDelay;
|
|
217
|
+
return cofheMocksSealOutput(this.ctHash, this.utype, permit, this.publicClient, mocksSealOutputDelay);
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
/**
|
|
220
221
|
* In the production context, perform a true decryption with the CoFHE coprocessor.
|
|
221
222
|
*/
|
|
222
|
-
private async productionSealOutput(
|
|
223
|
-
|
|
223
|
+
private async productionSealOutput(permit: Permit): Promise<bigint> {
|
|
224
|
+
this.assertChainId();
|
|
225
|
+
this.assertPublicClient();
|
|
226
|
+
|
|
227
|
+
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
224
228
|
const permission = PermitUtils.getPermission(permit, true);
|
|
225
|
-
const sealed = await
|
|
229
|
+
// const sealed = await tnSealOutputV1(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
|
|
230
|
+
const sealed = await tnSealOutputV2(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
|
|
226
231
|
return PermitUtils.unseal(permit, sealed);
|
|
227
232
|
}
|
|
228
233
|
|
|
@@ -246,36 +251,37 @@ export class DecryptHandlesBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
246
251
|
*
|
|
247
252
|
* @returns The unsealed item.
|
|
248
253
|
*/
|
|
249
|
-
decrypt(): Promise<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
await this.requireConnectedOrThrow();
|
|
254
|
+
async decrypt(): Promise<UnsealedItem<U>> {
|
|
255
|
+
// Ensure utype is valid
|
|
256
|
+
this.validateUtypeOrThrow();
|
|
253
257
|
|
|
254
|
-
|
|
255
|
-
|
|
258
|
+
// Resolve permit
|
|
259
|
+
const permit = await this.getResolvedPermit();
|
|
256
260
|
|
|
257
|
-
|
|
258
|
-
|
|
261
|
+
// Ensure permit validity
|
|
262
|
+
// TODO: This doesn't validate permit expiration
|
|
263
|
+
// TODO: This doesn't throw, returns a validation result instead
|
|
264
|
+
PermitUtils.validate(permit);
|
|
259
265
|
|
|
260
|
-
|
|
261
|
-
|
|
266
|
+
// TODO: Add this further validation step for the permit
|
|
267
|
+
// TODO: Ensure this throws if the permit is invalid
|
|
268
|
+
PermitUtils.isValid(permit);
|
|
262
269
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
// Extract chainId from signed permit
|
|
271
|
+
// Use this chainId to fetch the threshold network URL since this.chainId may be undefined
|
|
272
|
+
const chainId = permit._signedDomain!.chainId;
|
|
266
273
|
|
|
267
|
-
|
|
268
|
-
|
|
274
|
+
// Check permit validity on-chain
|
|
275
|
+
// TODO: PermitUtils.validateOnChain(permit, this.publicClient);
|
|
269
276
|
|
|
270
|
-
|
|
277
|
+
let unsealed: bigint;
|
|
271
278
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
279
|
+
if (chainId === hardhat.id) {
|
|
280
|
+
unsealed = await this.mocksSealOutput(permit);
|
|
281
|
+
} else {
|
|
282
|
+
unsealed = await this.productionSealOutput(permit);
|
|
283
|
+
}
|
|
277
284
|
|
|
278
|
-
|
|
279
|
-
});
|
|
285
|
+
return convertViaUtype(this.utype, unsealed);
|
|
280
286
|
}
|
|
281
287
|
}
|
|
@@ -2,7 +2,7 @@ import { type Permission, type EthEncryptedData } from '@/permits';
|
|
|
2
2
|
|
|
3
3
|
import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
|
|
4
4
|
|
|
5
|
-
export async function
|
|
5
|
+
export async function tnSealOutputV1(
|
|
6
6
|
ctHash: bigint,
|
|
7
7
|
chainId: number,
|
|
8
8
|
permission: Permission,
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { type Permission, type EthEncryptedData } from '@/permits';
|
|
2
|
+
|
|
3
|
+
import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
|
|
4
|
+
|
|
5
|
+
// Polling configuration
|
|
6
|
+
const POLL_INTERVAL_MS = 1000; // 1 second
|
|
7
|
+
const POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
8
|
+
|
|
9
|
+
// V2 API response types
|
|
10
|
+
type SealOutputSubmitResponse = {
|
|
11
|
+
request_id: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type SealOutputStatusResponse = {
|
|
15
|
+
request_id: string;
|
|
16
|
+
status: 'PROCESSING' | 'COMPLETED';
|
|
17
|
+
submitted_at: string;
|
|
18
|
+
completed_at?: string;
|
|
19
|
+
is_succeed?: boolean;
|
|
20
|
+
sealed?: {
|
|
21
|
+
data: number[];
|
|
22
|
+
public_key: number[];
|
|
23
|
+
nonce: number[];
|
|
24
|
+
};
|
|
25
|
+
signature?: string;
|
|
26
|
+
encryption_type?: number;
|
|
27
|
+
error_message?: string | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Converts a number array to Uint8Array
|
|
32
|
+
*/
|
|
33
|
+
function numberArrayToUint8Array(arr: number[]): Uint8Array {
|
|
34
|
+
return new Uint8Array(arr);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Converts the sealed data from the API response to EthEncryptedData
|
|
39
|
+
*/
|
|
40
|
+
function convertSealedData(sealed: SealOutputStatusResponse['sealed']): EthEncryptedData {
|
|
41
|
+
if (!sealed) {
|
|
42
|
+
throw new CofhesdkError({
|
|
43
|
+
code: CofhesdkErrorCode.SealOutputReturnedNull,
|
|
44
|
+
message: 'Sealed data is missing from completed response',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
data: numberArrayToUint8Array(sealed.data),
|
|
50
|
+
public_key: numberArrayToUint8Array(sealed.public_key),
|
|
51
|
+
nonce: numberArrayToUint8Array(sealed.nonce),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Submits a sealoutput request to the v2 API and returns the request_id
|
|
57
|
+
*/
|
|
58
|
+
async function submitSealOutputRequest(
|
|
59
|
+
thresholdNetworkUrl: string,
|
|
60
|
+
ctHash: bigint,
|
|
61
|
+
chainId: number,
|
|
62
|
+
permission: Permission
|
|
63
|
+
): Promise<string> {
|
|
64
|
+
const body = {
|
|
65
|
+
ct_tempkey: ctHash.toString(16).padStart(64, '0'),
|
|
66
|
+
host_chain_id: chainId,
|
|
67
|
+
permit: permission,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let response: Response;
|
|
71
|
+
try {
|
|
72
|
+
response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
});
|
|
79
|
+
} catch (e) {
|
|
80
|
+
throw new CofhesdkError({
|
|
81
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
82
|
+
message: `sealOutput request failed`,
|
|
83
|
+
hint: 'Ensure the threshold network URL is valid and reachable.',
|
|
84
|
+
cause: e instanceof Error ? e : undefined,
|
|
85
|
+
context: {
|
|
86
|
+
thresholdNetworkUrl,
|
|
87
|
+
body,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle non-200 status codes
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
95
|
+
try {
|
|
96
|
+
const errorBody = await response.json();
|
|
97
|
+
errorMessage = errorBody.error_message || errorBody.message || errorMessage;
|
|
98
|
+
} catch {
|
|
99
|
+
// Ignore JSON parse errors, use status text
|
|
100
|
+
errorMessage = response.statusText || errorMessage;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new CofhesdkError({
|
|
104
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
105
|
+
message: `sealOutput request failed: ${errorMessage}`,
|
|
106
|
+
hint: 'Check the threshold network URL and request parameters.',
|
|
107
|
+
context: {
|
|
108
|
+
thresholdNetworkUrl,
|
|
109
|
+
status: response.status,
|
|
110
|
+
statusText: response.statusText,
|
|
111
|
+
body,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let submitResponse: SealOutputSubmitResponse;
|
|
117
|
+
try {
|
|
118
|
+
submitResponse = (await response.json()) as SealOutputSubmitResponse;
|
|
119
|
+
} catch (e) {
|
|
120
|
+
throw new CofhesdkError({
|
|
121
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
122
|
+
message: `Failed to parse sealOutput submit response`,
|
|
123
|
+
cause: e instanceof Error ? e : undefined,
|
|
124
|
+
context: {
|
|
125
|
+
thresholdNetworkUrl,
|
|
126
|
+
body,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!submitResponse.request_id) {
|
|
132
|
+
throw new CofhesdkError({
|
|
133
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
134
|
+
message: `sealOutput submit response missing request_id`,
|
|
135
|
+
context: {
|
|
136
|
+
thresholdNetworkUrl,
|
|
137
|
+
body,
|
|
138
|
+
submitResponse,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return submitResponse.request_id;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Polls for the sealoutput status until completed or timeout
|
|
148
|
+
*/
|
|
149
|
+
async function pollSealOutputStatus(thresholdNetworkUrl: string, requestId: string): Promise<EthEncryptedData> {
|
|
150
|
+
const startTime = Date.now();
|
|
151
|
+
let completed = false;
|
|
152
|
+
|
|
153
|
+
while (!completed) {
|
|
154
|
+
// Check timeout
|
|
155
|
+
if (Date.now() - startTime > POLL_TIMEOUT_MS) {
|
|
156
|
+
throw new CofhesdkError({
|
|
157
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
158
|
+
message: `sealOutput polling timed out after ${POLL_TIMEOUT_MS}ms`,
|
|
159
|
+
hint: 'The request may still be processing. Try again later.',
|
|
160
|
+
context: {
|
|
161
|
+
thresholdNetworkUrl,
|
|
162
|
+
requestId,
|
|
163
|
+
timeoutMs: POLL_TIMEOUT_MS,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let response: Response;
|
|
169
|
+
try {
|
|
170
|
+
response = await fetch(`${thresholdNetworkUrl}/v2/sealoutput/${requestId}`, {
|
|
171
|
+
method: 'GET',
|
|
172
|
+
headers: {
|
|
173
|
+
'Content-Type': 'application/json',
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
} catch (e) {
|
|
177
|
+
throw new CofhesdkError({
|
|
178
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
179
|
+
message: `sealOutput status poll failed`,
|
|
180
|
+
hint: 'Ensure the threshold network URL is valid and reachable.',
|
|
181
|
+
cause: e instanceof Error ? e : undefined,
|
|
182
|
+
context: {
|
|
183
|
+
thresholdNetworkUrl,
|
|
184
|
+
requestId,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle 404 - request not found
|
|
190
|
+
if (response.status === 404) {
|
|
191
|
+
throw new CofhesdkError({
|
|
192
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
193
|
+
message: `sealOutput request not found: ${requestId}`,
|
|
194
|
+
hint: 'The request may have expired or been invalid.',
|
|
195
|
+
context: {
|
|
196
|
+
thresholdNetworkUrl,
|
|
197
|
+
requestId,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle other non-200 status codes
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
205
|
+
try {
|
|
206
|
+
const errorBody = await response.json();
|
|
207
|
+
errorMessage = errorBody.error_message || errorBody.message || errorMessage;
|
|
208
|
+
} catch {
|
|
209
|
+
errorMessage = response.statusText || errorMessage;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
throw new CofhesdkError({
|
|
213
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
214
|
+
message: `sealOutput status poll failed: ${errorMessage}`,
|
|
215
|
+
context: {
|
|
216
|
+
thresholdNetworkUrl,
|
|
217
|
+
requestId,
|
|
218
|
+
status: response.status,
|
|
219
|
+
statusText: response.statusText,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let statusResponse: SealOutputStatusResponse;
|
|
225
|
+
try {
|
|
226
|
+
statusResponse = (await response.json()) as SealOutputStatusResponse;
|
|
227
|
+
} catch (e) {
|
|
228
|
+
throw new CofhesdkError({
|
|
229
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
230
|
+
message: `Failed to parse sealOutput status response`,
|
|
231
|
+
cause: e instanceof Error ? e : undefined,
|
|
232
|
+
context: {
|
|
233
|
+
thresholdNetworkUrl,
|
|
234
|
+
requestId,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check if completed
|
|
240
|
+
if (statusResponse.status === 'COMPLETED') {
|
|
241
|
+
// Check if succeeded
|
|
242
|
+
if (statusResponse.is_succeed === false) {
|
|
243
|
+
const errorMessage = statusResponse.error_message || 'Unknown error';
|
|
244
|
+
throw new CofhesdkError({
|
|
245
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
246
|
+
message: `sealOutput request failed: ${errorMessage}`,
|
|
247
|
+
context: {
|
|
248
|
+
thresholdNetworkUrl,
|
|
249
|
+
requestId,
|
|
250
|
+
statusResponse,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check if sealed data exists
|
|
256
|
+
if (!statusResponse.sealed) {
|
|
257
|
+
throw new CofhesdkError({
|
|
258
|
+
code: CofhesdkErrorCode.SealOutputReturnedNull,
|
|
259
|
+
message: `sealOutput request completed but returned no sealed data`,
|
|
260
|
+
context: {
|
|
261
|
+
thresholdNetworkUrl,
|
|
262
|
+
requestId,
|
|
263
|
+
statusResponse,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Convert and return the sealed data
|
|
269
|
+
return convertSealedData(statusResponse.sealed);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Still processing, wait before next poll
|
|
273
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// This should never be reached, but TypeScript requires it
|
|
277
|
+
throw new CofhesdkError({
|
|
278
|
+
code: CofhesdkErrorCode.SealOutputFailed,
|
|
279
|
+
message: 'Polling loop exited unexpectedly',
|
|
280
|
+
context: {
|
|
281
|
+
thresholdNetworkUrl,
|
|
282
|
+
requestId,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function tnSealOutputV2(
|
|
288
|
+
ctHash: bigint,
|
|
289
|
+
chainId: number,
|
|
290
|
+
permission: Permission,
|
|
291
|
+
thresholdNetworkUrl: string
|
|
292
|
+
): Promise<EthEncryptedData> {
|
|
293
|
+
// Step 1: Submit the request and get request_id
|
|
294
|
+
const requestId = await submitSealOutputRequest(thresholdNetworkUrl, ctHash, chainId, permission);
|
|
295
|
+
|
|
296
|
+
// Step 2: Poll for status until completed
|
|
297
|
+
return await pollSealOutputStatus(thresholdNetworkUrl, requestId);
|
|
298
|
+
}
|