@cofhe/sdk 0.2.0 → 0.2.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 +8 -0
- package/chains/defineChain.ts +2 -2
- package/chains/types.ts +3 -3
- package/core/client.test.ts +111 -0
- package/core/client.ts +22 -2
- package/core/clientTypes.ts +7 -1
- package/core/config.test.ts +8 -0
- package/core/config.ts +10 -4
- package/core/consts.ts +18 -0
- package/core/decrypt/cofheMocksSealOutput.ts +2 -4
- package/core/encrypt/cofheMocksZkVerifySign.ts +4 -11
- package/core/index.ts +9 -1
- package/core/permits.test.ts +5 -6
- package/core/permits.ts +5 -4
- package/dist/chains.cjs +4 -7
- package/dist/chains.d.cts +12 -12
- package/dist/chains.d.ts +12 -12
- package/dist/chains.js +1 -1
- package/dist/{chunk-WGCRJCBR.js → chunk-I5WFEYXX.js} +33 -19
- package/dist/{chunk-UGBVZNRT.js → chunk-R3B5TMVX.js} +308 -189
- package/dist/{chunk-WEAZ25JO.js → chunk-TBLR7NNE.js} +4 -7
- package/dist/{clientTypes-Es7fyi65.d.ts → clientTypes-RqkgkV2i.d.ts} +34 -93
- package/dist/{clientTypes-5_1nwtUe.d.cts → clientTypes-e4filDzK.d.cts} +34 -93
- package/dist/core.cjs +343 -208
- package/dist/core.d.cts +17 -6
- package/dist/core.d.ts +17 -6
- package/dist/core.js +3 -3
- package/dist/node.cjs +337 -208
- package/dist/node.d.cts +3 -3
- package/dist/node.d.ts +3 -3
- package/dist/node.js +3 -3
- package/dist/{permit-fUSe6KKq.d.cts → permit-MZ502UBl.d.cts} +30 -33
- package/dist/{permit-fUSe6KKq.d.ts → permit-MZ502UBl.d.ts} +30 -33
- package/dist/permits.cjs +305 -187
- package/dist/permits.d.cts +111 -812
- package/dist/permits.d.ts +111 -812
- package/dist/permits.js +1 -1
- package/dist/types-YiAC4gig.d.cts +33 -0
- package/dist/types-YiAC4gig.d.ts +33 -0
- package/dist/web.cjs +337 -208
- package/dist/web.d.cts +3 -3
- package/dist/web.d.ts +3 -3
- package/dist/web.js +3 -3
- package/package.json +3 -3
- package/permits/localstorage.test.ts +9 -13
- package/permits/onchain-utils.ts +221 -0
- package/permits/permit.test.ts +51 -5
- package/permits/permit.ts +28 -74
- package/permits/store.test.ts +10 -50
- package/permits/store.ts +4 -14
- package/permits/test-utils.ts +10 -2
- package/permits/types.ts +22 -9
- package/permits/utils.ts +0 -4
- package/permits/validation.test.ts +29 -32
- package/permits/validation.ts +112 -194
- package/dist/types-KImPrEIe.d.cts +0 -48
- package/dist/types-KImPrEIe.d.ts +0 -48
package/permits/permit.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type EIP712Domain,
|
|
12
12
|
type Permission,
|
|
13
13
|
type EthEncryptedData,
|
|
14
|
+
type PermitHashFields,
|
|
14
15
|
} from './types.js';
|
|
15
16
|
import {
|
|
16
17
|
validateSelfPermitOptions,
|
|
@@ -21,8 +22,10 @@ import {
|
|
|
21
22
|
validateImportPermit,
|
|
22
23
|
ValidationUtils,
|
|
23
24
|
} from './validation.js';
|
|
25
|
+
import * as z from 'zod';
|
|
24
26
|
import { SignatureUtils } from './signature.js';
|
|
25
27
|
import { GenerateSealingKey, SealingKey } from './sealing.js';
|
|
28
|
+
import { checkPermitValidityOnChain, getAclEIP712Domain } from './onchain-utils.js';
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Main Permit utilities - functional approach for React compatibility
|
|
@@ -34,17 +37,12 @@ export const PermitUtils = {
|
|
|
34
37
|
createSelf: (options: CreateSelfPermitOptions): SelfPermit => {
|
|
35
38
|
const validation = validateSelfPermitOptions(options);
|
|
36
39
|
|
|
37
|
-
if (!validation.success) {
|
|
38
|
-
throw new Error(
|
|
39
|
-
'PermitUtils :: createSelf :: Parsing SelfPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
40
|
// Always generate a new sealing key - users cannot provide their own
|
|
44
41
|
const sealingPair = GenerateSealingKey();
|
|
45
42
|
|
|
46
43
|
const permit = {
|
|
47
|
-
|
|
44
|
+
hash: PermitUtils.getHash(validation),
|
|
45
|
+
...validation,
|
|
48
46
|
sealingPair,
|
|
49
47
|
_signedDomain: undefined,
|
|
50
48
|
} satisfies SelfPermit;
|
|
@@ -58,18 +56,12 @@ export const PermitUtils = {
|
|
|
58
56
|
createSharing: (options: CreateSharingPermitOptions): SharingPermit => {
|
|
59
57
|
const validation = validateSharingPermitOptions(options);
|
|
60
58
|
|
|
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
59
|
// Always generate a new sealing key - users cannot provide their own
|
|
69
60
|
const sealingPair = GenerateSealingKey();
|
|
70
61
|
|
|
71
62
|
const permit = {
|
|
72
|
-
|
|
63
|
+
hash: PermitUtils.getHash(validation),
|
|
64
|
+
...validation,
|
|
73
65
|
sealingPair,
|
|
74
66
|
_signedDomain: undefined,
|
|
75
67
|
} satisfies SharingPermit;
|
|
@@ -89,35 +81,28 @@ export const PermitUtils = {
|
|
|
89
81
|
try {
|
|
90
82
|
parsedOptions = JSON.parse(options);
|
|
91
83
|
} catch (error) {
|
|
92
|
-
throw new Error(`
|
|
84
|
+
throw new Error(`Failed to parse JSON string: ${error}`);
|
|
93
85
|
}
|
|
94
86
|
} else if (typeof options === 'object' && options !== null) {
|
|
95
87
|
// Handle both ImportSharedPermitOptions and any object
|
|
96
88
|
parsedOptions = options;
|
|
97
89
|
} else {
|
|
98
|
-
throw new Error(
|
|
99
|
-
'PermitUtils :: importShared :: Invalid input type, expected ImportSharedPermitOptions, object, or string'
|
|
100
|
-
);
|
|
90
|
+
throw new Error('Invalid input type, expected ImportSharedPermitOptions, object, or string');
|
|
101
91
|
}
|
|
102
92
|
|
|
103
93
|
// Validate type if provided
|
|
104
94
|
if (parsedOptions.type != null && parsedOptions.type !== 'sharing') {
|
|
105
|
-
throw new Error(`
|
|
95
|
+
throw new Error(`Invalid permit type <${parsedOptions.type}>, must be "sharing"`);
|
|
106
96
|
}
|
|
107
97
|
|
|
108
98
|
const validation = validateImportPermitOptions({ ...parsedOptions, type: 'recipient' });
|
|
109
99
|
|
|
110
|
-
if (!validation.success) {
|
|
111
|
-
throw new Error(
|
|
112
|
-
'PermitUtils :: importShared :: Parsing ImportPermitOptions failed ' + JSON.stringify(validation.error, null, 2)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
100
|
// Always generate a new sealing key - users cannot provide their own
|
|
117
101
|
const sealingPair = GenerateSealingKey();
|
|
118
102
|
|
|
119
103
|
const permit = {
|
|
120
|
-
|
|
104
|
+
hash: PermitUtils.getHash(validation),
|
|
105
|
+
...validation,
|
|
121
106
|
sealingPair,
|
|
122
107
|
_signedDomain: undefined,
|
|
123
108
|
} satisfies RecipientPermit;
|
|
@@ -131,12 +116,12 @@ export const PermitUtils = {
|
|
|
131
116
|
sign: async <T extends Permit>(permit: T, publicClient: PublicClient, walletClient: WalletClient): Promise<T> => {
|
|
132
117
|
if (walletClient == null || walletClient.account == null) {
|
|
133
118
|
throw new Error(
|
|
134
|
-
'
|
|
119
|
+
'Missing walletClient, you must pass in a `walletClient` for the connected user to create a permit signature'
|
|
135
120
|
);
|
|
136
121
|
}
|
|
137
122
|
|
|
138
123
|
const primaryType = SignatureUtils.getPrimaryType(permit.type);
|
|
139
|
-
const domain = await
|
|
124
|
+
const domain = await getAclEIP712Domain(publicClient);
|
|
140
125
|
const { types, message } = SignatureUtils.getSignatureParams(PermitUtils.getPermission(permit, true), primaryType);
|
|
141
126
|
|
|
142
127
|
const signature = await walletClient.signTypedData({
|
|
@@ -216,6 +201,7 @@ export const PermitUtils = {
|
|
|
216
201
|
*/
|
|
217
202
|
serialize: (permit: Permit): SerializedPermit => {
|
|
218
203
|
return {
|
|
204
|
+
hash: permit.hash,
|
|
219
205
|
name: permit.name,
|
|
220
206
|
type: permit.type,
|
|
221
207
|
issuer: permit.issuer,
|
|
@@ -241,7 +227,7 @@ export const PermitUtils = {
|
|
|
241
227
|
} else if (permit.type === 'recipient') {
|
|
242
228
|
return validateImportPermit(permit);
|
|
243
229
|
} else {
|
|
244
|
-
throw new Error('
|
|
230
|
+
throw new Error('Invalid permit type');
|
|
245
231
|
}
|
|
246
232
|
},
|
|
247
233
|
|
|
@@ -250,13 +236,7 @@ export const PermitUtils = {
|
|
|
250
236
|
*/
|
|
251
237
|
getPermission: (permit: Permit, skipValidation = false): Permission => {
|
|
252
238
|
if (!skipValidation) {
|
|
253
|
-
|
|
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
|
-
}
|
|
239
|
+
PermitUtils.validate(permit);
|
|
260
240
|
}
|
|
261
241
|
|
|
262
242
|
return {
|
|
@@ -274,7 +254,7 @@ export const PermitUtils = {
|
|
|
274
254
|
/**
|
|
275
255
|
* Get a stable hash for the permit (used as key in storage)
|
|
276
256
|
*/
|
|
277
|
-
getHash: (permit:
|
|
257
|
+
getHash: (permit: PermitHashFields): string => {
|
|
278
258
|
const data = JSON.stringify({
|
|
279
259
|
type: permit.type,
|
|
280
260
|
issuer: permit.issuer,
|
|
@@ -345,41 +325,7 @@ export const PermitUtils = {
|
|
|
345
325
|
* Fetch EIP712 domain from the blockchain
|
|
346
326
|
*/
|
|
347
327
|
fetchEIP712Domain: async (publicClient: PublicClient): Promise<EIP712Domain> => {
|
|
348
|
-
|
|
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
|
-
};
|
|
328
|
+
return getAclEIP712Domain(publicClient);
|
|
383
329
|
},
|
|
384
330
|
|
|
385
331
|
/**
|
|
@@ -399,7 +345,15 @@ export const PermitUtils = {
|
|
|
399
345
|
*/
|
|
400
346
|
checkSignedDomainValid: async (permit: Permit, publicClient: PublicClient): Promise<boolean> => {
|
|
401
347
|
if (permit._signedDomain == null) return false;
|
|
402
|
-
const domain = await
|
|
348
|
+
const domain = await getAclEIP712Domain(publicClient);
|
|
403
349
|
return PermitUtils.matchesDomain(permit, domain);
|
|
404
350
|
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Check if permit passes the on-chain validation
|
|
354
|
+
*/
|
|
355
|
+
checkValidityOnChain: async (permit: Permit, publicClient: PublicClient): Promise<boolean> => {
|
|
356
|
+
const permission = PermitUtils.getPermission(permit);
|
|
357
|
+
return checkPermitValidityOnChain(permission, publicClient);
|
|
358
|
+
},
|
|
405
359
|
};
|
package/permits/store.test.ts
CHANGED
|
@@ -32,10 +32,9 @@ describe('Storage Tests', () => {
|
|
|
32
32
|
describe('Permit Storage', () => {
|
|
33
33
|
it('should store and retrieve permits', async () => {
|
|
34
34
|
const permit = await createMockPermit();
|
|
35
|
-
const hash = PermitUtils.getHash(permit);
|
|
36
35
|
|
|
37
36
|
setPermit(chainId, account, permit);
|
|
38
|
-
const retrieved = getPermit(chainId, account, hash);
|
|
37
|
+
const retrieved = getPermit(chainId, account, permit.hash);
|
|
39
38
|
|
|
40
39
|
expect(retrieved).toBeDefined();
|
|
41
40
|
expect(PermitUtils.serialize(retrieved!)).toEqual(PermitUtils.serialize(permit));
|
|
@@ -43,13 +42,9 @@ describe('Storage Tests', () => {
|
|
|
43
42
|
|
|
44
43
|
it('should handle multiple permits per account', async () => {
|
|
45
44
|
const permit1 = await createMockPermit();
|
|
46
|
-
const permit2 = {
|
|
47
|
-
...(await createMockPermit()),
|
|
45
|
+
const permit2 = await createMockPermit({
|
|
48
46
|
issuer: '0x0987654321098765432109876543210987654321' as `0x${string}`,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const hash1 = PermitUtils.getHash(permit1);
|
|
52
|
-
const hash2 = PermitUtils.getHash(permit2);
|
|
47
|
+
});
|
|
53
48
|
|
|
54
49
|
setPermit(chainId, account, permit1);
|
|
55
50
|
setPermit(chainId, account, permit2);
|
|
@@ -57,19 +52,18 @@ describe('Storage Tests', () => {
|
|
|
57
52
|
const permits = getPermits(chainId, account);
|
|
58
53
|
expect(Object.keys(permits)).toHaveLength(2);
|
|
59
54
|
|
|
60
|
-
expect(PermitUtils.serialize(permits[
|
|
61
|
-
expect(PermitUtils.serialize(permits[
|
|
55
|
+
expect(PermitUtils.serialize(permits[permit1.hash])).toEqual(PermitUtils.serialize(permit1));
|
|
56
|
+
expect(PermitUtils.serialize(permits[permit2.hash])).toEqual(PermitUtils.serialize(permit2));
|
|
62
57
|
});
|
|
63
58
|
|
|
64
59
|
it('should handle active permit hash', async () => {
|
|
65
60
|
const permit = await createMockPermit();
|
|
66
|
-
const hash = PermitUtils.getHash(permit);
|
|
67
61
|
|
|
68
62
|
setPermit(chainId, account, permit);
|
|
69
|
-
setActivePermitHash(chainId, account, hash);
|
|
63
|
+
setActivePermitHash(chainId, account, permit.hash);
|
|
70
64
|
|
|
71
65
|
const activeHash = getActivePermitHash(chainId, account);
|
|
72
|
-
expect(activeHash).toBe(hash);
|
|
66
|
+
expect(activeHash).toBe(permit.hash);
|
|
73
67
|
|
|
74
68
|
const activePermit = getActivePermit(chainId, account);
|
|
75
69
|
expect(activePermit).toBeDefined();
|
|
@@ -78,51 +72,17 @@ describe('Storage Tests', () => {
|
|
|
78
72
|
|
|
79
73
|
it('should remove permits', async () => {
|
|
80
74
|
const permit = await createMockPermit();
|
|
81
|
-
const hash = PermitUtils.getHash(permit);
|
|
82
75
|
|
|
83
76
|
setPermit(chainId, account, permit);
|
|
84
|
-
setActivePermitHash(chainId, account, hash);
|
|
77
|
+
setActivePermitHash(chainId, account, permit.hash);
|
|
85
78
|
|
|
86
|
-
removePermit(chainId, account, hash
|
|
79
|
+
removePermit(chainId, account, permit.hash);
|
|
87
80
|
|
|
88
|
-
const retrieved = getPermit(chainId, account, hash);
|
|
81
|
+
const retrieved = getPermit(chainId, account, permit.hash);
|
|
89
82
|
expect(retrieved).toBeUndefined();
|
|
90
83
|
|
|
91
84
|
const activeHash = getActivePermitHash(chainId, account);
|
|
92
85
|
expect(activeHash).toBeUndefined();
|
|
93
86
|
});
|
|
94
|
-
|
|
95
|
-
it('should prevent removing last permit without force', async () => {
|
|
96
|
-
const permit = await createMockPermit();
|
|
97
|
-
const hash = PermitUtils.getHash(permit);
|
|
98
|
-
|
|
99
|
-
setPermit(chainId, account, permit);
|
|
100
|
-
setActivePermitHash(chainId, account, hash);
|
|
101
|
-
|
|
102
|
-
expect(() => {
|
|
103
|
-
removePermit(chainId, account, hash, false);
|
|
104
|
-
}).toThrow('Cannot remove the last permit without force flag');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should switch active permit when removing current active', async () => {
|
|
108
|
-
const permit1 = await createMockPermit();
|
|
109
|
-
const permit2 = {
|
|
110
|
-
...(await createMockPermit()),
|
|
111
|
-
name: 'Second Permit',
|
|
112
|
-
issuer: '0x0987654321098765432109876543210987654321' as `0x${string}`, // Different issuer
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const hash1 = PermitUtils.getHash(permit1);
|
|
116
|
-
const hash2 = PermitUtils.getHash(permit2);
|
|
117
|
-
|
|
118
|
-
setPermit(chainId, account, permit1);
|
|
119
|
-
setPermit(chainId, account, permit2);
|
|
120
|
-
setActivePermitHash(chainId, account, hash1);
|
|
121
|
-
|
|
122
|
-
removePermit(chainId, account, hash1, false);
|
|
123
|
-
|
|
124
|
-
const activeHash = getActivePermitHash(chainId, account);
|
|
125
|
-
expect(activeHash).toBe(hash2);
|
|
126
|
-
});
|
|
127
87
|
});
|
|
128
88
|
});
|
package/permits/store.ts
CHANGED
|
@@ -82,12 +82,12 @@ export const setPermit = (chainId: number, account: string, permit: Permit) => {
|
|
|
82
82
|
produce<PermitsStore>((state) => {
|
|
83
83
|
if (state.permits[chainId] == null) state.permits[chainId] = {};
|
|
84
84
|
if (state.permits[chainId][account] == null) state.permits[chainId][account] = {};
|
|
85
|
-
state.permits[chainId][account][
|
|
85
|
+
state.permits[chainId][account][permit.hash] = PermitUtils.serialize(permit);
|
|
86
86
|
})
|
|
87
87
|
);
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
export const removePermit = (chainId: number, account: string, hash: string
|
|
90
|
+
export const removePermit = (chainId: number, account: string, hash: string) => {
|
|
91
91
|
clearStaleStore();
|
|
92
92
|
_permitStore.setState(
|
|
93
93
|
produce<PermitsStore>((state) => {
|
|
@@ -100,18 +100,8 @@ export const removePermit = (chainId: number, account: string, hash: string, for
|
|
|
100
100
|
if (accountPermits[hash] == null) return;
|
|
101
101
|
|
|
102
102
|
if (state.activePermitHash[chainId][account] === hash) {
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (otherPermitHash) {
|
|
107
|
-
state.activePermitHash[chainId][account] = otherPermitHash;
|
|
108
|
-
} else {
|
|
109
|
-
if (!force) {
|
|
110
|
-
throw new Error('Cannot remove the last permit without force flag');
|
|
111
|
-
}
|
|
112
|
-
// Clear the active hash when removing the last permit with force
|
|
113
|
-
state.activePermitHash[chainId][account] = undefined;
|
|
114
|
-
}
|
|
103
|
+
// if the active permit is the one to be removed - unset it
|
|
104
|
+
state.activePermitHash[chainId][account] = undefined;
|
|
115
105
|
}
|
|
116
106
|
// Remove the permit
|
|
117
107
|
accountPermits[hash] = undefined;
|
package/permits/test-utils.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Permit, type SerializedPermit, GenerateSealingKey, PermitUtils } from './index.js';
|
|
2
2
|
|
|
3
3
|
// Mock permit for testing - using Bob's address as issuer
|
|
4
|
-
export const createMockPermit = async (): Promise<Permit> => {
|
|
4
|
+
export const createMockPermit = async (overrides: Partial<Permit> = {}): Promise<Permit> => {
|
|
5
5
|
const sealingPair = GenerateSealingKey();
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
const fields = {
|
|
7
8
|
name: 'Test Permit',
|
|
8
9
|
type: 'self',
|
|
9
10
|
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
@@ -15,6 +16,13 @@ export const createMockPermit = async (): Promise<Permit> => {
|
|
|
15
16
|
issuerSignature: '0x',
|
|
16
17
|
recipientSignature: '0x',
|
|
17
18
|
_signedDomain: undefined,
|
|
19
|
+
...overrides,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
const serializedPermit: SerializedPermit = {
|
|
23
|
+
hash: PermitUtils.getHash(fields),
|
|
24
|
+
...fields,
|
|
18
25
|
};
|
|
26
|
+
|
|
19
27
|
return PermitUtils.deserialize(serializedPermit);
|
|
20
28
|
};
|
package/permits/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SealingKey as SealingKeyClass, type EthEncryptedData } from './sealing.js';
|
|
2
|
+
import { type Hex } from 'viem';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* EIP712 related types
|
|
@@ -9,7 +10,7 @@ export type EIP712Message = Record<string, string>;
|
|
|
9
10
|
export type EIP712Domain = {
|
|
10
11
|
chainId: number;
|
|
11
12
|
name: string;
|
|
12
|
-
verifyingContract:
|
|
13
|
+
verifyingContract: Hex;
|
|
13
14
|
version: string;
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -29,6 +30,10 @@ export type { EthEncryptedData };
|
|
|
29
30
|
* Core Permit interface - immutable design for React compatibility
|
|
30
31
|
*/
|
|
31
32
|
export interface Permit {
|
|
33
|
+
/**
|
|
34
|
+
* Stable hash of relevant permit data, used as key in storage
|
|
35
|
+
*/
|
|
36
|
+
hash: string;
|
|
32
37
|
/**
|
|
33
38
|
* Name for this permit, for organization and UI usage, not included in signature.
|
|
34
39
|
*/
|
|
@@ -43,7 +48,7 @@ export interface Permit {
|
|
|
43
48
|
/**
|
|
44
49
|
* (base) User that initially created the permission, target of data fetching
|
|
45
50
|
*/
|
|
46
|
-
issuer:
|
|
51
|
+
issuer: Hex;
|
|
47
52
|
/**
|
|
48
53
|
* (base) Expiration timestamp
|
|
49
54
|
*/
|
|
@@ -52,7 +57,7 @@ export interface Permit {
|
|
|
52
57
|
* (sharing) The user that this permission will be shared with
|
|
53
58
|
* ** optional, use `address(0)` to disable **
|
|
54
59
|
*/
|
|
55
|
-
recipient:
|
|
60
|
+
recipient: Hex;
|
|
56
61
|
/**
|
|
57
62
|
* (issuer defined validation) An id used to query a contract to check this permissions validity
|
|
58
63
|
* ** optional, use `0` to disable **
|
|
@@ -62,7 +67,7 @@ export interface Permit {
|
|
|
62
67
|
* (issuer defined validation) The contract to query to determine permission validity
|
|
63
68
|
* ** optional, user `address(0)` to disable **
|
|
64
69
|
*/
|
|
65
|
-
validatorContract:
|
|
70
|
+
validatorContract: Hex;
|
|
66
71
|
/**
|
|
67
72
|
* (base) The publicKey of a sealingPair used to re-encrypt `issuer`s confidential data
|
|
68
73
|
* (non-sharing) Populated by `issuer`
|
|
@@ -75,13 +80,13 @@ export interface Permit {
|
|
|
75
80
|
* (non-sharing) < issuer, expiration, recipient, validatorId, validatorContract, sealingKey >
|
|
76
81
|
* (sharing) < issuer, expiration, recipient, validatorId, validatorContract >
|
|
77
82
|
*/
|
|
78
|
-
issuerSignature:
|
|
83
|
+
issuerSignature: Hex;
|
|
79
84
|
/**
|
|
80
85
|
* (sharing) `signTypedData` signature created by `recipient` with format:
|
|
81
86
|
* (sharing) < sealingKey, issuerSignature>
|
|
82
87
|
* ** required for shared permits **
|
|
83
88
|
*/
|
|
84
|
-
recipientSignature:
|
|
89
|
+
recipientSignature: Hex;
|
|
85
90
|
/**
|
|
86
91
|
* EIP712 domain used to sign this permit.
|
|
87
92
|
* Should not be set manually, included in metadata as part of serialization flows.
|
|
@@ -149,7 +154,7 @@ export type ImportSharedPermitOptions = {
|
|
|
149
154
|
recipient: string;
|
|
150
155
|
issuerSignature: string;
|
|
151
156
|
name?: string;
|
|
152
|
-
expiration
|
|
157
|
+
expiration: number;
|
|
153
158
|
validatorId?: number;
|
|
154
159
|
validatorContract?: string;
|
|
155
160
|
};
|
|
@@ -166,11 +171,19 @@ export type SerializedPermit = Omit<Permit, 'sealingPair'> & {
|
|
|
166
171
|
* A type representing the Permission struct that is passed to Permissioned.sol to grant encrypted data access.
|
|
167
172
|
*/
|
|
168
173
|
export type Permission = Expand<
|
|
169
|
-
Omit<Permit, 'name' | 'type' | 'sealingPair'> & {
|
|
170
|
-
sealingKey:
|
|
174
|
+
Omit<Permit, 'name' | 'type' | 'sealingPair' | 'hash'> & {
|
|
175
|
+
sealingKey: Hex;
|
|
171
176
|
}
|
|
172
177
|
>;
|
|
173
178
|
|
|
179
|
+
/**
|
|
180
|
+
* A type representing the permit fields that are used to generate the hash
|
|
181
|
+
*/
|
|
182
|
+
export type PermitHashFields = Pick<
|
|
183
|
+
Permit,
|
|
184
|
+
'type' | 'issuer' | 'expiration' | 'recipient' | 'validatorId' | 'validatorContract'
|
|
185
|
+
>;
|
|
186
|
+
|
|
174
187
|
/**
|
|
175
188
|
* Validation result type
|
|
176
189
|
*/
|
package/permits/utils.ts
CHANGED
|
@@ -23,9 +23,9 @@ describe('Validation Tests', () => {
|
|
|
23
23
|
name: 'Test Permit',
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
expect(() => validateSelfPermitOptions(options)).not.toThrow();
|
|
26
27
|
const result = validateSelfPermitOptions(options);
|
|
27
|
-
expect(result
|
|
28
|
-
expect(result.data).toBeDefined();
|
|
28
|
+
expect(result).toBeDefined();
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('should reject invalid address', () => {
|
|
@@ -35,9 +35,7 @@ describe('Validation Tests', () => {
|
|
|
35
35
|
name: 'Test Permit',
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
expect(result.success).toBe(false);
|
|
40
|
-
expect(result.error).toBeDefined();
|
|
38
|
+
expect(() => validateSelfPermitOptions(options)).toThrow();
|
|
41
39
|
});
|
|
42
40
|
|
|
43
41
|
it('should reject zero address', () => {
|
|
@@ -47,9 +45,7 @@ describe('Validation Tests', () => {
|
|
|
47
45
|
name: 'Test Permit',
|
|
48
46
|
};
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
expect(result.success).toBe(false);
|
|
52
|
-
expect(result.error).toBeDefined();
|
|
48
|
+
expect(() => validateSelfPermitOptions(options)).toThrow();
|
|
53
49
|
});
|
|
54
50
|
});
|
|
55
51
|
|
|
@@ -62,8 +58,7 @@ describe('Validation Tests', () => {
|
|
|
62
58
|
name: 'Sharing Permit',
|
|
63
59
|
};
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
expect(result.success).toBe(true);
|
|
61
|
+
expect(() => validateSharingPermitOptions(options)).not.toThrow();
|
|
67
62
|
});
|
|
68
63
|
|
|
69
64
|
it('should reject sharing permit with zero recipient', () => {
|
|
@@ -74,8 +69,7 @@ describe('Validation Tests', () => {
|
|
|
74
69
|
name: 'Sharing Permit',
|
|
75
70
|
};
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
expect(result.success).toBe(false);
|
|
72
|
+
expect(() => validateSharingPermitOptions(options)).toThrow();
|
|
79
73
|
});
|
|
80
74
|
|
|
81
75
|
it('should reject sharing permit with invalid recipient', () => {
|
|
@@ -86,8 +80,7 @@ describe('Validation Tests', () => {
|
|
|
86
80
|
name: 'Sharing Permit',
|
|
87
81
|
};
|
|
88
82
|
|
|
89
|
-
|
|
90
|
-
expect(result.success).toBe(false);
|
|
83
|
+
expect(() => validateSharingPermitOptions(options)).toThrow();
|
|
91
84
|
});
|
|
92
85
|
});
|
|
93
86
|
|
|
@@ -95,37 +88,47 @@ describe('Validation Tests', () => {
|
|
|
95
88
|
it('should validate valid import permit options', () => {
|
|
96
89
|
const options: ImportSharedPermitOptions = {
|
|
97
90
|
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
91
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
98
92
|
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
99
93
|
issuerSignature: '0x1234567890abcdef',
|
|
100
94
|
name: 'Import Permit',
|
|
101
95
|
};
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
expect(() => validateImportPermitOptions(options)).not.toThrow();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should reject import permit with missing expiration', () => {
|
|
101
|
+
const options = {
|
|
102
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
103
|
+
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
104
|
+
issuerSignature: '0x1234567890abcdef',
|
|
105
|
+
name: 'Import Permit',
|
|
106
|
+
};
|
|
107
|
+
expect(() => validateImportPermitOptions(options)).toThrow();
|
|
105
108
|
});
|
|
106
109
|
|
|
107
110
|
it('should reject import permit with empty signature', () => {
|
|
108
111
|
const options: ImportSharedPermitOptions = {
|
|
109
112
|
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
113
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
110
114
|
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
111
115
|
issuerSignature: '0x',
|
|
112
116
|
name: 'Import Permit',
|
|
113
117
|
};
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
expect(result.success).toBe(false);
|
|
119
|
+
expect(() => validateImportPermitOptions(options)).toThrow();
|
|
117
120
|
});
|
|
118
121
|
|
|
119
122
|
it('should reject import permit with invalid signature', () => {
|
|
120
123
|
const options: ImportSharedPermitOptions = {
|
|
121
124
|
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
125
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
122
126
|
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
123
127
|
issuerSignature: '0x',
|
|
124
128
|
name: 'Import Permit',
|
|
125
129
|
};
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
expect(result.success).toBe(false);
|
|
131
|
+
expect(() => validateImportPermitOptions(options)).toThrow();
|
|
129
132
|
});
|
|
130
133
|
});
|
|
131
134
|
|
|
@@ -135,15 +138,13 @@ describe('Validation Tests', () => {
|
|
|
135
138
|
permit.type = 'self';
|
|
136
139
|
permit.issuerSignature = '0x1234567890abcdef';
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
expect(result.success).toBe(true);
|
|
141
|
+
expect(() => validateSelfPermit(permit)).not.toThrow();
|
|
140
142
|
});
|
|
141
143
|
|
|
142
144
|
it('should reject self permit with missing sealing pair', async () => {
|
|
143
145
|
const permit = { ...(await createMockPermit()), sealingPair: undefined };
|
|
144
146
|
permit.type = 'self';
|
|
145
|
-
|
|
146
|
-
expect(result.success).toBe(false);
|
|
147
|
+
expect(() => validateSelfPermit(permit as unknown as Permit)).toThrow();
|
|
147
148
|
});
|
|
148
149
|
});
|
|
149
150
|
|
|
@@ -154,8 +155,7 @@ describe('Validation Tests', () => {
|
|
|
154
155
|
permit.issuerSignature = '0x1234567890abcdef';
|
|
155
156
|
permit.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
expect(result.success).toBe(true);
|
|
158
|
+
expect(() => validateSharingPermit(permit)).not.toThrow();
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
it('should reject sharing permit with zero recipient', async () => {
|
|
@@ -164,8 +164,7 @@ describe('Validation Tests', () => {
|
|
|
164
164
|
permit.issuerSignature = '0x1234567890abcdef';
|
|
165
165
|
permit.recipient = '0x0000000000000000000000000000000000000000';
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
expect(result.success).toBe(false);
|
|
167
|
+
expect(() => validateSharingPermit(permit)).toThrow();
|
|
169
168
|
});
|
|
170
169
|
});
|
|
171
170
|
|
|
@@ -177,8 +176,7 @@ describe('Validation Tests', () => {
|
|
|
177
176
|
permit.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
|
|
178
177
|
permit.recipientSignature = '0xabcdef1234567890';
|
|
179
178
|
|
|
180
|
-
|
|
181
|
-
expect(result.success).toBe(true);
|
|
179
|
+
expect(() => validateImportPermit(permit)).not.toThrow();
|
|
182
180
|
});
|
|
183
181
|
|
|
184
182
|
it('should reject import permit with empty recipient signature', async () => {
|
|
@@ -188,8 +186,7 @@ describe('Validation Tests', () => {
|
|
|
188
186
|
permit.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
|
|
189
187
|
permit.recipientSignature = '0x';
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
expect(result.success).toBe(false);
|
|
189
|
+
expect(() => validateImportPermit(permit)).toThrow();
|
|
193
190
|
});
|
|
194
191
|
});
|
|
195
192
|
|