天天看點

解析被納入布拉格更新的 EIP3074:用例、影響與風險

作者:MarsBit

原文作者:Anton Cheng

原文來源:medium

前言

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

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

解析被納入布拉格更新的 EIP3074:用例、影響與風險

EIP3074前的交易流程

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

解析被納入布拉格更新的 EIP3074:用例、影響與風險

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

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

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

解析被納入布拉格更新的 EIP3074:用例、影響與風險

需要兩筆交易進行一個swap

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

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

解析被納入布拉格更新的 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新的交易流程圖如下:

解析被納入布拉格更新的 EIP3074:用例、影響與風險

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

新的OP Code

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

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

解析被納入布拉格更新的 EIP3074:用例、影響與風險

一樣是一筆交易(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起來,看這樣能不能驗過簽章。
解析被納入布拉格更新的 EIP3074:用例、影響與風險

這個簽章内容其實還沒有蓋棺定論:其中比較有争議的是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:

  • Seaport keyless CREATE2
  • Deterministic-deployment-proxy
  • Safe-singleton-factory

(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目前還是暫時被加回了需要簽章的資訊中。

解析被納入布拉格更新的 EIP3074:用例、影響與風險

這個讨論還在持續中,大家可以加入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:用例、影響與風險

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必定是一個EOA,msg.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的自由之姿進入硬分叉,這樣就有更多跨鍊錢包等等功能可以期待了。