@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
package/core/clientTypes.ts
CHANGED
|
@@ -6,10 +6,11 @@ import { type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js'
|
|
|
6
6
|
import { type EncryptInputsBuilderUnset } from './encrypt/encryptInputsBuilder.js';
|
|
7
7
|
import { type ZkBuilderAndCrsGenerator, type ZkProveWorkerFunction } from './encrypt/zkPackProveVerify.js';
|
|
8
8
|
import { type FheKeyDeserializer } from './fetchKeys.js';
|
|
9
|
-
import { acps } from './acps.js';
|
|
9
|
+
import { acps, type ImportSharedOptions, type ShareOnChainOptions } from './acps.js';
|
|
10
10
|
import type { EncryptableItem, FheTypes, TfheInitializer } from './types.js';
|
|
11
11
|
import type { ACPUtils } from 'acps/acp.js';
|
|
12
12
|
import type {
|
|
13
|
+
ACPAccessStatus,
|
|
13
14
|
CreateSelfACPOptions,
|
|
14
15
|
ACP,
|
|
15
16
|
CreateSharingACPOptions,
|
|
@@ -18,7 +19,9 @@ import type {
|
|
|
18
19
|
RecipientACP,
|
|
19
20
|
SelfACP,
|
|
20
21
|
IncomingShare,
|
|
22
|
+
LabelledShare,
|
|
21
23
|
} from 'acps/types.js';
|
|
24
|
+
import type { ShareLabelCheck, ShareLabelVerifyMode } from 'acps/share-metadata/verify.js';
|
|
22
25
|
|
|
23
26
|
// CLIENT
|
|
24
27
|
|
|
@@ -74,6 +77,14 @@ export type CofheClientACPsClients = {
|
|
|
74
77
|
walletClient: WalletClient;
|
|
75
78
|
};
|
|
76
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Options for `importShared`. `activate: false` stores the imported acp without making it the active
|
|
82
|
+
* one (default `true`). `name` names it (default "Shared by 0x1234…abcd"). Clients default to the
|
|
83
|
+
* connected ones; pass both to override.
|
|
84
|
+
*/
|
|
85
|
+
export type CofheClientImportSharedOptions = ImportSharedOptions &
|
|
86
|
+
(CofheClientACPsClients | { publicClient?: undefined; walletClient?: undefined });
|
|
87
|
+
|
|
77
88
|
export type CofheClientACPs = {
|
|
78
89
|
getSnapshot: typeof acps.getSnapshot;
|
|
79
90
|
subscribe: typeof acps.subscribe;
|
|
@@ -81,7 +92,10 @@ export type CofheClientACPs = {
|
|
|
81
92
|
// Creation methods (require connection, no params)
|
|
82
93
|
createSelf: (options: CreateSelfACPOptions, clients?: CofheClientACPsClients) => Promise<SelfACP>;
|
|
83
94
|
createSharing: (options: CreateSharingACPOptions, clients?: CofheClientACPsClients) => Promise<SharingACP>;
|
|
84
|
-
importShared: (
|
|
95
|
+
importShared: (
|
|
96
|
+
options: ImportSharedACPOptions | string,
|
|
97
|
+
importOptions?: CofheClientImportSharedOptions
|
|
98
|
+
) => Promise<RecipientACP>;
|
|
85
99
|
|
|
86
100
|
// Retrieval methods (chainId/account optional)
|
|
87
101
|
getACP: (hash: string, chainId?: number, account?: string) => ACP | undefined;
|
|
@@ -102,13 +116,35 @@ export type CofheClientACPs = {
|
|
|
102
116
|
revokeACP: (acp: ACP) => Promise<`0x${string}`>;
|
|
103
117
|
revokeAllACPs: (revokerContract?: `0x${string}`) => Promise<`0x${string}`>;
|
|
104
118
|
isACPRevoked: (acp: ACP) => Promise<boolean>;
|
|
119
|
+
/**
|
|
120
|
+
* The ACP on-chain status as a value instead of a revert. Without a handle: 'valid', or
|
|
121
|
+
* 'expired' / 'revoked' / an invalid signature. With a handle, a valid ACP reports
|
|
122
|
+
* 'allowed', 'out-of-scope' or 'issuer-not-allowed'. For the issuer copy of a share (type
|
|
123
|
+
* 'sharing', no recipient signature yet) the handle-less check covers expiry and revocation only.
|
|
124
|
+
*/
|
|
125
|
+
checkAccess: (acp: ACP, handle?: bigint | `0x${string}`) => Promise<ACPAccessStatus>;
|
|
105
126
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Post a signed sharing ACP to the on-chain share registry (ACL-served, or config `acp.sharingRegistry`),
|
|
129
|
+
* optionally with the labels of its handles (`options.labels`, one per handle) or an encoded blob.
|
|
130
|
+
*/
|
|
131
|
+
shareOnChain: (acp: ACP, options?: ShareOnChainOptions) => Promise<{ txHash: `0x${string}`; shareId: `0x${string}` }>;
|
|
132
|
+
/** Importable shares addressed to the connected account (unexpired, not revoked), with their metadata. */
|
|
109
133
|
getIncomingShares: () => Promise<IncomingShare[]>;
|
|
110
|
-
/**
|
|
111
|
-
|
|
134
|
+
/** One share by id, as posted; null when unknown or removed. */
|
|
135
|
+
getShareFromChain: (shareId: `0x${string}`) => Promise<IncomingShare | null>;
|
|
136
|
+
/**
|
|
137
|
+
* Check a share's labels against the chain, index for index with its handles, as far as
|
|
138
|
+
* `verify` says (default 'all'). Takes a share read from the registry or an acp imported from
|
|
139
|
+
* exported JSON (or the issuer's sharing acp). Null for a share without metadata. Throws on a
|
|
140
|
+
* malformed blob.
|
|
141
|
+
*/
|
|
142
|
+
verifyShareLabels: (
|
|
143
|
+
share: LabelledShare,
|
|
144
|
+
options?: { verify?: ShareLabelVerifyMode }
|
|
145
|
+
) => Promise<ShareLabelCheck[] | null>;
|
|
146
|
+
/** Import a share read from the registry: sign as recipient, store and (unless `activate: false`) activate. */
|
|
147
|
+
importFromChain: (share: IncomingShare, options?: ImportSharedOptions) => Promise<RecipientACP>;
|
|
112
148
|
/** Recipient-side: remove a share from the registry (after import, or to decline). */
|
|
113
149
|
dismissShare: (shareId: `0x${string}`) => Promise<`0x${string}`>;
|
|
114
150
|
/** Issuer-side: retract a pending share from the registry. */
|
|
@@ -128,4 +164,11 @@ export type CofheClientParams<TConfig extends CofheConfig> = {
|
|
|
128
164
|
compactPkeCrsDeserializer: FheKeyDeserializer;
|
|
129
165
|
initTfhe: TfheInitializer;
|
|
130
166
|
zkProveWorkerFn?: ZkProveWorkerFunction;
|
|
167
|
+
/**
|
|
168
|
+
* Runs right before a ZK proof is generated on the calling thread, i.e. only
|
|
169
|
+
* when the worker path is disabled, unavailable or has failed. Platforms use
|
|
170
|
+
* it for setup that only pays off when that thread does the proving (web
|
|
171
|
+
* starts tfhe's rayon thread pool here).
|
|
172
|
+
*/
|
|
173
|
+
beforeMainThreadProve?: () => Promise<void>;
|
|
131
174
|
};
|
package/core/config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type CofheChain } from '@/chains';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { type WalletClient } from 'viem';
|
|
5
5
|
import { CofheError, CofheErrorCode } from './error.js';
|
|
6
|
-
import { type IStorage } from './types.js';
|
|
6
|
+
import { type IStorage, type TfheThreadsSetting } from './types.js';
|
|
7
7
|
|
|
8
8
|
export type CofheEnvironment = 'node' | 'hardhat' | 'web' | 'react';
|
|
9
9
|
|
|
@@ -29,6 +29,25 @@ export type CofheConfig = {
|
|
|
29
29
|
* Default: true
|
|
30
30
|
*/
|
|
31
31
|
useWorkers: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* How many threads tfhe's rayon pool may use for ZK proof generation
|
|
34
|
+
* (web platform only; ignored on node/hardhat).
|
|
35
|
+
*
|
|
36
|
+
* Proving dominates the cost of `encryptInputs`, and tfhe's wasm is built with
|
|
37
|
+
* rayon, so spreading it across threads is a large win — roughly 5x on an
|
|
38
|
+
* 8-thread pool.
|
|
39
|
+
*
|
|
40
|
+
* Requires a cross-origin-isolated page (`Cross-Origin-Opener-Policy: same-origin`
|
|
41
|
+
* plus `Cross-Origin-Embedder-Policy: require-corp` or `credentialless`). When the
|
|
42
|
+
* page isn't isolated the pool is skipped and proving stays single-threaded, so
|
|
43
|
+
* this is safe to leave on.
|
|
44
|
+
*
|
|
45
|
+
* `'auto'` derives the count from `navigator.hardwareConcurrency` (capped, since
|
|
46
|
+
* the speedup flattens out); a number requests exactly that many; `false` keeps
|
|
47
|
+
* tfhe single-threaded.
|
|
48
|
+
* Default: 'auto'
|
|
49
|
+
*/
|
|
50
|
+
tfheThreads: TfheThreadsSetting;
|
|
32
51
|
/** ACP acp defaults, applied when creating acps (user options always win) */
|
|
33
52
|
acp: {
|
|
34
53
|
/**
|
|
@@ -54,6 +73,13 @@ export type CofheConfig = {
|
|
|
54
73
|
* names a registry.
|
|
55
74
|
*/
|
|
56
75
|
sharingRegistry?: Record<number, `0x${string}`>;
|
|
76
|
+
/**
|
|
77
|
+
* The name of this app, e.g. 'explorer.fhenix.io'. Set, every share the client creates
|
|
78
|
+
* carries the app's key (`shareAppKey(app)`, kept in the share metadata), and
|
|
79
|
+
* `getIncomingShares` lists only the shares posted for this app. Unset, shares carry no
|
|
80
|
+
* key and every importable share is listed.
|
|
81
|
+
*/
|
|
82
|
+
app?: string;
|
|
57
83
|
};
|
|
58
84
|
/** Mocks configs */
|
|
59
85
|
mocks: {
|
|
@@ -108,12 +134,18 @@ export const CofheConfigSchema = z.object({
|
|
|
108
134
|
.default(null),
|
|
109
135
|
/** Whether to use Web Workers for ZK proof generation (web platform only) */
|
|
110
136
|
useWorkers: z.boolean().optional().default(true),
|
|
137
|
+
/** How many threads tfhe's rayon pool may use for ZK proof generation (web platform only) */
|
|
138
|
+
tfheThreads: z
|
|
139
|
+
.union([z.number().int().positive(), z.literal('auto'), z.literal(false)])
|
|
140
|
+
.optional()
|
|
141
|
+
.default('auto'),
|
|
111
142
|
/** ACP acp defaults */
|
|
112
143
|
acp: z
|
|
113
144
|
.object({
|
|
114
145
|
defaultRevoker: z.custom<Record<number, `0x${string}`>>().optional(),
|
|
115
146
|
defaultContractScopes: z.custom<Record<number, `0x${string}`[]>>().optional(),
|
|
116
147
|
sharingRegistry: z.custom<Record<number, `0x${string}`>>().optional(),
|
|
148
|
+
app: z.string().min(1).optional(),
|
|
117
149
|
})
|
|
118
150
|
.optional()
|
|
119
151
|
.default({}),
|
package/core/consts.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* CoFHEAddressBook contract address - the same on every chain. Resolves `TASK_MANAGER_ID` to the
|
|
3
|
+
* chain's Task Manager. Keep in sync with `COFHE_ADDRESS_BOOK` in `@fhenixprotocol/cofhe-contracts/FHE.sol`.
|
|
4
|
+
*/
|
|
5
|
+
export const COFHE_ADDRESS_BOOK_ADDRESS = '0xC0F4e00E531a2B086492Ae3DCC1515038307196b' as const;
|
|
6
|
+
|
|
7
|
+
/** Id of the Task Manager in the CoFHEAddressBook. Keep in sync with `TASK_MANAGER_ID` in FHE.sol */
|
|
8
|
+
export const TASK_MANAGER_ID = 1n;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mock Task Manager contract address (used for testing). Registered in the mock CoFHEAddressBook;
|
|
12
|
+
* SDK reads still resolve the Task Manager through the book.
|
|
13
|
+
*/
|
|
14
|
+
export const MOCKS_TASK_MANAGER_ADDRESS = '0x0000000000000000000000000000000000005000' as const;
|
|
3
15
|
|
|
4
16
|
/** Mock ZK Verifier contract address (used for testing) */
|
|
5
17
|
export const MOCKS_ZK_VERIFIER_ADDRESS = '0x0000000000000000000000000000000000005001' as const;
|
package/core/decrypt/apiError.ts
CHANGED
|
@@ -48,7 +48,10 @@ const BACKEND_ERROR_CODE_TO_COFHE_ERROR_CODE: Record<BackendApiErrorCode, CofheE
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
function isBackendApiErrorCode(value: string): value is BackendApiErrorCode {
|
|
51
|
-
|
|
51
|
+
// Own-property check, not `in`: `in` also matches Object.prototype keys, so codes like
|
|
52
|
+
// `toString` or `constructor` would be treated as recognised and the lookup would return a
|
|
53
|
+
// function instead of a CofheErrorCode.
|
|
54
|
+
return Object.prototype.hasOwnProperty.call(BACKEND_ERROR_CODE_TO_COFHE_ERROR_CODE, value);
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
export type ParsedApiError = {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type Hex,
|
|
10
10
|
type PublicClient,
|
|
11
11
|
} from 'viem';
|
|
12
|
-
import {
|
|
12
|
+
import { getTaskManagerAddress } from '../taskManager.js';
|
|
13
13
|
|
|
14
14
|
const decryptResultSignerAbi = parseAbi(['function decryptResultSigner() view returns (address)']);
|
|
15
15
|
const UINT_TYPE_MASK = 0x7fn;
|
|
@@ -30,10 +30,14 @@ const buildDecryptResultHash = (ctHash: bigint, cleartext: bigint, chainId: numb
|
|
|
30
30
|
*
|
|
31
31
|
* The recovered signer must equal the on-chain configured `decryptResultSigner`.
|
|
32
32
|
*
|
|
33
|
+
* Fails closed: if the chain has no `decryptResultSigner` set (zero address), this returns `false`.
|
|
34
|
+
* The on-chain `verifyDecryptResult` instead accepts any signature in that case.
|
|
35
|
+
*
|
|
33
36
|
* This mirrors the TaskManager decrypt-result hash format:
|
|
34
37
|
* `keccak256(abi.encodePacked(result, encType, chainId, ctHash))`
|
|
35
38
|
*
|
|
36
|
-
* The only on-chain
|
|
39
|
+
* The only on-chain reads performed are the Task Manager lookup in the CoFHEAddressBook (cached
|
|
40
|
+
* per chain) and `TaskManager.decryptResultSigner()` (via `eth_call`).
|
|
37
41
|
*
|
|
38
42
|
* Works with both production and mock deployments.
|
|
39
43
|
*/
|
|
@@ -45,13 +49,13 @@ export async function verifyDecryptResult(
|
|
|
45
49
|
): Promise<boolean> {
|
|
46
50
|
const chainId = publicClient.chain?.id ?? (await publicClient.getChainId());
|
|
47
51
|
const expectedSigner = await publicClient.readContract({
|
|
48
|
-
address:
|
|
52
|
+
address: await getTaskManagerAddress(publicClient, chainId),
|
|
49
53
|
abi: decryptResultSignerAbi,
|
|
50
54
|
functionName: 'decryptResultSigner',
|
|
51
55
|
args: [],
|
|
52
56
|
});
|
|
53
57
|
|
|
54
|
-
if (isAddressEqual(expectedSigner, zeroAddress)) return
|
|
58
|
+
if (isAddressEqual(expectedSigner, zeroAddress)) return false;
|
|
55
59
|
|
|
56
60
|
const ctHash = BigInt(handle);
|
|
57
61
|
const messageHash = buildDecryptResultHash(ctHash, cleartext, chainId);
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { type EncryptableItem, FheTypes } from '../types.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type VerifyBatchResult,
|
|
4
|
+
MAX_UINT8,
|
|
5
|
+
MAX_UINT16,
|
|
6
|
+
MAX_UINT32,
|
|
7
|
+
MAX_UINT64,
|
|
8
|
+
MAX_UINT128,
|
|
9
|
+
MAX_UINT160,
|
|
10
|
+
} from './zkPackProveVerify.js';
|
|
11
|
+
import { toBigIntOrThrow, validateBigIntInRange } from '../utils.js';
|
|
3
12
|
import { createWalletClient, http, encodePacked, keccak256, type PublicClient, type WalletClient } from 'viem';
|
|
4
13
|
import { MockZkVerifierAbi } from './MockZkVerifierAbi.js';
|
|
5
14
|
import { hardhat } from 'viem/chains';
|
|
@@ -19,7 +28,9 @@ function createMockZkVerifierSigner() {
|
|
|
19
28
|
});
|
|
20
29
|
}
|
|
21
30
|
/**
|
|
22
|
-
* The mocks don't use a tfhe builder, so we check the encryptable bits here to
|
|
31
|
+
* The mocks don't use a tfhe builder, so we check the encryptable bits and value ranges here to
|
|
32
|
+
* preserve parity with `zkPack`. Without the range check, MockCoFHE masks an out-of-range value
|
|
33
|
+
* with the utype mask (e.g. uint8(256) -> 0) instead of rejecting it like the production path.
|
|
23
34
|
*/
|
|
24
35
|
export async function cofheMocksCheckEncryptableBits(items: EncryptableItem[]): Promise<void> {
|
|
25
36
|
let totalBits = 0;
|
|
@@ -30,22 +41,27 @@ export async function cofheMocksCheckEncryptableBits(items: EncryptableItem[]):
|
|
|
30
41
|
break;
|
|
31
42
|
}
|
|
32
43
|
case FheTypes.Uint8: {
|
|
44
|
+
validateBigIntInRange(toBigIntOrThrow(item.data), MAX_UINT8);
|
|
33
45
|
totalBits += 8;
|
|
34
46
|
break;
|
|
35
47
|
}
|
|
36
48
|
case FheTypes.Uint16: {
|
|
49
|
+
validateBigIntInRange(toBigIntOrThrow(item.data), MAX_UINT16);
|
|
37
50
|
totalBits += 16;
|
|
38
51
|
break;
|
|
39
52
|
}
|
|
40
53
|
case FheTypes.Uint32: {
|
|
54
|
+
validateBigIntInRange(toBigIntOrThrow(item.data), MAX_UINT32);
|
|
41
55
|
totalBits += 32;
|
|
42
56
|
break;
|
|
43
57
|
}
|
|
44
58
|
case FheTypes.Uint64: {
|
|
59
|
+
validateBigIntInRange(toBigIntOrThrow(item.data), MAX_UINT64);
|
|
45
60
|
totalBits += 64;
|
|
46
61
|
break;
|
|
47
62
|
}
|
|
48
63
|
case FheTypes.Uint128: {
|
|
64
|
+
validateBigIntInRange(toBigIntOrThrow(item.data), MAX_UINT128);
|
|
49
65
|
totalBits += 128;
|
|
50
66
|
break;
|
|
51
67
|
}
|
|
@@ -55,6 +71,7 @@ export async function cofheMocksCheckEncryptableBits(items: EncryptableItem[]):
|
|
|
55
71
|
// break;
|
|
56
72
|
// }
|
|
57
73
|
case FheTypes.Uint160: {
|
|
74
|
+
validateBigIntInRange(toBigIntOrThrow(item.data), MAX_UINT160);
|
|
58
75
|
totalBits += 160;
|
|
59
76
|
break;
|
|
60
77
|
}
|
|
@@ -37,6 +37,7 @@ type EncryptInputsBuilderParams<T extends EncryptableItem[]> = BaseBuilderParams
|
|
|
37
37
|
zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator | undefined;
|
|
38
38
|
initTfhe: TfheInitializer | undefined;
|
|
39
39
|
zkProveWorkerFn: ZkProveWorkerFunction | undefined;
|
|
40
|
+
beforeMainThreadProve?: (() => Promise<void>) | undefined;
|
|
40
41
|
|
|
41
42
|
keysStorage: KeysStorage | undefined;
|
|
42
43
|
};
|
|
@@ -69,6 +70,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
69
70
|
private zkBuilderAndCrsGenerator: ZkBuilderAndCrsGenerator;
|
|
70
71
|
private initTfhe: TfheInitializer | undefined;
|
|
71
72
|
private zkProveWorkerFn: ZkProveWorkerFunction | undefined;
|
|
73
|
+
private beforeMainThreadProve: (() => Promise<void>) | undefined;
|
|
72
74
|
|
|
73
75
|
private keysStorage: KeysStorage | undefined;
|
|
74
76
|
|
|
@@ -143,6 +145,9 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
143
145
|
// Optional zkProve worker function, will be used on web if useWorkers is true and worker function is provided
|
|
144
146
|
this.zkProveWorkerFn = params.zkProveWorkerFn;
|
|
145
147
|
|
|
148
|
+
// Optional platform setup that only matters when this thread generates the proof
|
|
149
|
+
this.beforeMainThreadProve = params.beforeMainThreadProve;
|
|
150
|
+
|
|
146
151
|
// Keys storage is used to store the FHE key and CRS
|
|
147
152
|
this.keysStorage = params.keysStorage;
|
|
148
153
|
|
|
@@ -582,6 +587,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
582
587
|
|
|
583
588
|
if (proof == null) {
|
|
584
589
|
// Use main thread directly (workers disabled or unavailable)
|
|
590
|
+
await this.beforeMainThreadProve?.();
|
|
585
591
|
proof = await zkProve(zkBuilder, zkCrs, metadata);
|
|
586
592
|
usedWorker = false;
|
|
587
593
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TFHE_RS_ZK_MAX_BITS, TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT } from '../consts';
|
|
2
2
|
import { CofheError, CofheErrorCode } from '../error';
|
|
3
|
-
import { type EncryptableItem, FheTypes } from '../types';
|
|
3
|
+
import { type EncryptableItem, FheTypes, type TfheThreadsSetting } from '../types';
|
|
4
4
|
import { toBigIntOrThrow, validateBigIntInRange, toHexString, hexToBytes } from '../utils';
|
|
5
5
|
|
|
6
6
|
// ===== TYPES =====
|
|
@@ -29,6 +29,12 @@ export interface ZkProveWorkerRequest {
|
|
|
29
29
|
data: any;
|
|
30
30
|
}>;
|
|
31
31
|
metadata: number[]; // Uint8Array serialized as array
|
|
32
|
+
/**
|
|
33
|
+
* Forwarded from `config.tfheThreads`. The worker applies it the first time it
|
|
34
|
+
* initialises tfhe — `initThreadPool` can only run once per wasm instance, so
|
|
35
|
+
* later requests carrying a different value have no effect.
|
|
36
|
+
*/
|
|
37
|
+
tfheThreads?: TfheThreadsSetting;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
/**
|
package/core/error.ts
CHANGED
|
@@ -43,6 +43,7 @@ export enum CofheErrorCode {
|
|
|
43
43
|
PublicWalletGetChainIdFailed = 'PUBLIC_WALLET_GET_CHAIN_ID_FAILED',
|
|
44
44
|
PublicWalletGetAddressesFailed = 'PUBLIC_WALLET_GET_ADDRESSES_FAILED',
|
|
45
45
|
RehydrateKeysStoreFailed = 'REHYDRATE_KEYS_STORE_FAILED',
|
|
46
|
+
TaskManagerUnresolved = 'TASK_MANAGER_UNRESOLVED',
|
|
46
47
|
|
|
47
48
|
// Threshold-network stable error codes (decrypt/sealoutput), see API-RESPONSES.md
|
|
48
49
|
BadRequest = 'BAD_REQUEST',
|
package/core/fetchKeys.ts
CHANGED
|
@@ -77,7 +77,7 @@ const fetchCrs = async (
|
|
|
77
77
|
keysStorage?: KeysStorage | null
|
|
78
78
|
): Promise<[string, boolean]> => {
|
|
79
79
|
// Escape if key already exists
|
|
80
|
-
const storedKey = keysStorage?.getCrs(chainId);
|
|
80
|
+
const storedKey = keysStorage?.getCrs(chainId, securityZone);
|
|
81
81
|
const [storedKeyValid] = checkKeyValidity(storedKey, compactPkeCrsDeserializer);
|
|
82
82
|
if (storedKeyValid) return [storedKey!, false];
|
|
83
83
|
|
|
@@ -108,7 +108,7 @@ const fetchCrs = async (
|
|
|
108
108
|
throw new Error(`Error serializing CRS; ${err}`);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
keysStorage?.setCrs(chainId, crs_data);
|
|
111
|
+
keysStorage?.setCrs(chainId, securityZone, crs_data);
|
|
112
112
|
|
|
113
113
|
return [crs_data, true];
|
|
114
114
|
};
|
package/core/index.ts
CHANGED
|
@@ -62,6 +62,9 @@ export type {
|
|
|
62
62
|
// Util types
|
|
63
63
|
EncryptStepCallbackFunction as EncryptSetStateFn,
|
|
64
64
|
EncryptStepCallbackContext,
|
|
65
|
+
// tfhe init types
|
|
66
|
+
TfheInitializer,
|
|
67
|
+
TfheThreadsSetting,
|
|
65
68
|
} from './types.js';
|
|
66
69
|
export {
|
|
67
70
|
FheTypes,
|
|
@@ -103,7 +106,9 @@ export { zkProveWithWorker } from './encrypt/zkPackProveVerify.js';
|
|
|
103
106
|
|
|
104
107
|
// Consts (contract addresses, serialized size limits)
|
|
105
108
|
export {
|
|
106
|
-
|
|
109
|
+
COFHE_ADDRESS_BOOK_ADDRESS,
|
|
110
|
+
TASK_MANAGER_ID,
|
|
111
|
+
MOCKS_TASK_MANAGER_ADDRESS,
|
|
107
112
|
MOCKS_ZK_VERIFIER_ADDRESS,
|
|
108
113
|
MOCKS_ZK_VERIFIER_SIGNER_ADDRESS,
|
|
109
114
|
MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
|
|
@@ -113,6 +118,9 @@ export {
|
|
|
113
118
|
TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT,
|
|
114
119
|
} from './consts.js';
|
|
115
120
|
|
|
121
|
+
// Task Manager resolution (CoFHEAddressBook)
|
|
122
|
+
export { getTaskManagerAddress } from './taskManager.js';
|
|
123
|
+
|
|
116
124
|
// Decrypt result verification
|
|
117
125
|
export { verifyDecryptResult } from './decrypt/verifyDecryptResult.js';
|
|
118
126
|
|
package/core/keyStore.ts
CHANGED
|
@@ -11,15 +11,15 @@ type SecurityZoneRecord<T> = Record<number, T>;
|
|
|
11
11
|
// Keys store for FHE keys and CRS
|
|
12
12
|
export type KeysStore = {
|
|
13
13
|
fhe: ChainRecord<SecurityZoneRecord<string | undefined>>;
|
|
14
|
-
crs: ChainRecord<string | undefined
|
|
14
|
+
crs: ChainRecord<SecurityZoneRecord<string | undefined>>;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export type KeysStorage = {
|
|
18
18
|
store: StoreApi<KeysStore>;
|
|
19
19
|
getFheKey: (chainId: number | undefined, securityZone?: number) => string | undefined;
|
|
20
|
-
getCrs: (chainId: number | undefined) => string | undefined;
|
|
20
|
+
getCrs: (chainId: number | undefined, securityZone?: number) => string | undefined;
|
|
21
21
|
setFheKey: (chainId: number, securityZone: number, key: string) => void;
|
|
22
|
-
setCrs: (chainId: number, crs: string) => void;
|
|
22
|
+
setCrs: (chainId: number, securityZone: number, crs: string) => void;
|
|
23
23
|
clearKeysStorage: () => Promise<void>;
|
|
24
24
|
rehydrateKeysStore: () => Promise<void>;
|
|
25
25
|
};
|
|
@@ -45,6 +45,10 @@ const DEFAULT_KEYS_STORE: KeysStore = {
|
|
|
45
45
|
crs: {},
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
function isSecurityZoneRecord(value: unknown): value is SecurityZoneRecord<string | undefined> {
|
|
49
|
+
return value != null && typeof value === 'object';
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
type StoreWithPersist = ReturnType<typeof createStoreWithPersit>;
|
|
49
53
|
|
|
50
54
|
function isStoreWithPersist(store: StoreApi<KeysStore> | StoreWithPersist): store is StoreWithPersist {
|
|
@@ -72,9 +76,9 @@ export function createKeysStore(storage: IStorage | null): KeysStorage {
|
|
|
72
76
|
return stored;
|
|
73
77
|
};
|
|
74
78
|
|
|
75
|
-
const getCrs = (chainId: number | undefined) => {
|
|
76
|
-
if (chainId == null) return undefined;
|
|
77
|
-
const stored = keysStore.getState().crs[chainId];
|
|
79
|
+
const getCrs = (chainId: number | undefined, securityZone = 0) => {
|
|
80
|
+
if (chainId == null || securityZone == null) return undefined;
|
|
81
|
+
const stored = keysStore.getState().crs[chainId]?.[securityZone];
|
|
78
82
|
return stored;
|
|
79
83
|
};
|
|
80
84
|
|
|
@@ -87,10 +91,11 @@ export function createKeysStore(storage: IStorage | null): KeysStorage {
|
|
|
87
91
|
);
|
|
88
92
|
};
|
|
89
93
|
|
|
90
|
-
const setCrs = (chainId: number, crs: string) => {
|
|
94
|
+
const setCrs = (chainId: number, securityZone: number, crs: string) => {
|
|
91
95
|
keysStore.setState(
|
|
92
96
|
produce<KeysStore>((state: KeysStore) => {
|
|
93
|
-
state.crs[chainId] =
|
|
97
|
+
if (state.crs[chainId] == null) state.crs[chainId] = {};
|
|
98
|
+
state.crs[chainId][securityZone] = crs;
|
|
94
99
|
})
|
|
95
100
|
);
|
|
96
101
|
};
|
|
@@ -143,8 +148,16 @@ function createStoreWithPersit(storage: IStorage) {
|
|
|
143
148
|
mergedFhe[chainId] = { ...persistedZones, ...currentZones };
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
// Deep merge for crs
|
|
147
|
-
|
|
151
|
+
// Deep merge for crs. A `crs` persisted before it was keyed by security zone holds a
|
|
152
|
+
// bare string per chain; those entries are dropped so the CRS is refetched per zone
|
|
153
|
+
// rather than indexed as a string.
|
|
154
|
+
const mergedCrs: KeysStore['crs'] = {};
|
|
155
|
+
const allCrsChainIds = new Set([...Object.keys(current.crs), ...Object.keys(persisted.crs)]);
|
|
156
|
+
for (const chainId of allCrsChainIds) {
|
|
157
|
+
const persistedZones = isSecurityZoneRecord(persisted.crs[chainId]) ? persisted.crs[chainId] : {};
|
|
158
|
+
const currentZones = isSecurityZoneRecord(current.crs[chainId]) ? current.crs[chainId] : {};
|
|
159
|
+
mergedCrs[chainId] = { ...persistedZones, ...currentZones };
|
|
160
|
+
}
|
|
148
161
|
|
|
149
162
|
return {
|
|
150
163
|
fhe: mergedFhe,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parseAbi, type Address, type PublicClient } from 'viem';
|
|
2
|
+
|
|
3
|
+
import { COFHE_ADDRESS_BOOK_ADDRESS, TASK_MANAGER_ID } from './consts.js';
|
|
4
|
+
import { CofheError, CofheErrorCode } from './error.js';
|
|
5
|
+
|
|
6
|
+
const ADDRESS_BOOK_ABI = parseAbi([
|
|
7
|
+
'function getTm(uint256 id) view returns (address)',
|
|
8
|
+
'error TaskManagerNotSet(uint256 id)',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const taskManagerAddressCache = new Map<number, Promise<Address>>();
|
|
12
|
+
|
|
13
|
+
/** Test hook: forget resolved addresses (e.g. between redeployments on one chainId). */
|
|
14
|
+
export const clearTaskManagerAddressCache = () => taskManagerAddressCache.clear();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The chain's Task Manager, resolved through the CoFHEAddressBook
|
|
18
|
+
* (`getTm(TASK_MANAGER_ID)`) - the same lookup FHE.sol performs on every call.
|
|
19
|
+
*
|
|
20
|
+
* Resolutions are cached per chainId. A failed resolution (network error, no
|
|
21
|
+
* CoFHE deployment, id unset) is NOT cached, so a later call retries.
|
|
22
|
+
*/
|
|
23
|
+
export const getTaskManagerAddress = async (publicClient: PublicClient, chainId?: number): Promise<Address> => {
|
|
24
|
+
const id = chainId ?? publicClient.chain?.id ?? (await publicClient.getChainId());
|
|
25
|
+
|
|
26
|
+
const cached = taskManagerAddressCache.get(id);
|
|
27
|
+
if (cached != null) return cached;
|
|
28
|
+
|
|
29
|
+
const pending = publicClient
|
|
30
|
+
.readContract({
|
|
31
|
+
address: COFHE_ADDRESS_BOOK_ADDRESS,
|
|
32
|
+
abi: ADDRESS_BOOK_ABI,
|
|
33
|
+
functionName: 'getTm',
|
|
34
|
+
args: [TASK_MANAGER_ID],
|
|
35
|
+
})
|
|
36
|
+
.catch((error: unknown) => {
|
|
37
|
+
taskManagerAddressCache.delete(id);
|
|
38
|
+
throw CofheError.fromError(error, {
|
|
39
|
+
code: CofheErrorCode.TaskManagerUnresolved,
|
|
40
|
+
message: `Failed to resolve the Task Manager from the CoFHEAddressBook on chain ${id}`,
|
|
41
|
+
hint: 'Ensure CoFHE is deployed on this chain. On a local hardhat / anvil chain, deploy the CoFHE mocks first.',
|
|
42
|
+
context: { chainId: id, addressBook: COFHE_ADDRESS_BOOK_ADDRESS, taskManagerId: TASK_MANAGER_ID },
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
taskManagerAddressCache.set(id, pending);
|
|
47
|
+
return pending;
|
|
48
|
+
};
|