在不同鏈佈署相同合約到相同地址:Nick’s Method 介紹與比較
2024-11-08 09:32
Taipei Ethereum Meetup
2024-11-08 09:32
订阅此专栏
收藏此文章

最近因緣際會下看到 ERC-6551 提到 Nick’s Factory,好奇地去了解一下這是什麼魔法,發現了古老(?)而有趣的 Nick’s Method。這是一個可以在無信任的條件下,在特定地址上執行特定交易的方法(即便你和所有人都不知道密鑰)。在這篇文章中,我想介紹 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 到多鏈佈署

現在,你應該大概知道 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 小結

到這裡,我們嘗試在沒有密鑰的狀況下,創造一個有效交易出來。然而也可以發現以下問題:

  1. EIP-155 後,部分 RPC Node 或鏈不支援該類交易:如果你嘗試透過 Etherscan 廣播該交易,可能會告訴你該 RPC Node 不收這類交易。而有些鏈則是從一開始就不打算收,特別是 zk-Rollup 們因為電路大小的考量,傾向於不支援。
  2. Gas Price 與 Gas Limit 無法依需調整:由於 Gas Price 與 Gas Limit 在交易創建時就需要指定,因此對於如 Sepolia,100 gwei 可能太少,但對於其他鏈而言則可能太高。
延伸閱讀:Nick’s method — Ethereum Keyless Execution

其他多鏈佈署方案

如同最前面提到的,目前最通用的多鏈佈署方案大概非全新 EOA 莫屬,大概不會有幾個 EVM 鏈會去動到這部份的運算。然而若希望佈署過程不會依賴專有私鑰(例如提議 EIP 時),其他常見的多鏈佈署方案則主要有最一開始提到的 Nick’s Factory 以及 CreateX。

Nick’s Factory

Nick’s Factory 指的是透過 Nick’s Method 佈署的 Create2 Factory 來達到多鏈同地址。Create2 是 EVM 的一個 Opcode,同樣也會佈署合約,但地址的運算不依賴於佈署者的 Nonce,而是合約的 Init Code 與添加的 Salt。也就是說:

  1. 透過 Nick’s Method 佈署一個 Create2 Factory 到某地址
  2. 透過該地址,給定固定的 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 的影響,在更新的鏈可能需要鏈的項目方特別處理,因此不防過時。

EOA + Create2 Factory

用與 Nick’s Factory 類似的思路,我們可以使用新的 EOA 先建立一個 Create2 Factory,再透過此 Create2 Factory 佈署新的合約。由於該 Create2 的地址會是由該 EOA 的 Nonce 算出(各鏈中都相同),因此若透過此 Create2 Factory 使用相同 Bytecode 與 Salt 佈署合約,則會取得相同的合約地址。如此一來也可在不同鏈上取得相同地址。

CreateX

CreateX 是基於 Create3 概念所建立的專案。Create3 Factory 指的是結合 Create 與 Create2,目的在於消除 Init Code 的硬性要求。當在建立多鏈合約時,我們可能希望不同鏈有不同的 Constructor 參數,但這會影響到 Init Code。Create3 使用與 Create2 Factory 相同的概念,但會先產生一個合約,並在該合約使用 Create Opcode 建立最後希望的合約。具體而言:

  1. 透過 Create2 佈署一個 Deployer Contract
  2. 透過 Deployer Contract 建立合約(基於 Nonce 計算地址)

由於該 Deployer Contract 在給定相同 Salt 下,不同鏈中會是相同地址,且 Nonce 均為 1。因此佈署新的合約時,無論合約內容都會得到同一個地址。

延伸閱讀:CREATE3 多鏈部署合約於相同地址

Hardhat 使用的即是 CreateX

然而 CreateX 仰賴開發者的私鑰,因此有中心化的疑慮。

比較

到目前,我們看了五個多鏈佈署的方案,以下做個比較:

  • EOA(有 Private Key):需信任、成本最低、防過時、中心化
  • EOA(無 Private Key):不需信任、成本難控制,不防過時、去中心化
  • EOA + Create2:不需信任、成本可控、防過時、中心化
  • Nick’s Factory:不需要信任,成本可控,不防過時、去中心化
  • 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.

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

Taipei Ethereum Meetup
数据请求中
查看更多

推荐专栏

数据请求中
在 App 打开