qDHT is a Nostr-shaped distributed hash table for identity discovery, route discovery, announcement propagation, and content distribution.
It is not trying to be a generic key/value DHT. The design is centered on signed announcements, metadata search, route reflection, and content pointers rather than raw distributed storage of arbitrary records.
The "quantum" part of qDHT is a propagation model, not quantum hardware.
In the simulation layer, information spreads across a graph using amplitude-like weights instead of a simple flood or hop-count walk. That gives the model a few useful behaviors:
- nearby nodes receive stronger signal than distant nodes
- unpopular or low-reputation paths are damped
- a node can keep a small exploration floor so new paths are still tried
- propagation can be tuned for thresholded spread instead of all-or-nothing broadcast
In practical terms, this is a way to model selective diffusion across a network graph. It is inspired by wave behavior, but it runs as ordinary deterministic software.
The same idea shows up in the simulator and the propagator, not as a claim that qDHT itself is physically quantum.
It is organized around a few bounded layers:
src/corefor protocol, identity, graph, discovery, and content primitivessrc/nodefor the live node runtime, CLI, transports, and persistencewebfor the browser dashboard served by the live nodesrc/simfor the simulation harness and comparison scenariossrc/benchfor qDHT vs baseline benchmark runsexamplesfor runnable demos- optional local-network peer discovery via signed service-record gossip
At a high level:
- A node publishes signed announcements for content, routes, replicas, or requests.
- Those announcements propagate through the network and are persisted locally.
- Identity lookups resolve a pubkey or Nostr reference to a route announcement.
- Metadata searches return matching announcements, route records, or replica hints.
- Content fetches use the announcement metadata to locate providers or replicas.
- The node falls back across WebSocket, relay, and QUIC transports as needed.
This makes qDHT useful as both a discovery layer and a distribution layer.
- Announces and discovers content metadata
- Resolves Nostr identities to routeable endpoints
- Stores content locally and fetches from providers or replicas
- Supports WebSocket, relay, and QUIC transports
- Provides a simulation layer for churn, spam, and baseline comparison
| Topic | Standard DHT | qDHT |
|---|---|---|
| Primary unit | Key/value record | Signed announcement |
| Identity model | Usually network node IDs only | Nostr pubkeys and Nostr identity references |
| Search model | Exact key lookup | Metadata and route lookup |
| Content model | Often stores pointers or provider records | Stores content announcements, replicas, manifests, and provider hints |
| Routing model | Mostly overlay routing for key lookup | Discovery plus route reflection plus transport fallback |
| Transport layer | Typically one stack per implementation | WebSocket, relay, and QUIC can coexist |
| Search surface | Usually bounded to keys and provider records | Bounded request announcements over metadata and routes |
| Content bytes | Usually external to the DHT | Still external; fetched from providers or replicas |
| NAT handling | Often implicit or out-of-band | Observed address reflection and relay fallback are first-class |
| Searchability | Typically not the main goal | Metadata search is a first-class feature |
- It is not a full-text search engine for raw content.
- It is not a generic distributed database for arbitrary records.
- It is not a replacement for content stores or object storage.
- It is not a production proof of superiority over Kademlia.
- It is not a promise that every node is always directly reachable.
Install dependencies and run the test suite:
npm install
npm testRun the live node CLI:
npm run node:startnpm run node:startstart the daemonnpm run node:start -- --web-port 3000start the daemon with the web dashboard enablednpm run node:start -- --local-discoverystart the daemon with local-network peer discovery enablednpm run node:start -- --peer ws://1.2.3.4:7777connect to a specific bootstrap peernpm run web:startstart the daemon with the web dashboard on port 3000npm run example:hellorun the simplest publish/get demonpm run example:reachabilityrun the DNS-less identity-to-route demonpm run example:spamrun the spam-filter comparison demonpm run bench:dhtcompare qDHT against the libp2p Kad-DHT baselinenpm run stress:quicrun the QUIC live-node stress harness
Build the compiled output, then run the deploy script (requires node, npm, git, rsync, systemctl):
# First install
scripts/deploy.sh install --domain qdht.example.com
# Pull latest and restart
scripts/deploy.sh update
# Smoke-test a running node
scripts/deploy.sh testTo deploy a bootstrap node (rendezvous node for peer discovery, no content ops):
scripts/deploy.sh install --domain bootstrap.example.com --bootstrapThe script auto-detects Caddy or nginx for reverse proxy setup. Use --proxy none to skip.
Environment overrides: QDHT_DOMAIN, QDHT_PORT, QDHT_PROXY, QDHT_BOOTSTRAP, QDHT_DRY_RUN.
To build compiled JS locally without deploying:
npm run build:dist # emits to dist/src/core/README.mdsrc/node/README.mdsrc/sim/README.mdsrc/bench/README.mdbin/README.mdexamples/README.mdweb/README.md
announcement: a signed event that says something exists, such as a file, replica, or routerequest announcement: a signed metadata query asking the network for matching pointers or routesresponse announcement: a signed metadata reply containing matching announcements or route recordsroute announcement: a signed endpoint record that helps turn an identity into a dialable targetreplica record: a signed note that a peer has part or all of a content itemprovider: something that can supply the actual bytes for a content itemcontent store: local on-disk storage for pieces and indexessearch: metadata lookup over announcements and routes, not arbitrary raw contenttransport: the live network path used to carry events between nodes
The repo currently includes:
- simulation coverage for stable, churn, spam, swarming, and baseline cases
- a live node with CLI, WebSocket, relay, and QUIC transports
- a browser dashboard served from the live node when
webPortis configured - a shared
qdht.sqlitefile with normalized tables for kinds, identities, events, tags, route references, and content indexes - content-provider and reachability examples
- benchmark and stress harnesses
Identity-to-route resolution lives in src/core/discovery/reachability.ts. When a node wants to dial a peer by pubkey or Nostr identity reference, it:
- Normalises the identity reference (npub, hex pubkey, or
_@domainNIP-05 style) to a canonical form. - Looks up stored route announcements (kind
30181) in the local event log. - Scores candidates by confidence, sequence number, and observed NAT type.
- Returns a
ResolvedRoutewithbestEndpoint, optionalfallback, and anat.typeEstimatefield.
Peers also emit observed-address events so each node learns its own externally-visible address without a STUN server. The reachability layer uses those reflections to set typeEstimate to one of open, cone, symmetric, or unknown.
Regular nodes also ingest 30181 service records and may auto-connect to the advertised endpoint when the peer discovery policy allows it. Bootstrap nodes still cache and fan out service records, but they do not auto-dial peers.
If localDiscovery is enabled, nodes also gossip those signed service records over a local UDP discovery channel so nearby peers can find each other without manual bootstrap peers.
The quantum walk runs in src/core/graph/. Three files cooperate:
topology.ts— builds the adjacency structure from connected peers and their reputation scores.laplacian.ts— computes the bare graph LaplacianL = D - AwhereDis the degree matrix andAis the weighted adjacency matrix.graph-state.ts— applies the continuous-time quantum walk (CTQW) operatorexp(-iLΔt)to an initial amplitude vector, then squares amplitudes to get propagation probabilities:prob(i) = |<i| exp(-iLΔt) |source>|².
The propagator (src/core/propagation/propagator.ts) wraps graph-state and adds:
- reputation damping:
effective_prob = quantum_prob × reputation_factorwherereputation_factor = exp(-2γ|rep|t) - a configurable exploration floor so low-probability paths are still sampled occasionally
- top-k target selection from the resulting probability distribution
This runs as ordinary floating-point matrix math. There is no quantum hardware involved.
All persistence goes into a single SQLite file (qdht.sqlite) under dataDir. Two modules manage it:
src/core/storage/sqlite-node-storage.ts— the raw SQLite adapter. Tables include:kinds— catalog of known Nostr/qDHT event kindsidentities— deduplicated pubkeysevents— signed event headers linked to identities and kindstag_keys— deduplicated Nostr tag namesevent_tags— flattened tag values linked to tag keysevent_identity_refs— secondary identity references for route, request, and observed-address eventscontent_index— content metadata byqkeyandhashcontent_piece_hashes— one row per piece hash for a stored content item
src/core/storage/content-repository.ts— content index on top of the event store. Tracks content locations byqkeyandhash, piece manifests, and replica records. ReturnsContentLocationobjects with piece counts, piece size, and optional source URL.
The event store is append-only for Nostr events. Route and reachability records are stored as signed events and resolved through the normalized event tables. Service records are also used for peer discovery: when a regular node receives a valid 30181 record and the peer discovery policy approves it, the node will auto-connect to that advertised endpoint.
All runnable demos are in examples/. See examples/README.md for full details.
| File | Description | npm script |
|---|---|---|
hello-publish-get.ts |
Put a small payload on one node and read it back locally | npm run example:hello |
peer-gossip-demo.ts |
Start two nodes and observe peer discovery | npm run example:gossip |
sqlite-persistence-demo.ts |
Publish, restart the node, confirm the event store survives | npm run example:sqlite |
reachability-reflection-demo.ts |
Peer-observed address reflection, dialback, and relay fallback without DNS | npm run example:reachability |
churn-recovery-demo.ts |
Announcement delivery under simulated churn and recovery | npm run example:churn |
spam-filter-demo.ts |
Reputation suppression keeping legitimate traffic moving | npm run example:spam |
The live node tries transports in preference order:
- QUIC (
quic-adapter.ts) — low-latency direct UDP, used when both peers areopenorconeNAT. - WebSocket (
peer-manager.ts) — direct TCP connection, used when QUIC is unavailable or the peer is behind symmetric NAT. - Relay (
relay-adapter.ts) — routes events through a Nostr relay, used as the fallback of last resort when direct dial fails.
Transport selection happens in qdht-node.ts. All three can coexist; each implements the Transport interface in src/node/transport.ts. Relay transport adds latency but requires no open ports on either side.