@cofhe/sdk 0.6.0 → 0.7.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 +49 -0
- package/acps/acp.ts +411 -0
- package/acps/index.ts +70 -0
- package/{permits → acps}/onchain-utils.ts +49 -24
- package/acps/sealing.ts +90 -0
- package/acps/signature.ts +89 -0
- package/acps/store.ts +172 -0
- package/acps/test/acp.test.ts +615 -0
- package/acps/test/localstorage.test.ts +105 -0
- package/acps/test/sealing.test.ts +77 -0
- package/acps/test/store.test.ts +88 -0
- package/acps/test/validation.test.ts +361 -0
- package/acps/test-utils.ts +32 -0
- package/acps/types.ts +252 -0
- package/acps/validation.ts +392 -0
- package/adapters/test/ethers5.test.ts +4 -1
- package/adapters/test/ethers6.test.ts +4 -1
- package/adapters/test/wagmi.test.ts +5 -2
- package/chains/chains/stagingCofhe.ts +21 -0
- package/chains/index.ts +3 -1
- package/chains/test/chains.test.ts +2 -1
- package/core/acps.ts +625 -0
- package/core/client.ts +136 -39
- package/core/clientTypes.ts +52 -41
- package/core/config.ts +66 -5
- package/core/debug.ts +72 -0
- package/core/decrypt/MockThresholdNetworkAbi.ts +20 -11
- package/core/decrypt/apiError.ts +104 -0
- package/core/decrypt/cofheMocksDecryptForTx.ts +11 -11
- package/core/decrypt/cofheMocksDecryptForView.ts +7 -7
- package/core/decrypt/decryptForTxBuilder.ts +102 -102
- package/core/decrypt/decryptForViewBuilder.ts +90 -90
- package/core/decrypt/submitRetry.ts +38 -30
- package/core/decrypt/tnDecryptV1.ts +5 -5
- package/core/decrypt/tnDecryptV2.ts +45 -34
- package/core/decrypt/tnSealOutputV1.ts +4 -3
- package/core/decrypt/tnSealOutputV2.ts +44 -31
- package/core/encrypt/cofheMocksZkVerifySign.ts +59 -74
- package/core/encrypt/encryptInputsBuilder.ts +86 -52
- package/core/encrypt/zkPackProveVerify.ts +25 -18
- package/core/error.ts +34 -6
- package/core/index.ts +14 -14
- package/core/test/acpDefaults.test.ts +52 -0
- package/core/test/acps.test.ts +596 -0
- package/core/test/apiError.test.ts +130 -0
- package/core/test/client.test.ts +22 -19
- package/core/test/config.test.ts +25 -5
- package/core/test/decrypt.test.ts +40 -35
- package/core/test/decryptBuilders.test.ts +68 -68
- package/core/test/decryptErrorCodes.test.ts +217 -0
- package/core/test/encryptInputsBuilder.test.ts +72 -41
- package/core/test/pollCallbacks.test.ts +89 -18
- package/core/test/stagingRedirect.ts +18 -0
- package/core/test/submitRetry.test.ts +182 -0
- package/core/types.ts +9 -69
- package/dist/acp-Wi6isVQI.d.cts +407 -0
- package/dist/acp-Wi6isVQI.d.ts +407 -0
- package/dist/acps.cjs +1081 -0
- package/dist/acps.d.cts +480 -0
- package/dist/acps.d.ts +480 -0
- package/dist/acps.js +2 -0
- package/dist/chains.cjs +14 -1
- package/dist/chains.d.cts +30 -1
- package/dist/chains.d.ts +30 -1
- package/dist/chains.js +1 -1
- package/dist/{chunk-PE5V5CCV.js → chunk-43USWPEH.js} +1000 -604
- package/dist/{chunk-MTRAXQXC.js → chunk-N6IDQRRU.js} +14 -2
- package/dist/chunk-Q7CBWGQX.js +1029 -0
- package/dist/{clientTypes-CyUvRRzA.d.ts → clientTypes-BN3nbzYM.d.ts} +342 -165
- package/dist/{clientTypes-BDy1qIBu.d.cts → clientTypes-CYZjFznO.d.cts} +342 -165
- package/dist/core.cjs +1431 -1026
- package/dist/core.d.cts +55 -10
- package/dist/core.d.ts +55 -10
- package/dist/core.js +3 -3
- package/dist/node.cjs +1356 -976
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -3
- package/dist/web.cjs +1356 -976
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +3 -3
- package/node/test/inherited.test.ts +75 -65
- package/node/test/tfheinit.test.ts +23 -8
- package/package.json +6 -6
- package/web/test/client.web.test.ts +5 -1
- package/web/test/inherited.web.test.ts +75 -65
- package/web/test/tfheinit.web.test.ts +14 -5
- package/web/test/worker.config.web.test.ts +38 -23
- package/web/test/worker.output.web.test.ts +25 -24
- package/core/encrypt/encryptUtils.ts +0 -67
- package/core/permits.ts +0 -206
- package/core/test/permits.test.ts +0 -534
- package/dist/chunk-VB62WYPL.js +0 -978
- package/dist/permit-DnVMDT5h.d.cts +0 -376
- package/dist/permit-DnVMDT5h.d.ts +0 -376
- package/dist/permits.cjs +0 -1026
- package/dist/permits.d.cts +0 -353
- package/dist/permits.d.ts +0 -353
- package/dist/permits.js +0 -2
- package/permits/index.ts +0 -68
- package/permits/permit.ts +0 -385
- package/permits/sealing.ts +0 -131
- package/permits/signature.ts +0 -79
- package/permits/store.ts +0 -157
- package/permits/test/localstorage.test.ts +0 -113
- package/permits/test/permit.test.ts +0 -557
- package/permits/test/sealing.test.ts +0 -84
- package/permits/test/store.test.ts +0 -88
- package/permits/test/validation.test.ts +0 -361
- package/permits/test-utils.ts +0 -28
- package/permits/types.ts +0 -204
- package/permits/validation.ts +0 -327
- /package/{permits → acps}/utils.ts +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment happy-dom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { getACP, setACP, removeACP, getActiveACPHash, setActiveACPHash, ACPUtils, acpStore } from '../index.js';
|
|
7
|
+
import { createMockACP } from '../test-utils.js';
|
|
8
|
+
|
|
9
|
+
// Type declarations for happy-dom environment
|
|
10
|
+
declare const localStorage: {
|
|
11
|
+
clear: () => void;
|
|
12
|
+
getItem: (name: string) => string | null;
|
|
13
|
+
setItem: (name: string, value: string) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('ACPs localStorage Tests', () => {
|
|
17
|
+
const chainId = 1;
|
|
18
|
+
const account = '0x1234567890123456789012345678901234567890';
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
// Clear localStorage and reset store state
|
|
22
|
+
localStorage.clear();
|
|
23
|
+
acpStore.resetStore();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
// Clean up after each test
|
|
28
|
+
localStorage.clear();
|
|
29
|
+
acpStore.resetStore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should persist acps to localStorage', async () => {
|
|
33
|
+
const acp = await createMockACP();
|
|
34
|
+
|
|
35
|
+
setACP(chainId, account, acp);
|
|
36
|
+
|
|
37
|
+
// Verify data is stored in localStorage
|
|
38
|
+
const storedData = localStorage.getItem('cofhesdk-acps');
|
|
39
|
+
expect(storedData).toBeDefined();
|
|
40
|
+
|
|
41
|
+
const parsedData = JSON.parse(storedData!);
|
|
42
|
+
expect(parsedData.state.acps[chainId][account][acp.hash]).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should persist active acp hash to localStorage', async () => {
|
|
46
|
+
const acp = await createMockACP();
|
|
47
|
+
|
|
48
|
+
setACP(chainId, account, acp);
|
|
49
|
+
setActiveACPHash(chainId, account, acp.hash);
|
|
50
|
+
|
|
51
|
+
// Verify active acp hash is stored
|
|
52
|
+
const storedData = localStorage.getItem('cofhesdk-acps');
|
|
53
|
+
expect(storedData).toBeDefined();
|
|
54
|
+
|
|
55
|
+
const parsedData = JSON.parse(storedData!);
|
|
56
|
+
expect(parsedData.state.activeACPHash[chainId][account]).toBe(acp.hash);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should restore acps from localStorage', async () => {
|
|
60
|
+
const acp = await createMockACP();
|
|
61
|
+
|
|
62
|
+
// Add acp to localStorage
|
|
63
|
+
setACP(chainId, account, acp);
|
|
64
|
+
setActiveACPHash(chainId, account, acp.hash);
|
|
65
|
+
const serializedACP = ACPUtils.serialize(acp);
|
|
66
|
+
|
|
67
|
+
// Verify data is restored
|
|
68
|
+
const retrievedACP = getACP(chainId, account, acp.hash);
|
|
69
|
+
expect(retrievedACP).toBeDefined();
|
|
70
|
+
expect(ACPUtils.serialize(retrievedACP!)).toEqual(serializedACP);
|
|
71
|
+
|
|
72
|
+
const activeHash = getActiveACPHash(chainId, account);
|
|
73
|
+
expect(activeHash).toBe(acp.hash);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle corrupted localStorage data gracefully', () => {
|
|
77
|
+
// Set invalid JSON in localStorage
|
|
78
|
+
localStorage.setItem('cofhesdk-acps', 'invalid json');
|
|
79
|
+
|
|
80
|
+
// Store should handle this gracefully
|
|
81
|
+
expect(() => {
|
|
82
|
+
acpStore.store.getState();
|
|
83
|
+
}).not.toThrow();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should clean up localStorage when acps are removed', async () => {
|
|
87
|
+
const acp = await createMockACP();
|
|
88
|
+
|
|
89
|
+
setACP(chainId, account, acp);
|
|
90
|
+
setActiveACPHash(chainId, account, acp.hash);
|
|
91
|
+
|
|
92
|
+
// Verify data exists
|
|
93
|
+
let storedData = localStorage.getItem('cofhesdk-acps');
|
|
94
|
+
expect(storedData).toBeDefined();
|
|
95
|
+
|
|
96
|
+
// Remove acp
|
|
97
|
+
removeACP(chainId, account, acp.hash);
|
|
98
|
+
|
|
99
|
+
// Verify data is cleaned up
|
|
100
|
+
storedData = localStorage.getItem('cofhesdk-acps');
|
|
101
|
+
const parsedData = JSON.parse(storedData!);
|
|
102
|
+
expect(parsedData.state.acps[chainId][account][acp.hash]).toBeUndefined();
|
|
103
|
+
expect(parsedData.state.activeACPHash[chainId][account]).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { GenerateSealingKey, seal, unsealWithPrivateKey } from '../index.js';
|
|
3
|
+
|
|
4
|
+
describe('seal / unsealWithPrivateKey', () => {
|
|
5
|
+
it('should seal data into EthEncryptedData shape', () => {
|
|
6
|
+
const publicKey = `0x${'b'.repeat(64)}`;
|
|
7
|
+
const value = BigInt(12345);
|
|
8
|
+
|
|
9
|
+
const encryptedData = seal(value, publicKey);
|
|
10
|
+
|
|
11
|
+
expect(encryptedData).toHaveProperty('data');
|
|
12
|
+
expect(encryptedData).toHaveProperty('public_key');
|
|
13
|
+
expect(encryptedData).toHaveProperty('nonce');
|
|
14
|
+
expect(encryptedData.data).toBeInstanceOf(Uint8Array);
|
|
15
|
+
expect(encryptedData.public_key).toBeInstanceOf(Uint8Array);
|
|
16
|
+
expect(encryptedData.nonce).toBeInstanceOf(Uint8Array);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should round-trip: seal for a generated key, unseal with its private key', () => {
|
|
20
|
+
const pair = GenerateSealingKey();
|
|
21
|
+
const value = BigInt(987654321);
|
|
22
|
+
|
|
23
|
+
const sealed = seal(value, pair.publicKey);
|
|
24
|
+
const unsealed = unsealWithPrivateKey(pair.privateKey, sealed);
|
|
25
|
+
|
|
26
|
+
expect(unsealed).toBe(value);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('accepts keys with or without the 0x prefix', () => {
|
|
30
|
+
const pair = GenerateSealingKey();
|
|
31
|
+
const value = BigInt(42);
|
|
32
|
+
|
|
33
|
+
const sealed = seal(value, pair.publicKey.slice(2));
|
|
34
|
+
expect(unsealWithPrivateKey(pair.privateKey.slice(2), sealed)).toBe(value);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should throw error for invalid public key in seal', () => {
|
|
38
|
+
expect(() => {
|
|
39
|
+
seal(BigInt(12345), 'invalid');
|
|
40
|
+
}).toThrow('Public key must be of length 64');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should throw error for invalid private key in unseal', () => {
|
|
44
|
+
const pair = GenerateSealingKey();
|
|
45
|
+
const sealed = seal(BigInt(1), pair.publicKey);
|
|
46
|
+
|
|
47
|
+
expect(() => {
|
|
48
|
+
unsealWithPrivateKey('deadbeef', sealed);
|
|
49
|
+
}).toThrow('Private key must be of length 64');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should throw error for invalid value in seal', () => {
|
|
53
|
+
const publicKey = `0x${'b'.repeat(64)}`;
|
|
54
|
+
|
|
55
|
+
expect(() => {
|
|
56
|
+
// @ts-expect-error - invalid value
|
|
57
|
+
seal('not a number', publicKey);
|
|
58
|
+
}).toThrow('Value not a number is not a number or bigint: string');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('GenerateSealingKey', () => {
|
|
63
|
+
it('should generate a 0x-prefixed 32-byte hex pair', () => {
|
|
64
|
+
const pair = GenerateSealingKey();
|
|
65
|
+
|
|
66
|
+
expect(pair.privateKey).toMatch(/^0x[0-9a-f]{64}$/);
|
|
67
|
+
expect(pair.publicKey).toMatch(/^0x[0-9a-f]{64}$/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should generate different keys on each call', () => {
|
|
71
|
+
const key1 = GenerateSealingKey();
|
|
72
|
+
const key2 = GenerateSealingKey();
|
|
73
|
+
|
|
74
|
+
expect(key1.privateKey).not.toBe(key2.privateKey);
|
|
75
|
+
expect(key1.publicKey).not.toBe(key2.publicKey);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
acpStore,
|
|
8
|
+
getACP,
|
|
9
|
+
getActiveACP,
|
|
10
|
+
getACPs,
|
|
11
|
+
setACP,
|
|
12
|
+
removeACP,
|
|
13
|
+
getActiveACPHash,
|
|
14
|
+
setActiveACPHash,
|
|
15
|
+
ACPUtils,
|
|
16
|
+
} from '../index.js';
|
|
17
|
+
|
|
18
|
+
import { createMockACP } from '../test-utils.js';
|
|
19
|
+
|
|
20
|
+
describe('Storage Tests', () => {
|
|
21
|
+
const chainId = 1;
|
|
22
|
+
const account = '0x1234567890123456789012345678901234567890';
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
acpStore.resetStore();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
acpStore.resetStore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('ACP Storage', () => {
|
|
33
|
+
it('should store and retrieve acps', async () => {
|
|
34
|
+
const acp = await createMockACP();
|
|
35
|
+
|
|
36
|
+
setACP(chainId, account, acp);
|
|
37
|
+
const retrieved = getACP(chainId, account, acp.hash);
|
|
38
|
+
|
|
39
|
+
expect(retrieved).toBeDefined();
|
|
40
|
+
expect(ACPUtils.serialize(retrieved!)).toEqual(ACPUtils.serialize(acp));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should handle multiple acps per account', async () => {
|
|
44
|
+
const acp1 = await createMockACP();
|
|
45
|
+
const acp2 = await createMockACP({
|
|
46
|
+
issuer: '0x0987654321098765432109876543210987654321' as `0x${string}`,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
setACP(chainId, account, acp1);
|
|
50
|
+
setACP(chainId, account, acp2);
|
|
51
|
+
|
|
52
|
+
const acps = getACPs(chainId, account);
|
|
53
|
+
expect(Object.keys(acps)).toHaveLength(2);
|
|
54
|
+
|
|
55
|
+
expect(ACPUtils.serialize(acps[acp1.hash])).toEqual(ACPUtils.serialize(acp1));
|
|
56
|
+
expect(ACPUtils.serialize(acps[acp2.hash])).toEqual(ACPUtils.serialize(acp2));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle active acp hash', async () => {
|
|
60
|
+
const acp = await createMockACP();
|
|
61
|
+
|
|
62
|
+
setACP(chainId, account, acp);
|
|
63
|
+
setActiveACPHash(chainId, account, acp.hash);
|
|
64
|
+
|
|
65
|
+
const activeHash = getActiveACPHash(chainId, account);
|
|
66
|
+
expect(activeHash).toBe(acp.hash);
|
|
67
|
+
|
|
68
|
+
const activeACP = getActiveACP(chainId, account);
|
|
69
|
+
expect(activeACP).toBeDefined();
|
|
70
|
+
expect(ACPUtils.serialize(activeACP!)).toEqual(ACPUtils.serialize(acp));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should remove acps', async () => {
|
|
74
|
+
const acp = await createMockACP();
|
|
75
|
+
|
|
76
|
+
setACP(chainId, account, acp);
|
|
77
|
+
setActiveACPHash(chainId, account, acp.hash);
|
|
78
|
+
|
|
79
|
+
removeACP(chainId, account, acp.hash);
|
|
80
|
+
|
|
81
|
+
const retrieved = getACP(chainId, account, acp.hash);
|
|
82
|
+
expect(retrieved).toBeUndefined();
|
|
83
|
+
|
|
84
|
+
const activeHash = getActiveACPHash(chainId, account);
|
|
85
|
+
expect(activeHash).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
ValidationUtils,
|
|
4
|
+
validateSelfACPOptions,
|
|
5
|
+
validateSharingACPOptions,
|
|
6
|
+
validateImportACPOptions,
|
|
7
|
+
validateSelfACP,
|
|
8
|
+
validateSharingACP,
|
|
9
|
+
validateImportACP,
|
|
10
|
+
type ACP,
|
|
11
|
+
type CreateSelfACPOptions,
|
|
12
|
+
type CreateSharingACPOptions,
|
|
13
|
+
type ImportSharedACPOptions,
|
|
14
|
+
} from '../index.js';
|
|
15
|
+
import { createMockACP } from '../test-utils.js';
|
|
16
|
+
|
|
17
|
+
describe('Validation Tests', () => {
|
|
18
|
+
describe('validateSelfACPOptions', () => {
|
|
19
|
+
it('should validate valid self acp options', () => {
|
|
20
|
+
const options: CreateSelfACPOptions = {
|
|
21
|
+
type: 'self',
|
|
22
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
23
|
+
name: 'Test ACP',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
expect(() => validateSelfACPOptions(options)).not.toThrow();
|
|
27
|
+
const result = validateSelfACPOptions(options);
|
|
28
|
+
expect(result).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should reject invalid address', () => {
|
|
32
|
+
const options: CreateSelfACPOptions = {
|
|
33
|
+
type: 'self',
|
|
34
|
+
issuer: 'invalid-address',
|
|
35
|
+
name: 'Test ACP',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
expect(() => validateSelfACPOptions(options)).toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should reject zero address', () => {
|
|
42
|
+
const options: CreateSelfACPOptions = {
|
|
43
|
+
type: 'self',
|
|
44
|
+
issuer: '0x0000000000000000000000000000000000000000',
|
|
45
|
+
name: 'Test ACP',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
expect(() => validateSelfACPOptions(options)).toThrow();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('validateSharingACPOptions', () => {
|
|
53
|
+
it('should validate valid sharing acp options', () => {
|
|
54
|
+
const options: CreateSharingACPOptions = {
|
|
55
|
+
type: 'sharing',
|
|
56
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
57
|
+
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
58
|
+
name: 'Sharing ACP',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
expect(() => validateSharingACPOptions(options)).not.toThrow();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should reject sharing acp with zero recipient', () => {
|
|
65
|
+
const options: CreateSharingACPOptions = {
|
|
66
|
+
type: 'sharing',
|
|
67
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
68
|
+
recipient: '0x0000000000000000000000000000000000000000',
|
|
69
|
+
name: 'Sharing ACP',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
expect(() => validateSharingACPOptions(options)).toThrow();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should reject sharing acp with invalid recipient', () => {
|
|
76
|
+
const options: CreateSharingACPOptions = {
|
|
77
|
+
type: 'sharing',
|
|
78
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
79
|
+
recipient: 'invalid-address',
|
|
80
|
+
name: 'Sharing ACP',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
expect(() => validateSharingACPOptions(options)).toThrow();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('validateImportACPOptions', () => {
|
|
88
|
+
it('should validate valid import acp options', () => {
|
|
89
|
+
const options: ImportSharedACPOptions = {
|
|
90
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
91
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
92
|
+
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
93
|
+
issuerSignature: '0x1234567890abcdef',
|
|
94
|
+
name: 'Import ACP',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
expect(() => validateImportACPOptions(options)).not.toThrow();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should reject import acp with missing expiration', () => {
|
|
101
|
+
const options = {
|
|
102
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
103
|
+
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
104
|
+
issuerSignature: '0x1234567890abcdef',
|
|
105
|
+
name: 'Import ACP',
|
|
106
|
+
};
|
|
107
|
+
expect(() => validateImportACPOptions(options)).toThrow();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should reject import acp with empty signature', () => {
|
|
111
|
+
const options: ImportSharedACPOptions = {
|
|
112
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
113
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
114
|
+
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
115
|
+
issuerSignature: '0x',
|
|
116
|
+
name: 'Import ACP',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
expect(() => validateImportACPOptions(options)).toThrow();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should reject import acp with invalid signature', () => {
|
|
123
|
+
const options: ImportSharedACPOptions = {
|
|
124
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
125
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
126
|
+
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // Alice's address
|
|
127
|
+
issuerSignature: '0x',
|
|
128
|
+
name: 'Import ACP',
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
expect(() => validateImportACPOptions(options)).toThrow();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('validateSelfACP', () => {
|
|
136
|
+
it('should validate valid self acp', async () => {
|
|
137
|
+
const acp = await createMockACP();
|
|
138
|
+
acp.type = 'self';
|
|
139
|
+
acp.issuerSignature = '0x1234567890abcdef';
|
|
140
|
+
|
|
141
|
+
expect(() => validateSelfACP(acp)).not.toThrow();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should reject self acp with missing sealing pair', async () => {
|
|
145
|
+
const acp = { ...(await createMockACP()), sealingPrivateKey: undefined };
|
|
146
|
+
acp.type = 'self';
|
|
147
|
+
expect(() => validateSelfACP(acp as unknown as ACP)).toThrow();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('validateSharingACP', () => {
|
|
152
|
+
it('should validate valid sharing acp', async () => {
|
|
153
|
+
const acp = await createMockACP();
|
|
154
|
+
acp.type = 'sharing';
|
|
155
|
+
acp.issuerSignature = '0x1234567890abcdef';
|
|
156
|
+
acp.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
|
|
157
|
+
|
|
158
|
+
expect(() => validateSharingACP(acp)).not.toThrow();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should reject sharing acp with zero recipient', async () => {
|
|
162
|
+
const acp = await createMockACP();
|
|
163
|
+
acp.type = 'sharing';
|
|
164
|
+
acp.issuerSignature = '0x1234567890abcdef';
|
|
165
|
+
acp.recipient = '0x0000000000000000000000000000000000000000';
|
|
166
|
+
|
|
167
|
+
expect(() => validateSharingACP(acp)).toThrow();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('validateImportACP', () => {
|
|
172
|
+
it('should validate valid import acp', async () => {
|
|
173
|
+
const acp = await createMockACP();
|
|
174
|
+
acp.type = 'recipient';
|
|
175
|
+
acp.issuerSignature = '0x1234567890abcdef';
|
|
176
|
+
acp.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
|
|
177
|
+
acp.recipientSignature = '0xabcdef1234567890';
|
|
178
|
+
|
|
179
|
+
expect(() => validateImportACP(acp)).not.toThrow();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should reject import acp with empty recipient signature', async () => {
|
|
183
|
+
const acp = await createMockACP();
|
|
184
|
+
acp.type = 'recipient';
|
|
185
|
+
acp.issuerSignature = '0x1234567890abcdef';
|
|
186
|
+
acp.recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Alice's address
|
|
187
|
+
acp.recipientSignature = '0x';
|
|
188
|
+
|
|
189
|
+
expect(() => validateImportACP(acp)).toThrow();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('ValidationUtils', () => {
|
|
194
|
+
describe('isExpired', () => {
|
|
195
|
+
it('should return true for expired acp', async () => {
|
|
196
|
+
const acp = {
|
|
197
|
+
...(await createMockACP()),
|
|
198
|
+
expiration: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
|
|
199
|
+
};
|
|
200
|
+
expect(ValidationUtils.isExpired(acp)).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should return false for non-expired acp', async () => {
|
|
204
|
+
const acp = {
|
|
205
|
+
...(await createMockACP()),
|
|
206
|
+
expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
|
|
207
|
+
};
|
|
208
|
+
expect(ValidationUtils.isExpired(acp)).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('isSigned', () => {
|
|
213
|
+
it('should return true for signed self acp', async () => {
|
|
214
|
+
const acp = {
|
|
215
|
+
...(await createMockACP()),
|
|
216
|
+
type: 'self' as const,
|
|
217
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
218
|
+
};
|
|
219
|
+
expect(ValidationUtils.isSigned(acp)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should return false for unsigned self acp', async () => {
|
|
223
|
+
const acp = {
|
|
224
|
+
...(await createMockACP()),
|
|
225
|
+
type: 'self' as const,
|
|
226
|
+
issuerSignature: '0x' as `0x${string}`,
|
|
227
|
+
};
|
|
228
|
+
expect(ValidationUtils.isSigned(acp)).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should return true for signed recipient acp', async () => {
|
|
232
|
+
const acp = {
|
|
233
|
+
...(await createMockACP()),
|
|
234
|
+
type: 'recipient' as const,
|
|
235
|
+
recipientSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
236
|
+
};
|
|
237
|
+
expect(ValidationUtils.isSigned(acp)).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should return false for unsigned recipient acp', async () => {
|
|
241
|
+
const acp = {
|
|
242
|
+
...(await createMockACP()),
|
|
243
|
+
type: 'recipient' as const,
|
|
244
|
+
recipientSignature: '0x' as `0x${string}`,
|
|
245
|
+
};
|
|
246
|
+
expect(ValidationUtils.isSigned(acp)).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('isSignedAndNotExpired', () => {
|
|
251
|
+
it('should return valid for valid acp', async () => {
|
|
252
|
+
const acp = {
|
|
253
|
+
...(await createMockACP()),
|
|
254
|
+
expiration: Math.floor(Date.now() / 1000) + 3600,
|
|
255
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
256
|
+
};
|
|
257
|
+
const result = ValidationUtils.isSignedAndNotExpired(acp);
|
|
258
|
+
expect(result.valid).toBe(true);
|
|
259
|
+
expect(result.error).toBeNull();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should return invalid for expired acp', async () => {
|
|
263
|
+
const acp = {
|
|
264
|
+
...(await createMockACP()),
|
|
265
|
+
expiration: Math.floor(Date.now() / 1000) - 3600,
|
|
266
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
267
|
+
};
|
|
268
|
+
const result = ValidationUtils.isSignedAndNotExpired(acp);
|
|
269
|
+
expect(result.valid).toBe(false);
|
|
270
|
+
expect(result.error).toBe('expired');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should return invalid for unsigned acp', async () => {
|
|
274
|
+
const acp = {
|
|
275
|
+
...(await createMockACP()),
|
|
276
|
+
expiration: Math.floor(Date.now() / 1000) + 3600,
|
|
277
|
+
issuerSignature: '0x' as `0x${string}`,
|
|
278
|
+
};
|
|
279
|
+
const result = ValidationUtils.isSignedAndNotExpired(acp);
|
|
280
|
+
expect(result.valid).toBe(false);
|
|
281
|
+
expect(result.error).toBe('not-signed');
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('assertSignedAndNotExpired', () => {
|
|
286
|
+
it('should not throw for valid acp', async () => {
|
|
287
|
+
const acp = {
|
|
288
|
+
...(await createMockACP()),
|
|
289
|
+
expiration: Math.floor(Date.now() / 1000) + 3600,
|
|
290
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
291
|
+
};
|
|
292
|
+
expect(() => ValidationUtils.assertSignedAndNotExpired(acp)).not.toThrow();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should throw for expired acp', async () => {
|
|
296
|
+
const acp = {
|
|
297
|
+
...(await createMockACP()),
|
|
298
|
+
expiration: Math.floor(Date.now() / 1000) - 3600,
|
|
299
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
300
|
+
};
|
|
301
|
+
expect(() => ValidationUtils.assertSignedAndNotExpired(acp)).toThrow('ACP is expired');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should throw for unsigned acp', async () => {
|
|
305
|
+
const acp = {
|
|
306
|
+
...(await createMockACP()),
|
|
307
|
+
expiration: Math.floor(Date.now() / 1000) + 3600,
|
|
308
|
+
issuerSignature: '0x' as `0x${string}`,
|
|
309
|
+
};
|
|
310
|
+
expect(() => ValidationUtils.assertSignedAndNotExpired(acp)).toThrow('ACP is not signed');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('isValid', () => {
|
|
315
|
+
it('should return invalid-schema for schema-invalid acp', async () => {
|
|
316
|
+
const acp = {
|
|
317
|
+
...(await createMockACP()),
|
|
318
|
+
type: 'self' as const,
|
|
319
|
+
expiration: Math.floor(Date.now() / 1000) + 3600,
|
|
320
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
321
|
+
// Self acps must have recipient == zeroAddress per schema.
|
|
322
|
+
recipient: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' as `0x${string}`,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const result = ValidationUtils.isValid(acp as unknown as ACP);
|
|
326
|
+
expect(result.valid).toBe(false);
|
|
327
|
+
expect(result.error).toBe('invalid-schema');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should return expired for expired but otherwise schema-valid acp', async () => {
|
|
331
|
+
const acp = {
|
|
332
|
+
...(await createMockACP()),
|
|
333
|
+
type: 'self' as const,
|
|
334
|
+
expiration: Math.floor(Date.now() / 1000) - 3600,
|
|
335
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
336
|
+
recipient: '0x0000000000000000000000000000000000000000' as `0x${string}`,
|
|
337
|
+
recipientSignature: '0x' as `0x${string}`,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const result = ValidationUtils.isValid(acp as unknown as ACP);
|
|
341
|
+
expect(result.valid).toBe(false);
|
|
342
|
+
expect(result.error).toBe('expired');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should return valid for schema-valid, signed, non-expired acp', async () => {
|
|
346
|
+
const acp = {
|
|
347
|
+
...(await createMockACP()),
|
|
348
|
+
type: 'self' as const,
|
|
349
|
+
expiration: Math.floor(Date.now() / 1000) + 3600,
|
|
350
|
+
issuerSignature: '0x1234567890abcdef' as `0x${string}`,
|
|
351
|
+
recipient: '0x0000000000000000000000000000000000000000' as `0x${string}`,
|
|
352
|
+
recipientSignature: '0x' as `0x${string}`,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const result = ValidationUtils.isValid(acp as unknown as ACP);
|
|
356
|
+
expect(result.valid).toBe(true);
|
|
357
|
+
expect(result.error).toBeNull();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ACP, type SerializedACP, GenerateSealingKey, ACPUtils } from './index.js';
|
|
2
|
+
|
|
3
|
+
// Mock acp for testing - using Bob's address as issuer
|
|
4
|
+
export const createMockACP = async (overrides: Partial<ACP> = {}): Promise<ACP> => {
|
|
5
|
+
const sealingPair = GenerateSealingKey();
|
|
6
|
+
|
|
7
|
+
const fields = {
|
|
8
|
+
name: 'Test ACP',
|
|
9
|
+
type: 'self',
|
|
10
|
+
issuer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Bob's address
|
|
11
|
+
expiration: 1000000000000,
|
|
12
|
+
recipient: '0x0000000000000000000000000000000000000000',
|
|
13
|
+
revokerData: 0,
|
|
14
|
+
revokerContract: '0x0000000000000000000000000000000000000000',
|
|
15
|
+
scope: 0,
|
|
16
|
+
contracts: [] as ACP['contracts'],
|
|
17
|
+
handles: [] as ACP['handles'],
|
|
18
|
+
sealingPrivateKey: sealingPair.privateKey,
|
|
19
|
+
sealingKey: sealingPair.publicKey,
|
|
20
|
+
issuerSignature: '0x',
|
|
21
|
+
recipientSignature: '0x',
|
|
22
|
+
_signedDomain: undefined,
|
|
23
|
+
...overrides,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
const serializedACP: SerializedACP = {
|
|
27
|
+
hash: ACPUtils.getHash(fields),
|
|
28
|
+
...fields,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return ACPUtils.deserialize(serializedACP);
|
|
32
|
+
};
|