@cofhe/sdk 0.0.0-alpha-20260409113701
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 +146 -0
- package/adapters/ethers5.test.ts +174 -0
- package/adapters/ethers5.ts +36 -0
- package/adapters/ethers6.test.ts +169 -0
- package/adapters/ethers6.ts +36 -0
- package/adapters/hardhat-node.ts +167 -0
- package/adapters/hardhat.hh2.test.ts +159 -0
- package/adapters/hardhat.ts +36 -0
- package/adapters/index.test.ts +20 -0
- package/adapters/index.ts +5 -0
- package/adapters/smartWallet.ts +99 -0
- package/adapters/test-utils.ts +53 -0
- package/adapters/types.ts +6 -0
- package/adapters/wagmi.test.ts +156 -0
- package/adapters/wagmi.ts +17 -0
- package/chains/chains/arbSepolia.ts +14 -0
- package/chains/chains/baseSepolia.ts +14 -0
- package/chains/chains/hardhat.ts +15 -0
- package/chains/chains/localcofhe.ts +14 -0
- package/chains/chains/sepolia.ts +14 -0
- package/chains/chains.test.ts +50 -0
- package/chains/defineChain.ts +18 -0
- package/chains/index.ts +35 -0
- package/chains/types.ts +32 -0
- package/core/baseBuilder.ts +119 -0
- package/core/client.test.ts +429 -0
- package/core/client.ts +341 -0
- package/core/clientTypes.ts +119 -0
- package/core/config.test.ts +242 -0
- package/core/config.ts +225 -0
- package/core/consts.ts +22 -0
- package/core/decrypt/MockThresholdNetworkAbi.ts +179 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +84 -0
- package/core/decrypt/cofheMocksDecryptForView.ts +48 -0
- package/core/decrypt/decryptForTxBuilder.ts +359 -0
- package/core/decrypt/decryptForViewBuilder.ts +332 -0
- package/core/decrypt/decryptUtils.ts +28 -0
- package/core/decrypt/pollCallbacks.test.ts +194 -0
- package/core/decrypt/polling.ts +14 -0
- package/core/decrypt/tnDecryptUtils.ts +65 -0
- package/core/decrypt/tnDecryptV1.ts +171 -0
- package/core/decrypt/tnDecryptV2.ts +365 -0
- package/core/decrypt/tnSealOutputV1.ts +59 -0
- package/core/decrypt/tnSealOutputV2.ts +324 -0
- package/core/decrypt/verifyDecryptResult.ts +52 -0
- package/core/encrypt/MockZkVerifierAbi.ts +106 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +281 -0
- package/core/encrypt/encryptInputsBuilder.test.ts +747 -0
- package/core/encrypt/encryptInputsBuilder.ts +583 -0
- package/core/encrypt/encryptUtils.ts +67 -0
- package/core/encrypt/zkPackProveVerify.ts +335 -0
- package/core/error.ts +168 -0
- package/core/fetchKeys.test.ts +195 -0
- package/core/fetchKeys.ts +144 -0
- package/core/index.ts +106 -0
- package/core/keyStore.test.ts +226 -0
- package/core/keyStore.ts +154 -0
- package/core/permits.test.ts +493 -0
- package/core/permits.ts +201 -0
- package/core/types.ts +419 -0
- package/core/utils.ts +130 -0
- package/dist/adapters.cjs +88 -0
- package/dist/adapters.d.cts +14576 -0
- package/dist/adapters.d.ts +14576 -0
- package/dist/adapters.js +83 -0
- package/dist/chains.cjs +111 -0
- package/dist/chains.d.cts +121 -0
- package/dist/chains.d.ts +121 -0
- package/dist/chains.js +1 -0
- package/dist/chunk-36FBWLUS.js +3310 -0
- package/dist/chunk-7HLGHV67.js +990 -0
- package/dist/chunk-TBLR7NNE.js +102 -0
- package/dist/clientTypes-AVSCBet7.d.cts +998 -0
- package/dist/clientTypes-flH1ju82.d.ts +998 -0
- package/dist/core.cjs +4362 -0
- package/dist/core.d.cts +138 -0
- package/dist/core.d.ts +138 -0
- package/dist/core.js +3 -0
- package/dist/node.cjs +4225 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +91 -0
- package/dist/permit-jRirYqFt.d.cts +376 -0
- package/dist/permit-jRirYqFt.d.ts +376 -0
- package/dist/permits.cjs +1025 -0
- package/dist/permits.d.cts +353 -0
- package/dist/permits.d.ts +353 -0
- package/dist/permits.js +1 -0
- package/dist/types-YiAC4gig.d.cts +33 -0
- package/dist/types-YiAC4gig.d.ts +33 -0
- package/dist/web.cjs +4434 -0
- package/dist/web.d.cts +42 -0
- package/dist/web.d.ts +42 -0
- package/dist/web.js +256 -0
- package/dist/zkProve.worker.cjs +93 -0
- package/dist/zkProve.worker.d.cts +2 -0
- package/dist/zkProve.worker.d.ts +2 -0
- package/dist/zkProve.worker.js +91 -0
- package/node/client.test.ts +159 -0
- package/node/config.test.ts +68 -0
- package/node/encryptInputs.test.ts +155 -0
- package/node/index.ts +97 -0
- package/node/storage.ts +51 -0
- package/package.json +121 -0
- package/permits/index.ts +68 -0
- package/permits/localstorage.test.ts +113 -0
- package/permits/onchain-utils.ts +221 -0
- package/permits/permit.test.ts +534 -0
- package/permits/permit.ts +386 -0
- package/permits/sealing.test.ts +84 -0
- package/permits/sealing.ts +131 -0
- package/permits/signature.ts +79 -0
- package/permits/store.test.ts +88 -0
- package/permits/store.ts +156 -0
- package/permits/test-utils.ts +28 -0
- package/permits/types.ts +204 -0
- package/permits/utils.ts +58 -0
- package/permits/validation.test.ts +361 -0
- package/permits/validation.ts +327 -0
- package/web/client.web.test.ts +159 -0
- package/web/config.web.test.ts +69 -0
- package/web/const.ts +2 -0
- package/web/encryptInputs.web.test.ts +172 -0
- package/web/index.ts +166 -0
- package/web/storage.ts +49 -0
- package/web/worker.builder.web.test.ts +148 -0
- package/web/worker.config.web.test.ts +329 -0
- package/web/worker.output.web.test.ts +84 -0
- package/web/workerManager.test.ts +80 -0
- package/web/workerManager.ts +214 -0
- package/web/workerManager.web.test.ts +114 -0
- package/web/zkProve.worker.ts +133 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getAddress } from 'viem';
|
|
2
|
+
import { FheTypes, FheUintUTypes, type UnsealedItem } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export function uint160ToAddress(uint160: bigint): string {
|
|
5
|
+
// Convert bigint to hex string and pad to 20 bytes (40 hex chars)
|
|
6
|
+
const hexStr = uint160.toString(16).padStart(40, '0');
|
|
7
|
+
|
|
8
|
+
// Add 0x prefix and convert to checksum address
|
|
9
|
+
return getAddress('0x' + hexStr);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const isValidUtype = (utype: FheTypes): boolean => {
|
|
13
|
+
return (
|
|
14
|
+
utype === FheTypes.Bool || utype === FheTypes.Uint160 || utype == null || FheUintUTypes.includes(utype as number)
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const convertViaUtype = <U extends FheTypes>(utype: U, value: bigint): UnsealedItem<U> => {
|
|
19
|
+
if (utype === FheTypes.Bool) {
|
|
20
|
+
return !!value as UnsealedItem<U>;
|
|
21
|
+
} else if (utype === FheTypes.Uint160) {
|
|
22
|
+
return uint160ToAddress(value) as UnsealedItem<U>;
|
|
23
|
+
} else if (utype == null || FheUintUTypes.includes(utype as number)) {
|
|
24
|
+
return value as UnsealedItem<U>;
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error(`convertViaUtype :: invalid utype :: ${utype}`);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { tnDecryptV2 } from './tnDecryptV2.js';
|
|
3
|
+
import { tnSealOutputV2 } from './tnSealOutputV2.js';
|
|
4
|
+
|
|
5
|
+
const makeMockResponse = (opts: { ok: boolean; status?: number; statusText?: string; json: () => Promise<any> }) => {
|
|
6
|
+
return {
|
|
7
|
+
ok: opts.ok,
|
|
8
|
+
status: opts.status ?? (opts.ok ? 200 : 500),
|
|
9
|
+
statusText: opts.statusText ?? '',
|
|
10
|
+
json: opts.json,
|
|
11
|
+
} as unknown as Response;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('decrypt polling callbacks', () => {
|
|
15
|
+
const thresholdNetworkUrl = 'http://threshold.local';
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.useFakeTimers();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.useRealTimers();
|
|
23
|
+
vi.restoreAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('tnDecryptV2 calls onPoll once per poll attempt', async () => {
|
|
27
|
+
const onPoll = vi.fn();
|
|
28
|
+
|
|
29
|
+
let decryptStatusCalls = 0;
|
|
30
|
+
const fetchMock = vi.fn(async (url: string, options?: any) => {
|
|
31
|
+
if (url === `${thresholdNetworkUrl}/v2/decrypt` && options?.method === 'POST') {
|
|
32
|
+
return makeMockResponse({
|
|
33
|
+
ok: true,
|
|
34
|
+
json: async () => ({ request_id: 'req-1' }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (url === `${thresholdNetworkUrl}/v2/decrypt/req-1` && options?.method === 'GET') {
|
|
39
|
+
decryptStatusCalls += 1;
|
|
40
|
+
|
|
41
|
+
if (decryptStatusCalls === 1) {
|
|
42
|
+
return makeMockResponse({
|
|
43
|
+
ok: true,
|
|
44
|
+
json: async () => ({
|
|
45
|
+
request_id: 'req-1',
|
|
46
|
+
status: 'PROCESSING',
|
|
47
|
+
submitted_at: 't',
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return makeMockResponse({
|
|
53
|
+
ok: true,
|
|
54
|
+
json: async () => ({
|
|
55
|
+
request_id: 'req-1',
|
|
56
|
+
status: 'COMPLETED',
|
|
57
|
+
submitted_at: 't',
|
|
58
|
+
is_succeed: true,
|
|
59
|
+
decrypted: [0x01],
|
|
60
|
+
signature: `0x${'01'.repeat(32)}${'02'.repeat(32)}1b`,
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
global.fetch = fetchMock as any;
|
|
69
|
+
|
|
70
|
+
const promise = tnDecryptV2({
|
|
71
|
+
ctHash: 1n,
|
|
72
|
+
chainId: 1,
|
|
73
|
+
permission: null,
|
|
74
|
+
thresholdNetworkUrl,
|
|
75
|
+
onPoll,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < 25 && onPoll.mock.calls.length < 1; i += 1) {
|
|
79
|
+
await Promise.resolve();
|
|
80
|
+
}
|
|
81
|
+
expect(onPoll).toHaveBeenCalledTimes(1);
|
|
82
|
+
|
|
83
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
84
|
+
const result = await promise;
|
|
85
|
+
|
|
86
|
+
expect(result.decryptedValue).toBe(1n);
|
|
87
|
+
expect(result.signature.startsWith('0x')).toBe(true);
|
|
88
|
+
|
|
89
|
+
expect(onPoll).toHaveBeenCalledTimes(2);
|
|
90
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
91
|
+
1,
|
|
92
|
+
expect.objectContaining({
|
|
93
|
+
operation: 'decrypt',
|
|
94
|
+
requestId: 'req-1',
|
|
95
|
+
attemptIndex: 0,
|
|
96
|
+
intervalMs: 1000,
|
|
97
|
+
timeoutMs: 5 * 60 * 1000,
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
101
|
+
2,
|
|
102
|
+
expect.objectContaining({
|
|
103
|
+
operation: 'decrypt',
|
|
104
|
+
requestId: 'req-1',
|
|
105
|
+
attemptIndex: 1,
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('tnSealOutputV2 calls onPoll once per poll attempt', async () => {
|
|
111
|
+
const onPoll = vi.fn();
|
|
112
|
+
|
|
113
|
+
let sealStatusCalls = 0;
|
|
114
|
+
const fetchMock = vi.fn(async (url: string, options?: any) => {
|
|
115
|
+
if (url === `${thresholdNetworkUrl}/v2/sealoutput` && options?.method === 'POST') {
|
|
116
|
+
return makeMockResponse({
|
|
117
|
+
ok: true,
|
|
118
|
+
json: async () => ({ request_id: 'req-2' }),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (url === `${thresholdNetworkUrl}/v2/sealoutput/req-2` && options?.method === 'GET') {
|
|
123
|
+
sealStatusCalls += 1;
|
|
124
|
+
|
|
125
|
+
if (sealStatusCalls === 1) {
|
|
126
|
+
return makeMockResponse({
|
|
127
|
+
ok: true,
|
|
128
|
+
json: async () => ({
|
|
129
|
+
request_id: 'req-2',
|
|
130
|
+
status: 'PROCESSING',
|
|
131
|
+
submitted_at: 't',
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return makeMockResponse({
|
|
137
|
+
ok: true,
|
|
138
|
+
json: async () => ({
|
|
139
|
+
request_id: 'req-2',
|
|
140
|
+
status: 'COMPLETED',
|
|
141
|
+
submitted_at: 't',
|
|
142
|
+
is_succeed: true,
|
|
143
|
+
sealed: {
|
|
144
|
+
data: [1, 2, 3],
|
|
145
|
+
public_key: [4, 5],
|
|
146
|
+
nonce: [6],
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
global.fetch = fetchMock as any;
|
|
156
|
+
|
|
157
|
+
const promise = tnSealOutputV2({
|
|
158
|
+
ctHash: 1n,
|
|
159
|
+
chainId: 1,
|
|
160
|
+
permission: {} as any,
|
|
161
|
+
thresholdNetworkUrl,
|
|
162
|
+
onPoll,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < 25 && onPoll.mock.calls.length < 1; i += 1) {
|
|
166
|
+
await Promise.resolve();
|
|
167
|
+
}
|
|
168
|
+
expect(onPoll).toHaveBeenCalledTimes(1);
|
|
169
|
+
|
|
170
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
171
|
+
const sealed = await promise;
|
|
172
|
+
|
|
173
|
+
expect(sealed.data).toBeInstanceOf(Uint8Array);
|
|
174
|
+
expect(Array.from(sealed.data)).toEqual([1, 2, 3]);
|
|
175
|
+
|
|
176
|
+
expect(onPoll).toHaveBeenCalledTimes(2);
|
|
177
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
178
|
+
1,
|
|
179
|
+
expect.objectContaining({
|
|
180
|
+
operation: 'sealoutput',
|
|
181
|
+
requestId: 'req-2',
|
|
182
|
+
attemptIndex: 0,
|
|
183
|
+
})
|
|
184
|
+
);
|
|
185
|
+
expect(onPoll).toHaveBeenNthCalledWith(
|
|
186
|
+
2,
|
|
187
|
+
expect.objectContaining({
|
|
188
|
+
operation: 'sealoutput',
|
|
189
|
+
requestId: 'req-2',
|
|
190
|
+
attemptIndex: 1,
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { type Permission } from '@/permits';
|
|
2
|
+
|
|
3
|
+
import { CofheError, CofheErrorCode } from '../error';
|
|
4
|
+
import { normalizeTnSignature, parseDecryptedBytesToBigInt } from './tnDecryptUtils';
|
|
5
|
+
|
|
6
|
+
type TnDecryptResponseV1 = {
|
|
7
|
+
// TN returns bytes in big-endian order, e.g. [0,0,0,42]
|
|
8
|
+
decrypted: number[];
|
|
9
|
+
signature: string;
|
|
10
|
+
encryption_type: number;
|
|
11
|
+
error_message: string | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function assertTnDecryptResponseV1(value: unknown): TnDecryptResponseV1 {
|
|
15
|
+
if (value == null || typeof value !== 'object') {
|
|
16
|
+
throw new CofheError({
|
|
17
|
+
code: CofheErrorCode.DecryptFailed,
|
|
18
|
+
message: 'decrypt response must be a JSON object',
|
|
19
|
+
context: {
|
|
20
|
+
value,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const v = value as Record<string, unknown>;
|
|
26
|
+
const decrypted = v.decrypted;
|
|
27
|
+
const signature = v.signature;
|
|
28
|
+
const encryptionType = v.encryption_type;
|
|
29
|
+
const errorMessage = v.error_message;
|
|
30
|
+
|
|
31
|
+
if (!Array.isArray(decrypted)) {
|
|
32
|
+
throw new CofheError({
|
|
33
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
34
|
+
message: 'decrypt response missing <decrypted> byte array',
|
|
35
|
+
context: { decryptResponse: value },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (typeof signature !== 'string') {
|
|
39
|
+
throw new CofheError({
|
|
40
|
+
code: CofheErrorCode.DecryptReturnedNull,
|
|
41
|
+
message: 'decrypt response missing <signature> string',
|
|
42
|
+
context: { decryptResponse: value },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (typeof encryptionType !== 'number') {
|
|
46
|
+
throw new CofheError({
|
|
47
|
+
code: CofheErrorCode.DecryptFailed,
|
|
48
|
+
message: 'decrypt response missing <encryption_type> number',
|
|
49
|
+
context: { decryptResponse: value },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (!(typeof errorMessage === 'string' || errorMessage === null)) {
|
|
53
|
+
throw new CofheError({
|
|
54
|
+
code: CofheErrorCode.DecryptFailed,
|
|
55
|
+
message: 'decrypt response field <error_message> must be string or null',
|
|
56
|
+
context: { decryptResponse: value },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
decrypted: decrypted as number[],
|
|
62
|
+
signature,
|
|
63
|
+
encryption_type: encryptionType,
|
|
64
|
+
error_message: errorMessage,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function tnDecryptV1(
|
|
69
|
+
ctHash: bigint | string,
|
|
70
|
+
chainId: number,
|
|
71
|
+
permission: Permission | null,
|
|
72
|
+
thresholdNetworkUrl: string
|
|
73
|
+
): Promise<{ decryptedValue: bigint; signature: `0x${string}` }> {
|
|
74
|
+
const body: {
|
|
75
|
+
ct_tempkey: string;
|
|
76
|
+
host_chain_id: number;
|
|
77
|
+
permit?: Permission;
|
|
78
|
+
} = {
|
|
79
|
+
ct_tempkey: BigInt(ctHash).toString(16).padStart(64, '0'),
|
|
80
|
+
host_chain_id: chainId,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (permission) {
|
|
84
|
+
body.permit = permission;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let response: Response;
|
|
88
|
+
try {
|
|
89
|
+
response = await fetch(`${thresholdNetworkUrl}/decrypt`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
});
|
|
96
|
+
} catch (e) {
|
|
97
|
+
throw new CofheError({
|
|
98
|
+
code: CofheErrorCode.DecryptFailed,
|
|
99
|
+
message: `decrypt request failed`,
|
|
100
|
+
hint: 'Ensure the threshold network URL is valid and reachable.',
|
|
101
|
+
cause: e instanceof Error ? e : undefined,
|
|
102
|
+
context: {
|
|
103
|
+
thresholdNetworkUrl,
|
|
104
|
+
body,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const responseText = await response.text();
|
|
110
|
+
|
|
111
|
+
// Even on non-200 responses, TN may return JSON with { error_message }.
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
let errorMessage = response.statusText || `HTTP ${response.status}`;
|
|
114
|
+
try {
|
|
115
|
+
const errorBody = JSON.parse(responseText) as Record<string, unknown>;
|
|
116
|
+
const maybeMessage = (errorBody.error_message || errorBody.message) as unknown;
|
|
117
|
+
if (typeof maybeMessage === 'string' && maybeMessage.length > 0) errorMessage = maybeMessage;
|
|
118
|
+
} catch {
|
|
119
|
+
const trimmed = responseText.trim();
|
|
120
|
+
if (trimmed.length > 0) errorMessage = trimmed;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new CofheError({
|
|
124
|
+
code: CofheErrorCode.DecryptFailed,
|
|
125
|
+
message: `decrypt request failed: ${errorMessage}`,
|
|
126
|
+
hint: 'Check the threshold network URL and request parameters.',
|
|
127
|
+
context: {
|
|
128
|
+
thresholdNetworkUrl,
|
|
129
|
+
status: response.status,
|
|
130
|
+
statusText: response.statusText,
|
|
131
|
+
body,
|
|
132
|
+
responseText,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let rawJson: unknown;
|
|
138
|
+
try {
|
|
139
|
+
rawJson = JSON.parse(responseText) as unknown;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
throw new CofheError({
|
|
142
|
+
code: CofheErrorCode.DecryptFailed,
|
|
143
|
+
message: `Failed to parse decrypt response`,
|
|
144
|
+
cause: e instanceof Error ? e : undefined,
|
|
145
|
+
context: {
|
|
146
|
+
thresholdNetworkUrl,
|
|
147
|
+
body,
|
|
148
|
+
responseText,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const decryptResponse = assertTnDecryptResponseV1(rawJson);
|
|
154
|
+
|
|
155
|
+
if (decryptResponse.error_message) {
|
|
156
|
+
throw new CofheError({
|
|
157
|
+
code: CofheErrorCode.DecryptFailed,
|
|
158
|
+
message: `decrypt request failed: ${decryptResponse.error_message}`,
|
|
159
|
+
context: {
|
|
160
|
+
thresholdNetworkUrl,
|
|
161
|
+
body,
|
|
162
|
+
decryptResponse,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const decryptedValue = parseDecryptedBytesToBigInt(decryptResponse.decrypted);
|
|
168
|
+
const signature = normalizeTnSignature(decryptResponse.signature);
|
|
169
|
+
|
|
170
|
+
return { decryptedValue, signature };
|
|
171
|
+
}
|