@cofhe/sdk 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/core/baseBuilder.ts +18 -18
  3. package/core/client.test.ts +58 -55
  4. package/core/client.ts +50 -30
  5. package/core/clientTypes.ts +21 -17
  6. package/core/config.test.ts +32 -33
  7. package/core/config.ts +47 -48
  8. package/core/consts.ts +6 -2
  9. package/core/decrypt/{MockQueryDecrypterAbi.ts → MockThresholdNetworkAbi.ts} +71 -21
  10. package/core/decrypt/cofheMocksDecryptForTx.ts +142 -0
  11. package/core/decrypt/{cofheMocksSealOutput.ts → cofheMocksDecryptForView.ts} +12 -12
  12. package/core/decrypt/decryptForTxBuilder.ts +340 -0
  13. package/core/decrypt/{decryptHandleBuilder.ts → decryptForViewBuilder.ts} +75 -42
  14. package/core/decrypt/tnDecrypt.ts +232 -0
  15. package/core/decrypt/tnSealOutputV1.ts +5 -5
  16. package/core/decrypt/tnSealOutputV2.ts +27 -27
  17. package/core/encrypt/cofheMocksZkVerifySign.ts +15 -15
  18. package/core/encrypt/encryptInputsBuilder.test.ts +57 -61
  19. package/core/encrypt/encryptInputsBuilder.ts +65 -42
  20. package/core/encrypt/zkPackProveVerify.ts +11 -11
  21. package/core/error.ts +18 -18
  22. package/core/fetchKeys.test.ts +3 -3
  23. package/core/fetchKeys.ts +3 -3
  24. package/core/index.ts +14 -11
  25. package/core/utils.ts +10 -10
  26. package/dist/{chunk-I5WFEYXX.js → chunk-2TPSCOW3.js} +791 -209
  27. package/dist/{chunk-R3B5TMVX.js → chunk-NWDKXBIP.js} +3 -2
  28. package/dist/{clientTypes-RqkgkV2i.d.ts → clientTypes-6aTZPQ_4.d.ts} +204 -85
  29. package/dist/{clientTypes-e4filDzK.d.cts → clientTypes-Bhq7pCSA.d.cts} +204 -85
  30. package/dist/core.cjs +799 -214
  31. package/dist/core.d.cts +25 -23
  32. package/dist/core.d.ts +25 -23
  33. package/dist/core.js +2 -2
  34. package/dist/node.cjs +748 -165
  35. package/dist/node.d.cts +10 -10
  36. package/dist/node.d.ts +10 -10
  37. package/dist/node.js +7 -7
  38. package/dist/permits.js +1 -1
  39. package/dist/web.cjs +751 -168
  40. package/dist/web.d.cts +11 -11
  41. package/dist/web.d.ts +11 -11
  42. package/dist/web.js +9 -9
  43. package/node/client.test.ts +34 -34
  44. package/node/config.test.ts +11 -11
  45. package/node/encryptInputs.test.ts +29 -29
  46. package/node/index.ts +15 -15
  47. package/package.json +3 -3
  48. package/web/client.web.test.ts +34 -34
  49. package/web/config.web.test.ts +11 -11
  50. package/web/encryptInputs.web.test.ts +29 -29
  51. package/web/index.ts +19 -19
  52. package/web/worker.builder.web.test.ts +28 -28
  53. package/web/worker.config.web.test.ts +47 -47
  54. package/web/worker.output.web.test.ts +10 -10
@@ -2,17 +2,17 @@ import { sepolia, hardhat } from '@/chains';
2
2
 
3
3
  import { describe, it, expect, vi } from 'vitest';
4
4
  import {
5
- createCofhesdkConfigBase,
6
- getCofhesdkConfigItem,
7
- type CofhesdkInputConfig,
5
+ createCofheConfigBase,
6
+ getCofheConfigItem,
7
+ type CofheInputConfig,
8
8
  getSupportedChainOrThrow,
9
9
  getCoFheUrlOrThrow,
10
10
  getZkVerifierUrlOrThrow,
11
11
  getThresholdNetworkUrlOrThrow,
12
12
  } from './config.js';
13
13
 
