EXEC_TX:為帳戶功能設計的可擴展執行交易

EXEC_TX:為帳戶功能設計的可擴展執行交易

Ethereum Magicians·大約 5 小時前

EXEC_TX 引入了一種基於鉤子(hook)模型的新型交易類型,將身份驗證和託管等邏輯委託給智能合約,從而減少對協議層頻繁變動的需求。它具備多階段執行流程與二維 Nonce 設計,以實現並行交易處理並為帳戶功能提供更大的靈活性。

EXEC_TX 引入了一種基於 Hook 執行(hook-based execution) 的新型交易類型。我們不再為帳戶抽象、隱私、代贊助以及明年的新功能添加新的 EIP,而是提供一個協議層的封裝(envelope),由 Hook 來實現各項功能的邏輯。協議負責處理枯燥的部分(Nonce、Gas、多階段路由);Hook 則負責處理有趣的部分(驗證、託管、證明、策略)。


問題所在

目前,每一項新的帳戶功能都需要:

  • 新的交易類型或獨立的基礎設施(EIP-4337 捆綁器 bundlers、中繼器 relayers)

  • 新的錢包簽名邏輯

  • 新的客戶端驗證規則

  • 新的內存池(mempool)邏輯

  • 新的 dApp 集成

這造成了摩擦、碎片化,並減緩了功能開發的速度。

解決方案:一種可擴展的交易類型,其功能邏輯存在於合約中,而非共識層。


Hook 模型:核心設計

什麼是 Hook?

Hook 是一個智能合約,協議會在 EXEC_TX 執行流程中的特定點調用它。協議不再由自身代碼處理驗證、託管、證明,而是將其委託給 Hook。

Hook 接口(概念性):

interface IExecHook {
  // 在 PRE_VALIDATION(驗證模式)期間調用
  // 必須是確定性的(無狀態寫入,無依賴區塊的讀取)
  // 返回值:0 = 失敗,1 = 成功
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256);

  // 在 PRE_EXECUTION 期間調用
  // 可以自由讀寫狀態
  // 返回值:0 = 失敗,1 = 成功
  function preExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256);

  // 在 POST_EXECUTION 期間調用(僅在核心調用成功時)
  // 可以自由讀寫狀態
  // 返回值:0 = 失敗,1 = 成功
  function postExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256);
}

Hook 生命周期

EXEC_TX 執行流程:

  1. 基礎驗證
    ├─ 解碼 EXEC_TX
    ├─ 驗證簽名 (EOA) 或調用 isAuthorizedExecHook (合約帳戶)
    └─ 加載 Hook 合約

  2. PRE_VALIDATION(強制執行驗證模式)
    ├─ 協議調用 hook.preValidation(txData, hookData)
    ├─ Hook 邏輯:驗證簽名、檢查證明、驗證策略
    ├─ 限制:無狀態寫入,無依賴區塊的讀取
    └─ Hook 返回 0(失敗,交易被拒絕)或 1(成功,繼續)

  3. PRE_EXECUTION(若 phaseMask 第 1 位已設置)
    ├─ 協議調用 hook.preExecution(txData, hookData)
    ├─ Hook 邏輯:鎖定資金、預留容量、發出中間狀態
    ├─ 限制:無(完整的 EVM 訪問權限)
    └─ Hook 返回 0(失敗,交易回滾)或 1(成功,繼續)

  4. 核心調用 (Core CALL)
    ├─ 協議執行 CALL(to, data, value)
    ├─ 無 Hook 參與
    └─ 若回滾,則整筆交易回滾(跳過 POST_EXECUTION)

  5. POST_EXECUTION(若 phaseMask 第 2 位已設置且核心調用成功)
    ├─ 協議調用 hook.postExecution(txData, hookData)
    ├─ Hook 邏輯:釋放託管資金、發出作廢標誌(nullifiers)、結算帳戶
    ├─ 限制:無(完整的 EVM 訪問權限)
    └─ 若 Hook 回滾,則整筆交易回滾(核心調用的結果也會撤銷)

  6. 結算
    └─ 向支付者收取實際消耗的 Gas(或回退至 from 地址)

為什麼只用單個 Hook?

