@cofhe/sdk 0.3.1 → 0.4.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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @cofhe/sdk Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Patch Changes
6
+
7
+ - e446642: Switch `decryptForTx` to Threshold Network v2 decrypt (submit + poll)
8
+
9
+ ## 0.3.2
10
+
11
+ ### Patch Changes
12
+
13
+ - d4e86ea: Aligns with CTA encrypted variables bytes32 representation.
14
+
15
+ - **@cofhe/hardhat-plugin**: `hre.cofhe.mocks.getTestBed()`, `getMockTaskManager()`, `getMockACL()`, `getMockThresholdNetwork()`, and `getMockZkVerifier()` now return typed contracts (typechain interfaces) instead of untyped `Contract`. `getPlaintext(ctHash)` and `expectPlaintext(ctHash, value)` now accept bytes32 ctHashes as `string` support cofhe-contracts 0.1.0 CTA changes.
16
+ - **@cofhe/mock-contracts**: Export typechain-generated contract types (`TestBed`, `MockACL`, `MockTaskManager`, `MockZkVerifier`, `MockThresholdNetwork`) for use with the hardhat plugin. Typechain is run from artifact ABIs only; factory files are not generated.
17
+ - **@cofhe/abi**: CTA-related types use `bytes32` (string) instead of `uint256`. Decryption and return-type helpers aligned with cofhe-contracts 0.1.0.
18
+ - **@cofhe/sdk**: Decryption APIs (`decryptForTx`, `decryptForView`, and related builders) now also accept `string` for ciphertext hashes (bytes32) as well as `bigint`.
19
+
20
+ - 0feaf3f: `cofheClient.decryptForTx` returns a ready-to-use signature
21
+
3
22
  ## 0.3.1
4
23
 
5
24
  ### Patch Changes
@@ -164,6 +164,6 @@ describe('Ethers6Adapter', () => {
164
164
  expect(isInsufficientFunds).toBe(true);
165
165
  console.log('Expected error (insufficient funds or method not supported):', error.message);
166
166
  }
167
- }, 10000);
167
+ }, 20000);
168
168
  });
169
169
  });
package/core/client.ts CHANGED
@@ -146,7 +146,7 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
146
146
  });
147
147
  }
148
148
 
