@cofhe/sdk 0.1.1 → 0.2.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.
- package/CHANGELOG.md +14 -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/index.ts +3 -1
- package/core/baseBuilder.ts +30 -49
- package/core/client.test.ts +94 -77
- package/core/client.ts +133 -149
- package/core/clientTypes.ts +108 -0
- package/core/config.test.ts +22 -11
- package/core/config.ts +16 -9
- 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 +16 -10
- 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 +29 -17
- package/core/keyStore.ts +65 -38
- package/core/permits.test.ts +253 -1
- package/core/permits.ts +80 -16
- 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 +14 -1
- package/dist/chains.d.cts +23 -1
- package/dist/chains.d.ts +23 -1
- package/dist/chains.js +1 -1
- package/dist/{chunk-LU7BMUUT.js → chunk-UGBVZNRT.js} +39 -25
- package/dist/{chunk-GZCQQYVI.js → chunk-WEAZ25JO.js} +14 -2
- package/dist/{chunk-KFGPTJ6X.js → chunk-WGCRJCBR.js} +1920 -1692
- package/dist/{types-bB7wLj0q.d.cts → clientTypes-5_1nwtUe.d.cts} +308 -347
- package/dist/{types-PhwGgQvs.d.ts → clientTypes-Es7fyi65.d.ts} +308 -347
- package/dist/core.cjs +2872 -2632
- package/dist/core.d.cts +101 -6
- package/dist/core.d.ts +101 -6
- package/dist/core.js +3 -3
- package/dist/node.cjs +2716 -2520
- package/dist/node.d.cts +3 -3
- package/dist/node.d.ts +3 -3
- package/dist/node.js +4 -3
- package/dist/{permit-S9CnI6MF.d.cts → permit-fUSe6KKq.d.cts} +31 -15
- package/dist/{permit-S9CnI6MF.d.ts → permit-fUSe6KKq.d.ts} +31 -15
- package/dist/permits.cjs +39 -24
- package/dist/permits.d.cts +137 -148
- package/dist/permits.d.ts +137 -148
- package/dist/permits.js +1 -1
- package/dist/web.cjs +2929 -2518
- package/dist/web.d.cts +21 -5
- package/dist/web.d.ts +21 -5
- 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 +14 -14
- package/permits/index.ts +1 -0
- package/permits/localstorage.test.ts +0 -1
- package/permits/permit.test.ts +25 -22
- package/permits/permit.ts +30 -21
- package/permits/sealing.test.ts +3 -3
- package/permits/sealing.ts +2 -2
- package/permits/store.ts +5 -7
- package/permits/test-utils.ts +1 -1
- package/permits/types.ts +17 -0
- package/permits/utils.ts +0 -1
- package/permits/validation.ts +24 -4
- 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/permits/validation.ts
CHANGED
|
@@ -8,6 +8,8 @@ const SerializedSealingPair = z.object({
|
|
|
8
8
|
publicKey: z.string(),
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
+
const DEFAULT_EXPIRATION_FN = () => Math.round(Date.now() / 1000) + 7 * 24 * 60 * 60; // 7 days from now
|
|
12
|
+
|
|
11
13
|
const zPermitWithDefaults = z.object({
|
|
12
14
|
name: z.string().optional().default('Unnamed Permit'),
|
|
13
15
|
type: z.enum(['self', 'sharing', 'recipient']),
|
|
@@ -19,7 +21,7 @@ const zPermitWithDefaults = z.object({
|
|
|
19
21
|
.refine((val) => val !== zeroAddress, {
|
|
20
22
|
message: 'Permit issuer :: must not be zeroAddress',
|
|
21
23
|
}),
|
|
22
|
-
expiration: z.number().optional().default(
|
|
24
|
+
expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
|
|
23
25
|
recipient: z
|
|
24
26
|
.string()
|
|
25
27
|
.optional()
|
|
@@ -75,11 +77,14 @@ export const SelfPermitOptionsValidator = z
|
|
|
75
77
|
.refine((val) => isAddress(val), {
|
|
76
78
|
message: 'Self permit issuer :: invalid address',
|
|
77
79
|
})
|
|
80
|
+
.refine((val) => is0xPrefixed(val), {
|
|
81
|
+
message: 'Self permit issuer :: must be 0x prefixed',
|
|
82
|
+
})
|
|
78
83
|
.refine((val) => val !== zeroAddress, {
|
|
79
84
|
message: 'Self permit issuer :: must not be zeroAddress',
|
|
80
85
|
}),
|
|
81
86
|
name: z.string().optional().default('Unnamed Permit'),
|
|
82
|
-
expiration: z.number().optional().default(
|
|
87
|
+
expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
|
|
83
88
|
recipient: z
|
|
84
89
|
.string()
|
|
85
90
|
.optional()
|
|
@@ -87,6 +92,9 @@ export const SelfPermitOptionsValidator = z
|
|
|
87
92
|
.refine((val) => isAddress(val), {
|
|
88
93
|
message: 'Self permit recipient :: invalid address',
|
|
89
94
|
})
|
|
95
|
+
.refine((val) => is0xPrefixed(val), {
|
|
96
|
+
message: 'Self permit recipient :: must be 0x prefixed',
|
|
97
|
+
})
|
|
90
98
|
.refine((val) => val === zeroAddress, {
|
|
91
99
|
message: 'Self permit recipient :: must be zeroAddress',
|
|
92
100
|
}),
|
|
@@ -148,6 +156,9 @@ export const SharingPermitOptionsValidator = z
|
|
|
148
156
|
.refine((val) => isAddress(val), {
|
|
149
157
|
message: 'Sharing permit issuer :: invalid address',
|
|
150
158
|
})
|
|
159
|
+
.refine((val) => is0xPrefixed(val), {
|
|
160
|
+
message: 'Sharing permit issuer :: must be 0x prefixed',
|
|
161
|
+
})
|
|
151
162
|
.refine((val) => val !== zeroAddress, {
|
|
152
163
|
message: 'Sharing permit issuer :: must not be zeroAddress',
|
|
153
164
|
}),
|
|
@@ -156,11 +167,14 @@ export const SharingPermitOptionsValidator = z
|
|
|
156
167
|
.refine((val) => isAddress(val), {
|
|
157
168
|
message: 'Sharing permit recipient :: invalid address',
|
|
158
169
|
})
|
|
170
|
+
.refine((val) => is0xPrefixed(val), {
|
|
171
|
+
message: 'Sharing permit recipient :: must be 0x prefixed',
|
|
172
|
+
})
|
|
159
173
|
.refine((val) => val !== zeroAddress, {
|
|
160
174
|
message: 'Sharing permit recipient :: must not be zeroAddress',
|
|
161
175
|
}),
|
|
162
176
|
name: z.string().optional().default('Unnamed Permit'),
|
|
163
|
-
expiration: z.number().optional().default(
|
|
177
|
+
expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
|
|
164
178
|
validatorId: z.number().optional().default(0),
|
|
165
179
|
validatorContract: z
|
|
166
180
|
.string()
|
|
@@ -219,6 +233,9 @@ export const ImportPermitOptionsValidator = z
|
|
|
219
233
|
.refine((val) => isAddress(val), {
|
|
220
234
|
message: 'Import permit issuer :: invalid address',
|
|
221
235
|
})
|
|
236
|
+
.refine((val) => is0xPrefixed(val), {
|
|
237
|
+
message: 'Import permit issuer :: must be 0x prefixed',
|
|
238
|
+
})
|
|
222
239
|
.refine((val) => val !== zeroAddress, {
|
|
223
240
|
message: 'Import permit issuer :: must not be zeroAddress',
|
|
224
241
|
}),
|
|
@@ -227,6 +244,9 @@ export const ImportPermitOptionsValidator = z
|
|
|
227
244
|
.refine((val) => isAddress(val), {
|
|
228
245
|
message: 'Import permit recipient :: invalid address',
|
|
229
246
|
})
|
|
247
|
+
.refine((val) => is0xPrefixed(val), {
|
|
248
|
+
message: 'Import permit recipient :: must be 0x prefixed',
|
|
249
|
+
})
|
|
230
250
|
.refine((val) => val !== zeroAddress, {
|
|
231
251
|
message: 'Import permit recipient :: must not be zeroAddress',
|
|
232
252
|
}),
|
|
@@ -239,7 +259,7 @@ export const ImportPermitOptionsValidator = z
|
|
|
239
259
|
message: 'Import permit :: issuerSignature must be provided',
|
|
240
260
|
}),
|
|
241
261
|
name: z.string().optional().default('Unnamed Permit'),
|
|
242
|
-
expiration: z.number().optional().default(
|
|
262
|
+
expiration: z.number().optional().default(DEFAULT_EXPIRATION_FN),
|
|
243
263
|
validatorId: z.number().optional().default(0),
|
|
244
264
|
validatorContract: z
|
|
245
265
|
.string()
|
package/web/client.web.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CofhesdkClient } from '@/core';
|
|
1
|
+
import { CofhesdkErrorCode, CofhesdkError, type CofhesdkClient } from '@/core';
|
|
2
2
|
import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
|
|
3
3
|
|
|
4
4
|
import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
|
|
@@ -60,11 +60,16 @@ describe('@cofhe/web - Client', () => {
|
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
describe('Environment', () => {
|
|
64
|
+
it('should have the correct environment', () => {
|
|
65
|
+
expect(cofhesdkClient.config.environment).toBe('web');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
63
69
|
describe('Connection', () => {
|
|
64
70
|
it('should connect to real chain', async () => {
|
|
65
|
-
|
|
71
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
66
72
|
|
|
67
|
-
expect(result.success).toBe(true);
|
|
68
73
|
expect(cofhesdkClient.connected).toBe(true);
|
|
69
74
|
|
|
70
75
|
const snapshot = cofhesdkClient.getSnapshot();
|
|
@@ -74,15 +79,17 @@ describe('@cofhe/web - Client', () => {
|
|
|
74
79
|
}, 30000);
|
|
75
80
|
|
|
76
81
|
it('should handle network errors', async () => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
try {
|
|
83
|
+
await cofhesdkClient.connect(
|
|
84
|
+
{
|
|
85
|
+
getChainId: vi.fn().mockRejectedValue(new Error('Network error')),
|
|
86
|
+
} as unknown as PublicClient,
|
|
87
|
+
walletClient
|
|
88
|
+
);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
expect(error).toBeInstanceOf(CofhesdkError);
|
|
91
|
+
expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.PublicWalletGetChainIdFailed);
|
|
92
|
+
}
|
|
86
93
|
}, 30000);
|
|
87
94
|
});
|
|
88
95
|
|
|
@@ -113,23 +120,11 @@ describe('@cofhe/web - Client', () => {
|
|
|
113
120
|
}, 30000);
|
|
114
121
|
});
|
|
115
122
|
|
|
116
|
-
describe('Initialization Results', () => {
|
|
117
|
-
it('should have keyFetchResult promise', () => {
|
|
118
|
-
expect(cofhesdkClient.initializationResults).toBeDefined();
|
|
119
|
-
expect(cofhesdkClient.initializationResults.keyFetchResult).toBeInstanceOf(Promise);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should resolve keyFetchResult', async () => {
|
|
123
|
-
const result = await cofhesdkClient.initializationResults.keyFetchResult;
|
|
124
|
-
expect(result.success).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
123
|
describe('Builder Creation', () => {
|
|
129
124
|
it('should create encrypt builder after connection', async () => {
|
|
130
125
|
await cofhesdkClient.connect(publicClient, walletClient);
|
|
131
126
|
|
|
132
|
-
const builder = cofhesdkClient.encryptInputs([{ data: 100n, utype: 2 }]);
|
|
127
|
+
const builder = cofhesdkClient.encryptInputs([{ data: 100n, utype: 2, securityZone: 0 }]);
|
|
133
128
|
|
|
134
129
|
expect(builder).toBeDefined();
|
|
135
130
|
expect(typeof builder.setChainId).toBe('function');
|
package/web/config.web.test.ts
CHANGED
|
@@ -42,14 +42,12 @@ describe('@cofhe/web - Config', () => {
|
|
|
42
42
|
it('should preserve all other config options', () => {
|
|
43
43
|
const config = createCofhesdkConfig({
|
|
44
44
|
supportedChains: [arbSepolia],
|
|
45
|
-
fheKeysPrefetching: 'OFF',
|
|
46
45
|
mocks: {
|
|
47
46
|
sealOutputDelay: 500,
|
|
48
47
|
},
|
|
49
48
|
});
|
|
50
49
|
|
|
51
50
|
expect(config.supportedChains).toEqual([arbSepolia]);
|
|
52
|
-
expect(config.fheKeysPrefetching).toBe('OFF');
|
|
53
51
|
expect(config.mocks.sealOutputDelay).toBe(500);
|
|
54
52
|
expect(config.fheKeyStorage).toBeDefined();
|
|
55
53
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
|
|
2
|
-
import { Encryptable, FheTypes, type CofhesdkClient,
|
|
2
|
+
import { Encryptable, FheTypes, type CofhesdkClient, CofhesdkErrorCode, CofhesdkError } from '@/core';
|
|
3
3
|
|
|
4
4
|
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
|
5
5
|
import type { PublicClient, WalletClient } from 'viem';
|
|
@@ -11,22 +11,6 @@ import { createCofhesdkClient, createCofhesdkConfig } from './index.js';
|
|
|
11
11
|
// Real test setup - runs in browser with real tfhe
|
|
12
12
|
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
13
13
|
|
|
14
|
-
const expectResultSuccess = <T>(result: Result<T>): T => {
|
|
15
|
-
expect(result.success, `Result error: ${result.error?.toString()}`).toBe(true);
|
|
16
|
-
return result.data!;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const expectResultError = <T>(result: Result<T>, errorCode?: CofhesdkErrorCode): void => {
|
|
20
|
-
expect(result.success).toBe(false);
|
|
21
|
-
expect(result.data).toBe(null);
|
|
22
|
-
expect(result.error).not.toBe(null);
|
|
23
|
-
const error = result.error as CofhesdkError;
|
|
24
|
-
expect(error).toBeInstanceOf(CofhesdkError);
|
|
25
|
-
if (errorCode) {
|
|
26
|
-
expect(error.code, `Result error: ${result.error?.toString()}`).toBe(errorCode);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
14
|
describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
31
15
|
let cofhesdkClient: CofhesdkClient;
|
|
32
16
|
let publicClient: PublicClient;
|
|
@@ -60,22 +44,19 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
|
60
44
|
|
|
61
45
|
// This will trigger real TFHE initialization in browser
|
|
62
46
|
const result = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
|
|
63
|
-
const encrypted = expectResultSuccess(result);
|
|
64
47
|
|
|
65
48
|
// If we get here, TFHE was initialized successfully
|
|
66
|
-
expect(
|
|
49
|
+
expect(result).toBeDefined();
|
|
67
50
|
}, 60000); // Longer timeout for real operations
|
|
68
51
|
|
|
69
52
|
it('should handle multiple encryptions without re-initializing', async () => {
|
|
70
53
|
await cofhesdkClient.connect(publicClient, walletClient);
|
|
71
54
|
|
|
72
55
|
// First encryption
|
|
73
|
-
|
|
74
|
-
expectResultSuccess(result1);
|
|
56
|
+
expect(cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt()).resolves.not.toThrow();
|
|
75
57
|
|
|
76
58
|
// Second encryption should reuse initialization
|
|
77
|
-
|
|
78
|
-
expectResultSuccess(result2);
|
|
59
|
+
expect(cofhesdkClient.encryptInputs([Encryptable.uint64(50n)]).encrypt()).resolves.not.toThrow();
|
|
79
60
|
}, 60000);
|
|
80
61
|
});
|
|
81
62
|
|
|
@@ -84,15 +65,15 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
|
84
65
|
await cofhesdkClient.connect(publicClient, walletClient);
|
|
85
66
|
|
|
86
67
|
const result = await cofhesdkClient.encryptInputs([Encryptable.bool(true)]).encrypt();
|
|
87
|
-
const encrypted = expectResultSuccess(result);
|
|
88
68
|
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
91
|
-
expect(
|
|
92
|
-
expect(
|
|
93
|
-
expect(
|
|
94
|
-
expect(
|
|
95
|
-
expect(
|
|
69
|
+
expect(result).toBeDefined();
|
|
70
|
+
expect(result.length).toBe(1);
|
|
71
|
+
expect(result[0].utype).toBe(FheTypes.Bool);
|
|
72
|
+
expect(result[0].ctHash).toBeDefined();
|
|
73
|
+
expect(typeof result[0].ctHash).toBe('bigint');
|
|
74
|
+
expect(result[0].signature).toBeDefined();
|
|
75
|
+
expect(typeof result[0].signature).toBe('string');
|
|
76
|
+
expect(result[0].securityZone).toBe(0);
|
|
96
77
|
}, 60000);
|
|
97
78
|
|
|
98
79
|
it('should encrypt all supported types together', async () => {
|
|
@@ -109,17 +90,17 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
|
109
90
|
];
|
|
110
91
|
|
|
111
92
|
const result = await cofhesdkClient.encryptInputs(inputs).encrypt();
|
|
112
|
-
|
|
93
|
+
expect(result).toBeDefined();
|
|
113
94
|
|
|
114
|
-
expect(
|
|
95
|
+
expect(result.length).toBe(7);
|
|
115
96
|
// Verify each type
|
|
116
|
-
expect(
|
|
117
|
-
expect(
|
|
118
|
-
expect(
|
|
119
|
-
expect(
|
|
120
|
-
expect(
|
|
121
|
-
expect(
|
|
122
|
-
expect(
|
|
97
|
+
expect(result[0].utype).toBe(FheTypes.Bool);
|
|
98
|
+
expect(result[1].utype).toBe(FheTypes.Uint8);
|
|
99
|
+
expect(result[2].utype).toBe(FheTypes.Uint16);
|
|
100
|
+
expect(result[3].utype).toBe(FheTypes.Uint32);
|
|
101
|
+
expect(result[4].utype).toBe(FheTypes.Uint64);
|
|
102
|
+
expect(result[5].utype).toBe(FheTypes.Uint128);
|
|
103
|
+
expect(result[6].utype).toBe(FheTypes.Uint160);
|
|
123
104
|
}, 90000); // Longer timeout for multiple encryptions
|
|
124
105
|
});
|
|
125
106
|
|
|
@@ -128,14 +109,13 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
|
128
109
|
await cofhesdkClient.connect(publicClient, walletClient);
|
|
129
110
|
|
|
130
111
|
const snapshot = cofhesdkClient.getSnapshot();
|
|
131
|
-
const
|
|
112
|
+
const encrypted = await cofhesdkClient
|
|
132
113
|
.encryptInputs([Encryptable.uint128(100n)])
|
|
133
114
|
.setChainId(snapshot.chainId!)
|
|
134
115
|
.setAccount(snapshot.account!)
|
|
135
116
|
.setSecurityZone(0)
|
|
136
117
|
.encrypt();
|
|
137
118
|
|
|
138
|
-
const encrypted = expectResultSuccess(result);
|
|
139
119
|
expect(encrypted.length).toBe(1);
|
|
140
120
|
expect(encrypted[0].utype).toBe(FheTypes.Uint128);
|
|
141
121
|
}, 60000);
|
|
@@ -144,9 +124,12 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
|
144
124
|
describe('Browser Error Handling', () => {
|
|
145
125
|
it('should fail gracefully when not connected', async () => {
|
|
146
126
|
// Don't connect the client
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
127
|
+
try {
|
|
128
|
+
const promise = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
expect(error).toBeInstanceOf(CofhesdkError);
|
|
131
|
+
expect((error as CofhesdkError).code).toBe(CofhesdkErrorCode.NotConnected);
|
|
132
|
+
}
|
|
150
133
|
}, 30000);
|
|
151
134
|
|
|
152
135
|
it('should handle invalid CoFHE URL', async () => {
|
|
@@ -158,18 +141,13 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
|
158
141
|
verifierUrl: 'http://invalid-verifier-url.local',
|
|
159
142
|
},
|
|
160
143
|
],
|
|
161
|
-
fheKeysPrefetching: 'OFF',
|
|
162
144
|
});
|
|
163
145
|
|
|
164
146
|
const badClient = createCofhesdkClient(badConfig);
|
|
165
147
|
await badClient.connect(publicClient, walletClient);
|
|
166
148
|
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
expect(result.success).toBe(false);
|
|
170
|
-
if (!result.success) {
|
|
171
|
-
expect(result.error).toBeDefined();
|
|
172
|
-
}
|
|
149
|
+
const promise = badClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
|
|
150
|
+
expect(promise).rejects.toThrow();
|
|
173
151
|
}, 60000);
|
|
174
152
|
});
|
|
175
153
|
|
|
@@ -181,8 +159,7 @@ describe('@cofhe/web - Encrypt Inputs Browser Tests', () => {
|
|
|
181
159
|
|
|
182
160
|
// Perform 5 encryptions
|
|
183
161
|
for (let i = 0; i < 5; i++) {
|
|
184
|
-
|
|
185
|
-
expectResultSuccess(result);
|
|
162
|
+
await cofhesdkClient.encryptInputs([Encryptable.uint128(BigInt(i))]).encrypt();
|
|
186
163
|
}
|
|
187
164
|
|
|
188
165
|
const duration = Date.now() - start;
|
package/web/index.ts
CHANGED
|
@@ -8,11 +8,16 @@ import {
|
|
|
8
8
|
type CofhesdkInputConfig,
|
|
9
9
|
type ZkBuilderAndCrsGenerator,
|
|
10
10
|
type FheKeyDeserializer,
|
|
11
|
+
type EncryptableItem,
|
|
12
|
+
fheTypeToString,
|
|
11
13
|
} from '@/core';
|
|
12
14
|
|
|
13
15
|
// Import web-specific storage (internal use only)
|
|
14
16
|
import { createWebStorage } from './storage.js';
|
|
15
17
|
|
|
18
|
+
// Import worker manager
|
|
19
|
+
import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
|
|
20
|
+
|
|
16
21
|
// Import tfhe for web
|
|
17
22
|
import init, { init_panic_hook, TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
|
|
18
23
|
|
|
@@ -68,6 +73,27 @@ const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: st
|
|
|
68
73
|
return { zkBuilder, zkCrs };
|
|
69
74
|
};
|
|
70
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Worker-enabled zkProve function
|
|
78
|
+
* This submits proof generation to a Web Worker
|
|
79
|
+
*/
|
|
80
|
+
async function zkProveWithWorker(
|
|
81
|
+
fheKeyHex: string,
|
|
82
|
+
crsHex: string,
|
|
83
|
+
items: EncryptableItem[],
|
|
84
|
+
metadata: Uint8Array
|
|
85
|
+
): Promise<Uint8Array> {
|
|
86
|
+
// Serialize items for worker (convert enum to string name)
|
|
87
|
+
const serializedItems = items.map((item) => ({
|
|
88
|
+
utype: fheTypeToString(item.utype),
|
|
89
|
+
data: typeof item.data === 'bigint' ? item.data.toString() : item.data,
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
// Submit to worker
|
|
93
|
+
const workerManager = getWorkerManager();
|
|
94
|
+
return await workerManager.submitProof(fheKeyHex, crsHex, serializedItems, metadata);
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
/**
|
|
72
98
|
* Creates a CoFHE SDK configuration for web with IndexedDB storage as default
|
|
73
99
|
* @param config - The CoFHE SDK input configuration (fheKeyStorage will default to IndexedDB if not provided)
|
|
@@ -75,6 +101,7 @@ const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: st
|
|
|
75
101
|
*/
|
|
76
102
|
export function createCofhesdkConfig(config: CofhesdkInputConfig): CofhesdkConfig {
|
|
77
103
|
return createCofhesdkConfigBase({
|
|
104
|
+
environment: 'web',
|
|
78
105
|
...config,
|
|
79
106
|
fheKeyStorage: config.fheKeyStorage === null ? null : config.fheKeyStorage ?? createWebStorage(),
|
|
80
107
|
});
|
|
@@ -83,15 +110,52 @@ export function createCofhesdkConfig(config: CofhesdkInputConfig): CofhesdkConfi
|
|
|
83
110
|
/**
|
|
84
111
|
* Creates a CoFHE SDK client instance for web with TFHE automatically configured
|
|
85
112
|
* TFHE will be initialized automatically on first encryption - no manual setup required
|
|
113
|
+
* Workers are automatically enabled if available (can be disabled via config.useWorkers)
|
|
86
114
|
* @param config - The CoFHE SDK configuration (use createCofhesdkConfig to create with web defaults)
|
|
87
115
|
* @returns The CoFHE SDK client instance
|
|
88
116
|
*/
|
|
89
|
-
export function createCofhesdkClient(config:
|
|
117
|
+
export function createCofhesdkClient<TConfig extends CofhesdkConfig>(config: TConfig): CofhesdkClient<TConfig> {
|
|
118
|
+
return createCofhesdkClientBase({
|
|
119
|
+
config,
|
|
120
|
+
zkBuilderAndCrsGenerator,
|
|
121
|
+
tfhePublicKeyDeserializer,
|
|
122
|
+
compactPkeCrsDeserializer,
|
|
123
|
+
initTfhe,
|
|
124
|
+
// Always provide the worker function if available - config.useWorkers controls usage
|
|
125
|
+
// areWorkersAvailable will return true if the Worker API is available and false in Node.js
|
|
126
|
+
zkProveWorkerFn: areWorkersAvailable() ? zkProveWithWorker : undefined,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Terminate the worker (call on app cleanup)
|
|
132
|
+
*/
|
|
133
|
+
export { terminateWorker };
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if workers are available
|
|
137
|
+
*/
|
|
138
|
+
export { areWorkersAvailable };
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Test helper: Create a client with custom worker function (for testing fallback behavior)
|
|
142
|
+
* @internal - Only for testing purposes
|
|
143
|
+
*/
|
|
144
|
+
export function createCofhesdkClientWithCustomWorker(
|
|
145
|
+
config: CofhesdkConfig,
|
|
146
|
+
customZkProveWorkerFn: (
|
|
147
|
+
fheKeyHex: string,
|
|
148
|
+
crsHex: string,
|
|
149
|
+
items: EncryptableItem[],
|
|
150
|
+
metadata: Uint8Array
|
|
151
|
+
) => Promise<Uint8Array>
|
|
152
|
+
): CofhesdkClient {
|
|
90
153
|
return createCofhesdkClientBase({
|
|
91
154
|
config,
|
|
92
155
|
zkBuilderAndCrsGenerator,
|
|
93
156
|
tfhePublicKeyDeserializer,
|
|
94
157
|
compactPkeCrsDeserializer,
|
|
95
158
|
initTfhe,
|
|
159
|
+
zkProveWorkerFn: customZkProveWorkerFn,
|
|
96
160
|
});
|
|
97
161
|
}
|
package/web/storage.ts
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import type { IStorage } from '@/core';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { constructClient } from 'iframe-shared-storage';
|
|
4
3
|
/**
|
|
5
4
|
* Creates a web storage implementation using IndexedDB
|
|
6
5
|
* @returns IStorage implementation for browser environments
|
|
7
6
|
*/
|
|
8
7
|
export const createWebStorage = (): IStorage => {
|
|
8
|
+
const client = constructClient({
|
|
9
|
+
iframe: {
|
|
10
|
+
src: 'https://iframe-shared-storage.vercel.app/hub.html',
|
|
11
|
+
messagingOptions: {
|
|
12
|
+
enableLog: 'both',
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
iframeReadyTimeoutMs: 30_000, // if the iframe is not initied during this interval AND a reuqest is made, such request will throw an error
|
|
16
|
+
methodCallTimeoutMs: 10_000, // if a method call is not answered during this interval, the call will throw an error
|
|
17
|
+
methodCallRetries: 3, // number of retries for a method call if it times out
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const indexedDBKeyval = client.indexedDBKeyval;
|
|
9
22
|
return {
|
|
10
23
|
getItem: async (name: string) => {
|
|
11
|
-
|
|
24
|
+
// IndexedDBKeyval returns undefined if not found, but we want null (a json-deserialized value is expected)
|
|
25
|
+
return (await indexedDBKeyval.get(name)) ?? null;
|
|
12
26
|
},
|
|
13
27
|
setItem: async (name: string, value: any) => {
|
|
14
|
-
await set(name, value);
|
|
28
|
+
await indexedDBKeyval.set(name, value);
|
|
15
29
|
},
|
|
16
30
|
removeItem: async (name: string) => {
|
|
17
|
-
await del(name);
|
|
31
|
+
await indexedDBKeyval.del(name);
|
|
18
32
|
},
|
|
19
33
|
};
|
|
20
34
|
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
|
2
|
+
import { createCofhesdkClient, createCofhesdkConfig } from './index.js';
|
|
3
|
+
import { Encryptable, type CofhesdkClient } from '@/core';
|
|
4
|
+
import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
|
|
5
|
+
import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
6
|
+
import { createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
|
|
7
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
|
+
|
|
9
|
+
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
10
|
+
|
|
11
|
+
describe('@cofhe/sdk/web - EncryptInputsBuilder Worker Methods', () => {
|
|
12
|
+
let cofhesdkClient: CofhesdkClient;
|
|
13
|
+
let publicClient: PublicClient;
|
|
14
|
+
let walletClient: WalletClient;
|
|
15
|
+
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
publicClient = createPublicClient({
|
|
18
|
+
chain: viemArbitrumSepolia,
|
|
19
|
+
transport: http(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
23
|
+
walletClient = createWalletClient({
|
|
24
|
+
chain: viemArbitrumSepolia,
|
|
25
|
+
transport: http(),
|
|
26
|
+
account,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
const config = createCofhesdkConfig({
|
|
32
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
33
|
+
});
|
|
34
|
+
cofhesdkClient = createCofhesdkClient(config);
|
|
35
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('setUseWorker method', () => {
|
|
39
|
+
it('should have setUseWorker method on EncryptInputsBuilder', () => {
|
|
40
|
+
const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]);
|
|
41
|
+
|
|
42
|
+
expect(builder).toHaveProperty('setUseWorker');
|
|
43
|
+
expect(typeof builder.setUseWorker).toBe('function');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return builder for method chaining', () => {
|
|
47
|
+
const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]);
|
|
48
|
+
const returnedBuilder = builder.setUseWorker(false);
|
|
49
|
+
|
|
50
|
+
// Should return the same builder instance (or at least same type)
|
|
51
|
+
expect(returnedBuilder).toBe(builder);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should allow chaining with other builder methods', () => {
|
|
55
|
+
// Should be able to chain setUseWorker with setStepCallback
|
|
56
|
+
const builder = cofhesdkClient
|
|
57
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
58
|
+
.setUseWorker(false)
|
|
59
|
+
.setStepCallback(() => {});
|
|
60
|
+
|
|
61
|
+
expect(builder).toBeDefined();
|
|
62
|
+
expect(builder).toHaveProperty('encrypt');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should accept true parameter', () => {
|
|
66
|
+
expect(() => {
|
|
67
|
+
cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(true);
|
|
68
|
+
}).not.toThrow();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should accept false parameter', () => {
|
|
72
|
+
expect(() => {
|
|
73
|
+
cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(false);
|
|
74
|
+
}).not.toThrow();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should have getUseWorker method', () => {
|
|
78
|
+
const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]);
|
|
79
|
+
|
|
80
|
+
expect(builder).toHaveProperty('getUseWorker');
|
|
81
|
+
expect(typeof builder.getUseWorker).toBe('function');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return current useWorker value', () => {
|
|
85
|
+
const builderWithWorkers = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(true);
|
|
86
|
+
const builderWithoutWorkers = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(false);
|
|
87
|
+
|
|
88
|
+
// Should reflect config values
|
|
89
|
+
expect(builderWithWorkers.getUseWorker()).toBe(true);
|
|
90
|
+
expect(builderWithoutWorkers.getUseWorker()).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should reflect changes from setUseWorker', () => {
|
|
94
|
+
const builder = cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).setUseWorker(true);
|
|
95
|
+
expect(builder.getUseWorker()).toBe(true);
|
|
96
|
+
|
|
97
|
+
builder.setUseWorker(false);
|
|
98
|
+
expect(builder.getUseWorker()).toBe(false);
|
|
99
|
+
|
|
100
|
+
builder.setUseWorker(true);
|
|
101
|
+
expect(builder.getUseWorker()).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('Worker function availability', () => {
|
|
106
|
+
it('should initialize client without errors', () => {
|
|
107
|
+
const config = createCofhesdkConfig({
|
|
108
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
109
|
+
useWorkers: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(() => {
|
|
113
|
+
createCofhesdkClient(config);
|
|
114
|
+
}).not.toThrow();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle worker function when workers enabled', async () => {
|
|
118
|
+
const config = createCofhesdkConfig({
|
|
119
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
120
|
+
useWorkers: true,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const client = createCofhesdkClient(config);
|
|
124
|
+
await client.connect(publicClient, walletClient);
|
|
125
|
+
const builder = client.encryptInputs([Encryptable.uint128(100n)]);
|
|
126
|
+
|
|
127
|
+
// Should not throw even though workers aren't available in Node
|
|
128
|
+
expect(() => {
|
|
129
|
+
builder.setUseWorker(true);
|
|
130
|
+
}).not.toThrow();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle when workers disabled', async () => {
|
|
134
|
+
const config = createCofhesdkConfig({
|
|
135
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
136
|
+
useWorkers: false,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const client = createCofhesdkClient(config);
|
|
140
|
+
await client.connect(publicClient, walletClient);
|
|
141
|
+
const builder = client.encryptInputs([Encryptable.uint128(100n)]);
|
|
142
|
+
|
|
143
|
+
expect(() => {
|
|
144
|
+
builder.setUseWorker(false);
|
|
145
|
+
}).not.toThrow();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|