EIP3074 簡介

EIP3074 會帶來的風險與改變

Anton Cheng
Taipei Ethereum Meetup
19 min readApr 23, 2024

--

前言

這幾天由於一個跟 AA 相關的 EIP3074 被確認排入下一次 Pectra 硬分叉,因此引起了社群廣泛的討論。Twitter 和一些區塊鏈媒體上都有許多關於 EIP3074 的介紹,但是由於 EIP3074 實際改動有點複雜,也有容易讓人誤解的地方,因此今天希望用這篇文章釐清大家對於這個 EIP 的疑慮。

EIP3074 的主要目的,可以用一句話概括:讓 EOA (externally owned account) 在不需要部署合約錢包 (smart contract wallet) 的情況下,擁有合約錢包的功能。這表示一般錢包用戶能直接「升級」自己的 EOA,就像安裝擴充功能一樣,不需要部署新合約或變更現有的地址。那究竟是怎麼做到的呢?

EIP3074 前的交易流程

大部分的合約在開發時,都仰賴 msg.sender 這個變數來判斷來者何人,即「呼叫此合約的帳戶」。如果有一連串的合約連續呼叫,則每次對外進行呼叫 (CALL), 接收方收到的 msg.sender 都會更新。

每一個 call,都會開啟一個新的 call frame,有新的 msg.sender

由於合約都仰賴 msg.sender 來判斷呼叫者的身份,因此常常會不知不覺中限制了呼叫者的 UX。如果 Alice 用了一個 EOA (alice.eth) 收取了 ERC20 代幣,那麼她必須要從自己的 EOA 花 eth 發出交易,才能製造出呼叫者 msg.sender = alice.eth 這樣的條件,去呼叫合約進行轉帳。

同樣的原因,加上需要先對代幣合約進行 approve,才能給其他 Dapp 使用,也造成許多交易都必須用 EOA 發起兩筆交易才能完成。

需要兩筆交易進行一個 swap

這就最為人詬病的 UX 問題;也是所有 AA 文章都會介紹的兩點:必須要親自用 eth 付手續費、且不能使用批次交易。

在沒有 EIP3074 之前,我們必須仰賴合約錢包來解決這些問題,因為合約錢包能自動幫你打包多筆交易,並且讓別人代付手續費。但這樣的升級不是無痛的,你必須要把所有的資產轉移到你新的合約帳戶中,並且以這個新的合約錢包作為你去收錢、以及做合約互動的主體:

使用傳統「合約帳戶」如何解決手續費代付,以及批次交易問題

如上圖中,儘管透過一個合約能送多筆交易了,但實際對應用程式合約發送 Call 呼叫的主體從 EOA 變成了紫色的合約錢包,我們被記錄在 Dapp 中的地址也從原本的 alice.eth 變成 smartalice.eth ,是被當作不同的兩個實體。

這樣的解決方法其實很有效,但是對於很多既有的 EOA 用戶來說,遷移EOA 成本很高,不但要找出所有存在不同協議裡的倉位,哪天發現有忘記的錢或是有空投可以領又要大費周章轉 eth 回來付 gas。這或許是到今天為止合約錢包都不普遍的原因之一,畢竟大家剛開始接觸區塊鏈可能不會使用這麼 fancy 的錢包,也不會想要這些比較進階的功能,但使用久了又懶得換了。

EIP3074 的解決方法

如果退一步想,我們使用 msg.sender 是為了驗證發起交易者的身份,那麼其實只要確保 msg.sender 有正當的授權,我們並不一定要強制他一定要是呼叫本合約的「前一個地址」。

對於 EIP3074 很好的解釋方法,就是通過新的 OP code,和一個新的錢包外掛: invoker(調用者)合約打破既有的 msg.sender 必須來自前一個呼叫地址的限制。一旦我們打破這個限制,我們可以允許客製化的「代理邏輯」寫在 invoker 合約中,創造非常多的變化。

A motivating use case of this EIP is that it allows any EOA to act like a smart contract wallet without deploying a contract.

EIP3074 新的交易流程圖如下:

經由 Invoker 驗證後發出的呼叫,會使用新的 AUTHCALL,而非 CALL。對被呼叫的合約而言 msg.sender 變成了最初授權簽章的 EOA。

新的 OP Code

這個 EIP 透過兩個新的 OP code 來做到這個效果: AUTHAUTHCALL 。AUTH 就是授權的步驟,會確認 EOA 的持有者有簽過名,授權 invoker 做出代理呼叫,接著合約如果使用 AUTHCALL 取代 CALL ,則對外呼叫時 msg.sender 即可以被覆寫為 EOA 簽名者。