149
- function decryptForView<U extends FheTypes>(ctHash: bigint, utype: U): DecryptForViewBuilder<U> {
149
+ function decryptForView<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U> {
150
150
  const state = connectStore.getState();
151
151
 
152
152
  return new DecryptForViewBuilder({
@@ -163,7 +163,7 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
163
163
  });
164
164
  }
165
165
 
166
- function decryptForTx(ctHash: bigint): DecryptForTxBuilderUnset {
166
+ function decryptForTx(ctHash: bigint | string): DecryptForTxBuilderUnset {
167
167
  const state = connectStore.getState();
168
168
 
169
169
  return new DecryptForTxBuilder({
@@ -250,22 +250,22 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
250
250
  },
251
251
 
252
252
  // Retrieval methods (auto-fill chainId/account)
253
- getPermit: async (hash: string, chainId?: number, account?: string) => {
253
+ getPermit: (hash: string, chainId?: number, account?: string) => {
254
254
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
255
255
  return permits.getPermit(_chainId, _account, hash);
256
256
  },
257
257
 
258
- getPermits: async (chainId?: number, account?: string) => {
258
+ getPermits: (chainId?: number, account?: string) => {
259
259
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
260
260
  return permits.getPermits(_chainId, _account);
261
261
  },
262
262
 
263
- getActivePermit: async (chainId?: number, account?: string) => {
263
+ getActivePermit: (chainId?: number, account?: string) => {
264
264
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
265
265
  return permits.getActivePermit(_chainId, _account);
266
266
  },
267
267
 
268
- getActivePermitHash: async (chainId?: number, account?: string) => {
268
+ getActivePermitHash: (chainId?: number, account?: string) => {
269
269
  const { chainId: _chainId, account: _account } = _getChainIdAndAccount(chainId, account);
270
270
  return permits.getActivePermitHash(_chainId, _account);
271
271
  },
@@ -48,9 +48,9 @@ export type CofheClient<TConfig extends CofheConfig = CofheConfig> = {
48
48
  /**
49
49
  * @deprecated Use `decryptForView` instead. Kept for backward compatibility.
50
50
  */
51
- decryptHandle<U extends FheTypes>(ctHash: bigint, utype: U): DecryptForViewBuilder<U>;
52
- decryptForView<U extends FheTypes>(ctHash: bigint, utype: U): DecryptForViewBuilder<U>;
53
- decryptForTx(ctHash: bigint): DecryptForTxBuilderUnset;
51
+ decryptHandle<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
52
+ decryptForView<U extends FheTypes>(ctHash: bigint | string, utype: U): DecryptForViewBuilder<U>;
53
+ decryptForTx(ctHash: bigint | string): DecryptForTxBuilderUnset;
54
54
  permits: CofheClientPermits;
55
55
  };
56
56
 
@@ -84,10 +84,10 @@ export type CofheClientPermits = {
84
84
  ) => Promise<RecipientPermit>;
85
85
 
86
86
  // Retrieval methods (chainId/account optional)
87
- getPermit: (hash: string, chainId?: number, account?: string) => Promise<Permit | undefined>;
88
- getPermits: (chainId?: number, account?: string) => Promise<Record<string, Permit>>;
89
- getActivePermit: (chainId?: number, account?: string) => Promise<Permit | undefined>;
90
- getActivePermitHash: (chainId?: number, account?: string) => Promise<string | undefined>;
87
+ getPermit: (hash: string, chainId?: number, account?: string) => Permit | undefined;
88
+ getPermits: (chainId?: number, account?: string) => Record<string, Permit>;
89
+ getActivePermit: (chainId?: number, account?: string) => Permit | undefined;
90
+ getActivePermitHash: (chainId?: number, account?: string) => string | undefined;
91
91
 
92
92
  // Get or create methods (get active or create new, chainId/account optional)
93
93
  getOrCreateSelfPermit: (chainId?: number, account?: string, options?: CreateSelfPermitOptions) => Promise<Permit>;
@@ -1,8 +1,7 @@
1
1
  import { type Permit, PermitUtils } from '@/permits';
2
2
 
3
- import { encodePacked, keccak256, pad, toHex, type Hex, type PublicClient } from 'viem';
3
+ import { encodePacked, keccak256, type PublicClient } from 'viem';
4
4
  import { sign } from 'viem/accounts';
5
- import { sleep } from '../utils.js';
6
5
  import { MockThresholdNetworkAbi } from './MockThresholdNetworkAbi.js';
7
6
  import { FheTypes } from '../types.js';
8
7
  import { CofheError, CofheErrorCode } from '../error.js';
@@ -10,25 +9,23 @@ import { MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY } from '../consts.js';
10
9
  import { MOCKS_THRESHOLD_NETWORK_ADDRESS } from '../consts.js';
11
10
 
12
11
  export type DecryptForTxMocksResult = {
13
- ctHash: bigint;
12
+ ctHash: bigint | string;
14
13
  decryptedValue: bigint;
15
- signature: string;
14
+ signature: `0x${string}`;
16
15
  };
17
16
 
18
17
  export async function cofheMocksDecryptForTx(
19
- ctHash: bigint,
18
+ ctHash: bigint | string,
20
19
  utype: FheTypes,
21
20
  permit: Permit | null,
22
- publicClient: PublicClient,
23
- mocksDecryptForTxDelay: number
21
+ publicClient: PublicClient
24
22
  ): 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);
23
+ let allowed: boolean;
24
+ let error: string;
25
+ let decryptedValue: bigint;
29
26
 
27
+ // With permit
30
28
  if (permit !== null) {
31
- // With permit
32
29
  let permission = PermitUtils.getPermission(permit, true);
33
30
  const permissionWithBigInts = {
34
31
  ...permission,
@@ -36,66 +33,22 @@ export async function cofheMocksDecryptForTx(
36
33
  validatorId: BigInt(permission.validatorId),
37
34
  };
38
35
 
39
- const [allowed, error, result] = await publicClient.readContract({
36
+ [allowed, error, decryptedValue] = await publicClient.readContract({
40
37
  address: MOCKS_THRESHOLD_NETWORK_ADDRESS,
41
38
  abi: MockThresholdNetworkAbi,
42
39
  functionName: 'decryptForTxWithPermit',
43
- args: [ctHash, permissionWithBigInts],
40
+ args: [BigInt(ctHash), permissionWithBigInts],
44
41
  });
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',
42
+ } else {
43
+ // Without permit (global allowance)
44
+ [allowed, error, decryptedValue] = await publicClient.readContract({
45
+ address: MOCKS_THRESHOLD_NETWORK_ADDRESS,
46
+ abi: MockThresholdNetworkAbi,
47
+ functionName: 'decryptForTxWithoutPermit',
48
+ args: [BigInt(ctHash)],
81
49
  });
82
- const signature = signatureHex.slice(2); // no 0x prefix
83
-
84
- return {
85
- ctHash,
86
- decryptedValue: BigInt(result),
87
- signature,
88
- };
89
50
  }
90
51
 
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
52
  if (error != '') {
100
53
  throw new CofheError({
101
54
  code: CofheErrorCode.DecryptFailed,
@@ -113,30 +66,19 @@ export async function cofheMocksDecryptForTx(
113
66
  // decryptForTx returns plaintext directly (no sealing/unsealing needed)
114
67
  // Generate a mock threshold network signature (in production, this would be the actual signature)
115
68
  // 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
- );
69
+ const packed = encodePacked(['uint256', 'uint256'], [BigInt(ctHash), decryptedValue]);
127
70
  const messageHash = keccak256(packed);
128
71
 
129
72
  // Raw digest signature (no EIP-191 prefix). Must verify against OpenZeppelin ECDSA.recover(messageHash, signature).
130
- const signatureHex = await sign({
73
+ const signature = await sign({
131
74
  hash: messageHash,
132
75
  privateKey: MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY,
133
76
  to: 'hex',
134
77
  });
135
- const signature = signatureHex.slice(2); // no 0x prefix
136
78
 
137
79
  return {
138
80
  ctHash,
139
- decryptedValue: BigInt(result),
81
+ decryptedValue,
140
82
  signature,
141
83
  };
142
84
  }
@@ -1,24 +1,17 @@
1
1
  import { type Permit, PermitUtils } from '@/permits';
2
2
 
3
3
  import { type PublicClient } from 'viem';
4
- import { sleep } from '../utils.js';
5
4
  import { MockThresholdNetworkAbi } from './MockThresholdNetworkAbi.js';
6
5
  import { FheTypes } from '../types.js';
7
6
  import { CofheError, CofheErrorCode } from '../error.js';
8
7
  import { MOCKS_THRESHOLD_NETWORK_ADDRESS } from '../consts.js';
9
8
 
10
9
  export async function cofheMocksDecryptForView(
11
- ctHash: bigint,
10
+ ctHash: bigint | string,
12
11
  utype: FheTypes,
13
12
  permit: Permit,
14
- publicClient: PublicClient,
15
- mocksDecryptDelay: number
13
+ publicClient: PublicClient
16
14
  ): Promise<bigint> {
17
- // Configurable delay before decrypting the output to simulate the CoFHE decrypt processing time
18
- // Recommended 1000ms on web
19
- // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
20
- if (mocksDecryptDelay > 0) await sleep(mocksDecryptDelay);
21
-
22
15
  const permission = PermitUtils.getPermission(permit, true);
23
16
  const permissionWithBigInts = {
24
17
  ...permission,
@@ -30,7 +23,7 @@ export async function cofheMocksDecryptForView(
30
23
  address: MOCKS_THRESHOLD_NETWORK_ADDRESS,
31
24
  abi: MockThresholdNetworkAbi,
32
25
  functionName: 'querySealOutput',
33
- args: [ctHash, BigInt(utype), permissionWithBigInts],
26
+ args: [BigInt(ctHash), BigInt(utype), permissionWithBigInts],
34
27
  });
35
28
 
36
29
  if (error != '') {
@@ -2,14 +2,14 @@
2
2
  import { hardhat } from '@/chains';
3
3
  import { type Permit, type Permission, PermitUtils } from '@/permits';
4
4
 
5
- import { FheTypes } from '../types.js';
6
- import { getThresholdNetworkUrlOrThrow } from '../config.js';
7
- import { CofheError, CofheErrorCode } from '../error.js';
8
- import { permits } from '../permits.js';
9
- import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
10
- import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx.js';
11
- import { getPublicClientChainID } from '../utils.js';
12
- import { tnDecrypt } from './tnDecrypt.js';
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 { tnDecryptV2 } from './tnDecryptV2';
13
13
 
14
14
  /**
15
15
  * API
@@ -36,13 +36,13 @@ import { tnDecrypt } from './tnDecrypt.js';
36
36
  type DecryptForTxPermitSelection = 'unset' | 'with-permit' | 'without-permit';
37
37
 
38
38
  type DecryptForTxBuilderParams = BaseBuilderParams & {
39
- ctHash: bigint;
39
+ ctHash: bigint | string;
40
40
  };
41
41
 
42
42
  export type DecryptForTxResult = {
43
- ctHash: bigint;
43
+ ctHash: bigint | string;
44
44
  decryptedValue: bigint;
45
- signature: string; // Threshold network signature for publishDecryptResult
45
+ signature: `0x${string}`; // Threshold network signature for publishDecryptResult
46
46
  };
47
47
 
48
48
  /**
@@ -56,7 +56,7 @@ export type DecryptForTxBuilderUnset = Omit<DecryptForTxBuilder, 'execute'>;
56
56
  export type DecryptForTxBuilderSelected = Omit<DecryptForTxBuilder, 'withPermit' | 'withoutPermit'>;
57
57
 
58
58
  export class DecryptForTxBuilder extends BaseBuilder {
59
- private ctHash: bigint;
59
+ private ctHash: bigint | string;
60
60
  private permitHash?: string;
61
61
  private permit?: Permit;
62
62
  private permitSelection: DecryptForTxPermitSelection = 'unset';
@@ -271,8 +271,13 @@ export class DecryptForTxBuilder extends BaseBuilder {
271
271
  private async mocksDecryptForTx(permit: Permit | null): Promise<DecryptForTxResult> {
272
272
  this.assertPublicClient();
273
273
 
274
+ // Configurable delay before decrypting to simulate the CoFHE decrypt processing time
275
+ // Recommended 1000ms on web
276
+ // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
274
277
  const delay = this.config.mocks.decryptDelay;
275
- const result = await cofheMocksDecryptForTx(this.ctHash, 0 as FheTypes, permit, this.publicClient, delay);
278
+ if (delay > 0) await sleep(delay);
279
+
280
+ const result = await cofheMocksDecryptForTx(this.ctHash, 0 as FheTypes, permit, this.publicClient);
276
281
  return result;
277
282
  }
278
283
 
@@ -286,7 +291,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
286
291
  const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
287
292
 
288
293
  const permission = permit ? PermitUtils.getPermission(permit, true) : null;
289
- const { decryptedValue, signature } = await tnDecrypt(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
294
+ const { decryptedValue, signature } = await tnDecryptV2(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
290
295
 
291
296
  return {
292
297
  ctHash: this.ctHash,
@@ -11,7 +11,7 @@ import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
11
11
  import { cofheMocksDecryptForView } from './cofheMocksDecryptForView.js';
12
12
  // import { tnSealOutputV1 } from './tnSealOutputV1.js';
13
13
  import { tnSealOutputV2 } from './tnSealOutputV2.js';
14
- import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx.js';
14
+ import { sleep } from '../utils.js';
15
15
 
16
16
  /**
17
17
  * API
@@ -35,14 +35,14 @@ import { cofheMocksDecryptForTx } from './cofheMocksDecryptForTx.js';
35
35
  */
36
36
 
37
37
  type DecryptForViewBuilderParams<U extends FheTypes> = BaseBuilderParams & {
38
- ctHash: bigint;
38
+ ctHash: bigint | string;
39
39
  utype: U;
40
40
  permitHash?: string;
41
41
  permit?: Permit;
42
42
  };
43
43
 
44
44
  export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
45
- private ctHash: bigint;
45
+ private ctHash: bigint | string;
46
46
  private utype: U;
47
47
  private permitHash?: string;
48
48
  private permit?: Permit;
@@ -245,8 +245,13 @@ export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
245
245
  private async mocksSealOutput(permit: Permit): Promise<bigint> {
246
246
  this.assertPublicClient();
247
247
 
248
+ // Configurable delay before decrypting the output to simulate the CoFHE decrypt processing time
249
+ // Recommended 1000ms on web
250
+ // Recommended 0ms on hardhat (will be called during tests no need for fake delay)
248
251
  const mocksDecryptDelay = this.config.mocks.decryptDelay;
249
- return cofheMocksDecryptForView(this.ctHash, this.utype, permit, this.publicClient, mocksDecryptDelay);
252
+ if (mocksDecryptDelay > 0) await sleep(mocksDecryptDelay);
253
+
254
+ return cofheMocksDecryptForView(this.ctHash, this.utype, permit, this.publicClient);
250
255
  }
251
256
 
252
257
  /**
@@ -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,8 +1,9 @@
1
1
  import { type Permission } from '@/permits';
2
2
 
3
- import { CofheError, CofheErrorCode } from '../error.js';
3
+ import { CofheError, CofheErrorCode } from '../error';
4
+ import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
4
5
 
5
- type TnDecryptResponse = {
6
+ type TnDecryptResponseV1 = {
6
7
  // TN returns bytes in big-endian order, e.g. [0,0,0,42]
7
8
  decrypted: number[];
8
9
  signature: string;
@@ -10,69 +11,7 @@ type TnDecryptResponse = {
10
11
  error_message: string | null;
11
12
  };
12
13
 
13
- function normalizeSignature(signature: unknown): string {
14
- if (typeof signature !== 'string') {
15
- throw new CofheError({
16
- code: CofheErrorCode.DecryptReturnedNull,
17
- message: 'decrypt response missing signature',
18
- context: {
19
- signature,
20
- },
21
- });
22
- }
23
-
24
- const trimmed = signature.trim();
25
- if (trimmed.length === 0) {
26
- throw new CofheError({
27
- code: CofheErrorCode.DecryptReturnedNull,
28
- message: 'decrypt response returned empty signature',
29
- });
30
- }
31
-
32
- // SDK uses "no-0x" signatures in mocks/tests; normalize to that format.
33
- return trimmed.startsWith('0x') ? trimmed.slice(2) : trimmed;
34
- }
35
-
36
- function parseDecryptedBytesToBigInt(decrypted: unknown): bigint {
37
- if (!Array.isArray(decrypted)) {
38
- throw new CofheError({
39
- code: CofheErrorCode.DecryptReturnedNull,
40
- message: 'decrypt response field <decrypted> must be a byte array',
41
- context: {
42
- decrypted,
43
- },
44
- });
45
- }
46
-
47
- if (decrypted.length === 0) {
48
- throw new CofheError({
49
- code: CofheErrorCode.DecryptReturnedNull,
50
- message: 'decrypt response field <decrypted> was an empty byte array',
51
- context: {
52
- decrypted,
53
- },
54
- });
55
- }
56
-
57
- let hex = '';
58
- for (const b of decrypted as unknown[]) {
59
- if (typeof b !== 'number' || !Number.isInteger(b) || b < 0 || b > 255) {
60
- throw new CofheError({
61
- code: CofheErrorCode.DecryptReturnedNull,
62
- message: 'decrypt response field <decrypted> contained a non-byte value',
63
- context: {
64
- badElement: b,
65
- decrypted,
66
- },
67
- });
68
- }
69
- hex += b.toString(16).padStart(2, '0');
70
- }
71
-
72
- return BigInt(`0x${hex}`);
73
- }
74
-
75
- function assertTnDecryptResponse(value: unknown): TnDecryptResponse {
14
+ function assertTnDecryptResponseV1(value: unknown): TnDecryptResponseV1 {
76
15
  if (value == null || typeof value !== 'object') {
77
16
  throw new CofheError({
78
17
  code: CofheErrorCode.DecryptFailed,
@@ -126,18 +65,18 @@ function assertTnDecryptResponse(value: unknown): TnDecryptResponse {
126
65
  };
127
66
  }
128
67
 
129
- export async function tnDecrypt(
130
- ctHash: bigint,
68
+ export async function tnDecryptV1(
69
+ ctHash: bigint | string,
131
70
  chainId: number,
132
71
  permission: Permission | null,
133
72
  thresholdNetworkUrl: string
134
- ): Promise<{ decryptedValue: bigint; signature: string }> {
73
+ ): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
135
74
  const body: {
136
75
  ct_tempkey: string;
137
76
  host_chain_id: number;
138
77
  permit?: Permission;
139
78
  } = {
140
- ct_tempkey: ctHash.toString(16).padStart(64, '0'),
79
+ ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
141
80
  host_chain_id: chainId,
142
81
  };
143
82
 
@@ -211,7 +150,7 @@ export async function tnDecrypt(
211
150
  });
212
151
  }
213
152
 
214
- const decryptResponse = assertTnDecryptResponse(rawJson);
153
+ const decryptResponse = assertTnDecryptResponseV1(rawJson);
215
154
 
216
155
  if (decryptResponse.error_message) {
217
156
  throw new CofheError({
@@ -226,7 +165,7 @@ export async function tnDecrypt(
226
165
  }
227
166
 
228
167
  const decryptedValue = parseDecryptedBytesToBigInt(decryptResponse.decrypted);
229
- const signature = normalizeSignature(decryptResponse.signature);
168
+ const signature = normalizeTnSignature(decryptResponse.signature);
230
169
 
231
170
  return { decryptedValue, signature };
232
171
  }