@cofhe/sdk 0.1.0 → 0.2.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 +62 -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 +315 -0
- package/core/client.ts +292 -0
- package/core/clientTypes.ts +108 -0
- package/core/config.test.ts +235 -0
- package/core/config.ts +220 -0
- package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
- package/core/decrypt/cofheMocksSealOutput.ts +57 -0
- package/core/decrypt/decryptHandleBuilder.ts +287 -0
- package/core/decrypt/decryptUtils.ts +28 -0
- package/core/decrypt/tnSealOutputV1.ts +59 -0
- package/core/decrypt/tnSealOutputV2.ts +298 -0
- package/core/encrypt/MockZkVerifierAbi.ts +106 -0
- package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
- package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
- package/core/encrypt/encryptInputsBuilder.ts +560 -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 +89 -0
- package/core/keyStore.test.ts +226 -0
- package/core/keyStore.ts +154 -0
- package/core/permits.test.ts +494 -0
- package/core/permits.ts +200 -0
- package/core/types.ts +398 -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 +114 -0
- package/dist/chains.d.cts +121 -0
- package/dist/chains.d.ts +121 -0
- package/dist/chains.js +1 -0
- package/dist/chunk-UGBVZNRT.js +818 -0
- package/dist/chunk-WEAZ25JO.js +105 -0
- package/dist/chunk-WGCRJCBR.js +2523 -0
- package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
- package/dist/clientTypes-Es7fyi65.d.ts +914 -0
- package/dist/core.cjs +3414 -0
- package/dist/core.d.cts +111 -0
- package/dist/core.d.ts +111 -0
- package/dist/core.js +3 -0
- package/dist/node.cjs +3286 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +91 -0
- package/dist/permit-fUSe6KKq.d.cts +349 -0
- package/dist/permit-fUSe6KKq.d.ts +349 -0
- package/dist/permits.cjs +871 -0
- package/dist/permits.d.cts +1045 -0
- package/dist/permits.d.ts +1045 -0
- package/dist/permits.js +1 -0
- package/dist/types-KImPrEIe.d.cts +48 -0
- package/dist/types-KImPrEIe.d.ts +48 -0
- package/dist/web.cjs +3478 -0
- package/dist/web.d.cts +38 -0
- package/dist/web.d.ts +38 -0
- package/dist/web.js +240 -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 +147 -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 +27 -15
- package/permits/index.ts +68 -0
- package/permits/localstorage.test.ts +117 -0
- package/permits/permit.test.ts +477 -0
- package/permits/permit.ts +405 -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 +128 -0
- package/permits/store.ts +166 -0
- package/permits/test-utils.ts +20 -0
- package/permits/types.ts +191 -0
- package/permits/utils.ts +62 -0
- package/permits/validation.test.ts +288 -0
- package/permits/validation.ts +369 -0
- package/web/client.web.test.ts +147 -0
- package/web/config.web.test.ts +69 -0
- package/web/encryptInputs.web.test.ts +172 -0
- package/web/index.ts +161 -0
- package/web/storage.ts +34 -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,405 @@
|
|
|
1
|
+
import { keccak256, toHex, zeroAddress, parseAbi, type PublicClient, type WalletClient } from 'viem';
|
|
2
|
+
import {
|
|
3
|
+
type Permit,
|
|
4
|
+
type SelfPermit,
|
|
5
|
+
type SharingPermit,
|
|
6
|
+
type RecipientPermit,
|
|
7
|
+
type CreateSelfPermitOptions,
|
|
8
|
+
type CreateSharingPermitOptions,
|
|
9
|
+
type ImportSharedPermitOptions,
|
|
10
|
+
type SerializedPermit,
|
|
11
|
+
type EIP712Domain,
|
|
12
|
+
type Permission,
|
|
13
|
+
type EthEncryptedData,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
import {
|
|
16
|
+
validateSelfPermitOptions,
|
|
17
|
+
validateSharingPermitOptions,
|
|
18
|
+
validateImportPermitOptions,
|
|
19
|
+
validateSelfPermit,
|
|
20
|
+
validateSharingPermit,
|
|
21
|
+
validateImportPermit,
|
|
22
|
+
ValidationUtils,
|
|
23
|
+
} from './validation.js';
|
|
24
|
+
import { SignatureUtils } from './signature.js';
|
|
25
|
+
import { GenerateSealingKey, SealingKey } from './sealing.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Main Permit utilities - functional approach for React compatibility
|
|
29
|
+
*/
|
|
30
|
+
export const PermitUtils = {
|
|
31
|
+
/**
|
|
32
|
+
* Create a self permit for personal use
|
|
33
|
+
*/
|
|
34
|
+
createSelf: (options: CreateSelfPermitOptions): SelfPermit => {
|
|
35
|
+
const validation = validateSelfPermitOptions(options);
|
|
36
|
+
|
|
37
|
+
if (!validation.success) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'PermitUtils :: createSelf :: Parsing SelfPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Always generate a new sealing key - users cannot provide their own
|
|
44
|
+
const sealingPair = GenerateSealingKey();
|
|
45
|
+
|
|
46
|
+
const permit = {
|
|
47
|
+
...validation.data,
|
|
48
|
+
sealingPair,
|
|
49
|
+
_signedDomain: undefined,
|
|
50
|
+
} satisfies SelfPermit;
|
|
51
|
+
|
|
52
|
+
return permit;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a sharing permit to be shared with another user
|
|
57
|
+
*/
|
|
58
|
+
createSharing: (options: CreateSharingPermitOptions): SharingPermit => {
|
|
59
|
+
const validation = validateSharingPermitOptions(options);
|
|
60
|
+
|
|
61
|
+
if (!validation.success) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'PermitUtils :: createSharing :: Parsing SharingPermitOptions failed ' +
|
|
64
|
+
JSON.stringify(validation.error, null, 2)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Always generate a new sealing key - users cannot provide their own
|
|
69
|
+
const sealingPair = GenerateSealingKey();
|
|
70
|
+
|
|
71
|
+
const permit = {
|
|
72
|
+
...validation.data,
|
|
73
|
+
sealingPair,
|
|
74
|
+
_signedDomain: undefined,
|
|
75
|
+
} satisfies SharingPermit;
|
|
76
|
+
|
|
77
|
+
return permit;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Import a shared permit from various input formats
|
|
82
|
+
*/
|
|
83
|
+
importShared: (options: ImportSharedPermitOptions | string): RecipientPermit => {
|
|
84
|
+
let parsedOptions: ImportSharedPermitOptions;
|
|
85
|
+
|
|
86
|
+
// Handle different input types
|
|
87
|
+
if (typeof options === 'string') {
|
|
88
|
+
// Parse JSON string
|
|
89
|
+
try {
|
|
90
|
+
parsedOptions = JSON.parse(options);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error(`PermitUtils :: importShared :: Failed to parse JSON string: ${error}`);
|
|
93
|
+
}
|
|
94
|
+
} else if (typeof options === 'object' && options !== null) {
|
|
95
|
+
// Handle both ImportSharedPermitOptions and any object
|
|
96
|
+
parsedOptions = options;
|
|
97
|
+
} else {
|
|
98
|
+
throw new Error(
|
|
99
|
+
'PermitUtils :: importShared :: Invalid input type, expected ImportSharedPermitOptions, object, or string'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Validate type if provided
|
|
104
|
+
if (parsedOptions.type != null && parsedOptions.type !== 'sharing') {
|
|
105
|
+
throw new Error(`PermitUtils :: importShared :: Invalid permit type <${parsedOptions.type}>, must be "sharing"`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const validation = validateImportPermitOptions({ ...parsedOptions, type: 'recipient' });
|
|
109
|
+
|
|
110
|
+
if (!validation.success) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'PermitUtils :: importShared :: Parsing ImportPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Always generate a new sealing key - users cannot provide their own
|
|
117
|
+
const sealingPair = GenerateSealingKey();
|
|
118
|
+
|
|
119
|
+
const permit = {
|
|
120
|
+
...validation.data,
|
|
121
|
+
sealingPair,
|
|
122
|
+
_signedDomain: undefined,
|
|
123
|
+
} satisfies RecipientPermit;
|
|
124
|
+
|
|
125
|
+
return permit;
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sign a permit with the provided wallet client
|
|
130
|
+
*/
|
|
131
|
+
sign: async <T extends Permit>(permit: T, publicClient: PublicClient, walletClient: WalletClient): Promise<T> => {
|
|
132
|
+
if (walletClient == null || walletClient.account == null) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
'PermitUtils :: sign - walletClient undefined, you must pass in a `walletClient` for the connected user to create a permit signature'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const primaryType = SignatureUtils.getPrimaryType(permit.type);
|
|
139
|
+
const domain = await PermitUtils.fetchEIP712Domain(publicClient);
|
|
140
|
+
const { types, message } = SignatureUtils.getSignatureParams(PermitUtils.getPermission(permit, true), primaryType);
|
|
141
|
+
|
|
142
|
+
const signature = await walletClient.signTypedData({
|
|
143
|
+
domain,
|
|
144
|
+
types,
|
|
145
|
+
primaryType,
|
|
146
|
+
message,
|
|
147
|
+
account: walletClient.account,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
let updatedPermit: Permit;
|
|
151
|
+
if (permit.type === 'self' || permit.type === 'sharing') {
|
|
152
|
+
updatedPermit = {
|
|
153
|
+
...permit,
|
|
154
|
+
issuerSignature: signature,
|
|
155
|
+
_signedDomain: domain,
|
|
156
|
+
};
|
|
157
|
+
} else {
|
|
158
|
+
updatedPermit = {
|
|
159
|
+
...permit,
|
|
160
|
+
recipientSignature: signature,
|
|
161
|
+
_signedDomain: domain,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return updatedPermit as T;
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create and sign a self permit in one operation
|
|
170
|
+
*/
|
|
171
|
+
createSelfAndSign: async (
|
|
172
|
+
options: CreateSelfPermitOptions,
|
|
173
|
+
publicClient: PublicClient,
|
|
174
|
+
walletClient: WalletClient
|
|
175
|
+
): Promise<SelfPermit> => {
|
|
176
|
+
const permit = PermitUtils.createSelf(options);
|
|
177
|
+
return PermitUtils.sign(permit, publicClient, walletClient);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Create and sign a sharing permit in one operation
|
|
182
|
+
*/
|
|
183
|
+
createSharingAndSign: async (
|
|
184
|
+
options: CreateSharingPermitOptions,
|
|
185
|
+
publicClient: PublicClient,
|
|
186
|
+
walletClient: WalletClient
|
|
187
|
+
): Promise<SharingPermit> => {
|
|
188
|
+
const permit = PermitUtils.createSharing(options);
|
|
189
|
+
return PermitUtils.sign(permit, publicClient, walletClient);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Import and sign a shared permit in one operation from various input formats
|
|
194
|
+
*/
|
|
195
|
+
importSharedAndSign: async (
|
|
196
|
+
options: ImportSharedPermitOptions | string,
|
|
197
|
+
publicClient: PublicClient,
|
|
198
|
+
walletClient: WalletClient
|
|
199
|
+
): Promise<RecipientPermit> => {
|
|
200
|
+
const permit = PermitUtils.importShared(options);
|
|
201
|
+
return PermitUtils.sign(permit, publicClient, walletClient);
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Deserialize a permit from serialized data
|
|
206
|
+
*/
|
|
207
|
+
deserialize: (data: SerializedPermit): Permit => {
|
|
208
|
+
return {
|
|
209
|
+
...data,
|
|
210
|
+
sealingPair: SealingKey.deserialize(data.sealingPair.privateKey, data.sealingPair.publicKey),
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Serialize a permit for storage
|
|
216
|
+
*/
|
|
217
|
+
serialize: (permit: Permit): SerializedPermit => {
|
|
218
|
+
return {
|
|
219
|
+
name: permit.name,
|
|
220
|
+
type: permit.type,
|
|
221
|
+
issuer: permit.issuer,
|
|
222
|
+
expiration: permit.expiration,
|
|
223
|
+
recipient: permit.recipient,
|
|
224
|
+
validatorId: permit.validatorId,
|
|
225
|
+
validatorContract: permit.validatorContract,
|
|
226
|
+
issuerSignature: permit.issuerSignature,
|
|
227
|
+
recipientSignature: permit.recipientSignature,
|
|
228
|
+
_signedDomain: permit._signedDomain,
|
|
229
|
+
sealingPair: permit.sealingPair.serialize(),
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Validate a permit
|
|
235
|
+
*/
|
|
236
|
+
validate: (permit: Permit) => {
|
|
237
|
+
if (permit.type === 'self') {
|
|
238
|
+
return validateSelfPermit(permit);
|
|
239
|
+
} else if (permit.type === 'sharing') {
|
|
240
|
+
return validateSharingPermit(permit);
|
|
241
|
+
} else if (permit.type === 'recipient') {
|
|
242
|
+
return validateImportPermit(permit);
|
|
243
|
+
} else {
|
|
244
|
+
throw new Error('PermitUtils :: validate :: Invalid permit type');
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get the permission object from a permit (for use in contracts)
|
|
250
|
+
*/
|
|
251
|
+
getPermission: (permit: Permit, skipValidation = false): Permission => {
|
|
252
|
+
if (!skipValidation) {
|
|
253
|
+
const validationResult = PermitUtils.validate(permit);
|
|
254
|
+
|
|
255
|
+
if (!validationResult.success) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`PermitUtils :: getPermission :: permit validation failed - ${JSON.stringify(validationResult.error, null, 2)} ${JSON.stringify(permit, null, 2)}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
issuer: permit.issuer,
|
|
264
|
+
expiration: permit.expiration,
|
|
265
|
+
recipient: permit.recipient,
|
|
266
|
+
validatorId: permit.validatorId,
|
|
267
|
+
validatorContract: permit.validatorContract,
|
|
268
|
+
sealingKey: `0x${permit.sealingPair.publicKey}`,
|
|
269
|
+
issuerSignature: permit.issuerSignature,
|
|
270
|
+
recipientSignature: permit.recipientSignature,
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get a stable hash for the permit (used as key in storage)
|
|
276
|
+
*/
|
|
277
|
+
getHash: (permit: Permit): string => {
|
|
278
|
+
const data = JSON.stringify({
|
|
279
|
+
type: permit.type,
|
|
280
|
+
issuer: permit.issuer,
|
|
281
|
+
expiration: permit.expiration,
|
|
282
|
+
recipient: permit.recipient,
|
|
283
|
+
validatorId: permit.validatorId,
|
|
284
|
+
validatorContract: permit.validatorContract,
|
|
285
|
+
});
|
|
286
|
+
return keccak256(toHex(data));
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Export permit data for sharing (removes sensitive fields)
|
|
291
|
+
*/
|
|
292
|
+
export: (permit: Permit): string => {
|
|
293
|
+
const cleanedPermit: Record<string, unknown> = {
|
|
294
|
+
name: permit.name,
|
|
295
|
+
type: permit.type,
|
|
296
|
+
issuer: permit.issuer,
|
|
297
|
+
expiration: permit.expiration,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
if (permit.recipient !== zeroAddress) cleanedPermit.recipient = permit.recipient;
|
|
301
|
+
if (permit.validatorId !== 0) cleanedPermit.validatorId = permit.validatorId;
|
|
302
|
+
if (permit.validatorContract !== zeroAddress) cleanedPermit.validatorContract = permit.validatorContract;
|
|
303
|
+
if (permit.type === 'sharing' && permit.issuerSignature !== '0x')
|
|
304
|
+
cleanedPermit.issuerSignature = permit.issuerSignature;
|
|
305
|
+
|
|
306
|
+
return JSON.stringify(cleanedPermit, undefined, 2);
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Unseal encrypted data using the permit's sealing key
|
|
311
|
+
*/
|
|
312
|
+
unseal: (permit: Permit, ciphertext: EthEncryptedData): bigint => {
|
|
313
|
+
return permit.sealingPair.unseal(ciphertext);
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Check if permit is expired
|
|
318
|
+
*/
|
|
319
|
+
isExpired: (permit: Permit): boolean => {
|
|
320
|
+
return ValidationUtils.isExpired(permit);
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Check if permit is signed
|
|
325
|
+
*/
|
|
326
|
+
isSigned: (permit: Permit): boolean => {
|
|
327
|
+
return ValidationUtils.isSigned(permit);
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Check if permit is valid
|
|
332
|
+
*/
|
|
333
|
+
isValid: (permit: Permit) => {
|
|
334
|
+
return ValidationUtils.isValid(permit);
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Update permit name (returns new permit instance)
|
|
339
|
+
*/
|
|
340
|
+
updateName: (permit: Permit, name: string): Permit => {
|
|
341
|
+
return { ...permit, name };
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Fetch EIP712 domain from the blockchain
|
|
346
|
+
*/
|
|
347
|
+
fetchEIP712Domain: async (publicClient: PublicClient): Promise<EIP712Domain> => {
|
|
348
|
+
// Hardcoded constants from the original implementation
|
|
349
|
+
const TASK_MANAGER_ADDRESS = '0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9';
|
|
350
|
+
const ACL_IFACE = 'function acl() view returns (address)';
|
|
351
|
+
const EIP712_DOMAIN_IFACE =
|
|
352
|
+
'function eip712Domain() public view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)';
|
|
353
|
+
|
|
354
|
+
// Parse the ABI for the ACL function
|
|
355
|
+
const aclAbi = parseAbi([ACL_IFACE]);
|
|
356
|
+
|
|
357
|
+
// Get the ACL address
|
|
358
|
+
const aclAddress = (await publicClient.readContract({
|
|
359
|
+
address: TASK_MANAGER_ADDRESS as `0x${string}`,
|
|
360
|
+
abi: aclAbi,
|
|
361
|
+
functionName: 'acl',
|
|
362
|
+
})) as `0x${string}`;
|
|
363
|
+
|
|
364
|
+
// Parse the ABI for the EIP712 domain function
|
|
365
|
+
const domainAbi = parseAbi([EIP712_DOMAIN_IFACE]);
|
|
366
|
+
|
|
367
|
+
// Get the EIP712 domain
|
|
368
|
+
const domain = await publicClient.readContract({
|
|
369
|
+
address: aclAddress,
|
|
370
|
+
abi: domainAbi,
|
|
371
|
+
functionName: 'eip712Domain',
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// eslint-disable-next-line no-unused-vars
|
|
375
|
+
const [_fields, name, version, chainId, verifyingContract, _salt, _extensions] = domain;
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
name,
|
|
379
|
+
version,
|
|
380
|
+
chainId: Number(chainId),
|
|
381
|
+
verifyingContract,
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Check if permit's signed domain matches the provided domain
|
|
387
|
+
*/
|
|
388
|
+
matchesDomain: (permit: Permit, domain: EIP712Domain): boolean => {
|
|
389
|
+
return (
|
|
390
|
+
permit._signedDomain?.name === domain.name &&
|
|
391
|
+
permit._signedDomain?.version === domain.version &&
|
|
392
|
+
permit._signedDomain?.verifyingContract === domain.verifyingContract &&
|
|
393
|
+
permit._signedDomain?.chainId === domain.chainId
|
|
394
|
+
);
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Check if permit's signed domain is valid for the current chain
|
|
399
|
+
*/
|
|
400
|
+
checkSignedDomainValid: async (permit: Permit, publicClient: PublicClient): Promise<boolean> => {
|
|
401
|
+
if (permit._signedDomain == null) return false;
|
|
402
|
+
const domain = await PermitUtils.fetchEIP712Domain(publicClient);
|
|
403
|
+
return PermitUtils.matchesDomain(permit, domain);
|
|
404
|
+
},
|
|
405
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SealingKey, GenerateSealingKey } from './index.js';
|
|
3
|
+
|
|
4
|
+
describe('SealingKey', () => {
|
|
5
|
+
it('should create a SealingKey with valid keys', () => {
|
|
6
|
+
const privateKey = 'a'.repeat(64);
|
|
7
|
+
const publicKey = 'b'.repeat(64);
|
|
8
|
+
|
|
9
|
+
const sealingKey = new SealingKey(privateKey, publicKey);
|
|
10
|
+
|
|
11
|
+
expect(sealingKey.privateKey).toBe(privateKey);
|
|
12
|
+
expect(sealingKey.publicKey).toBe(publicKey);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should throw error for invalid private key length', () => {
|
|
16
|
+
const privateKey = 'a'.repeat(32); // Too short
|
|
17
|
+
const publicKey = 'b'.repeat(64);
|
|
18
|
+
|
|
19
|
+
expect(() => {
|
|
20
|
+
new SealingKey(privateKey, publicKey);
|
|
21
|
+
}).toThrow('Private key must be of length 64');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should throw error for invalid public key length', () => {
|
|
25
|
+
const privateKey = 'a'.repeat(64);
|
|
26
|
+
const publicKey = 'b'.repeat(32); // Too short
|
|
27
|
+
|
|
28
|
+
expect(() => {
|
|
29
|
+
new SealingKey(privateKey, publicKey);
|
|
30
|
+
}).toThrow('Public key must be of length 64');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should seal and unseal data correctly', () => {
|
|
34
|
+
const publicKey = 'b'.repeat(64);
|
|
35
|
+
const value = BigInt(12345);
|
|
36
|
+
|
|
37
|
+
// Seal the data
|
|
38
|
+
const encryptedData = SealingKey.seal(value, publicKey);
|
|
39
|
+
|
|
40
|
+
expect(encryptedData).toHaveProperty('data');
|
|
41
|
+
expect(encryptedData).toHaveProperty('public_key');
|
|
42
|
+
expect(encryptedData).toHaveProperty('nonce');
|
|
43
|
+
expect(encryptedData.data).toBeInstanceOf(Uint8Array);
|
|
44
|
+
expect(encryptedData.public_key).toBeInstanceOf(Uint8Array);
|
|
45
|
+
expect(encryptedData.nonce).toBeInstanceOf(Uint8Array);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should throw error for invalid public key in seal', () => {
|
|
49
|
+
const value = BigInt(12345);
|
|
50
|
+
const invalidPublicKey = 'invalid';
|
|
51
|
+
|
|
52
|
+
expect(() => {
|
|
53
|
+
SealingKey.seal(value, invalidPublicKey);
|
|
54
|
+
}).toThrow('bad public key size');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should throw error for invalid value in seal', () => {
|
|
58
|
+
const publicKey = 'b'.repeat(64);
|
|
59
|
+
const invalidValue = 'not a number';
|
|
60
|
+
|
|
61
|
+
expect(() => {
|
|
62
|
+
// @ts-expect-error - invalid value
|
|
63
|
+
SealingKey.seal(invalidValue, publicKey);
|
|
64
|
+
}).toThrow('Value not a number is not a number or bigint: string');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('GenerateSealingKey', () => {
|
|
69
|
+
it('should generate a valid SealingKey', async () => {
|
|
70
|
+
const sealingKey = GenerateSealingKey();
|
|
71
|
+
|
|
72
|
+
expect(sealingKey).toBeInstanceOf(SealingKey);
|
|
73
|
+
expect(sealingKey.privateKey).toHaveLength(64);
|
|
74
|
+
expect(sealingKey.publicKey).toHaveLength(64);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should generate different keys on each call', async () => {
|
|
78
|
+
const key1 = GenerateSealingKey();
|
|
79
|
+
const key2 = GenerateSealingKey();
|
|
80
|
+
|
|
81
|
+
expect(key1.privateKey).not.toBe(key2.privateKey);
|
|
82
|
+
expect(key1.publicKey).not.toBe(key2.publicKey);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as nacl from 'tweetnacl';
|
|
2
|
+
import { fromHexString, toBeArray, toBigInt, toHexString, isBigIntOrNumber, isString } from './utils.js';
|
|
3
|
+
|
|
4
|
+
const PRIVATE_KEY_LENGTH = 64;
|
|
5
|
+
const PUBLIC_KEY_LENGTH = 64;
|
|
6
|
+
|
|
7
|
+
export type EthEncryptedData = {
|
|
8
|
+
data: Uint8Array;
|
|
9
|
+
public_key: Uint8Array;
|
|
10
|
+
nonce: Uint8Array;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A class representing a SealingKey which provides cryptographic sealing (encryption)
|
|
15
|
+
* and unsealing (decryption) capabilities.
|
|
16
|
+
*/
|
|
17
|
+
export class SealingKey {
|
|
18
|
+
/**
|
|
19
|
+
* The private key used for decryption.
|
|
20
|
+
*/
|
|
21
|
+
privateKey: string;
|
|
22
|
+
/**
|
|
23
|
+
* The public key used for encryption.
|
|
24
|
+
*/
|
|
25
|
+
publicKey: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Constructs a SealingKey instance with the given private and public keys.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} privateKey - The private key used for decryption.
|
|
31
|
+
* @param {string} publicKey - The public key used for encryption.
|
|
32
|
+
* @throws Will throw an error if the provided keys lengths do not match
|
|
33
|
+
* the required lengths for private and public keys.
|
|
34
|
+
*/
|
|
35
|
+
constructor(privateKey: string, publicKey: string) {
|
|
36
|
+
if (privateKey.length !== PRIVATE_KEY_LENGTH) {
|
|
37
|
+
throw new Error(`Private key must be of length ${PRIVATE_KEY_LENGTH}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (publicKey.length !== PUBLIC_KEY_LENGTH) {
|
|
41
|
+
throw new Error(`Public key must be of length ${PUBLIC_KEY_LENGTH}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.privateKey = privateKey;
|
|
45
|
+
this.publicKey = publicKey;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
unseal = (parsedData: EthEncryptedData): bigint => {
|
|
49
|
+
// Ensure all parameters are Uint8Array
|
|
50
|
+
const nonce = parsedData.nonce instanceof Uint8Array ? parsedData.nonce : new Uint8Array(parsedData.nonce);
|
|
51
|
+
|
|
52
|
+
const ephemPublicKey =
|
|
53
|
+
parsedData.public_key instanceof Uint8Array ? parsedData.public_key : new Uint8Array(parsedData.public_key);
|
|
54
|
+
|
|
55
|
+
const dataToDecrypt = parsedData.data instanceof Uint8Array ? parsedData.data : new Uint8Array(parsedData.data);
|
|
56
|
+
|
|
57
|
+
// Make sure the private key is also a Uint8Array
|
|
58
|
+
const privateKeyBytes = fromHexString(this.privateKey);
|
|
59
|
+
|
|
60
|
+
// Debug information
|
|
61
|
+
// console.log("nonce length:", nonce.length);
|
|
62
|
+
// console.log("ephemPublicKey length:", ephemPublicKey.length);
|
|
63
|
+
// console.log("privateKeyBytes length:", privateKeyBytes.length);
|
|
64
|
+
// console.log("dataToDecrypt length:", dataToDecrypt.length);
|
|
65
|
+
|
|
66
|
+
// call the nacl box function to decrypt the data
|
|
67
|
+
const decryptedMessage = nacl.box.open(dataToDecrypt, nonce, ephemPublicKey, privateKeyBytes);
|
|
68
|
+
|
|
69
|
+
if (!decryptedMessage) {
|
|
70
|
+
throw new Error('Failed to decrypt message');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return toBigInt(decryptedMessage);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Serializes the SealingKey to a JSON object.
|
|
78
|
+
*/
|
|
79
|
+
serialize = () => {
|
|
80
|
+
return {
|
|
81
|
+
privateKey: this.privateKey,
|
|
82
|
+
publicKey: this.publicKey,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Deserializes the SealingKey from a JSON object.
|
|
88
|
+
*/
|
|
89
|
+
static deserialize = (privateKey: string, publicKey: string): SealingKey => {
|
|
90
|
+
return new SealingKey(privateKey, publicKey);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Seals (encrypts) the provided message for a receiver with the specified public key.
|
|
95
|
+
*
|
|
96
|
+
* @param {bigint | number} value - The message to be encrypted.
|
|
97
|
+
* @param {string} publicKey - The public key of the intended recipient.
|
|
98
|
+
* @returns string - The encrypted message in hexadecimal format.
|
|
99
|
+
* @static
|
|
100
|
+
* @throws Will throw if the provided publicKey or value do not meet defined preconditions.
|
|
101
|
+
*/
|
|
102
|
+
static seal = (value: bigint | number, publicKey: string): EthEncryptedData => {
|
|
103
|
+
isString(publicKey);
|
|
104
|
+
isBigIntOrNumber(value);
|
|
105
|
+
|
|
106
|
+
// generate ephemeral keypair
|
|
107
|
+
const ephemeralKeyPair = nacl.box.keyPair();
|
|
108
|
+
|
|
109
|
+
const nonce = nacl.randomBytes(nacl.box.nonceLength);
|
|
110
|
+
|
|
111
|
+
const encryptedMessage = nacl.box(toBeArray(value), nonce, fromHexString(publicKey), ephemeralKeyPair.secretKey);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
data: encryptedMessage,
|
|
115
|
+
public_key: ephemeralKeyPair.publicKey,
|
|
116
|
+
nonce: nonce,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Asynchronously generates a new SealingKey.
|
|
123
|
+
* This function uses the 'nacl' library to create a new public/private key pair for sealing purposes.
|
|
124
|
+
* A sealing key is used to encrypt data such that it can only be unsealed (decrypted) by the owner of the corresponding private key.
|
|
125
|
+
* @returns {SealingKey} - A new SealingKey object containing the hexadecimal strings of the public and private keys.
|
|
126
|
+
*/
|
|
127
|
+
export const GenerateSealingKey = (): SealingKey => {
|
|
128
|
+
const sodiumKeypair = nacl.box.keyPair();
|
|
129
|
+
|
|
130
|
+
return new SealingKey(toHexString(sodiumKeypair.secretKey), toHexString(sodiumKeypair.publicKey));
|
|
131
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type EIP712Message, type EIP712Types, type Permission, type PermitSignaturePrimaryType } from './types.js';
|
|
2
|
+
|
|
3
|
+
const PermitSignatureAllFields = [
|
|
4
|
+
{ name: 'issuer', type: 'address' },
|
|
5
|
+
{ name: 'expiration', type: 'uint64' },
|
|
6
|
+
{ name: 'recipient', type: 'address' },
|
|
7
|
+
{ name: 'validatorId', type: 'uint256' },
|
|
8
|
+
{ name: 'validatorContract', type: 'address' },
|
|
9
|
+
{ name: 'sealingKey', type: 'bytes32' },
|
|
10
|
+
{ name: 'issuerSignature', type: 'bytes' },
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
type PermitSignatureFieldOption = (typeof PermitSignatureAllFields)[number]['name'];
|
|
14
|
+
|
|
15
|
+
export const SignatureTypes = {
|
|
16
|
+
PermissionedV2IssuerSelf: [
|
|
17
|
+
'issuer',
|
|
18
|
+
'expiration',
|
|
19
|
+
'recipient',
|
|
20
|
+
'validatorId',
|
|
21
|
+
'validatorContract',
|
|
22
|
+
'sealingKey',
|
|
23
|
+
] satisfies PermitSignatureFieldOption[],
|
|
24
|
+
PermissionedV2IssuerShared: [
|
|
25
|
+
'issuer',
|
|
26
|
+
'expiration',
|
|
27
|
+
'recipient',
|
|
28
|
+
'validatorId',
|
|
29
|
+
'validatorContract',
|
|
30
|
+
] satisfies PermitSignatureFieldOption[],
|
|
31
|
+
PermissionedV2Recipient: ['sealingKey', 'issuerSignature'] satisfies PermitSignatureFieldOption[],
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get signature types and message for EIP712 signing
|
|
36
|
+
*/
|
|
37
|
+
export const getSignatureTypesAndMessage = <T extends PermitSignatureFieldOption>(
|
|
38
|
+
primaryType: PermitSignaturePrimaryType,
|
|
39
|
+
fields: T[] | readonly T[],
|
|
40
|
+
values: Pick<Permission, T> & Partial<Permission>
|
|
41
|
+
): { types: EIP712Types; primaryType: string; message: EIP712Message } => {
|
|
42
|
+
const types = {
|
|
43
|
+
[primaryType]: PermitSignatureAllFields.filter((fieldType) => fields.includes(fieldType.name as T)),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const message: Record<T, string | string[] | number | number[]> = {} as Record<
|
|
47
|
+
T,
|
|
48
|
+
string | string[] | number | number[]
|
|
49
|
+
>;
|
|
50
|
+
fields.forEach((field) => {
|
|
51
|
+
if (field in values) {
|
|
52
|
+
message[field] = values[field];
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return { types, primaryType, message: message as EIP712Message };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Signature utilities for permit operations
|
|
61
|
+
*/
|
|
62
|
+
export const SignatureUtils = {
|
|
63
|
+
/**
|
|
64
|
+
* Get signature parameters for a permit
|
|
65
|
+
*/
|
|
66
|
+
getSignatureParams: (permit: Permission, primaryType: PermitSignaturePrimaryType) => {
|
|
67
|
+
return getSignatureTypesAndMessage(primaryType, SignatureTypes[primaryType], permit);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Determine the required signature type based on permit type
|
|
72
|
+
*/
|
|
73
|
+
getPrimaryType: (permitType: 'self' | 'sharing' | 'recipient'): PermitSignaturePrimaryType => {
|
|
74
|
+
if (permitType === 'self') return 'PermissionedV2IssuerSelf';
|
|
75
|
+
if (permitType === 'sharing') return 'PermissionedV2IssuerShared';
|
|
76
|
+
if (permitType === 'recipient') return 'PermissionedV2Recipient';
|
|
77
|
+
throw new Error(`Unknown permit type: ${permitType}`);
|
|
78
|
+
},
|
|
79
|
+
};
|