@cofhe/sdk 0.1.0 → 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 +62 -0
- package/adapters/ethers5.test.ts +174 -0
- package/adapters/ethers5.ts +36 -0
- package/adapters/ethers6.test.ts +169 -0
- package/adapters/ethers6.ts +36 -0
- package/adapters/hardhat-node.ts +167 -0
- package/adapters/hardhat.hh2.test.ts +159 -0
- package/adapters/hardhat.ts +36 -0
- package/adapters/index.test.ts +20 -0
- package/adapters/index.ts +5 -0
- package/adapters/smartWallet.ts +99 -0
- package/adapters/test-utils.ts +53 -0
- package/adapters/types.ts +6 -0
- package/adapters/wagmi.test.ts +156 -0
- package/adapters/wagmi.ts +17 -0
- package/chains/chains/arbSepolia.ts +14 -0
- package/chains/chains/baseSepolia.ts +14 -0
- package/chains/chains/hardhat.ts +15 -0
- package/chains/chains/localcofhe.ts +14 -0
- package/chains/chains/sepolia.ts +14 -0
- package/chains/chains.test.ts +50 -0
- package/chains/defineChain.ts +18 -0
- package/chains/index.ts +35 -0
- package/chains/types.ts +32 -0
- package/core/baseBuilder.ts +119 -0
- package/core/client.test.ts +315 -0
- package/core/client.ts +292 -0
- package/core/clientTypes.ts +108 -0
- package/core/config.test.ts +235 -0
- package/core/config.ts +220 -0
- package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
- package/core/decrypt/cofheMocksSealOutput.ts +57 -0
- package/core/decrypt/decryptHandleBuilder.ts +287 -0
- package/core/decrypt/decryptUtils.ts +28 -0
- package/core/decrypt/tnSealOutputV1.ts +59 -0
- package/core/decrypt/tnSealOutputV2.ts +298 -0
- package/core/encrypt/MockZkVerifierAbi.ts +106 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
- package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
- package/core/encrypt/encryptInputsBuilder.ts +560 -0
- package/core/encrypt/encryptUtils.ts +67 -0
- package/core/encrypt/zkPackProveVerify.ts +335 -0
- package/core/error.ts +168 -0
- package/core/fetchKeys.test.ts +195 -0
- package/core/fetchKeys.ts +144 -0
- package/core/index.ts +89 -0
- package/core/keyStore.test.ts +226 -0
- package/core/keyStore.ts +154 -0
- package/core/permits.test.ts +494 -0
- package/core/permits.ts +200 -0
- package/core/types.ts +398 -0
- package/core/utils.ts +130 -0
- package/dist/adapters.cjs +88 -0
- package/dist/adapters.d.cts +14576 -0
- package/dist/adapters.d.ts +14576 -0
- package/dist/adapters.js +83 -0
- package/dist/chains.cjs +114 -0
- package/dist/chains.d.cts +121 -0
- package/dist/chains.d.ts +121 -0
- package/dist/chains.js +1 -0
- package/dist/chunk-UGBVZNRT.js +818 -0
- package/dist/chunk-WEAZ25JO.js +105 -0
- package/dist/chunk-WGCRJCBR.js +2523 -0
- package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
- package/dist/clientTypes-Es7fyi65.d.ts +914 -0
- package/dist/core.cjs +3414 -0
- package/dist/core.d.cts +111 -0
- package/dist/core.d.ts +111 -0
- package/dist/core.js +3 -0
- package/dist/node.cjs +3286 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +91 -0
- package/dist/permit-fUSe6KKq.d.cts +349 -0
- package/dist/permit-fUSe6KKq.d.ts +349 -0
- package/dist/permits.cjs +871 -0
- package/dist/permits.d.cts +1045 -0
- package/dist/permits.d.ts +1045 -0
- package/dist/permits.js +1 -0
- package/dist/types-KImPrEIe.d.cts +48 -0
- package/dist/types-KImPrEIe.d.ts +48 -0
- package/dist/web.cjs +3478 -0
- package/dist/web.d.cts +38 -0
- package/dist/web.d.ts +38 -0
- package/dist/web.js +240 -0
- 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 +147 -0
- package/node/config.test.ts +68 -0
- package/node/encryptInputs.test.ts +155 -0
- package/node/index.ts +97 -0
- package/node/storage.ts +51 -0
- package/package.json +27 -15
- package/permits/index.ts +68 -0
- package/permits/localstorage.test.ts +117 -0
- package/permits/permit.test.ts +477 -0
- package/permits/permit.ts +405 -0
- package/permits/sealing.test.ts +84 -0
- package/permits/sealing.ts +131 -0
- package/permits/signature.ts +79 -0
- package/permits/store.test.ts +128 -0
- package/permits/store.ts +166 -0
- package/permits/test-utils.ts +20 -0
- package/permits/types.ts +191 -0
- package/permits/utils.ts +62 -0
- package/permits/validation.test.ts +288 -0
- package/permits/validation.ts +369 -0
- package/web/client.web.test.ts +147 -0
- package/web/config.web.test.ts +69 -0
- package/web/encryptInputs.web.test.ts +172 -0
- package/web/index.ts +161 -0
- package/web/storage.ts +34 -0
- 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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { CofhesdkErrorCode, CofhesdkError, type CofhesdkClient } from '@/core';
|
|
2
|
+
import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
|
|
5
|
+
import { arbitrumSepolia as viemArbitrumSepolia } from 'viem/chains';
|
|
6
|
+
import type { PublicClient, WalletClient } from 'viem';
|
|
7
|
+
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
8
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
9
|
+
import { createCofhesdkClient, createCofhesdkConfig } from './index.js';
|
|
10
|
+
|
|
11
|
+
// Real test setup - runs in browser
|
|
12
|
+
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
13
|
+
const TEST_ACCOUNT = privateKeyToAccount(TEST_PRIVATE_KEY).address;
|
|
14
|
+
|
|
15
|
+
describe('@cofhe/web - Client', () => {
|
|
16
|
+
let cofhesdkClient: CofhesdkClient;
|
|
17
|
+
let publicClient: PublicClient;
|
|
18
|
+
let walletClient: WalletClient;
|
|
19
|
+
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
// Create real viem clients
|
|
22
|
+
publicClient = createPublicClient({
|
|
23
|
+
chain: viemArbitrumSepolia,
|
|
24
|
+
transport: http(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
28
|
+
walletClient = createWalletClient({
|
|
29
|
+
chain: viemArbitrumSepolia,
|
|
30
|
+
transport: http(),
|
|
31
|
+
account,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
const config = createCofhesdkConfig({
|
|
37
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
38
|
+
});
|
|
39
|
+
cofhesdkClient = createCofhesdkClient(config);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('Browser Client Initialization', () => {
|
|
43
|
+
it('should create a client with real tfhe for browser', () => {
|
|
44
|
+
expect(cofhesdkClient).toBeDefined();
|
|
45
|
+
expect(cofhesdkClient.config).toBeDefined();
|
|
46
|
+
expect(cofhesdkClient.connected).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should automatically use IndexedDB storage as default', () => {
|
|
50
|
+
expect(cofhesdkClient.config.fheKeyStorage).toBeDefined();
|
|
51
|
+
expect(cofhesdkClient.config.fheKeyStorage).not.toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should have all expected methods', () => {
|
|
55
|
+
expect(typeof cofhesdkClient.connect).toBe('function');
|
|
56
|
+
expect(typeof cofhesdkClient.encryptInputs).toBe('function');
|
|
57
|
+
expect(typeof cofhesdkClient.decryptHandle).toBe('function');
|
|
58
|
+
expect(typeof cofhesdkClient.getSnapshot).toBe('function');
|
|
59
|
+
expect(typeof cofhesdkClient.subscribe).toBe('function');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Environment', () => {
|
|
64
|
+
it('should have the correct environment', () => {
|
|
65
|
+
expect(cofhesdkClient.config.environment).toBe('web');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('Connection', () => {
|
|
70
|
+
it('should connect to real chain', async () => {
|
|
71
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
72
|
+
|
|
73
|
+
expect(cofhesdkClient.connected).toBe(true);
|
|
74
|
+
|
|
75
|
+
const snapshot = cofhesdkClient.getSnapshot();
|
|
76
|
+
expect(snapshot.connected).toBe(true);
|
|
77
|
+
expect(snapshot.chainId).toBe(cofhesdkArbSepolia.id);
|
|
78
|
+
expect(snapshot.account).toBe(TEST_ACCOUNT);
|
|
79
|
+
}, 30000);
|
|
80
|
+
|
|
81
|
+
it('should handle network errors', async () => {
|
|
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
|
+
}
|
|
93
|
+
}, 30000);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('State Management', () => {
|
|
97
|
+
it('should track connection state changes', async () => {
|
|
98
|
+
const states: any[] = [];
|
|
99
|
+
const unsubscribe = cofhesdkClient.subscribe((snapshot) => {
|
|
100
|
+
states.push({ ...snapshot });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
104
|
+
|
|
105
|
+
unsubscribe();
|
|
106
|
+
|
|
107
|
+
expect(states.length).toBeGreaterThan(0);
|
|
108
|
+
|
|
109
|
+
// First state should be connecting
|
|
110
|
+
const firstState = states.find((s) => s.connecting);
|
|
111
|
+
expect(firstState).toBeDefined();
|
|
112
|
+
expect(firstState?.connecting).toBe(true);
|
|
113
|
+
expect(firstState?.connected).toBe(false);
|
|
114
|
+
|
|
115
|
+
// Last state should be connected
|
|
116
|
+
const lastState = states[states.length - 1];
|
|
117
|
+
expect(lastState.connected).toBe(true);
|
|
118
|
+
expect(lastState.connecting).toBe(false);
|
|
119
|
+
expect(lastState.chainId).toBe(cofhesdkArbSepolia.id);
|
|
120
|
+
}, 30000);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Builder Creation', () => {
|
|
124
|
+
it('should create encrypt builder after connection', async () => {
|
|
125
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
126
|
+
|
|
127
|
+
const builder = cofhesdkClient.encryptInputs([{ data: 100n, utype: 2, securityZone: 0 }]);
|
|
128
|
+
|
|
129
|
+
expect(builder).toBeDefined();
|
|
130
|
+
expect(typeof builder.setChainId).toBe('function');
|
|
131
|
+
expect(typeof builder.setAccount).toBe('function');
|
|
132
|
+
expect(typeof builder.setSecurityZone).toBe('function');
|
|
133
|
+
expect(typeof builder.encrypt).toBe('function');
|
|
134
|
+
}, 30000);
|
|
135
|
+
|
|
136
|
+
it('should create decrypt builder after connection', async () => {
|
|
137
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
138
|
+
|
|
139
|
+
const builder = cofhesdkClient.decryptHandle(123n, 2);
|
|
140
|
+
|
|
141
|
+
expect(builder).toBeDefined();
|
|
142
|
+
expect(typeof builder.setChainId).toBe('function');
|
|
143
|
+
expect(typeof builder.setAccount).toBe('function');
|
|
144
|
+
expect(typeof builder.decrypt).toBe('function');
|
|
145
|
+
}, 30000);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { arbSepolia } from '@/chains';
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
import { createCofhesdkConfig, createCofhesdkClient } from './index.js';
|
|
5
|
+
|
|
6
|
+
describe('@cofhe/web - Config', () => {
|
|
7
|
+
describe('createCofhesdkConfig', () => {
|
|
8
|
+
it('should automatically inject IndexedDB storage as default', () => {
|
|
9
|
+
const config = createCofhesdkConfig({
|
|
10
|
+
supportedChains: [arbSepolia],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(config.fheKeyStorage).toBeDefined();
|
|
14
|
+
expect(config.fheKeyStorage).not.toBeNull();
|
|
15
|
+
expect(config.supportedChains).toEqual([arbSepolia]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should allow overriding storage', async () => {
|
|
19
|
+
const customStorage = {
|
|
20
|
+
getItem: () => Promise.resolve(10),
|
|
21
|
+
setItem: () => Promise.resolve(),
|
|
22
|
+
removeItem: () => Promise.resolve(),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const config = createCofhesdkConfig({
|
|
26
|
+
supportedChains: [arbSepolia],
|
|
27
|
+
fheKeyStorage: customStorage,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(await config.fheKeyStorage!.getItem('test')).toBe(10);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should allow null storage', () => {
|
|
34
|
+
const config = createCofhesdkConfig({
|
|
35
|
+
supportedChains: [arbSepolia],
|
|
36
|
+
fheKeyStorage: null,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(config.fheKeyStorage).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should preserve all other config options', () => {
|
|
43
|
+
const config = createCofhesdkConfig({
|
|
44
|
+
supportedChains: [arbSepolia],
|
|
45
|
+
mocks: {
|
|
46
|
+
sealOutputDelay: 500,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(config.supportedChains).toEqual([arbSepolia]);
|
|
51
|
+
expect(config.mocks.sealOutputDelay).toBe(500);
|
|
52
|
+
expect(config.fheKeyStorage).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('createCofhesdkClient with config', () => {
|
|
57
|
+
it('should create client with validated config', () => {
|
|
58
|
+
const config = createCofhesdkConfig({
|
|
59
|
+
supportedChains: [arbSepolia],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const client = createCofhesdkClient(config);
|
|
63
|
+
|
|
64
|
+
expect(client).toBeDefined();
|
|
65
|
+
expect(client.config).toBe(config);
|
|
66
|
+
expect(client.config.fheKeyStorage).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { arbSepolia as cofhesdkArbSepolia } from '@/chains';
|
|
2
|
+
import { Encryptable, FheTypes, type CofhesdkClient, CofhesdkErrorCode, CofhesdkError } 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 { createCofhesdkClient, createCofhesdkConfig } 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 - Encrypt Inputs Browser Tests', () => {
|
|
15
|
+
let cofhesdkClient: CofhesdkClient;
|
|
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 = createCofhesdkConfig({
|
|
36
|
+
supportedChains: [cofhesdkArbSepolia],
|
|
37
|
+
});
|
|
38
|
+
cofhesdkClient = createCofhesdkClient(config);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Browser TFHE Initialization', () => {
|
|
42
|
+
it('should initialize tfhe on first encryption', async () => {
|
|
43
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
44
|
+
|
|
45
|
+
// This will trigger real TFHE initialization in browser
|
|
46
|
+
const result = await cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
|
|
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 cofhesdkClient.connect(publicClient, walletClient);
|
|
54
|
+
|
|
55
|
+
// First encryption
|
|
56
|
+
expect(cofhesdkClient.encryptInputs([Encryptable.uint128(100n)]).encrypt()).resolves.not.toThrow();
|
|
57
|
+
|
|
58
|
+
// Second encryption should reuse initialization
|
|
59
|
+
expect(cofhesdkClient.encryptInputs([Encryptable.uint64(50n)]).encrypt()).resolves.not.toThrow();
|
|
60
|
+
}, 60000);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Browser Encryption', () => {
|
|
64
|
+
it('should encrypt a bool with real TFHE in browser', async () => {
|
|
65
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
66
|
+
|
|
67
|
+
const result = await cofhesdkClient.encryptInputs([Encryptable.bool(true)]).encrypt();
|
|
68
|
+
|
|
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);
|
|
77
|
+
}, 60000);
|
|
78
|
+
|
|
79
|
+
it('should encrypt all supported types together', async () => {
|
|
80
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
81
|
+
|
|
82
|
+
const inputs = [
|
|
83
|
+
Encryptable.bool(false),
|
|
84
|
+
Encryptable.uint8(1n),
|
|
85
|
+
Encryptable.uint16(2n),
|
|
86
|
+
Encryptable.uint32(3n),
|
|
87
|
+
Encryptable.uint64(4n),
|
|
88
|
+
Encryptable.uint128(5n),
|
|
89
|
+
Encryptable.address('0x742d35Cc6634C0532925a3b844D16faC4c175E99'),
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const result = await cofhesdkClient.encryptInputs(inputs).encrypt();
|
|
93
|
+
expect(result).toBeDefined();
|
|
94
|
+
|
|
95
|
+
expect(result.length).toBe(7);
|
|
96
|
+
// Verify each type
|
|
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);
|
|
104
|
+
}, 90000); // Longer timeout for multiple encryptions
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('Browser Builder Pattern', () => {
|
|
108
|
+
it('should support chaining builder methods with real encryption', async () => {
|
|
109
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
110
|
+
|
|
111
|
+
const snapshot = cofhesdkClient.getSnapshot();
|
|
112
|
+
const encrypted = await cofhesdkClient
|
|
113
|
+
.encryptInputs([Encryptable.uint128(100n)])
|
|
114
|
+
.setChainId(snapshot.chainId!)
|
|
115
|
+
.setAccount(snapshot.account!)
|
|
116
|
+
.setSecurityZone(0)
|
|
117
|
+
.encrypt();
|
|
118
|
+
|
|
119
|
+
expect(encrypted.length).toBe(1);
|
|
120
|
+
expect(encrypted[0].utype).toBe(FheTypes.Uint128);
|
|
121
|
+
}, 60000);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('Browser Error Handling', () => {
|
|
125
|
+
it('should fail gracefully when not connected', async () => {
|
|
126
|
+
// Don't connect the client
|
|
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
|
+
}
|
|
133
|
+
}, 30000);
|
|
134
|
+
|
|
135
|
+
it('should handle invalid CoFHE URL', async () => {
|
|
136
|
+
const badConfig = createCofhesdkConfig({
|
|
137
|
+
supportedChains: [
|
|
138
|
+
{
|
|
139
|
+
...cofhesdkArbSepolia,
|
|
140
|
+
coFheUrl: 'http://invalid-cofhe-url.local',
|
|
141
|
+
verifierUrl: 'http://invalid-verifier-url.local',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const badClient = createCofhesdkClient(badConfig);
|
|
147
|
+
await badClient.connect(publicClient, walletClient);
|
|
148
|
+
|
|
149
|
+
const promise = badClient.encryptInputs([Encryptable.uint128(100n)]).encrypt();
|
|
150
|
+
expect(promise).rejects.toThrow();
|
|
151
|
+
}, 60000);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('Browser Performance', () => {
|
|
155
|
+
it('should handle consecutive encryptions efficiently', async () => {
|
|
156
|
+
await cofhesdkClient.connect(publicClient, walletClient);
|
|
157
|
+
|
|
158
|
+
const start = Date.now();
|
|
159
|
+
|
|
160
|
+
// Perform 5 encryptions
|
|
161
|
+
for (let i = 0; i < 5; i++) {
|
|
162
|
+
await cofhesdkClient.encryptInputs([Encryptable.uint128(BigInt(i))]).encrypt();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const duration = Date.now() - start;
|
|
166
|
+
|
|
167
|
+
// Should complete all 5 encryptions in reasonable time
|
|
168
|
+
// This is a sanity check, not a strict benchmark
|
|
169
|
+
expect(duration).toBeLessThan(120000); // 2 minutes max
|
|
170
|
+
}, 180000); // 3 minute timeout
|
|
171
|
+
});
|
|
172
|
+
});
|
package/web/index.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Web specific functionality only
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createCofhesdkClientBase,
|
|
5
|
+
createCofhesdkConfigBase,
|
|
6
|
+
type CofhesdkClient,
|
|
7
|
+
type CofhesdkConfig,
|
|
8
|
+
type CofhesdkInputConfig,
|
|
9
|
+
type ZkBuilderAndCrsGenerator,
|
|
10
|
+
type FheKeyDeserializer,
|
|
11
|
+
type EncryptableItem,
|
|
12
|
+
fheTypeToString,
|
|
13
|
+
} from '@/core';
|
|
14
|
+
|
|
15
|
+
// Import web-specific storage (internal use only)
|
|
16
|
+
import { createWebStorage } from './storage.js';
|
|
17
|
+
|
|
18
|
+
// Import worker manager
|
|
19
|
+
import { getWorkerManager, terminateWorker, areWorkersAvailable } from './workerManager.js';
|
|
20
|
+
|
|
21
|
+
// Import tfhe for web
|
|
22
|
+
import init, { init_panic_hook, TfheCompactPublicKey, ProvenCompactCiphertextList, CompactPkeCrs } from 'tfhe';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Internal function to initialize TFHE for web
|
|
26
|
+
* Called automatically on first encryption - users don't need to call this manually
|
|
27
|
+
* @returns true if TFHE was initialized, false if already initialized
|
|
28
|
+
*/
|
|
29
|
+
let tfheInitialized = false;
|
|
30
|
+
async function initTfhe(): Promise<boolean> {
|
|
31
|
+
if (tfheInitialized) return false;
|
|
32
|
+
await init();
|
|
33
|
+
await init_panic_hook();
|
|
34
|
+
tfheInitialized = true;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Utility to convert the hex string key to a Uint8Array for use with tfhe
|
|
40
|
+
*/
|
|
41
|
+
const fromHexString = (hexString: string): Uint8Array => {
|
|
42
|
+
const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
|
|
43
|
+
const arr = cleanString.replace(/^0x/, '').match(/.{1,2}/g);
|
|
44
|
+
if (!arr) return new Uint8Array();
|
|
45
|
+
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Serializer for TFHE public keys
|
|
50
|
+
* Validates that the buffer can be deserialized into a TfheCompactPublicKey
|
|
51
|
+
*/
|
|
52
|
+
const tfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string): void => {
|
|
53
|
+
TfheCompactPublicKey.deserialize(fromHexString(buff));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Serializer for Compact PKE CRS
|
|
58
|
+
* Validates that the buffer can be deserialized into ZkCompactPkePublicParams
|
|
59
|
+
*/
|
|
60
|
+
const compactPkeCrsDeserializer: FheKeyDeserializer = (buff: string): void => {
|
|
61
|
+
CompactPkeCrs.deserialize(fromHexString(buff));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates a ZK builder and CRS from FHE public key and CRS buffers
|
|
66
|
+
* This is used internally by the SDK to create encrypted inputs
|
|
67
|
+
*/
|
|
68
|
+
const zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator = (fhe: string, crs: string) => {
|
|
69
|
+
const fhePublicKey = TfheCompactPublicKey.deserialize(fromHexString(fhe));
|
|
70
|
+
const zkBuilder = ProvenCompactCiphertextList.builder(fhePublicKey);
|
|
71
|
+
const zkCrs = CompactPkeCrs.deserialize(fromHexString(crs));
|
|
72
|
+
|
|
73
|
+
return { zkBuilder, zkCrs };
|
|
74
|
+
};
|
|
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
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates a CoFHE SDK configuration for web with IndexedDB storage as default
|
|
99
|
+
* @param config - The CoFHE SDK input configuration (fheKeyStorage will default to IndexedDB if not provided)
|
|
100
|
+
* @returns The CoFHE SDK configuration with web defaults applied
|
|
101
|
+
*/
|
|
102
|
+
export function createCofhesdkConfig(config: CofhesdkInputConfig): CofhesdkConfig {
|
|
103
|
+
return createCofhesdkConfigBase({
|
|
104
|
+
environment: 'web',
|
|
105
|
+
...config,
|
|
106
|
+
fheKeyStorage: config.fheKeyStorage === null ? null : config.fheKeyStorage ?? createWebStorage(),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a CoFHE SDK client instance for web with TFHE automatically configured
|
|
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)
|
|
114
|
+
* @param config - The CoFHE SDK configuration (use createCofhesdkConfig to create with web defaults)
|
|
115
|
+
* @returns The CoFHE SDK client instance
|
|
116
|
+
*/
|
|
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 {
|
|
153
|
+
return createCofhesdkClientBase({
|
|
154
|
+
config,
|
|
155
|
+
zkBuilderAndCrsGenerator,
|
|
156
|
+
tfhePublicKeyDeserializer,
|
|
157
|
+
compactPkeCrsDeserializer,
|
|
158
|
+
initTfhe,
|
|
159
|
+
zkProveWorkerFn: customZkProveWorkerFn,
|
|
160
|
+
});
|
|
161
|
+
}
|
package/web/storage.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IStorage } from '@/core';
|
|
2
|
+
import { constructClient } from 'iframe-shared-storage';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a web storage implementation using IndexedDB
|
|
5
|
+
* @returns IStorage implementation for browser environments
|
|
6
|
+
*/
|
|
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;
|
|
22
|
+
return {
|
|
23
|
+
getItem: async (name: string) => {
|
|
24
|
+
// IndexedDBKeyval returns undefined if not found, but we want null (a json-deserialized value is expected)
|
|
25
|
+
return (await indexedDBKeyval.get(name)) ?? null;
|
|
26
|
+
},
|
|
27
|
+
setItem: async (name: string, value: any) => {
|
|
28
|
+
await indexedDBKeyval.set(name, value);
|
|
29
|
+
},
|
|
30
|
+
removeItem: async (name: string) => {
|
|
31
|
+
await indexedDBKeyval.del(name);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
};
|