从 DNS 域名里领一份邻居名单:enrtree 怎么做到可验证 图 1
从 DNS 域名里领一份邻居名单:enrtree 怎么做到可验证 · 图 1

一台刚启动的以太坊节点要回答的第一个问题是:连谁。主动的办法是 Kademlia 式的发现协议,逢人就问更近的节点;另一条安静的路线是把现成的名单挂在一个域名下面,让新节点去领。EIP-1459(2018 年 9 月,作者 Felix Lange 与 Péter Szilágyi)定义的就是后者:用 DNS 分发可验证、可更新的节点记录名单。

一条 URL 装下域名和验签公钥

客户端拿到的引导信息是一条 enrtree 方案的 URL:用户名部分是公钥,主机名部分是域名。公钥是 32 字节压缩形式经 RFC 4648 base32 编码,例如 enrtree 开头的字符串加 at 符号接域名。这意味着名单的”所有权”随 URL 一起分发:只要客户端信这条 URL,它就不需要再信任域名运营商的良心——所有内容由那把公钥对应的私钥签过名,改一个字节都过不了验。

从 DNS 域名里领一份邻居名单:enrtree 怎么做到可验证 图 2
从 DNS 域名里领一份邻居名单:enrtree 怎么做到可验证 · 图 2

TXT 记录里的默克尔树

名单本体编码成一棵默克尔树,通过 DNS 的 TXT 记录分发。根记录的内容形如 enrtree-root:v1,后面跟两个子树根哈希(分别装节点条目和指向其他名单的链接)、一个十进制的更新序号,以及 sig 等号后那段签名:对记录内容(不含 sig 字段本身)做 keccak256 哈希后,用 secp256k1 签出的 65 字节签名,再按 URL 安全的 base64 编码。树下再展开:每个条目的子域名是条目内容哈希(截断后)的 base32 编码,条目分三类——装下层哈希的中间节点 enrtree-branch、指向别的域名的名单链接,以及节点记录叶子本身。验证逻辑因此是完整闭环:根签名锁根哈希,根哈希逐层锁到每一个叶子;域名可以被缓存、被代理、被 CDN 搬运,密码学信任锚始终只有 URL 里那把公钥。

geth 的现成用法

Geth 源码 params/bootnodes.go 里保留了这条路:代码内嵌一个 enrtree 前缀公钥,再按创世哈希拼出形如协议名点主网名点 ethdisco.net 的域名,主网、Sepolia、Hoodi 各有对应,名单本体由名为 discv4-dns-lists 的仓库公开维护。也就是说,虽然 EIP-1459 在 EIP 仓库里被标为 Stagnant(停滞),DNS 名单这条引导通道在主流客户端里并没有退役,它仍是节点冷启动拿到第一批合法邻居的来源之一,和编译进二进制的静态 bootnode 互补。

更新与新鲜度的软肋

节奏由序号承担:根记录的 seq 是只增不减的十进制整数,客户端先读一次根记录,发现 seq 变大了才沿哈希去取变化的叶子,增量更新的代价因此只是几条 TXT 查询;名单还能通过链接叶子把另一个域名连同另一把公钥整段挂进树里,让名单按网络或时期分片。这套设计换来的软肋也要说清:签名只能证明”没被篡改”,证明不了”足够新鲜”。解析链路里的缓存、中间盒或恶意的名单运营方都可以返回一份旧的合法名单做回退攻击——客户端唯一的对策是自己记住见过的最大 seq,见到更小的就报警或拒绝。密码学在这里只负责内容的真,时间的真仍要协议和使用习惯来补。

和比特币种子、ENR 的分工

比特币的 DNS 种子只返回 IP 与端口,信任建立在硬编码公钥对地址的启发式校验上;以太坊的 enrtree 从第一天起就把”名单内容”纳入密码学签名,而名单里的每条记录本身又是 EIP-778 定义的节点记录 ENR——一份带序列号和签名的可携带名片,能声明自己的公网地址、端口和以太坊共识身份。三种机制互补:发现协议负责持续找新邻居,静态 bootnode 负责最冷启动,DNS 名单负责不依赖任何单一节点也能换到一批新鲜地址。若运营商愿意配合部署 DNSSEC,域名解析层还能多一道防线,但名单内容层面的防篡改始终由内嵌签名承担,不依赖解析链路。本文只讨论协议机制,不构成任何投资建议。