每個 EXEC_TX 僅限一個 Hook,而非多個。原因如下:

  • 極簡共識規則:協議不定義 Hook 的順序、依賴關係或衝突解決。由應用程序決定。

  • 成熟模式:EIP-4337 的捆綁器邏輯證明了調度器(dispatcher)模式是成熟且經過測試的。

  • 不限制組合性:單個 Hook 可以調度內部模塊。下方的示例展示了如何通過一個 Hook 實現「3 分之 2 驗證 + 額度限制策略」。

Hook 授權

對於合約帳戶:協議在 PRE_VALIDATION 之前調用 isAuthorizedExecHook(hook.target)

// 多簽合約必須實現此接口
contract MultisigAccount {
  mapping(address => bool) authorizedHooks;  // 允許的 Hook 白名單

  function isAuthorizedExecHook(address hookTarget) 
    external view returns (bool) {
    return authorizedHooks[hookTarget];
  }
}

對於 EOA:簽名本身即為授權。不需要調用 isAuthorizedExecHook

Hook 數據編碼

Hook 邏輯傳遞於 hook.data(任意字節)。編碼方式由應用程序特定,而非協議強制規定。常見慣例:

// 簡單:單一用途 Hook
hook.data = abi.encode(signature1, signature2)  // 例如:3 分之 2 簽名

// 複雜:調度器路由
hook.data = abi.encode(
  phase0Data,  // 用於 PRE_VALIDATION
  phase1Data,  // 用於 PRE_EXECUTION
  phase2Data   // 用於 POST_EXECUTION
)

Hook 負責解碼 hook.data 並理解其當前運行的階段(通過 phaseMask)。


執行模型:多階段設計

什麼是 phaseMask?

一個控制哪些階段運行的位掩碼:

  • phaseMask = 1 (二進制 001): 僅 PRE_VALIDATION
  • phaseMask = 3 (二進制 011): PRE_VALIDATION + PRE_EXECUTION
  • phaseMask = 5 (二進制 101): PRE_VALIDATION + POST_EXECUTION
  • phaseMask = 7 (二進制 111): 所有三個階段
  • phaseMask = 0: 無 Hook,僅核心調用(類似傳統交易)

為什麼分為四個階段?

階段執行時機Hook 可執行的操作協議保證
PRE_VALIDATION核心調用前驗證簽名、檢查證明、驗證策略無狀態寫入;確定性;內存池安全
PRE_EXECUTION核心調用前鎖定託管、預留容量完整 EVM 訪問;與核心調用原子化
Core CALL用戶意圖(無 Hook)與傳統交易一致;可執行任何操作
POST_EXECUTION核心調用後(若成功)釋放託管、發出作廢標誌完整 EVM 訪問;與核心調用原子化;若回滾則全部回滾

為什麼採用這種結構?

  • PRE_VALIDATION:在觸碰狀態前進行確定性檢查。內存池節點可以在本地驗證而不會產生分歧。

  • PRE_EXECUTION:核心調用前的準備(鎖定資金、預留)。核心調用可以假設資源已就緒。

  • Core CALL:與傳統交易相同。用戶實際的交易內容。

  • POST_EXECUTION:核心調用成功後的清理。託管會計是原子化的:要麼核心調用成功且資金釋放,要麼兩者都不發生。

驗證模式:為什麼限制 PRE_VALIDATION?

內存池節點在將交易納入區塊前會在本地進行驗證。如果 PRE_VALIDATION 代碼讀取區塊狀態:

  • 節點 A 在區塊 1000:BALANCE 檢查通過(支付者有 1 ETH)
  • 節點 B 在區塊 1001:支付者資金被抽乾,BALANCE 檢查失敗
  • 結果:同一筆交易在 A 有效,在 B 無效 → 導致內存池分區。

解決方案:驗證模式禁止:

  • SLOAD, SSTORE(狀態讀/寫)

  • TIMESTAMP, BLOCKHASH, BASEFEE, BLOBBASEFEE, PREVRANDAO, COINBASE, GASLIMIT(依賴區塊的指令)

  • 例外:BALANCE, SELFBALANCE(支付者流動性檢查至關重要;TOCTOU 風險通過 PRE_EXECUTION 資金鎖定來緩解)

這確保了 PRE_VALIDATION 是確定性的:無論區塊高度如何,同一筆交易在所有節點上的結果始終一致。


二維 Nonce:並行化設計

線性 Nonce 的問題