14
- describe('createCofhesdkConfigBase', () => {
15
- const validBaseConfig: CofhesdkInputConfig = {
14
+ describe('createCofheConfigBase', () => {
15
+ const validBaseConfig: CofheInputConfig = {
16
16
  supportedChains: [],
17
17
  };
18
18
 
@@ -36,18 +36,18 @@ describe('createCofhesdkConfigBase', () => {
36
36
  if (log) {
37
37
  console.log('expect config invalid', path, value, config);
38
38
  try {
39
- createCofhesdkConfigBase(config as CofhesdkInputConfig);
39
+ createCofheConfigBase(config as CofheInputConfig);
40
40
  } catch (e) {
41
41
  console.log('expect config invalid', path, value, config, e);
42
42
  }
43
43
  }
44
- expect(() => createCofhesdkConfigBase(config as CofhesdkInputConfig)).toThrow('Invalid cofhesdk configuration:');
44
+ expect(() => createCofheConfigBase(config as CofheInputConfig)).toThrow('Invalid cofhe configuration:');
45
45
  };
46
46
 
47
47
  const expectValidConfigItem = (path: string, value: any, expectedValue: any): void => {
48
48
  const config = { ...validBaseConfig };
49
49
  setNestedValue(config, path, value);
50
- const result = createCofhesdkConfigBase(config);
50
+ const result = createCofheConfigBase(config);
51
51
  expect(getNestedValue(result, path)).toEqual(expectedValue);
52
52
  };
53
53
 
@@ -72,16 +72,6 @@ describe('createCofhesdkConfigBase', () => {
72
72
  expectValidConfigItem('supportedChains', [sepolia, hardhat], [sepolia, hardhat]);
73
73
  });
74
74
 
75
- it('permitGeneration', () => {
76
- expectInvalidConfigItem('permitGeneration', 'not-a-boolean');
77
- expectInvalidConfigItem('permitGeneration', null);
78
-
79
- expectValidConfigItem('permitGeneration', 'ON_CONNECT', 'ON_CONNECT');
80
- expectValidConfigItem('permitGeneration', 'ON_DECRYPT_HANDLES', 'ON_DECRYPT_HANDLES');
81
- expectValidConfigItem('permitGeneration', 'MANUAL', 'MANUAL');
82
- expectValidConfigItem('permitGeneration', undefined, 'ON_CONNECT');
83
- });
84
-
85
75
  it('defaultPermitExpiration', () => {
86
76
  expectInvalidConfigItem('defaultPermitExpiration', 'not-a-number');
87
77
  expectInvalidConfigItem('defaultPermitExpiration', null);
@@ -116,7 +106,7 @@ describe('createCofhesdkConfigBase', () => {
116
106
  };
117
107
 
118
108
  const config = { ...validBaseConfig, fheKeyStorage: fakeStorage };
119
- const result = createCofhesdkConfigBase(config);
109
+ const result = createCofheConfigBase(config);
120
110
 
121
111
  expect(result.fheKeyStorage).not.toBeNull();
122
112
  await result.fheKeyStorage!.getItem('test');
@@ -139,17 +129,26 @@ describe('createCofhesdkConfigBase', () => {
139
129
  it('mocks', () => {
140
130
  expectInvalidConfigItem('mocks', 'not-an-object');
141
131
  expectInvalidConfigItem('mocks', null);
132
+ });
133
+
134
+ it('mocks.decryptDelay', () => {
135
+ expectInvalidConfigItem('mocks.decryptDelay', 'not-a-number');
136
+ expectInvalidConfigItem('mocks.decryptDelay', null);
142
137
 
143
- expectValidConfigItem('mocks', { sealOutputDelay: 1000 }, { sealOutputDelay: 1000 });
144
- expectValidConfigItem('mocks', undefined, { sealOutputDelay: 0 });
138
+ expectValidConfigItem('mocks.decryptDelay', undefined, 0);
139
+ expectValidConfigItem('mocks.decryptDelay', 1000, 1000);
145
140
  });
146
141
 
147
- it('mocks.sealOutputDelay', () => {
148
- expectInvalidConfigItem('mocks.sealOutputDelay', 'not-a-number');
149
- expectInvalidConfigItem('mocks.sealOutputDelay', null);
142
+ it('mocks.encryptDelay', () => {
143
+ expectInvalidConfigItem('mocks.encryptDelay', 'not-a-number');
144
+ expectInvalidConfigItem('mocks.encryptDelay', null);
145
+ expectInvalidConfigItem('mocks.encryptDelay', [100, 100, 100]); // wrong tuple length
146
+ expectInvalidConfigItem('mocks.encryptDelay', ['a', 'b', 'c', 'd', 'e']); // non-number elements
150
147
 
151
- expectValidConfigItem('mocks.sealOutputDelay', undefined, 0);
152
- expectValidConfigItem('mocks.sealOutputDelay', 1000, 1000);
148
+ expectValidConfigItem('mocks.encryptDelay', undefined, [100, 100, 100, 500, 500]);
149
+ expectValidConfigItem('mocks.encryptDelay', 200, 200);
150
+ expectValidConfigItem('mocks.encryptDelay', 0, 0);
151
+ expectValidConfigItem('mocks.encryptDelay', [10, 20, 30, 40, 50], [10, 20, 30, 40, 50]);
153
152
  });
154
153
 
155
154
  it('useWorkers', () => {
@@ -164,19 +163,19 @@ describe('createCofhesdkConfigBase', () => {
164
163
  });
165
164
 
166
165
  it('should get config item', () => {
167
- const config: CofhesdkInputConfig = {
166
+ const config: CofheInputConfig = {
168
167
  supportedChains: [sepolia],
169
168
  };
170
169
 
171
- const result = createCofhesdkConfigBase(config);
170
+ const result = createCofheConfigBase(config);
172
171
 
173
- const supportedChains = getCofhesdkConfigItem(result, 'supportedChains');
172
+ const supportedChains = getCofheConfigItem(result, 'supportedChains');
174
173
  expect(supportedChains).toEqual(config.supportedChains);
175
174
  });
176
175
  });
177
176
 
178
177
  describe('Config helper functions', () => {
179
- const config = createCofhesdkConfigBase({
178
+ const config = createCofheConfigBase({
180
179
  supportedChains: [sepolia, hardhat],
181
180
  });
182
181
 
@@ -200,7 +199,7 @@ describe('Config helper functions', () => {
200
199
  });
201
200
 
202
201
  it('should throw MissingConfig when url not set', () => {
203
- const configWithoutUrl = createCofhesdkConfigBase({
202
+ const configWithoutUrl = createCofheConfigBase({
204
203
  supportedChains: [{ ...sepolia, coFheUrl: undefined } as any],
205
204
  });
206
205
  expect(() => getCoFheUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
@@ -217,7 +216,7 @@ describe('Config helper functions', () => {
217
216
  });
218
217
 
219
218
  it('should throw ZkVerifierUrlUninitialized when url not set', () => {
220
- const configWithoutUrl = createCofhesdkConfigBase({
219
+ const configWithoutUrl = createCofheConfigBase({
221
220
  supportedChains: [{ ...sepolia, verifierUrl: undefined } as any],
222
221
  });
223
222
  expect(() => getZkVerifierUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
@@ -234,7 +233,7 @@ describe('Config helper functions', () => {
234
233
  });
235
234
 
236
235
  it('should throw ThresholdNetworkUrlUninitialized when url not set', () => {
237
- const configWithoutUrl = createCofhesdkConfigBase({
236
+ const configWithoutUrl = createCofheConfigBase({
238
237
  supportedChains: [{ ...sepolia, thresholdNetworkUrl: undefined } as any],
239
238
  });
240
239
  expect(() => getThresholdNetworkUrlOrThrow(configWithoutUrl, sepolia.id)).toThrow();
package/core/config.ts CHANGED
@@ -2,26 +2,19 @@ import { type CofheChain } from '@/chains';
2
2
 
3
3
  import { z } from 'zod';
4
4
  import { type WalletClient } from 'viem';
5
- import { CofhesdkError, CofhesdkErrorCode } from './error.js';
5
+ import { CofheError, CofheErrorCode } from './error.js';
6
6
  import { type IStorage } from './types.js';
7
7
 
8
- export type CofhesdkEnvironment = 'node' | 'hardhat' | 'web' | 'react';
8
+ export type CofheEnvironment = 'node' | 'hardhat' | 'web' | 'react';
9
9
 
10
10
  /**
11
11
  * Usable config type inferred from the schema
12
12
  */
13
- export type CofhesdkConfig = {
13
+ export type CofheConfig = {
14
14
  /** Environment that the SDK is running in */
15
15
  environment: 'node' | 'hardhat' | 'web' | 'react';
16
16
  /** List of supported chains */
17
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
18
  /** Default permit expiration in seconds, default is 30 days */
26
19
  defaultPermitExpiration: number;
27
20
  /**
@@ -43,25 +36,30 @@ export type CofhesdkConfig = {
43
36
  * Default 1000ms on web
44
37
  * Default 0ms on hardhat (will be called during tests no need for fake delay)
45
38
  */
46
- sealOutputDelay: number;
39
+ decryptDelay: number;
40
+ /**
41
+ * Simulated delay(s) in milliseconds for each step of encryptInputs in mock mode.
42
+ * A single number applies the same delay to all five steps (InitTfhe, FetchKeys, Pack, Prove, Verify).
43
+ * A tuple of five numbers applies a per-step delay: [InitTfhe, FetchKeys, Pack, Prove, Verify].
44
+ * Default: [100, 100, 100, 500, 500]
45
+ */
46
+ encryptDelay: number | [number, number, number, number, number];
47
47
  };
48
- _internal?: CofhesdkInternalConfig;
48
+ _internal?: CofheInternalConfig;
49
49
  };
50
50
 
51
- export type CofhesdkInternalConfig = {
51
+ export type CofheInternalConfig = {
52
52
  zkvWalletClient?: WalletClient;
53
53
  };
54
54
 
55
55
  /**
56
56
  * Zod schema for configuration validation
57
57
  */
58
- export const CofhesdkConfigSchema = z.object({
58
+ export const CofheConfigSchema = z.object({
59
59
  /** Environment that the SDK is running in */
60
60
  environment: z.enum(['node', 'hardhat', 'web', 'react']).optional().default('node'),
61
61
  /** List of supported chain configurations */
62
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
63
  /** Default permit expiration in seconds, default is 30 days */
66
64
  defaultPermitExpiration: z
67
65
  .number()
@@ -87,10 +85,14 @@ export const CofhesdkConfigSchema = z.object({
87
85
  /** Mocks configs */
88
86
  mocks: z
89
87
  .object({
90
- sealOutputDelay: z.number().optional().default(0),
88
+ decryptDelay: z.number().optional().default(0),
89
+ encryptDelay: z
90
+ .union([z.number(), z.tuple([z.number(), z.number(), z.number(), z.number(), z.number()])])
91
+ .optional()
92
+ .default([100, 100, 100, 500, 500]),
91
93
  })
92
94
  .optional()
93
- .default({ sealOutputDelay: 0 }),
95
+ .default({ decryptDelay: 0, encryptDelay: [100, 100, 100, 500, 500] }),
94
96
  /** Internal configuration */
95
97
  _internal: z
96
98
  .object({
@@ -102,48 +104,45 @@ export const CofhesdkConfigSchema = z.object({
102
104
  /**
103
105
  * Input config type inferred from the schema
104
106
  */
105
- export type CofhesdkInputConfig = z.input<typeof CofhesdkConfigSchema>;
107
+ export type CofheInputConfig = z.input<typeof CofheConfigSchema>;
106
108
 
107
109
  /**
108
- * Creates and validates a cofhesdk configuration (base implementation)
110
+ * Creates and validates a cofhe configuration (base implementation)
109
111
  * @param config - The configuration object to validate
110
112
  * @returns The validated configuration
111
113
  * @throws {Error} If the configuration is invalid
112
114
  */
113
- export function createCofhesdkConfigBase(config: CofhesdkInputConfig): CofhesdkConfig {
114
- const result = CofhesdkConfigSchema.safeParse(config);
115
+ export function createCofheConfigBase(config: CofheInputConfig): CofheConfig {
116
+ const result = CofheConfigSchema.safeParse(config);
115
117
 
116
118
  if (!result.success) {
117
- throw new Error(`Invalid cofhesdk configuration: ${z.prettifyError(result.error)}`, { cause: result.error });
119
+ throw new Error(`Invalid cofhe configuration: ${z.prettifyError(result.error)}`, { cause: result.error });
118
120
  }
119
121
 
120
122
  return result.data;
121
123
  }
122
124
 
123
125
  /**
124
- * Access the CofhesdkConfig object directly by providing the key.
126
+ * Access the CofheConfig object directly by providing the key.
125
127
  * This is powerful when you use OnchainKit utilities outside of the React context.
126
128
  */
127
- export const getCofhesdkConfigItem = <K extends keyof CofhesdkConfig>(
128
- config: CofhesdkConfig,
129
- key: K
130
- ): CofhesdkConfig[K] => {
129
+ export const getCofheConfigItem = <K extends keyof CofheConfig>(config: CofheConfig, key: K): CofheConfig[K] => {
131
130
  return config[key];
132
131
  };
133
132
 
134
133
  /**
135
134
  * Gets a supported chain from config by chainId, throws if not found
136
- * @param config - The cofhesdk configuration
135
+ * @param config - The cofhe configuration
137
136
  * @param chainId - The chain ID to look up
138
137
  * @returns The supported chain configuration
139
- * @throws {CofhesdkError} If the chain is not found in the config
138
+ * @throws {CofheError} If the chain is not found in the config
140
139
  */
141
- export function getSupportedChainOrThrow(config: CofhesdkConfig, chainId: number): CofheChain {
140
+ export function getSupportedChainOrThrow(config: CofheConfig, chainId: number): CofheChain {
142
141
  const supportedChain = config.supportedChains.find((chain) => chain.id === chainId);
143
142
 
144
143
  if (!supportedChain) {
145
- throw new CofhesdkError({
146
- code: CofhesdkErrorCode.UnsupportedChain,
144
+ throw new CofheError({
145
+ code: CofheErrorCode.UnsupportedChain,
147
146
  message: `Config does not support chain <${chainId}>`,
148
147
  hint: 'Ensure config passed to client has been created with this chain in the config.supportedChains array.',
149
148
  context: {
@@ -158,18 +157,18 @@ export function getSupportedChainOrThrow(config: CofhesdkConfig, chainId: number
158
157
 
159
158
  /**
160
159
  * Gets the CoFHE URL for a chain, throws if not found
161
- * @param config - The cofhesdk configuration
160
+ * @param config - The cofhe configuration
162
161
  * @param chainId - The chain ID to look up
163
162
  * @returns The CoFHE URL for the chain
164
- * @throws {CofhesdkError} If the chain or URL is not found
163
+ * @throws {CofheError} If the chain or URL is not found
165
164
  */
166
- export function getCoFheUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
165
+ export function getCoFheUrlOrThrow(config: CofheConfig, chainId: number): string {
167
166
  const supportedChain = getSupportedChainOrThrow(config, chainId);
168
167
  const url = supportedChain.coFheUrl;
169
168
 
170
169
  if (!url) {
171
- throw new CofhesdkError({
172
- code: CofhesdkErrorCode.MissingConfig,
170
+ throw new CofheError({
171
+ code: CofheErrorCode.MissingConfig,
173
172
  message: `CoFHE URL is not configured for chain <${chainId}>`,
174
173
  hint: 'Ensure this chain config includes a coFheUrl property.',
175
174
  context: { chainId },
@@ -181,18 +180,18 @@ export function getCoFheUrlOrThrow(config: CofhesdkConfig, chainId: number): str
181
180
 
182
181
  /**
183
182
  * Gets the ZK verifier URL for a chain, throws if not found
184
- * @param config - The cofhesdk configuration
183
+ * @param config - The cofhe configuration
185
184
  * @param chainId - The chain ID to look up
186
185
  * @returns The ZK verifier URL for the chain
187
- * @throws {CofhesdkError} If the chain or URL is not found
186
+ * @throws {CofheError} If the chain or URL is not found
188
187
  */
189
- export function getZkVerifierUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
188
+ export function getZkVerifierUrlOrThrow(config: CofheConfig, chainId: number): string {
190
189
  const supportedChain = getSupportedChainOrThrow(config, chainId);
191
190
  const url = supportedChain.verifierUrl;
192
191
 
193
192
  if (!url) {
194
- throw new CofhesdkError({
195
- code: CofhesdkErrorCode.ZkVerifierUrlUninitialized,
193
+ throw new CofheError({
194
+ code: CofheErrorCode.ZkVerifierUrlUninitialized,
196
195
  message: `ZK verifier URL is not configured for chain <${chainId}>`,
197
196
  hint: 'Ensure this chain config includes a verifierUrl property.',
198
197
  context: { chainId },
@@ -204,18 +203,18 @@ export function getZkVerifierUrlOrThrow(config: CofhesdkConfig, chainId: number)
204
203
 
205
204
  /**
206
205
  * Gets the threshold network URL for a chain, throws if not found
207
- * @param config - The cofhesdk configuration
206
+ * @param config - The cofhe configuration
208
207
  * @param chainId - The chain ID to look up
209
208
  * @returns The threshold network URL for the chain
210
- * @throws {CofhesdkError} If the chain or URL is not found
209
+ * @throws {CofheError} If the chain or URL is not found
211
210
  */
212
- export function getThresholdNetworkUrlOrThrow(config: CofhesdkConfig, chainId: number): string {
211
+ export function getThresholdNetworkUrlOrThrow(config: CofheConfig, chainId: number): string {
213
212
  const supportedChain = getSupportedChainOrThrow(config, chainId);
214
213
  const url = supportedChain.thresholdNetworkUrl;
215
214
 
216
215
  if (!url) {
217
- throw new CofhesdkError({
218
- code: CofhesdkErrorCode.ThresholdNetworkUrlUninitialized,
216
+ throw new CofheError({
217
+ code: CofheErrorCode.ThresholdNetworkUrlUninitialized,
219
218
  message: `Threshold network URL is not configured for chain <${chainId}>`,
220
219
  hint: 'Ensure this chain config includes a thresholdNetworkUrl property.',
221
220
  context: { chainId },
package/core/consts.ts CHANGED
@@ -4,8 +4,8 @@ export const TASK_MANAGER_ADDRESS = '0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9'
4
4
  /** Mock ZK Verifier contract address (used for testing) */
5
5
  export const MOCKS_ZK_VERIFIER_ADDRESS = '0x0000000000000000000000000000000000005001' as const;
6
6
 
7
- /** Mock Query Decrypter contract address (used for testing) */
8
- export const MOCKS_QUERY_DECRYPTER_ADDRESS = '0x0000000000000000000000000000000000005002' as const;
7
+ /** Mock Threshold Network contract address (used for testing) */
8
+ export const MOCKS_THRESHOLD_NETWORK_ADDRESS = '0x0000000000000000000000000000000000005002' as const;
9
9
 
10
10
  /** Test Bed contract address (used for testing) */
11
11
  export const TEST_BED_ADDRESS = '0x0000000000000000000000000000000000005003' as const;
@@ -16,3 +16,7 @@ export const MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY =
16
16
 
17
17
  /** Address for the Mock ZK Verifier signer account */
18
18
  export const MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = '0x6E12D8C87503D4287c294f2Fdef96ACd9DFf6bd2' as const;
19
+
20
+ /** Private key for the Mock decrypt result signer account */
21
+ export const MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY =
22
+ '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d' as const;
@@ -1,4 +1,4 @@
1
- export const MockQueryDecrypterAbi = [
1
+ export const MockThresholdNetworkAbi = [
2
2
  {
3
3
  type: 'function',
4
4
  name: 'acl',
@@ -45,11 +45,7 @@ export const MockQueryDecrypterAbi = [
45
45
  { name: 'expiration', type: 'uint64', internalType: 'uint64' },
46
46
  { name: 'recipient', type: 'address', internalType: 'address' },
47
47
  { name: 'validatorId', type: 'uint256', internalType: 'uint256' },
48
- {
49
- name: 'validatorContract',
50
- type: 'address',
51
- internalType: 'address',
52
- },
48
+ { name: 'validatorContract', type: 'address', internalType: 'address' },
53
49
  { name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
54
50
  { name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
55
51
  { name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
@@ -78,11 +74,7 @@ export const MockQueryDecrypterAbi = [
78
74
  { name: 'expiration', type: 'uint64', internalType: 'uint64' },
79
75
  { name: 'recipient', type: 'address', internalType: 'address' },
80
76
  { name: 'validatorId', type: 'uint256', internalType: 'uint256' },
81
- {
82
- name: 'validatorContract',
83
- type: 'address',
84
- internalType: 'address',
85
- },
77
+ { name: 'validatorContract', type: 'address', internalType: 'address' },
86
78
  { name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
87
79
  { name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
88
80
  { name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
@@ -106,13 +98,6 @@ export const MockQueryDecrypterAbi = [
106
98
  outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }],
107
99
  stateMutability: 'pure',
108
100
  },
109
- {
110
- type: 'function',
111
- name: 'taskManager',
112
- inputs: [],
113
- outputs: [{ name: '', type: 'address', internalType: 'contract TaskManager' }],
114
- stateMutability: 'view',
115
- },
116
101
  {
117
102
  type: 'function',
118
103
  name: 'unseal',
@@ -123,7 +108,72 @@ export const MockQueryDecrypterAbi = [
123
108
  outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }],
124
109
  stateMutability: 'pure',
125
110
  },
126
- { type: 'error', name: 'NotAllowed', inputs: [] },
127
- { type: 'error', name: 'SealingKeyInvalid', inputs: [] },
128
- { type: 'error', name: 'SealingKeyMissing', inputs: [] },
111
+ {
112
+ type: 'function',
113
+ name: 'mockAcl',
114
+ inputs: [],
115
+ outputs: [{ name: '', type: 'address', internalType: 'contract MockACL' }],
116
+ stateMutability: 'view',
117
+ },
118
+ {
119
+ type: 'function',
120
+ name: 'mockTaskManager',
121
+ inputs: [],
122
+ outputs: [{ name: '', type: 'address', internalType: 'contract MockTaskManager' }],
123
+ stateMutability: 'view',
124
+ },
125
+ {
126
+ type: 'function',
127
+ name: 'mockQueryDecrypt',
128
+ inputs: [
129
+ { name: 'ctHash', type: 'uint256', internalType: 'uint256' },
130
+ { name: '', type: 'uint256', internalType: 'uint256' },
131
+ { name: 'issuer', type: 'address', internalType: 'address' },
132
+ ],
133
+ outputs: [
134
+ { name: 'allowed', type: 'bool', internalType: 'bool' },
135
+ { name: 'error', type: 'string', internalType: 'string' },
136
+ { name: '', type: 'uint256', internalType: 'uint256' },
137
+ ],
138
+ stateMutability: 'view',
139
+ },
140
+ {
141
+ type: 'function',
142
+ name: 'decryptForTxWithPermit',
143
+ inputs: [
144
+ { name: 'ctHash', type: 'uint256', internalType: 'uint256' },
145
+ {
146
+ name: 'permission',
147
+ type: 'tuple',
148
+ internalType: 'struct Permission',
149
+ components: [
150
+ { name: 'issuer', type: 'address', internalType: 'address' },
151
+ { name: 'expiration', type: 'uint64', internalType: 'uint64' },
152
+ { name: 'recipient', type: 'address', internalType: 'address' },
153
+ { name: 'validatorId', type: 'uint256', internalType: 'uint256' },
154
+ { name: 'validatorContract', type: 'address', internalType: 'address' },
155
+ { name: 'sealingKey', type: 'bytes32', internalType: 'bytes32' },
156
+ { name: 'issuerSignature', type: 'bytes', internalType: 'bytes' },
157
+ { name: 'recipientSignature', type: 'bytes', internalType: 'bytes' },
158
+ ],
159
+ },
160
+ ],
161
+ outputs: [
162
+ { name: 'allowed', type: 'bool', internalType: 'bool' },
163
+ { name: 'error', type: 'string', internalType: 'string' },
164
+ { name: 'decryptedValue', type: 'uint256', internalType: 'uint256' },
165
+ ],
166
+ stateMutability: 'view',
167
+ },
168
+ {
169
+ type: 'function',
170
+ name: 'decryptForTxWithoutPermit',
171
+ inputs: [{ name: 'ctHash', type: 'uint256', internalType: 'uint256' }],
172
+ outputs: [
173
+ { name: 'allowed', type: 'bool', internalType: 'bool' },
174
+ { name: 'error', type: 'string', internalType: 'string' },
175
+ { name: 'decryptedValue', type: 'uint256', internalType: 'uint256' },
176
+ ],
177
+ stateMutability: 'view',
178
+ },
129
179
  ] as const;
@@ -0,0 +1,142 @@
1
+ import { type Permit, PermitUtils } from '@/permits';
2
+
3
+ import { encodePacked, keccak256, pad, toHex, type Hex, type PublicClient } from 'viem';
4
+ import { sign } from 'viem/accounts';
5
+ import { sleep } from '../utils.js';
6
+ import { MockThresholdNetworkAbi } from './MockThresholdNetworkAbi.js';
7
+ import { FheTypes } from '../types.js';
8
+ import { CofheError, CofheErrorCode } from '../error.js';
9
+ import { MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY } from '../consts.js';
10
+ import { MOCKS_THRESHOLD_NETWORK_ADDRESS } from '../consts.js';
11
+
12
+ export type DecryptForTxMocksResult = {
13
+ ctHash: bigint;
14
+ decryptedValue: bigint;
15
+ signature: string;
16
+ };
17
+
18
+ export async function cofheMocksDecryptForTx(
19
+ ctHash: bigint,
20
+ utype: FheTypes,
21
+ permit: Permit | null,
22
+ publicClient: PublicClient,
23
+ mocksDecryptForTxDelay: number
24
+ ): Promise<DecryptForTxMocksResult> {
25
+ // Configurable delay before decrypting to simulate the CoFHE decrypt processing time
26
+ // Recommended 1000ms on web
27
+ // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
28
+ if (mocksDecryptForTxDelay > 0) await sleep(mocksDecryptForTxDelay);
29
+
30
+ if (permit !== null) {
31
+ // With permit
32
+ let permission = PermitUtils.getPermission(permit, true);
33
+ const permissionWithBigInts = {
34
+ ...permission,
35
+ expiration: BigInt(permission.expiration),
36
+ validatorId: BigInt(permission.validatorId),
37
+ };
38
+
39
+ const [allowed, error, result] = await publicClient.readContract({
40
+ address: MOCKS_THRESHOLD_NETWORK_ADDRESS,
41
+ abi: MockThresholdNetworkAbi,
42
+ functionName: 'decryptForTxWithPermit',
43
+ args: [ctHash, permissionWithBigInts],
44
+ });
45
+
46
+ if (error != '') {
47
+ throw new CofheError({
48
+ code: CofheErrorCode.DecryptFailed,
49
+ message: `mocks decryptForTx call failed: ${error}`,
50
+ });
51
+ }
52
+
53
+ if (allowed == false) {
54
+ throw new CofheError({
55
+ code: CofheErrorCode.DecryptFailed,
56
+ message: `mocks decryptForTx call failed: ACL Access Denied (NotAllowed)`,
57
+ });
58
+ }
59
+
60
+ // decryptForTx returns plaintext directly (no sealing/unsealing needed)
61
+ // Generate a mock threshold network signature (in production, this would be the actual signature)
62
+ // The signature must be valid for MockTaskManager verification.
63
+ const chainId = await publicClient.getChainId();
64
+ const ctHashBigInt = BigInt(ctHash);
65
+ const resultBigInt = BigInt(result);
66
+ const encryptionType = Number((ctHashBigInt & (0x7fn << 8n)) >> 8n);
67
+
68
+ // Matches Solidity: keccak256(abi.encodePacked(result, uint32(enc_type), uint64(chainId), bytes32(ctHash)))
69
+ const ctHashBytes32 = pad(toHex(ctHashBigInt), { size: 32 }) as Hex;
70
+ const packed = encodePacked(
71
+ ['uint256', 'uint32', 'uint64', 'bytes32'],
72
+ [resultBigInt, encryptionType, BigInt(chainId), ctHashBytes32]
73
+ );
74
+ const messageHash = keccak256(packed);
75
+
76
+ // Raw digest signature (no EIP-191 prefix). Must verify against OpenZeppelin ECDSA.recover(messageHash, signature).
77
+ const signatureHex = await sign({
78
+ hash: messageHash,
79
+ privateKey: MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY,
80
+ to: 'hex',
81
+ });
82
+ const signature = signatureHex.slice(2); // no 0x prefix
83
+
84
+ return {
85
+ ctHash,
86
+ decryptedValue: BigInt(result),
87
+ signature,
88
+ };
89
+ }
90
+
91
+ // Without permit (global allowance)
92
+ const [allowed, error, result] = await publicClient.readContract({
93
+ address: MOCKS_THRESHOLD_NETWORK_ADDRESS,
94
+ abi: MockThresholdNetworkAbi,
95
+ functionName: 'decryptForTxWithoutPermit',
96
+ args: [ctHash],
97
+ });
98
+
99
+ if (error != '') {
100
+ throw new CofheError({
101
+ code: CofheErrorCode.DecryptFailed,
102
+ message: `mocks decryptForTx call failed: ${error}`,
103
+ });
104
+ }
105
+
106
+ if (allowed == false) {
107
+ throw new CofheError({
108
+ code: CofheErrorCode.DecryptFailed,
109
+ message: `mocks decryptForTx call failed: ACL Access Denied (NotAllowed)`,
110
+ });
111
+ }
112
+
113
+ // decryptForTx returns plaintext directly (no sealing/unsealing needed)
114
+ // Generate a mock threshold network signature (in production, this would be the actual signature)
115
+ // The signature must be valid for MockTaskManager verification.
116
+ const chainId = await publicClient.getChainId();
117
+ const ctHashBigInt = BigInt(ctHash);
118
+ const resultBigInt = BigInt(result);
119
+ const encryptionType = Number((ctHashBigInt & (0x7fn << 8n)) >> 8n);
120
+
121
+ // Matches Solidity: keccak256(abi.encodePacked(result, uint32(enc_type), uint64(chainId), bytes32(ctHash)))
122
+ const ctHashBytes32 = pad(toHex(ctHashBigInt), { size: 32 }) as Hex;
123
+ const packed = encodePacked(
124
+ ['uint256', 'uint32', 'uint64', 'bytes32'],
125
+ [resultBigInt, encryptionType, BigInt(chainId), ctHashBytes32]
126
+ );
127
+ const messageHash = keccak256(packed);
128
+
129
+ // Raw digest signature (no EIP-191 prefix). Must verify against OpenZeppelin ECDSA.recover(messageHash, signature).
130
+ const signatureHex = await sign({
131
+ hash: messageHash,
132
+ privateKey: MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY,
133
+ to: 'hex',
134
+ });
135
+ const signature = signatureHex.slice(2); // no 0x prefix
136
+
137
+ return {
138
+ ctHash,
139
+ decryptedValue: BigInt(result),
140
+ signature,
141
+ };
142
+ }