@cofhe/sdk 0.3.2 → 0.5.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 +38 -0
- package/adapters/{ethers5.test.ts → test/ethers5.test.ts} +2 -2
- package/adapters/{ethers6.test.ts → test/ethers6.test.ts} +2 -2
- package/adapters/{hardhat.hh2.test.ts → test/hardhat.hh2.test.ts} +2 -2
- package/adapters/{index.test.ts → test/index.test.ts} +1 -1
- package/adapters/{wagmi.test.ts → test/wagmi.test.ts} +1 -1
- package/chains/{chains.test.ts → test/chains.test.ts} +1 -1
- package/core/client.ts +15 -5
- package/core/clientTypes.ts +7 -5
- package/core/consts.ts +9 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +14 -3
- package/core/decrypt/decryptForTxBuilder.ts +24 -10
- package/core/decrypt/decryptForViewBuilder.ts +14 -7
- package/core/decrypt/polling.ts +14 -0
- package/core/decrypt/tnDecryptUtils.ts +65 -0
- package/core/decrypt/{tnDecrypt.ts → tnDecryptV1.ts} +7 -70
- package/core/decrypt/tnDecryptV2.ts +483 -0
- package/core/decrypt/tnSealOutputV2.ts +245 -104
- package/core/decrypt/verifyDecryptResult.ts +65 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +6 -6
- package/core/encrypt/zkPackProveVerify.ts +10 -19
- package/core/fetchKeys.ts +0 -2
- package/core/index.ts +9 -1
- package/core/keyStore.ts +5 -2
- package/core/permits.ts +8 -3
- package/core/{client.test.ts → test/client.test.ts} +7 -7
- package/core/{config.test.ts → test/config.test.ts} +1 -1
- package/core/test/decrypt.test.ts +252 -0
- package/core/test/decryptBuilders.test.ts +390 -0
- package/core/{encrypt → test}/encryptInputsBuilder.test.ts +61 -6
- package/core/{fetchKeys.test.ts → test/fetchKeys.test.ts} +3 -3
- package/core/{keyStore.test.ts → test/keyStore.test.ts} +5 -3
- package/core/{permits.test.ts → test/permits.test.ts} +42 -1
- package/core/test/pollCallbacks.test.ts +563 -0
- package/core/types.ts +21 -0
- package/dist/chains.d.cts +2 -2
- package/dist/chains.d.ts +2 -2
- package/dist/chunk-4FP4V35O.js +13 -0
- package/dist/{chunk-NWDKXBIP.js → chunk-MRCKUMOS.js} +62 -22
- package/dist/{chunk-LWMRB6SD.js → chunk-S7OKGLFD.js} +615 -198
- package/dist/{clientTypes-Y43CKbOz.d.cts → clientTypes-BSbwairE.d.cts} +38 -13
- package/dist/{clientTypes-PQha8zes.d.ts → clientTypes-DDmcgZ0a.d.ts} +38 -13
- package/dist/core.cjs +691 -235
- package/dist/core.d.cts +24 -6
- package/dist/core.d.ts +24 -6
- package/dist/core.js +3 -2
- package/dist/node.cjs +696 -237
- package/dist/node.d.cts +3 -3
- package/dist/node.d.ts +3 -3
- package/dist/node.js +14 -7
- package/dist/{permit-MZ502UBl.d.ts → permit-DnVMDT5h.d.cts} +34 -4
- package/dist/{permit-MZ502UBl.d.cts → permit-DnVMDT5h.d.ts} +34 -4
- package/dist/permits.cjs +66 -29
- package/dist/permits.d.cts +18 -13
- package/dist/permits.d.ts +18 -13
- package/dist/permits.js +2 -1
- package/dist/web.cjs +718 -242
- package/dist/web.d.cts +8 -4
- package/dist/web.d.ts +8 -4
- package/dist/web.js +34 -11
- package/dist/zkProve.worker.cjs +6 -3
- package/dist/zkProve.worker.js +5 -3
- package/node/index.ts +13 -4
- package/node/test/client.test.ts +25 -0
- package/node/test/config.test.ts +16 -0
- package/node/test/inherited.test.ts +244 -0
- package/node/test/tfheinit.test.ts +56 -0
- package/package.json +24 -22
- package/permits/permit.ts +31 -5
- package/permits/sealing.ts +1 -1
- package/permits/{localstorage.test.ts → test/localstorage.test.ts} +2 -2
- package/permits/{permit.test.ts → test/permit.test.ts} +35 -1
- package/permits/{sealing.test.ts → test/sealing.test.ts} +1 -1
- package/permits/{store.test.ts → test/store.test.ts} +2 -2
- package/permits/{validation.test.ts → test/validation.test.ts} +82 -6
- package/permits/types.ts +1 -1
- package/permits/validation.ts +42 -2
- package/web/const.ts +2 -0
- package/web/index.ts +20 -6
- package/web/storage.ts +18 -3
- package/web/{client.web.test.ts → test/client.web.test.ts} +13 -1
- package/web/test/config.web.test.ts +16 -0
- package/web/test/inherited.web.test.ts +245 -0
- package/web/test/tfheinit.web.test.ts +62 -0
- package/web/{worker.config.web.test.ts → test/worker.config.web.test.ts} +1 -1
- package/web/{worker.output.web.test.ts → test/worker.output.web.test.ts} +1 -1
- package/web/{workerManager.test.ts → test/workerManager.test.ts} +1 -1
- package/web/{workerManager.web.test.ts → test/workerManager.web.test.ts} +1 -1
- package/web/zkProve.worker.ts +4 -3
- package/node/client.test.ts +0 -147
- package/node/config.test.ts +0 -68
- package/node/encryptInputs.test.ts +0 -155
- package/web/config.web.test.ts +0 -69
- package/web/encryptInputs.web.test.ts +0 -172
- package/web/worker.builder.web.test.ts +0 -148
- /package/dist/{types-YiAC4gig.d.cts → types-C07FK-cL.d.cts} +0 -0
- /package/dist/{types-YiAC4gig.d.ts → types-C07FK-cL.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @cofhe/sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 788a6e2: Add `onPoll` callback support for decrypt polling (tx + view) so consumers can observe poll progress.
|
|
8
|
+
|
|
9
|
+
- SDK decrypt helpers accept `onPoll` and emit `{ operation, requestId, attemptIndex, elapsedMs, intervalMs, timeoutMs }` once per poll attempt.
|
|
10
|
+
- React wiring supports passing the callback end-to-end.
|
|
11
|
+
- Docs updated with usage examples.
|
|
12
|
+
|
|
13
|
+
- 9a06012: Tighten permit validation and treat invalid permits as missing.
|
|
14
|
+
|
|
15
|
+
- SDK: `PermitUtils.validate` now enforces schema + signed + not-expired (use `PermitUtils.validateSchema` for schema-only validation).
|
|
16
|
+
- SDK: `ValidationResult.error` is now a typed union (`'invalid-schema' | 'expired' | 'not-signed' | null`).
|
|
17
|
+
- React: rename `disabledDueToMissingPermit` to `disabledDueToMissingValidPermit` in read/decrypt hooks and token balance helpers, and disable reads when the active permit is invalid.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 6c4084f: Add submit retries to the threshold-network decrypt flows used by both `decryptForTx` and `decryptForView` when the backend responds with `204 No Content` before a `request_id` is available.
|
|
22
|
+
|
|
23
|
+
- When the submit endpoint returns `204` without a body, the SDK now retries until it receives a `request_id` or the existing poll timeout budget is exhausted.
|
|
24
|
+
- These submit retries now emit `onPoll` callbacks, so consumers can observe retry progress before a request id exists.
|
|
25
|
+
- Submit retries and status polling now share the same overall timeout budget.
|
|
26
|
+
|
|
27
|
+
- 503536a: Improve logging ergonomics across React + web SDK.
|
|
28
|
+
|
|
29
|
+
- Add a configurable internal logger to `@cofhe/react` via `createCofheConfig({ react: { logger } })`.
|
|
30
|
+
- Make `@cofhe/sdk` `createWebStorage` logging opt-in via `createWebStorage({ enableLog })`.
|
|
31
|
+
|
|
32
|
+
- a685cd4: **Breaking change: upgraded to tfhe v1.5.3.**
|
|
33
|
+
Previous cofhesdk versions will no longer function.
|
|
34
|
+
|
|
35
|
+
## 0.4.0
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- e446642: Switch `decryptForTx` to Threshold Network v2 decrypt (submit + poll)
|
|
40
|
+
|
|
3
41
|
## 0.3.2
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { parseEther } from 'viem';
|
|
3
|
-
import { Ethers5Adapter } from '
|
|
4
|
-
import { createMockEIP1193Provider } from '
|
|
3
|
+
import { Ethers5Adapter } from '../ethers5.js';
|
|
4
|
+
import { createMockEIP1193Provider } from '../test-utils.js';
|
|
5
5
|
import * as ethers5 from 'ethers5';
|
|
6
6
|
|
|
7
7
|
describe('Ethers5Adapter', () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { parseEther } from 'viem';
|
|
3
|
-
import { Ethers6Adapter } from '
|
|
4
|
-
import { createMockEIP1193Provider } from '
|
|
3
|
+
import { Ethers6Adapter } from '../ethers6.js';
|
|
4
|
+
import { createMockEIP1193Provider } from '../test-utils.js';
|
|
5
5
|
import * as ethers6 from 'ethers6';
|
|
6
6
|
|
|
7
7
|
describe('Ethers6Adapter', () => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, beforeAll, afterAll } from 'vitest';
|
|
2
2
|
import { parseEther } from 'viem';
|
|
3
3
|
import { hardhat } from 'viem/chains';
|
|
4
|
-
import { HardhatSignerAdapter } from '
|
|
4
|
+
import { HardhatSignerAdapter } from '../hardhat.js';
|
|
5
5
|
import hre from 'hardhat';
|
|
6
6
|
import '@nomicfoundation/hardhat-ethers';
|
|
7
7
|
import type { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers.js';
|
|
8
|
-
import { hardhatNode } from '
|
|
8
|
+
import { hardhatNode } from '../hardhat-node.js';
|
|
9
9
|
|
|
10
10
|
describe('HardhatSignerAdapter', () => {
|
|
11
11
|
const HARDHAT_CHAIN_ID = 31337; // Hardhat local network
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { parseEther, createPublicClient, createWalletClient, http, type PublicClient, type WalletClient } from 'viem';
|
|
3
3
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
-
import { WagmiAdapter } from '
|
|
4
|
+
import { WagmiAdapter } from '../wagmi.js';
|
|
5
5
|
|
|
6
6
|
describe('WagmiAdapter', () => {
|
|
7
7
|
const testRpcUrl = 'https://ethereum-sepolia.rpc.subquery.network/public';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { sepolia, arbSepolia, baseSepolia, hardhat, chains, getChainById, getChainByName } from '
|
|
2
|
+
import { sepolia, arbSepolia, baseSepolia, hardhat, chains, getChainById, getChainByName } from '../index.js';
|
|
3
3
|
|
|
4
4
|
describe('Chains', () => {
|
|
5
5
|
it('should export all chains', () => {
|
package/core/client.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { CreateSelfPermitOptions, CreateSharingPermitOptions, ImportSharedPermitOptions } from '@/permits';
|
|
2
2
|
|
|
3
3
|
import { createStore } from 'zustand/vanilla';
|
|
4
|
-
import { type PublicClient, type WalletClient } from 'viem';
|
|
4
|
+
import { type Hex, type PublicClient, type WalletClient } from 'viem';
|
|
5
5
|
import { CofheError, CofheErrorCode } from './error.js';
|
|
6
6
|
import { EncryptInputsBuilder } from './encrypt/encryptInputsBuilder.js';
|
|
7
7
|
import { createKeysStore } from './keyStore.js';
|
|
8
8
|
import { permits } from './permits.js';
|
|
9
9
|
import { DecryptForViewBuilder } from './decrypt/decryptForViewBuilder.js';
|
|
10
10
|
import { DecryptForTxBuilder, type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js';
|
|
11
|
+
import { verifyDecryptResult as verifyDecryptResultStandalone } from './decrypt/verifyDecryptResult.js';
|
|
11
12
|
import { getPublicClientChainID, getWalletClientAccount } from './utils.js';
|
|
12
13
|
import type { CofheClientConnectionState, CofheClientParams, CofheClient, CofheClientPermits } from './clientTypes.js';
|
|
13
14
|
import type { EncryptableItem, FheTypes } from './types.js';
|
|
@@ -179,6 +180,13 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
179
180
|
});
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
// VERIFY DECRYPT RESULT
|
|
184
|
+
function verifyDecryptResult(handle: bigint | string, cleartext: bigint, signature: Hex): Promise<boolean> {
|
|
185
|
+
_requireConnected();
|
|
186
|
+
const { publicClient } = connectStore.getState();
|
|
187
|
+
return verifyDecryptResultStandalone(handle, cleartext, signature, publicClient!);
|
|
188
|
+
}
|
|
189
|
+
|
|
182
190
|
// PERMITS - Context-aware wrapper
|
|
183
191
|
|
|
184
192
|
const _getChainIdAndAccount = (chainId?: number, account?: string) => {
|
|
@@ -250,22 +258,22 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
250
258
|
},
|
|
251
259
|
|
|
252
260
|
// Retrieval methods (auto-fill chainId/account)
|
|
253
|
-
getPermit:
|
|
261
|
+
getPermit: (hash: string, chainId?: number, account?: string) => {
|
|
254
262
|
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
255
263
|
return permits.getPermit(_chainId, _account, hash);
|
|
256
264
|
},
|
|
257
265
|
|
|
258
|
-
getPermits:
|
|
266
|
+
getPermits: (chainId?: number, account?: string) => {
|
|
259
267
|
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
260
268
|
return permits.getPermits(_chainId, _account);
|
|
261
269
|
},
|
|
262
270
|
|
|
263
|
-
getActivePermit:
|
|
271
|
+
getActivePermit: (chainId?: number, account?: string) => {
|
|
264
272
|
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
265
273
|
return permits.getActivePermit(_chainId, _account);
|
|
266
274
|
},
|
|
267
275
|
|
|
268
|
-
getActivePermitHash:
|
|
276
|
+
getActivePermitHash: (chainId?: number, account?: string) => {
|
|
269
277
|
const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
|
|
270
278
|
return permits.getActivePermitHash(_chainId, _account);
|
|
271
279
|
},
|
|
@@ -288,6 +296,7 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
288
296
|
|
|
289
297
|
// Utils (no context needed)
|
|
290
298
|
getHash: permits.getHash,
|
|
299
|
+
export: permits.export,
|
|
291
300
|
serialize: permits.serialize,
|
|
292
301
|
deserialize: permits.deserialize,
|
|
293
302
|
};
|
|
@@ -320,6 +329,7 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
320
329
|
*/
|
|
321
330
|
decryptHandle: decryptForView,
|
|
322
331
|
decryptForTx,
|
|
332
|
+
verifyDecryptResult,
|
|
323
333
|
permits: clientPermits,
|
|
324
334
|
|
|
325
335
|
// Add SDK-specific methods below that require connection
|
package/core/clientTypes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// TODO: Extract client types to its own file, keep this one as primitives
|
|
2
|
-
import { type PublicClient, type WalletClient } from 'viem';
|
|
2
|
+
import { type Hex, type PublicClient, type WalletClient } from 'viem';
|
|
3
3
|
import { type CofheConfig } from './config.js';
|
|
4
4
|
import { type DecryptForViewBuilder } from './decrypt/decryptForViewBuilder.js';
|
|
5
5
|
import { type DecryptForTxBuilderUnset } from './decrypt/decryptForTxBuilder.js';
|
|
@@ -51,6 +51,7 @@ export type CofheClient<TConfig extends CofheConfig = CofheConfig> = {
|
|
|
51
51
|
decryptHandle<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
|
|
52
52
|
decryptForView<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
|
|
53
53
|
decryptForTx(ctHash: bigint | string): DecryptForTxBuilderUnset;
|
|
54
|
+
verifyDecryptResult(handle: bigint | string, cleartext: bigint, signature: Hex): Promise<boolean>;
|
|
54
55
|
permits: CofheClientPermits;
|
|
55
56
|
};
|
|
56
57
|
|
|
@@ -84,10 +85,10 @@ export type CofheClientPermits = {
|
|
|
84
85
|
) => Promise<RecipientPermit>;
|
|
85
86
|
|
|
86
87
|
// Retrieval methods (chainId/account optional)
|
|
87
|
-
getPermit: (hash: string, chainId?: number, account?: string) =>
|
|
88
|
-
getPermits: (chainId?: number, account?: string) =>
|
|
89
|
-
getActivePermit: (chainId?: number, account?: string) =>
|
|
90
|
-
getActivePermitHash: (chainId?: number, account?: string) =>
|
|
88
|
+
getPermit: (hash: string, chainId?: number, account?: string) => Permit | undefined;
|
|
89
|
+
getPermits: (chainId?: number, account?: string) => Record<string, Permit>;
|
|
90
|
+
getActivePermit: (chainId?: number, account?: string) => Permit | undefined;
|
|
91
|
+
getActivePermitHash: (chainId?: number, account?: string) => string | undefined;
|
|
91
92
|
|
|
92
93
|
// Get or create methods (get active or create new, chainId/account optional)
|
|
93
94
|
getOrCreateSelfPermit: (chainId?: number, account?: string, options?: CreateSelfPermitOptions) => Promise<Permit>;
|
|
@@ -104,6 +105,7 @@ export type CofheClientPermits = {
|
|
|
104
105
|
|
|
105
106
|
// Utils
|
|
106
107
|
getHash: typeof PermitUtils.getHash;
|
|
108
|
+
export: typeof PermitUtils.export;
|
|
107
109
|
serialize: typeof PermitUtils.serialize;
|
|
108
110
|
deserialize: typeof PermitUtils.deserialize;
|
|
109
111
|
};
|
package/core/consts.ts
CHANGED
|
@@ -20,3 +20,12 @@ export const MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = '0x6E12D8C87503D4287c294f2Fdef96
|
|
|
20
20
|
/** Private key for the Mock decrypt result signer account */
|
|
21
21
|
export const MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY =
|
|
22
22
|
'0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d' as const;
|
|
23
|
+
|
|
24
|
+
/** Maximum total bits for ZK proof packing */
|
|
25
|
+
export const TFHE_RS_ZK_MAX_BITS = 2048 as const;
|
|
26
|
+
|
|
27
|
+
/** Size limit for safe_serialize/safe_deserialize (1 GB) */
|
|
28
|
+
export const TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT = BigInt(1 << 30);
|
|
29
|
+
|
|
30
|
+
/** TFHE.rs key version (invalidates cached keys) */
|
|
31
|
+
export const TFHE_RS_KEY_VERSION = 2;
|
|
@@ -8,6 +8,11 @@ import { CofheError, CofheErrorCode } from '../error.js';
|
|
|
8
8
|
import { MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY } from '../consts.js';
|
|
9
9
|
import { MOCKS_THRESHOLD_NETWORK_ADDRESS } from '../consts.js';
|
|
10
10
|
|
|
11
|
+
const UINT_TYPE_MASK = 0x7fn;
|
|
12
|
+
const TYPE_BYTE_OFFSET = 8n;
|
|
13
|
+
|
|
14
|
+
const getEncryptionTypeFromCtHash = (ctHash: bigint) => Number((ctHash >> TYPE_BYTE_OFFSET) & UINT_TYPE_MASK);
|
|
15
|
+
|
|
11
16
|
export type DecryptForTxMocksResult = {
|
|
12
17
|
ctHash: bigint | string;
|
|
13
18
|
decryptedValue: bigint;
|
|
@@ -64,9 +69,15 @@ export async function cofheMocksDecryptForTx(
|
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
// decryptForTx returns plaintext directly (no sealing/unsealing needed)
|
|
67
|
-
// Generate a mock threshold network signature
|
|
68
|
-
//
|
|
69
|
-
const
|
|
72
|
+
// Generate a mock threshold network signature using the same payload format
|
|
73
|
+
// that TaskManager expects in production and in mocks.
|
|
74
|
+
const chainId = publicClient.chain?.id ?? (await publicClient.getChainId());
|
|
75
|
+
const normalizedCtHash = BigInt(ctHash);
|
|
76
|
+
const encryptionType = getEncryptionTypeFromCtHash(normalizedCtHash);
|
|
77
|
+
const packed = encodePacked(
|
|
78
|
+
['uint256', 'uint32', 'uint64', 'uint256'],
|
|
79
|
+
[decryptedValue, encryptionType, BigInt(chainId), normalizedCtHash]
|
|
80
|
+
);
|
|
70
81
|
const messageHash = keccak256(packed);
|
|
71
82
|
|
|
72
83
|
// Raw digest signature (no EIP-191 prefix). Must verify against OpenZeppelin ECDSA.recover(messageHash, signature).
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
import { hardhat } from '@/chains';
|
|
3
3
|
import { type Permit, type Permission, PermitUtils } from '@/permits';
|
|
4
4
|
|
|
5
|
-
import { FheTypes } from '../types
|
|
6
|
-
import { getThresholdNetworkUrlOrThrow } from '../config
|
|
7
|
-
import { CofheError, CofheErrorCode } from '../error
|
|
8
|
-
import { permits } from '../permits
|
|
9
|
-
import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder
|
|
10
|
-
import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx
|
|
11
|
-
import { getPublicClientChainID, sleep } from '../utils
|
|
12
|
-
import {
|
|
5
|
+
import { FheTypes } from '../types';
|
|
6
|
+
import { getThresholdNetworkUrlOrThrow } from '../config';
|
|
7
|
+
import { CofheError, CofheErrorCode } from '../error';
|
|
8
|
+
import { permits } from '../permits';
|
|
9
|
+
import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder';
|
|
10
|
+
import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx';
|
|
11
|
+
import { getPublicClientChainID, sleep } from '../utils';
|
|
12
|
+
import { type DecryptPollCallbackFunction } from '../types';
|
|
13
|
+
import { tnDecryptV2 } from './tnDecryptV2';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* API
|
|
@@ -60,6 +61,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
60
61
|
private permitHash?: string;
|
|
61
62
|
private permit?: Permit;
|
|
62
63
|
private permitSelection: DecryptForTxPermitSelection = 'unset';
|
|
64
|
+
private pollCallback?: DecryptPollCallbackFunction;
|
|
63
65
|
|
|
64
66
|
constructor(params: DecryptForTxBuilderParams) {
|
|
65
67
|
super({
|
|
@@ -124,6 +126,13 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
124
126
|
return this.account;
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
onPoll(this: DecryptForTxBuilderUnset, callback: DecryptPollCallbackFunction): DecryptForTxBuilderUnset;
|
|
130
|
+
onPoll(this: DecryptForTxBuilderSelected, callback: DecryptPollCallbackFunction): DecryptForTxBuilderSelected;
|
|
131
|
+
onPoll(callback: DecryptPollCallbackFunction): DecryptForTxBuilder {
|
|
132
|
+
this.pollCallback = callback;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
127
136
|
/**
|
|
128
137
|
* Select "use permit" mode.
|
|
129
138
|
*
|
|
@@ -291,7 +300,13 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
291
300
|
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
292
301
|
|
|
293
302
|
const permission = permit ? PermitUtils.getPermission(permit, true) : null;
|
|
294
|
-
const { decryptedValue, signature } = await
|
|
303
|
+
const { decryptedValue, signature } = await tnDecryptV2({
|
|
304
|
+
ctHash: this.ctHash,
|
|
305
|
+
chainId: this.chainId,
|
|
306
|
+
permission,
|
|
307
|
+
thresholdNetworkUrl,
|
|
308
|
+
onPoll: this.pollCallback,
|
|
309
|
+
});
|
|
295
310
|
|
|
296
311
|
return {
|
|
297
312
|
ctHash: this.ctHash,
|
|
@@ -315,7 +330,6 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
315
330
|
if (permit !== null) {
|
|
316
331
|
// Ensure permit validity
|
|
317
332
|
PermitUtils.validate(permit);
|
|
318
|
-
PermitUtils.isValid(permit);
|
|
319
333
|
|
|
320
334
|
// Extract chainId from signed permit
|
|
321
335
|
const chainId = permit._signedDomain!.chainId;
|
|
@@ -12,6 +12,7 @@ import { cofheMocksDecryptForView } from './cofheMocksDecryptForView.js';
|
|
|
12
12
|
// import { tnSealOutputV1 } from './tnSealOutputV1.js';
|
|
13
13
|
import { tnSealOutputV2 } from './tnSealOutputV2.js';
|
|
14
14
|
import { sleep } from '../utils.js';
|
|
15
|
+
import { type DecryptPollCallbackFunction } from '../types.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* API
|
|
@@ -46,6 +47,7 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
46
47
|
private utype: U;
|
|
47
48
|
private permitHash?: string;
|
|
48
49
|
private permit?: Permit;
|
|
50
|
+
private pollCallback?: DecryptPollCallbackFunction;
|
|
49
51
|
|
|
50
52
|
constructor(params: DecryptForViewBuilderParams<U>) {
|
|
51
53
|
super({
|
|
@@ -109,6 +111,11 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
109
111
|
return this.account;
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
onPoll(callback: DecryptPollCallbackFunction): DecryptForViewBuilder<U> {
|
|
115
|
+
this.pollCallback = callback;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
112
119
|
/**
|
|
113
120
|
* Select "use permit" mode (optional).
|
|
114
121
|
*
|
|
@@ -264,7 +271,13 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
264
271
|
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
265
272
|
const permission = PermitUtils.getPermission(permit, true);
|
|
266
273
|
// const sealed = await tnSealOutputV1(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
|
|
267
|
-
const sealed = await tnSealOutputV2(
|
|
274
|
+
const sealed = await tnSealOutputV2({
|
|
275
|
+
ctHash: this.ctHash,
|
|
276
|
+
chainId: this.chainId,
|
|
277
|
+
permission,
|
|
278
|
+
thresholdNetworkUrl,
|
|
279
|
+
onPoll: this.pollCallback,
|
|
280
|
+
});
|
|
268
281
|
return PermitUtils.unseal(permit, sealed);
|
|
269
282
|
}
|
|
270
283
|
|
|
@@ -297,14 +310,8 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
|
297
310
|
const permit = await this.getResolvedPermit();
|
|
298
311
|
|
|
299
312
|
// Ensure permit validity
|
|
300
|
-
// TODO: This doesn't validate permit expiration
|
|
301
|
-
// TODO: This doesn't throw, returns a validation result instead
|
|
302
313
|
PermitUtils.validate(permit);
|
|
303
314
|
|
|
304
|
-
// TODO: Add this further validation step for the permit
|
|
305
|
-
// TODO: Ensure this throws if the permit is invalid
|
|
306
|
-
PermitUtils.isValid(permit);
|
|
307
|
-
|
|
308
315
|
// Extract chainId from signed permit
|
|
309
316
|
// Use this chainId to fetch the threshold network URL since this.chainId may be undefined
|
|
310
317
|
const chainId = permit._signedDomain!.chainId;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function computeMinuteRampPollIntervalMs(
|
|
2
|
+
elapsedMs: number,
|
|
3
|
+
params: {
|
|
4
|
+
minIntervalMs: number;
|
|
5
|
+
maxIntervalMs: number;
|
|
6
|
+
}
|
|
7
|
+
): number {
|
|
8
|
+
// Increase interval by 1 second every minute: 1s, 2s, 3s... capped.
|
|
9
|
+
const elapsedSeconds = Math.floor(elapsedMs / 1000);
|
|
10
|
+
const intervalSeconds = 1 + Math.floor(elapsedSeconds / 60);
|
|
11
|
+
const intervalMs = intervalSeconds * 1000;
|
|
12
|
+
|
|
13
|
+
return Math.min(params.maxIntervalMs, Math.max(params.minIntervalMs, intervalMs));
|
|
14
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CofheError, CofheErrorCode } from '../error';
|
|
2
|
+
import { parseSignature, serializeSignature } from 'viem';
|
|
3
|
+
|
|
4
|
+
export function normalizeTnSignature(signature: unknown): `0x${string}` {
|
|
5
|
+
if (typeof signature !== 'string') {
|
|
6
|
+
throw new CofheError({
|
|
7
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
8
|
+
message: 'decrypt response missing signature',
|
|
9
|
+
context: {
|
|
10
|
+
signature,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const trimmed = signature.trim();
|
|
16
|
+
if (trimmed.length === 0) {
|
|
17
|
+
throw new CofheError({
|
|
18
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
19
|
+
message: 'decrypt response returned empty signature',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const prefixed = trimmed.startsWith('0x') ? (trimmed as `0x${string}`) : (`0x${trimmed}` as `0x${string}`);
|
|
24
|
+
const parsed = parseSignature(prefixed);
|
|
25
|
+
return serializeSignature(parsed);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseDecryptedBytesToBigInt(decrypted: unknown): bigint {
|
|
29
|
+
if (!Array.isArray(decrypted)) {
|
|
30
|
+
throw new CofheError({
|
|
31
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
32
|
+
message: 'decrypt response field <decrypted> must be a byte array',
|
|
33
|
+
context: {
|
|
34
|
+
decrypted,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (decrypted.length === 0) {
|
|
40
|
+
throw new CofheError({
|
|
41
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
42
|
+
message: 'decrypt response field <decrypted> was an empty byte array',
|
|
43
|
+
context: {
|
|
44
|
+
decrypted,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let hex = '';
|
|
50
|
+
for (const b of decrypted as unknown[]) {
|
|
51
|
+
if (typeof b !== 'number' || !Number.isInteger(b) || b < 0 || b > 255) {
|
|
52
|
+
throw new CofheError({
|
|
53
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
54
|
+
message: 'decrypt response field <decrypted> contained a non-byte value',
|
|
55
|
+
context: {
|
|
56
|
+
badElement: b,
|
|
57
|
+
decrypted,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
hex += b.toString(16).padStart(2, '0');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return BigInt(`0x${hex}`);
|
|
65
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type Permission } from '@/permits';
|
|
2
2
|
|
|
3
|
-
import { CofheError, CofheErrorCode } from '../error
|
|
4
|
-
import {
|
|
3
|
+
import { CofheError, CofheErrorCode } from '../error';
|
|
4
|
+
import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
type TnDecryptResponseV1 = {
|
|
7
7
|
// TN returns bytes in big-endian order, e.g. [0,0,0,42]
|
|
8
8
|
decrypted: number[];
|
|
9
9
|
signature: string;
|
|
@@ -11,70 +11,7 @@ type TnDecryptResponse = {
|
|
|
11
11
|
error_message: string | null;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
if (typeof signature !== 'string') {
|
|
16
|
-
throw new CofheError({
|
|
17
|
-
code: CofheErrorCode.DecryptReturnedNull,
|
|
18
|
-
message: 'decrypt response missing signature',
|
|
19
|
-
context: {
|
|
20
|
-
signature,
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const trimmed = signature.trim();
|
|
26
|
-
if (trimmed.length === 0) {
|
|
27
|
-
throw new CofheError({
|
|
28
|
-
code: CofheErrorCode.DecryptReturnedNull,
|
|
29
|
-
message: 'decrypt response returned empty signature',
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const prefixed = trimmed.startsWith('0x') ? (trimmed as `0x${string}`) : (`0x${trimmed}` as `0x${string}`);
|
|
34
|
-
const parsed = parseSignature(prefixed);
|
|
35
|
-
return serializeSignature(parsed);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function parseDecryptedBytesToBigInt(decrypted: unknown): bigint {
|
|
39
|
-
if (!Array.isArray(decrypted)) {
|
|
40
|
-
throw new CofheError({
|
|
41
|
-
code: CofheErrorCode.DecryptReturnedNull,
|
|
42
|
-
message: 'decrypt response field <decrypted> must be a byte array',
|
|
43
|
-
context: {
|
|
44
|
-
decrypted,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (decrypted.length === 0) {
|
|
50
|
-
throw new CofheError({
|
|
51
|
-
code: CofheErrorCode.DecryptReturnedNull,
|
|
52
|
-
message: 'decrypt response field <decrypted> was an empty byte array',
|
|
53
|
-
context: {
|
|
54
|
-
decrypted,
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let hex = '';
|
|
60
|
-
for (const b of decrypted as unknown[]) {
|
|
61
|
-
if (typeof b !== 'number' || !Number.isInteger(b) || b < 0 || b > 255) {
|
|
62
|
-
throw new CofheError({
|
|
63
|
-
code: CofheErrorCode.DecryptReturnedNull,
|
|
64
|
-
message: 'decrypt response field <decrypted> contained a non-byte value',
|
|
65
|
-
context: {
|
|
66
|
-
badElement: b,
|
|
67
|
-
decrypted,
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
hex += b.toString(16).padStart(2, '0');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return BigInt(`0x${hex}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function assertTnDecryptResponse(value: unknown): TnDecryptResponse {
|
|
14
|
+
function assertTnDecryptResponseV1(value: unknown): TnDecryptResponseV1 {
|
|
78
15
|
if (value == null || typeof value !== 'object') {
|
|
79
16
|
throw new CofheError({
|
|
80
17
|
code: CofheErrorCode.DecryptFailed,
|
|
@@ -128,7 +65,7 @@ function assertTnDecryptResponse(value: unknown): TnDecryptResponse {
|
|
|
128
65
|
};
|
|
129
66
|
}
|
|
130
67
|
|
|
131
|
-
export async function
|
|
68
|
+
export async function tnDecryptV1(
|
|
132
69
|
ctHash: bigint | string,
|
|
133
70
|
chainId: number,
|
|
134
71
|
permission: Permission | null,
|
|
@@ -213,7 +150,7 @@ export async function tnDecrypt(
|
|
|
213
150
|
});
|
|
214
151
|
}
|
|
215
152
|
|
|
216
|
-
const decryptResponse =
|
|
153
|
+
const decryptResponse = assertTnDecryptResponseV1(rawJson);
|
|
217
154
|
|
|
218
155
|
if (decryptResponse.error_message) {
|
|
219
156
|
throw new CofheError({
|
|
@@ -228,7 +165,7 @@ export async function tnDecrypt(
|
|
|
228
165
|
}
|
|
229
166
|
|
|
230
167
|
const decryptedValue = parseDecryptedBytesToBigInt(decryptResponse.decrypted);
|
|
231
|
-
const signature =
|
|
168
|
+
const signature = normalizeTnSignature(decryptResponse.signature);
|
|
232
169
|
|
|
233
170
|
return { decryptedValue, signature };
|
|
234
171
|
}
|