傳統 Nonce 強制執行線性順序:nonce=100, 101, 102, ...。一次失敗會阻塞所有後續交易。

  • 發送 nonce=100 的交易:待處理
  • 發送 nonce=101 的交易:待處理(等待 100)
  • 發送 nonce=102 的交易:待處理(等待 100 和 101)
  • nonce=100 的交易失敗
  • 結果:101 和 102 被永久阻塞

解決方案:帶有獨立通道的二維 Nonce

struct Nonce {
  uint64 nonceKey;   // 通道標識符
  uint64 nonceSeq;   // 通道內的序列號
}

通道是相互獨立的

  • 通道 0: nonce_seq=100, 101, 102, ... (支付交易)
  • 通道 1: nonce_seq=100, 101, 102, ... (治理投票)
  • 通道 2: nonce_seq=100, 101, 102, ... (意圖響應)

所有三個通道並行執行。通道 0 的失敗不會阻塞通道 1 或 2。

為什麼在協議層實現?

Hook 能否自行管理 Nonce 序列?不能,因為:

  • 共識分歧:兩個擁有相同 Hook 代碼但狀態不同的節點可能會對衝突交易產生不同的接受/拒絕結果。

  • 內存池攻擊:攻擊者利用 Hook 狀態差異進行雙重支出或死鎖。

  • 協議不變量:重放保護必須是共識保證,而非應用邏輯。

協議強制執行:「在每個通道內,nonceSeq 必須等於下一個預期序列。」應用程序可以在此之上疊加策略(例如,「通道 0 僅用於支付」)。

EOA 與合約帳戶的 Nonce 處理

帳戶類型協議強制執行應用程序責任
EOA嚴格:所有通道 nonceSeq = expected[lane]無;協議保證
合約帳戶僅限通道內的順序Hook 必須防止重複 Nonce 以避免重放攻擊

風險:如果合約帳戶 Hook 不檢查 Nonce 的唯一性,帳戶將面臨重放風險。同一筆已簽名的 EXEC_TX(nonceSeq=100)可能會執行兩次。


實例演練:Hook 運作中

示例 1:簡單授權(僅 PRE_VALIDATION)

// 3 分之 2 多簽合約帳戶發送代幣

EXEC_TX {
  from: safe_multisig_account,
  to: token_contract,
  data: transfer(recipient, amount),
  hook: {
    target: multisig_validator,  // 部署一次,由所有 3 分之 2 帳戶複用
    phaseMask: 1,  // 僅 PRE_VALIDATION
    gasLimit: 100_000,
    data: abi.encode([sig1, sig2])  // 3 分之 2 簽名
  }
}

Hook 執行

contract MultisigValidator {
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    // 解碼簽名
    (bytes memory sig1, bytes memory sig2) = abi.decode(hookData, (bytes, bytes));
    
    // 從 EXEC_TX 簽名哈希中恢復簽名者
    address signer1 = recoverFromSignature(txData, sig1);
    address signer2 = recoverFromSignature(txData, sig2);
    
    // 驗證兩位簽名者是否都在此帳戶的 3 分之 2 集合中
    Safe safe = Safe(msg.sender);  // msg.sender 是 EXEC_TX 的 from 地址
    require(safe.isOwner(signer1) && safe.isOwner(signer2), "Invalid signers");
    
    return 1;  // 成功
  }
}

為什麼這很重要:這是合約帳戶首次擁有原生的基礎層簽名驗證。不需要捆綁器。同一個驗證器合約可供 100,000 個 Safe 錢包複用。

示例 2:帶有 ZK 的隱私(僅 PRE_VALIDATION)

// 帶有 ZK 證明的隱私代幣轉帳

EXEC_TX {
  from: user,
  to: privacy_token_contract,
  data: [commitment_hash, encrypted_transfer_params],
  hook: {
    target: zk_verifier,
    phaseMask: 1,  // 僅 PRE_VALIDATION
    gasLimit: 200_000,
    data: zk_proof  // 公開證明
  }
}

Hook 執行

contract ZKVerifier {
  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    // 解碼 calldata 以提取 commitment_hash
    (bytes32 commitment, ) = abi.decode(txData, (bytes32, bytes));
    
    // 從 hook.data 解碼證明
    bytes memory proof = hookData;
    
    // 驗證證明(確定性,無狀態讀取)
    require(verifyProof(proof, commitment), "Invalid proof");
    
    return 1;  // 成功
  }
}