註:這個 EIP 剛提出時,其實只有一個 OP code,就是 AUTHCALL ,並且直接對著要對外呼叫的內容 (calldata) 進行簽章,相當於每個對外的授權呼叫就要簽名一次。但後考慮到 invoker 一個很大的使用情境是來幫大家做批次交易,如果每次對做 AUTHCALL 都要驗一次簽章,將會代表用戶如果要批次做多筆交易,就要簽名多次,體驗不是很好。因此後來轉向給予 invoker 最大的靈活度,把一個 OPCode 分成兩個階段,只要在同一個 call frame 中 AUTH 過一次,就可以做多次的 AUTHCALL

一樣是一筆交易 (1 tx),使用兩次對外 AUTHCALL 來做到 batching 的效果

執得注意的 EIP 細節

Invoker 的角色

這個 EIP 在第一眼看到時,看起來非常的可怕,畢竟「允許合約代表我的 EOA」聽起來是一個潘朵拉的盒子,將引來無盡災禍。但我認為這是因為我們平時在理解合約的時候,會時常聯想到一些 DeFi Protocol,他們通常可升級、有一些信任假設,並且常常被 hacked。這些都讓我們不信任智能合約。因此很多人對於這個 EIP 的質疑,來自合約的安全性。

但我自己認為一個比較好理解方式,是把 invoker 當作錢包的一部分,而非是一個傳統的合約。換句話說,上述這兩個 Opcode 不應該被任何 DeFi protocol 使用,相反的,他們只應該被新的錢包外掛使用。

未來錢包跟 invoker 將會有一層信任關係:錢包方會自己開發 invoker、或使用開源並經過審計的 invoker,並且僅允許用戶使用他們審核過、並且相信的 invoker。我們未來對錢包商的信任基礎,也會包含這些 invoker,有點像是選用合約錢包時一樣,把它歸為整體錢包的一部分。

簽章內容

前面有提到,原本這個提案是主張一個 AUTHCALL 簽一次名,不過在決定分成兩個階段後的一個重要精神,就是把大部分可以由 invoker 做到的工作都交給 invoker,因此用戶所須簽的訊息只包含了以下幾點:

  1. type byte (0x04):為了確保不同種類的簽名結果不被別人亂用,以太坊上的簽章系統都會有一個特有的前綴。例如一般交易、signed message,EIP1559 都有不同的前綴。
  2. chain id:目前的鏈 id
  3. nonce:EOA 目前交易的計數(同一般交易所需要附帶的 nonce)
  4. invoker:將會使用 AUTHCALL 的合約地址
  5. commit:32 bytes,通常拿來放 invoker 客製化邏輯中,需要確認使用者有簽名確認的資訊 hash 結果。例如有個 invoker 的驗證邏輯是確保用戶有對 AUTHCALL 的 calldata 簽章,這個 invoker 就需要在合約的驗證流程中,自己把呼叫者丟進來的 calldata 都 hash 起來,看這樣能不能驗過簽章。

這個簽章內容其實還沒有蓋棺定論:其中比較有爭議的是 chainId 以及 nonce。其實一開始作者們是期望把所有東西都拿掉,只剩下 type, invoker, 以及 commit,這樣就把所有的實作空間都留給不同的 invoker 合約。但這樣的提案被 core devs 回饋有風險太多:

(1). 關於 chainId

沒有 chainId可能允許跨鏈的重送攻擊:如果有兩條 EVM chain 同時支援 EIP3074,那麼可能有用戶在第一條鏈上授權一個 invoker,該簽名卻被拿到另一條鏈上,被使用在一個同地址卻不同實做的合約中,那麼用戶在第二條鏈上的資產就會有危險。

補充:理論上用 CREATE2 deployer 來部署 invoker 可以解決這個問題,因為這個問題的主要風險是來自在不同鏈上,同個地址可以存在不同合約。例如我用一個簡單的私鑰部署一個看似正當且去中心化的 invoker 到主網上,地址為 0xaaabbb,我等大家簽了 EIP3074 授權之後,再到 Arbitrum 上面在一樣的 0xaaabbb 地址部署一個可以對外做任何呼叫且沒有驗證的 invoker,那我這個壞人就可以偷走你在 Arbitrum 鏈上的 EOA 控制權。

