chat / client / src / sueta / lib / README.md
  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
# p2p core lib — what is still here, and where the rest went

The shared, app-agnostic core of the rooms stack **is no longer vendored in this
repo**. It is `ardegazu-rooms-kit`'s own sources, reached over the classpath:
`client/package.json` sha-pins the kit, `client/deps.edn` puts
`node_modules/ardegazu-rooms-kit/src` on `:paths`, and the namespaces are
`ardegazu.rooms.js` and `ardegazu.rooms.lib.*`. One fix site, one sha bump, no
byte-identity gate to maintain. Protocol and crypto: `docs/PROTOCOL.md`. Relay
side: `docs/RELAY.md`.

Persistent identity comes from the suite kit (`ardegazu-id-kit`), not from a
local `identity` module.

## From the kit (`ardegazu.rooms.*`)

| Namespace | Job |
|---|---|
| `js` | `ordered` — every wire object is built with sequential `unchecked-set`, because `#js {}` loses literal key order at nine pairs |
| `lib.crypto` | HKDF room derivations (`RoomCrypto`), session `Sealer` (structural nonce safety), room-wide `AtRestCipher` (random IVs) for the log |
| `lib.protocol` | shared wire types: connection states, signal payloads, log operations (documentation only — the TS original compiled to `export {}`) |
| `lib.net` | the libp2p node: relay bootstrap, pubsub peer discovery, WebRTC upgrades, sealed presence/roster, sealed broadcast + directed frame streams, redial/wake/ping resilience |
| `lib.log` | `RoomLog`: the OrbitDB events DB — deterministic address from the secret, encrypted entries, entry emission incl. branch catch-up, encrypted image blobs (unixfs/bitswap) with pin pruning. `open-helia` takes **already-open** stores and names no store package, so the same file serves a tab and a Node process |
| `lib.encryption` | OrbitDB encryption hooks backed by `RoomCrypto` (decrypt-throws = access control) |
| `lib.access` | hardened IPFS access controller: binds `entry.key` to the referenced identity (blocks member-to-member authorship forgery), address-identical to vanilla |
| `lib.orbit-identity` | OrbitDB identity provider chaining log entries to the persistent Ed25519 identity |
| `lib.turn` | ephemeral TURN credential fetch + cache → `RTCConfiguration` |
| `lib.descriptor` | best-effort `/.well-known/ap2p` fetch so the relay operator can rotate keys without a client release |

## Still in this directory (`sueta.lib.*`)

The kit has no counterpart for these; they are chat's.

| File | Job |
|---|---|
| `mailbox.cljs` | the identity-addressed offline mailbox: auth minting, sealed envelopes, send/recv/ack, the durable replay into the log |
| `media.cljs` | call media: per-pair media-only `RTCPeerConnection`s, perfect negotiation, transceiver reuse, sealed SDP over a libp2p stream |
| `selftest.cljs` | dev-mode crypto self-test (incl. cross-instance at-rest roundtrips + dbName determinism) |

`../stores.cljs` is the third app-local piece: the IndexedDB blockstore/datastore
pair `lib.log`'s `open-helia` deliberately does not name (the kit opens its own
on disk).

## Recipe: a new app

```clojure
(ns myapp.boot
  (:require [ardegazu.rooms.js :as j]
            [ardegazu.rooms.lib.crypto :as rc]
            [ardegazu.rooms.lib.descriptor :as descriptor]
            [ardegazu.rooms.lib.log :as rlog]
            [ardegazu.rooms.lib.net :as net]
            [ardegazu.rooms.lib.turn :as turn]
            ;; the IndexedDB store pair — app-local, NOT part of the kit
            ;; (lib.log's open-helia takes already-open stores)
            [myapp.stores :as stores]
            [shadow.cljs.modern :refer (js-await)]))

(def APP-SALT "myapp.example.org/v1") ; ← namespaces ALL crypto derivations
(def NS "myapp")                      ; ← namespaces ALL persistent storage
                                      ;   (localStorage keys, IDB names) so two
                                      ;   apps can share an origin

(defn boot [secret]
  ;; fresh network id per load
  (js-await [session (net/new-session-key)]
    (js-await [room-crypto (rc/create secret APP-SALT (unchecked-get session "peerId"))]
      (js-await [cfg (descriptor/resolve-relay-config
                      (j/ordered
                       ;; any willing relay
                       "relayMultiaddr" "/dns4/signal.ardegazu.ro/tcp/443/tls/ws/p2p/12D3KooW…"
                       "turnCredsUrl" "https://signal.ardegazu.ro/turn-credentials"
                       "discoveryTopic" "_peer-discovery._p2p._pubsub"))]
        (let [ice (turn/IceConfig. (unchecked-get cfg "turnCredsUrl") false)]
          (js-await [net-obj (net/create
                              (j/ordered
                               "privateKey" (unchecked-get session "privateKey")
                               "crypto" room-crypto
                               "ice" ice
                               "relayMultiaddr" (unchecked-get cfg "relayMultiaddr")
                               "discoveryTopic" (unchecked-get cfg "discoveryTopic")
                               "myName" (fn [] "anon")
                               "events" (j/ordered
                                         "peerState" (fn [_ _ _] js/undefined)
                                         "peerGone" (fn [_] js/undefined)
                                         "peerReady" (fn [_] js/undefined)
                                         "message" (fn [from payload]
                                                     (js/console.log "live:" from payload)
                                                     js/undefined)
                                         "binary" (fn [_ _] js/undefined)
                                         "status" (fn [_] js/undefined))))]
            ;; ephemeral traffic: (net/broadcast net-obj …) / (net/send-to net-obj peer …)
            ;; — sealed E2E
            ;;
            ;; durable, replicated state: the encrypted log (optional — skip it
            ;; for live-only apps)
            (js-await [helia (stores/open-idb-helia (unchecked-get net-obj "libp2p")
                                                    (j/ordered "blocks" (str NS "-blocks")
                                                               "data" (str NS "-data")))]
              (js-await [log (rlog/open
                              helia room-crypto nil
                              (j/ordered
                               ;; wire BEFORE replication starts — a
                               ;; late-attached handler loses entries
                               "onEntry" (fn [e]
                                           (js/console.log "log:" (unchecked-get e "hash")
                                                           (unchecked-get e "from")
                                                           (unchecked-get e "op"))
                                           js/undefined)
                               ;; non-members' undecryptable pushes — expected noise
                               "onError" (fn [_] js/undefined)))]
                ;; replay this device's copy, then append
                (js-await [_ (rlog/load-tail log 500)]
                  ;; every LogOp is a WIRE shape: build it with j/ordered, never
                  ;; #js {} (key order goes to hash order at nine pairs)
                  (rlog/append log (j/ordered "t" "chat" "ts" (js/Date.now)
                                              "name" "anon"
                                              "text" "hello, replicated world")))))))))))
```

Rules the lib enforces for you:

- the room secret stays in the URL fragment; everything the network sees is a
  one-way derivation of it
- every payload is AES-GCM sealed — live traffic per sender per session
  (structurally nonce-safe), the log under room-wide keys (random IVs);
  anything that fails to decrypt is dropped, and decryptability IS membership
- every member derives the same log address; non-members' writes are rejected
  at the crypto boundary
- the relay only ever carries encrypted handshakes; room traffic runs
  member⇄member, and relay loss never interrupts established connections

Keep rooms ≤ ~12 peers — it's a full mesh (O(n²) pairs, each broadcast uploads
n−1 times).

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/chat.git