@cofhe/sdk 0.3.2 → 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 +6 -0
- package/core/client.ts +4 -4
- package/core/clientTypes.ts +4 -4
- package/core/decrypt/decryptForTxBuilder.ts +9 -9
- package/core/decrypt/tnDecryptUtils.ts +65 -0
- package/core/decrypt/{tnDecrypt.ts → tnDecryptV1.ts} +7 -70
- package/core/decrypt/tnDecryptV2.ts +343 -0
- package/core/permits.ts +3 -3
- package/core/types.ts +8 -0
- package/dist/{chunk-LWMRB6SD.js → chunk-MXND5SVN.js} +201 -62
- package/dist/{clientTypes-Y43CKbOz.d.cts → clientTypes-ACVWbrXL.d.cts} +15 -7
- package/dist/{clientTypes-PQha8zes.d.ts → clientTypes-kkrRdawm.d.ts} +15 -7
- package/dist/core.cjs +201 -62
- package/dist/core.d.cts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +1 -1
- package/dist/node.cjs +201 -62
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/web.cjs +201 -62
- package/dist/web.d.cts +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/core/client.ts
CHANGED
|
@@ -250,22 +250,22 @@ export function createCofheClientBase<TConfig extends CofheConfig>(
|
|
|
250
250
|
},
|
|
251
251
|
|
|
252
252
|
// Retrieval methods (auto-fill chainId/account)
|
|
253
|
-
getPermit:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
},
|
package/core/clientTypes.ts
CHANGED
|
@@ -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) =>
|
|
88
|
-
getPermits: (chainId?: number, account?: string) =>
|
|
89
|
-
getActivePermit: (chainId?: number, account?: string) =>
|
|
90
|
-
getActivePermitHash: (chainId?: number, account?: string) =>
|
|
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>;
|
|
@@ -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
|
|
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 { tnDecryptV2 } from './tnDecryptV2';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* API
|
|
@@ -291,7 +291,7 @@ export class DecryptForTxBuilder extends BaseBuilder {
|
|
|
291
291
|
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
292
292
|
|
|
293
293
|
const permission = permit ? PermitUtils.getPermission(permit, true) : null;
|
|
294
|
-
const { decryptedValue, signature } = await
|
|
294
|
+
const { decryptedValue, signature } = await tnDecryptV2(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
|
|
295
295
|
|
|
296
296
|
return {
|
|
297
297
|
ctHash: this.ctHash,
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { type Permission } from '@/permits';
|
|
2
|
+
|
|
3
|
+
import { CofheError, CofheErrorCode } from '../error';
|
|
4
|
+
import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
|
|
5
|
+
|
|
6
|
+
// Polling configuration
|
|
7
|
+
const POLL_INTERVAL_MS = 1000; // 1 second
|
|
8
|
+
const POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
9
|
+
|
|
10
|
+
type DecryptSubmitResponseV2 = {
|
|
11
|
+
request_id: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type DecryptStatusResponseV2 = {
|
|
15
|
+
request_id: string;
|
|
16
|
+
status: 'PROCESSING' | 'COMPLETED';
|
|
17
|
+
submitted_at: string;
|
|
18
|
+
completed_at?: string;
|
|
19
|
+
is_succeed?: boolean;
|
|
20
|
+
decrypted?: number[];
|
|
21
|
+
signature?: string;
|
|
22
|
+
encryption_type?: number;
|
|
23
|
+
error_message?: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function assertDecryptSubmitResponseV2(value: unknown): DecryptSubmitResponseV2 {
|
|
27
|
+
if (value == null || typeof value !== 'object') {
|
|
28
|
+
throw new CofheError({
|
|
29
|
+
code: CofheErrorCode.DecryptFailed,
|
|
30
|
+
message: 'decrypt submit response must be a JSON object',
|
|
31
|
+
context: {
|
|
32
|
+
value,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const v = value as Record<string, unknown>;
|
|
38
|
+
if (typeof v.request_id !== 'string' || v.request_id.trim().length === 0) {
|
|
39
|
+
throw new CofheError({
|
|
40
|
+
code: CofheErrorCode.DecryptFailed,
|
|
41
|
+
message: 'decrypt submit response missing request_id',
|
|
42
|
+
context: {
|
|
43
|
+
value,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { request_id: v.request_id };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertDecryptStatusResponseV2(value: unknown): DecryptStatusResponseV2 {
|
|
52
|
+
if (value == null || typeof value !== 'object') {
|
|
53
|
+
throw new CofheError({
|
|
54
|
+
code: CofheErrorCode.DecryptFailed,
|
|
55
|
+
message: 'decrypt status response must be a JSON object',
|
|
56
|
+
context: {
|
|
57
|
+
value,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const v = value as Record<string, unknown>;
|
|
63
|
+
|
|
64
|
+
const requestId = v.request_id;
|
|
65
|
+
const status = v.status;
|
|
66
|
+
const submittedAt = v.submitted_at;
|
|
67
|
+
|
|
68
|
+
if (typeof requestId !== 'string' || requestId.trim().length === 0) {
|
|
69
|
+
throw new CofheError({
|
|
70
|
+
code: CofheErrorCode.DecryptFailed,
|
|
71
|
+
message: 'decrypt status response missing request_id',
|
|
72
|
+
context: {
|
|
73
|
+
value,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (status !== 'PROCESSING' && status !== 'COMPLETED') {
|
|
79
|
+
throw new CofheError({
|
|
80
|
+
code: CofheErrorCode.DecryptFailed,
|
|
81
|
+
message: 'decrypt status response has invalid status',
|
|
82
|
+
context: {
|
|
83
|
+
value,
|
|
84
|
+
status,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (typeof submittedAt !== 'string' || submittedAt.trim().length === 0) {
|
|
90
|
+
throw new CofheError({
|
|
91
|
+
code: CofheErrorCode.DecryptFailed,
|
|
92
|
+
message: 'decrypt status response missing submitted_at',
|
|
93
|
+
context: {
|
|
94
|
+
value,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return value as DecryptStatusResponseV2;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function submitDecryptRequestV2(
|
|
103
|
+
thresholdNetworkUrl: string,
|
|
104
|
+
ctHash: bigint | string,
|
|
105
|
+
chainId: number,
|
|
106
|
+
permission: Permission | null
|
|
107
|
+
): Promise<string> {
|
|
108
|
+
const body: {
|
|
109
|
+
ct_tempkey: string;
|
|
110
|
+
host_chain_id: number;
|
|
111
|
+
permit?: Permission;
|
|
112
|
+
} = {
|
|
113
|
+
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
|
|
114
|
+
host_chain_id: chainId,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (permission) {
|
|
118
|
+
body.permit = permission;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let response: Response;
|
|
122
|
+
try {
|
|
123
|
+
response = await fetch(`${thresholdNetworkUrl}/v2/decrypt`, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify(body),
|
|
129
|
+
});
|
|
130
|
+
} catch (e) {
|
|
131
|
+
throw new CofheError({
|
|
132
|
+
code: CofheErrorCode.DecryptFailed,
|
|
133
|
+
message: `decrypt request failed`,
|
|
134
|
+
hint: 'Ensure the threshold network URL is valid and reachable.',
|
|
135
|
+
cause: e instanceof Error ? e : undefined,
|
|
136
|
+
context: {
|
|
137
|
+
thresholdNetworkUrl,
|
|
138
|
+
body,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
145
|
+
try {
|
|
146
|
+
const errorBody = (await response.json()) as Record<string, unknown>;
|
|
147
|
+
const maybeMessage = (errorBody.error_message || errorBody.message) as unknown;
|
|
148
|
+
if (typeof maybeMessage === 'string' && maybeMessage.length > 0) errorMessage = maybeMessage;
|
|
149
|
+
} catch {
|
|
150
|
+
errorMessage = response.statusText || errorMessage;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new CofheError({
|
|
154
|
+
code: CofheErrorCode.DecryptFailed,
|
|
155
|
+
message: `decrypt request failed: ${errorMessage}`,
|
|
156
|
+
hint: 'Check the threshold network URL and request parameters.',
|
|
157
|
+
context: {
|
|
158
|
+
thresholdNetworkUrl,
|
|
159
|
+
status: response.status,
|
|
160
|
+
statusText: response.statusText,
|
|
161
|
+
body,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let rawJson: unknown;
|
|
167
|
+
try {
|
|
168
|
+
rawJson = (await response.json()) as unknown;
|
|
169
|
+
} catch (e) {
|
|
170
|
+
throw new CofheError({
|
|
171
|
+
code: CofheErrorCode.DecryptFailed,
|
|
172
|
+
message: `Failed to parse decrypt submit response`,
|
|
173
|
+
cause: e instanceof Error ? e : undefined,
|
|
174
|
+
context: {
|
|
175
|
+
thresholdNetworkUrl,
|
|
176
|
+
body,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const submitResponse = assertDecryptSubmitResponseV2(rawJson);
|
|
182
|
+
return submitResponse.request_id;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function pollDecryptStatusV2(
|
|
186
|
+
thresholdNetworkUrl: string,
|
|
187
|
+
requestId: string
|
|
188
|
+
): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
|
|
189
|
+
const startTime = Date.now();
|
|
190
|
+
let completed = false;
|
|
191
|
+
|
|
192
|
+
while (!completed) {
|
|
193
|
+
if (Date.now() - startTime > POLL_TIMEOUT_MS) {
|
|
194
|
+
throw new CofheError({
|
|
195
|
+
code: CofheErrorCode.DecryptFailed,
|
|
196
|
+
message: `decrypt polling timed out after ${POLL_TIMEOUT_MS}ms`,
|
|
197
|
+
hint: 'The request may still be processing. Try again later.',
|
|
198
|
+
context: {
|
|
199
|
+
thresholdNetworkUrl,
|
|
200
|
+
requestId,
|
|
201
|
+
timeoutMs: POLL_TIMEOUT_MS,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let response: Response;
|
|
207
|
+
try {
|
|
208
|
+
response = await fetch(`${thresholdNetworkUrl}/v2/decrypt/${requestId}`, {
|
|
209
|
+
method: 'GET',
|
|
210
|
+
headers: {
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
} catch (e) {
|
|
215
|
+
throw new CofheError({
|
|
216
|
+
code: CofheErrorCode.DecryptFailed,
|
|
217
|
+
message: `decrypt status poll failed`,
|
|
218
|
+
hint: 'Ensure the threshold network URL is valid and reachable.',
|
|
219
|
+
cause: e instanceof Error ? e : undefined,
|
|
220
|
+
context: {
|
|
221
|
+
thresholdNetworkUrl,
|
|
222
|
+
requestId,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (response.status === 404) {
|
|
228
|
+
throw new CofheError({
|
|
229
|
+
code: CofheErrorCode.DecryptFailed,
|
|
230
|
+
message: `decrypt request not found: ${requestId}`,
|
|
231
|
+
hint: 'The request may have expired or been invalid.',
|
|
232
|
+
context: {
|
|
233
|
+
thresholdNetworkUrl,
|
|
234
|
+
requestId,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
241
|
+
try {
|
|
242
|
+
const errorBody = (await response.json()) as Record<string, unknown>;
|
|
243
|
+
const maybeMessage = (errorBody.error_message || errorBody.message) as unknown;
|
|
244
|
+
if (typeof maybeMessage === 'string' && maybeMessage.length > 0) errorMessage = maybeMessage;
|
|
245
|
+
} catch {
|
|
246
|
+
errorMessage = response.statusText || errorMessage;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
throw new CofheError({
|
|
250
|
+
code: CofheErrorCode.DecryptFailed,
|
|
251
|
+
message: `decrypt status poll failed: ${errorMessage}`,
|
|
252
|
+
context: {
|
|
253
|
+
thresholdNetworkUrl,
|
|
254
|
+
requestId,
|
|
255
|
+
status: response.status,
|
|
256
|
+
statusText: response.statusText,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let rawJson: unknown;
|
|
262
|
+
try {
|
|
263
|
+
rawJson = (await response.json()) as unknown;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
throw new CofheError({
|
|
266
|
+
code: CofheErrorCode.DecryptFailed,
|
|
267
|
+
message: `Failed to parse decrypt status response`,
|
|
268
|
+
cause: e instanceof Error ? e : undefined,
|
|
269
|
+
context: {
|
|
270
|
+
thresholdNetworkUrl,
|
|
271
|
+
requestId,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const statusResponse = assertDecryptStatusResponseV2(rawJson);
|
|
277
|
+
|
|
278
|
+
if (statusResponse.status === 'COMPLETED') {
|
|
279
|
+
if (statusResponse.is_succeed === false) {
|
|
280
|
+
const errorMessage = statusResponse.error_message || 'Unknown error';
|
|
281
|
+
throw new CofheError({
|
|
282
|
+
code: CofheErrorCode.DecryptFailed,
|
|
283
|
+
message: `decrypt request failed: ${errorMessage}`,
|
|
284
|
+
context: {
|
|
285
|
+
thresholdNetworkUrl,
|
|
286
|
+
requestId,
|
|
287
|
+
statusResponse,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (statusResponse.error_message) {
|
|
293
|
+
throw new CofheError({
|
|
294
|
+
code: CofheErrorCode.DecryptFailed,
|
|
295
|
+
message: `decrypt request failed: ${statusResponse.error_message}`,
|
|
296
|
+
context: {
|
|
297
|
+
thresholdNetworkUrl,
|
|
298
|
+
requestId,
|
|
299
|
+
statusResponse,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!Array.isArray(statusResponse.decrypted)) {
|
|
305
|
+
throw new CofheError({
|
|
306
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
307
|
+
message: 'decrypt completed but response missing <decrypted> byte array',
|
|
308
|
+
context: {
|
|
309
|
+
thresholdNetworkUrl,
|
|
310
|
+
requestId,
|
|
311
|
+
statusResponse,
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const decryptedValue = parseDecryptedBytesToBigInt(statusResponse.decrypted);
|
|
317
|
+
const signature = normalizeTnSignature(statusResponse.signature);
|
|
318
|
+
return { decryptedValue, signature };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// This should never be reached, but keeps TS and linters happy.
|
|
325
|
+
throw new CofheError({
|
|
326
|
+
code: CofheErrorCode.DecryptFailed,
|
|
327
|
+
message: 'Polling loop exited unexpectedly',
|
|
328
|
+
context: {
|
|
329
|
+
thresholdNetworkUrl,
|
|
330
|
+
requestId,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function tnDecryptV2(
|
|
336
|
+
ctHash: bigint | string,
|
|
337
|
+
chainId: number,
|
|
338
|
+
permission: Permission | null,
|
|
339
|
+
thresholdNetworkUrl: string
|
|
340
|
+
): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
|
|
341
|
+
const requestId = await submitDecryptRequestV2(thresholdNetworkUrl, ctHash, chainId, permission);
|
|
342
|
+
return await pollDecryptStatusV2(thresholdNetworkUrl, requestId);
|
|
343
|
+
}
|
package/core/permits.ts
CHANGED
|
@@ -85,15 +85,15 @@ const deserialize = (serialized: SerializedPermit) => {
|
|
|
85
85
|
|
|
86
86
|
// GET
|
|
87
87
|
|
|
88
|
-
const getPermit =
|
|
88
|
+
const getPermit = (chainId: number, account: string, hash: string): Permit | undefined => {
|
|
89
89
|
return permitStore.getPermit(chainId, account, hash);
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
const getPermits =
|
|
92
|
+
const getPermits = (chainId: number, account: string): Record<string, Permit> => {
|
|
93
93
|
return permitStore.getPermits(chainId, account);
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
const getActivePermit =
|
|
96
|
+
const getActivePermit = (chainId: number, account: string): Permit | undefined => {
|
|
97
97
|
return permitStore.getActivePermit(chainId, account);
|
|
98
98
|
};
|
|
99
99
|
|
package/core/types.ts
CHANGED
|
@@ -389,6 +389,14 @@ export type EncryptStepCallbackFunction = (state: EncryptStep, context?: Encrypt
|
|
|
389
389
|
|
|
390
390
|
// DECRYPT
|
|
391
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Decrypted plaintext value returned by view-decryption helpers.
|
|
394
|
+
*
|
|
395
|
+
* This is a scalar JS value (not a wrapper object):
|
|
396
|
+
* - `boolean` for `FheTypes.Bool`
|
|
397
|
+
* - checksummed address `string` for `FheTypes.Uint160`
|
|
398
|
+
* - `bigint` for supported integer utypes
|
|
399
|
+
*/
|
|
392
400
|
export type UnsealedItem<U extends FheTypes> = U extends FheTypes.Bool
|
|
393
401
|
? boolean
|
|
394
402
|
: U extends FheTypes.Uint160
|