@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,359 @@
|
|
|
1
|
+
/* eslint-disable no-dupe-class-members */
|
|
2
|
+
import { hardhat } from '@/chains';
|
|
3
|
+
import { type Permit, type Permission, PermitUtils } from '@/permits';
|
|
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 { type DecryptPollCallbackFunction } from '../types';
|
|
13
|
+
import { tnDecryptV2 } from './tnDecryptV2';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* API
|
|
17
|
+
*
|
|
18
|
+
* await client.decryptForTx(ctHash)
|
|
19
|
+
* .setChainId(chainId)
|
|
20
|
+
* .setAccount(account)
|
|
21
|
+
* .withPermit(permit | permitHash | undefined)
|
|
22
|
+
* // or .withoutPermit()
|
|
23
|
+
* .execute()
|
|
24
|
+
*
|
|
25
|
+
* If chainId not set, uses client's chainId
|
|
26
|
+
* If account not set, uses client's account
|
|
27
|
+
* You MUST choose one permit mode before calling execute():
|
|
28
|
+
* - withPermit(...) to decrypt using a permit
|
|
29
|
+
* - withoutPermit() to decrypt via global allowance (no permit)
|
|
30
|
+
*
|
|
31
|
+
* withPermit() (no args / undefined) uses the active permit for chainId + account.
|
|
32
|
+
* withoutPermit() uses global allowance (no permit required).
|
|
33
|
+
*
|
|
34
|
+
* Returns the decrypted value + proof ready for tx.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
type DecryptForTxPermitSelection = 'unset' | 'with-permit' | 'without-permit';
|
|
38
|
+
|
|
39
|
+
type DecryptForTxBuilderParams = BaseBuilderParams & {
|
|
40
|
+
ctHash: bigint | string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type DecryptForTxResult = {
|
|
44
|
+
ctHash: bigint | string;
|
|
45
|
+
decryptedValue: bigint;
|
|
46
|
+
signature: `0x${string}`; // Threshold network signature for publishDecryptResult
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Type-level gating:
|
|
51
|
+
* - The initial builder returned from `client.decryptForTx(...)` intentionally does not expose `execute()`.
|
|
52
|
+
* - Calling `withPermit(...)` or `withoutPermit()` returns a builder that *does* expose `execute()`, but no longer
|
|
53
|
+
* exposes `withPermit/withoutPermit` (so you can't select twice, or switch modes).
|
|
54
|
+
*/
|
|
55
|
+
export type DecryptForTxBuilderUnset = Omit<DecryptForTxBuilder, 'execute'>;
|
|
56
|
+
|
|
57
|
+
export type DecryptForTxBuilderSelected = Omit<DecryptForTxBuilder, 'withPermit' | 'withoutPermit'>;
|
|
58
|
+
|
|
59
|
+
export class DecryptForTxBuilder extends BaseBuilder {
|
|
60
|
+
private ctHash: bigint | string;
|
|
61
|
+
private permitHash?: string;
|
|
62
|
+
private permit?: Permit;
|
|
63
|
+
private permitSelection: DecryptForTxPermitSelection = 'unset';
|
|
64
|
+
private pollCallback?: DecryptPollCallbackFunction;
|
|
65
|
+
|
|
66
|
+
constructor(params: DecryptForTxBuilderParams) {
|
|
67
|
+
super({
|
|
68
|
+
config: params.config,
|
|
69
|
+
publicClient: params.publicClient,
|
|
70
|
+
walletClient: params.walletClient,
|
|
71
|
+
chainId: params.chainId,
|
|
72
|
+
account: params.account,
|
|
73
|
+
requireConnected: params.requireConnected,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.ctHash = params.ctHash;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
|
|
81
|
+
*
|
|
82
|
+
* If not provided, the chainId will be fetched from the connected publicClient.
|
|
83
|
+
*
|
|
84
|
+
* Example:
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const result = await decryptForTx(ctHash)
|
|
87
|
+
* .setChainId(11155111)
|
|
88
|
+
* .execute();
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @returns The chainable DecryptForTxBuilder instance.
|
|
92
|
+
*/
|
|
93
|
+
setChainId(this: DecryptForTxBuilderUnset, chainId: number): DecryptForTxBuilderUnset;
|
|
94
|
+
setChainId(this: DecryptForTxBuilderSelected, chainId: number): DecryptForTxBuilderSelected;
|
|
95
|
+
setChainId(chainId: number): DecryptForTxBuilder {
|
|
96
|
+
this.chainId = chainId;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getChainId(): number | undefined {
|
|
101
|
+
return this.chainId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param account - Account to decrypt values from. Used to fetch the correct permit.
|
|
106
|
+
*
|
|
107
|
+
* If not provided, the account will be fetched from the connected walletClient.
|
|
108
|
+
*
|
|
109
|
+
* Example:
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const result = await decryptForTx(ctHash)
|
|
112
|
+
* .setAccount('0x1234567890123456789012345678901234567890')
|
|
113
|
+
* .execute();
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @returns The chainable DecryptForTxBuilder instance.
|
|
117
|
+
*/
|
|
118
|
+
setAccount(this: DecryptForTxBuilderUnset, account: string): DecryptForTxBuilderUnset;
|
|
119
|
+
setAccount(this: DecryptForTxBuilderSelected, account: string): DecryptForTxBuilderSelected;
|
|
120
|
+
setAccount(account: string): DecryptForTxBuilder {
|
|
121
|
+
this.account = account;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getAccount(): string | undefined {
|
|
126
|
+
return this.account;
|
|
127
|
+
}
|
|
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
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Select "use permit" mode.
|
|
138
|
+
*
|
|
139
|
+
* - `withPermit(permit)` uses the provided permit.
|
|
140
|
+
* - `withPermit(permitHash)` fetches that permit.
|
|
141
|
+
* - `withPermit()` uses the active permit for the resolved `chainId + account`.
|
|
142
|
+
*
|
|
143
|
+
* Note: "global allowance" (no permit) is ONLY available via `withoutPermit()`.
|
|
144
|
+
*/
|
|
145
|
+
withPermit(): DecryptForTxBuilderSelected;
|
|
146
|
+
withPermit(permitHash: string): DecryptForTxBuilderSelected;
|
|
147
|
+
withPermit(permit: Permit): DecryptForTxBuilderSelected;
|
|
148
|
+
withPermit(permitOrPermitHash?: Permit | string): DecryptForTxBuilderSelected {
|
|
149
|
+
if (this.permitSelection === 'with-permit') {
|
|
150
|
+
throw new CofheError({
|
|
151
|
+
code: CofheErrorCode.InternalError,
|
|
152
|
+
message: 'decryptForTx: withPermit() can only be selected once.',
|
|
153
|
+
hint: 'Choose the permit mode once. If you need a different permit, start a new decryptForTx() builder chain.',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (this.permitSelection === 'without-permit') {
|
|
158
|
+
throw new CofheError({
|
|
159
|
+
code: CofheErrorCode.InternalError,
|
|
160
|
+
message: 'decryptForTx: cannot call withPermit() after withoutPermit() has been selected.',
|
|
161
|
+
hint: 'Choose exactly one permit mode: either call .withPermit(...) or .withoutPermit(), but not both.',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.permitSelection = 'with-permit';
|
|
166
|
+
|
|
167
|
+
if (typeof permitOrPermitHash === 'string') {
|
|
168
|
+
this.permitHash = permitOrPermitHash;
|
|
169
|
+
this.permit = undefined;
|
|
170
|
+
} else if (permitOrPermitHash === undefined) {
|
|
171
|
+
// Explicitly choose "active permit" resolution at execute()
|
|
172
|
+
this.permitHash = undefined;
|
|
173
|
+
this.permit = undefined;
|
|
174
|
+
} else {
|
|
175
|
+
// Permit object
|
|
176
|
+
this.permit = permitOrPermitHash;
|
|
177
|
+
this.permitHash = undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return this as unknown as DecryptForTxBuilderSelected;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Select "no permit" mode.
|
|
185
|
+
*
|
|
186
|
+
* This uses global allowance (no permit required) and sends an empty permission payload to `/decrypt`.
|
|
187
|
+
*/
|
|
188
|
+
withoutPermit(): DecryptForTxBuilderSelected {
|
|
189
|
+
if (this.permitSelection === 'without-permit') {
|
|
190
|
+
throw new CofheError({
|
|
191
|
+
code: CofheErrorCode.InternalError,
|
|
192
|
+
message: 'decryptForTx: withoutPermit() can only be selected once.',
|
|
193
|
+
hint: 'Choose the permit mode once. If you need a different mode, start a new decryptForTx() builder chain.',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (this.permitSelection === 'with-permit') {
|
|
198
|
+
throw new CofheError({
|
|
199
|
+
code: CofheErrorCode.InternalError,
|
|
200
|
+
message: 'decryptForTx: cannot call withoutPermit() after withPermit() has been selected.',
|
|
201
|
+
hint: 'Choose exactly one permit mode: either call .withPermit(...) or .withoutPermit(), but not both.',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.permitSelection = 'without-permit';
|
|
206
|
+
this.permitHash = undefined;
|
|
207
|
+
this.permit = undefined;
|
|
208
|
+
return this as unknown as DecryptForTxBuilderSelected;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getPermit(): Permit | undefined {
|
|
212
|
+
return this.permit;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getPermitHash(): string | undefined {
|
|
216
|
+
return this.permitHash;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private async getThresholdNetworkUrl(): Promise<string> {
|
|
220
|
+
this.assertChainId();
|
|
221
|
+
return getThresholdNetworkUrlOrThrow(this.config, this.chainId);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async getResolvedPermit(): Promise<Permit | null> {
|
|
225
|
+
if (this.permitSelection === 'unset') {
|
|
226
|
+
throw new CofheError({
|
|
227
|
+
code: CofheErrorCode.InternalError,
|
|
228
|
+
message: 'decryptForTx: missing permit selection; call withPermit(...) or withoutPermit() before execute().',
|
|
229
|
+
hint: 'Call .withPermit() to use the active permit, or .withoutPermit() for global allowance.',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (this.permitSelection === 'without-permit') {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// with-permit mode
|
|
238
|
+
if (this.permit) return this.permit;
|
|
239
|
+
|
|
240
|
+
this.assertChainId();
|
|
241
|
+
this.assertAccount();
|
|
242
|
+
|
|
243
|
+
// Fetch with permit hash
|
|
244
|
+
if (this.permitHash) {
|
|
245
|
+
const permit = await permits.getPermit(this.chainId, this.account, this.permitHash);
|
|
246
|
+
if (!permit) {
|
|
247
|
+
throw new CofheError({
|
|
248
|
+
code: CofheErrorCode.PermitNotFound,
|
|
249
|
+
message: `Permit with hash <${this.permitHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
|
|
250
|
+
hint: 'Ensure the permit exists and is valid.',
|
|
251
|
+
context: {
|
|
252
|
+
chainId: this.chainId,
|
|
253
|
+
account: this.account,
|
|
254
|
+
permitHash: this.permitHash,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return permit;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Fetch active permit (default for withPermit() with no args)
|
|
262
|
+
const permit = await permits.getActivePermit(this.chainId, this.account);
|
|
263
|
+
if (!permit) {
|
|
264
|
+
throw new CofheError({
|
|
265
|
+
code: CofheErrorCode.PermitNotFound,
|
|
266
|
+
message: `Active permit not found for chainId <${this.chainId}> and account <${this.account}>`,
|
|
267
|
+
hint: 'Create a permit (e.g. client.permits.createSelf(...)) and/or set it active (client.permits.selectActivePermit(hash)).',
|
|
268
|
+
context: {
|
|
269
|
+
chainId: this.chainId,
|
|
270
|
+
account: this.account,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return permit;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* On hardhat, interact with MockThresholdNetwork contract
|
|
279
|
+
*/
|
|
280
|
+
private async mocksDecryptForTx(permit: Permit | null): Promise<DecryptForTxResult> {
|
|
281
|
+
this.assertPublicClient();
|
|
282
|
+
|
|
283
|
+
// Configurable delay before decrypting to simulate the CoFHE decrypt processing time
|
|
284
|
+
// Recommended 1000ms on web
|
|
285
|
+
// Recommended 0ms on hardhat (will be called during tests no need for fake delay)
|
|
286
|
+
const delay = this.config.mocks.decryptDelay;
|
|
287
|
+
if (delay > 0) await sleep(delay);
|
|
288
|
+
|
|
289
|
+
const result = await cofheMocksDecryptForTx(this.ctHash, 0 as FheTypes, permit, this.publicClient);
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* In the production context, perform a true decryption with the CoFHE coprocessor.
|
|
295
|
+
*/
|
|
296
|
+
private async productionDecryptForTx(permit: Permit | null): Promise<DecryptForTxResult> {
|
|
297
|
+
this.assertChainId();
|
|
298
|
+
this.assertPublicClient();
|
|
299
|
+
|
|
300
|
+
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
301
|
+
|
|
302
|
+
const permission = permit ? PermitUtils.getPermission(permit, true) : null;
|
|
303
|
+
const { decryptedValue, signature } = await tnDecryptV2({
|
|
304
|
+
ctHash: this.ctHash,
|
|
305
|
+
chainId: this.chainId,
|
|
306
|
+
permission,
|
|
307
|
+
thresholdNetworkUrl,
|
|
308
|
+
onPoll: this.pollCallback,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
ctHash: this.ctHash,
|
|
313
|
+
decryptedValue,
|
|
314
|
+
signature,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Final step of the decryptForTx process. MUST BE CALLED LAST IN THE CHAIN.
|
|
320
|
+
*
|
|
321
|
+
* You must explicitly choose one permit mode before calling `execute()`:
|
|
322
|
+
* - `withPermit(permit)` / `withPermit(permitHash)` / `withPermit()` (active permit)
|
|
323
|
+
* - `withoutPermit()` (global allowance)
|
|
324
|
+
*/
|
|
325
|
+
async execute(): Promise<DecryptForTxResult> {
|
|
326
|
+
// Resolve permit (can be Permit object or null for global allowance)
|
|
327
|
+
const permit = await this.getResolvedPermit();
|
|
328
|
+
|
|
329
|
+
// If permit is provided, validate it
|
|
330
|
+
if (permit !== null) {
|
|
331
|
+
// Ensure permit validity
|
|
332
|
+
PermitUtils.validate(permit);
|
|
333
|
+
|
|
334
|
+
// Extract chainId from signed permit
|
|
335
|
+
const chainId = permit._signedDomain!.chainId;
|
|
336
|
+
|
|
337
|
+
if (chainId === hardhat.id) {
|
|
338
|
+
return await this.mocksDecryptForTx(permit);
|
|
339
|
+
} else {
|
|
340
|
+
return await this.productionDecryptForTx(permit);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
// Global allowance - no permit
|
|
344
|
+
// If chainId not set, try to get it from publicClient
|
|
345
|
+
if (!this.chainId) {
|
|
346
|
+
this.assertPublicClient();
|
|
347
|
+
this.chainId = await getPublicClientChainID(this.publicClient);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.assertChainId();
|
|
351
|
+
|
|
352
|
+
if (this.chainId === hardhat.id) {
|
|
353
|
+
return await this.mocksDecryptForTx(null);
|
|
354
|
+
} else {
|
|
355
|
+
return await this.productionDecryptForTx(null);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/* eslint-disable no-dupe-class-members */
|
|
2
|
+
import { hardhat } from '@/chains';
|
|
3
|
+
import { type Permit, PermitUtils } from '@/permits';
|
|
4
|
+
|
|
5
|
+
import { FheTypes, type UnsealedItem } from '../types.js';
|
|
6
|
+
import { getThresholdNetworkUrlOrThrow } from '../config.js';
|
|
7
|
+
import { CofheError, CofheErrorCode } from '../error.js';
|
|
8
|
+
import { permits } from '../permits.js';
|
|
9
|
+
import { isValidUtype, convertViaUtype } from './decryptUtils.js';
|
|
10
|
+
import { BaseBuilder, type BaseBuilderParams } from '../baseBuilder.js';
|
|
11
|
+
import { cofheMocksDecryptForView } from './cofheMocksDecryptForView.js';
|
|
12
|
+
// import { tnSealOutputV1 } from './tnSealOutputV1.js';
|
|
13
|
+
import { tnSealOutputV2 } from './tnSealOutputV2.js';
|
|
14
|
+
import { sleep } from '../utils.js';
|
|
15
|
+
import { type DecryptPollCallbackFunction } from '../types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* API
|
|
19
|
+
*
|
|
20
|
+
* await client.decryptForView(ctHash, utype)
|
|
21
|
+
* .setChainId(chainId)
|
|
22
|
+
* .setAccount(account)
|
|
23
|
+
* .withPermit() // optional (active permit)
|
|
24
|
+
* // or .withPermit(permitHash) / .withPermit(permit)
|
|
25
|
+
* .execute()
|
|
26
|
+
*
|
|
27
|
+
* If chainId not set, uses client's chainId
|
|
28
|
+
* If account not set, uses client's account
|
|
29
|
+
* withPermit() uses chainId + account to get the active permit.
|
|
30
|
+
* withPermit(permitHash) fetches that permit using chainId + account.
|
|
31
|
+
* withPermit(permit) uses the provided permit regardless of chainId/account.
|
|
32
|
+
*
|
|
33
|
+
* Note: decryptForView always requires a permit (no global-allowance mode).
|
|
34
|
+
*
|
|
35
|
+
* Returns the unsealed item.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
type DecryptForViewBuilderParams<U extends FheTypes> = BaseBuilderParams & {
|
|
39
|
+
ctHash: bigint | string;
|
|
40
|
+
utype: U;
|
|
41
|
+
permitHash?: string;
|
|
42
|
+
permit?: Permit;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export class DecryptForViewBuilder<U extends FheTypes> extends BaseBuilder {
|
|
46
|
+
private ctHash: bigint | string;
|
|
47
|
+
private utype: U;
|
|
48
|
+
private permitHash?: string;
|
|
49
|
+
private permit?: Permit;
|
|
50
|
+
private pollCallback?: DecryptPollCallbackFunction;
|
|
51
|
+
|
|
52
|
+
constructor(params: DecryptForViewBuilderParams<U>) {
|
|
53
|
+
super({
|
|
54
|
+
config: params.config,
|
|
55
|
+
publicClient: params.publicClient,
|
|
56
|
+
walletClient: params.walletClient,
|
|
57
|
+
chainId: params.chainId,
|
|
58
|
+
account: params.account,
|
|
59
|
+
requireConnected: params.requireConnected,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.ctHash = params.ctHash;
|
|
63
|
+
this.utype = params.utype;
|
|
64
|
+
this.permitHash = params.permitHash;
|
|
65
|
+
this.permit = params.permit;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param chainId - Chain to decrypt values from. Used to fetch the threshold network URL and use the correct permit.
|
|
70
|
+
*
|
|
71
|
+
* If not provided, the chainId will be fetched from the connected publicClient.
|
|
72
|
+
*
|
|
73
|
+
* Example:
|
|
74
|
+
* ```typescript
|
|
75
|
+
* const unsealed = await client.decryptForView(ctHash, utype)
|
|
76
|
+
* .setChainId(11155111)
|
|
77
|
+
* .execute();
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @returns The chainable DecryptForViewBuilder instance.
|
|
81
|
+
*/
|
|
82
|
+
setChainId(chainId: number): DecryptForViewBuilder<U> {
|
|
83
|
+
this.chainId = chainId;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getChainId(): number | undefined {
|
|
88
|
+
return this.chainId;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param account - Account to decrypt values from. Used to fetch the correct permit.
|
|
93
|
+
*
|
|
94
|
+
* If not provided, the account will be fetched from the connected walletClient.
|
|
95
|
+
*
|
|
96
|
+
* Example:
|
|
97
|
+
* ```typescript
|
|
98
|
+
* const unsealed = await client.decryptForView(ctHash, utype)
|
|
99
|
+
* .setAccount('0x1234567890123456789012345678901234567890')
|
|
100
|
+
* .execute();
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @returns The chainable DecryptForViewBuilder instance.
|
|
104
|
+
*/
|
|
105
|
+
setAccount(account: string): DecryptForViewBuilder<U> {
|
|
106
|
+
this.account = account;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getAccount(): string | undefined {
|
|
111
|
+
return this.account;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onPoll(callback: DecryptPollCallbackFunction): DecryptForViewBuilder<U> {
|
|
115
|
+
this.pollCallback = callback;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Select "use permit" mode (optional).
|
|
121
|
+
*
|
|
122
|
+
* - `withPermit(permit)` uses the provided permit.
|
|
123
|
+
* - `withPermit(permitHash)` fetches that permit.
|
|
124
|
+
* - `withPermit()` uses the active permit for the resolved `chainId + account`.
|
|
125
|
+
*/
|
|
126
|
+
withPermit(): DecryptForViewBuilder<U>;
|
|
127
|
+
withPermit(permitHash: string): DecryptForViewBuilder<U>;
|
|
128
|
+
withPermit(permit: Permit): DecryptForViewBuilder<U>;
|
|
129
|
+
withPermit(permitOrPermitHash?: Permit | string): DecryptForViewBuilder<U> {
|
|
130
|
+
if (typeof permitOrPermitHash === 'string') {
|
|
131
|
+
this.permitHash = permitOrPermitHash;
|
|
132
|
+
this.permit = undefined;
|
|
133
|
+
} else if (permitOrPermitHash === undefined) {
|
|
134
|
+
// Explicitly choose "active permit" resolution at execute()
|
|
135
|
+
this.permitHash = undefined;
|
|
136
|
+
this.permit = undefined;
|
|
137
|
+
} else {
|
|
138
|
+
// Permit object
|
|
139
|
+
this.permit = permitOrPermitHash;
|
|
140
|
+
this.permitHash = undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param permitHash - Permit hash to decrypt values from. Used to fetch the correct permit.
|
|
148
|
+
*
|
|
149
|
+
* If not provided, the active permit for the chainId and account will be used.
|
|
150
|
+
* If `setPermit()` is called, it will be used regardless of chainId, account, or permitHash.
|
|
151
|
+
*
|
|
152
|
+
* Example:
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const unsealed = await client.decryptForView(ctHash, utype)
|
|
155
|
+
* .setPermitHash('0x1234567890123456789012345678901234567890')
|
|
156
|
+
* .execute();
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* @returns The chainable DecryptForViewBuilder instance.
|
|
160
|
+
*/
|
|
161
|
+
/** @deprecated Use `withPermit(permitHash)` instead. */
|
|
162
|
+
setPermitHash(permitHash: string): DecryptForViewBuilder<U> {
|
|
163
|
+
return this.withPermit(permitHash);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getPermitHash(): string | undefined {
|
|
167
|
+
return this.permitHash;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param permit - Permit to decrypt values with. If provided, it will be used regardless of chainId, account, or permitHash.
|
|
172
|
+
*
|
|
173
|
+
* If not provided, the permit will be determined by chainId, account, and permitHash.
|
|
174
|
+
*
|
|
175
|
+
* Example:
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const unsealed = await client.decryptForView(ctHash, utype)
|
|
178
|
+
* .setPermit(permit)
|
|
179
|
+
* .execute();
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @returns The chainable DecryptForViewBuilder instance.
|
|
183
|
+
*/
|
|
184
|
+
/** @deprecated Use `withPermit(permit)` instead. */
|
|
185
|
+
setPermit(permit: Permit): DecryptForViewBuilder<U> {
|
|
186
|
+
return this.withPermit(permit);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getPermit(): Permit | undefined {
|
|
190
|
+
return this.permit;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async getThresholdNetworkUrl(): Promise<string> {
|
|
194
|
+
this.assertChainId();
|
|
195
|
+
return getThresholdNetworkUrlOrThrow(this.config, this.chainId);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private validateUtypeOrThrow(): void {
|
|
199
|
+
if (!isValidUtype(this.utype))
|
|
200
|
+
throw new CofheError({
|
|
201
|
+
code: CofheErrorCode.InvalidUtype,
|
|
202
|
+
message: `Invalid utype to decrypt to`,
|
|
203
|
+
context: {
|
|
204
|
+
utype: this.utype,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async getResolvedPermit(): Promise<Permit> {
|
|
210
|
+
if (this.permit) return this.permit;
|
|
211
|
+
|
|
212
|
+
this.assertChainId();
|
|
213
|
+
this.assertAccount();
|
|
214
|
+
|
|
215
|
+
// Fetch with permit hash
|
|
216
|
+
if (this.permitHash) {
|
|
217
|
+
const permit = await permits.getPermit(this.chainId, this.account, this.permitHash);
|
|
218
|
+
if (!permit) {
|
|
219
|
+
throw new CofheError({
|
|
220
|
+
code: CofheErrorCode.PermitNotFound,
|
|
221
|
+
message: `Permit with hash <${this.permitHash}> not found for account <${this.account}> and chainId <${this.chainId}>`,
|
|
222
|
+
hint: 'Ensure the permit exists and is valid.',
|
|
223
|
+
context: {
|
|
224
|
+
chainId: this.chainId,
|
|
225
|
+
account: this.account,
|
|
226
|
+
permitHash: this.permitHash,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return permit;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Fetch with active permit
|
|
234
|
+
const permit = await permits.getActivePermit(this.chainId, this.account);
|
|
235
|
+
if (!permit) {
|
|
236
|
+
throw new CofheError({
|
|
237
|
+
code: CofheErrorCode.PermitNotFound,
|
|
238
|
+
message: `Active permit not found for chainId <${this.chainId}> and account <${this.account}>`,
|
|
239
|
+
hint: 'Ensure a permit exists for this account on this chain.',
|
|
240
|
+
context: {
|
|
241
|
+
chainId: this.chainId,
|
|
242
|
+
account: this.account,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return permit;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* On hardhat, interact with MockZkVerifier contract instead of CoFHE
|
|
251
|
+
*/
|
|
252
|
+
private async mocksSealOutput(permit: Permit): Promise<bigint> {
|
|
253
|
+
this.assertPublicClient();
|
|
254
|
+
|
|
255
|
+
// Configurable delay before decrypting the output to simulate the CoFHE decrypt processing time
|
|
256
|
+
// Recommended 1000ms on web
|
|
257
|
+
// Recommended 0ms on hardhat (will be called during tests no need for fake delay)
|
|
258
|
+
const mocksDecryptDelay = this.config.mocks.decryptDelay;
|
|
259
|
+
if (mocksDecryptDelay > 0) await sleep(mocksDecryptDelay);
|
|
260
|
+
|
|
261
|
+
return cofheMocksDecryptForView(this.ctHash, this.utype, permit, this.publicClient);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* In the production context, perform a true decryption with the CoFHE coprocessor.
|
|
266
|
+
*/
|
|
267
|
+
private async productionSealOutput(permit: Permit): Promise<bigint> {
|
|
268
|
+
this.assertChainId();
|
|
269
|
+
this.assertPublicClient();
|
|
270
|
+
|
|
271
|
+
const thresholdNetworkUrl = await this.getThresholdNetworkUrl();
|
|
272
|
+
const permission = PermitUtils.getPermission(permit, true);
|
|
273
|
+
// const sealed = await tnSealOutputV1(this.ctHash, this.chainId, permission, thresholdNetworkUrl);
|
|
274
|
+
const sealed = await tnSealOutputV2({
|
|
275
|
+
ctHash: this.ctHash,
|
|
276
|
+
chainId: this.chainId,
|
|
277
|
+
permission,
|
|
278
|
+
thresholdNetworkUrl,
|
|
279
|
+
onPoll: this.pollCallback,
|
|
280
|
+
});
|
|
281
|
+
return PermitUtils.unseal(permit, sealed);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Final step of the decryption process. MUST BE CALLED LAST IN THE CHAIN.
|
|
286
|
+
*
|
|
287
|
+
* This will:
|
|
288
|
+
* - Use a permit based on provided permit OR chainId + account + permitHash
|
|
289
|
+
* - Check permit validity
|
|
290
|
+
* - Call CoFHE `/sealoutput` with the permit, which returns a sealed (encrypted) item
|
|
291
|
+
* - Unseal the sealed item with the permit
|
|
292
|
+
* - Return the unsealed item
|
|
293
|
+
*
|
|
294
|
+
* Example:
|
|
295
|
+
* ```typescript
|
|
296
|
+
* const unsealed = await client.decryptForView(ctHash, utype)
|
|
297
|
+
* .setChainId(11155111) // optional
|
|
298
|
+
* .setAccount('0x123...890') // optional
|
|
299
|
+
* .withPermit() // optional
|
|
300
|
+
* .execute(); // execute
|
|
301
|
+
* ```
|
|
302
|
+
*
|
|
303
|
+
* @returns The unsealed item.
|
|
304
|
+
*/
|
|
305
|
+
async execute(): Promise<UnsealedItem<U>> {
|
|
306
|
+
// Ensure utype is valid
|
|
307
|
+
this.validateUtypeOrThrow();
|
|
308
|
+
|
|
309
|
+
// Resolve permit
|
|
310
|
+
const permit = await this.getResolvedPermit();
|
|
311
|
+
|
|
312
|
+
// Ensure permit validity
|
|
313
|
+
PermitUtils.validate(permit);
|
|
314
|
+
|
|
315
|
+
// Extract chainId from signed permit
|
|
316
|
+
// Use this chainId to fetch the threshold network URL since this.chainId may be undefined
|
|
317
|
+
const chainId = permit._signedDomain!.chainId;
|
|
318
|
+
|
|
319
|
+
// Check permit validity on-chain
|
|
320
|
+
// TODO: PermitUtils.validateOnChain(permit, this.publicClient);
|
|
321
|
+
|
|
322
|
+
let unsealed: bigint;
|
|
323
|
+
|
|
324
|
+
if (chainId === hardhat.id) {
|
|
325
|
+
unsealed = await this.mocksSealOutput(permit);
|
|
326
|
+
} else {
|
|
327
|
+
unsealed = await this.productionSealOutput(permit);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return convertViaUtype(this.utype, unsealed);
|
|
331
|
+
}
|
|
332
|
+
}
|