@cofhe/sdk 0.7.1 → 1.0.0-alpha.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 +35 -0
- package/acps/acp.ts +35 -7
- package/acps/index.ts +28 -12
- package/acps/onchain-utils.ts +188 -49
- package/acps/registry.ts +231 -0
- package/acps/sealing.ts +9 -3
- package/acps/share-metadata/appKey.ts +8 -0
- package/acps/share-metadata/decode.ts +120 -0
- package/acps/share-metadata/describe.ts +198 -0
- package/acps/share-metadata/encode.ts +147 -0
- package/acps/share-metadata/erc7984.ts +26 -0
- package/acps/share-metadata/index.ts +9 -0
- package/acps/share-metadata/layout.ts +69 -0
- package/acps/share-metadata/presets.ts +116 -0
- package/acps/share-metadata/schema.ts +47 -0
- package/acps/share-metadata/test/codec.test.ts +140 -0
- package/acps/share-metadata/test/describe.test.ts +112 -0
- package/acps/share-metadata/test/fixtures.ts +157 -0
- package/acps/share-metadata/test/live.test.ts +56 -0
- package/acps/share-metadata/test/presets.test.ts +119 -0
- package/acps/share-metadata/test/verify.test.ts +144 -0
- package/acps/share-metadata/verify.ts +155 -0
- package/acps/store.ts +48 -18
- package/acps/test/acp.test.ts +102 -7
- package/acps/test/localstorage.test.ts +28 -1
- package/acps/test/onchainAccessStatus.test.ts +116 -0
- package/acps/test/registry.test.ts +144 -0
- package/acps/test/sealing.test.ts +23 -0
- package/acps/test/store.test.ts +71 -0
- package/acps/test/utils.test.ts +63 -0
- package/acps/test/validation.test.ts +0 -4
- package/acps/types.ts +59 -6
- package/acps/utils.ts +15 -2
- package/acps/validation.ts +3 -1
- package/chains/chains/arbitrum.ts +14 -0
- package/chains/chains/ethereum.ts +14 -0
- package/chains/index.ts +5 -1
- package/chains/test/chains.test.ts +24 -3
- package/core/acps.ts +164 -207
- package/core/client.ts +44 -13
- package/core/clientTypes.ts +50 -7
- package/core/config.ts +33 -1
- package/core/consts.ts +14 -2
- package/core/decrypt/apiError.ts +4 -1
- package/core/decrypt/verifyDecryptResult.ts +8 -4
- package/core/encrypt/cofheMocksZkVerifySign.ts +19 -2
- package/core/encrypt/encryptInputsBuilder.ts +6 -0
- package/core/encrypt/zkPackProveVerify.ts +7 -1
- package/core/error.ts +1 -0
- package/core/fetchKeys.ts +2 -2
- package/core/index.ts +9 -1
- package/core/keyStore.ts +23 -10
- package/core/taskManager.ts +48 -0
- package/core/test/acpDefaults.test.ts +108 -19
- package/core/test/acpStoreAccountCase.test.ts +80 -0
- package/core/test/acps.test.ts +128 -3
- package/core/test/apiError.test.ts +8 -0
- package/core/test/appKeyConfig.test.ts +94 -0
- package/core/test/client.test.ts +41 -0
- package/core/test/config.test.ts +24 -0
- package/core/test/decrypt.test.ts +11 -4
- package/core/test/decryptBuilders.test.ts +110 -2
- package/core/test/encryptInputsBuilder.test.ts +1 -1
- package/core/test/fetchKeys.test.ts +23 -2
- package/core/test/incomingShares.test.ts +84 -0
- package/core/test/keyStore.test.ts +18 -6
- package/core/test/mocksEncryptRange.test.ts +90 -0
- package/core/test/taskManager.test.ts +57 -0
- package/core/test/utils.test.ts +128 -0
- package/core/test/verifyDecryptResult.test.ts +51 -0
- package/core/types.ts +8 -0
- package/core/utils.ts +16 -9
- package/dist/acps.cjs +984 -64
- package/dist/acps.d.cts +589 -10
- package/dist/acps.d.ts +589 -10
- package/dist/acps.js +2 -2
- package/dist/chains.cjs +26 -0
- package/dist/chains.d.cts +45 -1
- package/dist/chains.d.ts +45 -1
- package/dist/chains.js +1 -1
- package/dist/chunk-6ZW76ZKB.js +228 -0
- package/dist/chunk-BQWHPIYU.js +1828 -0
- package/dist/chunk-KSOV5B4B.js +50 -0
- package/dist/{chunk-43USWPEH.js → chunk-OL3DZN7A.js} +159 -523
- package/dist/{chunk-N6IDQRRU.js → chunk-UKQAJK4N.js} +25 -1
- package/dist/chunk-XQ32JG3T.js +174 -0
- package/dist/{clientTypes-CYZjFznO.d.cts → clientTypes-C0A9ji5O.d.cts} +132 -29
- package/dist/{clientTypes-BN3nbzYM.d.ts → clientTypes-CcU5WvdV.d.ts} +132 -29
- package/dist/core.cjs +3269 -2626
- package/dist/core.d.cts +32 -8
- package/dist/core.d.ts +32 -8
- package/dist/core.js +5 -4
- package/dist/node.cjs +3074 -2436
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +20 -19
- package/dist/{acp-Wi6isVQI.d.cts → onchain-utils-D9Z33IP7.d.cts} +166 -12
- package/dist/{acp-Wi6isVQI.d.ts → onchain-utils-D9Z33IP7.d.ts} +166 -12
- package/dist/web.cjs +3204 -2493
- package/dist/web.d.cts +49 -3
- package/dist/web.d.ts +49 -3
- package/dist/web.js +52 -27
- package/dist/zkProve.worker.cjs +79 -18
- package/dist/zkProve.worker.js +24 -24
- package/node/index.ts +1 -10
- package/node/storage.ts +20 -9
- package/node/test/inherited.test.ts +5 -1
- package/node/test/storage-memory-fallback.test.ts +47 -0
- package/node/test/storage-permissions.test.ts +40 -0
- package/package.json +3 -1
- package/web/index.ts +101 -36
- package/web/test/countingZkProveWorker.ts +31 -0
- package/web/test/inherited.web.test.ts +5 -1
- package/web/test/mainThreadPool.web.test.ts +174 -0
- package/web/test/threadPoolBenchHelper.ts +134 -0
- package/web/test/threadPoolOff.web.test.ts +17 -0
- package/web/test/threadPoolOn.web.test.ts +80 -0
- package/web/test/workerInitRace.web.test.ts +79 -0
- package/web/tfheThreadPool.ts +140 -0
- package/web/workerManager.ts +4 -2
- package/web/zkProve.worker.ts +42 -26
- package/dist/chunk-ESMZCFJY.js +0 -12
- package/dist/chunk-Q7CBWGQX.js +0 -1029
package/acps/test/store.test.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
removeACP,
|
|
13
13
|
getActiveACPHash,
|
|
14
14
|
setActiveACPHash,
|
|
15
|
+
clearStaleStore,
|
|
16
|
+
ACP_STORE_DEFAULTS,
|
|
15
17
|
ACPUtils,
|
|
16
18
|
} from '../index.js';
|
|
17
19
|
|
|
@@ -85,4 +87,73 @@ describe('Storage Tests', () => {
|
|
|
85
87
|
expect(activeHash).toBeUndefined();
|
|
86
88
|
});
|
|
87
89
|
});
|
|
90
|
+
|
|
91
|
+
describe('Stale Store Detection', () => {
|
|
92
|
+
// `typeof null` and `typeof []` are both 'object', so these shapes used to pass the
|
|
93
|
+
// structure guard and stay in the store; a `null` record then throws on `acps[chainId]`.
|
|
94
|
+
const seedRawState = (state: unknown) => {
|
|
95
|
+
acpStore.store.setState(state as never, true);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
it('should clear the store when acps is null', () => {
|
|
99
|
+
seedRawState({ acps: null, activeACPHash: {} });
|
|
100
|
+
|
|
101
|
+
clearStaleStore();
|
|
102
|
+
|
|
103
|
+
expect(acpStore.store.getState()).toEqual(ACP_STORE_DEFAULTS);
|
|
104
|
+
expect(() => getACP(chainId, account, '0xdeadbeef')).not.toThrow();
|
|
105
|
+
expect(getACP(chainId, account, '0xdeadbeef')).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should clear the store when acps is an array', () => {
|
|
109
|
+
seedRawState({ acps: [], activeACPHash: {} });
|
|
110
|
+
|
|
111
|
+
clearStaleStore();
|
|
112
|
+
|
|
113
|
+
expect(acpStore.store.getState()).toEqual(ACP_STORE_DEFAULTS);
|
|
114
|
+
expect(() => getACP(chainId, account, '0xdeadbeef')).not.toThrow();
|
|
115
|
+
expect(getACP(chainId, account, '0xdeadbeef')).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should clear the store when activeACPHash is null', () => {
|
|
119
|
+
seedRawState({ acps: {}, activeACPHash: null });
|
|
120
|
+
|
|
121
|
+
clearStaleStore();
|
|
122
|
+
|
|
123
|
+
expect(acpStore.store.getState()).toEqual(ACP_STORE_DEFAULTS);
|
|
124
|
+
expect(() => getActiveACP(chainId, account)).not.toThrow();
|
|
125
|
+
expect(getActiveACP(chainId, account)).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should clear the store when activeACPHash is an array', () => {
|
|
129
|
+
seedRawState({ acps: {}, activeACPHash: [] });
|
|
130
|
+
|
|
131
|
+
clearStaleStore();
|
|
132
|
+
|
|
133
|
+
expect(acpStore.store.getState()).toEqual(ACP_STORE_DEFAULTS);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should let setACP recover a store whose acps is null', async () => {
|
|
137
|
+
const acp = await createMockACP();
|
|
138
|
+
seedRawState({ acps: null, activeACPHash: {} });
|
|
139
|
+
|
|
140
|
+
expect(() => setACP(chainId, account, acp)).not.toThrow();
|
|
141
|
+
expect(ACPUtils.serialize(getACP(chainId, account, acp.hash)!)).toEqual(ACPUtils.serialize(acp));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should leave a valid store untouched', async () => {
|
|
145
|
+
const acp = await createMockACP();
|
|
146
|
+
setACP(chainId, account, acp);
|
|
147
|
+
setActiveACPHash(chainId, account, acp.hash);
|
|
148
|
+
|
|
149
|
+
const before = acpStore.store.getState();
|
|
150
|
+
|
|
151
|
+
clearStaleStore();
|
|
152
|
+
|
|
153
|
+
// Same reference: a valid store must not be rewritten at all.
|
|
154
|
+
expect(acpStore.store.getState()).toBe(before);
|
|
155
|
+
expect(ACPUtils.serialize(getACP(chainId, account, acp.hash)!)).toEqual(ACPUtils.serialize(acp));
|
|
156
|
+
expect(getActiveACPHash(chainId, account)).toBe(acp.hash);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
88
159
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { fromHexString, toBeArray, toHexString } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
describe('fromHexString', () => {
|
|
5
|
+
it('strips the 0x prefix before padding an odd-length string', () => {
|
|
6
|
+
// Regression: padding first produced `00x123`, which decoded to [0x00, NaN->0, 0x23].
|
|
7
|
+
expect(Array.from(fromHexString('0x123'))).toEqual([0x01, 0x23]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('pads odd-length strings without a prefix', () => {
|
|
11
|
+
expect(Array.from(fromHexString('123'))).toEqual([0x01, 0x23]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('decodes even-length strings with and without the 0x prefix identically', () => {
|
|
15
|
+
expect(Array.from(fromHexString('0x1234'))).toEqual([0x12, 0x34]);
|
|
16
|
+
expect(Array.from(fromHexString('1234'))).toEqual([0x12, 0x34]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('decodes uppercase hex', () => {
|
|
20
|
+
expect(Array.from(fromHexString('0xDEADBEEF'))).toEqual([0xde, 0xad, 0xbe, 0xef]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns an empty array for empty input', () => {
|
|
24
|
+
expect(fromHexString('')).toEqual(new Uint8Array());
|
|
25
|
+
expect(fromHexString('0x')).toEqual(new Uint8Array());
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('round-trips a 32-byte sealing key through toHexString', () => {
|
|
29
|
+
const privateKey = 'a3'.repeat(32);
|
|
30
|
+
|
|
31
|
+
const bytes = fromHexString(privateKey);
|
|
32
|
+
|
|
33
|
+
expect(bytes.length).toBe(32);
|
|
34
|
+
expect(toHexString(bytes)).toBe(privateKey);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('throws instead of silently producing zero bytes for non-hex characters', () => {
|
|
38
|
+
expect(() => fromHexString('zzzz')).toThrow('Invalid hex string');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('throws for hex strings with a single invalid character', () => {
|
|
42
|
+
expect(() => fromHexString('0x12g4')).toThrow('Invalid hex string');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('only strips a lowercase 0x prefix, matching the sealing key and ACP schema checks', () => {
|
|
46
|
+
expect(() => fromHexString('0X12')).toThrow('Invalid hex string');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('does not leak the value in the error message', () => {
|
|
50
|
+
const secret = 'f'.repeat(63) + 'z';
|
|
51
|
+
|
|
52
|
+
expect(() => fromHexString(secret)).toThrow(
|
|
53
|
+
expect.objectContaining({ message: expect.not.stringContaining(secret) })
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('toBeArray', () => {
|
|
59
|
+
it('still encodes values that need an odd-length hex representation', () => {
|
|
60
|
+
expect(Array.from(toBeArray(0x123))).toEqual([0x01, 0x23]);
|
|
61
|
+
expect(Array.from(toBeArray(255n))).toEqual([0xff]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -91,7 +91,6 @@ describe('Validation Tests', () => {
|
|
|
91
91
|
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
92
92
|
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
93
93
|
issuerSignature: '0x1234567890abcdef',
|
|
94
|
-
name: 'Import ACP',
|
|
95
94
|
};
|
|
96
95
|
|
|
97
96
|
expect(() => validateImportACPOptions(options)).not.toThrow();
|
|
@@ -102,7 +101,6 @@ describe('Validation Tests', () => {
|
|
|
102
101
|
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
103
102
|
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
104
103
|
issuerSignature: '0x1234567890abcdef',
|
|
105
|
-
name: 'Import ACP',
|
|
106
104
|
};
|
|
107
105
|
expect(() => validateImportACPOptions(options)).toThrow();
|
|
108
106
|
});
|
|
@@ -113,7 +111,6 @@ describe('Validation Tests', () => {
|
|
|
113
111
|
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
114
112
|
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
115
113
|
issuerSignature: '0x',
|
|
116
|
-
name: 'Import ACP',
|
|
117
114
|
};
|
|
118
115
|
|
|
119
116
|
expect(() => validateImportACPOptions(options)).toThrow();
|
|
@@ -125,7 +122,6 @@ describe('Validation Tests', () => {
|
|
|
125
122
|
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
126
123
|
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
127
124
|
issuerSignature: '0x',
|
|
128
|
-
name: 'Import ACP',
|
|
129
125
|
};
|
|
130
126
|
|
|
131
127
|
expect(() => validateImportACPOptions(options)).toThrow();
|
package/acps/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type EthEncryptedData } from './sealing.js';
|
|
2
2
|
import { type Hex } from 'viem';
|
|
3
|
+
import type { ShareLabel } from './share-metadata/schema.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* EIP712 related types
|
|
@@ -46,6 +47,12 @@ export interface ACPPrivate {
|
|
|
46
47
|
* Never leaves the client.
|
|
47
48
|
*/
|
|
48
49
|
sealingPrivateKey: Hex;
|
|
50
|
+
/**
|
|
51
|
+
* (sharing / recipient) The share metadata: the labels of its handles, a v3 blob
|
|
52
|
+
* (`decodeShareMetadata` / `describeShareMetadata`). Not signed; travels with the share, in the
|
|
53
|
+
* exported JSON or in the registry's `Shared` event. Absent when the share has none.
|
|
54
|
+
*/
|
|
55
|
+
metadata?: Hex;
|
|
49
56
|
/**
|
|
50
57
|
* EIP712 domain used to sign this acp.
|
|
51
58
|
* Should not be set manually, included in metadata as part of serialization flows.
|
|
@@ -191,17 +198,30 @@ export type CreateSharingACPOptions = ACPScopeOptions & {
|
|
|
191
198
|
expiration?: number;
|
|
192
199
|
revokerData?: number;
|
|
193
200
|
revokerContract?: string;
|
|
201
|
+
/**
|
|
202
|
+
* The labels of the handles, one per handle in order. Kept on the acp as `metadata`, then
|
|
203
|
+
* written into `export()` and posted by `shareOnChain`.
|
|
204
|
+
*/
|
|
205
|
+
labels?: readonly ShareLabel[];
|
|
206
|
+
/**
|
|
207
|
+
* The key of the app the share is for (`shareAppKey(app)`), kept in `metadata` with the labels.
|
|
208
|
+
* A client configured with `acp: { app }` fills it in; a recipient client with the same `app`
|
|
209
|
+
* lists the share, one with another app does not.
|
|
210
|
+
*/
|
|
211
|
+
appKey?: Hex;
|
|
194
212
|
};
|
|
195
213
|
|
|
214
|
+
/** A share as exported (`SharedACP`) or read from the registry. A `name` in it is ignored. */
|
|
196
215
|
export type ImportSharedACPOptions = ACPScopeOptions & {
|
|
197
216
|
type?: 'sharing';
|
|
198
217
|
issuer: string;
|
|
199
218
|
recipient: string;
|
|
200
219
|
issuerSignature: string;
|
|
201
|
-
name?: string;
|
|
202
220
|
expiration: number;
|
|
203
221
|
revokerData?: number;
|
|
204
222
|
revokerContract?: string;
|
|
223
|
+
/** The share metadata (labels blob), kept on the recipient acp; `0x` or absent for none. */
|
|
224
|
+
metadata?: string;
|
|
205
225
|
};
|
|
206
226
|
|
|
207
227
|
/** An ACP is plain JSON-serializable data — the serialized form is the ACP itself. */
|
|
@@ -210,22 +230,55 @@ export type SerializedACP = ACP;
|
|
|
210
230
|
/**
|
|
211
231
|
* The share payload produced by `ACPUtils.export()` — the full public component
|
|
212
232
|
* with `sealingKey`/`recipientSignature` left for the recipient to fill, plus
|
|
213
|
-
* the
|
|
233
|
+
* the acp type. The issuer's `name` is not exported: it is their own note, and the
|
|
234
|
+
* recipient names the share themselves. Fixed shape: every field is always present
|
|
214
235
|
* (zero-values instead of omissions), so importers can parse a single schema.
|
|
215
|
-
* Mirrors the on-chain sharing payload struct field-for-field.
|
|
236
|
+
* Mirrors the on-chain sharing payload struct field-for-field. The one optional
|
|
237
|
+
* field is `metadata`, the labels of the handles, present only when the share has them.
|
|
216
238
|
*/
|
|
217
239
|
export type SharedACP = Expand<
|
|
218
240
|
Omit<ACPPublic, 'sealingKey' | 'recipientSignature'> & {
|
|
219
|
-
name: string;
|
|
220
241
|
type: 'sharing';
|
|
242
|
+
metadata?: Hex;
|
|
221
243
|
}
|
|
222
244
|
>;
|
|
223
245
|
|
|
246
|
+
/**
|
|
247
|
+
* An ACP on-chain status, as `client.acp.checkAccess` reports it instead of a revert.
|
|
248
|
+
* Without a handle: 'valid' or the reason it is not. With a handle, a valid ACP reports
|
|
249
|
+
* 'allowed', 'out-of-scope' (the ACP scope does not cover the handle) or
|
|
250
|
+
* 'issuer-not-allowed' (the issuer itself may not read the handle, so no ACP of theirs can).
|
|
251
|
+
*/
|
|
252
|
+
export type ACPAccessStatus =
|
|
253
|
+
| 'valid'
|
|
254
|
+
| 'allowed'
|
|
255
|
+
| 'out-of-scope'
|
|
256
|
+
| 'issuer-not-allowed'
|
|
257
|
+
| 'expired'
|
|
258
|
+
| 'revoked'
|
|
259
|
+
| 'invalid-issuer-signature'
|
|
260
|
+
| 'invalid-recipient-signature';
|
|
261
|
+
|
|
224
262
|
/**
|
|
225
263
|
* A share read back from the on-chain ACPShareRegistry: the posted payload
|
|
226
|
-
* (SharedACP minus
|
|
264
|
+
* (SharedACP minus its type), its registry id, and the
|
|
265
|
+
* metadata blob posted with it (`0x` when none) — for a SNAPSHOT share, the
|
|
266
|
+
* labels of its handles (`decodeShareMetadata` / `describeShareMetadata`).
|
|
267
|
+
*/
|
|
268
|
+
export type IncomingShare = Expand<
|
|
269
|
+
Omit<SharedACP, 'type' | 'metadata'> & {
|
|
270
|
+
shareId: Hex;
|
|
271
|
+
metadata: Hex;
|
|
272
|
+
/** The key of the app the share was posted for (`shareAppKey(app)`), when it names one. */
|
|
273
|
+
appKey?: Hex;
|
|
274
|
+
}
|
|
275
|
+
>;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* What the share label helpers read: the handles, their issuer and the metadata blob. An
|
|
279
|
+
* `IncomingShare` (on-chain route) and a sharing or recipient `ACP` (exported JSON route) both fit.
|
|
227
280
|
*/
|
|
228
|
-
export type
|
|
281
|
+
export type LabelledShare = { issuer: Hex; handles: readonly Hex[]; metadata?: Hex };
|
|
229
282
|
|
|
230
283
|
/**
|
|
231
284
|
* A type representing the acp fields that are used to generate the hash
|
package/acps/utils.ts
CHANGED
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
declare const BigInt: (value: string | number | bigint) => bigint;
|
|
4
4
|
|
|
5
|
+
const HEX_BODY_REGEX = /^[0-9a-fA-F]*$/;
|
|
6
|
+
|
|
5
7
|
export const fromHexString = (hexString: string): Uint8Array => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
// Strip `0x` before padding: padding first turns `0x123` into `00x123`, which no
|
|
9
|
+
// longer matches the prefix and decodes to garbage bytes.
|
|
10
|
+
const body = hexString.replace(/^0x/, '');
|
|
11
|
+
|
|
12
|
+
// `parseInt` maps a non-hex pair to NaN, which `Uint8Array` stores as 0. Without this check a
|
|
13
|
+
// malformed sealing key silently becomes zero bytes instead of failing.
|
|
14
|
+
if (!HEX_BODY_REGEX.test(body)) {
|
|
15
|
+
// The value is omitted on purpose: this helper also receives sealing private keys.
|
|
16
|
+
throw new Error('Invalid hex string: contains non-hexadecimal characters');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const padded = body.length % 2 === 1 ? `0${body}` : body;
|
|
20
|
+
const arr = padded.match(/.{1,2}/g);
|
|
8
21
|
if (!arr) return new Uint8Array();
|
|
9
22
|
return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
|
|
10
23
|
};
|
package/acps/validation.ts
CHANGED
|
@@ -85,6 +85,7 @@ const zACPWithDefaults = z.object({
|
|
|
85
85
|
handles: handlesSchema,
|
|
86
86
|
issuerSignature: bytesSchema.optional().default('0x'),
|
|
87
87
|
recipientSignature: bytesSchema.optional().default('0x'),
|
|
88
|
+
metadata: bytesSchema.optional(),
|
|
88
89
|
});
|
|
89
90
|
|
|
90
91
|
const zACPWithSealingKeys = zACPWithDefaults.extend({
|
|
@@ -228,7 +229,6 @@ export const ImportACPOptionsValidator = z
|
|
|
228
229
|
type: z.literal('recipient').optional().default('recipient'),
|
|
229
230
|
issuer: addressNotZeroSchema,
|
|
230
231
|
recipient: addressNotZeroSchema,
|
|
231
|
-
name: z.string().optional().default('Unnamed ACP'),
|
|
232
232
|
expiration: z.int(),
|
|
233
233
|
revokerData: z.int().optional().default(0),
|
|
234
234
|
revokerContract: addressSchema.optional().default(zeroAddress),
|
|
@@ -237,6 +237,8 @@ export const ImportACPOptionsValidator = z
|
|
|
237
237
|
handles: handlesSchema,
|
|
238
238
|
issuerSignature: bytesNotEmptySchema,
|
|
239
239
|
recipientSignature: bytesSchema.optional().default('0x'),
|
|
240
|
+
/** The share metadata (labels blob); `0x` means none and is dropped */
|
|
241
|
+
metadata: bytesSchema.optional().transform((val) => (val === '0x' ? undefined : val)),
|
|
240
242
|
})
|
|
241
243
|
.refine(...ExternalValidatorRefinement)
|
|
242
244
|
.transform(withDerivedScope)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineChain } from '../defineChain.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Arbitrum One mainnet chain configuration
|
|
5
|
+
*/
|
|
6
|
+
export const arbitrum = defineChain({
|
|
7
|
+
id: 42161,
|
|
8
|
+
name: 'Arbitrum One',
|
|
9
|
+
network: 'arbitrum',
|
|
10
|
+
coFheUrl: 'https://cofhe.mainnet.fhenix.zone',
|
|
11
|
+
verifierUrl: 'https://cofhe-vrf.mainnet.fhenix.zone',
|
|
12
|
+
thresholdNetworkUrl: 'https://cofhe-tn.mainnet.fhenix.zone',
|
|
13
|
+
environment: 'MAINNET',
|
|
14
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineChain } from '../defineChain.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ethereum mainnet chain configuration
|
|
5
|
+
*/
|
|
6
|
+
export const ethereum = defineChain({
|
|
7
|
+
id: 1,
|
|
8
|
+
name: 'Ethereum',
|
|
9
|
+
network: 'ethereum',
|
|
10
|
+
coFheUrl: 'https://cofhe.mainnet.fhenix.zone',
|
|
11
|
+
verifierUrl: 'https://cofhe-vrf.mainnet.fhenix.zone',
|
|
12
|
+
thresholdNetworkUrl: 'https://cofhe-tn.mainnet.fhenix.zone',
|
|
13
|
+
environment: 'MAINNET',
|
|
14
|
+
});
|
package/chains/index.ts
CHANGED
|
@@ -5,17 +5,21 @@ export type { CofheChain, Environment } from './types.js';
|
|
|
5
5
|
import { sepolia } from './chains/sepolia.js';
|
|
6
6
|
import { arbSepolia } from './chains/arbSepolia.js';
|
|
7
7
|
import { baseSepolia } from './chains/baseSepolia.js';
|
|
8
|
+
import { ethereum } from './chains/ethereum.js';
|
|
9
|
+
import { arbitrum } from './chains/arbitrum.js';
|
|
8
10
|
import { hardhat } from './chains/hardhat.js';
|
|
9
11
|
import { localcofhe } from './chains/localcofhe.js';
|
|
10
12
|
import { stagingCofhe } from './chains/stagingCofhe.js';
|
|
11
13
|
|
|
12
|
-
export { sepolia, arbSepolia, baseSepolia, hardhat, localcofhe, stagingCofhe };
|
|
14
|
+
export { sepolia, arbSepolia, baseSepolia, ethereum, arbitrum, hardhat, localcofhe, stagingCofhe };
|
|
13
15
|
|
|
14
16
|
// Export all chains as a collection
|
|
15
17
|
export const chains = {
|
|
16
18
|
sepolia,
|
|
17
19
|
arbSepolia,
|
|
18
20
|
baseSepolia,
|
|
21
|
+
ethereum,
|
|
22
|
+
arbitrum,
|
|
19
23
|
hardhat,
|
|
20
24
|
localcofhe,
|
|
21
25
|
stagingCofhe,
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
sepolia,
|
|
4
|
+
arbSepolia,
|
|
5
|
+
baseSepolia,
|
|
6
|
+
ethereum,
|
|
7
|
+
arbitrum,
|
|
8
|
+
hardhat,
|
|
9
|
+
chains,
|
|
10
|
+
getChainById,
|
|
11
|
+
getChainByName,
|
|
12
|
+
} from '../index.js';
|
|
3
13
|
|
|
4
14
|
describe('Chains', () => {
|
|
5
15
|
it('should export all chains', () => {
|
|
6
|
-
expect(Object.keys(chains)).toHaveLength(
|
|
16
|
+
expect(Object.keys(chains)).toHaveLength(8);
|
|
7
17
|
expect(chains).toHaveProperty('sepolia');
|
|
8
18
|
expect(chains).toHaveProperty('arbSepolia');
|
|
9
19
|
expect(chains).toHaveProperty('baseSepolia');
|
|
20
|
+
expect(chains).toHaveProperty('ethereum');
|
|
21
|
+
expect(chains).toHaveProperty('arbitrum');
|
|
10
22
|
expect(chains).toHaveProperty('hardhat');
|
|
11
23
|
expect(chains).toHaveProperty('localcofhe');
|
|
12
24
|
expect(chains).toHaveProperty('stagingCofhe');
|
|
@@ -17,6 +29,11 @@ describe('Chains', () => {
|
|
|
17
29
|
expect(sepolia.name).toBe('Sepolia');
|
|
18
30
|
expect(sepolia.environment).toBe('TESTNET');
|
|
19
31
|
|
|
32
|
+
expect(ethereum.id).toBe(1);
|
|
33
|
+
expect(ethereum.environment).toBe('MAINNET');
|
|
34
|
+
expect(arbitrum.id).toBe(42161);
|
|
35
|
+
expect(arbitrum.environment).toBe('MAINNET');
|
|
36
|
+
|
|
20
37
|
expect(hardhat.id).toBe(31337);
|
|
21
38
|
expect(hardhat.name).toBe('Hardhat');
|
|
22
39
|
expect(hardhat.environment).toBe('MOCK');
|
|
@@ -24,6 +41,8 @@ describe('Chains', () => {
|
|
|
24
41
|
|
|
25
42
|
it('should find chains by ID', () => {
|
|
26
43
|
expect(getChainById(11155111)).toBe(sepolia);
|
|
44
|
+
expect(getChainById(1)).toBe(ethereum);
|
|
45
|
+
expect(getChainById(42161)).toBe(arbitrum);
|
|
27
46
|
expect(getChainById(31337)).toBe(hardhat);
|
|
28
47
|
expect(getChainById(999999)).toBeUndefined();
|
|
29
48
|
});
|
|
@@ -31,12 +50,14 @@ describe('Chains', () => {
|
|
|
31
50
|
it('should find chains by name', () => {
|
|
32
51
|
expect(getChainByName('sepolia')).toBe(sepolia);
|
|
33
52
|
expect(getChainByName('Sepolia')).toBe(sepolia);
|
|
53
|
+
expect(getChainByName('ethereum')).toBe(ethereum);
|
|
54
|
+
expect(getChainByName('arbitrum')).toBe(arbitrum);
|
|
34
55
|
expect(getChainByName('hardhat')).toBe(hardhat);
|
|
35
56
|
expect(getChainByName('nonexistent')).toBeUndefined();
|
|
36
57
|
});
|
|
37
58
|
|
|
38
59
|
it('should validate chain properties', () => {
|
|
39
|
-
const allChains = [sepolia, arbSepolia, baseSepolia, hardhat];
|
|
60
|
+
const allChains = [sepolia, arbSepolia, baseSepolia, ethereum, arbitrum, hardhat];
|
|
40
61
|
|
|
41
62
|
allChains.forEach((chain) => {
|
|
42
63
|
expect(typeof chain.id).toBe('number');
|