作者:Kong

編輯:Sherry

前言

以太坊即將迎來Pectra 升級,無疑是個意義重大的更新,許多重要的以太坊改進提案將藉此契機被引進。其中,EIP-7702 對以太坊外部帳戶(EOA) 進行了變革性的改造。該提案模糊了EOA 與合約帳戶CA 之間的界限,是繼EIP-4337 之後,朝著原⽣帳戶抽象邁進的關鍵一步,為以太坊生態系統帶來了全新的互動模式。

目前,Pectra 已在測試網路完成部署,預計不久後便會上線主網。本文將深入剖析EIP-7702 的實現機制,探討其可能帶來的機會與挑戰,並為不同的參與者提供實用的操作指南。

協定分析

概述

EIP-7702 引入了一種全新的交易類型,它允許EOA 指定一個智慧合約位址,進而為其設定代碼。如此一來,EOA 便能夠像智慧合約一樣執行程式碼,同時也保留了發起交易的能力。這項特性為EOA 賦予了可程式化與可組合性,使用者藉此可以在EOA 中實現諸如社交恢復、權限控制、多簽管理、zk 驗證、訂閱式支付、交易贊助以及交易批次等功能。值得一提的是,EIP-7702 能夠與EIP-4337 實現的智慧合約錢包完美相容,二者的無縫整合極大地簡化了新功能的開發與應用過程。

EIP-7702 的具體實作是引入了交易類型為 SET_CODE_TX_TYPE (0x04) 的交易,其資料結構定義如下:

rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, value, data, access_list, authorization_list, signature_y_parity, signature_r, signature_s])

其中 authorization_list 欄位定義為:

authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...]

在新的交易結構中,除了 authorization_list 字段,其餘都遵循與EIP-4844 相同的語義。此欄位是清單類型,清單中可以包含多個授權條目,在每個授權條目中:

  • chain_id 欄位表示此授權委託所生效的鏈
  • address 欄位表示委託的目標位址
  • nonce 欄位需與目前授權帳戶的nonce 相符
  • y_parity, r, s 欄位則是授權帳號簽署授權的簽章數據

在一筆交易內的 authorization_list 欄位可以包含多個不同授權帳戶(EOA) 簽署的授權條目,即交易發起者可以與授權者不同,以實現對授權者的授權操作進行gas 代付。

實現

授權者在簽署授權資料時,需要先將 chain_id, address, nonce 進行RLP 編碼。隨後將編碼後的資料與 MAGIC 數一起進行keccak256 雜湊運算,從而得到待簽署的資料[1]。最後,使用授權者的私鑰對哈希後的資料進行簽名,進而獲得 y_parity, r, s 資料。其中,MAGIC (0x05) 是作為域分隔符號使用,其目的是確保不同類型簽署的結果不會產生衝突。

// Go-ethereum/core/types/tx_setcode.go#L109-L113func (a *SetCodeAuthorization) sigHash() common.Hash { return prefixedRlpHash(0x05, []any{ a.ChainID, a.Address, a.Nonce,a.

需要注意的是,當授權者授權的 chain_id 為 0 時,則代表著授權者允許[2]在所有支援EIP-7702 的EVM 相容鏈上重播授權(前提是nonce 也剛好符合)。

// Go-ethereum/core/state_transition.go#L562if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 { return authority, ErrAuthorizationWrongChainID}

當授權者簽署授權資料後,交易發起者會將其匯聚在 authorization_list 欄位中進行簽署並透過RPC 進行交易廣播。在交易包含在區塊中執行之前,Proposer 會先對交易進行預檢查[3],其中對 to 位址進行強制檢查以確保此交易不是合約創建交易,也就是說在發送EIP-7702 類型的交易時,交易的 to 位址不能為空[4]。

// Go-ethereum/core/state_transition.go#L388-L390if msg.To == nil { return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)}

同時,此類交易會強制要求交易中的 authorization_list 欄位必須至少包含有一項授權條目,如果有多個授權條目都由同一個授權者簽署,那麼最終只有最後一個授權條目起效。

// Go-ethereum/core/state_transition.go#L391-L393if len(msg.SetCodeAuthorizations) == 0 { return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)}

隨後,在交易執行過程中,節點會先增加交易發起者的 nonce 數值,再對 authorization_list 中的每個授權條目進行 applyAuthorization 操作。在 applyAuthorization 操作中,節點會先檢查授權者的 nonce,然後再增加授權者的 nonce。這表示如果交易發起者與授權者為相同使用者(EOA) 時,在簽署授權交易時 nonce 的數值應該再加1。

// Go-ethereum/core/state_transition.go#L489-L497func (st *stateTransition) execute() (*ExecutionResult, error) { ... st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.