執行後續

  • 核心:隱私代幣合約接收加密參數,更新屏蔽池狀態。

  • 用戶在鏈下保留私鑰;未來交易提款時可進行解密。

為什麼這有效:證明驗證(公開、確定性)與狀態變更(加密)解耦。隱私邏輯可以在合約中演進,無需更改共識。

示例 3:帶有調度器的多模塊組合(所有階段)

// 3 分之 2 多簽 + 額度限制 + 贊助 Gas

EXEC_TX {
  from: safe_multisig_account,
  to: uniswap,
  data: swap_calldata,
  payer: relayer_sponsor,
  hook: {
    target: dispatcher,  // 路由至 AuthModule, PolicyModule, EscrowModule
    phaseMask: 7,  // 所有三個階段
    gasLimit: 500_000,
    data: abi.encode(
      [sig1, sig2],                    // 用於 AuthModule (階段 0)
      {amount: 100e18, recipient: "0x..."},  // 用於 PolicyModule (階段 1)
      relayer_address                  // 用於 EscrowModule (階段 2)
    )
  }
}

Hook 執行

contract Dispatcher {
  AuthModule authModule;
  PolicyModule policyModule;
  EscrowModule escrowModule;

  function preValidation(bytes calldata txData, bytes calldata hookData) 
    external view returns (uint256) {
    (bytes[] memory sigs, , ) = abi.decode(hookData, (bytes[], bytes32, address));
    return authModule.verify(txData, sigs) ? 1 : 0;
  }

  function preExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256) {
    (, bytes32 constraints, address relayer) = abi.decode(hookData, (bytes[], bytes32, address));
    
    // 驗證策略
    require(policyModule.validateConstraints(constraints, txData), "Policy violation");
    
    // 為 Gas 贊助鎖定託管
    require(escrowModule.lockEscrow(relayer, tx.gasprice * 500_000), "Escrow lock failed");
    
    return 1;
  }

  function postExecution(bytes calldata txData, bytes calldata hookData) 
    external returns (uint256) {
    (, , address relayer) = abi.decode(hookData, (bytes[], bytes32, address));
    
    // 釋放託管資金(計算實際消耗的 Gas,退回差額)
    uint256 actualGasCost = (initialGas - gasleft()) * tx.gasprice;
    escrowModule.releaseEscrow(relayer, actualGasCost);
    
    return 1;
  }
}

執行時間線

  • PRE_VALIDATION:Dispatcher.preValidation → AuthModule.verify → 3 分之 2 簽名檢查(約 8K Gas)

  • PRE_EXECUTION:Dispatcher.preExecution → PolicyModule.validate (額度限制) → EscrowModule.lockEscrow(約 10K Gas)

  • 核心:Uniswap 兌換執行(約 100K Gas,取決於市場)

  • POST_EXECUTION(僅因核心成功):釋放託管,退款給中繼器(約 5K Gas)

  • 結算:向中繼器收取實際 Gas;若中繼器資金耗盡則回退至 Safe 帳戶。

為什麼採用此架構:單個 Hook(調度器)路由至 3 個內部模塊。協議不在乎存在多少模塊;調度器負責處理組合。路由開銷約為 1K Gas(可忽略不計)。


Hook 設計如何解決問題

從「為每個功能升級」到「部署一個 Hook」

舊模式(EIP-4337,捆綁中繼器)

  • 需要新功能 → 新 EIP 提案 → 客戶端共識 → 主網升級 → 錢包集成 → Dapp 集成

  • 每個功能耗時數月至數年

  • 每個功能都會使基礎設施碎片化

新模式(EXEC_TX + Hook)

  • 需要新功能 → 編寫 Hook 合約 → 部署 → Dapp 集成

  • 每個功能耗時數週

  • 單一統一的基礎設施 (EXEC_TX)

未來 Hook 示例(無需更改協議)

  • 風險管理:風險 Hook 根據用戶的風險概況驗證交易。

  • 意圖求解:意圖 Hook 驗證鏈上狀態是否符合用戶意圖。

  • 隱私:隱私 Hook 根據承諾驗證證明。

  • 抗量子驗證:Hook 實現後量子簽名驗證。

  • 社交恢復:Hook 實現社交恢復機制。

  • 授權委託:Hook 驗證授權委託證明。

  • 批量操作:Hook 驗證批量結構和依賴關係。

  • 抗 MEV:Hook 實現加密 calldata + 揭示機制。

