A Python port of the Invisible Internet Project (I2P)
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<title>The Fix: I2P Addresses in Ethereum Node Records</title>
7<link rel="preconnect" href="https://fonts.googleapis.com">
8<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Space+Mono:wght@400;700&family=Syne:wght@400;600;700;800&display=swap" rel="stylesheet">
9<style>
10 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
11 :root {
12 --navy: #06091a; --navy2: #0c1230;
13 --red: #ff2b4a; --mint: #00e5a0; --cyan: #00c8ff;
14 --gray: #8a95b0; --light: #c8d0e8; --white: #eef1fb;
15 --mono: 'Space Mono', monospace;
16 --display: 'Bebas Neue', sans-serif;
17 --body: 'Syne', sans-serif;
18 }
19 html { scroll-behavior: smooth; }
20 body { background: var(--navy); color: var(--white); font-family: var(--body); font-size: 16px; line-height: 1.6; }
21
22 nav {
23 position: fixed; top: 0; left: 0; right: 0; z-index: 100;
24 display: flex; align-items: center; justify-content: space-between;
25 padding: 1.25rem 2.5rem;
26 background: linear-gradient(to bottom, rgba(6,9,26,0.95), transparent);
27 backdrop-filter: blur(2px);
28 }
29 .nav-logo { font-family: var(--mono); font-size: 13px; color: var(--mint); letter-spacing: 0.08em; text-decoration: none; }
30 .nav-links { display: flex; gap: 2rem; list-style: none; }
31 .nav-links a { font-family: var(--mono); font-size: 12px; color: var(--gray); text-decoration: none; letter-spacing: 0.05em; transition: color 0.2s; }
32 .nav-links a:hover { color: var(--white); }
33 .nav-cta { font-family: var(--mono); font-size: 12px; color: var(--navy); background: var(--mint); padding: 0.5rem 1.25rem; text-decoration: none; letter-spacing: 0.05em; }
34
35 .article { max-width: 720px; margin: 0 auto; padding: 8rem 2rem 5rem; }
36 .article-tag { font-family: var(--mono); font-size: 11px; color: var(--mint); letter-spacing: 0.2em; text-transform: uppercase; margin-bottom: 1.5rem; }
37 .article h1 { font-family: var(--display); font-size: clamp(48px, 8vw, 80px); line-height: 0.95; margin-bottom: 1.5rem; }
38 .article h1 .mint { color: var(--mint); }
39 .article .lede { font-size: 18px; color: var(--light); line-height: 1.7; margin-bottom: 3rem; border-left: 2px solid var(--mint); padding-left: 1.25rem; }
40 .article h2 { font-family: var(--display); font-size: 40px; color: var(--white); margin: 3.5rem 0 1rem; letter-spacing: 0.02em; }
41 .article h2 .accent { color: var(--red); }
42 .article h2 .mint { color: var(--mint); }
43 .article p { font-size: 15px; color: var(--light); line-height: 1.8; margin-bottom: 1.25rem; }
44 .article p strong { color: var(--white); font-weight: 700; }
45 .article a { color: var(--mint); text-decoration: none; border-bottom: 1px solid rgba(0,229,160,0.3); }
46 .article a:hover { border-color: var(--mint); }
47
48 .code-block {
49 background: var(--navy2); border: 1px solid rgba(0,200,255,0.15);
50 padding: 1.5rem; margin: 1.5rem 0; overflow-x: auto;
51 font-family: var(--mono); font-size: 13px; color: var(--cyan);
52 line-height: 1.8; position: relative;
53 }
54 .code-block::before {
55 content: attr(data-label);
56 position: absolute; top: -10px; left: 1rem;
57 font-family: var(--mono); font-size: 10px; color: var(--gray);
58 letter-spacing: 0.1em; background: var(--navy); padding: 0 0.5rem;
59 }
60 .code-block .comment { color: var(--gray); }
61 .code-block .key { color: var(--mint); }
62 .code-block .value { color: var(--red); }
63
64 .flow-diagram {
65 display: grid; grid-template-columns: 1fr auto 1fr auto 1fr;
66 gap: 0; align-items: center; margin: 2.5rem 0;
67 }
68 .flow-step {
69 background: var(--navy2); border: 1px solid rgba(0,229,160,0.15);
70 padding: 1.25rem; text-align: center;
71 }
72 .flow-step .step-label { font-family: var(--mono); font-size: 10px; color: var(--gray); letter-spacing: 0.1em; margin-bottom: 0.5rem; }
73 .flow-step .step-text { font-size: 14px; color: var(--light); font-weight: 600; }
74 .flow-arrow { font-family: var(--mono); font-size: 20px; color: var(--mint); text-align: center; padding: 0 0.5rem; }
75
76 .status-table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; }
77 .status-table th {
78 font-family: var(--mono); font-size: 11px; color: var(--gray);
79 letter-spacing: 0.1em; text-align: left; padding: 0.75rem 1rem;
80 border-bottom: 1px solid rgba(255,255,255,0.08);
81 }
82 .status-table td {
83 padding: 0.75rem 1rem; font-size: 14px; color: var(--light);
84 border-bottom: 1px solid rgba(255,255,255,0.04);
85 }
86 .status-done { color: var(--mint); font-family: var(--mono); font-size: 12px; }
87 .status-wip { color: var(--cyan); font-family: var(--mono); font-size: 12px; }
88 .status-planned { color: var(--gray); font-family: var(--mono); font-size: 12px; }
89
90 .cta-box {
91 background: var(--navy2); border: 1px solid rgba(0,229,160,0.2);
92 padding: 2.5rem; text-align: center; margin: 3rem 0;
93 }
94 .cta-box h3 { font-family: var(--display); font-size: 36px; margin-bottom: 1rem; }
95 .cta-box p { color: var(--gray); margin-bottom: 1.5rem; }
96 .cta-box a {
97 font-family: var(--mono); font-size: 13px; color: var(--navy);
98 background: var(--mint); padding: 0.75rem 2rem;
99 text-decoration: none; letter-spacing: 0.08em; display: inline-block;
100 border: none;
101 }
102
103 footer {
104 padding: 2.5rem; border-top: 1px solid rgba(255,255,255,0.06);
105 display: flex; align-items: center; justify-content: space-between;
106 }
107 footer .footer-left { font-family: var(--mono); font-size: 11px; color: var(--gray); letter-spacing: 0.08em; }
108 footer .footer-right { display: flex; gap: 1.5rem; }
109 footer a { font-family: var(--mono); font-size: 11px; color: var(--gray); text-decoration: none; letter-spacing: 0.08em; transition: color 0.2s; }
110 footer a:hover { color: var(--white); }
111
112 @media (max-width: 768px) {
113 nav { padding: 1rem 1.25rem; }
114 .nav-links { display: none; }
115 .article { padding: 6rem 1.25rem 3rem; }
116 .flow-diagram { grid-template-columns: 1fr; gap: 0.5rem; }
117 .flow-arrow { transform: rotate(90deg); }
118 }
119</style>
120</head>
121<body>
122
123<nav>
124 <a href="index.html" class="nav-logo">i2p // python</a>
125 <ul class="nav-links">
126 <li><a href="index.html#attack">The Attack</a></li>
127 <li><a href="index.html#compare">Why I2P</a></li>
128 <li><a href="blog.html">The Port</a></li>
129 <li><a href="the-fix.html">The Fix</a></li>
130 </ul>
131 <a href="https://github.com/Bimo-Studio/i2p-python" class="nav-cta">VIEW ON GITHUB</a>
132</nav>
133
134<article class="article">
135 <div class="article-tag">TECHNICAL SPECIFICATION</div>
136 <h1>The <span class="mint">Fix</span></h1>
137
138 <p class="lede">
139 Add one key to an Ethereum Node Record. Replace a cleartext IP address with
140 a 32-byte I2P destination hash. Zero breaking changes.
141 </p>
142
143 <h2>The ENR <span class="accent">Spec</span></h2>
144 <p>
145 Ethereum Node Records (EIP-778) are key-value identity records that nodes exchange
146 during peer discovery. The format is explicitly designed to be extensible:
147 </p>
148 <div class="code-block" data-label="// EIP-778 specification">
149<span class="comment">// "Records can contain any key-value pairs."</span>
150<span class="comment">// "Nodes should ignore keys they do not know how to interpret."</span>
151<span class="comment">// — EIP-778, Extensibility</span>
152 </div>
153 <p>
154 Adding a new key requires no hard fork, no EIP approval vote, no consensus change.
155 Any node can publish an ENR with custom keys. Nodes that understand the key use it.
156 Nodes that don't — ignore it. This is exactly how Bitcoin added I2P support via BIP-155.
157 </p>
158
159 <h2>The Proposed <span class="mint">Key</span></h2>
160 <div class="code-block" data-label="// proposed ENR extension">
161<span class="key">"i2p"</span> : <span class="value"><32 bytes></span> <span class="comment">// SHA-256(I2P Destination)</span>
162
163<span class="comment">// Full ENR example:</span>
164{
165 <span class="key">"id"</span>: <span class="value">"v4"</span>,
166 <span class="key">"secp256k1"</span>: <span class="value"><33 bytes></span>, <span class="comment">// Ethereum identity</span>
167 <span class="key">"ip"</span>: <span class="value">null</span>, <span class="comment">// omitted or zeroed</span>
168 <span class="key">"udp"</span>: <span class="value">null</span>,
169 <span class="key">"i2p"</span>: <span class="value">3a9f1c2d8e7b...</span> <span class="comment">// I2P destination hash</span>
170}
171 </div>
172 <p>
173 The value is the <strong>SHA-256 hash</strong> of the node's full I2P Destination —
174 a 387-byte structure containing public encryption and signing keys. This 32-byte hash
175 is what the I2P network uses to route traffic. It functions like an IP address but
176 with no physical location attached.
177 </p>
178
179 <h2>How It <span class="mint">Works</span></h2>
180
181 <div class="flow-diagram">
182 <div class="flow-step">
183 <div class="step-label">STEP 1</div>
184 <div class="step-text">Node publishes ENR<br>with <span style="color:var(--mint)">i2p</span> key</div>
185 </div>
186 <div class="flow-arrow">→</div>
187 <div class="flow-step">
188 <div class="step-label">STEP 2</div>
189 <div class="step-text">I2P-aware peers<br>connect via I2P</div>
190 </div>
191 <div class="flow-arrow">→</div>
192 <div class="flow-step">
193 <div class="step-label">STEP 3</div>
194 <div class="step-text">Non-I2P peers<br>silently ignore key</div>
195 </div>
196 </div>
197
198 <p>
199 <strong>Backwards compatible.</strong> Nodes without I2P support skip the unknown key.
200 The network continues to function identically for them.
201 </p>
202 <p>
203 <strong>Opt-in.</strong> Validators choose whether to publish an I2P address. No one
204 is forced to run an I2P router. But those who do become invisible to the
205 deanonymization attack described by Heimbach et al.
206 </p>
207 <p>
208 <strong>Additive.</strong> A node can publish both a cleartext IP and an I2P address
209 simultaneously, allowing I2P-aware peers to use the private channel while maintaining
210 full connectivity with the rest of the network.
211 </p>
212
213 <h2>Implementation <span class="accent">Status</span></h2>
214
215 <table class="status-table">
216 <thead>
217 <tr><th>COMPONENT</th><th>STATUS</th><th>NOTES</th></tr>
218 </thead>
219 <tbody>
220 <tr>
221 <td>I2P protocol stack (Python)</td>
222 <td><span class="status-done">COMPLETE</span></td>
223 <td>i2p-python: full router, SAM, transports</td>
224 </tr>
225 <tr>
226 <td>NTCP2 interop with Java peers</td>
227 <td><span class="status-done">VERIFIED</span></td>
228 <td>Noise XK handshake confirmed on live network</td>
229 </tr>
230 <tr>
231 <td>Security audit</td>
232 <td><span class="status-done">COMPLETE</span></td>
233 <td>bandit + pip-audit + crypto checklist</td>
234 </tr>
235 <tr>
236 <td>Cross-platform builds</td>
237 <td><span class="status-done">COMPLETE</span></td>
238 <td>deb, rpm, Windows exe, sdist, wheel</td>
239 </tr>
240 <tr>
241 <td>EIP draft</td>
242 <td><span class="status-wip">IN PROGRESS</span></td>
243 <td>ENR extension specification</td>
244 </tr>
245 <tr>
246 <td>Nethermind plugin</td>
247 <td><span class="status-planned">PLANNED</span></td>
248 <td>C# Ethereum client I2P integration</td>
249 </tr>
250 <tr>
251 <td>Test network (20 nodes)</td>
252 <td><span class="status-planned">PLANNED</span></td>
253 <td>I2P compliance validation at scale</td>
254 </tr>
255 </tbody>
256 </table>
257
258 <div class="cta-box">
259 <h3>Get <span style="color: var(--mint);">Involved</span></h3>
260 <p>The code is open source. The spec is public. Contributions welcome.</p>
261 <a href="https://github.com/Bimo-Studio/i2p-python">VIEW ON GITHUB</a>
262 </div>
263
264</article>
265
266<footer>
267 <div class="footer-left">
268 i2p // python — MIT LICENSE — A <a href="https://bimo.studio" style="color: var(--mint);">bimo.studio</a> PROJECT
269 </div>
270 <div class="footer-right">
271 <a href="index.html">HOME</a>
272 <a href="blog.html">BUILD LOG</a>
273 <a href="https://github.com/Bimo-Studio/i2p-python">GITHUB</a>
274 </div>
275</footer>
276
277</body>
278</html>