要如何用 CREATE2 deployer 解決這個問題呢?因為 CREATE2 這個 Op code會強制一定要部署一樣的合約程式碼及一樣的 salt(亂數),才有辦法部署出一樣的地址。因此透過通用的 CREATE2 Deployer 部署出來的合約,你可以相信這個地址要嘛在其他鏈上不存在合約,如果存在,則合約內容一定跟主網一樣。

這裡列幾個常見的 CREATE2 Deployer,連結到他們的 repo 或是 etherscan:

(2). 關於 nonce

沒有 nonce 則是可能允許 invoker 拿到無法撤銷的授權:假如今天一個 invoker 忘了實作簽章不能被重複使用的邏輯 (replay protection),有可能造成壞人能無限制的使用一個用戶同個簽名發起多次 AUTHCALL 。如果保留這個 nonce 的驗證,用戶在任何時候只要發起一筆普通交易,就可以讓所有現在在外面的簽章無效化,過程大概是:

假設一個 EOA 現在 nonce 是 10 => 他的主人可以做一筆簡單的以太坊交易,例如轉帳給自己 => 以太坊紀錄的 EOA nonce 會增加 1 變成 11 => 之後驗證 AUTHCALL 都會使用 nonce = 11 來驗章,造成之前簽的簽章無效化。

這裡牽扯到的問題是,到底這個 EIP 要給 invoker 多大的自由與彈性:沒有了 chainId 的限制,invoker 可以透過多鏈部署,讓用戶享有「一個簽章、全部 EVM chain 都升級」的功能;而沒有 nonce 的限制的話,能夠讓錢包設計 invoker 跟 EOA 能同步做交易,做到更多自動化。

目前這些細節都還在討論中,以提案的現有狀況來說,作者 (SamWilsn) 其實也是偏好開放最大自由度的,但 core dev 的立場比較偏好不要一次加入太多的 attack vector,因此 nonce 跟 chainId 目前還是暫時被加回了需要簽章的資訊中。

這個討論還在持續中,大家可以加入 forum 來發表意見

重送攻擊的預防實作

儘管上面有說, EOA 交易的nonce 必須涵蓋在簽章中,但這只是為了確保「使用者 100% 可以讓 EIP3074 簽章無效化」並不限制一個簽章能被用幾次。

這是因為當 AUTH 被使用的時候,他會檢查這個 nonce 是否等於使用者目前 EOA 交易的 nonce ,只要相同就會給過,但並不會更改 EOA 的 nonce ,因此一個簽名是可以被 AUTH 驗證數次的。

Invoker 有義務要自行實作一些重送攻擊的預防邏輯,但是可以有很多變化,另如自己做一個像是 Uniswap Permit2 的 nonce 系統,不要用遞增的方式來確保可以平行送出多筆交易、也可以批次取消等等。

風險與常見誤解

以下列出對於這個 EIP 常見的誤解:

1. EIP3074 讓釣魚詐騙變得更簡單了?

有了這個 EIP 之後,智能合約確實有了透過一個簽章得到 EOA 控制權的能力。但如前面所說的,invoker 應該要跟錢包是一個綁定的關係,錢包應該要拒絕所有不認識的 EIP3074 簽章請求。

由於每種不同的簽章有不同的前綴,目前所有市面上的錢包都會默認拒絕 EIP3074 的代理簽章,因此不用擔心你的錢包不支援讓你被騙。(也沒有任何錢包支援用 private key 盲簽未知交易格式。)

除了錢包方需要主動的去開啟這個功能,大家也並不預期錢包方會接受一個網站「要求簽署 EIP3074」簽章,因為像上面說的,這並不是一個正常的 DAPP 會要求錢包做的事情,因此我們可以預期錢包只會允許啟用他們自己認證過的 Invokers。

這跟釣魚網站不同的點是:錢包本來就會期待來自網站(Dapp 端)的交易請求,因此釣魚網站透過騙你點擊按鈕,傳送「惡意交易請求」給錢包端,如果錢包端沒有好的預防,你可能就會點下簽署交易。而 EIP 3074 的簽章是完全不同的形式,錢包也沒有理由要允許來自網站的「代管私鑰需求」, 因此不用擔心你的錢包會偵測不到。

這裡補上作者之一 lightclient 對此問題的回應

雖然理論上錢包不該開啟客製 invoker 的功能,但還是有可能有些錢包為了給用戶最大自由度,而提供這個進階選項。如果真的有這樣的錢包,那就可能會出現類似釣魚網站的攻擊了:例如他們做一個很酷的網站告訴你加入 invoker 後能有什麼功能,騙用戶忽略錢包的一些警告而簽章。

