@cofhe/sdk 0.7.1 → 1.0.0-alpha.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 +35 -0
- package/acps/acp.ts +35 -7
- package/acps/index.ts +28 -12
- package/acps/onchain-utils.ts +188 -49
- package/acps/registry.ts +231 -0
- package/acps/sealing.ts +9 -3
- package/acps/share-metadata/appKey.ts +8 -0
- package/acps/share-metadata/decode.ts +120 -0
- package/acps/share-metadata/describe.ts +198 -0
- package/acps/share-metadata/encode.ts +147 -0
- package/acps/share-metadata/erc7984.ts +26 -0
- package/acps/share-metadata/index.ts +9 -0
- package/acps/share-metadata/layout.ts +69 -0
- package/acps/share-metadata/presets.ts +116 -0
- package/acps/share-metadata/schema.ts +47 -0
- package/acps/share-metadata/test/codec.test.ts +140 -0
- package/acps/share-metadata/test/describe.test.ts +112 -0
- package/acps/share-metadata/test/fixtures.ts +157 -0
- package/acps/share-metadata/test/live.test.ts +56 -0
- package/acps/share-metadata/test/presets.test.ts +119 -0
- package/acps/share-metadata/test/verify.test.ts +144 -0
- package/acps/share-metadata/verify.ts +155 -0
- package/acps/store.ts +48 -18
- package/acps/test/acp.test.ts +102 -7
- package/acps/test/localstorage.test.ts +28 -1
- package/acps/test/onchainAccessStatus.test.ts +116 -0
- package/acps/test/registry.test.ts +144 -0
- package/acps/test/sealing.test.ts +23 -0
- package/acps/test/store.test.ts +71 -0
- package/acps/test/utils.test.ts +63 -0
- package/acps/test/validation.test.ts +0 -4
- package/acps/types.ts +59 -6
- package/acps/utils.ts +15 -2
- package/acps/validation.ts +3 -1
- package/chains/chains/arbitrum.ts +14 -0
- package/chains/chains/ethereum.ts +14 -0
- package/chains/index.ts +5 -1
- package/chains/test/chains.test.ts +24 -3
- package/core/acps.ts +164 -207
- package/core/client.ts +44 -13
- package/core/clientTypes.ts +50 -7
- package/core/config.ts +33 -1
- package/core/consts.ts +14 -2
- package/core/decrypt/apiError.ts +4 -1
- package/core/decrypt/verifyDecryptResult.ts +8 -4
- package/core/encrypt/cofheMocksZkVerifySign.ts +19 -2
- package/core/encrypt/encryptInputsBuilder.ts +6 -0
- package/core/encrypt/zkPackProveVerify.ts +7 -1
- package/core/error.ts +1 -0
- package/core/fetchKeys.ts +2 -2
- package/core/index.ts +9 -1
- package/core/keyStore.ts +23 -10
- package/core/taskManager.ts +48 -0
- package/core/test/acpDefaults.test.ts +108 -19
- package/core/test/acpStoreAccountCase.test.ts +80 -0
- package/core/test/acps.test.ts +128 -3
- package/core/test/apiError.test.ts +8 -0
- package/core/test/appKeyConfig.test.ts +94 -0
- package/core/test/client.test.ts +41 -0
- package/core/test/config.test.ts +24 -0
- package/core/test/decrypt.test.ts +11 -4
- package/core/test/decryptBuilders.test.ts +110 -2
- package/core/test/encryptInputsBuilder.test.ts +1 -1
- package/core/test/fetchKeys.test.ts +23 -2
- package/core/test/incomingShares.test.ts +84 -0
- package/core/test/keyStore.test.ts +18 -6
- package/core/test/mocksEncryptRange.test.ts +90 -0
- package/core/test/taskManager.test.ts +57 -0
- package/core/test/utils.test.ts +128 -0
- package/core/test/verifyDecryptResult.test.ts +51 -0
- package/core/types.ts +8 -0
- package/core/utils.ts +16 -9
- package/dist/acps.cjs +984 -64
- package/dist/acps.d.cts +589 -10
- package/dist/acps.d.ts +589 -10
- package/dist/acps.js +2 -2
- package/dist/chains.cjs +26 -0
- package/dist/chains.d.cts +45 -1
- package/dist/chains.d.ts +45 -1
- package/dist/chains.js +1 -1
- package/dist/chunk-6ZW76ZKB.js +228 -0
- package/dist/chunk-BQWHPIYU.js +1828 -0
- package/dist/chunk-KSOV5B4B.js +50 -0
- package/dist/{chunk-43USWPEH.js → chunk-OL3DZN7A.js} +159 -523
- package/dist/{chunk-N6IDQRRU.js → chunk-UKQAJK4N.js} +25 -1
- package/dist/chunk-XQ32JG3T.js +174 -0
- package/dist/{clientTypes-CYZjFznO.d.cts → clientTypes-C0A9ji5O.d.cts} +132 -29
- package/dist/{clientTypes-BN3nbzYM.d.ts → clientTypes-CcU5WvdV.d.ts} +132 -29
- package/dist/core.cjs +3269 -2626
- package/dist/core.d.cts +32 -8
- package/dist/core.d.ts +32 -8
- package/dist/core.js +5 -4
- package/dist/node.cjs +3074 -2436
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +20 -19
- package/dist/{acp-Wi6isVQI.d.cts → onchain-utils-D9Z33IP7.d.cts} +166 -12
- package/dist/{acp-Wi6isVQI.d.ts → onchain-utils-D9Z33IP7.d.ts} +166 -12
- package/dist/web.cjs +3204 -2493
- package/dist/web.d.cts +49 -3
- package/dist/web.d.ts +49 -3
- package/dist/web.js +52 -27
- package/dist/zkProve.worker.cjs +79 -18
- package/dist/zkProve.worker.js +24 -24
- package/node/index.ts +1 -10
- package/node/storage.ts +20 -9
- package/node/test/inherited.test.ts +5 -1
- package/node/test/storage-memory-fallback.test.ts +47 -0
- package/node/test/storage-permissions.test.ts +40 -0
- package/package.json +3 -1
- package/web/index.ts +101 -36
- package/web/test/countingZkProveWorker.ts +31 -0
- package/web/test/inherited.web.test.ts +5 -1
- package/web/test/mainThreadPool.web.test.ts +174 -0
- package/web/test/threadPoolBenchHelper.ts +134 -0
- package/web/test/threadPoolOff.web.test.ts +17 -0
- package/web/test/threadPoolOn.web.test.ts +80 -0
- package/web/test/workerInitRace.web.test.ts +79 -0
- package/web/tfheThreadPool.ts +140 -0
- package/web/workerManager.ts +4 -2
- package/web/zkProve.worker.ts +42 -26
- package/dist/chunk-ESMZCFJY.js +0 -12
- package/dist/chunk-Q7CBWGQX.js +0 -1029
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { type PublicClient, type WalletClient, createPublicClient, createWalletClient, http } from 'viem';
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { type PublicClient, type WalletClient, createPublicClient, createWalletClient, http, zeroAddress } from 'viem';
|
|
3
3
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
4
|
import { arbitrumSepolia } from 'viem/chains';
|
|
5
|
+
import { ACPUtils, acpStore, type ACP } from '@/acps';
|
|
5
6
|
import { DecryptForTxBuilder } from '../decrypt/decryptForTxBuilder.js';
|
|
6
7
|
import { DecryptForViewBuilder } from '../decrypt/decryptForViewBuilder.js';
|
|
8
|
+
import { tnDecryptV2 } from '../decrypt/tnDecryptV2';
|
|
9
|
+
import { tnSealOutputV2 } from '../decrypt/tnSealOutputV2';
|
|
7
10
|
import { createCofheConfigBase, type CofheConfig } from '../config.js';
|
|
8
11
|
import { CofheErrorCode } from '../error.js';
|
|
9
12
|
import { FheTypes } from '../types.js';
|
|
10
13
|
|
|
14
|
+
// Stub the threshold network calls so tests can assert whether a request was dispatched
|
|
15
|
+
vi.mock('../decrypt/tnDecryptV2', async (importOriginal) => ({
|
|
16
|
+
...(await importOriginal<typeof import('../decrypt/tnDecryptV2')>()),
|
|
17
|
+
tnDecryptV2: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock('../decrypt/tnSealOutputV2', async (importOriginal) => ({
|
|
20
|
+
...(await importOriginal<typeof import('../decrypt/tnSealOutputV2')>()),
|
|
21
|
+
tnSealOutputV2: vi.fn(),
|
|
22
|
+
}));
|
|
23
|
+
|
|
11
24
|
const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
12
25
|
const account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
13
26
|
const TEST_CHAIN_ID = 421614;
|
|
@@ -416,3 +429,98 @@ describe('DecryptForViewBuilder', () => {
|
|
|
416
429
|
});
|
|
417
430
|
});
|
|
418
431
|
});
|
|
432
|
+
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
// Expired acp (regression, issue #272)
|
|
435
|
+
//
|
|
436
|
+
// The getOrCreate* half of #272 is covered in acps.test.ts. These cover the decrypt path:
|
|
437
|
+
// execute() must reject an expired acp (ACPUtils.validate) before any request is dispatched
|
|
438
|
+
// to the threshold network, however the acp was selected.
|
|
439
|
+
// ---------------------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
describe('execute – expired acp (regression, issue #272)', () => {
|
|
442
|
+
type Selection = 'active acp' | 'acp hash' | 'acp object';
|
|
443
|
+
const selections: Selection[] = ['active acp', 'acp hash', 'acp object'];
|
|
444
|
+
|
|
445
|
+
const now = () => Math.floor(Date.now() / 1000);
|
|
446
|
+
|
|
447
|
+
// Built by hand rather than with ACPUtils.sign, which reads the ACL's EIP-712 domain over RPC.
|
|
448
|
+
// validate() only checks that a signature is present, and the threshold network is stubbed.
|
|
449
|
+
function createSignedSelfACP(expiration: number): ACP {
|
|
450
|
+
return {
|
|
451
|
+
...ACPUtils.createSelf({ name: 'Test Self ACP', issuer: account.address, expiration }),
|
|
452
|
+
issuerSignature: `0x${'11'.repeat(65)}`,
|
|
453
|
+
_signedDomain: { name: 'ACL', version: '1', chainId: TEST_CHAIN_ID, verifyingContract: zeroAddress },
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Stores the acp and makes it the active acp for the test chain + account
|
|
458
|
+
function seedACP(acp: ACP) {
|
|
459
|
+
acpStore.setACP(TEST_CHAIN_ID, account.address, acp);
|
|
460
|
+
acpStore.setActiveACPHash(TEST_CHAIN_ID, account.address, acp.hash);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function selectTxACP(selection: Selection, acp: ACP) {
|
|
464
|
+
const builder = createTxBuilder();
|
|
465
|
+
if (selection === 'acp hash') return builder.withACP(acp.hash);
|
|
466
|
+
if (selection === 'acp object') return builder.withACP(acp);
|
|
467
|
+
return builder.withACP();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function selectViewACP(selection: Selection, acp: ACP) {
|
|
471
|
+
const builder = createViewBuilder(FheTypes.Uint32);
|
|
472
|
+
if (selection === 'acp hash') return builder.withACP(acp.hash);
|
|
473
|
+
if (selection === 'acp object') return builder.withACP(acp);
|
|
474
|
+
return builder.withACP();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
beforeEach(() => {
|
|
478
|
+
acpStore.store.setState({ acps: {}, activeACPHash: {} });
|
|
479
|
+
vi.mocked(tnDecryptV2).mockReset();
|
|
480
|
+
vi.mocked(tnSealOutputV2).mockReset();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
afterEach(() => {
|
|
484
|
+
acpStore.store.setState({ acps: {}, activeACPHash: {} });
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
describe('decryptForTx', () => {
|
|
488
|
+
it.each(selections)('rejects an expired acp selected via %s without dispatching', async (selection) => {
|
|
489
|
+
const acp = createSignedSelfACP(now() - 3600);
|
|
490
|
+
seedACP(acp);
|
|
491
|
+
|
|
492
|
+
await expect(selectTxACP(selection, acp).execute()).rejects.toThrow('ACP is expired');
|
|
493
|
+
expect(tnDecryptV2).not.toHaveBeenCalled();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('dispatches a valid acp (control for the expired cases)', async () => {
|
|
497
|
+
vi.mocked(tnDecryptV2).mockResolvedValue({ decryptedValue: 7n, signature: '0x1234' });
|
|
498
|
+
const acp = createSignedSelfACP(now() + 3600);
|
|
499
|
+
seedACP(acp);
|
|
500
|
+
|
|
501
|
+
const result = await selectTxACP('active acp', acp).execute();
|
|
502
|
+
|
|
503
|
+
expect(result.decryptedValue).toBe(7n);
|
|
504
|
+
expect(tnDecryptV2).toHaveBeenCalledOnce();
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
describe('decryptForView', () => {
|
|
509
|
+
it.each(selections)('rejects an expired acp selected via %s without dispatching', async (selection) => {
|
|
510
|
+
const acp = createSignedSelfACP(now() - 3600);
|
|
511
|
+
seedACP(acp);
|
|
512
|
+
|
|
513
|
+
await expect(selectViewACP(selection, acp).execute()).rejects.toThrow('ACP is expired');
|
|
514
|
+
expect(tnSealOutputV2).not.toHaveBeenCalled();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('dispatches a valid acp (control for the expired cases)', async () => {
|
|
518
|
+
vi.mocked(tnSealOutputV2).mockRejectedValue(new Error('stubbed threshold network'));
|
|
519
|
+
const acp = createSignedSelfACP(now() + 3600);
|
|
520
|
+
seedACP(acp);
|
|
521
|
+
|
|
522
|
+
await expect(selectViewACP('active acp', acp).execute()).rejects.toThrow('stubbed threshold network');
|
|
523
|
+
expect(tnSealOutputV2).toHaveBeenCalledOnce();
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
});
|
|
@@ -180,7 +180,7 @@ let keysStorage: KeysStorage;
|
|
|
180
180
|
|
|
181
181
|
const insertMockKeys = (chainId: number, securityZone: number) => {
|
|
182
182
|
keysStorage.setFheKey(chainId, securityZone, '0x1234567890');
|
|
183
|
-
keysStorage.setCrs(chainId, '0x1234567890');
|
|
183
|
+
keysStorage.setCrs(chainId, securityZone, '0x1234567890');
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
const mockTfhePublicKeyDeserializer: FheKeyDeserializer = (buff: string) => {
|
|
@@ -110,7 +110,7 @@ describe('fetchKeys', () => {
|
|
|
110
110
|
it('should not fetch CRS if already cached', async () => {
|
|
111
111
|
// Pre-populate with a cached CRS
|
|
112
112
|
const mockCachedCrs = '0x2345678901';
|
|
113
|
-
keysStorage.setCrs(sepolia.id, mockCachedCrs);
|
|
113
|
+
keysStorage.setCrs(sepolia.id, 0, mockCachedCrs);
|
|
114
114
|
|
|
115
115
|
const [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
116
116
|
config,
|
|
@@ -133,12 +133,33 @@ describe('fetchKeys', () => {
|
|
|
133
133
|
expect(retrievedKey).toBeDefined();
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
it('should not reuse a cached CRS from a different security zone', async () => {
|
|
137
|
+
// CoFHE serves a distinct CRS per security zone, so a zone 0 CRS must not satisfy zone 1
|
|
138
|
+
const zone0Crs = '0x2345678901';
|
|
139
|
+
keysStorage.setCrs(sepolia.id, 0, zone0Crs);
|
|
140
|
+
|
|
141
|
+
const [, [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
142
|
+
config,
|
|
143
|
+
sepolia.id,
|
|
144
|
+
1,
|
|
145
|
+
mockTfhePublicKeyDeserializer,
|
|
146
|
+
mockCompactPkeCrsDeserializer,
|
|
147
|
+
keysStorage
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
expect(crsFetchedFromCoFHE).toBe(true);
|
|
151
|
+
expect(crs).not.toEqual(zone0Crs);
|
|
152
|
+
|
|
153
|
+
// The zone 0 entry is untouched
|
|
154
|
+
expect(keysStorage.getCrs(sepolia.id, 0)).toEqual(zone0Crs);
|
|
155
|
+
});
|
|
156
|
+
|
|
136
157
|
it('should not make any network calls if both keys are cached', async () => {
|
|
137
158
|
// Pre-populate both keys
|
|
138
159
|
const mockCachedKey = '0x1234567890';
|
|
139
160
|
const mockCachedCrs = '0x2345678901';
|
|
140
161
|
keysStorage.setFheKey(sepolia.id, 0, mockCachedKey);
|
|
141
|
-
keysStorage.setCrs(sepolia.id, mockCachedCrs);
|
|
162
|
+
keysStorage.setCrs(sepolia.id, 0, mockCachedCrs);
|
|
142
163
|
|
|
143
164
|
const [[fheKey, fheKeyFetchedFromCoFHE], [crs, crsFetchedFromCoFHE]] = await fetchKeys(
|
|
144
165
|
config,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { PublicClient } from 'viem';
|
|
3
|
+
import { acps } from '../acps';
|
|
4
|
+
import { UNLABELLED, encodeShareMetadata, shareAppKey } from '../../acps/share-metadata';
|
|
5
|
+
import { shareIdOfChainShare, type ChainShare, type ShareHeader } from '../../acps/registry';
|
|
6
|
+
|
|
7
|
+
const REGISTRY = '0x00000000000000000000000000000000000000e0';
|
|
8
|
+
const ISSUER = '0x00000000000000000000000000000000000000b0';
|
|
9
|
+
const RECIPIENT = '0x00000000000000000000000000000000000000a1';
|
|
10
|
+
const ZERO = '0x0000000000000000000000000000000000000000';
|
|
11
|
+
|
|
12
|
+
const chainShare = (handle: number): ChainShare => ({
|
|
13
|
+
issuer: ISSUER,
|
|
14
|
+
expiration: 2_000_000_000n,
|
|
15
|
+
recipient: RECIPIENT,
|
|
16
|
+
revokerData: 0n,
|
|
17
|
+
revokerContract: ZERO,
|
|
18
|
+
scope: 2,
|
|
19
|
+
contracts: [],
|
|
20
|
+
handles: [`0x${handle.toString(16).padStart(64, '0')}`],
|
|
21
|
+
sealingKey: `0x${'0'.repeat(64)}`,
|
|
22
|
+
issuerSignature: '0x11',
|
|
23
|
+
recipientSignature: '0x',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const header: ShareHeader = {
|
|
27
|
+
issuer: ISSUER,
|
|
28
|
+
expiration: 2_000_000_000n,
|
|
29
|
+
recipient: RECIPIENT,
|
|
30
|
+
blockNumber: 10n,
|
|
31
|
+
revokerContract: ZERO,
|
|
32
|
+
revokerData: 0n,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const EXPLORER = shareAppKey('explorer.fhenix.io');
|
|
36
|
+
const WALLET = shareAppKey('wallet.example');
|
|
37
|
+
|
|
38
|
+
/** Three shares in one block: one for the explorer, one for a wallet app, one for no app. */
|
|
39
|
+
const shares = [chainShare(1), chainShare(2), chainShare(3)];
|
|
40
|
+
const ids = shares.map(shareIdOfChainShare);
|
|
41
|
+
const metadata = [
|
|
42
|
+
encodeShareMetadata([UNLABELLED], { appKey: EXPLORER }),
|
|
43
|
+
encodeShareMetadata([UNLABELLED], { appKey: WALLET }),
|
|
44
|
+
'0x',
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
const client = {
|
|
48
|
+
readContract: vi.fn(async () => [ids, shares.map(() => header)]),
|
|
49
|
+
getContractEvents: vi.fn(async ({ args }: { args: { shareId: string[] } }) =>
|
|
50
|
+
shares
|
|
51
|
+
.map((acp, i) => ({ args: { shareId: ids[i], acp, metadata: metadata[i] } }))
|
|
52
|
+
.filter((e) => args.shareId.includes(e.args.shareId))
|
|
53
|
+
),
|
|
54
|
+
} as unknown as PublicClient;
|
|
55
|
+
|
|
56
|
+
describe('getIncomingShares and the app key', () => {
|
|
57
|
+
it('lists every share without an app, each with the key it carries', async () => {
|
|
58
|
+
const incoming = await acps.getIncomingShares(client, REGISTRY, RECIPIENT);
|
|
59
|
+
expect(incoming.map((s) => s.shareId)).toEqual(ids);
|
|
60
|
+
expect(incoming.map((s) => s.appKey)).toEqual([EXPLORER, WALLET, undefined]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('lists only the shares posted for the app', async () => {
|
|
64
|
+
const incoming = await acps.getIncomingShares(client, REGISTRY, RECIPIENT, { appKey: EXPLORER });
|
|
65
|
+
expect(incoming.map((s) => s.shareId)).toEqual([ids[0]]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('leaves out a share whose event the node cannot serve yet, and keeps the rest', async () => {
|
|
69
|
+
const lagging = {
|
|
70
|
+
readContract: vi.fn(async () => [ids, shares.map(() => header)]),
|
|
71
|
+
getContractEvents: vi.fn(async ({ args }: { args: { shareId: string[] } }) =>
|
|
72
|
+
shares
|
|
73
|
+
.map((acp, i) => ({ args: { shareId: ids[i], acp, metadata: metadata[i] } }))
|
|
74
|
+
.filter((e) => args.shareId.includes(e.args.shareId) && e.args.shareId !== ids[1])
|
|
75
|
+
),
|
|
76
|
+
} as unknown as PublicClient;
|
|
77
|
+
const incoming = await acps.getIncomingShares(lagging, REGISTRY, RECIPIENT);
|
|
78
|
+
expect(incoming.map((s) => s.shareId)).toEqual([ids[0], ids[2]]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('lists nothing for an app no share names', async () => {
|
|
82
|
+
expect(await acps.getIncomingShares(client, REGISTRY, RECIPIENT, { appKey: shareAppKey('other') })).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -98,7 +98,7 @@ describe('KeyStore', () => {
|
|
|
98
98
|
const testCrs = '0x1234567890';
|
|
99
99
|
|
|
100
100
|
it('should set and get CRS', () => {
|
|
101
|
-
keysStorage.setCrs(testChainId, testCrs);
|
|
101
|
+
keysStorage.setCrs(testChainId, 0, testCrs);
|
|
102
102
|
|
|
103
103
|
const retrievedCrs = keysStorage.getCrs(testChainId);
|
|
104
104
|
|
|
@@ -109,16 +109,28 @@ describe('KeyStore', () => {
|
|
|
109
109
|
const crs1 = '0x1234567890';
|
|
110
110
|
const crs2 = '0x2345678901';
|
|
111
111
|
|
|
112
|
-
keysStorage.setCrs(1, crs1);
|
|
113
|
-
keysStorage.setCrs(2, crs2);
|
|
112
|
+
keysStorage.setCrs(1, 0, crs1);
|
|
113
|
+
keysStorage.setCrs(2, 0, crs2);
|
|
114
114
|
|
|
115
115
|
expect(keysStorage.getCrs(1)).toEqual(crs1);
|
|
116
116
|
expect(keysStorage.getCrs(2)).toEqual(crs2);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
+
it('should keep CRS separate per security zone', () => {
|
|
120
|
+
const crsZone0 = '0xaaaaaaaaaa';
|
|
121
|
+
const crsZone1 = '0xbbbbbbbbbb';
|
|
122
|
+
|
|
123
|
+
keysStorage.setCrs(testChainId, 0, crsZone0);
|
|
124
|
+
keysStorage.setCrs(testChainId, 1, crsZone1);
|
|
125
|
+
|
|
126
|
+
expect(keysStorage.getCrs(testChainId, 0)).toEqual(crsZone0);
|
|
127
|
+
expect(keysStorage.getCrs(testChainId, 1)).toEqual(crsZone1);
|
|
128
|
+
});
|
|
129
|
+
|
|
119
130
|
it('should return undefined for non-existent CRS', () => {
|
|
120
131
|
expect(keysStorage.getCrs(999)).toBeUndefined();
|
|
121
132
|
expect(keysStorage.getCrs(undefined)).toBeUndefined();
|
|
133
|
+
expect(keysStorage.getCrs(testChainId, 999)).toBeUndefined();
|
|
122
134
|
});
|
|
123
135
|
});
|
|
124
136
|
|
|
@@ -162,7 +174,7 @@ describe('KeyStore', () => {
|
|
|
162
174
|
const testCrs = '0x2345678901';
|
|
163
175
|
|
|
164
176
|
keysStorage.setFheKey(testChainId, 0, testKey);
|
|
165
|
-
keysStorage.setCrs(testChainId, testCrs);
|
|
177
|
+
keysStorage.setCrs(testChainId, 0, testCrs);
|
|
166
178
|
|
|
167
179
|
expect(keysStorage.getFheKey(testChainId, 0)).toEqual(testKey);
|
|
168
180
|
expect(keysStorage.getCrs(testChainId)).toEqual(testCrs);
|
|
@@ -194,13 +206,13 @@ describe('KeyStore', () => {
|
|
|
194
206
|
|
|
195
207
|
keysStorage.setFheKey(1, 0, key1);
|
|
196
208
|
keysStorage.setFheKey(2, 0, key2);
|
|
197
|
-
keysStorage.setCrs(1, crs1);
|
|
209
|
+
keysStorage.setCrs(1, 0, crs1);
|
|
198
210
|
|
|
199
211
|
const state = keysStorage.store.getState();
|
|
200
212
|
|
|
201
213
|
expect(state.fhe[1][0]).toEqual(key1);
|
|
202
214
|
expect(state.fhe[2][0]).toEqual(key2);
|
|
203
|
-
expect(state.crs[1]).toEqual(crs1);
|
|
215
|
+
expect(state.crs[1][0]).toEqual(crs1);
|
|
204
216
|
});
|
|
205
217
|
});
|
|
206
218
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { type PublicClient, type WalletClient } from 'viem';
|
|
3
|
+
import { hardhat } from 'viem/chains';
|
|
4
|
+
import { EncryptInputsBuilder } from '../encrypt/encryptInputsBuilder.js';
|
|
5
|
+
import { Encryptable, FheTypes, type EncryptableItem } from '../types.js';
|
|
6
|
+
import { createCofheConfigBase } from '../config.js';
|
|
7
|
+
import { zkPack, type ZkCiphertextListBuilder } from '../encrypt/zkPackProveVerify.js';
|
|
8
|
+
|
|
9
|
+
// The hardhat (mocks) encrypt path must reject the same out-of-range plaintexts that the
|
|
10
|
+
// production path rejects in zkPack. Otherwise MockCoFHE._set masks the value with the utype
|
|
11
|
+
// mask (e.g. uint8(256) -> 0) and tests pass on hardhat while the same call throws on a real chain.
|
|
12
|
+
|
|
13
|
+
const account = '0x1234567890123456789012345678901234567890';
|
|
14
|
+
const consumingContract = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef';
|
|
15
|
+
|
|
16
|
+
const fakeClients = () => {
|
|
17
|
+
const readContract = vi.fn(async ({ args }: any) => (args[0] as bigint[]).map((_, i) => BigInt(i + 1)));
|
|
18
|
+
const writeContract = vi.fn(async () => '0x' as `0x${string}`);
|
|
19
|
+
const publicClient = { readContract } as unknown as PublicClient;
|
|
20
|
+
const walletClient = { account: { address: account }, writeContract } as unknown as WalletClient;
|
|
21
|
+
return { publicClient, walletClient, readContract, writeContract };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mocksBuilder = (inputs: EncryptableItem[]) => {
|
|
25
|
+
const { publicClient, walletClient, readContract, writeContract } = fakeClients();
|
|
26
|
+
const builder = new EncryptInputsBuilder({
|
|
27
|
+
inputs: inputs as any,
|
|
28
|
+
account,
|
|
29
|
+
chainId: hardhat.id,
|
|
30
|
+
config: createCofheConfigBase({ supportedChains: [], mocks: { encryptDelay: 0 } }),
|
|
31
|
+
publicClient,
|
|
32
|
+
walletClient,
|
|
33
|
+
zkvWalletClient: walletClient,
|
|
34
|
+
tfhePublicKeyDeserializer: vi.fn(),
|
|
35
|
+
compactPkeCrsDeserializer: vi.fn(),
|
|
36
|
+
zkBuilderAndCrsGenerator: vi.fn() as any,
|
|
37
|
+
initTfhe: undefined,
|
|
38
|
+
zkProveWorkerFn: undefined,
|
|
39
|
+
keysStorage: undefined,
|
|
40
|
+
requireConnected: vi.fn(),
|
|
41
|
+
});
|
|
42
|
+
builder.setConsumingContract(consumingContract);
|
|
43
|
+
return { builder, readContract, writeContract };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const noop = () => undefined;
|
|
47
|
+
const noopZkBuilder = {
|
|
48
|
+
push_boolean: noop,
|
|
49
|
+
push_u8: noop,
|
|
50
|
+
push_u16: noop,
|
|
51
|
+
push_u32: noop,
|
|
52
|
+
push_u64: noop,
|
|
53
|
+
push_u128: noop,
|
|
54
|
+
push_u160: noop,
|
|
55
|
+
build_with_proof_packed: noop,
|
|
56
|
+
} as unknown as ZkCiphertextListBuilder;
|
|
57
|
+
|
|
58
|
+
const outOfRange: [string, EncryptableItem][] = [
|
|
59
|
+
['uint8(256)', Encryptable.uint8(256n)],
|
|
60
|
+
['uint16(65536)', Encryptable.uint16(65536n)],
|
|
61
|
+
['uint32(2^32)', Encryptable.uint32(2n ** 32n)],
|
|
62
|
+
['uint64(2^64)', Encryptable.uint64(2n ** 64n)],
|
|
63
|
+
['uint128(2^128)', Encryptable.uint128(2n ** 128n)],
|
|
64
|
+
['address(2^160)', { data: 2n ** 160n, securityZone: 0, utype: FheTypes.Uint160 } as EncryptableItem],
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
describe('mocks encrypt path value range parity', () => {
|
|
68
|
+
it.each(outOfRange)('production zkPack rejects %s', (_, item) => {
|
|
69
|
+
expect(() => zkPack([item], noopZkBuilder)).toThrow(/out of range/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it.each(outOfRange)('hardhat mocks encrypt rejects %s before touching the chain', async (_, item) => {
|
|
73
|
+
const { builder, readContract, writeContract } = mocksBuilder([item]);
|
|
74
|
+
await expect(builder.execute()).rejects.toThrow(/out of range/);
|
|
75
|
+
expect(readContract).not.toHaveBeenCalled();
|
|
76
|
+
expect(writeContract).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('hardhat mocks encrypt still accepts the max value of each type', async () => {
|
|
80
|
+
const { builder, writeContract } = mocksBuilder([
|
|
81
|
+
Encryptable.uint8(255n),
|
|
82
|
+
Encryptable.uint16(65535n),
|
|
83
|
+
Encryptable.uint32(2n ** 32n - 1n),
|
|
84
|
+
Encryptable.uint64(2n ** 64n - 1n),
|
|
85
|
+
Encryptable.bool(true),
|
|
86
|
+
]);
|
|
87
|
+
await expect(builder.execute()).resolves.toBeDefined();
|
|
88
|
+
expect(writeContract).toHaveBeenCalledTimes(1);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import type { PublicClient } from 'viem';
|
|
3
|
+
|
|
4
|
+
import { getTaskManagerAddress, clearTaskManagerAddressCache } from '../taskManager.js';
|
|
5
|
+
import { COFHE_ADDRESS_BOOK_ADDRESS, TASK_MANAGER_ID } from '../consts.js';
|
|
6
|
+
import { CofheErrorCode, isCofheError } from '../error.js';
|
|
7
|
+
|
|
8
|
+
const TM = '0x0000000000000000000000000000000000005000';
|
|
9
|
+
|
|
10
|
+
const fakeClient = (readContract: (...args: any[]) => Promise<unknown>, chainId = 31337) =>
|
|
11
|
+
({ chain: { id: chainId }, readContract: vi.fn(readContract) }) as unknown as PublicClient & {
|
|
12
|
+
readContract: ReturnType<typeof vi.fn>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('getTaskManagerAddress', () => {
|
|
16
|
+
beforeEach(() => clearTaskManagerAddressCache());
|
|
17
|
+
|
|
18
|
+
it('resolves getTm(TASK_MANAGER_ID) on the CoFHEAddressBook', async () => {
|
|
19
|
+
const client = fakeClient(async () => TM);
|
|
20
|
+
|
|
21
|
+
expect(await getTaskManagerAddress(client)).to.equal(TM);
|
|
22
|
+
expect(client.readContract).toHaveBeenCalledWith(
|
|
23
|
+
expect.objectContaining({
|
|
24
|
+
address: COFHE_ADDRESS_BOOK_ADDRESS,
|
|
25
|
+
functionName: 'getTm',
|
|
26
|
+
args: [TASK_MANAGER_ID],
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('caches per chainId and shares an in-flight lookup', async () => {
|
|
32
|
+
const client = fakeClient(async () => TM);
|
|
33
|
+
|
|
34
|
+
await Promise.all([getTaskManagerAddress(client), getTaskManagerAddress(client)]);
|
|
35
|
+
await getTaskManagerAddress(client);
|
|
36
|
+
expect(client.readContract).toHaveBeenCalledTimes(1);
|
|
37
|
+
|
|
38
|
+
await getTaskManagerAddress(client, 11155111);
|
|
39
|
+
expect(client.readContract).toHaveBeenCalledTimes(2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('throws TASK_MANAGER_UNRESOLVED and does not cache the failure', async () => {
|
|
43
|
+
let calls = 0;
|
|
44
|
+
const client = fakeClient(async () => {
|
|
45
|
+
calls += 1;
|
|
46
|
+
if (calls === 1) throw new Error('returned no data ("0x")');
|
|
47
|
+
return TM;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const error = await getTaskManagerAddress(client).catch((e: unknown) => e);
|
|
51
|
+
expect(isCofheError(error)).to.equal(true);
|
|
52
|
+
expect((error as any).code).to.equal(CofheErrorCode.TaskManagerUnresolved);
|
|
53
|
+
|
|
54
|
+
expect(await getTaskManagerAddress(client)).to.equal(TM);
|
|
55
|
+
expect(client.readContract).toHaveBeenCalledTimes(2);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
fromHexString,
|
|
4
|
+
toHexString,
|
|
5
|
+
hexToBytes,
|
|
6
|
+
toBigIntOrThrow,
|
|
7
|
+
validateBigIntInRange,
|
|
8
|
+
fheTypeToString,
|
|
9
|
+
} from '../utils.js';
|
|
10
|
+
import { FheTypes } from '../types.js';
|
|
11
|
+
|
|
12
|
+
describe('fromHexString', () => {
|
|
13
|
+
it('strips the 0x prefix before padding an odd-length string', () => {
|
|
14
|
+
// Regression: padding first produced `00x123`, which decoded to [0x00, NaN->0, 0x23].
|
|
15
|
+
expect(Array.from(fromHexString('0x123'))).toEqual([0x01, 0x23]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('pads odd-length strings without a prefix', () => {
|
|
19
|
+
expect(Array.from(fromHexString('123'))).toEqual([0x01, 0x23]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('decodes even-length strings with and without the 0x prefix identically', () => {
|
|
23
|
+
expect(Array.from(fromHexString('0x1234'))).toEqual([0x12, 0x34]);
|
|
24
|
+
expect(Array.from(fromHexString('1234'))).toEqual([0x12, 0x34]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('decodes uppercase hex', () => {
|
|
28
|
+
expect(Array.from(fromHexString('0xDEADBEEF'))).toEqual([0xde, 0xad, 0xbe, 0xef]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns an empty array for empty input', () => {
|
|
32
|
+
expect(fromHexString('')).toEqual(new Uint8Array());
|
|
33
|
+
expect(fromHexString('0x')).toEqual(new Uint8Array());
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('round-trips through toHexString', () => {
|
|
37
|
+
const hex = 'a3'.repeat(32);
|
|
38
|
+
const bytes = fromHexString(hex);
|
|
39
|
+
|
|
40
|
+
expect(bytes.length).toBe(32);
|
|
41
|
+
expect(toHexString(bytes)).toBe(hex);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('throws instead of silently producing zero bytes for non-hex characters', () => {
|
|
45
|
+
expect(() => fromHexString('zzzz')).toThrow('Invalid hex string');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('throws for hex strings with a single invalid character', () => {
|
|
49
|
+
expect(() => fromHexString('0x12g4')).toThrow('Invalid hex string');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('only strips a lowercase 0x prefix', () => {
|
|
53
|
+
expect(() => fromHexString('0X12')).toThrow('Invalid hex string');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('does not leak sensitive input in the error message', () => {
|
|
57
|
+
const secret = 'f'.repeat(63) + 'z';
|
|
58
|
+
|
|
59
|
+
expect(() => fromHexString(secret)).toThrow(
|
|
60
|
+
expect.objectContaining({ message: expect.not.stringContaining(secret) })
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('hexToBytes', () => {
|
|
66
|
+
it('decodes even-length hex with and without prefix', () => {
|
|
67
|
+
expect(Array.from(hexToBytes('0x1234'))).toEqual([0x12, 0x34]);
|
|
68
|
+
expect(Array.from(hexToBytes('1234'))).toEqual([0x12, 0x34]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('handles odd-length hex correctly with prefix', () => {
|
|
72
|
+
expect(Array.from(hexToBytes('0x123'))).toEqual([0x01, 0x23]);
|
|
73
|
+
expect(Array.from(hexToBytes('123'))).toEqual([0x01, 0x23]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('throws for invalid hex characters', () => {
|
|
77
|
+
expect(() => hexToBytes('0x12z4')).toThrow('Invalid hex string');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('toHexString', () => {
|
|
82
|
+
it('pads each byte to 2 hex characters', () => {
|
|
83
|
+
const bytes = new Uint8Array([0x00, 0x0f, 0x10, 0xff]);
|
|
84
|
+
expect(toHexString(bytes)).toBe('000f10ff');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('toBigIntOrThrow', () => {
|
|
89
|
+
it('returns bigint unchanged if passed bigint', () => {
|
|
90
|
+
expect(toBigIntOrThrow(123n)).toBe(123n);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('converts valid string to bigint', () => {
|
|
94
|
+
expect(toBigIntOrThrow('456')).toBe(456n);
|
|
95
|
+
expect(toBigIntOrThrow('0x10')).toBe(16n);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('throws for invalid string', () => {
|
|
99
|
+
expect(() => toBigIntOrThrow('not-a-number')).toThrow('Invalid input: Unable to convert to bigint');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('validateBigIntInRange', () => {
|
|
104
|
+
it('accepts values within range', () => {
|
|
105
|
+
expect(() => validateBigIntInRange(10n, 20n, 0n)).not.toThrow();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('throws when value exceeds max', () => {
|
|
109
|
+
expect(() => validateBigIntInRange(25n, 20n, 0n)).toThrow('Value out of range');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('throws when value is below min', () => {
|
|
113
|
+
expect(() => validateBigIntInRange(5n, 20n, 10n)).toThrow('Value out of range');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('fheTypeToString', () => {
|
|
118
|
+
it('maps known types to string identifiers', () => {
|
|
119
|
+
expect(fheTypeToString(FheTypes.Bool)).toBe('bool');
|
|
120
|
+
expect(fheTypeToString(FheTypes.Uint8)).toBe('uint8');
|
|
121
|
+
expect(fheTypeToString(FheTypes.Uint16)).toBe('uint16');
|
|
122
|
+
expect(fheTypeToString(FheTypes.Uint32)).toBe('uint32');
|
|
123
|
+
expect(fheTypeToString(FheTypes.Uint64)).toBe('uint64');
|
|
124
|
+
expect(fheTypeToString(FheTypes.Uint128)).toBe('uint128');
|
|
125
|
+
expect(fheTypeToString(FheTypes.Uint160)).toBe('uint160');
|
|
126
|
+
expect(fheTypeToString(FheTypes.Uint256)).toBe('uint256');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { encodePacked, keccak256, zeroAddress, type Address, type PublicClient } from 'viem';
|
|
3
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
4
|
+
|
|
5
|
+
import { verifyDecryptResult } from '../decrypt/verifyDecryptResult.js';
|
|
6
|
+
import { clearTaskManagerAddressCache } from '../taskManager.js';
|
|
7
|
+
|
|
8
|
+
const CHAIN_ID = 31337;
|
|
9
|
+
const TM = '0x0000000000000000000000000000000000005000';
|
|
10
|
+
// euint32 (utype 4) in the type byte
|
|
11
|
+
const CT_HASH = (0xabcdefn << 16n) | (4n << 8n);
|
|
12
|
+
const CLEARTEXT = 42n;
|
|
13
|
+
|
|
14
|
+
const signer = privateKeyToAccount(generatePrivateKey());
|
|
15
|
+
|
|
16
|
+
const signDecryptResult = (ctHash: bigint, cleartext: bigint) =>
|
|
17
|
+
signer.sign({
|
|
18
|
+
hash: keccak256(encodePacked(['uint256', 'uint32', 'uint64', 'uint256'], [cleartext, 4, BigInt(CHAIN_ID), ctHash])),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const fakeClient = (decryptResultSigner: Address) =>
|
|
22
|
+
({
|
|
23
|
+
chain: { id: CHAIN_ID },
|
|
24
|
+
readContract: vi.fn(async ({ functionName }: { functionName: string }) =>
|
|
25
|
+
functionName === 'getTm' ? TM : decryptResultSigner
|
|
26
|
+
),
|
|
27
|
+
}) as unknown as PublicClient;
|
|
28
|
+
|
|
29
|
+
describe('verifyDecryptResult', () => {
|
|
30
|
+
beforeEach(() => clearTaskManagerAddressCache());
|
|
31
|
+
|
|
32
|
+
it('accepts a result signed by the configured decryptResultSigner', async () => {
|
|
33
|
+
const signature = await signDecryptResult(CT_HASH, CLEARTEXT);
|
|
34
|
+
|
|
35
|
+
expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, signature, fakeClient(signer.address))).to.equal(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rejects a result signed by a different key', async () => {
|
|
39
|
+
const signature = await signDecryptResult(CT_HASH, CLEARTEXT);
|
|
40
|
+
const other = privateKeyToAccount(generatePrivateKey());
|
|
41
|
+
|
|
42
|
+
expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, signature, fakeClient(other.address))).to.equal(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('fails closed when the decryptResultSigner is unset (zero address)', async () => {
|
|
46
|
+
const signature = await signDecryptResult(CT_HASH, CLEARTEXT);
|
|
47
|
+
|
|
48
|
+
expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, signature, fakeClient(zeroAddress))).to.equal(false);
|
|
49
|
+
expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, '0x', fakeClient(zeroAddress))).to.equal(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
package/core/types.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export type TfheInitializer = () => Promise<boolean>;
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* How many threads tfhe's rayon pool may use for ZK proof generation (web only).
|
|
5
|
+
* - `'auto'` (default): derive from `navigator.hardwareConcurrency`, capped
|
|
6
|
+
* - a positive number: request exactly that many
|
|
7
|
+
* - `false`: stay single-threaded
|
|
8
|
+
*/
|
|
9
|
+
export type TfheThreadsSetting = number | 'auto' | false;
|
|
10
|
+
|
|
3
11
|
export interface IStorage {
|
|
4
12
|
getItem: (name: string) => Promise<any>;
|
|
5
13
|
setItem: (name: string, value: any) => Promise<void>;
|