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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Phase 1: Nodes Find Each Other — Tesseras</title>
<meta name="description" content="Tesseras nodes can now discover peers, form a Kademlia DHT over QUIC, and publish and find tessera pointers across the network.">
<!-- Open Graph -->
<meta property="og:type" content="article">
<meta property="og:title" content="Phase 1: Nodes Find Each Other">
<meta property="og:description" content="Tesseras nodes can now discover peers, form a Kademlia DHT over QUIC, and publish and find tessera pointers across the network.">
<meta property="og:image" content="https://tesseras.net/images/social.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:site_name" content="Tesseras">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Phase 1: Nodes Find Each Other">
<meta name="twitter:description" content="Tesseras nodes can now discover peers, form a Kademlia DHT over QUIC, and publish and find tessera pointers across the network.">
<meta name="twitter:image" content="https://tesseras.net/images/social.jpg">
<link rel="stylesheet" href="https://tesseras.net/style.css?h=21f0f32121928ee5c690">
<link rel="alternate" type="application/atom+xml" title="Tesseras" href="https://tesseras.net/atom.xml">
<link rel="icon" type="image/png" sizes="32x32" href="https://tesseras.net/images/favicon.png?h=be4e123a23393b1a027d">
</head>
<body>
<header>
<h1>
<a href="https://tesseras.net/">
<img src="https://tesseras.net/images/logo-64.png?h=c1b8d0c4c5f93b49d40b" alt="Tesseras" width="40" height="40" class="logo">
Tesseras
</a>
</h1>
<nav>
<a href="https://tesseras.net/about/">About</a>
<a href="https://tesseras.net/news/">News</a>
<a href="https://tesseras.net/releases/">Releases</a>
<a href="https://tesseras.net/faq/">FAQ</a>
<a href="https://tesseras.net/subscriptions/">Subscriptions</a>
<a href="https://tesseras.net/contact/">Contact</a>
</nav>
<nav class="lang-switch">
<strong>English</strong> | <a href="/pt-br/news/phase1-basic-network/">Português</a>
</nav>
</header>
<main>
<article>
<h2>Phase 1: Nodes Find Each Other</h2>
<p class="news-date">2026-02-14</p>
<p>Tesseras is no longer a local-only tool. Phase 1 delivers the networking layer:
nodes discover each other through a Kademlia DHT, communicate over QUIC, and
publish tessera pointers that any peer on the network can find. A tessera
created on node A is now findable from node C.</p>
<h2 id="what-was-built">What was built</h2>
<p><strong>tesseras-core</strong> (updated) — New network domain types: <code>TesseraPointer</code>
(lightweight reference to a tessera's holders and fragment locations),
<code>NodeIdentity</code> (node ID + public key + proof-of-work nonce), <code>NodeInfo</code>
(identity + address + capabilities), and <code>Capabilities</code> (bitflags for what a
node supports: DHT, storage, relay, replication).</p>
<p><strong>tesseras-net</strong> — The transport layer, built on QUIC via quinn. The <code>Transport</code>
trait defines the port: <code>send</code>, <code>recv</code>, <code>disconnect</code>, <code>local_addr</code>. Two adapters
implement it:</p>
<ul>
<li><code>QuinnTransport</code> — real QUIC with self-signed TLS, ALPN negotiation
(<code>tesseras/1</code>), connection pooling via DashMap, and a background accept loop
that handles incoming streams.</li>
<li><code>MemTransport</code> + <code>SimNetwork</code> — in-memory channels for deterministic testing
without network I/O. Every integration test in the DHT crate runs against
this.</li>
</ul>
<p>The wire protocol uses length-prefixed MessagePack: a 4-byte big-endian length
header followed by an rmp-serde payload. <code>WireMessage</code> carries a version byte,
request ID, and a body that can be a request, response, or protocol-level error.
Maximum message size is 64 KiB.</p>
<p><strong>tesseras-dht</strong> — A complete Kademlia implementation:</p>
<ul>
<li><em>Routing table</em>: 160 k-buckets with k=20. Least-recently-seen eviction,
move-to-back on update, ping-check before replacing a full bucket's oldest
entry.</li>
<li><em>XOR distance</em>: 160-bit XOR metric with bucket indexing by highest differing
bit.</li>
<li><em>Proof-of-work</em>: nodes grind a nonce until <code>BLAKE3(pubkey || nonce)[..20]</code> has
8 leading zero bits (~256 hash attempts on average). Cheap enough for any
device, expensive enough to make Sybil attacks impractical at scale.</li>
<li><em>Protocol messages</em>: Ping/Pong, FindNode/FindNodeResponse,
FindValue/FindValueResult, Store — all serialized with MessagePack via serde.</li>
<li><em>Pointer store</em>: bounded in-memory store with configurable TTL (24 hours
default) and max entries (10,000 default). When full, evicts pointers furthest
from the local node ID, following Kademlia's distance-based responsibility
model.</li>
<li><em>DhtEngine</em>: the main orchestrator. Handles incoming RPCs, runs iterative
lookups (alpha=3 parallelism), bootstrap, publish, and find. The <code>run()</code>
method drives a <code>tokio::select!</code> loop with maintenance timers: routing table
refresh every 60 seconds, pointer expiry every 5 minutes.</li>
</ul>
<p><strong>tesd</strong> — A full-node binary. Parses CLI args (bind address, bootstrap peers,
data directory), generates a PoW-valid node identity, binds a QUIC endpoint,
bootstraps into the network, and runs the DHT engine. Graceful shutdown on
Ctrl+C via tokio signal handling.</p>
<p><strong>Infrastructure</strong> — OpenTofu configuration for two Hetzner Cloud bootstrap
nodes (cx22 instances in Falkenstein, Germany and Helsinki, Finland). Cloud-init
provisioning script creates a dedicated <code>tesseras</code> user, writes a config file,
and sets up a systemd service. Firewall rules open UDP 4433 (QUIC) and restrict
metrics to internal access.</p>
<p><strong>Testing</strong> — 139 tests across the workspace:</p>
<ul>
<li>47 unit tests in tesseras-dht (routing table, distance, PoW, pointer store,
message serialization, engine RPCs)</li>
<li>5 multi-node integration tests (3-node bootstrap, 10-node lookup convergence,
publish-and-find, node departure detection, PoW rejection)</li>
<li>14 tests in tesseras-net (codec roundtrips, transport send/recv, backpressure,
disconnect)</li>
<li>Docker Compose smoke tests with 3 containerized nodes communicating over real
QUIC</li>
<li>Zero clippy warnings, clean formatting</li>
</ul>
<h2 id="architecture-decisions">Architecture decisions</h2>
<ul>
<li><strong>Transport as a port</strong>: the <code>Transport</code> trait is the only interface between
the DHT engine and the network. Swapping QUIC for any other protocol means
implementing four methods. All DHT tests use the in-memory adapter, making
them fast and deterministic.</li>
<li><strong>One stream per RPC</strong>: each DHT request-response pair uses a fresh
bidirectional QUIC stream. No multiplexing complexity, no head-of-line
blocking between independent operations. QUIC handles the multiplexing at the
connection level.</li>
<li><strong>MessagePack over Protobuf</strong>: compact binary encoding without code generation
or schema files. Serde integration means adding a field to a message is a
one-line change. Trade-off: no built-in schema evolution guarantees, but at
this stage velocity matters more.</li>
<li><strong>PoW instead of stake or reputation</strong>: a node identity costs ~256 BLAKE3
hashes. This runs in under a second on any hardware, including a Raspberry Pi,
but generating thousands of identities for a Sybil attack becomes expensive.
No tokens, no blockchain, no external dependencies.</li>
<li><strong>Iterative lookup with routing table updates</strong>: discovered nodes are added to
the routing table as they're encountered during iterative lookups, following
standard Kademlia behavior. This ensures the routing table improves
organically as nodes interact.</li>
</ul>
<h2 id="what-comes-next">What comes next</h2>
<ul>
<li><strong>Phase 2: Replication</strong> — Reed-Solomon erasure coding over the network,
fragment distribution, automatic repair loops, bilateral reciprocity ledger
(no blockchain, no tokens)</li>
<li><strong>Phase 3: API and Apps</strong> — Flutter mobile/desktop app via
flutter_rust_bridge, GraphQL API (async-graphql), WASM browser node</li>
<li><strong>Phase 4: Resilience and Scale</strong> — ML-DSA post-quantum signatures, advanced
NAT traversal, Shamir's Secret Sharing for heirs, packaging for
Alpine/Arch/Debian/FreeBSD/OpenBSD, CI on SourceHut</li>
<li><strong>Phase 5: Exploration and Culture</strong> — public tessera browser, institutional
curation, genealogy integration, physical media export</li>
</ul>
<p>Nodes can find each other. Next, they learn to keep each other's memories alive.</p>
</article>
</main>
<footer>
<p>© 2026 Tesseras Project. <a href="/atom.xml">News Feed</a> · <a href="https://git.sr.ht/~ijanc/tesseras">Source</a></p>
</footer>
</body>
</html>
|