omnius-fabric
v0.12.9
Published
Omnius Fabric: install-to-node encrypted cooperative routing fabric with automatic UPnP, bootstrap discovery, peerbook/DHT building, relay fallback, QR-based connect handshake, rolling-key sessions, and E2E addressable messaging.
Maintainers
Readme
Omnius Fabric
Install-to-node encrypted cooperative routing fabric for Node.js. One
npm install turns a machine into a reachable peer: it self-maps a public
port, bootstraps from signed HTTPS manifests, IPFS peer indexes, NKN
route-sync, libp2p discovery and NATS only as a final fallback, relays for
NATed peers, and exchanges end-to-end encrypted messages
— addressable by a signed fabric:// ticket.
Architecture
Each node runs a libp2p peer with the services below. The fabric is self-provisioning: any node that becomes publicly reachable (via UPnP/NAT-PMP) automatically serves as a circuit relay for peers that cannot map a port. Bootstrap is hybrid and signed by default: a fresh node first replays its persisted peerbook, then imports signed peer indexes, fetches signed HTTPS manifests, mirrors peer indexes through IPFS, exchanges route-sync over NKN, and uses libp2p DHT/GossipSub/mDNS/relay discovery. Public/operator NATS remains enabled for resilience, but it is treated as the last-resort discovery and encrypted inbox path, not a requirement for healthy startup.
| Service | Purpose |
|---|---|
| KadDHT | Distributed hash table for peer discovery and route-record storage (/omnius/fabric/kad/1.0.0) |
| GossipSub | Pub/sub mesh for route announcements, peer exchange, and metadata |
| pubsub peer discovery | Peers announce multiaddrs over a well-known topic; learned peers are dialed and seed the DHT |
| Circuit Relay v2 | Relay reservations for NATed peers — client, server, or both (default both) |
| DCUtR | Direct Connection Upgrade through Relay — hole-punches NATed peers after relay rendezvous |
| Noise | Transport-level encryption (XX handshake, Curve25519, AEAD) |
| Yamux | Stream multiplexing over a single connection |
| Identify | Protocol and multiaddr exchange between connected peers |
| mDNS | Local-network peer discovery (LAN/zeroconf) |
| Bootstrap | Static bootstrap peer list for initial DHT seeding (when configured) |
| NKN overlay | Name-resolved public p2p mesh used as an additional encrypted delivery fallback; advertised through signed nkn route hints |
| IPFS / Kubo | Content-addressed storage for signed peer indexes and peer-DHT snapshots |
| Signed manifests | Trusted Ed25519-signed bootstrap manifests and peer-index CID mirrors |
| Ping | Connectivity health checks |
Libp2p transports: TCP, WebSocket, and Circuit-Relay (client/both modes). NATS acts as an out-of-band fallback rendezvous plus encrypted inbox. NKN is a daemon-level overlay carried in signed address route hints and route-sync messages, not a libp2p multiaddr transport.
All protocol identifiers use the /omnius/fabric/... namespace:
| Protocol | String |
|---|---|
| DHT | /omnius/fabric/kad/1.0.0 |
| Direct Message | /omnius/fabric/dm/1.0.0 |
| Session | /omnius/fabric/session/1.0.0 |
| Route Sync | /omnius/fabric/route-sync/1.0.0 |
| Connection Upgrade | /omnius/fabric/upgrade/1.0.0 |
| Media Transfer | /omnius/fabric/media/1.0.0 |
| Relay Probe | /omnius/fabric/relay-probe/1.0.0 |
| Meta (pubsub) | /omnius/fabric/meta/1.0.0 |
| Routes (pubsub) | /omnius/fabric/routes/1.0.0 |
| PeerX (pubsub) | /omnius/fabric/peerx/1.0.0 |
| Relays (pubsub) | /omnius/fabric/relays/1.0.0 |
Current feature snapshot
- default-on hybrid bootstrap: persisted peerbook, cached signed peer indexes, signed HTTPS manifests, IPFS CID mirrors, NKN route-sync, libp2p DHT/GossipSub/mDNS/relay discovery, then NATS only as the final fallback
- default-on reachability services: UPnP/NAT-PMP, Circuit Relay v2 in
bothmode, Tor onion reachability, NKN overlay, local IPFS/Kubo storage, and auto-update provenance checks - signed bootstrap trust: committed bootstrap public key, signed manifest,
signed peer-index validation, expired/tampered/unsigned rejection by default,
and independent verification of every returned
fabric://address - route-aware chat across GUI, TUI, REST, and MCP: per-message transport attribution, failed fallback attempts, route probing, discovery refresh, and endpoint diagnostics
- agent-readable local control plane: REST catalog plus MCP tools/resources for status, peers, chat, sessions, media, trust upgrades, discovery, routes, DHT, endpoint health, and settings
- security hardening: loopback-only API binding, restricted browser origins, header/session token use after initial UI handoff, bounded request/media body sizes, double-signed trust grants, and digest-checked media transfers
Discovery cascade
Peers are discovered through every available channel:
- Persisted — previously known peers from the local peerbook (
peers.json) - Cached signed peer indexes — the last accepted peer-index document is replayed before any public service is needed.
- Signed HTTPS manifests — default manifest:
https://raw.githubusercontent.com/robit-man/omnius-fabric/main/bootstrap/manifest.v1.json. Unsigned manifests are rejected by default. - IPFS peer-index mirrors — CIDs from settings/manifests/NKN responses
are fetched from local Kubo, signature-checked, and every returned
fabric://address is independently verified before storage. - NKN route-sync — signed
route-sync-request/route-sync-responsemessages exchange peer indexes and relay hints with manifest-listed NKN bootstrap peers. - libp2p bootstrap + DHT — configured bootstrap multiaddrs, KadDHT random walk, and signed route-record storage.
- GossipSub peer exchange — route-record gossip + pubsub peer discovery.
- mDNS — local-network multicast discovery (zeroconf).
- Relay reservation discovery — reachable nodes serve reservations; private nodes reserve from discovered fabric relays.
- NATS fallback — public/operator NATS route gossip and encrypted inbox only after the above sources are unavailable or incomplete.
Transport selection
When delivering a message the daemon walks a cascade, preferring the most direct path and falling back only as needed:
- Direct libp2p — dial the peer's
fabric://route multiaddrs (public IP or UPnP-mapped) over TCP/WebSocket. - Circuit Relay v2 + DCUtR — reach NATed peers through a relay, then hole-punch to a direct path where the NATs allow it.
- Tor onion route — hidden-service reachability when the peer advertises an onion route.
- NKN overlay — universal fallback. If the recipient's signed address
carries an unexpired
nknhint and this daemon's overlay client is live, the same encrypted envelope is sent to the recipient's NKN address. No public IP, open port, relay reservation, or NATS inbox reachability is required between the two peers. - NATS inbox — absolute last resort. The already-encrypted envelope is published to the recipient's inbox subject; NATS brokers see only opaque ciphertext.
Connection tiers (trust upgrade)
Relationships between peers are tiered and established by a mutual, double-signed handshake — neither side can fabricate a relationship alone:
| Tier | Name | Unlocks | How established |
|---|---|---|---|
| T0 | Discovered | route exchange, relay, basic E2E messaging | automatic — the open fabric |
| T1 | Trusted | richer messaging, scoped capability delegation | requester asks → target approves → both countersign a TrustGrant |
| T2 | Media | file, image, and video sharing in chat (media:share) | requester asks → target approves → both countersign a TrustGrant |
| T3 | Administrative | service exposure and scoped administrative access (e.g. shell:exec) | T2-style handshake at the highest bar; target opts in |
Grants carry an Ed25519 signature from both parties (granter +
grantee), a capability scopes[] list, and an expiry. Either side can
revoke; revocation is gossiped to the peer and the relationship downgrades
immediately. This is exposed today over the token-gated control API:
POST /api/upgrade/request { peerId, tier, scopes?, reason?, address? }
POST /api/upgrade/respond { requestId, decision: approve|deny, scopes? }
GET /api/relationships
POST /api/relationships/revoke { peerId, grantId? }
GET /api/notifications # pending inbound/outbound requests
GET /api/remote/peers # peers who granted us administrative scopeA grant only authorizes the relationship; any privileged action layered on top (such as an SSH passthrough) still performs its own authentication.
Chat media transfer
Tier 2 unlocks media sharing in the web chat. Dropping one or more files onto
an active chat either requests the Tier 2 media upgrade, or, when the
relationship already has media:share, stages the files through the local
daemon in disk-backed chunks. The sender then creates a media-offer control
message over the existing end-to-end encrypted DM channel. The recipient sees
an incoming file bubble and explicitly accepts or denies it. Only after
acceptance does the sender push the bytes over the dedicated libp2p media
stream:
/omnius/fabric/media/1.0.0The media stream is authenticated against the libp2p peer id carried in the
accepted offer's signed route. Files are sent as ordered chunks, reassembled to
a .part file, and promoted only after the receiver verifies the byte count
and SHA-256 hash from the offer. Received files are stored under the local data
directory and downloaded through the token-gated loopback control API without
buffering the whole file in memory. Images show an inline preview after
receipt; all file types can be downloaded.
Media control API:
POST /api/media/offer { peerId, filename, mime, dataBase64, address? }
POST /api/media/upload/start { peerId, filename, mime, size, address? }
POST /api/media/upload/chunk?transferId=<id>&offset=<n> raw bytes
POST /api/media/upload/finish { transferId }
POST /api/media/accept { transferId }
POST /api/media/deny { transferId }
GET /api/media/download/<transferId>Bulk file transfer is intentionally gated on media:share; T0/T1 peers can
still chat, but cannot offer files.
Signed route records
Every node periodically publishes a SignedRouteRecord to the DHT and
GossipSub topics. The record is an Ed25519-signed JSON envelope:
schema: omnius:fabric:route-record:v1
peerId + optional nickname + signing + encryption public keys
directAddrs + relayAddrs (libp2p multiaddrs)
observedAddrs (what other peers see)
preferredRelays, capabilities, role, seq, issuedAt, expiresAt
Ed25519 signature over canonical JSON bodyRecords are stored in the DHT at /omnius/fabric/route/<peerId> and gossiped
on topic /omnius/fabric/routes/1.0.0.
Install
npm install -g omnius-fabricOn first install, the package starts a background fabric node automatically. That node:
- creates
~/.omnius-fabric/default - generates/persists Ed25519 + X25519 identity keys
- starts a libp2p node (TCP + WebSocket transports, KadDHT, GossipSub)
- attempts UPnP/NAT-PMP port mapping by default, and auto-serves as a relay if it succeeds
- loads the default signed HTTPS bootstrap manifest and rejects unsigned bootstrap data by default
- starts local IPFS/Kubo storage for signed peer indexes and peer-DHT snapshots unless disabled
- starts Tor onion reachability unless disabled
- starts the NKN overlay by default and stores its seed under the data dir
- exchanges signed NKN route-sync peer indexes with bootstrap peers
- keeps public/operator NATS as last-resort discovery and encrypted inbox fallback
- publishes a signed route record to DHT + GossipSub
- starts building
peers.jsonas the local peerbook - writes
state.jsonsofabric address/fabric statuswork from any shell - exposes a loopback HTTP control endpoint (token-gated) for
fabric send/fabric connect/ the web console
Upgrades replace the running daemon: the postinstall stops any stale fabric
daemon (graceful SIGTERM, then SIGKILL) and spawns the new version, so a
npm install -g omnius-fabric@latest never leaves an old process running
outdated code.
Running daemons also self-check for npm releases. By default the daemon polls
omnius-fabric@latest once per hour; when a newer version appears it starts
npm install -g omnius-fabric@<exact-version> in the background after npm
reports valid tarball integrity/shasum metadata. The package
postinstall then stops the stale daemon and starts the freshly installed
version with the same data directory, so the fabric stays current without a
manual fabric stop && npm install && fabric start cycle. Status is exposed
as autoUpdate in fabric status and in the web console.
Opt out of first-install background start:
OMNIUS_FABRIC_START_ON_INSTALL=0 npm install -g omnius-fabricOpt out of daemon self-updates:
OMNIUS_FABRIC_AUTO_UPDATE=0 fabric start
fabric start --no-auto-updateVerify it is running
fabric status # JSON daemon snapshot
fabric address # just the fabric:// address
fabric peers # learned peer book entries
fabric dht # real KadDHT routing table (or peerbook fallback)
fabric doctor # full diagnostics: libp2p, DHT, connections, bootstrap
fabric ui # open the embedded dark-mode web console
fabric tui # open the terminal-native console (also the no-arg default)
fabric stop # gracefully terminate the daemonWeb console
Every daemon serves a self-contained single-page console on a loopback port.
fabric ui prints (and opens) a one-time URL of the form
http://127.0.0.1:<port>/?token=<token> — the page captures the token from
the query string, strips it from the address bar, then talks to the daemon's
token-gated control API. The control API is bound to 127.0.0.1 only, so
other hosts on your LAN cannot reach it.
The console is a monochrome (black / mid-grey) SPA with:
- a collapsible left sidebar with hash-routed tabs and a live endpoint
list (UPnP, relay, Tor, NATS, NKN, KadDHT); the top-left brand shows the
running
omnius-fabricversion - hash routes for every tab (
#/status,#/peers,#/chat,#/sessions,#/connect,#/privileges,#/graph,#/settings,#/endpoint?kind=tor) with query-param deep-links (#/chat?peer=nf_…,#/connect?token=…) - clickable endpoint detail/config pages for libp2p, KadDHT, NATS, NKN,
IPFS/storage, hybrid discovery, Tor, UPnP/NAT-PMP, and Circuit Relay. Long
endpoint values such as
.onionaddresses are ellipsized in the sidebar and expanded on the detail page. - hybrid discovery health in Status: signed bootstrap source, peer-index CID, signature status, NKN seeds, discovery order, route order, and NATS fallback role.
- a Chat view with per-peer conversation panes, on-disk persistence, and transport-endpoint icons per peer. Peer nicknames advertised by the originator appear in chat and can be used as a search term.
- message delivery details in chat: a small info icon next to each timestamp opens a hover card showing delivery state, transport, route, latency, and raw attempt strings (for example direct libp2p, route multiaddr, circuit relay, or NATS inbox fallback)
- a connection upgrade dropdown in chat for requesting T1, T2, or T3, plus an inline approve/deny banner for inbound upgrade requests
- Tier 2 media chat: attach or drag/drop one or more files/images/videos
after a
media:shareupgrade, auto-request Tier 2 when media is not yet allowed, accept or deny inbound file offers, preview received images, and download accepted files. Large files use disk-backed chunk upload, chunked libp2p transfer, and SHA-256 reassembly verification. - a Sessions view with a quick request row: type a nickname, peer id, or
full
fabric://address, send a session request, approve/deny inbound session requests, and cancel/delete pending or active sessions. - a Settings view to toggle UPnP, Tor, NATS (and its public defaults),
NKN overlay settings, relay mode, capacity, bootstrap, manifest URLs,
automatic update polling, and the node nickname — persisted to
settings.jsonand applied on next restart - automatic update status in Node status, showing current version, latest observed version, last check, next check, and any update error
- a Graph view: a three.js force-directed topology with monochrome splines and animated activity dots
- topbar status pills (online / dht / upnp / nats / nkn / ipfs)
Terminal UI
fabric tui opens a terminal-native console against the running daemon's
loopback control API. Invoking fabric with no subcommand opens the same TUI
when stdout is a TTY; if no daemon is running, it starts the background daemon
first and then opens the TUI. Non-interactive shells still get command help.
The TUI is read/write where the control API supports it and otherwise mirrors the browser console with keyboard navigation:
- status dashboard with online, DHT, UPnP, NATS, NKN, IPFS, Tor, peer-index CID/signature, and hybrid bootstrap health
- peerbook table with nicknames, route counts, and last-seen data
- chat peer list, transcript view, and encrypted DM composer
- left/right arrows shift pane focus, up/down move within the active pane,
Enter focuses/sends in chat,
iremains only a shortcut, andoopens the browser GUI from the TUI - sessions and notifications views for rolling-encrypted session state
- read-only settings summary for daemon configuration, including NKN state
REST and MCP agent surface
The loopback control port exposes a machine-readable REST catalog and a Model Context Protocol endpoint for agents. Both surfaces use the same one-time daemon token as the GUI:
Authorization: Bearer <token>
x-fabric-token: <token>REST discovery:
GET /api/capabilities # REST catalog + MCP connection guidance
GET /api/rest # alias for the catalog
GET /api/endpoints # computed libp2p/discovery/upnp/relay/tor/nats/nkn/ipfs/storage/dht states
GET /api/endpoints/<kind> # endpoint detail
GET /api/discovery # signed manifest/IPFS/NKN/libp2p/NATS fallback health
POST /api/discovery/refresh
GET /api/routes/resolve?peerId=<peerId>
POST /api/routes/probe # { target|peerId|address }The MCP streamable HTTP endpoint is:
POST /mcpInitialize with protocol version 2025-11-25, then use tools/list,
tools/call, resources/list, resources/read,
resources/templates/list, prompts/list, and prompts/get. The server
returns an MCP-Session-Id header on initialize, supports
MCP-Protocol-Version: 2025-11-25, requires
Accept: application/json, text/event-stream, supports JSON responses,
single-event SSE responses, JSON-RPC batches, notification 202 Accepted
responses, and DELETE /mcp session termination, validates browser Origin
headers for the local endpoint, and exposes REST-equivalent tools for status, peers,
sessions, chat, trust upgrades, media offer/accept, settings, privileges,
WebRTC status, DHT, hybrid discovery, route probing, and endpoint diagnostics.
Agent guidance is available as both a resource and prompt:
resources/read omnius://guide
resources/read omnius://discovery
prompts/get omnius-agent-guideTransport-level encryption is handled by Noise (XX handshake, Curve25519, AEAD). Application payloads are additionally encrypted with the session ratchet (AES-256-GCM + HKDF-SHA512). Relay nodes cannot decrypt application data.
UPnP / NAT-PMP
The daemon attempts UPnP gateway discovery first, then NAT-PMP/PCP as a
fallback (many consumer routers fail SSDP but answer NAT-PMP cleanly). The
discovery budget is 30 s per protocol. The current mapping state is visible
in fabric status and in the web console.
When UPnP succeeds, the mapped external address is added to the node's route
record and published via DHT/GossipSub, making the node reachable from outside
the local network. A reachable node also auto-serves circuit-relay
reservations (relay mode defaults to both), so it immediately helps NATed
peers join the fabric. The mapping maps the actual libp2p-bound TCP port,
which can differ from the requested one if the port was taken at startup.
QR connect handshake (rolling-encrypted sessions)
The simplest path to a rolling-encrypted channel between two peers.
Issue an invite
fabric connectfabric connect prints a scannable QR code in the terminal followed by a
fabric+invite://… token. The invite contains:
- the issuer's signed
fabric://address - a single-use 256-bit pre-shared key (PSK)
- an X25519 ephemeral public key bound to this invite
- a deterministic session id
- issued/expires timestamps
- an Ed25519 signature over the body
The PSK + ephemeral keys are stored locally under
~/.omnius-fabric/default/sessions/<sessionId>.json. The invite is consumed
exactly once by the joiner.
Consume the invite
On the other peer:
fabric join 'fabric+invite://…'This:
- Verifies the invite signature and freshness.
- Generates the joiner's own X25519 ephemeral keypair.
- Derives the session secret with HKDF-SHA512 over two Diffie-Hellman shared secrets (
static×static+ephemeral×ephemeral) salted with the PSK. - Saves a local session record with both directions of the ratchet chain key.
- Sends a signed
session-claimenvelope to the issuer through the libp2p DM protocol.
The issuer's running daemon recognises the claim, matches the session id,
derives the same secret, marks the invite consumed, and emits a signed
session-ack. From that moment both sides hold matching forward-secret chain
keys.
Send rolling-encrypted messages
fabric sessions # list local sessions and their state
fabric send-session <sessionId> hello worldEach message advances a one-way HKDF ratchet: the chain key is overwritten after every send, so an attacker who compromises a chain key cannot decrypt prior messages. Inbound messages are dispatched by the running daemon and printed to its stdout/log.
Under the hood every session message is double-wrapped: the outer envelope is encrypted to the recipient's static X25519 identity (and signed with the sender's Ed25519 identity), and the inner payload is AES-256-GCM with the per-message ratchet key. A relay sees only the outer ciphertext.
Command name
Use the shorthand command:
fabricThe package also exposes the long bin:
omnius-fabricOne-node start
fabric startThe node will:
- generate/persist an Ed25519 + X25519 identity under
~/.omnius-fabric/default - start a libp2p node (TCP + WebSocket, KadDHT, GossipSub, Circuit Relay, DCUtR)
- attempt UPnP/NAT-PMP TCP port mapping automatically
- join bootstrap discovery (persisted peerbook, signed HTTPS manifests, IPFS peer indexes, NKN route-sync, libp2p bootstrap, DHT/GossipSub/mDNS/relay, then NATS fallback) unless disabled
- start the NKN overlay unless disabled, and publish an
nknroute hint when the overlay address is ready - start local IPFS/Kubo storage unless disabled and publish signed peer-index CIDs when pinning succeeds
- start Tor onion reachability unless disabled
- publish a signed route record to DHT + GossipSub
- print a signed
fabric://address ticket - listen for encrypted inbound messages via libp2p DM protocol handler
- discover peers via KadDHT, GossipSub, mDNS, and bootstrap
- persist the local peerbook as it learns routes
- write
~/.omnius-fabric/default/state.jsonso other shells can find it - expose a loopback HTTP control endpoint (token-gated) for
fabric send/fabric connect - poll npm for new releases and replace the daemon automatically unless disabled
Run as a background daemon manually:
fabric start --daemonLogs are written under:
~/.omnius-fabric/logs/Relay mode
The daemon's circuit relay behaviour is controlled by --relay-mode. The
default is both: any node that becomes reachable auto-serves relay
reservations for peers that can't map a port, while still using other relays
itself. NATed nodes pay no cost — no peer dials them for a reservation.
fabric start --relay-mode both # host AND use relays (default)
fabric start --relay-mode server # host reservations only
fabric start --relay-mode client # connect via relays, don't hostWith relay capacity (max concurrent reservations):
fabric start --relay-mode both --relay-capacity 16Bootstrap discovery
By default, fabric start loads bootstrap peers from multiple sources:
- CLI flags:
--relay,--nats(libp2p multiaddrs and/or NATS servers) - env vars:
OMNIUS_FABRIC_RELAYS,OMNIUS_FABRIC_NATS ~/.omnius-fabric/default/bootstrap.json- cached signed peer indexes from prior runs
- signed HTTPS bootstrap manifests, including the default GitHub raw manifest
- IPFS peer-index CIDs from settings, manifests, and NKN route-sync responses
- NKN route-sync request/response with bootstrap peers
- libp2p bootstrap service (
@libp2p/bootstrapwith configured multiaddrs) - GossipSub peer exchange + pubsub peer discovery + DHT walking
- mDNS + relay reservation discovery
- public/operator NATS (
demo.nats.ioby default) only as final fallback discovery and encrypted inbox
Unsigned bootstrap manifests are rejected by default. Signed peer indexes are
accepted only after their Ed25519 signature is valid, their issuer peer id
matches the signing key, they are not expired, and every returned fabric://
address verifies independently. NATS is useful as one more resilience layer,
but a healthy mesh should continue discovery and chat with NATS disabled once
signed manifest/IPFS/NKN/libp2p paths have peers.
Disable the public defaults / bootstrap entirely:
fabric start --no-bootstrap # isolated/local tests
OMNIUS_FABRIC_DEFAULT_NATS=0 fabric start # keep bootstrap, drop public NATSAdditional bootstrap env vars:
OMNIUS_FABRIC_BOOTSTRAP_URLS=https://example.com/manifest.v1.json
OMNIUS_FABRIC_BOOTSTRAP_IPFS_CIDS=bafy...
OMNIUS_FABRIC_NKN_SEEDS=omnius.<pubkey-hex>
OMNIUS_FABRIC_TRUSTED_MANIFEST_KEYS='-----BEGIN PUBLIC KEY-----...'
OMNIUS_FABRIC_REQUIRE_SIGNED_MANIFEST=0 # unsafe compatibility overrideNKN overlay fallback
NKN is enabled by default as an additional p2p delivery path. The daemon
creates or reuses a persistent seed at <dataDir>/nkn-seed, starts an
nkn-sdk MultiClient with the omnius identifier and four sub-clients, and
reports live overlay state in fabric status under nkn.
When the NKN client connects early enough during startup, the daemon includes
an unexpired route hint like this in the signed fabric:// address:
{ "type": "nkn", "role": "nkn", "url": "omnius.<pubkey-hex>", "score": 70 }Delivery still prefers existing/direct libp2p routes, circuit relay/DCUtR,
and Tor first. NKN is tried before NATS as a universal fallback for peers
whose signed address contains an nkn hint. The payload is the same signed,
end-to-end encrypted envelope used everywhere else; NKN nodes route opaque
ciphertext.
NKN also participates in bootstrap. Bootstrap peers can exchange signed route-sync messages:
{ "kind": "route-sync-request", "peerId": "nf_...", "address": "fabric://...", "want": "peer-index" }
{ "kind": "route-sync-response", "peers": ["fabric://..."], "relays": ["..."], "peerIndexCid": "bafy..." }Every route-sync message is signed by the sender identity and expires quickly. Returned addresses are verified before the peerbook stores them. Message delivery order is: existing libp2p, direct libp2p, circuit relay/DCUtR, Tor, NKN overlay, then NATS inbox as absolute last resort.
Disable NKN for isolated tests or private/offline deployments:
fabric start --no-nknNetwork characteristics at scale
Fabric is intentionally not a full-mesh broadcast system. A node stores a
bounded peerbook, joins bounded libp2p routing/gossip structures, and only
falls back to heavier paths when direct reachability fails. Current local
guards include a 2,048-peer peerbook/index cap, 512 relay/seed entries per
signed peer index, relayCapacity: 16 by default, nknNumSubClients: 4 by
default, and maxUploadBytes: 536870912 for local media staging.
The largest bandwidth consumers are workload-dependent:
- Relayed traffic and accepted media transfers — highest potential usage. A relay forwards ciphertext for other peers, so relay bandwidth is roughly the bytes forwarded in each direction plus libp2p framing. Media can saturate the available link if users accept large transfers.
- IPFS/Kubo — peer-index snapshots are small JSON documents, but Kubo can maintain swarm connections and may move more data when operators pin, mirror, or fetch larger CIDs.
- NKN overlay — four default sub-clients keep persistent overlay connectivity and add route-sync/message overhead. It is normally modest compared with relay/media traffic, but more active NKN fallback traffic raises usage.
- libp2p DHT/GossipSub/mDNS — steady background discovery, routing-table maintenance, and route gossip. Growth is bounded by routing-table and mesh behavior rather than every node sending to every other node.
- Tor hidden-service reachability — modest idle traffic; message traffic pays Tor path overhead when the onion route is selected.
- NATS fallback — low idle usage and low-priority route gossip/inbox traffic because it is used only after signed/IPFS/NKN/libp2p paths are not enough.
Practical per-node expectations are order-of-magnitude estimates:
| Scenario | Expected network shape | |---|---| | Private desktop, no chats/media | Usually low steady-state traffic, often single-digit to tens of KB/s aggregate after startup, with short bursts during DHT walks, NKN connect, Kubo swarm connect, and manifest/index refresh. | | Reachable node serving relay reservations | Same idle baseline plus whatever ciphertext it forwards. Relay nodes should expect user traffic, media, and failed-direct fallback traffic to dominate. | | 100-1,000 peer mesh | Nodes do not connect to all peers. Peer knowledge spreads through DHT, GossipSub, signed peer indexes, NKN route-sync, and relay discovery; per-node load is mostly bounded by local caps and churn. | | 10k+ peer network | Run multiple signed bootstrap anchors, IPFS mirrors, NKN bootstrap seeds, and reachable relays. Tune relay capacity, peer-index refresh cadence, NKN sub-client count, Kubo swarm limits, and optional operator NATS capacity. |
Signed manifests and peer indexes protect bootstrap integrity, not availability. At larger scale, run more than one trusted manifest URL, publish peer-index CIDs through IPFS, list multiple NKN bootstrap peers, and keep NATS available only as a final resilience path. If public NATS is shut down, nodes that already know signed peers through persisted state, manifests, IPFS, NKN, or libp2p should continue discovering and delivering over those paths.
Two-node workflow (no QR)
Terminal A:
fabric startCopy the printed fabric://... address.
Terminal B:
fabric send 'fabric://PASTE_ADDRESS_FROM_A' 'hello from B'Terminal A receives and decrypts the message. The daemon delivers it via the
libp2p DM protocol (/omnius/fabric/dm/1.0.0) using the peer's multiaddrs
from the address ticket.
Three-node fabric workflow
Node A acts as a full node and also as a reachable relay server:
fabric start --port 7766 --relay-mode bothNodes B and C join A as relay clients:
fabric start --relay ws://YOUR_PUBLIC_HOST_OR_IP:7766All three now:
- register signed route records in DHT + GossipSub
- exchange signed bootstrap and route data through the available channels: cached peerbook, signed manifests/IPFS peer indexes, NKN route-sync, KadDHT/GossipSub/mDNS, relay discovery, and NATS only if the earlier paths are not enough
- persist learned peers in
peers.json - can send encrypted messages through direct libp2p, relay/DCUtR, Tor, NKN, or final-resort NATS inbox routes
Inspect learned peers:
fabric peersInspect real KadDHT routing table:
fabric dhtRun full diagnostics:
fabric doctorSend to a full address:
fabric send 'fabric://...' 'message'Send to a learned peer ID:
fabric send nf_abc123... 'message'Diagnostics
The doctor command runs comprehensive diagnostics against the running daemon:
fabric doctorOutput includes:
{
"running": true,
"dataDir": "/home/user/.omnius-fabric/default",
"diagnostics": {
"startedAt": 1716800000000,
"pid": 12345,
"peerId": "nf_abc123...",
"multiaddrs": ["/ip4/192.168.1.5/tcp/7766", "/ip4/127.0.0.1/tcp/7767/ws"],
"connections": 3,
"dht": {
"ready": true,
"routingTableSize": 8,
"peersInTable": ["12D3K...", "16Uiu2..."],
"mode": "server"
},
"bootstrap": {
"sources": ["signed-manifest", "ipfs-peer-index", "nkn-route-sync"],
"peerCount": 5,
"errors": []
},
"discovery": {
"bootstrap": "ready",
"peerIndexCid": "bafy...",
"signatureStatus": "valid",
"routeOrder": ["libp2p", "direct", "relay", "tor", "nkn", "nats"],
"natsRole": "last-resort fallback"
},
"controlPort": 7765,
"upnp": { "ok": true, "externalAddress": "203.0.113.5", "protocol": "nat-pmp" },
"tor": { "available": true, "onionAddress": "...", "socksPort": 9050 },
"ipfs": { "enabled": true, "available": true, "api": "http://127.0.0.1:5001" },
"storage": { "latestPeerIndexCid": "bafy..." },
"nkn": {
"enabled": true,
"connected": true,
"address": "omnius.<pubkey-hex>",
"identifier": "omnius",
"numSubClients": 4
}
}
}The dht command provides a quick view of KadDHT state, or falls back to
peerbook data when no daemon is running:
fabric dhtAutostart
Enable user-level autostart:
fabric autostart enableWith relay/NATS hints:
fabric autostart enable --relay ws://seed.example.com:7766 --nats nats://seed.example.com:4222Status:
fabric autostart statusDisable:
fabric autostart disableAutostart targets:
- Windows: Startup
.cmd - macOS: user LaunchAgent
- Linux: user systemd service file
Linux note: after enabling, run the command printed by the CLI, usually:
systemctl --user daemon-reload && systemctl --user enable --now omnius-fabric.serviceEnvironment variables
OMNIUS_FABRIC_START_ON_INSTALL=0 disable postinstall background start
OMNIUS_FABRIC_DEFAULT_NATS=0 disable default NATS discovery
OMNIUS_FABRIC_BOOTSTRAP=0 disable all automatic bootstrap config
OMNIUS_FABRIC_RELAYS=ws://host:7766,ws://host2:7766
OMNIUS_FABRIC_NATS=nats://host:4222,nats://host2:4222
OMNIUS_FABRIC_NATS_SUBJECT=omnius.fabric.routes
OMNIUS_FABRIC_BOOTSTRAP_URLS=https://example.com/manifest.v1.json
OMNIUS_FABRIC_BOOTSTRAP_IPFS_CIDS=bafy...
OMNIUS_FABRIC_NKN_SEEDS=omnius.<pubkey-hex>
OMNIUS_FABRIC_TRUSTED_MANIFEST_KEYS='-----BEGIN PUBLIC KEY-----...'
OMNIUS_FABRIC_REQUIRE_SIGNED_MANIFEST=0
OMNIUS_FABRIC_AUTO_UPDATE=0 disable daemon release pollingLegacy aliases are also accepted:
FABRIC_START_ON_INSTALL
FABRIC_DEFAULT_NATS
FABRIC_BOOTSTRAP
FABRIC_RELAYS
FABRIC_NATS
FABRIC_NATS_SUBJECTPersisted configuration
Beyond env vars and one-off CLI flags, the daemon persists preferences to
~/.omnius-fabric/<data>/settings.json (mode 0600). The Settings tab in the
web console reads and writes these; a CLI flag always overrides the persisted
value for that one invocation. Changes apply on the next daemon restart —
libp2p services are configured at startup and can't be hot-swapped.
| Setting | Default | Effect |
|---|---|---|
| nickname | "" | Public node display name advertised in signed addresses and route records |
| upnpEnabled | true | Attempt UPnP/NAT-PMP port mapping |
| torEnabled | true | Spawn a Tor onion hidden service |
| ipfsEnabled | true | Start/monitor local Kubo/IPFS storage |
| relayMode | both | Circuit-relay role: client / server / both |
| relayCapacity | 16 | Max concurrent relay reservations |
| autoRelayOnReachable | true | Auto-host relay once publicly reachable |
| useDefaultNats | true | Merge public NATS as final fallback discovery/inbox |
| natsFallbackOnly | true | Keep NATS low-priority and last in route selection |
| natsServers | [] | Operator NATS servers (added on top of defaults) |
| nknEnabled | true | Start the NKN overlay and advertise an nkn route hint when connected |
| nknDiscoveryEnabled | true | Exchange signed peer indexes over NKN bootstrap route-sync |
| nknIdentifier | omnius | Prefix for this node's NKN address (<identifier>.<pubkey-hex>) |
| nknNumSubClients | 4 | NKN MultiClient sub-client count for overlay reliability |
| nknRpcServers | [] | Explicit NKN RPC servers; empty uses NKN mainnet defaults |
| bootstrapEnabled | true | Load relay/NATS/manifest bootstrap on start |
| bootstrapManifestUrls | [] | Extra signed HTTPS manifest URLs; default public manifest is also loaded |
| bootstrapTrustedKeys | [] | Extra Ed25519 public keys trusted for manifests/peer indexes |
| bootstrapIpfsCids | [] | Signed peer-index CIDs to import before NATS fallback |
| maxUploadBytes | 536870912 | Maximum local control API media upload size |
| port | 7766 | libp2p listen port |
| remoteShellEnabled | false | Master switch for administrative passthrough (off by default) |
Acceptance tests
npm run test:localThis runs:
node dist/cli.js test-local
node dist/cli.js test-triad
node dist/cli.js test-bootstrap-config
node dist/cli.js test-bootstrap-security
node dist/cli.js test-route-order
node dist/cli.js test-connecttest-local proves: libp2p node starts, KadDHT reports status, multiaddrs are announced.
test-triad proves: three libp2p nodes start and verify all are running.
test-bootstrap-config proves: first-run bootstrap discovery loads by default and can be disabled.
test-bootstrap-security proves: signed manifest/index validation rejects
unsigned, tampered, untrusted, expired, and poisoned-address bootstrap data.
test-route-order proves: delivery order keeps NATS last and after NKN.
test-connect proves: invite creation, session key derivation, and ratchet encryption round-trip verified.
Additional local validation used for the web media path starts two runtimes, requests and approves a Tier 2 relationship, stages a file through the chunk-upload API, sends a file offer, accepts it, pushes the chunked media stream, verifies reassembly, and downloads the received file through the control API.
Recent TUI and NKN validation:
npm run build
node dist/cli.js --help
node dist/cli.js start --help
node dist/cli.js start --foreground --port 0 --host 127.0.0.1 --no-upnp --no-tor --no-bootstrap --no-auto-update --no-nkn --no-ipfsnpm run build type-checks and compiles the terminal UI, NKN transport, and
runtime/status/settings integration. The local acceptance tests pass
nkn: false internally for deterministic offline runs; live NKN validation is
a daemon smoke check. With a default start, fabric status should include
nkn.enabled, nkn.connected, nkn.address, nkn.identifier, ipfs,
storage.latestPeerIndexCid, and discovery.routeOrder; when the overlay
connects before the first address publish, fabric inspect "$(fabric address)"
shows a route with "type": "nkn". fabric tui validates the terminal
console against the same token-gated control API used by the browser console.
Security model
Omnius Fabric uses two layers of encryption:
Layer 1: Transport (libp2p Noise)
- Noise XX handshake with Curve25519 key agreement
- AEAD (AES-256-GCM or ChaCha20-Poly1305) per-packet encryption
- Authenticates both sides of every connection
- Relay nodes see only Noise-encrypted streams — they cannot inspect payloads
Layer 2: Application (Session Ratchet)
- X25519 static + ephemeral key agreement
- HKDF-SHA512 key derivation from two Diffie-Hellman shared secrets
- AES-256-GCM per-message encryption with rolling ratchet keys
- Forward secrecy: chain key is overwritten after each send
Route records
- Ed25519 signatures over canonical JSON bodies
- Signed
fabric://address tickets - TTL/replay-expiry fields (default 24h)
Bootstrap trust
- Default HTTPS manifests and peer indexes are Ed25519-signed.
- Unsigned manifests are rejected by default.
- Peer-index documents expire, must match issuer peer id to signing key, and
every returned
fabric://address is independently verified before storage. - NKN route-sync request/response messages are signed and short-lived.
- NATS is a fallback for opaque signed addresses and encrypted inbox payloads; it is not required for normal mesh health.
Metadata and scale considerations
- Bootstrap signatures authenticate who published a manifest or peer index; they do not guarantee that every listed peer is online, reachable, or non-malicious. The daemon still verifies each address and treats discovery data as routing hints, not authority to grant capabilities.
- Relays, NKN, Tor, NATS, and IPFS providers can observe metadata appropriate to their role, such as timing, approximate payload sizes, peer identifiers, CIDs, or connection attempts. They do not receive application plaintext.
- Operators that serve relays should treat relay bandwidth as user traffic and
tune
relayCapacity, process limits, and host egress quotas accordingly. - Public bootstrap anchors can be blocked or unavailable. Production fabrics should publish multiple signed manifest URLs, IPFS mirrors, and NKN bootstrap peers under independent infrastructure.
- Tor is used for hidden-service reachability only. Fabric does not act as a Tor exit relay.
Control plane
- The REST/MCP server binds to loopback and rejects non-loopback browser origins.
- API/MCP calls require
Authorization: Bearer <token>orx-fabric-token: <token>; query tokens are only used for the initial browser UI handoff and are stripped from the address bar. - JSON request bodies and media uploads are bounded by configured limits.
Release updates
- Auto-update checks npm metadata for a concrete latest version and requires valid tarball integrity or shasum metadata before invoking npm.
- Installs use the exact reported version instead of a mutable
@latestinstall target.
Trust and media
- T1/T2/T3 upgrades are double-signed by both peers and expire.
- T2 grants include
media:share; media offers still require recipient acceptance before any bytes are transferred. - Media bytes travel over a dedicated libp2p stream only after acceptance, are checked against the offered SHA-256 digest, and are served to the local browser through the token-gated loopback API.
- T3 grants include administrative scopes such as
shell:exec, but privileged features remain disabled unless their own local settings and authentication checks also pass.
Primitives
- Ed25519 identity/signatures
- X25519 key agreement
- HKDF-SHA512 key derivation
- double AES-256-GCM payload encryption (Noise transport + application ratchet)
- signed route records + invite tokens
- double-signed trust grants and revocations
- SHA-256 verification for accepted media transfers
- TTL/replay-expiry fields
Rolling session forward secrecy
Session secret = HKDF-SHA512(static×static ‖ ephem×ephem, salt = invite PSK, info = sessionId)
Per-direction chain key K0 = HKDF(secret, info = "chain:v1:role")
For message n: (msgKey_n, K_{n+1}) = HKDF(K_n, info = "ratchet:v1")
K_n is overwritten with K_{n+1} after each send — the old key is gone.
Replay rejected by counter monotonicity.Relays can see routing metadata such as connection timing, approximate sizes, and destination peer IDs. They cannot decrypt Noise transport payloads (layer 1), and they cannot derive session secrets (layer 2) without one side's static or ephemeral private key.
Guarantee boundary
Valid claim:
If a recipient's fabric:// address contains at least one route reachable by the sender,
the sender can deliver an end-to-end encrypted message to that recipient.Fabric claim:
As more users run the package, reachable nodes automatically become relay nodes,
private nodes reserve relay paths, and KadDHT/GossipSub route propagation compounds
peer knowledge across the network.Invalid claim:
Two isolated devices behind arbitrary hostile NATs can connect with zero reachable third-party path.A cooperative reachable relay path is required for hostile NAT/CGNAT/corporate networks. Circuit Relay v2 + DCUtR handles most consumer NAT scenarios.
Commands
fabric start [--port N] [--host HOST] [--public-url URL]
[--relay URLS] [--nats SERVERS] [--nats-subject SUBJECT]
[--relay-mode client|server|both] [--relay-capacity N]
[--daemon] [--foreground] [--daemon-child]
[--no-server] [--no-upnp] [--no-tor] [--no-bootstrap]
[--no-auto-update] [--no-nkn] [--no-ipfs]
fabric stop [--force]
fabric address
fabric status
fabric doctor
fabric dht
fabric peers
fabric ui [--print]
fabric tui
fabric connect [--ttl-minutes N] [--label TEXT] [--no-qr]
fabric join <fabric+invite://...>
fabric send-session <sessionId> <message>
fabric sessions
fabric send <fabric://address|peerId> <message>
fabric inspect <fabric://address>
fabric autostart enable|disable|status
fabric test-local
fabric test-triad
fabric test-bootstrap-config
fabric test-bootstrap-security
fabric test-route-order
fabric test-connectMedia transfer is currently exposed through the web console and local control API rather than a dedicated CLI command.
