@totemsdk/manifest 0.1.0 → 0.1.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/LICENSE +21 -0
- package/README.md +4 -4
- package/dist/constants.js +6 -3
- package/dist/encoding.js +11 -7
- package/dist/guards.js +10 -4
- package/dist/id.js +6 -3
- package/dist/index.js +19 -6
- package/dist/sign.js +14 -10
- package/dist/types.js +2 -1
- package/dist/verify.js +10 -7
- package/package.json +26 -5
- package/src/__tests__/encoding.test.ts +0 -185
- package/src/__tests__/guards.test.ts +0 -140
- package/src/__tests__/id.test.ts +0 -143
- package/src/__tests__/sign-verify.test.ts +0 -157
- package/src/constants.ts +0 -15
- package/src/encoding.ts +0 -100
- package/src/guards.ts +0 -48
- package/src/id.ts +0 -46
- package/src/index.ts +0 -26
- package/src/sign.ts +0 -63
- package/src/types.ts +0 -140
- package/src/verify.ts +0 -67
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* guards.test.ts
|
|
3
|
-
* Type guard tests for all four manifest kinds.
|
|
4
|
-
* Guards must work on both raw manifests and SignedManifest wrappers.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
isAppManifest,
|
|
9
|
-
isCapabilityManifest,
|
|
10
|
-
isDAppManifest,
|
|
11
|
-
isEdgeServiceManifest,
|
|
12
|
-
} from '../guards.js';
|
|
13
|
-
import type {
|
|
14
|
-
AppManifest,
|
|
15
|
-
CapabilityManifest,
|
|
16
|
-
DAppManifest,
|
|
17
|
-
EdgeServiceManifest,
|
|
18
|
-
SignedManifest,
|
|
19
|
-
} from '../types.js';
|
|
20
|
-
|
|
21
|
-
const APP: AppManifest = {
|
|
22
|
-
type: 'app',
|
|
23
|
-
appId: '',
|
|
24
|
-
name: 'Test',
|
|
25
|
-
version: '1.0.0',
|
|
26
|
-
authorAddress: 'MxAPP',
|
|
27
|
-
pearTopicKey: 'a'.repeat(64),
|
|
28
|
-
price: '0',
|
|
29
|
-
category: [],
|
|
30
|
-
permissions: [],
|
|
31
|
-
description: '',
|
|
32
|
-
minTotemVersion: '0.1.0',
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const CAP: CapabilityManifest = {
|
|
36
|
-
type: 'capability',
|
|
37
|
-
capabilityId: '',
|
|
38
|
-
capabilityName: 'x',
|
|
39
|
-
agentAddress: 'MxCAP',
|
|
40
|
-
agentIdentityKey: 'b'.repeat(64),
|
|
41
|
-
description: '',
|
|
42
|
-
inputSchema: {},
|
|
43
|
-
outputSchema: {},
|
|
44
|
-
pricePerCall: '1',
|
|
45
|
-
expiresAt: 0,
|
|
46
|
-
tags: [],
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const DAPP: DAppManifest = {
|
|
50
|
-
type: 'dapp',
|
|
51
|
-
dappId: '',
|
|
52
|
-
name: 'x',
|
|
53
|
-
version: '1.0.0',
|
|
54
|
-
authorAddress: 'MxDAPP',
|
|
55
|
-
contractHash: 'c'.repeat(64),
|
|
56
|
-
abi: [],
|
|
57
|
-
price: '0',
|
|
58
|
-
category: [],
|
|
59
|
-
description: '',
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const EDGE: EdgeServiceManifest = {
|
|
63
|
-
type: 'edge-service',
|
|
64
|
-
serviceId: '',
|
|
65
|
-
name: 'x',
|
|
66
|
-
version: '1.0.0',
|
|
67
|
-
operatorAddress: 'MxEDGE',
|
|
68
|
-
serviceType: 'lookup-provider',
|
|
69
|
-
description: '',
|
|
70
|
-
capabilities: [],
|
|
71
|
-
tags: [],
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function mockSigned<T extends { type: string }>(manifest: T): SignedManifest<T extends AppManifest ? AppManifest : T extends CapabilityManifest ? CapabilityManifest : T extends DAppManifest ? DAppManifest : EdgeServiceManifest> {
|
|
75
|
-
return {
|
|
76
|
-
manifest: manifest as any,
|
|
77
|
-
authorAddress: 'MxSIGNER',
|
|
78
|
-
signerPublicKey: 'aa'.repeat(544),
|
|
79
|
-
signedAt: Date.now(),
|
|
80
|
-
signature: 'bb'.repeat(100),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
describe('isAppManifest', () => {
|
|
85
|
-
it('returns true for raw AppManifest', () => expect(isAppManifest(APP)).toBe(true));
|
|
86
|
-
it('returns true for SignedManifest<AppManifest>', () => expect(isAppManifest(mockSigned(APP))).toBe(true));
|
|
87
|
-
it('returns false for CapabilityManifest', () => expect(isAppManifest(CAP)).toBe(false));
|
|
88
|
-
it('returns false for DAppManifest', () => expect(isAppManifest(DAPP)).toBe(false));
|
|
89
|
-
it('returns false for EdgeServiceManifest', () => expect(isAppManifest(EDGE)).toBe(false));
|
|
90
|
-
it('returns false for null', () => expect(isAppManifest(null)).toBe(false));
|
|
91
|
-
it('returns false for undefined', () => expect(isAppManifest(undefined)).toBe(false));
|
|
92
|
-
it('returns false for SignedManifest<CapabilityManifest>', () => expect(isAppManifest(mockSigned(CAP))).toBe(false));
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('isCapabilityManifest', () => {
|
|
96
|
-
it('returns true for raw CapabilityManifest', () => expect(isCapabilityManifest(CAP)).toBe(true));
|
|
97
|
-
it('returns true for SignedManifest<CapabilityManifest>', () => expect(isCapabilityManifest(mockSigned(CAP))).toBe(true));
|
|
98
|
-
it('returns false for AppManifest', () => expect(isCapabilityManifest(APP)).toBe(false));
|
|
99
|
-
it('returns false for DAppManifest', () => expect(isCapabilityManifest(DAPP)).toBe(false));
|
|
100
|
-
it('returns false for EdgeServiceManifest', () => expect(isCapabilityManifest(EDGE)).toBe(false));
|
|
101
|
-
it('returns false for null', () => expect(isCapabilityManifest(null)).toBe(false));
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('isDAppManifest', () => {
|
|
105
|
-
it('returns true for raw DAppManifest', () => expect(isDAppManifest(DAPP)).toBe(true));
|
|
106
|
-
it('returns true for SignedManifest<DAppManifest>', () => expect(isDAppManifest(mockSigned(DAPP))).toBe(true));
|
|
107
|
-
it('returns false for AppManifest', () => expect(isDAppManifest(APP)).toBe(false));
|
|
108
|
-
it('returns false for CapabilityManifest', () => expect(isDAppManifest(CAP)).toBe(false));
|
|
109
|
-
it('returns false for EdgeServiceManifest', () => expect(isDAppManifest(EDGE)).toBe(false));
|
|
110
|
-
it('returns false for null', () => expect(isDAppManifest(null)).toBe(false));
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe('isEdgeServiceManifest', () => {
|
|
114
|
-
it('returns true for raw EdgeServiceManifest', () => expect(isEdgeServiceManifest(EDGE)).toBe(true));
|
|
115
|
-
it('returns true for SignedManifest<EdgeServiceManifest>', () => expect(isEdgeServiceManifest(mockSigned(EDGE))).toBe(true));
|
|
116
|
-
it('returns false for AppManifest', () => expect(isEdgeServiceManifest(APP)).toBe(false));
|
|
117
|
-
it('returns false for CapabilityManifest', () => expect(isEdgeServiceManifest(CAP)).toBe(false));
|
|
118
|
-
it('returns false for DAppManifest', () => expect(isEdgeServiceManifest(DAPP)).toBe(false));
|
|
119
|
-
it('returns false for null', () => expect(isEdgeServiceManifest(null)).toBe(false));
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('cross-type guard correctness', () => {
|
|
123
|
-
const allManifests = [APP, CAP, DAPP, EDGE];
|
|
124
|
-
const guards = [isAppManifest, isCapabilityManifest, isDAppManifest, isEdgeServiceManifest];
|
|
125
|
-
|
|
126
|
-
it('each manifest is identified by exactly one guard', () => {
|
|
127
|
-
for (const m of allManifests) {
|
|
128
|
-
const trueCount = guards.filter(g => g(m)).length;
|
|
129
|
-
expect(trueCount).toBe(1);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('each signed manifest is identified by exactly one guard', () => {
|
|
134
|
-
for (const m of allManifests) {
|
|
135
|
-
const signed = mockSigned(m);
|
|
136
|
-
const trueCount = guards.filter(g => g(signed)).length;
|
|
137
|
-
expect(trueCount).toBe(1);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
});
|
package/src/__tests__/id.test.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* id.test.ts
|
|
3
|
-
* Stable IDs: same inputs → same ID; version change → same ID; key field change → different ID.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { computeManifestId } from '../id.js';
|
|
7
|
-
import type {
|
|
8
|
-
AppManifest,
|
|
9
|
-
CapabilityManifest,
|
|
10
|
-
DAppManifest,
|
|
11
|
-
EdgeServiceManifest,
|
|
12
|
-
} from '../types.js';
|
|
13
|
-
|
|
14
|
-
const APP: AppManifest = {
|
|
15
|
-
type: 'app',
|
|
16
|
-
appId: '',
|
|
17
|
-
name: 'Test App',
|
|
18
|
-
version: '1.0.0',
|
|
19
|
-
authorAddress: 'MxAUTHOR1111',
|
|
20
|
-
pearTopicKey: 'aaaa1111',
|
|
21
|
-
price: '0',
|
|
22
|
-
category: ['finance'],
|
|
23
|
-
permissions: ['wallet:read-balance'],
|
|
24
|
-
description: 'Test',
|
|
25
|
-
minTotemVersion: '0.1.0',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const CAP: CapabilityManifest = {
|
|
29
|
-
type: 'capability',
|
|
30
|
-
capabilityId: '',
|
|
31
|
-
capabilityName: 'invoice-translate',
|
|
32
|
-
agentAddress: 'MxAGENT2222',
|
|
33
|
-
agentIdentityKey: 'bbbb2222',
|
|
34
|
-
description: 'Translates',
|
|
35
|
-
inputSchema: {},
|
|
36
|
-
outputSchema: {},
|
|
37
|
-
pricePerCall: '1',
|
|
38
|
-
expiresAt: 9999,
|
|
39
|
-
tags: [],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const DAPP: DAppManifest = {
|
|
43
|
-
type: 'dapp',
|
|
44
|
-
dappId: '',
|
|
45
|
-
name: 'Swap',
|
|
46
|
-
version: '1.0.0',
|
|
47
|
-
authorAddress: 'MxAUTHOR3333',
|
|
48
|
-
contractHash: 'cccc3333',
|
|
49
|
-
abi: [],
|
|
50
|
-
price: '0',
|
|
51
|
-
category: [],
|
|
52
|
-
description: '',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const EDGE: EdgeServiceManifest = {
|
|
56
|
-
type: 'edge-service',
|
|
57
|
-
serviceId: '',
|
|
58
|
-
name: 'My Sensor',
|
|
59
|
-
version: '1.0.0',
|
|
60
|
-
operatorAddress: 'MxOPERATOR4444',
|
|
61
|
-
serviceType: 'sensor',
|
|
62
|
-
description: '',
|
|
63
|
-
capabilities: [],
|
|
64
|
-
tags: [],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
describe('computeManifestId — stable IDs', () => {
|
|
68
|
-
it('returns the same ID for identical AppManifest inputs', () => {
|
|
69
|
-
expect(computeManifestId(APP)).toBe(computeManifestId({ ...APP }));
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('AppManifest: ID is stable when version changes', () => {
|
|
73
|
-
expect(computeManifestId(APP)).toBe(computeManifestId({ ...APP, version: '2.0.0' }));
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('AppManifest: ID changes when authorAddress changes', () => {
|
|
77
|
-
expect(computeManifestId(APP)).not.toBe(computeManifestId({ ...APP, authorAddress: 'MxOTHER' }));
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('AppManifest: ID changes when pearTopicKey changes', () => {
|
|
81
|
-
expect(computeManifestId(APP)).not.toBe(computeManifestId({ ...APP, pearTopicKey: 'bbbb' }));
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns the same ID for identical CapabilityManifest inputs', () => {
|
|
85
|
-
expect(computeManifestId(CAP)).toBe(computeManifestId({ ...CAP }));
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('CapabilityManifest: ID is stable when expiresAt changes', () => {
|
|
89
|
-
expect(computeManifestId(CAP)).toBe(computeManifestId({ ...CAP, expiresAt: 0 }));
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('CapabilityManifest: ID changes when agentAddress changes', () => {
|
|
93
|
-
expect(computeManifestId(CAP)).not.toBe(computeManifestId({ ...CAP, agentAddress: 'MxOTHER' }));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('CapabilityManifest: ID changes when capabilityName changes', () => {
|
|
97
|
-
expect(computeManifestId(CAP)).not.toBe(computeManifestId({ ...CAP, capabilityName: 'other-capability' }));
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('returns the same ID for identical DAppManifest inputs', () => {
|
|
101
|
-
expect(computeManifestId(DAPP)).toBe(computeManifestId({ ...DAPP }));
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('DAppManifest: ID is stable when version changes', () => {
|
|
105
|
-
expect(computeManifestId(DAPP)).toBe(computeManifestId({ ...DAPP, version: '9.9.9' }));
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('DAppManifest: ID changes when contractHash changes', () => {
|
|
109
|
-
expect(computeManifestId(DAPP)).not.toBe(computeManifestId({ ...DAPP, contractHash: 'dddd' }));
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('returns the same ID for identical EdgeServiceManifest inputs', () => {
|
|
113
|
-
expect(computeManifestId(EDGE)).toBe(computeManifestId({ ...EDGE }));
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('EdgeServiceManifest: ID is stable when version changes', () => {
|
|
117
|
-
expect(computeManifestId(EDGE)).toBe(computeManifestId({ ...EDGE, version: '9.0.0' }));
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('EdgeServiceManifest: ID changes when serviceType changes', () => {
|
|
121
|
-
expect(computeManifestId(EDGE)).not.toBe(computeManifestId({ ...EDGE, serviceType: 'robot' }));
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('EdgeServiceManifest: ID changes when name changes', () => {
|
|
125
|
-
expect(computeManifestId(EDGE)).not.toBe(computeManifestId({ ...EDGE, name: 'Other Sensor' }));
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('IDs are 64-character hex strings (SHA3-256)', () => {
|
|
129
|
-
const id = computeManifestId(APP);
|
|
130
|
-
expect(id).toMatch(/^[0-9a-f]{64}$/);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('IDs differ across manifest types', () => {
|
|
134
|
-
const ids = [
|
|
135
|
-
computeManifestId(APP),
|
|
136
|
-
computeManifestId(CAP),
|
|
137
|
-
computeManifestId(DAPP),
|
|
138
|
-
computeManifestId(EDGE),
|
|
139
|
-
];
|
|
140
|
-
const unique = new Set(ids);
|
|
141
|
-
expect(unique.size).toBe(4);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sign-verify.test.ts
|
|
3
|
-
* Round-trip sign → verify for all four manifest types.
|
|
4
|
-
* Also tests tamper detection.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { signManifest } from '../sign.js';
|
|
8
|
-
import { verifyManifest } from '../verify.js';
|
|
9
|
-
import type {
|
|
10
|
-
AppManifest,
|
|
11
|
-
CapabilityManifest,
|
|
12
|
-
DAppManifest,
|
|
13
|
-
EdgeServiceManifest,
|
|
14
|
-
} from '../types.js';
|
|
15
|
-
import { computeManifestId } from '../id.js';
|
|
16
|
-
|
|
17
|
-
jest.setTimeout(60_000);
|
|
18
|
-
|
|
19
|
-
const TEST_SEED = new Uint8Array(32).fill(0x42);
|
|
20
|
-
const KEY_INDEX = 0;
|
|
21
|
-
|
|
22
|
-
const APP: AppManifest = {
|
|
23
|
-
type: 'app',
|
|
24
|
-
appId: '',
|
|
25
|
-
name: 'Test App',
|
|
26
|
-
version: '1.0.0',
|
|
27
|
-
authorAddress: 'MxAUTHOR0000000000000000000000000000000000000000',
|
|
28
|
-
pearTopicKey: 'a'.repeat(64),
|
|
29
|
-
price: '0',
|
|
30
|
-
category: ['finance'],
|
|
31
|
-
permissions: ['wallet:read-balance'],
|
|
32
|
-
description: 'A test app',
|
|
33
|
-
minTotemVersion: '0.1.0',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const CAPABILITY: CapabilityManifest = {
|
|
37
|
-
type: 'capability',
|
|
38
|
-
capabilityId: '',
|
|
39
|
-
capabilityName: 'invoice-translate',
|
|
40
|
-
agentAddress: 'MxAGENT0000000000000000000000000000000000000000',
|
|
41
|
-
agentIdentityKey: 'b'.repeat(64),
|
|
42
|
-
description: 'Translates invoices',
|
|
43
|
-
inputSchema: { type: 'object' },
|
|
44
|
-
outputSchema: { type: 'object' },
|
|
45
|
-
pricePerCall: '1',
|
|
46
|
-
expiresAt: Date.now() + 86_400_000,
|
|
47
|
-
tags: ['finance'],
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const DAPP: DAppManifest = {
|
|
51
|
-
type: 'dapp',
|
|
52
|
-
dappId: '',
|
|
53
|
-
name: 'Test dApp',
|
|
54
|
-
version: '1.0.0',
|
|
55
|
-
authorAddress: 'MxAUTHOR0000000000000000000000000000000000000000',
|
|
56
|
-
contractHash: 'c'.repeat(64),
|
|
57
|
-
abi: [{ name: 'transfer', description: 'Transfer tokens', params: [{ name: 'amount', type: 'string' }] }],
|
|
58
|
-
price: '0',
|
|
59
|
-
category: ['defi'],
|
|
60
|
-
description: 'A test dApp',
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const EDGE: EdgeServiceManifest = {
|
|
64
|
-
type: 'edge-service',
|
|
65
|
-
serviceId: '',
|
|
66
|
-
name: 'Test Sensor',
|
|
67
|
-
version: '1.0.0',
|
|
68
|
-
operatorAddress: 'MxOPERATOR000000000000000000000000000000000000',
|
|
69
|
-
serviceType: 'sensor',
|
|
70
|
-
description: 'A test sensor node',
|
|
71
|
-
capabilities: ['temperature'],
|
|
72
|
-
tags: ['iot'],
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
function withId<T extends { type: string }>(m: T): T {
|
|
76
|
-
return m;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
describe('signManifest + verifyManifest round-trips', () => {
|
|
80
|
-
let signedAddress: string;
|
|
81
|
-
|
|
82
|
-
beforeAll(async () => {
|
|
83
|
-
const tmp = await signManifest(APP, TEST_SEED, KEY_INDEX);
|
|
84
|
-
signedAddress = tmp.authorAddress;
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('signs and verifies AppManifest', async () => {
|
|
88
|
-
const app = { ...APP, authorAddress: signedAddress };
|
|
89
|
-
const signed = await signManifest(app, TEST_SEED, KEY_INDEX);
|
|
90
|
-
expect(signed.manifest.type).toBe('app');
|
|
91
|
-
expect(signed.authorAddress).toBe(signedAddress);
|
|
92
|
-
expect(signed.signature).toBeTruthy();
|
|
93
|
-
expect(signed.signerPublicKey).toBeTruthy();
|
|
94
|
-
|
|
95
|
-
const result = verifyManifest(signed);
|
|
96
|
-
expect(result.valid).toBe(true);
|
|
97
|
-
expect(result.signerAddress).toBe(signedAddress);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('signs and verifies CapabilityManifest', async () => {
|
|
101
|
-
const cap = { ...CAPABILITY, agentAddress: signedAddress };
|
|
102
|
-
const signed = await signManifest(cap, TEST_SEED, KEY_INDEX);
|
|
103
|
-
expect(signed.manifest.type).toBe('capability');
|
|
104
|
-
|
|
105
|
-
const result = verifyManifest(signed);
|
|
106
|
-
expect(result.valid).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('signs and verifies DAppManifest', async () => {
|
|
110
|
-
const dapp = { ...DAPP, authorAddress: signedAddress };
|
|
111
|
-
const signed = await signManifest(dapp, TEST_SEED, KEY_INDEX);
|
|
112
|
-
expect(signed.manifest.type).toBe('dapp');
|
|
113
|
-
|
|
114
|
-
const result = verifyManifest(signed);
|
|
115
|
-
expect(result.valid).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('signs and verifies EdgeServiceManifest', async () => {
|
|
119
|
-
const edge = { ...EDGE, operatorAddress: signedAddress };
|
|
120
|
-
const signed = await signManifest(edge, TEST_SEED, KEY_INDEX);
|
|
121
|
-
expect(signed.manifest.type).toBe('edge-service');
|
|
122
|
-
|
|
123
|
-
const result = verifyManifest(signed);
|
|
124
|
-
expect(result.valid).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('detects manifest tampering — changed name field', async () => {
|
|
128
|
-
const app = { ...APP, authorAddress: signedAddress };
|
|
129
|
-
const signed = await signManifest(app, TEST_SEED, KEY_INDEX);
|
|
130
|
-
|
|
131
|
-
const tampered = {
|
|
132
|
-
...signed,
|
|
133
|
-
manifest: { ...signed.manifest, name: 'EVIL APP' } as AppManifest,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const result = verifyManifest(tampered);
|
|
137
|
-
expect(result.valid).toBe(false);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('detects tampered authorAddress in SignedManifest wrapper', async () => {
|
|
141
|
-
const app = { ...APP, authorAddress: signedAddress };
|
|
142
|
-
const signed = await signManifest(app, TEST_SEED, KEY_INDEX);
|
|
143
|
-
|
|
144
|
-
const tampered = { ...signed, authorAddress: 'Mx999999999999999999' };
|
|
145
|
-
const result = verifyManifest(tampered);
|
|
146
|
-
expect(result.valid).toBe(false);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('detects corrupted signature bytes', async () => {
|
|
150
|
-
const app = { ...APP, authorAddress: signedAddress };
|
|
151
|
-
const signed = await signManifest(app, TEST_SEED, KEY_INDEX);
|
|
152
|
-
|
|
153
|
-
const corrupted = { ...signed, signature: 'ff' + signed.signature.slice(2) };
|
|
154
|
-
const result = verifyManifest(corrupted);
|
|
155
|
-
expect(result.valid).toBe(false);
|
|
156
|
-
});
|
|
157
|
-
});
|
package/src/constants.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const MANIFEST_VERSION = 1 as const;
|
|
2
|
-
|
|
3
|
-
export const MANIFEST_TYPE_BYTE = {
|
|
4
|
-
app: 0x01,
|
|
5
|
-
capability: 0x02,
|
|
6
|
-
dapp: 0x03,
|
|
7
|
-
'edge-service': 0x04,
|
|
8
|
-
} as const satisfies Record<string, number>;
|
|
9
|
-
|
|
10
|
-
export const MANIFEST_BYTE_TO_TYPE: Record<number, string> = {
|
|
11
|
-
0x01: 'app',
|
|
12
|
-
0x02: 'capability',
|
|
13
|
-
0x03: 'dapp',
|
|
14
|
-
0x04: 'edge-service',
|
|
15
|
-
};
|
package/src/encoding.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire encoding/decoding for SignedManifest.
|
|
3
|
-
*
|
|
4
|
-
* Format:
|
|
5
|
-
* [1 byte MANIFEST_VERSION ]
|
|
6
|
-
* [1 byte type discriminant] 0x01=app 0x02=capability 0x03=dapp 0x04=edge-service
|
|
7
|
-
* [4 bytes big-endian JSON length]
|
|
8
|
-
* [N bytes canonical JSON (UTF-8)]
|
|
9
|
-
* [remaining bytes = WOTS signature]
|
|
10
|
-
*
|
|
11
|
-
* The JSON payload is the full SignedManifest object (manifest + metadata + signature).
|
|
12
|
-
* The trailing WOTS signature bytes are stored separately for easy extraction by
|
|
13
|
-
* the DHT / lookup-protocol layer without needing to parse the JSON.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { MANIFEST_VERSION, MANIFEST_TYPE_BYTE, MANIFEST_BYTE_TO_TYPE } from './constants.js';
|
|
17
|
-
import type { SignedManifest, Manifest } from './types.js';
|
|
18
|
-
|
|
19
|
-
function manifestTypeByte(type: Manifest['type']): number {
|
|
20
|
-
const byte = MANIFEST_TYPE_BYTE[type];
|
|
21
|
-
if (byte === undefined) {
|
|
22
|
-
throw new Error(`encodeManifest: unknown manifest type: ${type}`);
|
|
23
|
-
}
|
|
24
|
-
return byte;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function encodeManifest(signed: SignedManifest): Uint8Array {
|
|
28
|
-
const enc = new TextEncoder();
|
|
29
|
-
const json = JSON.stringify(signed);
|
|
30
|
-
const jsonBytes = enc.encode(json);
|
|
31
|
-
const sigBytes = hexToBytes(signed.signature);
|
|
32
|
-
|
|
33
|
-
const len = jsonBytes.length;
|
|
34
|
-
const out = new Uint8Array(1 + 1 + 4 + len + sigBytes.length);
|
|
35
|
-
let offset = 0;
|
|
36
|
-
|
|
37
|
-
out[offset++] = MANIFEST_VERSION;
|
|
38
|
-
out[offset++] = manifestTypeByte(signed.manifest.type);
|
|
39
|
-
out[offset++] = (len >>> 24) & 0xff;
|
|
40
|
-
out[offset++] = (len >>> 16) & 0xff;
|
|
41
|
-
out[offset++] = (len >>> 8) & 0xff;
|
|
42
|
-
out[offset++] = len & 0xff;
|
|
43
|
-
out.set(jsonBytes, offset);
|
|
44
|
-
offset += len;
|
|
45
|
-
out.set(sigBytes, offset);
|
|
46
|
-
|
|
47
|
-
return out;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function decodeManifest(bytes: Uint8Array): SignedManifest {
|
|
51
|
-
if (bytes.length < 6) {
|
|
52
|
-
throw new Error('decodeManifest: buffer too short (< 6 bytes)');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const version = bytes[0];
|
|
56
|
-
if (version !== MANIFEST_VERSION) {
|
|
57
|
-
throw new Error(`decodeManifest: unsupported MANIFEST_VERSION ${version}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const typeByte = bytes[1];
|
|
61
|
-
const expectedType = MANIFEST_BYTE_TO_TYPE[typeByte];
|
|
62
|
-
if (!expectedType) {
|
|
63
|
-
throw new Error(`decodeManifest: unknown type discriminant 0x${typeByte.toString(16).padStart(2, '0')}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const jsonLen =
|
|
67
|
-
(bytes[2] << 24) | (bytes[3] << 16) | (bytes[4] << 8) | bytes[5];
|
|
68
|
-
|
|
69
|
-
if (bytes.length < 6 + jsonLen) {
|
|
70
|
-
throw new Error(`decodeManifest: buffer too short for declared JSON length ${jsonLen}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const jsonBytes = bytes.slice(6, 6 + jsonLen);
|
|
74
|
-
const json = new TextDecoder().decode(jsonBytes);
|
|
75
|
-
|
|
76
|
-
let signed: SignedManifest;
|
|
77
|
-
try {
|
|
78
|
-
signed = JSON.parse(json) as SignedManifest;
|
|
79
|
-
} catch (e) {
|
|
80
|
-
throw new Error(`decodeManifest: invalid JSON payload: ${String(e)}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (signed.manifest?.type !== expectedType) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`decodeManifest: type discriminant mismatch — wire says '${expectedType}' but JSON manifest.type is '${signed.manifest?.type}'`,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return signed;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function hexToBytes(hex: string): Uint8Array {
|
|
93
|
-
if (hex.startsWith('0x')) hex = hex.slice(2);
|
|
94
|
-
if (hex.length % 2 !== 0) throw new Error('hexToBytes: odd-length hex string');
|
|
95
|
-
const out = new Uint8Array(hex.length / 2);
|
|
96
|
-
for (let i = 0; i < out.length; i++) {
|
|
97
|
-
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
98
|
-
}
|
|
99
|
-
return out;
|
|
100
|
-
}
|
package/src/guards.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type guards for manifest kinds.
|
|
3
|
-
* Each guard accepts a raw Manifest or a SignedManifest.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
Manifest,
|
|
8
|
-
SignedManifest,
|
|
9
|
-
AppManifest,
|
|
10
|
-
CapabilityManifest,
|
|
11
|
-
DAppManifest,
|
|
12
|
-
EdgeServiceManifest,
|
|
13
|
-
} from './types.js';
|
|
14
|
-
|
|
15
|
-
type MaybeSignedOrRaw = Manifest | SignedManifest | null | undefined;
|
|
16
|
-
|
|
17
|
-
function getRawManifest(input: MaybeSignedOrRaw): Manifest | null {
|
|
18
|
-
if (!input || typeof input !== 'object') return null;
|
|
19
|
-
if ('manifest' in input && input.manifest && typeof input.manifest === 'object') {
|
|
20
|
-
return (input as SignedManifest).manifest;
|
|
21
|
-
}
|
|
22
|
-
if ('type' in input) return input as Manifest;
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function isAppManifest(
|
|
27
|
-
input: MaybeSignedOrRaw,
|
|
28
|
-
): input is AppManifest | SignedManifest<AppManifest> {
|
|
29
|
-
return getRawManifest(input)?.type === 'app';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function isCapabilityManifest(
|
|
33
|
-
input: MaybeSignedOrRaw,
|
|
34
|
-
): input is CapabilityManifest | SignedManifest<CapabilityManifest> {
|
|
35
|
-
return getRawManifest(input)?.type === 'capability';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function isDAppManifest(
|
|
39
|
-
input: MaybeSignedOrRaw,
|
|
40
|
-
): input is DAppManifest | SignedManifest<DAppManifest> {
|
|
41
|
-
return getRawManifest(input)?.type === 'dapp';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function isEdgeServiceManifest(
|
|
45
|
-
input: MaybeSignedOrRaw,
|
|
46
|
-
): input is EdgeServiceManifest | SignedManifest<EdgeServiceManifest> {
|
|
47
|
-
return getRawManifest(input)?.type === 'edge-service';
|
|
48
|
-
}
|
package/src/id.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* computeManifestId — deterministic stable ID for any manifest.
|
|
3
|
-
*
|
|
4
|
-
* The ID is SHA3-256 over a type-specific stable key string.
|
|
5
|
-
* It must be stable across version bumps (version is NOT part of the input).
|
|
6
|
-
*
|
|
7
|
-
* Stable key rules:
|
|
8
|
-
* AppManifest → "app" + authorAddress + pearTopicKey
|
|
9
|
-
* CapabilityManifest → "capability" + agentAddress + capabilityName
|
|
10
|
-
* DAppManifest → "dapp" + authorAddress + contractHash
|
|
11
|
-
* EdgeServiceManifest → "edge-service" + operatorAddress + serviceType + name
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { sha3_256 } from '@noble/hashes/sha3.js';
|
|
15
|
-
import type { Manifest } from './types.js';
|
|
16
|
-
|
|
17
|
-
function bytesToHex(b: Uint8Array): string {
|
|
18
|
-
return Array.from(b)
|
|
19
|
-
.map((x) => x.toString(16).padStart(2, '0'))
|
|
20
|
-
.join('');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function computeManifestId(manifest: Manifest): string {
|
|
24
|
-
let stableKey: string;
|
|
25
|
-
|
|
26
|
-
switch (manifest.type) {
|
|
27
|
-
case 'app':
|
|
28
|
-
stableKey = `app\0${manifest.authorAddress}\0${manifest.pearTopicKey}`;
|
|
29
|
-
break;
|
|
30
|
-
case 'capability':
|
|
31
|
-
stableKey = `capability\0${manifest.agentAddress}\0${manifest.capabilityName}`;
|
|
32
|
-
break;
|
|
33
|
-
case 'dapp':
|
|
34
|
-
stableKey = `dapp\0${manifest.authorAddress}\0${manifest.contractHash}`;
|
|
35
|
-
break;
|
|
36
|
-
case 'edge-service':
|
|
37
|
-
stableKey = `edge-service\0${manifest.operatorAddress}\0${manifest.serviceType}\0${manifest.name}`;
|
|
38
|
-
break;
|
|
39
|
-
default: {
|
|
40
|
-
const _exhaustive: never = manifest;
|
|
41
|
-
throw new Error(`computeManifestId: unknown manifest type: ${JSON.stringify(_exhaustive)}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return bytesToHex(sha3_256(new TextEncoder().encode(stableKey)));
|
|
46
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
AppManifest,
|
|
3
|
-
CapabilityManifest,
|
|
4
|
-
DAppManifest,
|
|
5
|
-
EdgeServiceManifest,
|
|
6
|
-
EdgeServiceType,
|
|
7
|
-
Manifest,
|
|
8
|
-
SignedManifest,
|
|
9
|
-
AppPermission,
|
|
10
|
-
DAppAbiEntry,
|
|
11
|
-
VerifyResult,
|
|
12
|
-
} from './types.js';
|
|
13
|
-
|
|
14
|
-
export { MANIFEST_VERSION } from './constants.js';
|
|
15
|
-
|
|
16
|
-
export { computeManifestId } from './id.js';
|
|
17
|
-
export { signManifest } from './sign.js';
|
|
18
|
-
export { verifyManifest } from './verify.js';
|
|
19
|
-
export { encodeManifest, decodeManifest } from './encoding.js';
|
|
20
|
-
|
|
21
|
-
export {
|
|
22
|
-
isAppManifest,
|
|
23
|
-
isCapabilityManifest,
|
|
24
|
-
isDAppManifest,
|
|
25
|
-
isEdgeServiceManifest,
|
|
26
|
-
} from './guards.js';
|