2. 無法奪回的錢包控制權

有人在介紹 EIP3074 時寫到,這可能造成用戶一個無法駁回的單次授權,造成所有的控制權喪失。這也是不對的,在目前的 EIP 中,因為簽名內容保有 nonce,任何用戶都可以發交易來讓簽出去的簽章無效化。

若是 nonce 沒有被加入必要簽章格式中,這樣的情境仍然建立在「合約有漏洞」的情況下。在一般合約實作中,讓 nonce 無效化是很簡單的一個步驟功能,也有無數經過驗證的實作方法,除非遇到惡意的 invoker(惡意錢包),否則這方面要出錯的機率很低。

我認為討論這個風險(或是類似的多鏈重送攻擊)就好像討論「合約錢包可能讓你永久失去控制權」一樣,就是在討論合約存在 bug 的風險,考慮到這個問題的複雜度,要防範不會太困難。

潛在的風險

上面說的兩個點:惡意 invoker 釣魚網站,還有 bug 造成的永久喪失控制權,我都覺得不是可行的攻擊。但這個 EIP 確實還是有帶來風險,我認為比較大的風險會在攻擊硬體錢包這塊。

軟體錢包一直都有很大的風險:假如下載了一個假的軟體錢包,把私鑰存進去,那麼你的錢就會直接不見了,因此大家對於軟體錢包 App 已經有比較高的資安意識。但硬體錢包在以前是比較安全的,只要你的硬體不要掉,基本上不可能有人可以從中讀走你的私鑰。

但如果有開源的硬體錢包支援了 EIP3074 簽章,不能排除有人做一些新的 App,同樣去接硬體錢包,並推廣給用戶(例如做一個手機 App 可以跟 Ledger 一起使用)。這些惡意的錢包 App 就可以接著騙你簽一個簽名來獲得私鑰控制權。當然,這個前提也是要先讓你相信這個新的錢包 App 是真的,不過假如他們打著「跟老牌硬體錢包合作」的口號,可能很多用戶會忽略這個風險。或是未來有更多詐騙會嘗試攻擊錢包 App DNS 、騙你下載假的 Ledger App 等等,都能用來偷取既有硬體錢包用戶私鑰控制權。

總體來說,大家只要繼續謹記謹慎選錢包(無論軟硬體),就不會有上述問題。遇到錢包推出新的 invoker 功能時,記得以「審視合約錢包」的安全指標來審視 invoker,也能有效的避免一些 bug 造成的問題。

革新應用

這裡提幾個我覺得很有趣,非常值得期待的應用:

(1) 老地址空投領取器:

現在如果有舊地址收到空投,都需要轉一些 eth 回去當 gas,領了空投後再轉出來,如果有了 EIP3074,就可以用領到的空投代付手續費,直接一個簽名領取空投、付費並且轉到新地址中。或是以前老舊錢包裡的一些殘留 ERC20 也可以拿來付交易手續費。

(2) 簽署對 ENS 的交易:

以前 EOA 交易格式都是簽署轉帳到一個地址,未來可以讓用戶簽署「要轉給哪個 ENS」,透過 invoker 解析出地址再進行轉帳,大大增加轉帳的 UX。

(3) 簽署有到期日的交易:

以前的交易內容沒有到期日這樣的參數,如果你簽出去之後一直沒被 confirm,後面悔改了要手動取消,否則他就會一直處在 pending 狀態。有了 invoker 之後我們可以很輕鬆的加入「超過時間便無效」這種限制,簡單的製造出有到期日的交易格式。

(4) 更多元的錢包代理:

我們可以有更多「代理錢包」的玩法,甚至增加錢包的安全性:使用冷錢包授權不同的地址,指定各個地址只能做特定交易、有不同的到期時間,例如:每一個月用冷錢包授權一次我的 metamask 地址可以為我發起最多 10 筆 Uniswap 交易。這樣對於「私鑰」的安全等級我們可以有更多的彈性,也將不再受到「不同私鑰不能管同筆資產」的限制。

EIP3074 未來可期!

可能帶來的改變

我自己覺得比較宏觀來說,可能對整個市場帶來的改變有幾個:

1. 錢包差異化更大,用戶黏著度更高

以前做錢包的時候,最常想的就是如何留住用戶:畢竟大家私鑰自己存好、隨時想換別的錢包,只要下載安裝導入私鑰,馬上就搬家了。因為大家能做到的事情大同小異,就是發送交易。

