1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 | /**
* THE interop invariant: the OrbitDB address the port opens must be byte-for-byte
* the address the v1.0.0 TypeScript build opened, and the address every deployed
* browser derives (PROTOCOL.md §5).
*
* It is the CID of the manifest {name, type, accessController}: a pure function
* of the secret-derived dbName and the hardened access controller's own address.
* Nothing is exchanged to agree on it, and nothing fails loudly if it diverges —
* the bot would just replicate a database no one else is in. So this boots the
* whole real stack (libp2p v2 with the native WebRTC transport, Helia on a
* filesystem blockstore, @orbitdb/core with entry+payload encryption and the
* sueta identity provider) and compares the strings.
*
* Fixtures: test/vectors/orbit-address.json, extracted by
* test/vectors/generate-orbit-address.mjs from the TypeScript build.
*
* This is also the end-to-end proof that the two-stack law holds from this side:
* the process really does load @ipshipyard/node-datachannel (via @libp2p/webrtc
* v5) and really does not load the unscoped node-datachannel that peer-kit's
* libp2p v3 pulls.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createRequire } from "node:module";
import { load, readVector, DIST } from "./helpers/load.mjs";
import { addressCases, openAddressCase, SEED } from "./helpers/orbit-address.mjs";
// Every module first — installBrowserGlobals() (inside openAddressCase) sets a
// `window` with no `location`, and helia's graph pulls axios, which reads
// `window.location.href` at module init.
const mods = {
crypto: await load("lib/crypto.js"),
log: await load("lib/log.js"),
net: await load("lib/net.js"),
turn: await load("lib/turn.js"),
env: await load("node/env.js"),
id: await load("index.js"),
};
test("the OrbitDB address matches the TypeScript build byte-for-byte", async () => {
const expected = await readVector("orbit-address");
assert.equal(expected.length, addressCases.length);
for (let i = 0; i < addressCases.length; i++) {
const dir = mkdtempSync(join(tmpdir(), "rooms-addr-"));
const got = await openAddressCase(mods, addressCases[i], dir);
assert.deepEqual(got, expected[i], `case ${i}: ${addressCases[i].appSalt}`);
assert.match(got.address, /^\/orbitdb\/zdpu/);
}
// identity-less and identity-carrying members open the SAME database: the
// manifest hashes only {name, type, accessController}, never the creator
assert.equal(expected[0].address, expected[1].address);
// a different app salt is a different room even with the same secret
assert.notEqual(expected[0].address, expected[2].address);
// and the sueta identity is what authorship chains to
assert.equal(expected[0].seed, SEED);
assert.match(expected[0].myAuthorId, /^[A-Za-z0-9_-]{43}$/);
});
test("a live round trip through the real log: append, replay, seal, images", async () => {
const dir = mkdtempSync(join(tmpdir(), "rooms-live-"));
try {
mods.env.installOrigin("https://chat.ardegazu.ro");
} catch {
/* already installed */
}
mods.env.installBrowserGlobals();
const { privateKey } = await mods.net.newSessionKey();
const rc = await mods.crypto.RoomCrypto.create("L".repeat(43), "chat.ardegazu.ro/v2", "12D3KooWLive");
const net = await mods.net.Net.create({
privateKey,
crypto: rc,
ice: new mods.turn.IceConfig("https://127.0.0.1:1/turn-credentials"),
wsOrigin: "https://chat.ardegazu.ro",
relayMultiaddr: "/dns4/127-0-0-1.sslip.io/tcp/1/tls/ws/p2p/12D3KooWQzZvygPwd2F4JAqqf6tfBSJ29YtjzzftUyJ37RLCG5WT",
discoveryTopic: "_peer-discovery._p2p._pubsub",
myName: () => "live-probe",
events: { peerState() {}, peerGone() {}, message() {}, binary() {}, peerReady() {}, status() {} },
});
assert.match(net.myId, /^12D3Koo/);
const helia = await mods.log.openHelia(net.libp2p, { blocks: join(dir, "b"), data: join(dir, "d") });
const identity = await mods.id.Identity.fromSeed(SEED);
const log = await mods.log.RoomLog.open(helia, rc, identity, { directory: join(dir, "orbitdb") });
// authorship chains to the durable identity, not the device key
assert.equal(log.myAuthorId, identity.publicKeyB64);
const hash = await log.append({ t: "chat", ts: 1_700_000_000_000, name: "probe", text: "hello" });
const seen = [];
log.onEntry = (e) => seen.push(e);
assert.equal(await log.loadTail(10), 1);
assert.deepEqual(JSON.parse(JSON.stringify(seen.at(-1).op)), {
t: "chat",
ts: 1_700_000_000_000,
name: "probe",
text: "hello",
});
// the verified author survives the signer↔identity binding check
assert.equal(seen.at(-1).from, identity.publicKeyB64);
assert.equal(seen.at(-1).hash, hash);
assert.equal(await log.hasEntry(hash), true);
// the stored block is the SEALED one (dag-cbor over kEntry ciphertext), and it
// decodes back to the same entry hash on this side
const sealed = await log.sealedEntryBytes(hash);
assert.equal(sealed instanceof Uint8Array, true);
const decoded = await log.decodeSealedEntry(sealed);
assert.equal(decoded.hash, hash);
// garbage is rejected, never thrown
assert.equal(await log.decodeSealedEntry(new Uint8Array([1, 2, 3])), null);
// and re-ingesting a known entry is a duplicate, not a rewrite
assert.equal(await log.ingestEntry(decoded), "duplicate");
// the image cipher round trip through real unixfs blocks
const { cid, bytes } = await log.putImage(new Uint8Array([9, 8, 7, 6, 5]));
assert.equal(bytes, 1 + 12 + 5 + 16); // 0x02 | IV | ct | tag
assert.deepEqual([...(await log.getImage(cid))], [9, 8, 7, 6, 5]);
assert.equal(await log.pinImageIfLocal(cid), true);
const dag = await log.imageDagBlocks(cid);
assert.equal(dag.length >= 1, true);
// the identity record block is addressable as a base58btc CID
const mib = log.myIdentityBlock();
assert.equal(typeof mib.cid.toString(), "string");
assert.equal(mib.bytes instanceof Uint8Array, true);
await log.close();
await net.close();
});
test("THE TWO-STACK LAW: only the v2 native is in this process (house rule 11)", () => {
const require = createRequire(join(DIST, "..", "package.json"));
const loaded = Object.keys(require.cache);
// the run above brought up the real @libp2p/webrtc transport
assert.equal(
loaded.some((m) => m.includes("@ipshipyard/node-datachannel")),
true,
"the v2 native (@ipshipyard/node-datachannel) should be loaded",
);
// ... and peer-kit's stack must be nowhere near it: two builds of
// libdatachannel in one process abort it (ThreadSafeCallback cancellation)
assert.equal(
loaded.some((m) => /node_modules\/node-datachannel\//.test(m)),
false,
"the UNSCOPED node-datachannel (peer-kit's v3 native) must never load here",
);
assert.equal(loaded.some((m) => m.includes("ardegazu-peer-kit")), false);
});
|