由於 Hook 接口是通用的,所有這些都可以在不對協議進行任何更改的情況下實現。


Hook 模型的關鍵屬性

使其運作的屬性

  • 確定性 PRE_VALIDATION:不同節點對有效性始終達成一致。內存池不會產生分區。

  • 原子化階段:PRE_EXECUTION 和 POST_EXECUTION 與核心調用原子化。託管不變量得以維持。

  • 授權被委託:協議不定義何為「有效」。由合約定義 (isAuthorizedExecHook)。

  • 應用層可擴展:新的 Hook 不需要共識。可以快速部署和迭代。

  • 通過調度器組合:單個 Hook 邊界不防礙多功能交易。調度器模式已獲證實。

權衡

權衡點我們的選擇原因
協議 vs 應用協議:Nonce, 階段, Gas協議強制執行不變量(重放保護、原子性、確定性)。應用程序執行策略。
單個 vs 多個 Hook單個 Hook極簡共識規則;調度器模式已獲證實。未來可擴展。
Nonce 唯一性EOA:強制,合約:委託EOA 默認安全。合約帳戶獲得靈活性;需對自身安全負責。
驗證模式嚴格性嚴格(禁止 9 個指令)內存池一致性。為支付者檢查保留 BALANCE 例外。
支付者回退耗盡時回退核心原子性。風險:靜默回退。緩解措施:錢包 UI。

與替代方案的比較

我們並非試圖取代 EIP-4337、EIP-8130 或 EIP-7702。不同的工具用於不同的目的。

  • EIP-4337(捆綁器,UserOps):適用於鏈下基礎設施。將繼續存在。EXEC_TX 是基礎層原生的替代方案。

  • EIP-8130(帳戶配置):規範性的帳戶管理(多所有者、權限範圍)。EXEC_TX 是一個極簡封裝,由合約實現策略。

  • EIP-7702(EOA 委託):讓 EOA 為每筆交易附加代碼。EXEC_TX 適用於希望獲得原生支持的合約帳戶。

所有方案可以共存。不同的用戶會選擇不同的工具。


社區開放問題

  • Hook 接口 ABI:協議是否應該定義標準的 Hook 函數簽名,還是應用程序特定的編碼即可接受?

  • 驗證模式一致性:我們如何確保 geth、erigon、reth 都以完全相同的方式執行驗證模式?存在客戶端分歧的風險。

  • 階段上下文傳遞:Hook 應該如何知道當前正在執行哪個階段?通過 phaseMask 參數?還是從上下文中隱式獲取?

  • 支付者回退 UX:帶有錢包警告的靜默回退是否可以接受,還是我們應該嚴格執行失敗?

  • 合約帳戶 Nonce 安全:委託給 Hook 是否足夠,還是我們應該添加協議層的合理性檢查?

  • Hook 回滾數據:協議是否應該捕獲回滾原因以用於錢包錯誤消息?

  • Gas 計量:當階段是可選時,MAX_HOOK_GAS = gasLimit - MIN_CORE_GAS 應該如何運作?MIN_CORE_GAS 是否應隨階段而變化?

  • 二維 Nonce 錢包 UX:如何向非技術用戶展示「通道」概念而不讓他們感到困惑?


更新日誌

外部審查

截至 2026-04-08 尚無。

待解決問題

  • Hook 合約 ABI 與接口標準化
  • 各 EVM 客戶端間驗證模式實現的一致性
  • Hook 的階段上下文傳遞機制
  • 支付者回退語義及 UX 緩解
  • 合約帳戶 Nonce 安全保證
  • Hook 回滾數據捕獲與錯誤消息傳遞
  • 可選階段的 Gas 計量
  • 二維 Nonce 錢包 UX 設計
  • 調度器模式數據編碼標準化

作者:Louis Liu (@louisliu2048) 及貢獻者

相關項目:EIP-155, EIP-1559, EIP-1153, EIP-2718, EIP-2929, EIP-4844, EIP-6780

        1 則貼文 - 1 位參與者

        [閱讀完整主題](https://ethereum-magicians.org/t/exec-tx-extensible-execution-transaction-for-account-capabilities/28168)
https://ethereum-magicians.org/t/exec-tx-extensible-execution-transaction-for-account-capabilities/28168