@totemsdk/manifest 0.1.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/README.md +223 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +13 -0
- package/dist/encoding.d.ts +17 -0
- package/dist/encoding.js +83 -0
- package/dist/guards.d.ts +11 -0
- package/dist/guards.js +26 -0
- package/dist/id.d.ts +14 -0
- package/dist/id.js +40 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/sign.d.ts +19 -0
- package/dist/sign.js +48 -0
- package/dist/types.d.ts +113 -0
- package/dist/types.js +12 -0
- package/dist/verify.d.ts +15 -0
- package/dist/verify.js +59 -0
- package/package.json +49 -0
- package/src/__tests__/encoding.test.ts +185 -0
- package/src/__tests__/guards.test.ts +140 -0
- package/src/__tests__/id.test.ts +143 -0
- package/src/__tests__/sign-verify.test.ts +157 -0
- package/src/constants.ts +15 -0
- package/src/encoding.ts +100 -0
- package/src/guards.ts +48 -0
- package/src/id.ts +46 -0
- package/src/index.ts +26 -0
- package/src/sign.ts +63 -0
- package/src/types.ts +140 -0
- package/src/verify.ts +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# @totemsdk/manifest
|
|
2
|
+
|
|
3
|
+
**Canonical signed declarations for every Totem Edge entity.**
|
|
4
|
+
|
|
5
|
+
Defines the schema, deterministic IDs, wire encoding, WOTS signing, and type guards for all manifest types in the Totem ecosystem. Every entity that wants to be discovered, paid, or trusted — an app, an AI agent capability, a smart contract, or a persistent edge service — is represented as a `SignedManifest`.
|
|
6
|
+
|
|
7
|
+
No network. No blockchain. No Hyperswarm. Pure schema + cryptographic binding.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @totemsdk/manifest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What's inside
|
|
16
|
+
|
|
17
|
+
| Type | Role |
|
|
18
|
+
|------|------|
|
|
19
|
+
| `AppManifest` | A human-facing Pear app — name, version, permissions, pricing, install topic key |
|
|
20
|
+
| `CapabilityManifest` | An ephemeral AI/agent service — input/output schema, per-call pricing, expiry |
|
|
21
|
+
| `DAppManifest` | A KISSVM smart contract / covenant — contract hash, ABI, audit report |
|
|
22
|
+
| `EdgeServiceManifest` | Any persistent Totem Edge service — sensors, robots, MQTT feeds, Omnia routers, lookup providers, proof indexers, and more |
|
|
23
|
+
| `SignedManifest<T>` | Wraps any manifest with a WOTS signature and signer address |
|
|
24
|
+
| `VerifyResult` | Result of `verifyManifest` — `valid`, optional `reason`, `signerAddress` |
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Sign a manifest
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { signManifest } from '@totemsdk/manifest';
|
|
32
|
+
import type { AppManifest } from '@totemsdk/manifest';
|
|
33
|
+
|
|
34
|
+
const app: AppManifest = {
|
|
35
|
+
type: 'app',
|
|
36
|
+
appId: computeManifestId(app), // fill after construction
|
|
37
|
+
name: 'My dApp',
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
authorAddress: 'MxABC123...',
|
|
40
|
+
pearTopicKey: 'a1b2c3...', // 32-byte hex — Hyperswarm install topic
|
|
41
|
+
price: '0',
|
|
42
|
+
category: ['finance'],
|
|
43
|
+
permissions: ['wallet:read-balance', 'wallet:request-payment'],
|
|
44
|
+
description: 'My first Totem app',
|
|
45
|
+
minTotemVersion: '0.1.0',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// seed = your 32-byte WOTS seed; keyIndex = pre-reserved key index
|
|
49
|
+
const signed = await signManifest(app, seed, keyIndex);
|
|
50
|
+
// signed.authorAddress — the Minima address of the signer
|
|
51
|
+
// signed.signature — WOTS signature hex
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Verify a manifest
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { verifyManifest } from '@totemsdk/manifest';
|
|
58
|
+
|
|
59
|
+
const result = verifyManifest(signed);
|
|
60
|
+
if (!result.valid) {
|
|
61
|
+
console.error('Invalid manifest:', result.reason);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Compute a stable manifest ID
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { computeManifestId } from '@totemsdk/manifest';
|
|
69
|
+
|
|
70
|
+
// ID is SHA3-256 over stable key fields — does not change when version changes
|
|
71
|
+
const id = computeManifestId(app);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Wire encode / decode
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { encodeManifest, decodeManifest } from '@totemsdk/manifest';
|
|
78
|
+
|
|
79
|
+
// Encode to Uint8Array for DHT / lookup-protocol embedding
|
|
80
|
+
const bytes = encodeManifest(signed);
|
|
81
|
+
|
|
82
|
+
// Decode back from bytes (validates type discriminant and version)
|
|
83
|
+
const decoded = decodeManifest(bytes);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Wire format: `[1 version][1 type][4-byte JSON length][JSON payload][WOTS signature]`
|
|
87
|
+
|
|
88
|
+
### Type guards
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import {
|
|
92
|
+
isAppManifest,
|
|
93
|
+
isCapabilityManifest,
|
|
94
|
+
isDAppManifest,
|
|
95
|
+
isEdgeServiceManifest,
|
|
96
|
+
} from '@totemsdk/manifest';
|
|
97
|
+
|
|
98
|
+
// Accept raw Manifest or SignedManifest<T>
|
|
99
|
+
if (isCapabilityManifest(signed)) {
|
|
100
|
+
console.log(signed.manifest.capabilityName);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Manifest types
|
|
105
|
+
|
|
106
|
+
### `AppManifest`
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface AppManifest {
|
|
110
|
+
type: 'app';
|
|
111
|
+
appId: string; // SHA3-256(authorAddress + pearTopicKey)
|
|
112
|
+
name: string;
|
|
113
|
+
version: string; // semver
|
|
114
|
+
authorAddress: string; // Minima address
|
|
115
|
+
pearTopicKey: string; // 32-byte hex — Hyperswarm install topic
|
|
116
|
+
price: string; // '0' for free; Minima amount for paid
|
|
117
|
+
priceToken?: string;
|
|
118
|
+
subscriptionInterval?: number;
|
|
119
|
+
category: string[];
|
|
120
|
+
permissions: AppPermission[];
|
|
121
|
+
iconCid?: string;
|
|
122
|
+
description: string;
|
|
123
|
+
repoUrl?: string;
|
|
124
|
+
minTotemVersion: string;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### `CapabilityManifest`
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
interface CapabilityManifest {
|
|
132
|
+
type: 'capability';
|
|
133
|
+
capabilityId: string; // SHA3-256(agentAddress + capabilityName)
|
|
134
|
+
capabilityName: string;
|
|
135
|
+
agentAddress: string;
|
|
136
|
+
agentIdentityKey: string; // ed25519 pubkey for auth challenge/response
|
|
137
|
+
description: string;
|
|
138
|
+
inputSchema: object; // JSON Schema
|
|
139
|
+
outputSchema: object; // JSON Schema
|
|
140
|
+
pricePerCall: string;
|
|
141
|
+
priceToken?: string;
|
|
142
|
+
paymentChannel?: 'omnia' | 'onchain';
|
|
143
|
+
maxLatencyMs?: number;
|
|
144
|
+
maxCallsPerMinute?: number;
|
|
145
|
+
expiresAt: number; // ms — must be re-announced
|
|
146
|
+
tags: string[];
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `DAppManifest`
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface DAppManifest {
|
|
154
|
+
type: 'dapp';
|
|
155
|
+
dappId: string; // SHA3-256(authorAddress + contractHash)
|
|
156
|
+
name: string;
|
|
157
|
+
version: string;
|
|
158
|
+
authorAddress: string;
|
|
159
|
+
contractHash: string; // SHA3-256 of the KISSVM script
|
|
160
|
+
contractSource?: string;
|
|
161
|
+
abi: DAppAbiEntry[];
|
|
162
|
+
price: string;
|
|
163
|
+
priceToken?: string;
|
|
164
|
+
category: string[];
|
|
165
|
+
description: string;
|
|
166
|
+
auditReport?: string; // CID of a simulation audit result
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### `EdgeServiceManifest`
|
|
171
|
+
|
|
172
|
+
Covers all persistent Totem Edge services that are not apps, capabilities, or dApps.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
type EdgeServiceType =
|
|
176
|
+
| 'sensor'
|
|
177
|
+
| 'robot'
|
|
178
|
+
| 'mqtt-feed'
|
|
179
|
+
| 'proof-index'
|
|
180
|
+
| 'lookup-provider'
|
|
181
|
+
| 'omnia-router'
|
|
182
|
+
| 'calibration-authority'
|
|
183
|
+
| 'verifier'
|
|
184
|
+
| 'machine-service'
|
|
185
|
+
| 'other';
|
|
186
|
+
|
|
187
|
+
interface EdgeServiceManifest {
|
|
188
|
+
type: 'edge-service';
|
|
189
|
+
serviceId: string; // SHA3-256(operatorAddress + serviceType + name)
|
|
190
|
+
name: string;
|
|
191
|
+
version: string;
|
|
192
|
+
operatorAddress: string;
|
|
193
|
+
serviceType: EdgeServiceType;
|
|
194
|
+
description: string;
|
|
195
|
+
endpoints?: Array<{
|
|
196
|
+
type: 'https' | 'mqtt' | 'hyperswarm' | 'websocket' | 'other';
|
|
197
|
+
uri: string;
|
|
198
|
+
}>;
|
|
199
|
+
capabilities: string[];
|
|
200
|
+
price?: string;
|
|
201
|
+
priceToken?: string;
|
|
202
|
+
paymentMethods?: Array<'omnia' | 'onchain' | 'invoice' | 'free'>;
|
|
203
|
+
tags: string[];
|
|
204
|
+
expiresAt?: number;
|
|
205
|
+
minTotemVersion?: string;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Wire type discriminants
|
|
210
|
+
|
|
211
|
+
| Type | Byte |
|
|
212
|
+
|------|------|
|
|
213
|
+
| `app` | `0x01` |
|
|
214
|
+
| `capability` | `0x02` |
|
|
215
|
+
| `dapp` | `0x03` |
|
|
216
|
+
| `edge-service` | `0x04` |
|
|
217
|
+
|
|
218
|
+
## See also
|
|
219
|
+
|
|
220
|
+
- [`@totemsdk/core`](../core) — WOTS signing primitives used internally
|
|
221
|
+
- [`@totemsdk/lookup-protocol`](../lookup-protocol) — embeds `encodeManifest()` output in `APP_ANNOUNCE` / `AGENT_ANNOUNCE` messages
|
|
222
|
+
- [`@totemsdk/wots-lease`](../wots-lease) — manage WOTS key index reservation before calling `signManifest`
|
|
223
|
+
- [`@totemsdk/root-identity`](../root-identity) — optional `rootIdentityProof` field for chain-of-custody linking
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const MANIFEST_VERSION = 1;
|
|
2
|
+
export const MANIFEST_TYPE_BYTE = {
|
|
3
|
+
app: 0x01,
|
|
4
|
+
capability: 0x02,
|
|
5
|
+
dapp: 0x03,
|
|
6
|
+
'edge-service': 0x04,
|
|
7
|
+
};
|
|
8
|
+
export const MANIFEST_BYTE_TO_TYPE = {
|
|
9
|
+
0x01: 'app',
|
|
10
|
+
0x02: 'capability',
|
|
11
|
+
0x03: 'dapp',
|
|
12
|
+
0x04: 'edge-service',
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
import type { SignedManifest } from './types.js';
|
|
16
|
+
export declare function encodeManifest(signed: SignedManifest): Uint8Array;
|
|
17
|
+
export declare function decodeManifest(bytes: Uint8Array): SignedManifest;
|
package/dist/encoding.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
import { MANIFEST_VERSION, MANIFEST_TYPE_BYTE, MANIFEST_BYTE_TO_TYPE } from './constants.js';
|
|
16
|
+
function manifestTypeByte(type) {
|
|
17
|
+
const byte = MANIFEST_TYPE_BYTE[type];
|
|
18
|
+
if (byte === undefined) {
|
|
19
|
+
throw new Error(`encodeManifest: unknown manifest type: ${type}`);
|
|
20
|
+
}
|
|
21
|
+
return byte;
|
|
22
|
+
}
|
|
23
|
+
export function encodeManifest(signed) {
|
|
24
|
+
const enc = new TextEncoder();
|
|
25
|
+
const json = JSON.stringify(signed);
|
|
26
|
+
const jsonBytes = enc.encode(json);
|
|
27
|
+
const sigBytes = hexToBytes(signed.signature);
|
|
28
|
+
const len = jsonBytes.length;
|
|
29
|
+
const out = new Uint8Array(1 + 1 + 4 + len + sigBytes.length);
|
|
30
|
+
let offset = 0;
|
|
31
|
+
out[offset++] = MANIFEST_VERSION;
|
|
32
|
+
out[offset++] = manifestTypeByte(signed.manifest.type);
|
|
33
|
+
out[offset++] = (len >>> 24) & 0xff;
|
|
34
|
+
out[offset++] = (len >>> 16) & 0xff;
|
|
35
|
+
out[offset++] = (len >>> 8) & 0xff;
|
|
36
|
+
out[offset++] = len & 0xff;
|
|
37
|
+
out.set(jsonBytes, offset);
|
|
38
|
+
offset += len;
|
|
39
|
+
out.set(sigBytes, offset);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
export function decodeManifest(bytes) {
|
|
43
|
+
if (bytes.length < 6) {
|
|
44
|
+
throw new Error('decodeManifest: buffer too short (< 6 bytes)');
|
|
45
|
+
}
|
|
46
|
+
const version = bytes[0];
|
|
47
|
+
if (version !== MANIFEST_VERSION) {
|
|
48
|
+
throw new Error(`decodeManifest: unsupported MANIFEST_VERSION ${version}`);
|
|
49
|
+
}
|
|
50
|
+
const typeByte = bytes[1];
|
|
51
|
+
const expectedType = MANIFEST_BYTE_TO_TYPE[typeByte];
|
|
52
|
+
if (!expectedType) {
|
|
53
|
+
throw new Error(`decodeManifest: unknown type discriminant 0x${typeByte.toString(16).padStart(2, '0')}`);
|
|
54
|
+
}
|
|
55
|
+
const jsonLen = (bytes[2] << 24) | (bytes[3] << 16) | (bytes[4] << 8) | bytes[5];
|
|
56
|
+
if (bytes.length < 6 + jsonLen) {
|
|
57
|
+
throw new Error(`decodeManifest: buffer too short for declared JSON length ${jsonLen}`);
|
|
58
|
+
}
|
|
59
|
+
const jsonBytes = bytes.slice(6, 6 + jsonLen);
|
|
60
|
+
const json = new TextDecoder().decode(jsonBytes);
|
|
61
|
+
let signed;
|
|
62
|
+
try {
|
|
63
|
+
signed = JSON.parse(json);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
throw new Error(`decodeManifest: invalid JSON payload: ${String(e)}`);
|
|
67
|
+
}
|
|
68
|
+
if (signed.manifest?.type !== expectedType) {
|
|
69
|
+
throw new Error(`decodeManifest: type discriminant mismatch — wire says '${expectedType}' but JSON manifest.type is '${signed.manifest?.type}'`);
|
|
70
|
+
}
|
|
71
|
+
return signed;
|
|
72
|
+
}
|
|
73
|
+
function hexToBytes(hex) {
|
|
74
|
+
if (hex.startsWith('0x'))
|
|
75
|
+
hex = hex.slice(2);
|
|
76
|
+
if (hex.length % 2 !== 0)
|
|
77
|
+
throw new Error('hexToBytes: odd-length hex string');
|
|
78
|
+
const out = new Uint8Array(hex.length / 2);
|
|
79
|
+
for (let i = 0; i < out.length; i++) {
|
|
80
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
package/dist/guards.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guards for manifest kinds.
|
|
3
|
+
* Each guard accepts a raw Manifest or a SignedManifest.
|
|
4
|
+
*/
|
|
5
|
+
import type { Manifest, SignedManifest, AppManifest, CapabilityManifest, DAppManifest, EdgeServiceManifest } from './types.js';
|
|
6
|
+
type MaybeSignedOrRaw = Manifest | SignedManifest | null | undefined;
|
|
7
|
+
export declare function isAppManifest(input: MaybeSignedOrRaw): input is AppManifest | SignedManifest<AppManifest>;
|
|
8
|
+
export declare function isCapabilityManifest(input: MaybeSignedOrRaw): input is CapabilityManifest | SignedManifest<CapabilityManifest>;
|
|
9
|
+
export declare function isDAppManifest(input: MaybeSignedOrRaw): input is DAppManifest | SignedManifest<DAppManifest>;
|
|
10
|
+
export declare function isEdgeServiceManifest(input: MaybeSignedOrRaw): input is EdgeServiceManifest | SignedManifest<EdgeServiceManifest>;
|
|
11
|
+
export {};
|
package/dist/guards.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guards for manifest kinds.
|
|
3
|
+
* Each guard accepts a raw Manifest or a SignedManifest.
|
|
4
|
+
*/
|
|
5
|
+
function getRawManifest(input) {
|
|
6
|
+
if (!input || typeof input !== 'object')
|
|
7
|
+
return null;
|
|
8
|
+
if ('manifest' in input && input.manifest && typeof input.manifest === 'object') {
|
|
9
|
+
return input.manifest;
|
|
10
|
+
}
|
|
11
|
+
if ('type' in input)
|
|
12
|
+
return input;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
export function isAppManifest(input) {
|
|
16
|
+
return getRawManifest(input)?.type === 'app';
|
|
17
|
+
}
|
|
18
|
+
export function isCapabilityManifest(input) {
|
|
19
|
+
return getRawManifest(input)?.type === 'capability';
|
|
20
|
+
}
|
|
21
|
+
export function isDAppManifest(input) {
|
|
22
|
+
return getRawManifest(input)?.type === 'dapp';
|
|
23
|
+
}
|
|
24
|
+
export function isEdgeServiceManifest(input) {
|
|
25
|
+
return getRawManifest(input)?.type === 'edge-service';
|
|
26
|
+
}
|
package/dist/id.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
import type { Manifest } from './types.js';
|
|
14
|
+
export declare function computeManifestId(manifest: Manifest): string;
|
package/dist/id.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
import { sha3_256 } from '@noble/hashes/sha3.js';
|
|
14
|
+
function bytesToHex(b) {
|
|
15
|
+
return Array.from(b)
|
|
16
|
+
.map((x) => x.toString(16).padStart(2, '0'))
|
|
17
|
+
.join('');
|
|
18
|
+
}
|
|
19
|
+
export function computeManifestId(manifest) {
|
|
20
|
+
let stableKey;
|
|
21
|
+
switch (manifest.type) {
|
|
22
|
+
case 'app':
|
|
23
|
+
stableKey = `app\0${manifest.authorAddress}\0${manifest.pearTopicKey}`;
|
|
24
|
+
break;
|
|
25
|
+
case 'capability':
|
|
26
|
+
stableKey = `capability\0${manifest.agentAddress}\0${manifest.capabilityName}`;
|
|
27
|
+
break;
|
|
28
|
+
case 'dapp':
|
|
29
|
+
stableKey = `dapp\0${manifest.authorAddress}\0${manifest.contractHash}`;
|
|
30
|
+
break;
|
|
31
|
+
case 'edge-service':
|
|
32
|
+
stableKey = `edge-service\0${manifest.operatorAddress}\0${manifest.serviceType}\0${manifest.name}`;
|
|
33
|
+
break;
|
|
34
|
+
default: {
|
|
35
|
+
const _exhaustive = manifest;
|
|
36
|
+
throw new Error(`computeManifestId: unknown manifest type: ${JSON.stringify(_exhaustive)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return bytesToHex(sha3_256(new TextEncoder().encode(stableKey)));
|
|
40
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { AppManifest, CapabilityManifest, DAppManifest, EdgeServiceManifest, EdgeServiceType, Manifest, SignedManifest, AppPermission, DAppAbiEntry, VerifyResult, } from './types.js';
|
|
2
|
+
export { MANIFEST_VERSION } from './constants.js';
|
|
3
|
+
export { computeManifestId } from './id.js';
|
|
4
|
+
export { signManifest } from './sign.js';
|
|
5
|
+
export { verifyManifest } from './verify.js';
|
|
6
|
+
export { encodeManifest, decodeManifest } from './encoding.js';
|
|
7
|
+
export { isAppManifest, isCapabilityManifest, isDAppManifest, isEdgeServiceManifest, } from './guards.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { MANIFEST_VERSION } from './constants.js';
|
|
2
|
+
export { computeManifestId } from './id.js';
|
|
3
|
+
export { signManifest } from './sign.js';
|
|
4
|
+
export { verifyManifest } from './verify.js';
|
|
5
|
+
export { encodeManifest, decodeManifest } from './encoding.js';
|
|
6
|
+
export { isAppManifest, isCapabilityManifest, isDAppManifest, isEdgeServiceManifest, } from './guards.js';
|
package/dist/sign.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* signManifest — signs a manifest with a WOTS key.
|
|
3
|
+
*
|
|
4
|
+
* Steps:
|
|
5
|
+
* 1. Canonicalise the manifest as deterministic JSON (sorted keys).
|
|
6
|
+
* 2. SHA3-256 the UTF-8 bytes → 32-byte digest.
|
|
7
|
+
* 3. Sign with wotsSign(seed, keyIndex, digest).
|
|
8
|
+
* 4. Derive address + PKdigest via wotsKeypairFromSeed + wotsAddressFromKeypair.
|
|
9
|
+
* 5. Return SignedManifest<T>.
|
|
10
|
+
*
|
|
11
|
+
* signerPublicKey in SignedManifest is the 32-byte WOTS PKdigest (hex, 64 chars).
|
|
12
|
+
* This is the exact value expected by verifyManifest (via wotsVerifyDigest).
|
|
13
|
+
*
|
|
14
|
+
* The caller is responsible for reserving the WOTS key index before calling
|
|
15
|
+
* this function. This package does NOT depend on @totemsdk/wots-lease.
|
|
16
|
+
*/
|
|
17
|
+
import type { Manifest, SignedManifest } from './types.js';
|
|
18
|
+
export declare function manifestDigest(manifest: Manifest): Uint8Array;
|
|
19
|
+
export declare function signManifest<T extends Manifest>(manifest: T, seed: Uint8Array, keyIndex: number): Promise<SignedManifest<T>>;
|
package/dist/sign.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* signManifest — signs a manifest with a WOTS key.
|
|
3
|
+
*
|
|
4
|
+
* Steps:
|
|
5
|
+
* 1. Canonicalise the manifest as deterministic JSON (sorted keys).
|
|
6
|
+
* 2. SHA3-256 the UTF-8 bytes → 32-byte digest.
|
|
7
|
+
* 3. Sign with wotsSign(seed, keyIndex, digest).
|
|
8
|
+
* 4. Derive address + PKdigest via wotsKeypairFromSeed + wotsAddressFromKeypair.
|
|
9
|
+
* 5. Return SignedManifest<T>.
|
|
10
|
+
*
|
|
11
|
+
* signerPublicKey in SignedManifest is the 32-byte WOTS PKdigest (hex, 64 chars).
|
|
12
|
+
* This is the exact value expected by verifyManifest (via wotsVerifyDigest).
|
|
13
|
+
*
|
|
14
|
+
* The caller is responsible for reserving the WOTS key index before calling
|
|
15
|
+
* this function. This package does NOT depend on @totemsdk/wots-lease.
|
|
16
|
+
*/
|
|
17
|
+
import { sha3_256 } from '@noble/hashes/sha3.js';
|
|
18
|
+
import { wotsSign, wotsKeypairFromSeed, wotsAddressFromKeypair, bytesToHex, } from '@totemsdk/core';
|
|
19
|
+
/** Produce a deterministic canonical JSON string with sorted keys (recursive). */
|
|
20
|
+
function canonicalJson(value) {
|
|
21
|
+
if (value === null || typeof value !== 'object') {
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return '[' + value.map(canonicalJson).join(',') + ']';
|
|
26
|
+
}
|
|
27
|
+
const obj = value;
|
|
28
|
+
const keys = Object.keys(obj).sort();
|
|
29
|
+
const pairs = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`);
|
|
30
|
+
return '{' + pairs.join(',') + '}';
|
|
31
|
+
}
|
|
32
|
+
export function manifestDigest(manifest) {
|
|
33
|
+
const canonical = canonicalJson(manifest);
|
|
34
|
+
return sha3_256(new TextEncoder().encode(canonical));
|
|
35
|
+
}
|
|
36
|
+
export async function signManifest(manifest, seed, keyIndex) {
|
|
37
|
+
const digest = manifestDigest(manifest);
|
|
38
|
+
const sigBytes = wotsSign(seed, keyIndex, digest);
|
|
39
|
+
const kp = wotsKeypairFromSeed(seed, keyIndex);
|
|
40
|
+
const address = wotsAddressFromKeypair(kp);
|
|
41
|
+
return {
|
|
42
|
+
manifest,
|
|
43
|
+
authorAddress: address,
|
|
44
|
+
signerPublicKey: bytesToHex(kp.pk),
|
|
45
|
+
signedAt: Date.now(),
|
|
46
|
+
signature: bytesToHex(sigBytes),
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @totemsdk/manifest — Manifest type definitions
|
|
3
|
+
*
|
|
4
|
+
* Four manifest categories for the MVP:
|
|
5
|
+
* AppManifest — human-facing Pear app
|
|
6
|
+
* CapabilityManifest — ephemeral AI/agent service
|
|
7
|
+
* DAppManifest — KISSVM contract / covenant
|
|
8
|
+
* EdgeServiceManifest — any persistent Totem Edge service
|
|
9
|
+
*
|
|
10
|
+
* No network, no blockchain, no Hyperswarm — pure schema.
|
|
11
|
+
*/
|
|
12
|
+
export type AppPermission = 'wallet:read-balance' | 'wallet:request-payment' | 'omnia:open-channel' | 'omnia:update-channel' | 'lookup:watch-address' | 'kissvm:evaluate' | 'qvac:call-agent';
|
|
13
|
+
export interface AppManifest {
|
|
14
|
+
type: 'app';
|
|
15
|
+
appId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
authorAddress: string;
|
|
19
|
+
pearTopicKey: string;
|
|
20
|
+
price: string;
|
|
21
|
+
priceToken?: string;
|
|
22
|
+
subscriptionInterval?: number;
|
|
23
|
+
category: string[];
|
|
24
|
+
permissions: AppPermission[];
|
|
25
|
+
iconCid?: string;
|
|
26
|
+
description: string;
|
|
27
|
+
repoUrl?: string;
|
|
28
|
+
minTotemVersion: string;
|
|
29
|
+
}
|
|
30
|
+
export interface CapabilityManifest {
|
|
31
|
+
type: 'capability';
|
|
32
|
+
capabilityId: string;
|
|
33
|
+
capabilityName: string;
|
|
34
|
+
agentAddress: string;
|
|
35
|
+
agentIdentityKey: string;
|
|
36
|
+
description: string;
|
|
37
|
+
inputSchema: object;
|
|
38
|
+
outputSchema: object;
|
|
39
|
+
pricePerCall: string;
|
|
40
|
+
priceToken?: string;
|
|
41
|
+
paymentChannel?: 'omnia' | 'onchain';
|
|
42
|
+
maxLatencyMs?: number;
|
|
43
|
+
maxCallsPerMinute?: number;
|
|
44
|
+
expiresAt: number;
|
|
45
|
+
tags: string[];
|
|
46
|
+
}
|
|
47
|
+
export interface DAppAbiEntry {
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
params: {
|
|
51
|
+
name: string;
|
|
52
|
+
type: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
}[];
|
|
55
|
+
}
|
|
56
|
+
export interface DAppManifest {
|
|
57
|
+
type: 'dapp';
|
|
58
|
+
dappId: string;
|
|
59
|
+
name: string;
|
|
60
|
+
version: string;
|
|
61
|
+
authorAddress: string;
|
|
62
|
+
contractHash: string;
|
|
63
|
+
contractSource?: string;
|
|
64
|
+
abi: DAppAbiEntry[];
|
|
65
|
+
price: string;
|
|
66
|
+
priceToken?: string;
|
|
67
|
+
category: string[];
|
|
68
|
+
description: string;
|
|
69
|
+
auditReport?: string;
|
|
70
|
+
}
|
|
71
|
+
export type EdgeServiceType = 'sensor' | 'robot' | 'mqtt-feed' | 'proof-index' | 'lookup-provider' | 'omnia-router' | 'calibration-authority' | 'verifier' | 'machine-service' | 'other';
|
|
72
|
+
export interface EdgeServiceManifest {
|
|
73
|
+
type: 'edge-service';
|
|
74
|
+
serviceId: string;
|
|
75
|
+
name: string;
|
|
76
|
+
version: string;
|
|
77
|
+
operatorAddress: string;
|
|
78
|
+
serviceType: EdgeServiceType;
|
|
79
|
+
description: string;
|
|
80
|
+
endpoints?: Array<{
|
|
81
|
+
type: 'https' | 'mqtt' | 'hyperswarm' | 'websocket' | 'other';
|
|
82
|
+
uri: string;
|
|
83
|
+
}>;
|
|
84
|
+
capabilities: string[];
|
|
85
|
+
price?: string;
|
|
86
|
+
priceToken?: string;
|
|
87
|
+
paymentMethods?: Array<'omnia' | 'onchain' | 'invoice' | 'free'>;
|
|
88
|
+
tags: string[];
|
|
89
|
+
expiresAt?: number;
|
|
90
|
+
minTotemVersion?: string;
|
|
91
|
+
}
|
|
92
|
+
export type Manifest = AppManifest | CapabilityManifest | DAppManifest | EdgeServiceManifest;
|
|
93
|
+
/**
|
|
94
|
+
* Wraps any manifest with a WOTS signature.
|
|
95
|
+
*
|
|
96
|
+
* `signerPublicKey` — hex of the full WOTS public key (required for
|
|
97
|
+
* self-contained verification via verifyManifest).
|
|
98
|
+
* `authorAddress` — the Minima address of the signer, derived at sign time
|
|
99
|
+
* and stored for quick policy checks without re-deriving from the public key.
|
|
100
|
+
*/
|
|
101
|
+
export interface SignedManifest<T extends Manifest = Manifest> {
|
|
102
|
+
manifest: T;
|
|
103
|
+
authorAddress: string;
|
|
104
|
+
signerPublicKey: string;
|
|
105
|
+
signedAt: number;
|
|
106
|
+
signature: string;
|
|
107
|
+
rootIdentityProof?: string;
|
|
108
|
+
}
|
|
109
|
+
export interface VerifyResult {
|
|
110
|
+
valid: boolean;
|
|
111
|
+
reason?: string;
|
|
112
|
+
signerAddress: string;
|
|
113
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @totemsdk/manifest — Manifest type definitions
|
|
3
|
+
*
|
|
4
|
+
* Four manifest categories for the MVP:
|
|
5
|
+
* AppManifest — human-facing Pear app
|
|
6
|
+
* CapabilityManifest — ephemeral AI/agent service
|
|
7
|
+
* DAppManifest — KISSVM contract / covenant
|
|
8
|
+
* EdgeServiceManifest — any persistent Totem Edge service
|
|
9
|
+
*
|
|
10
|
+
* No network, no blockchain, no Hyperswarm — pure schema.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verifyManifest — verifies a SignedManifest without external state.
|
|
3
|
+
*
|
|
4
|
+
* Steps:
|
|
5
|
+
* 1. Recompute the canonical manifest digest.
|
|
6
|
+
* 2. Verify the WOTS signature against the stored 32-byte PKdigest
|
|
7
|
+
* (signerPublicKey field) using wotsVerifyDigest.
|
|
8
|
+
* 3. Confirm authorAddress matches the manifest's address field.
|
|
9
|
+
*
|
|
10
|
+
* wotsVerifyDigest is used (rather than wotsVerify) because signerPublicKey
|
|
11
|
+
* stores the 32-byte WOTS PKdigest as returned by wotsKeypairFromSeed.kp.pk.
|
|
12
|
+
* The full 1088-byte key is not stored in SignedManifest to keep it compact.
|
|
13
|
+
*/
|
|
14
|
+
import type { SignedManifest, VerifyResult } from './types.js';
|
|
15
|
+
export declare function verifyManifest(signed: SignedManifest): VerifyResult;
|