@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.
Files changed (121) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/adapters/ethers5.test.ts +174 -0
  3. package/adapters/ethers5.ts +36 -0
  4. package/adapters/ethers6.test.ts +169 -0
  5. package/adapters/ethers6.ts +36 -0
  6. package/adapters/hardhat-node.ts +167 -0
  7. package/adapters/hardhat.hh2.test.ts +159 -0
  8. package/adapters/hardhat.ts +36 -0
  9. package/adapters/index.test.ts +20 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +99 -0
  12. package/adapters/test-utils.ts +53 -0
  13. package/adapters/types.ts +6 -0
  14. package/adapters/wagmi.test.ts +156 -0
  15. package/adapters/wagmi.ts +17 -0
  16. package/chains/chains/arbSepolia.ts +14 -0
  17. package/chains/chains/baseSepolia.ts +14 -0
  18. package/chains/chains/hardhat.ts +15 -0
  19. package/chains/chains/localcofhe.ts +14 -0
  20. package/chains/chains/sepolia.ts +14 -0
  21. package/chains/chains.test.ts +50 -0
  22. package/chains/defineChain.ts +18 -0
  23. package/chains/index.ts +35 -0
  24. package/chains/types.ts +32 -0
  25. package/core/baseBuilder.ts +119 -0
  26. package/core/client.test.ts +315 -0
  27. package/core/client.ts +292 -0
  28. package/core/clientTypes.ts +108 -0
  29. package/core/config.test.ts +235 -0
  30. package/core/config.ts +220 -0
  31. package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
  32. package/core/decrypt/cofheMocksSealOutput.ts +57 -0
  33. package/core/decrypt/decryptHandleBuilder.ts +287 -0
  34. package/core/decrypt/decryptUtils.ts +28 -0
  35. package/core/decrypt/tnSealOutputV1.ts +59 -0
  36. package/core/decrypt/tnSealOutputV2.ts +298 -0
  37. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  38. package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
  39. package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
  40. package/core/encrypt/encryptInputsBuilder.ts +560 -0
  41. package/core/encrypt/encryptUtils.ts +67 -0
  42. package/core/encrypt/zkPackProveVerify.ts +335 -0
  43. package/core/error.ts +168 -0
  44. package/core/fetchKeys.test.ts +195 -0
  45. package/core/fetchKeys.ts +144 -0
  46. package/core/index.ts +89 -0
  47. package/core/keyStore.test.ts +226 -0
  48. package/core/keyStore.ts +154 -0
  49. package/core/permits.test.ts +494 -0
  50. package/core/permits.ts +200 -0
  51. package/core/types.ts +398 -0
  52. package/core/utils.ts +130 -0
  53. package/dist/adapters.cjs +88 -0
  54. package/dist/adapters.d.cts +14576 -0
  55. package/dist/adapters.d.ts +14576 -0
  56. package/dist/adapters.js +83 -0
  57. package/dist/chains.cjs +114 -0
  58. package/dist/chains.d.cts +121 -0
  59. package/dist/chains.d.ts +121 -0
  60. package/dist/chains.js +1 -0
  61. package/dist/chunk-UGBVZNRT.js +818 -0
  62. package/dist/chunk-WEAZ25JO.js +105 -0
  63. package/dist/chunk-WGCRJCBR.js +2523 -0
  64. package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
  65. package/dist/clientTypes-Es7fyi65.d.ts +914 -0
  66. package/dist/core.cjs +3414 -0
  67. package/dist/core.d.cts +111 -0
  68. package/dist/core.d.ts +111 -0
  69. package/dist/core.js +3 -0
  70. package/dist/node.cjs +3286 -0
  71. package/dist/node.d.cts +22 -0
  72. package/dist/node.d.ts +22 -0
  73. package/dist/node.js +91 -0
  74. package/dist/permit-fUSe6KKq.d.cts +349 -0
  75. package/dist/permit-fUSe6KKq.d.ts +349 -0
  76. package/dist/permits.cjs +871 -0
  77. package/dist/permits.d.cts +1045 -0
  78. package/dist/permits.d.ts +1045 -0
  79. package/dist/permits.js +1 -0
  80. package/dist/types-KImPrEIe.d.cts +48 -0
  81. package/dist/types-KImPrEIe.d.ts +48 -0
  82. package/dist/web.cjs +3478 -0
  83. package/dist/web.d.cts +38 -0
  84. package/dist/web.d.ts +38 -0
  85. package/dist/web.js +240 -0
  86. package/dist/zkProve.worker.cjs +93 -0
  87. package/dist/zkProve.worker.d.cts +2 -0
  88. package/dist/zkProve.worker.d.ts +2 -0
  89. package/dist/zkProve.worker.js +91 -0
  90. package/node/client.test.ts +147 -0
  91. package/node/config.test.ts +68 -0
  92. package/node/encryptInputs.test.ts +155 -0
  93. package/node/index.ts +97 -0
  94. package/node/storage.ts +51 -0
  95. package/package.json +27 -15
  96. package/permits/index.ts +68 -0
  97. package/permits/localstorage.test.ts +117 -0
  98. package/permits/permit.test.ts +477 -0
  99. package/permits/permit.ts +405 -0
  100. package/permits/sealing.test.ts +84 -0
  101. package/permits/sealing.ts +131 -0
  102. package/permits/signature.ts +79 -0
  103. package/permits/store.test.ts +128 -0
  104. package/permits/store.ts +166 -0
  105. package/permits/test-utils.ts +20 -0
  106. package/permits/types.ts +191 -0
  107. package/permits/utils.ts +62 -0
  108. package/permits/validation.test.ts +288 -0
  109. package/permits/validation.ts +369 -0
  110. package/web/client.web.test.ts +147 -0
  111. package/web/config.web.test.ts +69 -0
  112. package/web/encryptInputs.web.test.ts +172 -0
  113. package/web/index.ts +161 -0
  114. package/web/storage.ts +34 -0
  115. package/web/worker.builder.web.test.ts +148 -0
  116. package/web/worker.config.web.test.ts +329 -0
  117. package/web/worker.output.web.test.ts +84 -0
  118. package/web/workerManager.test.ts +80 -0
  119. package/web/workerManager.ts +214 -0
  120. package/web/workerManager.web.test.ts +114 -0
  121. package/web/zkProve.worker.ts +133 -0
