原文標題:《Buidler DAO:以ENS 為例深度分析Web3 域名系統的技術設計》
原文作者:@axtrur,Buidler DAO 研究員
Web3 域名系統,簡而言之就是基於區塊鏈的分佈式、去中心化的命名系統,與DNS(互聯網名稱服務)類似,將地址(錢包地址或智能合約地址)解析成可讀性的名稱,本文以ENS 為例從整體架構到合約細節,深度剖析web3 域名系統的設計。
ENS 前置概念
域名層級
類似DNS,ENS 域名層級同樣分為
根域名,即””域名
一級域名,類似.com,.cn,在ens 中有.eth 和.reverse( 用來記錄反向解析,後面會提到)
二級域名,即用戶註冊的域名,比如axtrur.eth
三級域名,用戶註冊了二級域名之後,可以創建或修改該二級域名下的三級域名,比如app.axtrur.eth
NameHash 算法
由於智能合約直接與可讀的域名進行交互效率低,因此ENS 採用固定長度的256 位加密哈希作為域名記錄。 namehash 是一個遞歸算法,可從任意域名的NameHash 值推導出任意子域名的值,而無需知道原域名的真實文本字符串,同時符合域名的多層級特性。
Namehash(“”) = “0x0000000000000000000000000000000000000000000000000000000000000000” Namehash(“eth”) = keccak256(Namehash(“”), keccak256(“eth”)) Namehash(“axtrur.eth”) = keccak256(Namehash(“eth”), keccak256(“axtrur”)) Namehash(“app.axtrur.eth”) = keccak256(Namehash(“axtrur.eth”), keccak256(“app”))
node
在ens 中,用戶註冊的域名比如axtrur.eth,會採用namehash 算法生成的哈希node 去記錄鏈上數據,後文中我們將提到的node 理解為一個域名名稱比如axtrur 即可,關於ENS 名稱的處理請參考https://ensuser.com/docs/contract-api-reference/name-processing.html
ENS 模塊概念
註冊器合約(綠色部分):負責分配名稱的合約,有正向註冊器,反向註冊器,DNS 註冊器。
解析器合約(藍色部分):負責記錄域名映射關係的合約,分正向、反向解析器,其中正向解析器合約也可自定義實現:
正向解析(主網上有默認的公共解析器合約):負責記錄域名所綁定的內容,即域名的nameNode(比如axtrur.eth,app.axtrur.eth)到(包括不限於name, addr, txt, contenthash 等內容)的解析,可設置各類幣種的錢包地址,還可以設置IPFS 的內容哈希,甚至記錄郵箱等第三方賬號作為文本記錄。
反向解析(主網上有默認的反向解析器合約):負責記錄錢包地址所綁定的域名,即反向域名的nameNode(比如{{錢包地址}}.addr.reverse) 到域名名稱的解析
根合約:是根域名的owner,擁有一級域名的管理權限
控制器合約:官網的註冊入口合約(如果需要實現不同的玩法合約,則統一歸為控制器合約模塊,需要將對應註冊器合約地址設置給註冊器,才有權限操作註冊器進行域名NFT 註冊與記錄反向解析)
價格預言機:ENS 定價採用的是U 本位(usdt),所以需要USDT 預言機來計算某一時刻的註冊費的eth 換算值,ENS 主網上的註冊費為:
長度為5+ 個字符的域名:每年支付5 美元
長度為4 個字符的域名:每年支付160 美元
長度為3 個字符的域名:每年支付640 美元
DNSSEC 預言機:DNS 安全擴展預言機合約,負責校驗證明web3 域名的所有權和有效性
ENS 模塊解析
註冊表合約(EnsRegistry.sol)
註冊表是ENS 最核心的合約,上圖為註冊表合約內部的records 結構,維護著域名層級node 對應的owner、解析器、ttl 信息註冊表是ENS 最核心的合約,上圖為註冊表合約內部的records 結構,維護著域名層級node 對應的owner、解析器、ttl 信息。
除了註冊表信息records 維護,合約還維護owner 的委託管理者信息operators,owner 可以通過添加設置委託管理者地址(可以是用戶地址,也可以是合約地址)來共同管理域名信息合約中相關管理設置接口(比如設置解析器,ttl,以及創建和修改子域名),都會通過修飾器`authorised(node)`來限制調用權限;該修飾器將判斷該接口的交易請求者是否為當前域名的owner,或者委託管理者地址,保證了僅有域名的owner 或委託者才有創建下一級子域名的權限。同時這裡部署初始化的時候將`」」`根域名的node 的owner 設置為部署者,只有這樣,部署者才能將根域名的owner 設置給Root 合約根合約。
(Root.sol)根合約是根域名的owner,同時根合約作為根域名的owner,有權限調用註冊表合約的setSubnodeOwner 接口,將域名.eth 的owner 指向基礎註冊器合約。
基礎註冊器合約(BaseRegistrarImplementation.sol)
由於Root 合約將域名.eth 的owner 指向基礎註冊器合約(又稱正向註冊器合約),從而基礎註冊器擁有.eth 底下的二級域名的設置權限,使得用戶可以通過基礎註冊器合約進行域名註冊;同時該註冊器合約繼承了ERC721 協議標準,這也就是為什麼ENS 域名可以作為NFT 在交易市場比如opensea 上買賣的原因。除此之外,基礎註冊器合約還維護著每個域名的過期時間expiries,註冊器為每個域名設置了90 天的保護期,當域名過期後且在保護期內,域名擁有者可以通過調用續期renew 接口進行續期,如果超過了保護期,則需要重新註冊(這裡重新註冊會先銷毀NFT 在重新mint)。同時在ENS 設計中,註冊器合約(不管是正向註冊器還是反向註冊器)基本上都有controllers 結構,維護著可信的controller 註冊器合約,只有可信合約才可進行調用。
控制器合約(ETHRegsiterController.sol)
用戶在官網中,將要註冊的域名等註冊信息傳給控制器合約,控制器合約通過預言機計算該域名的價格,同時將域名通過namehash 轉成node 後傳給基礎註冊器進行域名NFT 的註冊,同時將域名相關註冊表信息寫入註冊表合約完成註冊,同時域名的owner 可以在官網通過註冊表合約進行管理操作,官網中的註冊頁面如下:
核心註冊流程:
ENS 註冊採用「請求- 提交」兩階段註冊模式ENS 註冊採用「請求- 提交」兩階段註冊模式,為什麼需要兩階段提交?我們知道以太坊節點從交易池pool 中撈取交易是會按照交易給的gas 費進行優先級排序;在註冊者攜帶待註冊域名構造的交易提交上鍊前,在整個網絡是公開透明的,惡意的攻擊者可以監聽並解析此類待上鍊交易,並構造相同域名的註冊交易,通過提高gas 費的方式搶先上鍊註冊控制器合約註冊。
為了防止此類域名搶注問題,ENS 採用了先請求,後提交的註冊模式。在第一階段並不直接提交域名,而是先調用
makeCommitment 接口根據待申請域名name、待申請地址owner、隨機值secret 進行哈希後生成一條特殊的commitment 後,通過commit 提交上鍊。
提交階段的commitment 記錄著當前時間戳,同時ENS 設置commitment 的有效期為60s 到86400s 之間;第二階段註冊的時候合約會重新計算commitment,判斷是否與第一階段提交的一致,同時檢查Commitment 的有效期,保證跟第一階段的鏈上處理時間間隔1 分鐘以上,保證記錄了第一階段交易的區塊經過了至少5 個後續區塊的確認。 (此時攻擊者雖然可以獲取域名值,但由於只有第一階段的owner 需要根第二階段的owner 一致才能生成一致的commitment,從而避免了被搶注的風險)
用戶在官網的第二階段註冊流程實際上是代碼中的resolver != address(0) 邏輯分支,因為ENS 默認會將註冊的resolver 解析器設置為默認的公共正向解析器(publicResolver 後面會提到),這里為什麼需要將域名註冊給合約本身然後在轉移給用戶呢?因為上文中我們提到註冊表合約中只有owner 或者委託管理者才有權限設置解析器或更新owner,所以為了幫用戶設置好解析器,需要通過基礎註冊器註冊(register)給合約自身,再通過註冊表合約設置解析器(setResolver),然後聲明所有權(reclaim),最後才轉移給註冊者(transferFrom)。
解析器(Resolver)
ENS 中的解析器合約分為正向解析和反向解析,解析記錄是ENS 比較重要的內容,只有定義好規範,生態才能方便的即成ENS 這類web3 域名系統。
正向解析(ENS 默認的正向解析器合約PublicResolver.sol 或者自定義解析器合約。)
負責將域名映射為對應用戶設置的內容(包括幣種地址,ipfs 內容hash,通用text 記錄等等。
首先metamask 會通過註冊表合約獲取域名node 設置的解析器地址(默認的公共解析器,也可以是用戶自定義的解析器合約地址),然後與該解析器地址交互,獲取用戶設置的eth 的幣種地址(官網註冊默認會設置成註冊者,註冊者後續可自由更改)進行轉賬操作。
反向解析(ENS 默認反向解析器合約DefaultReverseResolver.sol)
負責將用戶錢包地址映射為對應的域名。
反向解析實際上是對用戶不透明的,用戶也無法像正向解析器合約那樣可以自定義。用戶也可以通過反向註冊器(ReverseRegistrar.sol)的setName 方法設置當前錢包地址要綁定的域名,反向記錄同樣在ENS 註冊表合約維護,用戶註冊的反向記錄在三級域名記錄中,格式為:具體用戶地址.addr.reverse
設置反向解析之後,opensea 用戶界面會將用戶錢包地址展示為可讀的ENS 域名,則是反向解析的過程解析器結構以及node 對應的註冊表信息。
根域名的owner 是根域名:
.eth 一級域名的owner 是正向註冊器也就是(BaseRegistrarImplement.sol)
用戶註冊的二級、三級域名的owner 是用戶本身,同時可以自由設置解析器合約地址
.reserve 一級域名owner 是ENS 的多簽錢包地址
.addr.reserve 二級域名owner 是反向註冊器,ens 當前主網版本控制器合約註冊的時候默認通過反向註冊器(ReverseRegistrar.sol)設置反向解析記錄(比如具體用戶地址.addr.reverse 指向axtrur.eth),用戶無需提供gas 之外的反向註冊費。
用戶註冊域名的反向三級域名的owner 都默認指向反向註冊器合約,同時resolver 默認指向反向解析器合約
上面我們已經把ENS 域名合約設計以及主要的模塊梳理完了,ENS 在設計上比如模塊拆分,權限拆分方面都是值得我們藉鑑的,但是目前主網上的ENS 也存在一些問題。
ENS 存在問題與解決
1、零寬問題:這是ENS 目前比較麻煩的問題,因為合約設計之初並沒有限制零寬字符(關於零寬問題解釋 https://mirror.xyz/0xc952fE149b640097054CFa53cAf7aC2bfd0162C5/RW6psQ2mnxyzmQx08PUXgLXOZc0kjvfXm8RGRVu8s0Y),比如可以官網註冊某個域名的時候,如果該域名已被註冊,此時用https://unicode-table.com/en/200B/ 拷貝對應某種零寬字符串插入到要註冊的域名中間某個位置,則可以註冊對應的域名了。
2、特殊字符:ENS 合約並沒有限制.,emoji 表情等特殊字符過濾,以致於目前交易市場存在太多冗雜非規範的域名。
目前ENS 官網已經對特殊字符進行過濾,並給予必要的警告提示(但是合約本身並沒有限制,所以科學家一樣可以通過合約進行註冊)
3、transfer 問題:ENS 目前有個比較麻煩的問題就是域名NFT 在轉移的時候,owner 沒有同步轉移,所以當你在交易市場買了一個ENS 域名NFT 的時候,你需要通過基礎註冊器合約的reclaim 接口,消耗一定的gas 費聲明NFT 所有權後,才能到ens 官網上看到自己擁有的域。
4、tokenURI 問題:ENS 的基礎註冊器合約並沒有即成ERC721 標準的tokenURI,可能是設計之初沒有考慮好,所以目前我們在交易市場比如opensea 上的ENS 的NFT 的metadata,是交易市場特殊對ENS 即成了ENS 中心化的metaservice 的API(比如:https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/92165218023603606815515740961699344403389102980529428548045197994533319339809)。
5、保留字:這是我覺得.bit 這方面做得比較好的方面,.bit 官方會把web2 世界中的機構或公司名稱保留下來,便於後續web2 與web3 之間的連接(https://github.com/dotbitHQ/Documents/blob/main/Reserved_DAS/Reserved_DAS_List.md)這對於web3 域名生態發展是有意義。
6、基礎合約可升級,ENS 目前對於基礎模塊並沒有採用代理模式支持合約可升級,這樣當未來需要對基礎模塊升級的時候是比較麻煩的,一種是fallback 一種是遷移數據,但這都只能解決部分問題,這個方面ENS 是有改善空間的。
我們可以怎麼解決上面那些問題呢?
1、字符問題,我們可以在控制器合約的valid 函數修改邏輯,一種實現方式是限制零寬等特殊字符比如spaceid(https://github.com/Space-ID/SpaceIDContract-Audit/blob/main/contracts/bnbregistrar/BNBRegistrarControllerV9.sol#L88),另一種就是只允許符合規範的字符。
2、transfer 問題:我們可以在基礎註冊器裡複寫transferFrom 和saveTransferFrom 函數,在轉移nft 的同時調用setSubnodeOwner 轉移owner。
3、tokenURI 問題:這個比較簡單我們只要繼承ECR721 的tokenURI 標準呢就可以了,那怎麼實現在圖片中動態的域名的展示呢?我們可以採用svg 上鍊(可以看文章後面改造後的合約代碼的TokenURIBuilder.sol)。
4、保留字問題:可以將保留字以及對應要保留的錢包地址上鍊,先保留給合約本身,後面可以通過apply 接口申請給某個特定地址。
5、基礎合約可升級:我們可以採用代理模式(eip-1967) 對基礎模塊合約進行改造,感興趣可以參考lens-protocol 的合約設計(https://github.com/lens-protocol/core/tree/main/contracts)。
DNS 模塊
ENS 的DNS 能力並不是我們說的web2 域名系統比如.com 可以實現在瀏覽器裡訪問域名來訪問你的ens 域名,ENS 的dns 註冊實際上只是基於DNS 安全擴展,通過相關的證明,校驗算法證明你對於該web2 的域名的所有權,然後在鏈上做一個(web2 域名到錢包地址)的記錄,使得我們可以用web2 域名進行鏈上轉賬。詳見(https://ensuser.com/docs/dns-registrar-guide.html)
但是本文為什麼我們沒有詳細講ENS 的DNS 模塊呢?是因為ENS 雖然花了大部分精力在實現DNS,但是這個功能放在ENS 比較雞肋,用的人很少。其實這實際上是一個did 聚合的範疇,類似的能力個人覺得更適合放到聚合DID 中去實現,比如像mask network 的nextid,cloak network 的zkid。可以把proof 做好,向即成twitter 等web2 的handler 一樣,去集成web2 域名。
部署自己的web3 域名
本文最後給大家提供一個改造後的ENS 域名合約版本( https://github.com/axtrur/xens-contracts 改造內容以及部署方式詳見readme),方便大家自行部署自己的web3 域名,深入理解web3 域名系統的設計原理部署goerli 測試網命令
OWNER_KEY={{account private key}} INFURA_ID=c03713652e3c4ef6a3c09ea7dbf58711 npx hardhat deploy –network goerli (INFURA_ID 可以替換成自己的infuraid,執行前刪除deployment/goerli 文件夾以及deployment/goerli_result.json)
部署測試網goerli 後,執行註冊腳本ens.js 註冊域名
OWNKEY={{account private key}} INFURA=https://goerli.infura.io/v3/c03713652e3c4ef6a3c09ea7dbf58711 node ens.js
就可以到opensea 測試網查看已經部署的nft 了,比如我部署的.buidlerdao 後綴的域名就可以到opensea 測試網查看已經部署的nft 了,比如我部署的.buidlerdao 後綴的域名
https://testnets.opensea.io/collection/buildlerdao-name-service
總結
ENS 域名作為web3 域名的先行者,在設計上有很多值得借鑒的地方,我們看到的.bnb,.nft 也都是基於ens 合約基礎上搭建的。希望通過本文大家對ENS 的設計從整體到細節有個深入的深入,web3 域名不僅僅是一個NFT,他有著更深遠的意義。同時web 域名只是一個很小的開始,相信隨之普及、生態集成以及大家對did 的探索,原生鏈上的可讀的web3 域名將會被聚合起來,使得每個用戶在加密世界裡都有個統一的名片描述,更好地去連接多鏈生態,連接用戶。
ENS 域名系統相關EIP 標準
EIP 137 – 註冊表https://eips.ethereum.org/EIPS/eip-137
EIP 181 – 反向註冊器https://eips.ethereum.org/EIPS/eip-181
EIP 205 – ABI 解析(ABI()).
EIP 619 – SECP256k1 公鑰解析(pubkey()).
EIP 634 – 文本記錄解析(text()).
EIP 1577 – 內容hash 解析(contenthash()).
EIP 2304 – 多Token地址解析(addr()). 新的記錄類型可以隨時通過EIP 標準化程序進行定義
主網部署的ENS 合約
註冊表合約:0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e
根合約:0xab528d626ec275e3fad363ff1393a41f581c5897
基礎正向註冊器合約:0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85
反向註冊器合約:0x084b1c3c81545d370f3634392de611caabff8148
默認正向解析器合約:0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41
默認反向解析器合約:0xa2c122be93b0074270ebee7f6b7292c7deb45047
控制器合約:0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5
ENS 域名資料
官網: https://app.ens.domains/
github: https://github.com/ensdomains
合約代碼:https://github.com/ensdomains/ens-contracts/tree/master/contracts
線網部署版本:https://etherscan.io/accounts/label/ens
文檔:https://ensuser.com/docs/contract-api-reference/ens-contracts-overview.html
交易市場:https://opensea.io/collection/ens
其他web3 域名系統資料
.bnb
官網: https://space.id/
github: https://github.com/Space-ID
交易市場:https://www.element.market/collections/space-id-bnb
.bit
官網:https://www.did.id/
github:https://github.com/dotbitHQ
交易市場:https://opensea.io/collection/dotbit
.nft
官網:https://nft.space/
交易市場:https://www.element.market/collections/nft-name-service