最近因緣際會下看到 ERC-6551 提到 Nick’s Factory,好奇地去了解一下這是什麼魔法,發現了古老(?)而有趣的 Nick’s Method。這是一個可以在無信任的條件下,在特定地址上執行特定交易的方法(即便你和所有人都不知道密鑰)。在這篇文章中,我想介紹 Nick’s Method 和其他方法的比較。
Nick’s Method 最早是 Nick Johnson 使用的技法,他是 ENS 的首席開發者,之前曾是 Ethereum Foundation 的人。這個技法被運用在 The DAO 攻擊事件後的代幣分發,目的是發代幣到 11440 個地址中。發代幣的錢包是個多簽錢包(所以要他們聚在一起簽 11440 個交易多少有點不實際,即便透過 MultiSend 也需要 104 個)。
Nick’s Method 使我們可以先決定交易內容,接著再決定執行交易的錢包。因此在代幣分發時,首先先建立分發交易(104 個 MultiSend 分發到 11440 個錢包)的集合,再透過 Nick’s Method 決定執行這 104 個交易的錢包,接著用多簽錢包執行一次 MultiSend 到這 104 個錢包,最後發出 104 個 MultiSend 交易。如此一來就避免需要信任分發者的問題。
你可能會好奇:所以這關多鏈地址什麼事?
在說明 Nick’s Method 為什麼可以用到多鏈地址前,先說明地址是如何決定的可能會更好理解。目前地址的決定有兩種:一、透過合約建立者地址與其 Nonce 計算得出。二、透過建立者地址、合約 Bytecode、以及自選的 Salt 計算得出。而第一種就是 Nick’s Method 能成立的原因。
而在 Nick’s Method 中,我們會「創造」一個建立者,因為沒有用過的地址的 Nonce 是 0,因此在不同鏈上所計算出來的地址就都會一樣。接著下個問題是:怎麼「創造」的?
我們習慣的流程通常是先產生一個私鑰(也就是錢包),接著放一些原生代幣到這個錢包,再用這個錢包簽署一個建立合約的交易,最後把這個交易放到鏈上完成佈署。
我們有沒有可能把產生私鑰這段過程變不見呢?
為了說明流程,我們還要了解一下交易簽署與驗證的過程,為了避免密碼學的數學部分讓你失去方向,這裡會將太過數學的部分跳過。簡單而言,交易簽署與驗證是透過數位簽章技術達成的。這也是當你按下錢包介面中的送出後,錢包內部會做的事情。當這個簽章與交易一起被發出去之後,其他人便會檢查這個簽章和內容是否相符,以及簽署者是否正確。
而以太坊使用的數位簽章,ECDSA,會對於一段訊息使用私鑰(約略等於助記詞)進行簽章,這個簽章是三個數字,通常使用 r、s、v 作為符號。當驗證簽章時,我們使用原本的訊息與這三個數字去逆算出簽名者,再比對這個簽名者是否與我們預期的一樣。
而數位簽章確保了使用某金鑰進行簽章的話,反算出來的簽名者一定會是該金鑰對應的公鑰(約略等於地址)。因此可以確保該交易確實是被該地址核可的。
或許你注意到了,簽名者實際上是被逆算出來的,因此即便隨意的給一個簽章,幾乎都能找到對應的簽名者(但如果你想找到特定的簽名者,那機率是十分地渺茫了)。
延伸閱讀:Kimi 老師的 ECDSA 介紹
現在,你應該大概知道 Nick’s Method 是怎麼發生的了:我們創建一個交易,任意的給予一個簽章,然後看看這個簽名者是誰。現在我們來嘗試一下,我們使用 Remix 隨意地建立一個合約並取得 Bytecode:
如果你很懶,以下是圖中合約的結果:
6080604052348015600e575f80fd5b50603e80601a5f395ff3fe60806040525f80fdfea26469706673582212205dd80bbc56676990b6e0302cf7586b7d29c9a86e8e324ccbe505b5729a26ca2d64736f6c634300081a0033
接著,我們透過 Ethers.js 的 Playground 來建立一個交易。我們執行以下指令(撰文當下為 v6,未來改版可能會有所不同):
tx = new Transaction()
tx.gasPrice = 100_000_000_000
tx.gasLimit = 1_000_000
tx.to = ZeroAddress
tx.type = 0
tx.data = '0x' + '6080604052348015600e575f80fd5b50603e80601a5f395ff3fe60806040525f80fdfea26469706673582212205dd80bbc56676990b6e0302cf7586b7d29c9a86e8e324ccbe505b5729a26ca2d64736f6c634300081a0033'
這個交易是一個 Gas Price 為 100 gwei、Gas Limit 為 100 萬的合約創建交易。最後的 '60....' 是剛剛產生的合約,如果有使用自己的合約的話請記得要更改。接著,我們隨意的產生簽章:
tx.signature = Signature.from({r: hexlify(randomBytes(32)), s: hexlify(randomBytes(31)), v: '0x1b'})
我們隨機的產生 32 bytes 給 r、31 bytes 給 s、並固定 v 為 27(0x1b)。s 是因為被要求要是 low s value,簡單而言第一個 bit 要是 0。v 為 27 的原因是 EIP-155 開始透過簽名的 v 來指定鏈,因此使用 EIP-155 前的值 27 或 28 來避免違反。(目前似乎有許多鏈拒絕這類交易,因此部分鏈可能無法使用。)
我們輸入 tx 來看看目前的交易,會發現 from 欄位被推算出來了。
如果我們真的傳送一點 ETH 到這個地址,再廣播出去的話,會發現該交易是有效的。你可以透過 tx.serialized 來得到 Raw Transaction,並在瀏覽器的 Console 發出交易。請注意替換 <INFURA-KEY> 與 <TX> ,前者可以在 Infura 申請免費帳號並取得,後者則是 tx.serialized 的內容。
fetch('https://sepolia.infura.io/v3/<INFURA-KEY>', {
method: 'POST',
headers: {
'Content-Type': 'application.json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'eth_sendRawTransaction',
params: ['<TX>'],
id: "1"
})
}).then(e => e.json()).then(console.log)
查詢該交易,可以看到確實進到 Mempool 了(至於他要怎麼在平均 Gas Price 3.5k 的 Sepolia 鏈上飄到確認則是另一個故事了。)
到這裡,我們嘗試在沒有密鑰的狀況下,創造一個有效交易出來。然而也可以發現以下問題:
延伸閱讀:Nick’s method — Ethereum Keyless Execution
如同最前面提到的,目前最通用的多鏈佈署方案大概非全新 EOA 莫屬,大概不會有幾個 EVM 鏈會去動到這部份的運算。然而若希望佈署過程不會依賴專有私鑰(例如提議 EIP 時),其他常見的多鏈佈署方案則主要有最一開始提到的 Nick’s Factory 以及 CreateX。
Nick’s Factory 指的是透過 Nick’s Method 佈署的 Create2 Factory 來達到多鏈同地址。Create2 是 EVM 的一個 Opcode,同樣也會佈署合約,但地址的運算不依賴於佈署者的 Nonce,而是合約的 Init Code 與添加的 Salt。也就是說:
延伸閱讀:Create2 介紹—EIP-1014 產生可控的智能合約地址
Nick’s Factory 的佈署在 0x4e59b44847b379578588920cA78FbF26c0B4956C 。透過發送 Data 為 <SALT><BYTECODE> 的交易到此地址就可以建立合約。範例可以參考 ERC-6551。
Foundry 中,如果佈署合約的腳本添加了 Salt,則預設使用此 Nick’s Factory 佈署。而 ERC-2470 也是利用相同方法,建立了 Create2 Factory。
然而,由於 Nick’s Factory 應用了 Nick’s Method,因此無法躲過 EIP-155 的影響,在更新的鏈可能需要鏈的項目方特別處理,因此不防過時。
用與 Nick’s Factory 類似的思路,我們可以使用新的 EOA 先建立一個 Create2 Factory,再透過此 Create2 Factory 佈署新的合約。由於該 Create2 的地址會是由該 EOA 的 Nonce 算出(各鏈中都相同),因此若透過此 Create2 Factory 使用相同 Bytecode 與 Salt 佈署合約,則會取得相同的合約地址。如此一來也可在不同鏈上取得相同地址。
CreateX 是基於 Create3 概念所建立的專案。Create3 Factory 指的是結合 Create 與 Create2,目的在於消除 Init Code 的硬性要求。當在建立多鏈合約時,我們可能希望不同鏈有不同的 Constructor 參數,但這會影響到 Init Code。Create3 使用與 Create2 Factory 相同的概念,但會先產生一個合約,並在該合約使用 Create Opcode 建立最後希望的合約。具體而言:
由於該 Deployer Contract 在給定相同 Salt 下,不同鏈中會是相同地址,且 Nonce 均為 1。因此佈署新的合約時,無論合約內容都會得到同一個地址。
延伸閱讀:CREATE3 多鏈部署合約於相同地址
Hardhat 使用的即是 CreateX。
然而 CreateX 仰賴開發者的私鑰,因此有中心化的疑慮。
到目前,我們看了五個多鏈佈署的方案,以下做個比較:
註:信任指其他人是否可信任某多鏈合約的內容在不同鏈上均相同。中心化指是否會依賴特定私鑰。
對於一般多鏈專案而言,使用專用 Private Key 可能還是更好的選項,而當建立基於多鏈專案應用上,還是免不了要逐鏈審查合約內容(畢竟還是得看看合約是用什麼方法佈署的,但可能比看 Bytecode 或 Source Code 要快一點點……嗎),可能沒有方法輕易的繞過這部份的工作。
最近因緣際會(?)下看了 ERC-6551,而發現了陌生的詞「Nick’s Method」,起初還查不太到是什麼,但後來看到那篇介紹後才意外發現這個古老的手藝(但可能也快被迫失傳了)。個人感到最有趣的點是沒想過可以反過來用簽章來決定地址(雖然之前一直摸 ECDSA),從而創建一個沒有私鑰的交易。
不過之前聽了海帶老師介紹 EOFv1,似乎 Create 和 Create2 也快要被淘汰了,所以可能這些都不防過時了。
在不同鏈佈署相同合約到相同地址:Nick’s Method 介紹與比較 was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。