package/core/config.ts ADDED
@@ -0,0 +1,220 @@
1
+ import { type CofheChain } from '@/chains';
2
+
3
+ import { z } from 'zod';
4
+ import { type WalletClient } from 'viem';
5
+ import { CofhesdkError, CofhesdkErrorCode } from './error.js';
6
+ import { type IStorage } from './types.js';
7
+
8
+ export type CofhesdkEnvironment = 'node' | 'hardhat' | 'web' | 'react';
9
+
10
+ /**
11
+ * Usable config type inferred from the schema
12
+ */
13
+ export type CofhesdkConfig = {
14
+ /** Environment that the SDK is running in */
15
+ environment: 'node' | 'hardhat' | 'web' | 'react';
16
+ /** List of supported chains */
17
+ supportedChains: CofheChain[];
18
+ /**
19
+ * How permits are generated
20
+ * - ON_CONNECT: Generate a permit when client.connect() is called
21
+ * - ON_DECRYPT_HANDLES: Generate a permit when client.decryptHandles() is called
22
+ * - MANUAL: Generate a permit manually using client.generatePermit()
23
+ */
24
+ permitGeneration: 'ON_CONNECT' | 'ON_DECRYPT_HANDLES' | 'MANUAL';
25
+ /** Default permit expiration in seconds, default is 30 days */
26
+ defaultPermitExpiration: number;
27
+ /**
28
+ * Storage scheme for the fetched fhe keys
29
+ * FHE keys are large, and caching prevents re-fetching them on each encryptInputs call
30
+ * (defaults to indexedDB on web, filesystem on node)
31
+ */
32
+ fheKeyStorage: IStorage | null;
33
+ /**
34
+ * Whether to use Web Workers for ZK proof generation (web platform only)
35
+ * When enabled, heavy WASM computation is offloaded to prevent UI freezing
36
+ * Default: true
37
+ */
38
+ useWorkers: boolean;
39
+ /** Mocks configs */
40
+ mocks: {
41
+ /**
42
+ * Length of the simulated seal output delay in milliseconds
43
+ * Default 1000ms on web
44
+ * Default 0ms on hardhat (will be called during tests no need for fake delay)
45
+ */
46
+ sealOutputDelay: number;
47
+ };
48
+ _internal?: CofhesdkInternalConfig;
49
+ };
50
+
51
+ export type CofhesdkInternalConfig = {
52
+ zkvWalletClient?: WalletClient;
53
+ };
54
+
55
+ /**
56
+ * Zod schema for configuration validation
57
+ */
58
+ export const CofhesdkConfigSchema = z.object({
59
+ /** Environment that the SDK is running in */
60
+ environment: z.enum(['node', 'hardhat', 'web', 'react']).optional().default('node'),
61
+ /** List of supported chain configurations */
62
+ supportedChains: z.array(z.custom<CofheChain>()),
63
+ /** How permits are generated */
64
+ permitGeneration: z.enum(['ON_CONNECT', 'ON_DECRYPT_HANDLES', 'MANUAL']).optional().default('ON_CONNECT'),
65
+ /** Default permit expiration in seconds, default is 30 days */
66
+ defaultPermitExpiration: z
67
+ .number()
68
+ .optional()
69
+ .default(60 * 60 * 24 * 30),
70
+ /** Storage method for fhe keys (defaults to indexedDB on web, filesystem on node) */
71
+ fheKeyStorage: z
72
+ .object({
73
+ getItem: z.function().args(z.string()).returns(z.promise(z.any())),
74
+ setItem: z.function().args(z.string(), z.any()).returns(z.promise(z.void())),
75
+ removeItem: z.function().args(z.string()).returns(z.promise(z.void())),
76
+ })
77
+ .or(z.null())
78
+ .default(null),
79
+ /** Whether to use Web Workers for ZK proof generation (web platform only) */
80
+ useWorkers: z.boolean().optional().default(true),
81
+ /** Mocks configs */
82
+ mocks: z
83
+ .object({
84
+ sealOutputDelay: z.number().optional().default(0),
85
+ })
86
+ .optional()
87
+ .default({ sealOutputDelay: 0 }),
88
+ /** Internal configuration */
89
+ _internal: z
90
+ .object({
91
+ zkvWalletClient: z.any().optional(),
92
+ })
93
+ .optional(),
94
+ });
95
+
96
+ /**
97
+ * Input config type inferred from the schema
98
+ */
99
+ export type CofhesdkInputConfig = z.input<typeof CofhesdkConfigSchema>;
100
+
101
+ /**
102
+ * Creates and validates a cofhesdk configuration (base implementation)
103
+ * @param config - The configuration object to validate
104
+ * @returns The validated configuration
105
+ * @throws {Error} If the configuration is invalid
106
+ */
107
+ export function createCofhesdkConfigBase(config: CofhesdkInputConfig): CofhesdkConfig {
108
+ const result = CofhesdkConfigSchema.safeParse(config);
109
+
110
+ if (!result.success) {
111
+ throw new Error(`Invalid cofhesdk configuration: ${result.error.message}`);
112
+ }
113
+
114
+ return result.data;
115
+ }
116
+
117
+ /**
118
+ * Access the CofhesdkConfig object directly by providing the key.
119
+ * This is powerful when you use OnchainKit utilities outside of the React context.
120
+ */
121
+ export const getCofhesdkConfigItem = <K extends keyof CofhesdkConfig>(
122
+ config: CofhesdkConfig,
123
+ key: K
124
+ ): CofhesdkConfig[K] => {
125
+ return config[key];
126
+ };
127
+
128
+ /**
129
+ * Gets a supported chain from config by chainId, throws if not found
130
+ * @param config - The cofhesdk configuration
131
+ * @param chainId - The chain ID to look up
132
+ * @returns The supported chain configuration
133
+ * @throws {CofhesdkError} If the chain is not found in the config
134
+ */
135
+ export function getSupportedChainOrThrow(config: CofhesdkConfig, chainId: number): CofheChain {
136
+ const supportedChain = config.supportedChains.find((chain) => chain.id === chainId);
137
+
138
+ if (!supportedChain) {
139
+ throw new CofhesdkError({
140
+ code: CofhesdkErrorCode.UnsupportedChain,
141
+ message: `Config does not support chain <${chainId}>`,
142
+ hint: 'Ensure config passed to client has been created with this chain in the config.supportedChains array.',
143
+ context: {
144
+ chainId,
145
+ supportedChainIds: config.supportedChains.map((c) => c.id),
146
+ },
147
+ });
148
+ }
149
+
150
+ return supportedChain;
151
+ }
152
+
153
+ /**
154
+ * Gets the CoFHE URL for a chain, throws if not found
155
+ * @param config - The cofhesdk configuration
156
+ * @param chainId - The chain ID to look up
157
+ * @returns The CoFHE URL for the chain
158
+ * @throws {CofhesdkError} If the chain or URL is not found
159
+ */
160
+ export function getCoFheUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
161
+ const supportedChain = getSupportedChainOrThrow(config, chainId);
162
+ const url = supportedChain.coFheUrl;
163
+
164
+ if (!url) {
165
+ throw new CofhesdkError({
166
+ code: CofhesdkErrorCode.MissingConfig,
167
+ message: `CoFHE URL is not configured for chain <${chainId}>`,
168
+ hint: 'Ensure this chain config includes a coFheUrl property.',
169
+ context: { chainId },
170
+ });
171
+ }
172
+
173
+ return url;
174
+ }
175
+
176
+ /**
177
+ * Gets the ZK verifier URL for a chain, throws if not found
178
+ * @param config - The cofhesdk configuration
179
+ * @param chainId - The chain ID to look up
180
+ * @returns The ZK verifier URL for the chain
181
+ * @throws {CofhesdkError} If the chain or URL is not found
182
+ */
183
+ export function getZkVerifierUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
184
+ const supportedChain = getSupportedChainOrThrow(config, chainId);
185
+ const url = supportedChain.verifierUrl;
186
+
187
+ if (!url) {
188
+ throw new CofhesdkError({
189
+ code: CofhesdkErrorCode.ZkVerifierUrlUninitialized,
190
+ message: `ZK verifier URL is not configured for chain <${chainId}>`,
191
+ hint: 'Ensure this chain config includes a verifierUrl property.',
192
+ context: { chainId },
193
+ });
194
+ }
195
+
196
+ return url;
197
+ }
198
+
199
+ /**
200
+ * Gets the threshold network URL for a chain, throws if not found
201
+ * @param config - The cofhesdk configuration
202
+ * @param chainId - The chain ID to look up
203
+ * @returns The threshold network URL for the chain
204
+ * @throws {CofhesdkError} If the chain or URL is not found
205
+ */
206
+ export function getThresholdNetworkUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
207
+ const supportedChain = getSupportedChainOrThrow(config, chainId);
208
+ const url = supportedChain.thresholdNetworkUrl;
209
+
210
+ if (!url) {
211
+ throw new CofhesdkError({
212
+ code: CofhesdkErrorCode.ThresholdNetworkUrlUninitialized,
213
+ message: `Threshold network URL is not configured for chain <${chainId}>`,
214
+ hint: 'Ensure this chain config includes a thresholdNetworkUrl property.',
215
+ context: { chainId },
216
+ });
217
+ }
218
+
219
+ return url;
220
+ }
@@ -0,0 +1,129 @@
1
+ export const MockQueryDecrypterAbi = [
2
+ {
3
+ type: 'function',
4
+ name: 'acl',
5
+ inputs: [],
6
+ outputs: [{ name: '', type: 'address', internalType: 'contract ACL' }],
7
+ stateMutability: 'view',
8
+ },
9
+ {
10
+ type: 'function',
11
+ name: 'decodeLowLevelReversion',
12
+ inputs: [{ name: 'data', type: 'bytes', internalType: 'bytes' }],
13
+ outputs: [{ name: 'error', type: 'string', internalType: 'string' }],
14
+ stateMutability: 'pure',
15
+ },
16
+ {
17
+ type: 'function',
18
+ name: 'exists',
19
+ inputs: [],
20
+ outputs: [{ name: '', type: 'bool', internalType: 'bool' }],
21
+ stateMutability: 'pure',
22
+ },
23
+ {
24
+ type: 'function',
25
+ name: 'initialize',
26
+ inputs: [
27
+ { name: '_taskManager', type: 'address', internalType: 'address' },
28
+ { name: '_acl', type: 'address', internalType: 'address' },
29
+ ],
30
+ outputs: [],
31
+ stateMutability: 'nonpayable',
32
+ },
33
+ {
34
+ type: 'function',
35
+ name: 'queryDecrypt',
36
+ inputs: [
37
+ { name: 'ctHash', type: 'uint256', internalType: 'uint256' },
38
+ { name: '', type: 'uint256', internalType: 'uint256' },
39
+ {
40
+ name: 'permission',
41
+ type: 'tuple',
42
+ internalType: 'struct Permission',
43
+ components: [
44
+ { name: 'issuer', type: 'address', internalType: 'address' },
45
+ { name: 'expiration', type: 'uint64', internalType: 'uint64' },
46
+ { name: 'recipient', type: 'address', internalType: 'address' },
47
+ { name: 'validatorId', type: 'uint256', internalType: 'uint256' },
48
+ {
49
+ name: 'validatorContract',
50
+ type: 'address',
51
+ internalType: 'address',
52
+ },
53
+ { name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
54
+ { name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
55
+ { name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
56
+ ],
57
+ },
58
+ ],
59
+ outputs: [
60
+ { name: 'allowed', type: 'bool', internalType: 'bool' },
61
+ { name: 'error', type: 'string', internalType: 'string' },
62
+ { name: '', type: 'uint256', internalType: 'uint256' },
63
+ ],
64
+ stateMutability: 'view',
65
+ },
66
+ {
67
+ type: 'function',
68
+ name: 'querySealOutput',
69
+ inputs: [
70
+ { name: 'ctHash', type: 'uint256', internalType: 'uint256' },
71
+ { name: '', type: 'uint256', internalType: 'uint256' },
72
+ {
73
+ name: 'permission',
74
+ type: 'tuple',
75
+ internalType: 'struct Permission',
76
+ components: [
77
+ { name: 'issuer', type: 'address', internalType: 'address' },
78
+ { name: 'expiration', type: 'uint64', internalType: 'uint64' },
79
+ { name: 'recipient', type: 'address', internalType: 'address' },
80
+ { name: 'validatorId', type: 'uint256', internalType: 'uint256' },
81
+ {
82
+ name: 'validatorContract',
83
+ type: 'address',
84
+ internalType: 'address',
85
+ },
86
+ { name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
87
+ { name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
88
+ { name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
89
+ ],
90
+ },
91
+ ],
92
+ outputs: [
93
+ { name: 'allowed', type: 'bool', internalType: 'bool' },
94
+ { name: 'error', type: 'string', internalType: 'string' },
95
+ { name: '', type: 'bytes32', internalType: 'bytes32' },
96
+ ],
97
+ stateMutability: 'view',
98
+ },
99
+ {
100
+ type: 'function',
101
+ name: 'seal',
102
+ inputs: [
103
+ { name: 'input', type: 'uint256', internalType: 'uint256' },
104
+ { name: 'key', type: 'bytes32', internalType: 'bytes32' },
105
+ ],
106
+ outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }],
107
+ stateMutability: 'pure',
108
+ },
109
+ {
110
+ type: 'function',
111
+ name: 'taskManager',
112
+ inputs: [],
113
+ outputs: [{ name: '', type: 'address', internalType: 'contract TaskManager' }],
114
+ stateMutability: 'view',
115
+ },
116
+ {
117
+ type: 'function',
118
+ name: 'unseal',
119
+ inputs: [
120
+ { name: 'hashed', type: 'bytes32', internalType: 'bytes32' },
121
+ { name: 'key', type: 'bytes32', internalType: 'bytes32' },
122
+ ],
123
+ outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }],
124
+ stateMutability: 'pure',
125
+ },
126
+ { type: 'error', name: 'NotAllowed', inputs: [] },
127
+ { type: 'error', name: 'SealingKeyInvalid', inputs: [] },
128
+ { type: 'error', name: 'SealingKeyMissing', inputs: [] },
129
+ ] as const;
@@ -0,0 +1,57 @@
1
+ import { type Permit, PermitUtils } from '@/permits';
2
+
3
+ import { type PublicClient } from 'viem';
4
+ import { sleep } from '../utils.js';
5
+ import { MockQueryDecrypterAbi } from './MockQueryDecrypterAbi.js';
6
+ import { FheTypes } from '../types.js';
7
+ import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
8
+
9
+ // Address the Mock Query Decrypter contract is deployed to on the Hardhat chain
10
+ export const MockQueryDecrypterAddress = '0x0000000000000000000000000000000000000200';
11
+
12
+ export async function cofheMocksSealOutput(
13
+ ctHash: bigint,
14
+ utype: FheTypes,
15
+ permit: Permit,
16
+ publicClient: PublicClient,
17
+ mocksSealOutputDelay: number
18
+ ): Promise<bigint> {
19
+ // Configurable delay before decrypting the output to simulate the CoFHE decrypt processing time
20
+ // Recommended 1000ms on web
21
+ // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
22
+ if (mocksSealOutputDelay > 0) await sleep(mocksSealOutputDelay);
23
+
24
+ const permission = PermitUtils.getPermission(permit, true);
25
+ const permissionWithBigInts = {
26
+ ...permission,
27
+ expiration: BigInt(permission.expiration),
28
+ validatorId: BigInt(permission.validatorId),
29
+ };
30
+
31
+ const [allowed, error, result] = await publicClient.readContract({
32
+ address: MockQueryDecrypterAddress,
33
+ abi: MockQueryDecrypterAbi,
34
+ functionName: 'querySealOutput',
35
+ args: [ctHash, BigInt(utype), permissionWithBigInts],
36
+ });
37
+
38
+ if (error != '') {
39
+ throw new CofhesdkError({
40
+ code: CofhesdkErrorCode.SealOutputFailed,
41
+ message: `mocks querySealOutput call failed: ${error}`,
42
+ });
43
+ }
44
+
45
+ if (allowed == false) {
46
+ throw new CofhesdkError({
47
+ code: CofhesdkErrorCode.SealOutputFailed,
48
+ message: `mocks querySealOutput call failed: ACL Access Denied (NotAllowed)`,
49
+ });
50
+ }
51
+
52
+ const sealedBigInt = BigInt(result);
53
+ const sealingKeyBigInt = BigInt(permission.sealingKey);
54
+ const unsealed = sealedBigInt ^ sealingKeyBigInt;
55
+
56
+ return unsealed;
57
+ }
@@ -0,0 +1,287 @@
1
+ import { hardhat } from '@/chains';
2
+ import { type Permit, PermitUtils } from '@/permits';
3
+
4
+ import { FheTypes, type UnsealedItem } from '../types.js';
5
+ import { getThresholdNetworkUrlOrThrow } from '../config.js';
6
+ import { CofhesdkError, CofhesdkErrorCode } from '../error.js';
7
+ import { permits } from '../permits.js';
8
+ import { isValidUtype, convertViaUtype } from './decryptUtils.js';
9
+ import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
10
+ import { cofheMocksSealOutput } from './cofheMocksSealOutput.js';
11
+ // import { tnSealOutputV1 } from './tnSealOutputV1.js';
12
+ import { tnSealOutputV2 } from './tnSealOutputV2.js';
13
+
14
+ /**
15
+ * API
16
+ *
17
+ * await client.decryptHandle(ctHash, utype)
18
+ * .setChainId(chainId)
19
+ * .setAccount(account)
20
+ * .setPermitHash(permitHash)
21
+ * .setPermit(permit)
22
+ * .decrypt()
23
+ *
24
+ * If chainId not set, uses client's chainId
25
+ * If account not set, uses client's account
26
+ * If permitHash not set, uses chainId and account to get active permit
27
+ * If permit is set, uses permit to decrypt regardless of chainId, account, or permitHash
28
+ *
29
+ * Returns the unsealed item.
30
+ */
31
+
32
+ type DecryptHandlesBuilderParams<U extends FheTypes> = BaseBuilderParams & {
33
+ ctHash: bigint;
34
+ utype: U;
35
+ permitHash?: string;
36
+ permit?: Permit;
37
+ };
38
+
39
+ export class DecryptHandlesBuilder<U extends FheTypes> extends BaseBuilder {
40
+ private ctHash: bigint;
41
+ private utype: U;
42
+ private permitHash?: string;
43
+ private permit?: Permit;
44
+
45
+ constructor(params: DecryptHandlesBuilderParams<U>) {
46
+ super({
47
+ config: params.config,
48
+ publicClient: params.publicClient,
49
+ walletClient: params.walletClient,
50
+ chainId: params.chainId,
51
+ account: params.account,
52
+ requireConnected: params.requireConnected,
53
+ });
54
+
55
+ this.ctHash = params.ctHash;
56
+ this.utype = params.utype;
57
+ this.permitHash = params.permitHash;
58
+ this.permit = params.permit;
59
+ }
60
+
61
+ /**
62
+ * @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
63
+ *
64
+ * If not provided, the chainId will be fetched from the connected publicClient.
65
+ *
66
+ * Example:
67
+ * ```typescript
68
+ * const unsealed = await decryptHandle(ctHash, utype)
69
+ * .setChainId(11155111)
70
+ * .decrypt();
71
+ * ```
72
+ *
73
+ * @returns The chainable DecryptHandlesBuilder instance.
74
+ */
75
+ setChainId(chainId: number): DecryptHandlesBuilder<U> {
76
+ this.chainId = chainId;
77
+ return this;
78
+ }
79
+
80
+ getChainId(): number | undefined {
81
+ return this.chainId;
82
+ }
83
+
84
+ /**
85
+ * @param account - Account to decrypt values from. Used to fetch the correct permit.
86
+ *
87
+ * If not provided, the account will be fetched from the connected walletClient.
88
+ *
89
+ * Example:
90
+ * ```typescript
91
+ * const unsealed = await decryptHandle(ctHash, utype)
92
+ * .setAccount('0x1234567890123456789012345678901234567890')
93
+ * .decrypt();
94
+ * ```
95
+ *
96
+ * @returns The chainable DecryptHandlesBuilder instance.
97
+ */
98
+ setAccount(account: string): DecryptHandlesBuilder<U> {
99
+ this.account = account;
100
+ return this;
101
+ }
102
+
103
+ getAccount(): string | undefined {
104
+ return this.account;
105
+ }
106
+
107
+ /**
108
+ * @param permitHash - Permit hash to decrypt values from. Used to fetch the correct permit.
109
+ *
110
+ * If not provided, the active permit for the chainId and account will be used.
111
+ * If `setPermit()` is called, it will be used regardless of chainId, account, or permitHash.
112
+ *
113
+ * Example:
114
+ * ```typescript
115
+ * const unsealed = await decryptHandle(ctHash, utype)
116
+ * .setPermitHash('0x1234567890123456789012345678901234567890')
117
+ * .decrypt();
118
+ * ```
119
+ *
120
+ * @returns The chainable DecryptHandlesBuilder instance.
121
+ */
122
+ setPermitHash(permitHash: string): DecryptHandlesBuilder<U> {
123
+ this.permitHash = permitHash;
124
+ return this;
125
+ }
126
+
127
+ getPermitHash(): string | undefined {
128
+ return this.permitHash;
129
+ }
130
+
131
+ /**
132
+ * @param permit - Permit to decrypt values with. If provided, it will be used regardless of chainId, account, or permitHash.
133
+ *
134
+ * If not provided, the permit will be determined by chainId, account, and permitHash.
135
+ *
136
+ * Example:
137
+ * ```typescript
138
+ * const unsealed = await decryptHandle(ctHash, utype)
139
+ * .setPermit(permit)
140
+ * .decrypt();
141
+ * ```
142
+ *
143
+ * @returns The chainable DecryptHandlesBuilder instance.
144
+ */
145
+ setPermit(permit: Permit): DecryptHandlesBuilder<U> {
146
+ this.permit = permit;
147
+ return this;
148
+ }
149
+
150
+ getPermit(): Permit | undefined {
151
+ return this.permit;
152
+ }
153
+
154
+ private async getThresholdNetworkUrl(): Promise<string> {
155
+ this.assertChainId();
156
+ return getThresholdNetworkUrlOrThrow(this.config, this.chainId);
157
+ }
158
+
159
+ private validateUtypeOrThrow(): void {
160
+ if (!isValidUtype(this.utype))
161
+ throw new CofhesdkError({
162
+ code: CofhesdkErrorCode.InvalidUtype,
163
+ message: `Invalid utype to decrypt to`,
164
+ context: {
165
+ utype: this.utype,
166
+ },
167
+ });
168
+ }
169
+
170
+ private async getResolvedPermit(): Promise<Permit> {
171
+ if (this.permit) return this.permit;
172
+
173
+ this.assertChainId();
174
+ this.assertAccount();
175
+
176
+ // Fetch with permit hash
177
+ if (this.permitHash) {
178
+ const permit = await permits.getPermit(this.chainId, this.account, this.permitHash);
179
+ if (!permit) {
180
+ throw new CofhesdkError({
181
+ code: CofhesdkErrorCode.PermitNotFound,
182
+ message: `Permit with hash <${this.permitHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
183
+ hint: 'Ensure the permit exists and is valid.',
184
+ context: {
185
+ chainId: this.chainId,
186
+ account: this.account,
187
+ permitHash: this.permitHash,
188
+ },
189
+ });
190
+ }
191
+ return permit;
192
+ }
193
+
194
+ // Fetch with active permit
195
+ const permit = await permits.getActivePermit(this.chainId, this.account);
196
+ if (!permit) {
197
+ throw new CofhesdkError({
198
+ code: CofhesdkErrorCode.PermitNotFound,
199
+ message: `Active permit not found for chainId <${this.chainId}> and account <${this.account}>`,
200
+ hint: 'Ensure a permit exists for this account on this chain.',
201
+ context: {
202
+ chainId: this.chainId,
203
+ account: this.account,
204
+ },
205
+ });
206
+ }
207
+ return permit;
208
+ }
209
+
210
+ /**
211
+ * On hardhat, interact with MockZkVerifier contract instead of CoFHE
212
+ */
213
+ private async mocksSealOutput(permit: Permit): Promise<bigint> {
214
+ this.assertPublicClient();
215
+
216
+ const mocksSealOutputDelay = this.config.mocks.sealOutputDelay;
217
+ return cofheMocksSealOutput(this.ctHash, this.utype, permit, this.publicClient, mocksSealOutputDelay);
218
+ }
219
+
220
+ /**
221
+ * In the production context, perform a true decryption with the CoFHE coprocessor.
222
+ */
223
+ private async productionSealOutput(permit: Permit): Promise<bigint> {
224
+ this.assertChainId();
225
+ this.assertPublicClient();
226
+
227
+ const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
228
+ const permission = PermitUtils.getPermission(permit, true);
229
+ // const sealed = await tnSealOutputV1(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
230
+ const sealed = await tnSealOutputV2(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
231
+ return PermitUtils.unseal(permit, sealed);
232
+ }
233
+
234
+ /**
235
+ * Final step of the decryption process. MUST BE CALLED LAST IN THE CHAIN.
236
+ *
237
+ * This will:
238
+ * - Use a permit based on provided permit OR chainId + account + permitHash
239
+ * - Check permit validity
240
+ * - Call CoFHE `/sealoutput` with the permit, which returns a sealed (encrypted) item
241
+ * - Unseal the sealed item with the permit
242
+ * - Return the unsealed item
243
+ *
244
+ * Example:
245
+ * ```typescript
246
+ * const unsealed = await decryptHandle(ctHash, utype)
247
+ * .setChainId(11155111) // optional
248
+ * .setAccount('0x123...890') // optional
249
+ * .decrypt(); // execute
250
+ * ```
251
+ *
252
+ * @returns The unsealed item.
253
+ */
254
+ async decrypt(): Promise<UnsealedItem<U>> {
255
+ // Ensure utype is valid
256
+ this.validateUtypeOrThrow();
257
+
258
+ // Resolve permit
259
+ const permit = await this.getResolvedPermit();
260
+
261
+ // Ensure permit validity
262
+ // TODO: This doesn't validate permit expiration
263
+ // TODO: This doesn't throw, returns a validation result instead
264
+ PermitUtils.validate(permit);
265
+
266
+ // TODO: Add this further validation step for the permit
267
+ // TODO: Ensure this throws if the permit is invalid
268
+ PermitUtils.isValid(permit);
269
+
270
+ // Extract chainId from signed permit
271
+ // Use this chainId to fetch the threshold network URL since this.chainId may be undefined
272
+ const chainId = permit._signedDomain!.chainId;
273
+
274
+ // Check permit validity on-chain
275
+ // TODO: PermitUtils.validateOnChain(permit, this.publicClient);
276
+
277
+ let unsealed: bigint;
278
+
279
+ if (chainId === hardhat.id) {
280
+ unsealed = await this.mocksSealOutput(permit);
281
+ } else {
282
+ unsealed = await this.productionSealOutput(permit);
283
+ }
284
+
285
+ return convertViaUtype(this.utype, unsealed);
286
+ }
287
+ }