// Apply EIP-7702 authorizations. if msg.SetCodeAuthorizations != nil { for _, auth := range msg.SetCodeAuthorizations { // Note errors are ignored, we simply skip invalid authorization peridity. go#L604func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error { authority, err := st.validateAuthorization(auth) ... st.state.SetNonce(authority, authidateAuthorization(auth) ... st.state.SetNonce(authority, auth.Nonce>, tra. 566func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) { ... if auth.Nonce+1 < auth.Nonce { return authority, ErrAuthorizationNonceOverflow } ...}

在節點套用某個授權條目時,如果遇到任何錯誤,這條授權條目將被跳過,交易也不會失敗,其他授權條目會繼續進行應用,以此確保在批量授權場景中不會出現DoS 風險。

// Go-ethereum/core/state_transition.go#L494for _, auth := range msg.SetCodeAuthorizations { // 註 errors are ignored, we simply skip invalid authorizations here. st.applyAuthorization(&authorization(&auth)}

在授權應用程式完成後,授權者位址的 code 欄位將被設定為 0xef0100 || address,其中 0xef0100 是固定的標識,address 是委託的目標位址。由於EIP-3541 的限制,使得使用者無法透過常規方式部署 0xef 位元組開頭的合約程式碼,這就保證了此類識別只能由 SET_CODE_TX_TYPE (0x04) 類型的交易才能部署。

// Go-ethereum/core/state_transition.go#L612st.state.SetCode(authority, types.AddressToDelegation(auth.Address))

// Go-ethereum/core/types/tx_setcode.go#L45var DelegationPrefix = []byte{0xef, 0x01, 0x00}

func AddressToDelegation(addr common.Address) []byte { return append(DelegationPrefix, addr.Bytes()...)}

授權完成後,授權者若要移除授權,只需將委託的目標位址設定為 0 位址即可。

透過EIP-7702 引入的新的交易類型,使得授權者(EOA) 既可像智能合約一樣執行程式碼,也同時保留了發起交易的能力。相較於EIP-4337,這為使用者帶來了更接近原生帳戶抽象化(Native AA) 的體驗,大大降低了使用者的使用門檻。

最佳實踐‍

儘管EIP-7702 為以太坊生態注入了新的活力,但新的應用場景也會帶來新的風險。以下是生態參與者在實踐過程中需要警惕的面向:

私鑰儲存

即便EOA 在委託後可以藉助智慧合約內建的社交復原等手段解決因私鑰遺失導致的資金損失問題,但它仍然無法避免EOA 私鑰洩漏的風險。需要明確的是,執行委託後,EOA 私鑰依舊對帳戶擁有最高控制權,持有私鑰便能夠隨意處置帳戶中的資產。用戶或錢包服務商在為EOA 完成委託後,即便完全刪除儲存在本地的私鑰,也無法完全杜絕私鑰洩漏風險,尤其是處於存在供應鏈攻擊風險的場景中。

對於用戶來說,在使用委託後的帳戶時,用戶仍應該將私鑰保護放在首位,時刻注意:Not your keys, not your coins。

多鏈重播

用戶在簽署委託授權時,能透過 chainId 選擇委託可以生效的鏈,當然用戶也可以選擇使用 chainId 為 0 進行委託,這使得委託可以在多鏈上重播生效,以方便用戶一次簽名即可在多鏈上進行委託。但要注意的是,在多鏈上委託的同一合約位址中,也可能有不同的實作代碼。

對於錢包服務商來說,當使用者進行委託時,應檢查委託生效鏈與目前連接的網路是否相符,並提醒使用者簽署 chainId 為 0 的委託可能帶來的風險。

使用者也應該注意,在不同鏈上的相同合約地址,其合約代碼並不總是相同,應先了解清楚委託的目標。

無法初始化

目前主流的智慧合約錢包大多採用代理模型,錢包代理部署時,會透過DELEGATECALL 呼叫實現合約的初始化函數,以此達成錢包初始化與代理錢包部署的原子化操作,避免被搶先初始化的問題。但用戶在使用EIP-7702 進行委託時,只會更新其位址的 code 字段,無法透過呼叫委託位址進行初始化。這使得EIP-7702 無法像常見的ERC-1967 代理合約一樣能在合約部署的交易中呼叫初始化函數進行錢包初始化。

對於開發者來說,在將EIP-7702 與現有的EIP-4337 錢包進行群組合適配時,應該注意在錢包的初始化操作中進行權限檢查(例如透過ecrecover 恢復簽名位址進行權限檢查),以避免錢包初始化操作被搶跑的風險。

儲存管理

