diff options
| author | Adam Soltys <[email protected]> | 2026-08-02 10:09:42 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-02 10:09:42 -0700 |
| commit | 3205acf157b7f1cfb857a383f374ef4d739d82cc (patch) | |
| tree | c61dd9e51b230106bc3134a22eef774dc1decd8c /tools | |
| parent | 6ebae6bce794c8f2db8337cb189b9095975e36ad (diff) | |
| download | coinosv3-3205acf157b7f1cfb857a383f374ef4d739d82cc.tar.gz coinosv3-3205acf157b7f1cfb857a383f374ef4d739d82cc.zip | |
Chat: Concord encrypted community messaging, with a default coinos room
Implements the Concord protocol (CORD-01..04) — private streams, the
control-plane fold, guestbook membership, owner-rooted authority — and
ships a built-in "coinos" community founded by Adam's npub (owner/admin).
Messages sign with the nostr login identity when a signer is live, else
the wallet's NIP-06 key; all encryption rides derived stream keys so
remote signers only ever sign seals.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/concord-genesis.js | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/tools/concord-genesis.js b/tools/concord-genesis.js new file mode 100644 index 0000000..797902d --- /dev/null +++ b/tools/concord-genesis.js @@ -0,0 +1,85 @@ +// One-time genesis for a Concord community (CORD-02 §1): publishes the two +// owner-signed editions (community metadata + #general) and the owner's Join, +// then prints the join material to embed in the app. +// +// bun run tools/concord-genesis.js <owner-privkey-hex> <name> <relay> [relay...] + +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; +import { getPublicKey } from "nostr-tools/pure"; +import { Relay, useWebSocketImplementation } from "nostr-tools/relay"; +import WebSocket from "ws"; +import { + communityId, + controlKey, + guestbookKey, + makeEdition, + wrapRumor, + msTags, +} from "../src/concord.js"; + +try { useWebSocketImplementation(WebSocket); } catch {} + +let [skHex, name, ...relays] = process.argv.slice(2); +if (!skHex || !name || !relays.length) { + console.error("usage: bun run tools/concord-genesis.js <owner-privkey-hex> <name> <relay...>"); + process.exit(1); +} + +let ownerSk = hexToBytes(skHex); +let owner = getPublicKey(ownerSk); +let ownerSalt = bytesToHex(crypto.getRandomValues(new Uint8Array(32))); +let communityRoot = crypto.getRandomValues(new Uint8Array(32)); +let cid = communityId(owner, ownerSalt); +let generalId = bytesToHex(crypto.getRandomValues(new Uint8Array(32))); + +let control = controlKey(communityRoot, cid, 0); +let guestbook = guestbookKey(communityRoot, cid, 0); + +let now = Date.now(); +let metadata = makeEdition( + { vsk: 0, eid: cid, version: 1, content: JSON.stringify({ name, relays }) }, + now +); +let general = makeEdition( + { vsk: 2, eid: generalId, version: 1, content: JSON.stringify({ name: "general", private: false }) }, + now + 1 +); +let { created_at, ms } = msTags(now + 2); +let join = { kind: 3306, content: "join", tags: [ms], created_at }; + +let events = [ + await wrapRumor({ ...metadata, pubkey: owner }, ownerSk, control, { plaintext: true }), + await wrapRumor({ ...general, pubkey: owner }, ownerSk, control, { plaintext: true }), + await wrapRumor({ ...join, pubkey: owner }, ownerSk, guestbook), +]; + +for (let url of relays) { + try { + let r = await Relay.connect(url); + for (let e of events) await r.publish(e); + r.close(); + console.error(`published ${events.length} events to ${url}`); + } catch (e) { + console.error(`FAILED ${url}: ${e.message ?? e}`); + } +} + +// Join material (CORD-02 §8) — the bundle's membership subset. Contains the +// community_root: embedding it in the app makes every app user a member. +console.log( + JSON.stringify( + { + community_id: cid, + owner, + owner_salt: ownerSalt, + community_root: bytesToHex(communityRoot), + root_epoch: 0, + channels: [{ id: generalId, name: "general" }], + relays, + name, + }, + null, + 2 + ) +); +process.exit(0); |
