sello 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.
Files changed (47) hide show
  1. package/LICENSE +200 -0
  2. package/README.md +195 -0
  3. package/SPEC.md +738 -0
  4. package/docs/assets/sello-banner.png +0 -0
  5. package/docs/assets/sello-social-preview.png +0 -0
  6. package/docs/decisions.md +79 -0
  7. package/docs/paper/notarized-agents.md +523 -0
  8. package/docs/paper/notarized-agents.pdf +0 -0
  9. package/docs/paper/notarized-agents.tex +1387 -0
  10. package/docs/paper/refs.bib +245 -0
  11. package/docs/performance.md +24 -0
  12. package/docs/release-checklist.md +56 -0
  13. package/docs/sdk-build-plan.md +214 -0
  14. package/docs/sdk-quickstart.md +115 -0
  15. package/docs/sdk-security-audit.md +53 -0
  16. package/docs/security-review.md +54 -0
  17. package/examples/mcp-tool-server.ts +250 -0
  18. package/examples/quickstart-tool.ts +178 -0
  19. package/fixtures/vectors/.gitkeep +1 -0
  20. package/fixtures/vectors/sello-v0.1.json +101 -0
  21. package/package.json +52 -0
  22. package/src/cbor.ts +337 -0
  23. package/src/cli/bench.ts +390 -0
  24. package/src/cli/demo.ts +114 -0
  25. package/src/cli/sello.ts +514 -0
  26. package/src/cose/protected-header.ts +210 -0
  27. package/src/cose/sign1.ts +124 -0
  28. package/src/crypto/ed25519.ts +117 -0
  29. package/src/crypto/identifiers.ts +64 -0
  30. package/src/hpke/base.ts +349 -0
  31. package/src/hpke/receipt.ts +79 -0
  32. package/src/index.ts +15 -0
  33. package/src/log/canonical-url.ts +168 -0
  34. package/src/log/mock-log.ts +170 -0
  35. package/src/log/rekor.ts +147 -0
  36. package/src/log/types.ts +27 -0
  37. package/src/mcp/middleware.ts +198 -0
  38. package/src/owner/verify.ts +276 -0
  39. package/src/receipt/body.ts +210 -0
  40. package/src/registry/json-registry.ts +233 -0
  41. package/src/sdk/index.ts +22 -0
  42. package/src/sdk/keys.ts +191 -0
  43. package/src/sdk/logs.ts +200 -0
  44. package/src/sdk/publisher.ts +145 -0
  45. package/src/sdk/service.ts +562 -0
  46. package/src/service/create-receipt.ts +178 -0
  47. package/src/token/jws-profile.ts +174 -0
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "sello",
3
+ "version": "0.1.0",
4
+ "description": "Reference implementation of the Sello protocol for service-signed AI agent receipts.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./fixtures/vectors/sello-v0.1.json": "./fixtures/vectors/sello-v0.1.json",
11
+ "./spec": "./SPEC.md",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "bin": {
15
+ "sello": "src/cli/sello.ts",
16
+ "sello-bench": "src/cli/bench.ts",
17
+ "sello-demo": "src/cli/demo.ts"
18
+ },
19
+ "files": [
20
+ "examples",
21
+ "src",
22
+ "fixtures/vectors",
23
+ "docs",
24
+ "SPEC.md",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "scripts": {
29
+ "actions": "node --experimental-strip-types src/cli/sello.ts actions",
30
+ "bench": "node --experimental-strip-types src/cli/bench.ts",
31
+ "demo": "node --experimental-strip-types src/cli/demo.ts",
32
+ "dev": "node --experimental-strip-types src/cli/sello.ts dev",
33
+ "example:mcp": "node --experimental-strip-types examples/mcp-tool-server.ts",
34
+ "example:tool": "node --experimental-strip-types examples/quickstart-tool.ts",
35
+ "pack:check": "npm pack --dry-run",
36
+ "test": "node --test --experimental-strip-types"
37
+ },
38
+ "engines": {
39
+ "node": ">=24.0.0"
40
+ },
41
+ "keywords": [
42
+ "ai-agents",
43
+ "receipts",
44
+ "transparency-log",
45
+ "cose",
46
+ "hpke",
47
+ "mcp"
48
+ ],
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }
package/src/cbor.ts ADDED
@@ -0,0 +1,337 @@
1
+ export type CborKey = number | string;
2
+
3
+ export type CborValue =
4
+ | number
5
+ | string
6
+ | Uint8Array
7
+ | readonly CborValue[]
8
+ | CborTagged
9
+ | CborMap;
10
+
11
+ export type CborMap = Map<CborKey, CborValue>;
12
+
13
+ export type CborTagged = {
14
+ tag: number;
15
+ value: CborValue;
16
+ };
17
+
18
+ export function cborTag(tag: number, value: CborValue): CborTagged {
19
+ assertNonNegativeSafeInteger(tag, "tag");
20
+ return { tag, value };
21
+ }
22
+
23
+ export function encodeCbor(value: CborValue): Uint8Array {
24
+ if (typeof value === "number") {
25
+ return encodeInteger(value);
26
+ }
27
+
28
+ if (typeof value === "string") {
29
+ return concat([encodeLength(3, utf8ByteLength(value)), encodeUtf8(value)]);
30
+ }
31
+
32
+ if (value instanceof Uint8Array) {
33
+ return concat([encodeLength(2, value.byteLength), value]);
34
+ }
35
+
36
+ if (Array.isArray(value)) {
37
+ return concat([
38
+ encodeLength(4, value.length),
39
+ ...value.map((entry) => encodeCbor(entry)),
40
+ ]);
41
+ }
42
+
43
+ if (value instanceof Map) {
44
+ return encodeMap(value);
45
+ }
46
+
47
+ if (isTagged(value)) {
48
+ return concat([encodeLength(6, value.tag), encodeCbor(value.value)]);
49
+ }
50
+
51
+ throw new TypeError("unsupported CBOR value");
52
+ }
53
+
54
+ export function decodeCbor(bytes: Uint8Array): CborValue {
55
+ const reader = new CborReader(bytes);
56
+ const value = reader.readValue();
57
+ reader.assertDone();
58
+ return value;
59
+ }
60
+
61
+ export function concat(parts: readonly Uint8Array[]): Uint8Array {
62
+ const length = parts.reduce((sum, part) => sum + part.byteLength, 0);
63
+ const out = new Uint8Array(length);
64
+ let offset = 0;
65
+
66
+ for (const part of parts) {
67
+ out.set(part, offset);
68
+ offset += part.byteLength;
69
+ }
70
+
71
+ return out;
72
+ }
73
+
74
+ function encodeInteger(value: number): Uint8Array {
75
+ if (!Number.isSafeInteger(value)) {
76
+ throw new TypeError("CBOR integer must be a safe integer");
77
+ }
78
+
79
+ if (value >= 0) {
80
+ return encodeLength(0, value);
81
+ }
82
+
83
+ return encodeLength(1, -1 - value);
84
+ }
85
+
86
+ function encodeMap(value: CborMap): Uint8Array {
87
+ const entries = [...value.entries()].map(([key, entryValue]) => {
88
+ if (typeof key !== "number" && typeof key !== "string") {
89
+ throw new TypeError("CBOR map keys must be strings or numbers");
90
+ }
91
+
92
+ return {
93
+ key: encodeCbor(key),
94
+ value: encodeCbor(entryValue),
95
+ };
96
+ });
97
+
98
+ entries.sort((a, b) => compareBytes(a.key, b.key));
99
+
100
+ for (let index = 1; index < entries.length; index += 1) {
101
+ if (compareBytes(entries[index - 1].key, entries[index].key) === 0) {
102
+ throw new TypeError("CBOR map contains duplicate keys");
103
+ }
104
+ }
105
+
106
+ return concat([
107
+ encodeLength(5, entries.length),
108
+ ...entries.flatMap((entry) => [entry.key, entry.value]),
109
+ ]);
110
+ }
111
+
112
+ function encodeLength(majorType: number, value: number): Uint8Array {
113
+ assertNonNegativeSafeInteger(value, "CBOR length/value");
114
+ const prefix = majorType << 5;
115
+
116
+ if (value < 24) {
117
+ return Uint8Array.of(prefix | value);
118
+ }
119
+
120
+ if (value <= 0xff) {
121
+ return Uint8Array.of(prefix | 24, value);
122
+ }
123
+
124
+ if (value <= 0xffff) {
125
+ return Uint8Array.of(prefix | 25, value >> 8, value & 0xff);
126
+ }
127
+
128
+ if (value <= 0xffffffff) {
129
+ return Uint8Array.of(
130
+ prefix | 26,
131
+ (value >>> 24) & 0xff,
132
+ (value >>> 16) & 0xff,
133
+ (value >>> 8) & 0xff,
134
+ value & 0xff,
135
+ );
136
+ }
137
+
138
+ throw new RangeError("CBOR values larger than uint32 are not supported yet");
139
+ }
140
+
141
+ function compareBytes(a: Uint8Array, b: Uint8Array): number {
142
+ const length = Math.min(a.byteLength, b.byteLength);
143
+
144
+ for (let index = 0; index < length; index += 1) {
145
+ const diff = a[index] - b[index];
146
+ if (diff !== 0) {
147
+ return diff;
148
+ }
149
+ }
150
+
151
+ return a.byteLength - b.byteLength;
152
+ }
153
+
154
+ function assertNonNegativeSafeInteger(value: number, name: string): void {
155
+ if (!Number.isSafeInteger(value) || value < 0) {
156
+ throw new TypeError(`${name} must be a non-negative safe integer`);
157
+ }
158
+ }
159
+
160
+ function isTagged(value: unknown): value is CborTagged {
161
+ return (
162
+ typeof value === "object" &&
163
+ value !== null &&
164
+ "tag" in value &&
165
+ "value" in value
166
+ );
167
+ }
168
+
169
+ const textEncoder = new TextEncoder();
170
+ const textDecoder = new TextDecoder("utf-8", { fatal: true });
171
+
172
+ function encodeUtf8(value: string): Uint8Array {
173
+ return textEncoder.encode(value);
174
+ }
175
+
176
+ function decodeUtf8(value: Uint8Array): string {
177
+ return textDecoder.decode(value);
178
+ }
179
+
180
+ function utf8ByteLength(value: string): number {
181
+ return encodeUtf8(value).byteLength;
182
+ }
183
+
184
+ class CborReader {
185
+ readonly #bytes: Uint8Array;
186
+ #offset = 0;
187
+
188
+ constructor(bytes: Uint8Array) {
189
+ this.#bytes = bytes;
190
+ }
191
+
192
+ readValue(): CborValue {
193
+ const initialByte = this.readByte();
194
+ const majorType = initialByte >> 5;
195
+ const additionalInfo = initialByte & 0x1f;
196
+
197
+ if (majorType === 0) {
198
+ return this.readLength(additionalInfo);
199
+ }
200
+
201
+ if (majorType === 1) {
202
+ return -1 - this.readLength(additionalInfo);
203
+ }
204
+
205
+ if (majorType === 2) {
206
+ const length = this.readLength(additionalInfo);
207
+ return this.readBytes(length);
208
+ }
209
+
210
+ if (majorType === 3) {
211
+ const length = this.readLength(additionalInfo);
212
+ return decodeUtf8(this.readBytes(length));
213
+ }
214
+
215
+ if (majorType === 4) {
216
+ const length = this.readLength(additionalInfo);
217
+ return this.readArray(length);
218
+ }
219
+
220
+ if (majorType === 5) {
221
+ const length = this.readLength(additionalInfo);
222
+ return this.readMap(length);
223
+ }
224
+
225
+ if (majorType === 6) {
226
+ const tag = this.readLength(additionalInfo);
227
+ return cborTag(tag, this.readValue());
228
+ }
229
+
230
+ throw new TypeError(`unsupported CBOR major type ${majorType}`);
231
+ }
232
+
233
+ assertDone(): void {
234
+ if (this.#offset !== this.#bytes.byteLength) {
235
+ throw new TypeError("CBOR data has trailing bytes");
236
+ }
237
+ }
238
+
239
+ private readArray(length: number): CborValue[] {
240
+ const out: CborValue[] = [];
241
+
242
+ for (let index = 0; index < length; index += 1) {
243
+ out.push(this.readValue());
244
+ }
245
+
246
+ return out;
247
+ }
248
+
249
+ private readMap(length: number): CborMap {
250
+ const out: CborMap = new Map();
251
+ let previousEncodedKey: Uint8Array | undefined;
252
+
253
+ for (let index = 0; index < length; index += 1) {
254
+ const keyStart = this.#offset;
255
+ const key = this.readValue();
256
+ const encodedKey = this.#bytes.subarray(keyStart, this.#offset);
257
+
258
+ if (typeof key !== "string" && typeof key !== "number") {
259
+ throw new TypeError("CBOR map keys must be strings or numbers");
260
+ }
261
+
262
+ if (previousEncodedKey && compareBytes(previousEncodedKey, encodedKey) >= 0) {
263
+ throw new TypeError("CBOR map keys are not in deterministic order");
264
+ }
265
+
266
+ previousEncodedKey = encodedKey;
267
+ out.set(key, this.readValue());
268
+ }
269
+
270
+ return out;
271
+ }
272
+
273
+ private readLength(additionalInfo: number): number {
274
+ if (additionalInfo < 24) {
275
+ return additionalInfo;
276
+ }
277
+
278
+ if (additionalInfo === 24) {
279
+ const value = this.readByte();
280
+ if (value < 24) {
281
+ throw new TypeError("CBOR integer/length is not minimally encoded");
282
+ }
283
+ return value;
284
+ }
285
+
286
+ if (additionalInfo === 25) {
287
+ const value = this.readUint16();
288
+ if (value <= 0xff) {
289
+ throw new TypeError("CBOR integer/length is not minimally encoded");
290
+ }
291
+ return value;
292
+ }
293
+
294
+ if (additionalInfo === 26) {
295
+ const value = this.readUint32();
296
+ if (value <= 0xffff) {
297
+ throw new TypeError("CBOR integer/length is not minimally encoded");
298
+ }
299
+ return value;
300
+ }
301
+
302
+ throw new TypeError("CBOR indefinite or uint64 lengths are not supported");
303
+ }
304
+
305
+ private readByte(): number {
306
+ if (this.#offset >= this.#bytes.byteLength) {
307
+ throw new TypeError("unexpected end of CBOR data");
308
+ }
309
+
310
+ return this.#bytes[this.#offset++];
311
+ }
312
+
313
+ private readBytes(length: number): Uint8Array {
314
+ const end = this.#offset + length;
315
+
316
+ if (end > this.#bytes.byteLength) {
317
+ throw new TypeError("unexpected end of CBOR data");
318
+ }
319
+
320
+ const out = this.#bytes.subarray(this.#offset, end);
321
+ this.#offset = end;
322
+ return out;
323
+ }
324
+
325
+ private readUint16(): number {
326
+ return (this.readByte() << 8) | this.readByte();
327
+ }
328
+
329
+ private readUint32(): number {
330
+ return (
331
+ (this.readByte() * 0x1000000) +
332
+ (this.readByte() << 16) +
333
+ (this.readByte() << 8) +
334
+ this.readByte()
335
+ );
336
+ }
337
+ }