用戶在使用EIP-7702 委託功能時,可能會因為功能需求變更、錢包升級等原因,需要重新委託到不同的合約位址。但不同合約的儲存結構可能存在差異(例如不同合約的slot0 插槽可能代表不同類型的數據),在重新委託的情況下,有可能導致新合約意外復用舊合約的數據,進而引發帳戶鎖定、資金損失等不良後果。

對於用戶來說,應該謹慎處理重新委託的狀況。

對於開發者來說,在開發過程中應遵循ERC-7201 提出的 Namespace Formula,將變數分配到指定的獨立儲存位置,以緩解儲存衝突的風險。此外 ERC-7779 (draft) 還專為EIP-7702 提供了重新委託的標準流程:包括使用ERC-7201 防止儲存衝突,並在重新委託之前驗證儲存相容性,以及呼叫舊委託的介面清理儲存的舊資料。

假儲值

用戶在進行委託後,EOA 也將可以作為智能合約使用,因此中心化交易所(CEX) 可能會面臨智能合約充值普遍化的情況。

CEX 應透過trace 檢查每筆儲值交易的狀態,防範智能合約假儲值風險。

帳戶轉換

在實施了EIP-7702 委託後,使用者的帳戶類型可以在EOA 與SC 之間自由轉換,這使得帳戶既可以發起交易,也可以被呼叫。意味著當帳戶呼叫本身並進行外部呼叫時,其 msg.sender 也將是 tx.origin,這會打破一些僅限EOA 參與專案的安全假設。

對於合約開發者來說,假設 tx.origin 始終是EOA 將不再可行。同樣的,透過 msg.sender == tx.origin 檢查來防禦重入攻擊也將失效。

開發者在開發過程中理應假設未來的參與者可能都為智能合約。

合約相容性

現有的ERC-721,ERC-777 代幣在對合約進行轉帳時都具有Hook 功能,這意味著接收者必須實現相應的回呼函數以成功接收代幣。

對於開發者來說,用戶委託的目標合約理應實現相應的回調函數,以確保能夠和主流代幣相容。

釣魚檢查

在實施了EIP-7702 委託後,用戶帳戶中的資產可能都將由智能合約控制,一旦用戶將帳戶委託到了惡意的合約,那麼攻擊者竊取資金將變得輕而易舉。

對於錢包服務商來說,盡快支援EIP-7702 類型的交易尤其重要,並且在用戶進行委託簽名時,應向用戶著重展示委託的目標合約,以緩解用戶可能遭受釣魚攻擊的風險。

此外,對帳戶委託的目標合約進行更深入的自動分析(開源檢查,權限檢查等)可以更好地幫助使用者避免此類風險。

總結

本文圍繞著以太坊即將到來的Pectra 升級中的EIP-7702 提案展開探討。 EIP-7702 透過引進新的交易類型,使EOA 具備可程式性與可組合性,模糊了EOA 與合約帳戶的界線。由於目前還沒有一個經過實戰考驗的兼容EIP-7702 類型的智能合約標準,因而在實際應用中,不同的生態參與者,如用戶、錢包服務商、開發者、CEX 等,都面臨著許多挑戰與機遇。本文所闡述的最佳實務內容無法涵蓋所有的潛在風險,但仍值得各方在實際操作中藉鏡應用。

範例

[Set EOA Account Code]

https://holesky.etherscan.io/tx/0x29252bf527155a29fc0df3a2eb7f5259564f5ee7a15792ba4e2ca59318080182

[Unset EOA Account Code]

https://holesky.etherscan.io/tx/0xd410d2d2a2ad19dc82a19435faa9c19279fa5b96985988daad5d40d1a8ee2269

相關連結

[1] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/types/tx_setcode.go#L109-L113

[2] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L562

[3] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L304

[4] https://github.com/ethereum/go-ethereum/blob/7fed9584b5426be5db6d7b0198acdec6515d9c81/core/state_transition.go#L388-L390

參考資料‍‍‍‍

[EIP-7702]https://eips.ethereum.org/EIPS/eip-7702

[EIP-4844]https://eips.ethereum.org/EIPS/eip-4844

[Go-ethereum]https://github.com/ethereum/go-ethereum/tree/7fed9584b5426be5db6d7b0198acdec6515d9c81

[EIP-3541]https://eips.ethereum.org/EIPS/eip-3541#backwards-compatibility

[Cobo: EIP-7702實用指南]

https://mp.weixin.qq.com/s/ojh9uLw-sJNArQe-U73lHQ

[Viem]https://viem.sh/experimental/eip7702/signAuthorization

[ERC-7210]https://eips.ethereum.org/EIPS/eip-7201

[ERC-7779]https://eips.ethereum.org/EIPS/eip-7779