@cofhe/sdk 0.5.2 → 0.6.1
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 +30 -0
- package/chains/chains/hardhat.ts +3 -3
- package/core/consts.ts +0 -3
- package/core/debug.ts +72 -0
- package/core/decrypt/tnDecryptV2.ts +20 -13
- package/core/decrypt/tnSealOutputV2.ts +20 -13
- package/core/encrypt/cofheMocksZkVerifySign.ts +2 -2
- package/core/encrypt/encryptInputsBuilder.ts +46 -11
- package/core/encrypt/zkPackProveVerify.ts +3 -3
- package/core/index.ts +23 -1
- package/core/permits.ts +17 -7
- package/core/test/encryptInputsBuilder.test.ts +35 -0
- package/core/test/permits.test.ts +62 -0
- package/core/types.ts +65 -5
- package/dist/chains.cjs +3 -3
- package/dist/chains.js +1 -1
- package/dist/{chunk-4FP4V35O.js → chunk-ESMZCFJY.js} +1 -2
- package/dist/{chunk-TBLR7NNE.js → chunk-MTRAXQXC.js} +3 -3
- package/dist/{chunk-YDOK4BDL.js → chunk-NOC3PYB7.js} +108 -33
- package/dist/{chunk-MRCKUMOS.js → chunk-VB62WYPL.js} +1 -1
- package/dist/{clientTypes-BJbFeeno.d.cts → clientTypes-BDy1qIBu.d.cts} +73 -11
- package/dist/{clientTypes-CEno_BEf.d.ts → clientTypes-CyUvRRzA.d.ts} +73 -11
- package/dist/core.cjs +110 -34
- package/dist/core.d.cts +26 -5
- package/dist/core.d.ts +26 -5
- package/dist/core.js +4 -4
- package/dist/node.cjs +100 -32
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +4 -4
- package/dist/permits.d.cts +10 -6
- package/dist/permits.d.ts +10 -6
- package/dist/permits.js +2 -2
- package/dist/web.cjs +100 -32
- package/dist/web.d.cts +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js +4 -4
- package/dist/zkProve.worker.js +1 -1
- package/package.json +2 -2
- package/permits/store.ts +1 -0
- package/web/test/ssr.test.ts +23 -0
- package/web/test/tfheinit.web.test.ts +81 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @cofhe/sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 670cda8: Prepare an alpha snapshot release.
|
|
8
|
+
|
|
9
|
+
## 0.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 566f126: Remove the legacy `TestBed` mock surface and stop auto-deploying `SimpleTest` through the Hardhat plugins. Core mock contracts still deploy automatically, while tests that need `SimpleTest` should deploy it explicitly from their own artifacts.
|
|
14
|
+
|
|
15
|
+
This also removes `TEST_BED_ADDRESS` and cleans up duplicate `SimpleTest` exports from `@cofhe/mock-contracts`.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- bf23270: Upgrade `zustand` to 5.0.13 to pick up the upstream persist storage fix used by the SDK and React package.
|
|
20
|
+
- 2711f9b: Add hash-plus-proof (HPP) support to `EncryptInputsBuilder`.
|
|
21
|
+
|
|
22
|
+
Calling `.asHashPlusProof()` on the builder transitions its type parameter to `HPP = true`, causing `.execute()` to return `HashPlusProofResult<T>` — a typed tuple of per-input `External*Hash` values followed by a single `ExternalHashProof`, matching the Solidity `externalEbool` / `externalEuint*` / `externalEaddress` type aliases.
|
|
23
|
+
|
|
24
|
+
New public types exported from `@cofhe/sdk`:
|
|
25
|
+
|
|
26
|
+
- `ExternalBoolHash`, `ExternalUint8Hash`, `ExternalUint16Hash`, `ExternalUint32Hash`, `ExternalUint64Hash`, `ExternalUint128Hash`, `ExternalAddressHash` — branded `0x${string}` types discriminated by `utype`
|
|
27
|
+
- `ExternalHashProof` — branded `0x${string}` proof blob
|
|
28
|
+
- `AnyExternalHash` — union of all `External*Hash` types
|
|
29
|
+
- `EncryptableToExternalHashMap<E>` — maps a single `EncryptableItem` to its `External*Hash`
|
|
30
|
+
- `ExternalItemHashes<T>` — maps an `EncryptableItem[]` tuple to the corresponding hash tuple
|
|
31
|
+
- `HashPlusProofResult<T>` — `[...ExternalItemHashes<T>, ExternalHashProof]`
|
|
32
|
+
|
|
3
33
|
## 0.5.2
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/chains/chains/hardhat.ts
CHANGED
|
@@ -8,8 +8,8 @@ export const hardhat = defineChain({
|
|
|
8
8
|
name: 'Hardhat',
|
|
9
9
|
network: 'localhost',
|
|
10
10
|
// These are unused in the mock environment
|
|
11
|
-
coFheUrl: 'http://
|
|
12
|
-
verifierUrl: 'http://
|
|
13
|
-
thresholdNetworkUrl: 'http://
|
|
11
|
+
coFheUrl: 'http://ignored-in-mock-environment',
|
|
12
|
+
verifierUrl: 'http://ignored-in-mock-environment',
|
|
13
|
+
thresholdNetworkUrl: 'http://ignored-in-mock-environment',
|
|
14
14
|
environment: 'MOCK',
|
|
15
15
|
});
|
package/core/consts.ts
CHANGED
|
@@ -7,9 +7,6 @@ export const MOCKS_ZK_VERIFIER_ADDRESS = '0x000000000000000000000000000000000000
|
|
|
7
7
|
/** Mock Threshold Network contract address (used for testing) */
|
|
8
8
|
export const MOCKS_THRESHOLD_NETWORK_ADDRESS = '0x0000000000000000000000000000000000005002' as const;
|
|
9
9
|
|
|
10
|
-
/** Test Bed contract address (used for testing) */
|
|
11
|
-
export const TEST_BED_ADDRESS = '0x0000000000000000000000000000000000005003' as const;
|
|
12
|
-
|
|
13
10
|
/** Private key for the Mock ZK Verifier signer account */
|
|
14
11
|
export const MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY =
|
|
15
12
|
'0x6C8D7F768A6BB4AAFE85E8A2F5A9680355239C7E14646ED62B044E39DE154512' as const;
|
package/core/debug.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Neutral, opt-in debug interceptors around every low-level SDK network request.
|
|
2
|
+
// Purpose-agnostic: a registered handler can observe, rewrite the URL, mutate the
|
|
3
|
+
// request body/init, replace the response, or throw — enabling fault-injection,
|
|
4
|
+
// logging, latency simulation, endpoint redirection, etc. without any
|
|
5
|
+
// scenario-specific code in the SDK.
|
|
6
|
+
//
|
|
7
|
+
// Nothing is active unless an interceptor is registered (setCofheDebugInterceptors),
|
|
8
|
+
// so this is a no-op in production by default.
|
|
9
|
+
|
|
10
|
+
export interface CofheRequestContext {
|
|
11
|
+
/** Coarse label for the call site, e.g. 'decrypt' | 'sealoutput' | 'fetchKeys'. */
|
|
12
|
+
op: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CofheRequestOverride {
|
|
16
|
+
url?: string;
|
|
17
|
+
init?: RequestInit;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type CofheOnRequest = (
|
|
21
|
+
url: string,
|
|
22
|
+
init: RequestInit | undefined,
|
|
23
|
+
ctx: CofheRequestContext
|
|
24
|
+
) => CofheRequestOverride | void | Promise<CofheRequestOverride | void>;
|
|
25
|
+
|
|
26
|
+
export type CofheOnResponse = (
|
|
27
|
+
response: Response,
|
|
28
|
+
ctx: CofheRequestContext
|
|
29
|
+
) => Response | void | Promise<Response | void>;
|
|
30
|
+
|
|
31
|
+
export interface CofheDebugInterceptors {
|
|
32
|
+
onRequest?: CofheOnRequest;
|
|
33
|
+
onResponse?: CofheOnResponse;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const interceptors: CofheDebugInterceptors = {};
|
|
37
|
+
|
|
38
|
+
/** Register (or clear, with null) the debug interceptors. */
|
|
39
|
+
export function setCofheDebugInterceptors(next: CofheDebugInterceptors | null): void {
|
|
40
|
+
interceptors.onRequest = next?.onRequest;
|
|
41
|
+
interceptors.onResponse = next?.onResponse;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getCofheDebugInterceptors(): CofheDebugInterceptors {
|
|
45
|
+
return interceptors;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* fetch() wrapper used by all low-level SDK network ops. Applies the registered
|
|
50
|
+
* debug interceptors when present; otherwise behaves exactly like fetch().
|
|
51
|
+
*/
|
|
52
|
+
export async function cofheFetch(
|
|
53
|
+
url: string,
|
|
54
|
+
init?: RequestInit,
|
|
55
|
+
ctx: CofheRequestContext = { op: 'request' }
|
|
56
|
+
): Promise<Response> {
|
|
57
|
+
let finalUrl = url;
|
|
58
|
+
let finalInit = init;
|
|
59
|
+
if (interceptors.onRequest) {
|
|
60
|
+
const override = await interceptors.onRequest(finalUrl, finalInit, ctx);
|
|
61
|
+
if (override) {
|
|
62
|
+
if (override.url !== undefined) finalUrl = override.url;
|
|
63
|
+
if (override.init !== undefined) finalInit = override.init;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let response = await fetch(finalUrl, finalInit);
|
|
67
|
+
if (interceptors.onResponse) {
|
|
68
|
+
const replaced = await interceptors.onResponse(response, ctx);
|
|
69
|
+
if (replaced) response = replaced;
|
|
70
|
+
}
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Permission } from '@/permits';
|
|
2
|
+
import { cofheFetch } from '../debug.js';
|
|
2
3
|
|
|
3
4
|
import { CofheError, CofheErrorCode } from '../error';
|
|
4
5
|
import { type DecryptPollCallbackFunction } from '../types';
|
|
@@ -200,13 +201,16 @@ async function submitDecryptRequestV2(
|
|
|
200
201
|
for (;;) {
|
|
201
202
|
let response: Response;
|
|
202
203
|
try {
|
|
203
|
-
response = await
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
204
|
+
response = await cofheFetch(
|
|
205
|
+
`${thresholdNetworkUrl}/v2/decrypt`,
|
|
206
|
+
/*op:decrypt*/ {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
headers: {
|
|
209
|
+
'Content-Type': 'application/json',
|
|
210
|
+
},
|
|
211
|
+
body: JSON.stringify(body),
|
|
212
|
+
}
|
|
213
|
+
);
|
|
210
214
|
} catch (e) {
|
|
211
215
|
throw new CofheError({
|
|
212
216
|
code: CofheErrorCode.DecryptFailed,
|
|
@@ -352,12 +356,15 @@ async function pollDecryptStatusV2(
|
|
|
352
356
|
|
|
353
357
|
let response: Response;
|
|
354
358
|
try {
|
|
355
|
-
response = await
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
359
|
+
response = await cofheFetch(
|
|
360
|
+
`${thresholdNetworkUrl}/v2/decrypt/${requestId}`,
|
|
361
|
+
/*op:decrypt-poll*/ {
|
|
362
|
+
method: 'GET',
|
|
363
|
+
headers: {
|
|
364
|
+
'Content-Type': 'application/json',
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
);
|
|
361
368
|
} catch (e) {
|
|
362
369
|
throw new CofheError({
|
|
363
370
|
code: CofheErrorCode.DecryptFailed,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Permission, type EthEncryptedData } from '@/permits';
|
|
2
|
+
import { cofheFetch } from '../debug.js';
|
|
2
3
|
|
|
3
4
|
import { CofheError, CofheErrorCode } from '../error.js';
|
|
4
5
|
import { type DecryptPollCallbackFunction } from '../types.js';
|
|
@@ -150,13 +151,16 @@ async function submitSealOutputRequest(
|
|
|
150
151
|
for (;;) {
|
|
151
152
|
let response: Response;
|
|
152
153
|
try {
|
|
153
|
-
response = await
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
response = await cofheFetch(
|
|
155
|
+
`${thresholdNetworkUrl}/v2/sealoutput`,
|
|
156
|
+
/*op:sealoutput*/ {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
'Content-Type': 'application/json',
|
|
160
|
+
},
|
|
161
|
+
body: JSON.stringify(body),
|
|
162
|
+
}
|
|
163
|
+
);
|
|
160
164
|
} catch (e) {
|
|
161
165
|
throw new CofheError({
|
|
162
166
|
code: CofheErrorCode.SealOutputFailed,
|
|
@@ -303,12 +307,15 @@ async function pollSealOutputStatus(
|
|
|
303
307
|
|
|
304
308
|
let response: Response;
|
|
305
309
|
try {
|
|
306
|
-
response = await
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
310
|
+
response = await cofheFetch(
|
|
311
|
+
`${thresholdNetworkUrl}/v2/sealoutput/${requestId}`,
|
|
312
|
+
/*op:sealoutput-poll*/ {
|
|
313
|
+
method: 'GET',
|
|
314
|
+
headers: {
|
|
315
|
+
'Content-Type': 'application/json',
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
);
|
|
312
319
|
} catch (e) {
|
|
313
320
|
throw new CofheError({
|
|
314
321
|
code: CofheErrorCode.SealOutputFailed,
|
|
@@ -185,8 +185,8 @@ async function createProofSignatures(
|
|
|
185
185
|
items: EncryptableItemWithCtHash[],
|
|
186
186
|
securityZone: number,
|
|
187
187
|
account: string
|
|
188
|
-
): Promise
|
|
189
|
-
let signatures: string[] = [];
|
|
188
|
+
): Promise<`0x${string}`[]> {
|
|
189
|
+
let signatures: `0x${string}`[] = [];
|
|
190
190
|
|
|
191
191
|
// Create wallet client for the encrypted input signer
|
|
192
192
|
// This wallet won't send a transaction, so gas isn't needed
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type EncryptedItemInputs,
|
|
17
17
|
type TfheInitializer,
|
|
18
18
|
type EncryptStepCallbackContext,
|
|
19
|
+
type HashPlusProofResult,
|
|
19
20
|
} from '../types.js';
|
|
20
21
|
import { cofheMocksCheckEncryptableBits, cofheMocksZkVerifySign } from './cofheMocksZkVerifySign.js';
|
|
21
22
|
import { hardhat } from 'viem/chains';
|
|
@@ -47,10 +48,11 @@ type EncryptInputsBuilderParams<T extends EncryptableItem[]> = BaseBuilderParams
|
|
|
47
48
|
* config, tfhePublicKeyDeserializer, compactPkeCrsDeserializer, and zkBuilderAndCrsGenerator are required to be set in the builder.
|
|
48
49
|
*/
|
|
49
50
|
|
|
50
|
-
export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuilder {
|
|
51
|
+
export class EncryptInputsBuilder<T extends EncryptableItem[], HPP extends boolean = false> extends BaseBuilder {
|
|
51
52
|
private securityZone: number;
|
|
52
53
|
private stepCallback?: EncryptStepCallbackFunction;
|
|
53
54
|
private inputItems: [...T];
|
|
55
|
+
private hpp: boolean = false;
|
|
54
56
|
|
|
55
57
|
private zkvWalletClient: WalletClient | undefined;
|
|
56
58
|
|
|
@@ -154,7 +156,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
154
156
|
*
|
|
155
157
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
156
158
|
*/
|
|
157
|
-
setAccount(account: string): EncryptInputsBuilder<T> {
|
|
159
|
+
setAccount(account: string): EncryptInputsBuilder<T, HPP> {
|
|
158
160
|
this.account = account;
|
|
159
161
|
return this;
|
|
160
162
|
}
|
|
@@ -177,7 +179,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
177
179
|
*
|
|
178
180
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
179
181
|
*/
|
|
180
|
-
setChainId(chainId: number): EncryptInputsBuilder<T> {
|
|
182
|
+
setChainId(chainId: number): EncryptInputsBuilder<T, HPP> {
|
|
181
183
|
this.chainId = chainId;
|
|
182
184
|
return this;
|
|
183
185
|
}
|
|
@@ -200,7 +202,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
200
202
|
*
|
|
201
203
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
202
204
|
*/
|
|
203
|
-
setSecurityZone(securityZone: number): EncryptInputsBuilder<T> {
|
|
205
|
+
setSecurityZone(securityZone: number): EncryptInputsBuilder<T, HPP> {
|
|
204
206
|
this.securityZone = securityZone;
|
|
205
207
|
return this;
|
|
206
208
|
}
|
|
@@ -209,6 +211,21 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
209
211
|
return this.securityZone;
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Example:
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const encrypted = await encryptInputs([Encryptable.uint128(10n)])
|
|
218
|
+
* .asHashPlusProof()
|
|
219
|
+
* .execute();
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @returns Chainable EncryptInputsBuilder instance that will return a HashPlusProofResult instead of an array of EncryptedItemInputs.
|
|
223
|
+
*/
|
|
224
|
+
asHashPlusProof(): EncryptInputsBuilder<T, true> {
|
|
225
|
+
this.hpp = true;
|
|
226
|
+
return this as unknown as EncryptInputsBuilder<T, true>;
|
|
227
|
+
}
|
|
228
|
+
|
|
212
229
|
/**
|
|
213
230
|
* @param useWorker - Whether to use Web Workers for ZK proof generation.
|
|
214
231
|
*
|
|
@@ -223,7 +240,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
223
240
|
*
|
|
224
241
|
* @returns The chainable EncryptInputsBuilder instance.
|
|
225
242
|
*/
|
|
226
|
-
setUseWorker(useWorker: boolean): EncryptInputsBuilder<T> {
|
|
243
|
+
setUseWorker(useWorker: boolean): EncryptInputsBuilder<T, HPP> {
|
|
227
244
|
this.useWorker = useWorker;
|
|
228
245
|
return this;
|
|
229
246
|
}
|
|
@@ -260,7 +277,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
260
277
|
*
|
|
261
278
|
* @returns The EncryptInputsBuilder instance.
|
|
262
279
|
*/
|
|
263
|
-
onStep(callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T> {
|
|
280
|
+
onStep(callback: EncryptStepCallbackFunction): EncryptInputsBuilder<T, HPP> {
|
|
264
281
|
this.stepCallback = callback;
|
|
265
282
|
return this;
|
|
266
283
|
}
|
|
@@ -541,7 +558,7 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
541
558
|
const verifyResults = await zkVerify(zkVerifierUrl, proof, this.account, this.securityZone, this.chainId);
|
|
542
559
|
// Add securityZone and utype to the verify results
|
|
543
560
|
const encryptedInputs: EncryptedItemInput[] = verifyResults.map(
|
|
544
|
-
({ ct_hash, signature }: { ct_hash: string; signature: string }, index: number) => ({
|
|
561
|
+
({ ct_hash, signature }: { ct_hash: string; signature: `0x${string}` }, index: number) => ({
|
|
545
562
|
ctHash: BigInt(ct_hash),
|
|
546
563
|
securityZone: this.securityZone,
|
|
547
564
|
utype: this.inputItems[index].utype,
|
|
@@ -554,6 +571,18 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
554
571
|
return encryptedInputs as [...EncryptedItemInputs<T>];
|
|
555
572
|
}
|
|
556
573
|
|
|
574
|
+
private structsToHashPlusProof(inItems: [...EncryptedItemInputs<T>]): HashPlusProofResult<T> {
|
|
575
|
+
let hashes: string[] = [];
|
|
576
|
+
let proof: string = '';
|
|
577
|
+
|
|
578
|
+
for (const item of inItems) {
|
|
579
|
+
hashes.push('0x' + item.ctHash.toString(16).padStart(64, '0'));
|
|
580
|
+
proof += item.signature;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return [...hashes, proof] as unknown as HashPlusProofResult<T>;
|
|
584
|
+
}
|
|
585
|
+
|
|
557
586
|
/**
|
|
558
587
|
* Final step of the encryption process. MUST BE CALLED LAST IN THE CHAIN.
|
|
559
588
|
*
|
|
@@ -573,11 +602,17 @@ export class EncryptInputsBuilder<T extends EncryptableItem[]> extends BaseBuild
|
|
|
573
602
|
*
|
|
574
603
|
* @returns The encrypted inputs.
|
|
575
604
|
*/
|
|
576
|
-
async execute(): Promise<[...EncryptedItemInputs<T>]> {
|
|
577
|
-
|
|
578
|
-
|
|
605
|
+
async execute(): Promise<HPP extends true ? HashPlusProofResult<T> : [...EncryptedItemInputs<T>]> {
|
|
606
|
+
type Result = HPP extends true ? HashPlusProofResult<T> : [...EncryptedItemInputs<T>];
|
|
607
|
+
|
|
608
|
+
let items: [...EncryptedItemInputs<T>];
|
|
579
609
|
|
|
610
|
+
// On hardhat chain, interact with MockZkVerifier contract instead of CoFHE
|
|
611
|
+
if (this.chainId === hardhat.id) items = await this.mocksExecute();
|
|
580
612
|
// On other chains, interact with CoFHE coprocessor
|
|
581
|
-
|
|
613
|
+
else items = await this.productionExecute();
|
|
614
|
+
|
|
615
|
+
if (this.hpp) return this.structsToHashPlusProof(items) as unknown as Result;
|
|
616
|
+
return items as unknown as Result;
|
|
582
617
|
}
|
|
583
618
|
}
|
|
@@ -49,7 +49,7 @@ export type VerifyResultRaw = {
|
|
|
49
49
|
|
|
50
50
|
export type VerifyResult = {
|
|
51
51
|
ct_hash: string;
|
|
52
|
-
signature: string
|
|
52
|
+
signature: `0x${string}`;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
export type ZkProvenCiphertextList = {
|
|
@@ -321,6 +321,6 @@ export const zkVerify = async (
|
|
|
321
321
|
}
|
|
322
322
|
};
|
|
323
323
|
|
|
324
|
-
const concatSigRecid = (signature: string, recid: number): string => {
|
|
325
|
-
return signature
|
|
324
|
+
const concatSigRecid = (signature: string, recid: number): `0x${string}` => {
|
|
325
|
+
return `${signature}${(recid + 27).toString(16).padStart(2, '0')}` as `0x${string}`;
|
|
326
326
|
};
|
package/core/index.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// Client (base implementations)
|
|
2
2
|
export { createCofheClientBase, InitialConnectStore as CONNECT_STORE_DEFAULTS } from './client.js';
|
|
3
3
|
|
|
4
|
+
// Debug interceptors (neutral, opt-in network hooks for fault injection / logging)
|
|
5
|
+
export { setCofheDebugInterceptors, getCofheDebugInterceptors, cofheFetch } from './debug.js';
|
|
6
|
+
export type {
|
|
7
|
+
CofheDebugInterceptors,
|
|
8
|
+
CofheOnRequest,
|
|
9
|
+
CofheOnResponse,
|
|
10
|
+
CofheRequestContext,
|
|
11
|
+
CofheRequestOverride,
|
|
12
|
+
} from './debug.js';
|
|
13
|
+
|
|
4
14
|
// Configuration (base implementations)
|
|
5
15
|
export { createCofheConfigBase, getCofheConfigItem } from './config.js';
|
|
6
16
|
export type { CofheConfig, CofheInputConfig, CofheInternalConfig } from './config.js';
|
|
@@ -41,6 +51,19 @@ export type {
|
|
|
41
51
|
EncryptedItemInputs,
|
|
42
52
|
EncryptableToEncryptedItemInputMap,
|
|
43
53
|
FheTypeValue,
|
|
54
|
+
// Hash-plus-proof external types
|
|
55
|
+
ExternalBoolHash,
|
|
56
|
+
ExternalUint8Hash,
|
|
57
|
+
ExternalUint16Hash,
|
|
58
|
+
ExternalUint32Hash,
|
|
59
|
+
ExternalUint64Hash,
|
|
60
|
+
ExternalUint128Hash,
|
|
61
|
+
ExternalAddressHash,
|
|
62
|
+
ExternalHashProof,
|
|
63
|
+
AnyExternalHash,
|
|
64
|
+
EncryptableToExternalHashMap,
|
|
65
|
+
ExternalItemHashes,
|
|
66
|
+
HashPlusProofResult,
|
|
44
67
|
// Decryption types
|
|
45
68
|
UnsealedItem,
|
|
46
69
|
DecryptPollCallbackFunction,
|
|
@@ -96,7 +119,6 @@ export {
|
|
|
96
119
|
MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
|
|
97
120
|
MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY,
|
|
98
121
|
MOCKS_THRESHOLD_NETWORK_ADDRESS,
|
|
99
|
-
TEST_BED_ADDRESS,
|
|
100
122
|
TFHE_RS_ZK_MAX_BITS,
|
|
101
123
|
TFHE_RS_SAFE_SERIALIZATION_SIZE_LIMIT,
|
|
102
124
|
} from './consts.js';
|
package/core/permits.ts
CHANGED
|
@@ -112,13 +112,18 @@ const selectActivePermit = (chainId: number, account: string, hash: string): voi
|
|
|
112
112
|
// GET OR CREATE
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
* Get the active self permit
|
|
115
|
+
* Get the active self permit if a valid one exists, otherwise create a new one.
|
|
116
|
+
*
|
|
117
|
+
* An active permit is reused only when it is a self permit and is still valid
|
|
118
|
+
* (signed and not expired). An expired or otherwise invalid active permit is
|
|
119
|
+
* treated as missing and a fresh permit is created.
|
|
120
|
+
*
|
|
116
121
|
* @param publicClient - The public client
|
|
117
122
|
* @param walletClient - The wallet client
|
|
118
123
|
* @param chainId - Optional chain ID (will use publicClient if not provided)
|
|
119
124
|
* @param account - Optional account (will use walletClient if not provided)
|
|
120
125
|
* @param options - The options for creating a self permit
|
|
121
|
-
* @returns The existing or newly created
|
|
126
|
+
* @returns The existing valid permit or a newly created one
|
|
122
127
|
*/
|
|
123
128
|
const getOrCreateSelfPermit = async (
|
|
124
129
|
publicClient: PublicClient,
|
|
@@ -133,22 +138,27 @@ const getOrCreateSelfPermit = async (
|
|
|
133
138
|
// Try to get active permit first
|
|
134
139
|
const activePermit = await getActivePermit(_chainId, _account);
|
|
135
140
|
|
|
136
|
-
if (activePermit && activePermit.type === 'self') {
|
|
141
|
+
if (activePermit && activePermit.type === 'self' && PermitUtils.isValid(activePermit).valid) {
|
|
137
142
|
return activePermit;
|
|
138
143
|
}
|
|
139
144
|
|
|
140
|
-
// No active permit
|
|
145
|
+
// No active permit, wrong type, or expired/invalid - create new one
|
|
141
146
|
return createSelf(options ?? { issuer: _account, name: 'Autogenerated Self Permit' }, publicClient, walletClient);
|
|
142
147
|
};
|
|
143
148
|
|
|
144
149
|
/**
|
|
145
|
-
* Get the active sharing permit
|
|
150
|
+
* Get the active sharing permit if a valid one exists, otherwise create a new one.
|
|
151
|
+
*
|
|
152
|
+
* An active permit is reused only when it is a sharing permit and is still valid
|
|
153
|
+
* (signed and not expired). An expired or otherwise invalid active permit is
|
|
154
|
+
* treated as missing and a fresh permit is created.
|
|
155
|
+
*
|
|
146
156
|
* @param publicClient - The public client
|
|
147
157
|
* @param walletClient - The wallet client
|
|
148
158
|
* @param options - The options for creating a sharing permit (required)
|
|
149
159
|
* @param chainId - Optional chain ID (will use publicClient if not provided)
|
|
150
160
|
* @param account - Optional account (will use walletClient if not provided)
|
|
151
|
-
* @returns The existing or newly created
|
|
161
|
+
* @returns The existing valid permit or a newly created one
|
|
152
162
|
*/
|
|
153
163
|
const getOrCreateSharingPermit = async (
|
|
154
164
|
publicClient: PublicClient,
|
|
@@ -163,7 +173,7 @@ const getOrCreateSharingPermit = async (
|
|
|
163
173
|
// Try to get active permit first
|
|
164
174
|
const activePermit = await getActivePermit(_chainId, _account);
|
|
165
175
|
|
|
166
|
-
if (activePermit && activePermit.type === 'sharing') {
|
|
176
|
+
if (activePermit && activePermit.type === 'sharing' && PermitUtils.isValid(activePermit).valid) {
|
|
167
177
|
return activePermit;
|
|
168
178
|
}
|
|
169
179
|
|
|
@@ -744,6 +744,41 @@ describe('EncryptInputsBuilder', () => {
|
|
|
744
744
|
});
|
|
745
745
|
});
|
|
746
746
|
|
|
747
|
+
describe('asHashPlusProof', () => {
|
|
748
|
+
it('should return the same builder instance for chaining', () => {
|
|
749
|
+
expect(builder.asHashPlusProof()).toBe(builder);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('execute() returns [hash, proof] for a single input', async () => {
|
|
753
|
+
const result = await builder.asHashPlusProof().execute();
|
|
754
|
+
expect(Array.isArray(result)).toBe(true);
|
|
755
|
+
expect(result).toHaveLength(2); // 1 hash + 1 proof
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it('execute() returns [hash1, hash2, proof] for two inputs', async () => {
|
|
759
|
+
const result = await new EncryptInputsBuilder({
|
|
760
|
+
...createDefaultParams(),
|
|
761
|
+
inputs: [Encryptable.uint128(100n), Encryptable.bool(true)] as [
|
|
762
|
+
ReturnType<typeof Encryptable.uint128>,
|
|
763
|
+
ReturnType<typeof Encryptable.bool>,
|
|
764
|
+
],
|
|
765
|
+
})
|
|
766
|
+
.asHashPlusProof()
|
|
767
|
+
.execute();
|
|
768
|
+
|
|
769
|
+
expect(Array.isArray(result)).toBe(true);
|
|
770
|
+
expect(result).toHaveLength(3); // 2 hashes + 1 proof
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it('should be composable with other builder methods', async () => {
|
|
774
|
+
const overriddenSender = '0x5555555555555555555555555555555555555555';
|
|
775
|
+
const result = await builder.asHashPlusProof().setAccount(overriddenSender).execute();
|
|
776
|
+
|
|
777
|
+
expect(Array.isArray(result)).toBe(true);
|
|
778
|
+
expect(result).toHaveLength(2);
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
747
782
|
describe('setUseWorker and getUseWorker', () => {
|
|
748
783
|
it('should have setUseWorker method', () => {
|
|
749
784
|
expect(builder).toHaveProperty('setUseWorker');
|
|
@@ -304,6 +304,33 @@ describe('Core Permits Tests', () => {
|
|
|
304
304
|
expect(Object.keys(allPermits).length).toBe(2);
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
+
it('should create a new self permit when active permit is expired', async () => {
|
|
308
|
+
// Create an expired self permit (expiration in the past)
|
|
309
|
+
const expiredPermit = await permits.createSelf(
|
|
310
|
+
{ name: 'Expired Self Permit', issuer: bobAddress, expiration: Math.floor(Date.now() / 1000) - 3600 },
|
|
311
|
+
publicClient,
|
|
312
|
+
bobWalletClient
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// Sanity check - it is the active permit and is expired
|
|
316
|
+
const activeBefore = await permits.getActivePermit(chainId, bobAddress);
|
|
317
|
+
expect(activeBefore?.hash).toBe(expiredPermit.hash);
|
|
318
|
+
|
|
319
|
+
// getOrCreateSelfPermit should treat the expired permit as missing and create a fresh one
|
|
320
|
+
const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress, {
|
|
321
|
+
issuer: bobAddress,
|
|
322
|
+
name: 'Fresh Self Permit',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
expect(permit.name).toBe('Fresh Self Permit');
|
|
326
|
+
expect(permit.type).toBe('self');
|
|
327
|
+
expect(permit.hash).not.toBe(expiredPermit.hash);
|
|
328
|
+
|
|
329
|
+
// The fresh permit should now be active
|
|
330
|
+
const activeAfter = await permits.getActivePermit(chainId, bobAddress);
|
|
331
|
+
expect(activeAfter?.hash).toBe(permit.hash);
|
|
332
|
+
});
|
|
333
|
+
|
|
307
334
|
it('should use default options when none provided', async () => {
|
|
308
335
|
const permit = await permits.getOrCreateSelfPermit(publicClient, bobWalletClient, chainId, bobAddress);
|
|
309
336
|
|
|
@@ -412,6 +439,41 @@ describe('Core Permits Tests', () => {
|
|
|
412
439
|
expect(Object.keys(allPermits).length).toBe(2);
|
|
413
440
|
});
|
|
414
441
|
|
|
442
|
+
it('should create a new sharing permit when active permit is expired', async () => {
|
|
443
|
+
// Create an expired sharing permit (expiration in the past)
|
|
444
|
+
const expiredPermit = await permits.createSharing(
|
|
445
|
+
{
|
|
446
|
+
name: 'Expired Sharing Permit',
|
|
447
|
+
issuer: bobAddress,
|
|
448
|
+
recipient: aliceAddress,
|
|
449
|
+
expiration: Math.floor(Date.now() / 1000) - 3600,
|
|
450
|
+
},
|
|
451
|
+
publicClient,
|
|
452
|
+
bobWalletClient
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
// getOrCreateSharingPermit should treat the expired permit as missing and create a fresh one
|
|
456
|
+
const permit = await permits.getOrCreateSharingPermit(
|
|
457
|
+
publicClient,
|
|
458
|
+
bobWalletClient,
|
|
459
|
+
{
|
|
460
|
+
issuer: bobAddress,
|
|
461
|
+
recipient: aliceAddress,
|
|
462
|
+
name: 'Fresh Sharing Permit',
|
|
463
|
+
},
|
|
464
|
+
chainId,
|
|
465
|
+
bobAddress
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(permit.name).toBe('Fresh Sharing Permit');
|
|
469
|
+
expect(permit.type).toBe('sharing');
|
|
470
|
+
expect(permit.hash).not.toBe(expiredPermit.hash);
|
|
471
|
+
|
|
472
|
+
// The fresh permit should now be active
|
|
473
|
+
const activeAfter = await permits.getActivePermit(chainId, bobAddress);
|
|
474
|
+
expect(activeAfter?.hash).toBe(permit.hash);
|
|
475
|
+
});
|
|
476
|
+
|
|
415
477
|
it('should use default chainId and account when not provided', async () => {
|
|
416
478
|
const permit = await permits.getOrCreateSharingPermit(
|
|
417
479
|
publicClient,
|