未來有了 EIP3074 之後,各家 EVM 錢包 UX 可能會差的越來越多:有的可能單純支援 ERC20 付手續費、進階一點的就會有前述的代理、訂閱制的手續費、批次幫所有用戶一起交易等等。這些都會造成好的錢包跟一般錢包之間的差距放大,也會增加用戶黏稠度。

2. 錢包方可能擁有更大權力(交易排序權)

未來錢包可能在為用戶安排 orderflow 這塊會出現更大的競爭。我們可以越期越來越的用戶會習慣把真正「送出交易」的步驟代理給錢包方,錢包方也可以透過 invoker 做出「只有我能代理上鏈」的交易,相較於傳統交易在曝光到 mempool 後大家 (MEV searcher ) 都可以任意排序,錢包方可能也有動機去限制這些交易的順序,從中獲利。

3. 一般合約可以回歸更基礎的設計流程

以前為了提升 UX,合約設計中會考慮 permit 、multicall,meta-transaction, wrap ETH 等等。我一直很希望 AA 時代快點到來,就是因為覺得這些東西如果不需要在 protocol 層考慮,可以減少很多安全疑慮,例如完全讓錢包端處理 Wrap ETH 可以確保一個 protocol 完全不需要考慮 eth 的轉帳以及重入風險。這個 EIP 我覺得可以期望看到現有的錢包商如 Metamsk, Rabby 都馬上投入 invoker 的開發,大大增加了普及批次交易的機會,如果未來批次交易普及,就能省去很多這方面的合約優化。(不用每個人的合約都繼承 MultiCall 也是幫整個網路省空間吧!)

4. 對合約使用 msg.sender 與 tx.origin 的影響

最後一個講的比較細節,跟合約開發者相關的議題。

合約常常會使用 msg.sender == tx.origin 來做不同的限制。在 EIP3074 之後,使用者可以使用特別的 EIP3074 交易繞過這個檢查:最初發出交易的人(tx.origin) 可以透過一個中繼合約來做 AUTHCALL,同時附上自己的簽名,因此可以做到從中繼合約打出去的 AUTHCALL ,然後仍然符合 msg.sender == tx.origin 的條件。

大部分合約使用 msg.sender == tx.origin 的檢查是為了保證 msg.sender 為EOA,免得送 ETH 回去的時候觸發重入攻擊等。這個特性依然沒有改變,因為 tx.origin 必定是一個 EOAmsg.sender 也會指向最一開始的發起者,不用擔心不小心跟中繼合約互動,造成意想不到的後果。

不過這個 EIP 還是會影響到一些 usecase,例如用 tx.origin == msg.sender 來防止「合約依照一筆交易的狀態來控制後續交易的行為」。例如有人用合約 mint NFT,只要沒有成功 mint 出稀有的 NFT,就把整筆交易 revert 掉。但這個 usecase 其實已經可以透過 MEV 等等手法透過跟 builder 合作來破解了,所以算已經是無效的防護,影響並不大。

另一個會被影響的 usecase,是用同樣的條件來防止 flashloan。以前如果有合約不想被用戶透過 flashloan 使用,可以透過 tx.origin == msg.sender 確定來的人沒有透過 flashloan 借錢。現在發起者可以在 flashloan 借到錢後,透過中繼合約呼叫 AUTHCALL ,對協議方來說,看起來就會是 msg.sender 真的帶著大筆的鈔票來使用這個合約。

理論上,好的合約不應該使用 tx.origin 做任何判斷,因為這會破壞 AA 錢包或 meta-transaction 等體驗,使用 tx.origin也已經被大量推廣為 bad practice。因此儘管這個 EIP 打破了某些使用情境,對整體影響並不大。

結語

我自己十分期待下次升級的到來,畢竟 AA 說了這麼久了,真的看到做出抽象帳戶的錢包屈指可數,也一直沒有等來說好的大量從 EOA 到合約錢包的升級。

我認爲 EIP3074 給予了所有錢包開發商非常大的升級空間,因為大家可以幾乎無痛(不需遷移)的為用戶帶來體感上的大升級。真的很期待真的上線時,各個錢包商能變出什麼新玩法,這裡也小小許願,最後 EIP 可以以不限制 nonce 跟 chainId 的自由之姿進入硬分叉,這樣就有更多跨鏈錢包等等功能可以期待了。

Reference 與推薦讀物

--

--