tinyrelayfile
tinyrelay/scripts/blossom-manifests.test.mjs/

scripts/blossom-manifests.test.mjs raw
1import assert from "node:assert/strict";
2import {readFile} from "node:fs/promises";
3import test from "node:test";
4import vm from "node:vm";
5
6const source = await readFile(new URL("../tinyclient/blossom-manifests.js", import.meta.url), "utf8");
7const shared = await readFile(new URL("../tinyclient/tiny.js", import.meta.url), "utf8");
8const sandbox = {TextEncoder, TextDecoder, Uint8Array, ArrayBuffer, crypto: (await import("node:crypto")).webcrypto};
9sandbox.globalThis = sandbox;
10vm.runInNewContext(shared + source, sandbox);
11const api = sandbox.tiny.blossom.manifests;
12const bytes = hex => Uint8Array.from(hex.match(/../g), x => parseInt(x, 16));
13const hash = c => c.repeat(64);
14
15test("matches the BUD-16 empty and single-entry vectors", async () => {
16 const empty = {l: [], t: 2};
17 assert.equal(Buffer.from(api.encodeManifest(empty)).toString("hex"), "82a16c90a17402");
18 assert.equal(await api.manifestHash(empty), "0218ed9a4fbb0993757f17e5d08d089cb0c6ac851928ba1ba82d337d76c41c0c");
19 const one = {l: [{h: bytes("ab".repeat(32)), n: "test.txt", s: 100, t: 0}], t: 2};
20 assert.equal(await api.manifestHash(one), "16121fa792b3afc72ec8bfc1dc85060518b6adba1429973ecc12891165cbe67e");
21 const decoded = api.decodeManifest(api.encodeManifest(one));
22 assert.equal(decoded.t, 2);
23 assert.equal(decoded.l[0].n, "test.txt");
24 assert.equal(Buffer.from(decoded.l[0].h).toString("hex"), "ab".repeat(32));
25});
26
27test("matches the BUD-17 two-chunk vector and preserves chunk order", async () => {
28 const node = {l: [{h: bytes("aa".repeat(32)), s: 100, t: 0}, {h: bytes("bb".repeat(32)), s: 50, t: 0}], t: 1};
29 assert.equal(await api.manifestHash(node), "559b726c38295aa0ecbbaef43d438cc86dd63324a0c3e9426dc5f1d0285f483f");
30 const decoded = api.decodeManifest(api.encodeManifest(node));
31 assert.equal(decoded.t, 1);
32 assert.equal(JSON.stringify(decoded.l.map(x => [Buffer.from(x.h).toString("hex"), x.s, x.t])), JSON.stringify([["aa".repeat(32), 100, 0], ["bb".repeat(32), 50, 0]]));
33});
34
35test("canonicalizes directory names by UTF-8 bytes and builds file manifests", async () => {
36 const directory = await api.buildDirectory([
37 {hash: hash("a"), name: "z", size: 2}, {hash: hash("b"), name: "a", size: 1}
38 ]);
39 assert.deepEqual(directory.l.map(x => x.n), ["a", "z"]);
40 const details = await api.buildFile(Array.from({length: 175}, (_, i) => ({hash: hash((i % 10).toString()), size: 1})), {returnDetails: true});
41 assert.equal(details.root.t, 1);
42 assert.equal(details.root.l.length, 2);
43 assert.equal(details.manifests.length, 3);
44 assert.equal(details.root.l.reduce((n, x) => n + x.s, 0), 175);
45});
46
47test("builds typed directory fanout nodes with bounded child metadata", async () => {
48 const details = await api.buildDirectory(Array.from({length: 175}, (_, i) => ({hash: hash("a"), name: `file-${String(i).padStart(3, "0")}`, size: 1})), {returnDetails: true});
49 assert.equal(details.root.t, 3);
50 assert.equal(details.root.l.length, 2);
51 assert.equal(JSON.stringify(details.root.l[0].m), JSON.stringify({count: 174, first: "file-000", last: "file-173"}));
52 assert.equal(details.root.l[0].t, 2);
53});
54
55test("resolves fanout children only after hash and bound verification", async () => {
56 const child = {l: [{h: bytes("aa".repeat(32)), n: "a.txt", s: 1, t: 0}], t: 2};
57 const childHash = await api.manifestHash(child);
58 const root = {l: [{h: bytes(childHash), m: {count: 1, first: "a.txt", last: "a.txt"}, s: 1, t: 2}], t: 3};
59 const resolved = await api.resolveDirectory(root, async hash => hash === childHash ? api.encodeManifest(child) : new Uint8Array());
60 assert.equal(resolved.length, 1);
61 await assert.rejects(api.resolveDirectory(root, async () => api.encodeManifest({...child, l: []})), /manifest hash mismatch/);
62});
63
64test("rejects traversal, duplicate names, unknown types and malformed bytes", () => {
65 const link = {h: bytes("aa".repeat(32)), n: "ok", s: 1, t: 0};
66 assert.throws(() => api.encodeManifest({l: [{...link, n: "../secret"}], t: 2}), /invalid directory entry name/);
67 assert.throws(() => api.encodeManifest({l: [link, link], t: 2}), /duplicate directory entry/);
68 assert.throws(() => api.encodeManifest({l: [{...link, t: 9}], t: 2}), /unsupported link type/);
69 assert.throws(() => api.decodeManifest(Uint8Array.from([0x82, 0xa1, 0x6c])), /truncated/);
70 assert.throws(() => api.encodeManifest({l: [{...link, n: "b"}, {...link, n: "a"}], t: 2}), /canonical/);
71});

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs