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 | /**
* Boot a real room log and read its OrbitDB address — shared by
* test/vectors/generate-orbit-address.mjs (against the TypeScript build) and
* test/orbit-address.test.mjs (against the ClojureScript build), so both
* implementations are driven through the identical sequence.
*
* THE interop invariant (PROTOCOL.md §5): the address 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 negotiates it and
* nothing fails loudly if it diverges; a diverged build silently replicates a
* database nobody else is in.
*
* What is real here: @orbitdb/core with entry+payload encryption and the sueta
* identity provider, the hardened IPFS access controller, Helia on the
* IndexedDB blockstore the browser build uses (fake-indexeddb standing in for
* the browser's), and a real libp2p — helia gates on `isLibp2p()`, so a stub is
* not an option.
*
* What is not: the libp2p node is built HERE with websockets only, instead of
* through lib/net's `Net.create`. Net's browser stack adds @libp2p/webrtc and
* window listeners, neither of which exists in node; and the address depends on
* the transport list not at all. Net's own behaviour is pinned separately by the
* membership transcripts in test/machines.test.mjs.
*/
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
/** 32 × 0x01, base64url — the seed rooms-kit's fixtures use, so `myAuthorId`
* is directly comparable across the two repos. */
export const SEED = "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE";
/**
* Case 1 and 2 are the same room opened by an identity-carrying and an
* identity-less member: the manifest hashes only {name, type, accessController},
* never the creator, so they MUST land on one address. Case 3 is the same
* secret under a different app salt: a different room.
*
* Cases 1–3 duplicate rooms-kit/test/vectors/orbit-address.json and
* chat/client/test/vectors/orbit-address.json (same secrets, same salts, same
* seed) — board's crypto/access/log modules are byte-identical to both repos'
* ancestors, so all three fixtures must agree, and test/orbit-address.test.mjs
* asserts the two published strings verbatim. Case 3 is chat's own room, so a
* board build must NOT land on it.
*/
export const addressCases = [
{ secret: "B".repeat(43), appSalt: "board.ardegazu.ro/v1", withIdentity: true },
{ secret: "B".repeat(43), appSalt: "board.ardegazu.ro/v1", withIdentity: false },
{ secret: "A".repeat(43), appSalt: "chat.ardegazu.ro/v2", withIdentity: true },
];
/** A real libp2p with node-capable transports only. */
async function nodeLibp2p() {
const { createLibp2p } = await import("libp2p");
const { webSockets } = await import("@libp2p/websockets");
const { noise } = await import("@chainsafe/libp2p-noise");
const { yamux } = await import("@chainsafe/libp2p-yamux");
const { identify } = await import("@libp2p/identify");
const { gossipsub } = await import("@chainsafe/libp2p-gossipsub");
return createLibp2p({
addresses: { listen: [] },
transports: [webSockets()],
connectionEncrypters: [noise()],
streamMuxers: [yamux()],
services: { identify: identify(), pubsub: gossipsub({ allowPublishToZeroTopicPeers: true }) },
});
}
/**
* @param mods {{RoomCrypto, openHelia, RoomLog}} the implementation under test
* @param c one entry of `addressCases`
*/
export async function openAddressCase(mods, c) {
// RoomLog.open defaults its OrbitDB directory base to the relative literal
// "sueta" (no caller here passes a `directory` hook), so it lands under the
// CWD in node — run from a temp dir so nothing is written into the repo.
const cwd = process.cwd();
process.chdir(mkdtempSync(join(tmpdir(), "board-addr-")));
const libp2p = await nodeLibp2p();
try {
const { Identity } = await import("ardegazu-id-kit");
const rc = await mods.RoomCrypto.create(c.secret, c.appSalt, "12D3KooWFixed");
// unique store names per case: two rooms must never share a blockstore
const tag = `${c.appSalt}-${c.withIdentity}`.replace(/[^a-z0-9]+/gi, "-");
const helia = await mods.openHelia(libp2p, { blocks: `b-${tag}`, data: `d-${tag}` });
const identity = c.withIdentity ? await Identity.fromSeed(SEED) : null;
const log = await mods.RoomLog.open(helia, rc, identity, { onEntry() {}, onError() {} });
const result = {
...c,
seed: c.withIdentity ? SEED : null,
dbName: await rc.dbName(),
address: log.address,
// the durable author id: the identity pub for a sueta identity, OrbitDB's
// own per-device id otherwise (non-deterministic, so only its type is kept)
myAuthorId: c.withIdentity ? log.myAuthorId : typeof log.myAuthorId,
};
await log.close();
return result;
} finally {
await libp2p.stop().catch(() => {});
process.chdir(cwd);
}
}
|