@cofhe/sdk 0.4.0 → 0.5.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 +38 -0
- package/adapters/{ethers5.test.ts → test/ethers5.test.ts} +2 -2
- package/adapters/{ethers6.test.ts → test/ethers6.test.ts} +2 -2
- package/adapters/{hardhat.hh2.test.ts → test/hardhat.hh2.test.ts} +2 -2
- package/adapters/{index.test.ts → test/index.test.ts} +1 -1
- package/adapters/{wagmi.test.ts → test/wagmi.test.ts} +1 -1
- package/chains/{chains.test.ts → test/chains.test.ts} +1 -1
- package/core/client.ts +11 -1
- package/core/clientTypes.ts +3 -1
- package/core/consts.ts +9 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
- package/core/decrypt/decryptForTxBuilder.ts +16 -2
- package/core/decrypt/decryptForViewBuilder.ts +14 -7
- package/core/decrypt/polling.ts +14 -0
- package/core/decrypt/tnDecryptV2.ts +250 -110
- package/core/decrypt/tnSealOutputV2.ts +245 -104
- package/core/decrypt/verifyDecryptResult.ts +65 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
- package/core/encrypt/zkPackProveVerify.ts +10 -19
- package/core/fetchKeys.ts +0 -2
- package/core/index.ts +9 -1
- package/core/keyStore.ts +5 -2
- package/core/permits.ts +5 -0
- package/core/{client.test.ts → test/client.test.ts} +7 -7
- package/core/{config.test.ts → test/config.test.ts} +1 -1
- package/core/test/decrypt.test.ts +252 -0
- package/core/test/decryptBuilders.test.ts +390 -0
- package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
- package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
- package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
- package/core/{permits.test.ts → test/permits.test.ts} +42 -1
- package/core/test/pollCallbacks.test.ts +563 -0
- package/core/types.ts +13 -0
- package/dist/chains.d.cts +2 -2
- package/dist/chains.d.ts +2 -2
- package/dist/chunk-4FP4V35O.js +13 -0
- package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
- package/dist/{chunk-MXND5SVN.js → chunk-S7OKGLFD.js} +485 -207
- package/dist/{clientTypes-kkrRdawm.d.ts → clientTypes-BSbwairE.d.cts} +23 -6
- package/dist/{clientTypes-ACVWbrXL.d.cts → clientTypes-DDmcgZ0a.d.ts} +23 -6
- package/dist/core.cjs +561 -244
- package/dist/core.d.cts +24 -6
- package/dist/core.d.ts +24 -6
- package/dist/core.js +3 -2
- package/dist/node.cjs +566 -246
- package/dist/node.d.cts +3 -3
- package/dist/node.d.ts +3 -3
- package/dist/node.js +14 -7
- package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.cts} +34 -4
- package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.ts} +34 -4
- package/dist/permits.cjs +66 -29
- package/dist/permits.d.cts +18 -13
- package/dist/permits.d.ts +18 -13
- package/dist/permits.js +2 -1
- package/dist/web.cjs +604 -256
- package/dist/web.d.cts +8 -4
- package/dist/web.d.ts +8 -4
- package/dist/web.js +49 -14
- package/dist/zkProve.worker.cjs +72 -64
- package/dist/zkProve.worker.js +71 -64
- package/node/index.ts +13 -4
- package/node/test/client.test.ts +25 -0
- package/node/test/config.test.ts +16 -0
- package/node/test/inherited.test.ts +244 -0
- package/node/test/tfheinit.test.ts +56 -0
- package/package.json +24 -22
- package/permits/permit.ts +31 -5
- package/permits/sealing.ts +1 -1
- package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
- package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
- package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
- package/permits/{store.test.ts → test/store.test.ts} +2 -2
- package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
- package/permits/types.ts +1 -1
- package/permits/validation.ts +42 -2
- package/web/const.ts +2 -0
- package/web/index.ts +40 -11
- package/web/storage.ts +18 -3
- package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
- package/web/test/config.web.test.ts +16 -0
- package/web/test/inherited.web.test.ts +245 -0
- package/web/test/tfheinit.web.test.ts +62 -0
- package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
- package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
- package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
- package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
- package/web/zkProve.worker.ts +94 -84
- package/node/client.test.ts +0 -147
- package/node/config.test.ts +0 -68
- package/node/encryptInputs.test.ts +0 -155
- package/web/config.web.test.ts +0 -69
- package/web/encryptInputs.web.test.ts +0 -172
- package/web/worker.builder.web.test.ts +0 -148
- /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
- /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
package/permits/validation.ts
CHANGED
|
@@ -273,9 +273,9 @@ export const ValidationUtils = {
|
|
|
273
273
|
},
|
|
274
274
|
|
|
275
275
|
/**
|
|
276
|
-
*
|
|
276
|
+
* Checks that a permit is signed and not expired.
|
|
277
277
|
*/
|
|
278
|
-
|
|
278
|
+
isSignedAndNotExpired: (permit: Permit): ValidationResult => {
|
|
279
279
|
if (ValidationUtils.isExpired(permit)) {
|
|
280
280
|
return { valid: false, error: 'expired' };
|
|
281
281
|
}
|
|
@@ -284,4 +284,44 @@ export const ValidationUtils = {
|
|
|
284
284
|
}
|
|
285
285
|
return { valid: true, error: null };
|
|
286
286
|
},
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Asserts that a permit is signed and not expired.
|
|
290
|
+
*
|
|
291
|
+
* Throws `Error` with message:
|
|
292
|
+
* - `Permit is expired`
|
|
293
|
+
* - `Permit is not signed`
|
|
294
|
+
*/
|
|
295
|
+
assertSignedAndNotExpired: (permit: Permit): void => {
|
|
296
|
+
const result = ValidationUtils.isSignedAndNotExpired(permit);
|
|
297
|
+
if (result.valid) return;
|
|
298
|
+
|
|
299
|
+
if (result.error === 'expired') {
|
|
300
|
+
throw new Error('Permit is expired');
|
|
301
|
+
}
|
|
302
|
+
if (result.error === 'not-signed') {
|
|
303
|
+
throw new Error('Permit is not signed');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Should be unreachable, but keeps this future-proof.
|
|
307
|
+
throw new Error('Permit is invalid');
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
isValid: (permit: Permit): ValidationResult => {
|
|
311
|
+
const schema =
|
|
312
|
+
permit.type === 'self'
|
|
313
|
+
? SelfPermitValidator
|
|
314
|
+
: permit.type === 'sharing'
|
|
315
|
+
? SharingPermitValidator
|
|
316
|
+
: permit.type === 'recipient'
|
|
317
|
+
? ImportPermitValidator
|
|
318
|
+
: null;
|
|
319
|
+
|
|
320
|
+
if (schema == null) return { valid: false, error: 'invalid-schema' };
|
|
321
|
+
|
|
322
|
+
const schemaResult = schema.safeParse(permit);
|
|
323
|
+
if (!schemaResult.success) return { valid: false, error: 'invalid-schema' };
|
|
324
|
+
|
|
325
|
+
return ValidationUtils.isSignedAndNotExpired(permit);
|
|
326
|
+
},
|
|
287
327
|
};
|
package/web/const.ts
ADDED
package/web/index.ts
CHANGED
|
@@ -10,31 +10,45 @@ import {
|
|
|
10
10
|
type FheKeyDeserializer,
|
|
11
11
|
type EncryptableItem,
|
|
12
12
|
fheTypeToString,
|
|
13
|
+
TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT,
|
|
13
14
|
} from '@/core';
|
|
14
15
|
|
|
15
16
|
// Import web-specific storage (internal use only)
|
|
16
|
-
import { createWebStorage } from './storage.js';
|
|
17
|
+
import { createSsrStorage, createWebStorage } from './storage.js';
|
|
17
18
|
|
|
18
19
|
// Import worker manager
|
|
19
20
|
import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
|
|
20
21
|
|
|
21
|
-
//
|
|
22
|
-
|
|
22
|
+
// Type-only import for tfhe — the runtime is loaded lazily via `await import('tfhe')`
|
|
23
|
+
// inside `initTfhe()` so that simply importing `@cofhe/sdk/web` (e.g. transitively
|
|
24
|
+
// through `@cofhe/react`) does not pull tfhe — and its worker helpers that
|
|
25
|
+
// reference `self` at module top — into the import graph during Next.js SSR.
|
|
26
|
+
import type { TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
|
|
27
|
+
import { hasDOM } from './const';
|
|
23
28
|
|
|
24
29
|
/**
|
|
25
30
|
* Internal function to initialize TFHE for web
|
|
26
31
|
* Called automatically on first encryption - users don't need to call this manually
|
|
27
32
|
* @returns true if TFHE was initialized, false if already initialized
|
|
28
33
|
*/
|
|
34
|
+
let tfheModule: typeof import('tfhe') | null = null;
|
|
29
35
|
let tfheInitialized = false;
|
|
30
36
|
async function initTfhe(): Promise<boolean> {
|
|
31
37
|
if (tfheInitialized) return false;
|
|
32
|
-
await
|
|
33
|
-
await
|
|
38
|
+
tfheModule = await import('tfhe');
|
|
39
|
+
await tfheModule.default();
|
|
40
|
+
await tfheModule.init_panic_hook();
|
|
34
41
|
tfheInitialized = true;
|
|
35
42
|
return true;
|
|
36
43
|
}
|
|
37
44
|
|
|
45
|
+
function requireTfhe(): typeof import('tfhe') {
|
|
46
|
+
if (!tfheModule) {
|
|
47
|
+
throw new Error('TFHE not initialized — call initTfhe() (or any client method that triggers it) first');
|
|
48
|
+
}
|
|
49
|
+
return tfheModule;
|
|
50
|
+
}
|
|
51
|
+
|
|
38
52
|
/**
|
|
39
53
|
* Utility to convert the hex string key to a Uint8Array for use with tfhe
|
|
40
54
|
*/
|
|
@@ -45,12 +59,23 @@ const fromHexString = (hexString: string): Uint8Array => {
|
|
|
45
59
|
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
46
60
|
};
|
|
47
61
|
|
|
62
|
+
const _deserializeTfhePublicKey = (buff: string): TfheCompactPublicKey => {
|
|
63
|
+
return requireTfhe().TfheCompactPublicKey.safe_deserialize(
|
|
64
|
+
fromHexString(buff),
|
|
65
|
+
TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const _deserializeCompactPkeCrs = (buff: string): CompactPkeCrs => {
|
|
70
|
+
return requireTfhe().CompactPkeCrs.safe_deserialize(fromHexString(buff), TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT);
|
|
71
|
+
};
|
|
72
|
+
|
|
48
73
|
/**
|
|
49
74
|
* Serializer for TFHE public keys
|
|
50
75
|
* Validates that the buffer can be deserialized into a TfheCompactPublicKey
|
|
51
76
|
*/
|
|
52
77
|
const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
|
|
53
|
-
|
|
78
|
+
_deserializeTfhePublicKey(buff);
|
|
54
79
|
};
|
|
55
80
|
|
|
56
81
|
/**
|
|
@@ -58,7 +83,7 @@ const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
|
|
|
58
83
|
* Validates that the buffer can be deserialized into ZkCompactPkePublicParams
|
|
59
84
|
*/
|
|
60
85
|
const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
|
|
61
|
-
|
|
86
|
+
_deserializeCompactPkeCrs(buff);
|
|
62
87
|
};
|
|
63
88
|
|
|
64
89
|
/**
|
|
@@ -66,9 +91,9 @@ const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
|
|
|
66
91
|
* This is used internally by the SDK to create encrypted inputs
|
|
67
92
|
*/
|
|
68
93
|
const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: string) => {
|
|
69
|
-
const fhePublicKey =
|
|
70
|
-
const zkBuilder = ProvenCompactCiphertextList.builder(fhePublicKey);
|
|
71
|
-
const zkCrs =
|
|
94
|
+
const fhePublicKey = _deserializeTfhePublicKey(fhe);
|
|
95
|
+
const zkBuilder = requireTfhe().ProvenCompactCiphertextList.builder(fhePublicKey);
|
|
96
|
+
const zkCrs = _deserializeCompactPkeCrs(crs);
|
|
72
97
|
|
|
73
98
|
return { zkBuilder, zkCrs };
|
|
74
99
|
};
|
|
@@ -103,7 +128,8 @@ export function createCofheConfig(config: CofheInputConfig): CofheConfig {
|
|
|
103
128
|
return createCofheConfigBase({
|
|
104
129
|
environment: 'web',
|
|
105
130
|
...config,
|
|
106
|
-
fheKeyStorage:
|
|
131
|
+
fheKeyStorage:
|
|
132
|
+
config.fheKeyStorage === null ? null : config.fheKeyStorage ?? (hasDOM ? createWebStorage() : createSsrStorage()),
|
|
107
133
|
});
|
|
108
134
|
}
|
|
109
135
|
|
|
@@ -159,3 +185,6 @@ export function createCofheClientWithCustomWorker(
|
|
|
159
185
|
zkProveWorkerFn: customZkProveWorkerFn,
|
|
160
186
|
});
|
|
161
187
|
}
|
|
188
|
+
|
|
189
|
+
export { createSsrStorage };
|
|
190
|
+
export { hasDOM } from './const';
|
package/web/storage.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import type { IStorage } from '@/core';
|
|
2
2
|
import { constructClient } from 'iframe-shared-storage';
|
|
3
|
+
import { hasDOM } from './const';
|
|
3
4
|
/**
|
|
4
|
-
* Creates a web storage implementation using IndexedDB
|
|
5
|
+
* Creates a web storage implementation using IndexedDB.
|
|
6
|
+
* Must only be called in a browser environment (requires `document` for iframe injection).
|
|
5
7
|
* @returns IStorage implementation for browser environments
|
|
6
8
|
*/
|
|
7
|
-
export const createWebStorage = (): IStorage => {
|
|
9
|
+
export const createWebStorage = (opts = { enableLog: false }): IStorage => {
|
|
10
|
+
if (!hasDOM) throw new Error('createWebStorage can only be used in a browser environment');
|
|
11
|
+
|
|
8
12
|
const client = constructClient({
|
|
9
13
|
iframe: {
|
|
10
14
|
src: 'https://iframe-shared-storage.vercel.app/hub.html',
|
|
11
15
|
messagingOptions: {
|
|
12
|
-
enableLog: 'both',
|
|
16
|
+
enableLog: opts.enableLog ? 'both' : undefined,
|
|
13
17
|
},
|
|
14
18
|
|
|
15
19
|
iframeReadyTimeoutMs: 30_000, // if the iframe is not initied during this interval AND a reuqest is made, such request will throw an error
|
|
@@ -32,3 +36,14 @@ export const createWebStorage = (): IStorage => {
|
|
|
32
36
|
},
|
|
33
37
|
};
|
|
34
38
|
};
|
|
39
|
+
|
|
40
|
+
export function createSsrStorage(): IStorage {
|
|
41
|
+
// TODO: consider doing something like wagmi's cookies storage for SSR - this in-memory storage will not persist across requests, but it also won't throw errors if accessed in SSR (e.g. during getServerSideProps in Next.js)
|
|
42
|
+
// https://wagmi.sh/react/guides/ssr#_1-set-up-cookie-storage
|
|
43
|
+
console.warn('using no-op server-side SSR storage');
|
|
44
|
+
return {
|
|
45
|
+
getItem: async () => null,
|
|
46
|
+
setItem: async () => {},
|
|
47
|
+
removeItem: async () => {},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -6,7 +6,7 @@ import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
|
6
6
|
import type { PublicClient, WalletClient } from 'viem';
|
|
7
7
|
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
8
8
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
9
|
-
import { createCofheClient, createCofheConfig } from '
|
|
9
|
+
import { createCofheClient, createCofheConfig } from '../index.js';
|
|
10
10
|
|
|
11
11
|
// Real test setup - runs in browser
|
|
12
12
|
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
@@ -141,7 +141,19 @@ describe('@cofhe/web - Client', () => {
|
|
|
141
141
|
expect(builder).toBeDefined();
|
|
142
142
|
expect(typeof builder.setChainId).toBe('function');
|
|
143
143
|
expect(typeof builder.setAccount).toBe('function');
|
|
144
|
+
expect(typeof builder.onPoll).toBe('function');
|
|
144
145
|
expect(typeof builder.execute).toBe('function');
|
|
145
146
|
}, 30000);
|
|
147
|
+
|
|
148
|
+
it('should create decryptForTx builder after connection', async () => {
|
|
149
|
+
await cofheClient.connect(publicClient, walletClient);
|
|
150
|
+
|
|
151
|
+
const builder = cofheClient.decryptForTx('0x123');
|
|
152
|
+
|
|
153
|
+
expect(builder).toBeDefined();
|
|
154
|
+
expect(typeof builder.setChainId).toBe('function');
|
|
155
|
+
expect(typeof builder.setAccount).toBe('function');
|
|
156
|
+
expect(typeof builder.onPoll).toBe('function');
|
|
157
|
+
}, 30000);
|
|
146
158
|
});
|
|
147
159
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { arbSepolia } from '@/chains';
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
import { createCofheConfig } from '../index.js';
|
|
5
|
+
|
|
6
|
+
describe('@cofhe/web - Config', () => {
|
|
7
|
+
it('should automatically inject IndexedDB storage as default', () => {
|
|
8
|
+
const config = createCofheConfig({
|
|
9
|
+
supportedChains: [arbSepolia],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(config.fheKeyStorage).toBeDefined();
|
|
13
|
+
expect(config.fheKeyStorage).not.toBeNull();
|
|
14
|
+
expect(config.supportedChains).toEqual([arbSepolia]);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Encryptable, FheTypes, type CofheClient } from '@/core';
|
|
2
|
+
import { arbSepolia as cofheArbSepolia, getChainById } from '@/chains';
|
|
3
|
+
import {
|
|
4
|
+
TEST_PRIVATE_KEY,
|
|
5
|
+
PRIMARY_TEST_CHAIN,
|
|
6
|
+
primaryTestChainRegistry,
|
|
7
|
+
isPrimaryTestChainReady,
|
|
8
|
+
} from '@cofhe/test-setup';
|
|
9
|
+
|
|
10
|
+
import { createCofheClient, createCofheConfig } from '../index.js';
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
|
13
|
+
import type { Chain, PublicClient, WalletClient } from 'viem';
|
|
14
|
+
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
15
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
16
|
+
import { arbitrumSepolia, baseSepolia, sepolia } from 'viem/chains';
|
|
17
|
+
|
|
18
|
+
const DEFAULT_TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
19
|
+
const BOB_PRIVATE_KEY = (TEST_PRIVATE_KEY || DEFAULT_TEST_PRIVATE_KEY) as `0x${string}`;
|
|
20
|
+
const ALICE_PRIVATE_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d';
|
|
21
|
+
|
|
22
|
+
const bobAccount = privateKeyToAccount(BOB_PRIVATE_KEY);
|
|
23
|
+
const aliceAccount = privateKeyToAccount(ALICE_PRIVATE_KEY);
|
|
24
|
+
|
|
25
|
+
const VIEM_CHAINS: Record<number, Chain> = {
|
|
26
|
+
421614: arbitrumSepolia,
|
|
27
|
+
84532: baseSepolia,
|
|
28
|
+
11155111: sepolia,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe('@cofhe/web - Inherited Client Tests', () => {
|
|
32
|
+
let cofheClient: CofheClient;
|
|
33
|
+
let publicClient: PublicClient;
|
|
34
|
+
let bobWalletClient: WalletClient;
|
|
35
|
+
let aliceWalletClient: WalletClient;
|
|
36
|
+
|
|
37
|
+
beforeAll(() => {
|
|
38
|
+
publicClient = createPublicClient({
|
|
39
|
+
chain: arbitrumSepolia,
|
|
40
|
+
transport: http(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
bobWalletClient = createWalletClient({
|
|
44
|
+
chain: arbitrumSepolia,
|
|
45
|
+
transport: http(),
|
|
46
|
+
account: bobAccount,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
aliceWalletClient = createWalletClient({
|
|
50
|
+
chain: arbitrumSepolia,
|
|
51
|
+
transport: http(),
|
|
52
|
+
account: aliceAccount,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
const config = createCofheConfig({
|
|
58
|
+
supportedChains: [cofheArbSepolia],
|
|
59
|
+
});
|
|
60
|
+
cofheClient = createCofheClient(config);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Client Creation', () => {
|
|
64
|
+
it('should create a client with expected surface', () => {
|
|
65
|
+
expect(cofheClient).toBeDefined();
|
|
66
|
+
expect(cofheClient.config).toBeDefined();
|
|
67
|
+
expect(cofheClient.connected).toBe(false);
|
|
68
|
+
expect(typeof cofheClient.connect).toBe('function');
|
|
69
|
+
expect(typeof cofheClient.disconnect).toBe('function');
|
|
70
|
+
expect(typeof cofheClient.encryptInputs).toBe('function');
|
|
71
|
+
expect(typeof cofheClient.decryptForView).toBe('function');
|
|
72
|
+
expect(typeof cofheClient.decryptForTx).toBe('function');
|
|
73
|
+
expect(typeof cofheClient.getSnapshot).toBe('function');
|
|
74
|
+
expect(typeof cofheClient.subscribe).toBe('function');
|
|
75
|
+
expect(cofheClient.permits).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('Connection', () => {
|
|
80
|
+
it('should connect to a real chain', async () => {
|
|
81
|
+
await cofheClient.connect(publicClient, bobWalletClient);
|
|
82
|
+
|
|
83
|
+
expect(cofheClient.connected).toBe(true);
|
|
84
|
+
|
|
85
|
+
const snapshot = cofheClient.getSnapshot();
|
|
86
|
+
expect(snapshot.connected).toBe(true);
|
|
87
|
+
expect(snapshot.chainId).toBe(cofheArbSepolia.id);
|
|
88
|
+
expect(snapshot.account).toBe(bobAccount.address);
|
|
89
|
+
}, 30000);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Encrypt Input', () => {
|
|
93
|
+
it('should encrypt a uint128 value', async () => {
|
|
94
|
+
await cofheClient.connect(publicClient, bobWalletClient);
|
|
95
|
+
|
|
96
|
+
const encrypted = await cofheClient.encryptInputs([Encryptable.uint128(100n)]).execute();
|
|
97
|
+
|
|
98
|
+
expect(encrypted).toBeDefined();
|
|
99
|
+
expect(encrypted.length).toBe(1);
|
|
100
|
+
expect(encrypted[0].utype).toBe(FheTypes.Uint128);
|
|
101
|
+
expect(encrypted[0].ctHash).toBeDefined();
|
|
102
|
+
expect(typeof encrypted[0].ctHash).toBe('bigint');
|
|
103
|
+
expect(encrypted[0].signature).toBeDefined();
|
|
104
|
+
expect(typeof encrypted[0].signature).toBe('string');
|
|
105
|
+
expect(encrypted[0].securityZone).toBe(0);
|
|
106
|
+
}, 60000);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('Self Permit', () => {
|
|
110
|
+
it('should create a self permit', async () => {
|
|
111
|
+
await cofheClient.connect(publicClient, bobWalletClient);
|
|
112
|
+
|
|
113
|
+
const permit = await cofheClient.permits.createSelf({
|
|
114
|
+
issuer: bobAccount.address,
|
|
115
|
+
name: 'Test Self Permit',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(permit).toBeDefined();
|
|
119
|
+
expect(permit.type).toBe('self');
|
|
120
|
+
expect(permit.name).toBe('Test Self Permit');
|
|
121
|
+
expect(permit.issuer).toBe(bobAccount.address);
|
|
122
|
+
expect(permit.issuerSignature).not.toBe('0x');
|
|
123
|
+
expect(permit.sealingPair).toBeDefined();
|
|
124
|
+
expect(permit.sealingPair.publicKey).toBeDefined();
|
|
125
|
+
|
|
126
|
+
const activePermit = cofheClient.permits.getActivePermit();
|
|
127
|
+
expect(activePermit).toBeDefined();
|
|
128
|
+
expect(activePermit!.hash).toBe(permit.hash);
|
|
129
|
+
}, 30000);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Sharing Permit', () => {
|
|
133
|
+
it('should create a sharing permit, export it, and import it as another user', async () => {
|
|
134
|
+
await cofheClient.connect(publicClient, bobWalletClient);
|
|
135
|
+
|
|
136
|
+
const sharingPermit = await cofheClient.permits.createSharing({
|
|
137
|
+
issuer: bobAccount.address,
|
|
138
|
+
recipient: aliceAccount.address,
|
|
139
|
+
name: 'Test Sharing Permit',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(sharingPermit).toBeDefined();
|
|
143
|
+
expect(sharingPermit.type).toBe('sharing');
|
|
144
|
+
expect(sharingPermit.issuer).toBe(bobAccount.address);
|
|
145
|
+
expect(sharingPermit.recipient).toBe(aliceAccount.address);
|
|
146
|
+
expect(sharingPermit.issuerSignature).not.toBe('0x');
|
|
147
|
+
|
|
148
|
+
const exported = cofheClient.permits.export(sharingPermit);
|
|
149
|
+
expect(exported).toBeDefined();
|
|
150
|
+
const parsed = JSON.parse(exported);
|
|
151
|
+
expect(parsed.type).toBe('sharing');
|
|
152
|
+
expect(parsed.issuer).toBe(bobAccount.address);
|
|
153
|
+
expect(parsed.recipient).toBe(aliceAccount.address);
|
|
154
|
+
expect(parsed.issuerSignature).toBeDefined();
|
|
155
|
+
expect(parsed).not.toHaveProperty('sealingPair');
|
|
156
|
+
|
|
157
|
+
const aliceConfig = createCofheConfig({
|
|
158
|
+
supportedChains: [cofheArbSepolia],
|
|
159
|
+
});
|
|
160
|
+
const aliceClient = createCofheClient(aliceConfig);
|
|
161
|
+
await aliceClient.connect(publicClient, aliceWalletClient);
|
|
162
|
+
|
|
163
|
+
const importedPermit = await aliceClient.permits.importShared(exported);
|
|
164
|
+
|
|
165
|
+
expect(importedPermit).toBeDefined();
|
|
166
|
+
expect(importedPermit.type).toBe('recipient');
|
|
167
|
+
expect(importedPermit.issuer).toBe(bobAccount.address);
|
|
168
|
+
expect(importedPermit.recipient).toBe(aliceAccount.address);
|
|
169
|
+
expect(importedPermit.recipientSignature).not.toBe('0x');
|
|
170
|
+
expect(importedPermit.sealingPair).toBeDefined();
|
|
171
|
+
}, 30000);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('Decrypt (read-only, pre-stored values)', () => {
|
|
175
|
+
let decryptClient: CofheClient;
|
|
176
|
+
let decryptPublicClient: PublicClient;
|
|
177
|
+
let decryptWalletClient: WalletClient;
|
|
178
|
+
|
|
179
|
+
let privateCtHash: `0x${string}`;
|
|
180
|
+
let privateValue: bigint;
|
|
181
|
+
let publicCtHash: `0x${string}`;
|
|
182
|
+
let publicValue: bigint;
|
|
183
|
+
|
|
184
|
+
beforeAll(() => {
|
|
185
|
+
if (!isPrimaryTestChainReady(primaryTestChainRegistry)) {
|
|
186
|
+
throw new Error('Primary test chain registry not initialized. Run `pnpm test:setup` first.');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const reg = primaryTestChainRegistry;
|
|
190
|
+
const viemChain = VIEM_CHAINS[reg.chainId];
|
|
191
|
+
if (!viemChain) throw new Error(`No viem chain mapping for chain ${reg.chainId}`);
|
|
192
|
+
|
|
193
|
+
const cofheChain = getChainById(reg.chainId);
|
|
194
|
+
if (!cofheChain) throw new Error(`No cofhe chain config for chain ${reg.chainId}`);
|
|
195
|
+
|
|
196
|
+
privateCtHash = reg.privateValue.ctHash as `0x${string}`;
|
|
197
|
+
privateValue = BigInt(reg.privateValue.value);
|
|
198
|
+
publicCtHash = reg.publicValue.ctHash as `0x${string}`;
|
|
199
|
+
publicValue = BigInt(reg.publicValue.value);
|
|
200
|
+
|
|
201
|
+
decryptPublicClient = createPublicClient({ chain: viemChain, transport: http() });
|
|
202
|
+
decryptWalletClient = createWalletClient({ chain: viemChain, transport: http(), account: bobAccount });
|
|
203
|
+
|
|
204
|
+
const config = createCofheConfig({ supportedChains: [cofheChain] });
|
|
205
|
+
decryptClient = createCofheClient(config);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('decryptForView — private value with permit', async () => {
|
|
209
|
+
await decryptClient.connect(decryptPublicClient, decryptWalletClient);
|
|
210
|
+
|
|
211
|
+
await decryptClient.permits.createSelf({
|
|
212
|
+
issuer: bobAccount.address,
|
|
213
|
+
name: 'Decrypt View Permit',
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const result = await decryptClient.decryptForView(privateCtHash, FheTypes.Uint32).execute();
|
|
217
|
+
expect(result).toBe(privateValue);
|
|
218
|
+
}, 180000);
|
|
219
|
+
|
|
220
|
+
it('decryptForTx — public value without permit', async () => {
|
|
221
|
+
await decryptClient.connect(decryptPublicClient, decryptWalletClient);
|
|
222
|
+
|
|
223
|
+
const result = await decryptClient.decryptForTx(publicCtHash).withoutPermit().execute();
|
|
224
|
+
|
|
225
|
+
expect(BigInt(result.ctHash)).toBe(BigInt(publicCtHash));
|
|
226
|
+
expect(result.decryptedValue).toBe(publicValue);
|
|
227
|
+
expect(result.signature).toMatch(/^0x[0-9a-fA-F]+$/);
|
|
228
|
+
}, 180000);
|
|
229
|
+
|
|
230
|
+
it('decryptForTx — private value with permit', async () => {
|
|
231
|
+
await decryptClient.connect(decryptPublicClient, decryptWalletClient);
|
|
232
|
+
|
|
233
|
+
const permit = await decryptClient.permits.createSelf({
|
|
234
|
+
issuer: bobAccount.address,
|
|
235
|
+
name: 'Decrypt Tx Permit',
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const result = await decryptClient.decryptForTx(privateCtHash).withPermit(permit).execute();
|
|
239
|
+
|
|
240
|
+
expect(BigInt(result.ctHash)).toBe(BigInt(privateCtHash));
|
|
241
|
+
expect(result.decryptedValue).toBe(privateValue);
|
|
242
|
+
expect(result.signature).toBeDefined();
|
|
243
|
+
}, 180000);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { arbSepolia as cofheArbSepolia } from '@/chains';
|
|
2
|
+
import { Encryptable, FheTypes, type CofheClient, CofheErrorCode, CofheError } from '@/core';
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
|
5
|
+
import type { PublicClient, WalletClient } from 'viem';
|
|
6
|
+
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
7
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
|
+
import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
9
|
+
import { createCofheClient, createCofheConfig } from '../index.js';
|
|
10
|
+
|
|
11
|
+
// Real test setup - runs in browser with real tfhe
|
|
12
|
+
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
13
|
+
|
|
14
|
+
describe('@cofhe/web - TFHE Initialization Browser Tests', () => {
|
|
15
|
+
let cofheClient: CofheClient;
|
|
16
|
+
let publicClient: PublicClient;
|
|
17
|
+
let walletClient: WalletClient;
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
// Create real viem clients
|
|
21
|
+
publicClient = createPublicClient({
|
|
22
|
+
chain: viemArbitrumSepolia,
|
|
23
|
+
transport: http(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
27
|
+
walletClient = createWalletClient({
|
|
28
|
+
chain: viemArbitrumSepolia,
|
|
29
|
+
transport: http(),
|
|
30
|
+
account,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
const config = createCofheConfig({
|
|
36
|
+
supportedChains: [cofheArbSepolia],
|
|
37
|
+
});
|
|
38
|
+
cofheClient = createCofheClient(config);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Browser TFHE Initialization', () => {
|
|
42
|
+
it('should initialize tfhe on first encryption', async () => {
|
|
43
|
+
await cofheClient.connect(publicClient, walletClient);
|
|
44
|
+
|
|
45
|
+
// This will trigger real TFHE initialization in browser
|
|
46
|
+
const result = await cofheClient.encryptInputs([Encryptable.uint128(100n)]).execute();
|
|
47
|
+
|
|
48
|
+
// If we get here, TFHE was initialized successfully
|
|
49
|
+
expect(result).toBeDefined();
|
|
50
|
+
}, 60000); // Longer timeout for real operations
|
|
51
|
+
|
|
52
|
+
it('should handle multiple encryptions without re-initializing', async () => {
|
|
53
|
+
await cofheClient.connect(publicClient, walletClient);
|
|
54
|
+
|
|
55
|
+
// First encryption
|
|
56
|
+
expect(cofheClient.encryptInputs([Encryptable.uint128(100n)]).execute()).resolves.not.toThrow();
|
|
57
|
+
|
|
58
|
+
// Second encryption should reuse initialization
|
|
59
|
+
expect(cofheClient.encryptInputs([Encryptable.uint64(50n)]).execute()).resolves.not.toThrow();
|
|
60
|
+
}, 60000);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -6,7 +6,7 @@ import type { PublicClient, WalletClient } from 'viem';
|
|
|
6
6
|
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
7
7
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
8
|
import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
9
|
-
import { createCofheClient, createCofheConfig, createCofheClientWithCustomWorker } from '
|
|
9
|
+
import { createCofheClient, createCofheConfig, createCofheClientWithCustomWorker } from '../index.js';
|
|
10
10
|
|
|
11
11
|
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
12
12
|
|
|
@@ -6,7 +6,7 @@ import type { PublicClient, WalletClient } from 'viem';
|
|
|
6
6
|
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
7
7
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
8
|
import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
9
|
-
import { createCofheClient, createCofheConfig } from '
|
|
9
|
+
import { createCofheClient, createCofheConfig } from '../index.js';
|
|
10
10
|
|
|
11
11
|
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
12
12
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { getWorkerManager, terminateWorker, areWorkersAvailable } from '
|
|
2
|
+
import { getWorkerManager, terminateWorker, areWorkersAvailable } from '../workerManager.js';
|
|
3
3
|
|
|
4
4
|
describe('WorkerManager', () => {
|
|
5
5
|
afterEach(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { getWorkerManager, terminateWorker, areWorkersAvailable } from '
|
|
2
|
+
import { getWorkerManager, terminateWorker, areWorkersAvailable } from '../workerManager.js';
|
|
3
3
|
|
|
4
4
|
describe('WorkerManager (Browser)', () => {
|
|
5
5
|
afterEach(() => {
|