
用於帳戶功能的擴展執行交易架構
EXEC_TX 引入了一種基於鉤子執行模型的新型交易類型,旨在簡化帳戶抽象與新功能的實現,而無需不斷更改協議層。透過將驗證與執行邏輯委派給合約鉤子,該架構解決了現有帳戶功能開發中的碎片化問題,並透過二維 Nonce 設計實現了交易的並行處理。
EXEC_TX 引入了一種基於 Hook 執行(hook-based execution) 的新型交易類型。我們不為帳戶抽象、隱私、贊助和明年的新功能添加新的 EIP,而是提供一個協議層的外殼(envelope),由 Hook 來實現各項功能的邏輯。協議負責處理枯燥的部分(Nonce、Gas、多階段路由);Hook 則負責處理有趣的部分(驗證、託管、證明、策略)。
問題所在
目前,每一項新的帳戶功能都需要:
-
新的交易類型或獨立的基礎設施(EIP-4337 捆綁器、中繼器)
-
新的錢包簽名邏輯
-
新的客戶端驗證規則
-
新的內存池(mempool)邏輯
-
新的 dApp 集成
這造成了摩擦、碎片化,並減緩了功能開發的速度。
解決方案:一種可擴展的交易類型,其功能邏輯存在於合約中,而非共識層。
Hook 模型:核心設計
什麼是 Hook?
Hook 是一個智能合約,協議會在 EXEC_TX 執行流程中的特定點調用它。協議不再由協議代碼處理驗證、託管、證明,而是將其委託給 Hook。
Hook 接口:
interface IExecHook {
// PRE_VALIDATION:確定性驗證(強制執行驗證模式)
// 返回值:0=失敗,1=成功。Revert → 交易被拒絕。
function preValidation(bytes calldata txData, bytes calldata hookData)
external view returns (uint256);
// PRE_EXECUTION:設置/託管(完整 EVM 環境)
// 返回值:0=失敗(交易回滾),1=成功。Revert → 交易回滾。
function preExecution(bytes calldata txData, bytes calldata hookData)
external returns (uint256);
// POST_EXECUTION:清理/釋放(完整 EVM 環境,僅在核心調用成功時執行)
// 返回值:0=失敗(整筆交易回滾),1=成功。Revert → 核心執行結果回滾。
function postExecution(bytes calldata txData, bytes calldata hookData)
external returns (uint256);
}
Hook 語義:
-
必須僅返回 0(失敗)或 1(成功)
-
Revert(異常)= 該階段執行失敗(參見生命週期)
-
txData = ABI 編碼的 EXEC_TX;hookData = 應用程序特定數據
-
Hook 通過 phaseMask 或內部邏輯檢測當前階段
Hook 生命周期
EXEC_TX 執行流程:
-
基礎驗證
├─ 解碼 EXEC_TX
├─ 驗證簽名 (EOA) 或調用 isAuthorizedExecHook (合約帳戶)
└─ 加載 Hook 合約 -
PRE_VALIDATION(強制執行驗證模式)
├─ 協議調用 hook.preValidation(txData, hookData)
├─ Hook 邏輯:驗證簽名、檢查證明、驗證策略
├─ 限制:禁止寫入狀態,禁止讀取區塊相關數據
└─ Hook 返回 0(失敗,交易被拒絕)或 1(成功,繼續) -
PRE_EXECUTION(如果 phaseMask 第 1 位已設置)
├─ 協議調用 hook.preExecution(txData, hookData)
├─ Hook 邏輯:鎖定資金、預留容量、發出中間狀態事件
├─ 限制:無(可完整訪問 EVM)
└─ Hook 返回 0(失敗,交易回滾)或 1(成功,繼續) -
核心調用 (Core CALL)
├─ 協議執行 CALL(to, data, value)
├─ 不涉及 Hook
└─ 如果回滾,整筆交易回滾(跳過 POST_EXECUTION) -
POST_EXECUTION(如果 phaseMask 第 2 位已設置且核心調用成功)
├─ 協議調用 hook.postExecution(txData, hookData)
├─ Hook 邏輯:釋放託管資金、發出作廢號(nullifiers)、結算帳戶
├─ 限制:無(可完整訪問 EVM)
└─ 如果 Hook 回滾,整筆交易回滾(核心調用結果被撤銷) -
結算
└─ 向支付者收取實際消耗的 Gas(或回退至 from 地址)
為什麼採用單一 Hook?
每個 EXEC_TX 僅限一個 Hook,而非多個。原因如下:
-
最小化共識規則:協議不定義 Hook 的順序、依賴關係或衝突解決。由應用程序決定。
-
成熟模式:EIP-4337 捆綁器邏輯證明了調度器(dispatcher)模式是成熟且經過測試的。
-
不限制組合性:單個 Hook 可以調度至內部模塊。下文示例展示了如何通過一個 Hook 同時實現 2-of-3 驗證和限額策略。
Hook 授權
合約帳戶必須實現:
function isAuthorizedExecHook(address hookTarget) external view returns (bool);
協議在 PRE_VALIDATION 之前調用此函數:
-
如果返回 false → 交易被拒絕
-
如果 Revert → 交易被拒絕
-
Gas 限制:5,000 gas(固定)
EOA:簽名即為授權;不調用 isAuthorizedExecHook。
Revert 處理機制
| 發生時機 | 行為 | 結果 |
|---|---|---|
| isAuthorizedExecHook Revert | 立即失敗 | 交易被拒絕 |
| PRE_VALIDATION Revert | 階段失敗 | 交易被拒絕;收取驗證 Gas |
| PRE_EXECUTION Revert | 階段失敗 | 交易回滾;收取所有 Gas |
| 核心調用 Revert | 執行失敗 | 交易回滾;跳過 POST_EXECUTION |
| POST_EXECUTION Revert | 階段失敗 | 核心結果回滾;整筆交易回滾 |
Hook 數據編碼
Hook 邏輯在 hook.data 中傳遞(任意字節)。編碼方式由應用程序特定,而非協議強制。
// 簡單:單一用途
hook.data = abi.encode(signature1, signature2)
// 複雜:調度器路由
hook.data = abi.encode(phase0Data, phase1Data, phase2Data)
Hook 通過 phaseMask 檢測階段或解碼特定階段的數據。
簽名與 Gas 語義
簽名(針對 EOA):對 EXEC_TX 哈希進行標準 EIP-191 簽名(字段順序:chainId, nonceKey, nonceSeq, from, to, value, data, gasLimit, maxFeePerGas, payer, payerData, hook, v, r, s)。
合約帳戶:無簽名。Hook 通過 isAuthorizedExecHook 驗證授權。
Gas 預算:
MAX_HOOK_GAS = gasLimit - MIN_CORE_GAS
MIN_CORE_GAS = 21_000
Intrinsic = 21_000 + calldata_bytes × 16 (非零) / 4 (零)
結算:charge = (intrinsic + 實際 hook_gas + 實際 core_gas) × maxFeePerGas
執行模型:多階段設計
phaseMask:階段選擇
一個 8 位(8-bit)值,位控制執行哪些階段:
phaseMask & 0x1 → 運行 PRE_VALIDATION (bit 0)
phaseMask & 0x2 → 運行 PRE_EXECUTION (bit 1)
phaseMask & 0x4 → 運行 POST_EXECUTION (bit 2)
示例:
-
phaseMask = 1:僅 PRE_VALIDATION
-
phaseMask = 3:PRE_VALIDATION + PRE_EXECUTION
-
phaseMask = 7:所有三個階段
-
phaseMask = 0:無 Hook;僅核心調用(行為類似傳統交易)
語義:如果 Hook 不為空,則必須運行 PRE_VALIDATION(必須設置 bit 0)。
為什麼需要四個階段?
| 階段 | 時機 | Hook 可執行的操作 | 協議保證 |
|---|---|---|---|
| PRE_VALIDATION | 核心前 | 驗證簽名、檢查證明、驗證策略 | 禁止寫入狀態;確定性;內存池安全 |
| PRE_EXECUTION | 核心前 | 鎖定託管、預留容量 | 完整 EVM 訪問;與核心調用原子化 |
| 核心調用 | 用戶意圖 | (無 Hook) | 與傳統交易一致;可執行任何操作 |
| POST_EXECUTION | 核心後 (成功時) | 釋放託管、發出作廢號 | 完整 EVM 訪問;與核心原子化;若 Revert 則全部回滾 |
為什麼採用這種結構?
-
PRE_VALIDATION:在接觸狀態前進行確定性檢查。內存池節點可以在本地驗證而不會產生分歧。
-
PRE_EXECUTION:核心調用前的設置(鎖定資金、預留)。核心調用可以假設資源已就緒。
-
核心調用:與傳統交易相同。用戶實際的交易內容。
-
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 被永久阻塞
解決方案:帶有獨立通道的 2D 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 == next[from][nonceKey] | 無 |
| 合約帳戶 | 順序:nonceSeq <= next[from][nonceKey] | Hook 必須防止重複 |
Nonce 狀態更新時機:在執行成功(POST_EXECUTION 完成)後,協議增加 next[from][nonceKey] = max(next[from][nonceKey], nonceSeq) + 1。
風險:如果合約 Hook 不檢查 Nonce 的唯一性,同一筆 EXEC_TX 可能會執行兩次(重放攻擊)。
交易格式 (EIP-2718)
EXEC_TX 是交易類型 0x08(待定最終化)。
RLP 結構:
0x08 || RLP([
chainId, nonceKey, nonceSeq, from, to, value, data,
gasLimit, maxFeePerGas, payer, payerData,
hook.target, hook.phaseMask, hook.gasLimit, hook.data,
v, r, s // 僅限 EOA;合約帳戶為 0
])
字段說明:
-
nonceKey, nonceSeq:2D Nonce(各為 uint256)
-
payer:若無贊助則為 address(0)
-
payerData:字節數據(若 payer 為 address(0) 則為空)
-
hook.target:若無 Hook 則為 address(0)
-
所有其他字段:與傳統交易相同
實例分析:Hook 的實際應用
示例 1:簡單授權(僅 PRE_VALIDATION)
// 2-of-3 多簽合約帳戶發送代幣
EXEC_TX {
from: safe_multisig_account,
to: token_contract,
data: transfer(recipient, amount),
hook: {
target: multisig_validator, // 部署一次,供所有 2-of-3 帳戶複用
phaseMask: 1, // 僅 PRE_VALIDATION
gasLimit: 100_000,
data: abi.encode([sig1, sig2]) // 2-of-3 簽名
}
}
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);
// 驗證兩位簽名者是否都在此帳戶的 2-of-3 集合中
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; // 成功
}
}
後續執行:
-
核心:隱私代幣合約接收加密參數,更新屏蔽池(shielded pool)狀態
-
用戶在鏈下保留私鑰;可在未來交易提現時解密
運作原理:證明驗證(公開、確定性)與狀態變更(加密)解耦。隱私邏輯在合約中演進,無需更改共識。
示例 3:帶有調度器的多模塊組合(所有階段)
// 2-of-3 多簽 + 金額限制 + 贊助 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 → 2-of-3 簽名檢查(約 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(可忽略不計)。
示例 4:意圖結算 Hook(後量子安全)
// 用戶提交意圖:“以 10 ETH 換取至少 15,000 USDC”
// 意圖解算器返回匹配的交易及證明
EXEC_TX {
from: user,
to: uniswap_router,
data: swap_with_minimum_check,
hook: {
target: intent_settlement,
phaseMask: 1, // 僅 PRE_VALIDATION
gasLimit: 150_000,
data: abi.encode(intent_hash, solver_proof, is_post_quantum_sig)
}
}
Hook 執行:
contract IntentSettlement {
function preValidation(bytes calldata txData, bytes calldata hookData)
external view returns (uint256) {
(bytes32 intent, bytes memory proof, bool usePostQuantum) =
abi.decode(hookData, (bytes32, bytes, bool));
Intent memory original = intentRegistry[intent];
require(block.timestamp <= original.expiry, "Intent expired");
require(verifySolverProof(proof, original.minOut), "Solver mismatch");
if (usePostQuantum) {
require(verifyDilithiumProof(proof, intent), "PQ sig failed");
}
return 1;
}
}
意義:意圖鏈現在可以進行透明結算,無需鏈下排序基礎設施。解算器競爭在鏈上進行。後量子帳戶只需更改 Hook 即可升級,無需主網分叉。
示例 5:委託授權(無需重新驗證)
// 被委託的中繼器可以代表委託人提交交易
// 委託證明嵌入在 hook.data 中
EXEC_TX {
from: delegator,
to: dapp,
data: user_intent,
payer: relayer,
hook: {
target: delegation_verifier,
phaseMask: 1,
gasLimit: 80_000,
data: abi.encode(delegation_proof, relayer_address, expiry_time)
}
}
Hook 驗證:
contract DelegationVerifier {
function preValidation(bytes calldata txData, bytes calldata hookData)
external view returns (uint256) {
(bytes memory delegProof, address relayer, uint256 expiry) =
abi.decode(hookData, (bytes, address, uint256));
require(block.timestamp <= expiry, "Delegation expired");
(, address to, , bytes calldata data, ) = decodeTx(txData);
require(verifyDelegation(delegProof, relayer, to, expiry), "Invalid delegation");
return 1;
}
}
高級模式:組合與優化
模式 1:用於重試邏輯的並行通道
使用 2D Nonce 通道解耦重試:
EXEC_TX (通道 0) {
phaseMask: 7,
data: primary_operation
}
EXEC_TX (通道 1) {
phaseMask: 1,
data: fallback_operation // 若通道 0 失敗則執行
}
兩者同時待處理且互不阻塞。主操作成功 → 備用操作取消。
模式 2:通過調度器實現批量原子性
通過基於 Hook 的調度器實現多操作原子性:
contract BatchDispatcher {
function preExecution(bytes calldata txData, bytes calldata hookData)
external returns (uint256) {
(Operation[] memory ops) = abi.decode(hookData, (Operation[]));
require(lockEscrow(sumCosts(ops)), "Escrow failed");
return 1;
}
function postExecution(bytes calldata txData, bytes calldata hookData)
external returns (uint256) {
(Operation[] memory ops) = abi.decode(hookData, (Operation[]));
for (uint i = 0; i < ops.length; i++) {
releaseEscrow(ops[i].actualCost);
}
return 1;
}
}
單個核心調用在內部進行調度。要麼全部成功,要麼全部回滾。
模式 3:用於並行工作流的 2D Nonce 通道
為不同交易類型設置不同通道:
通道 0:支付(順序執行)
通道 1:意圖響應(並行執行)
通道 2:治理投票(並行執行)
用戶同時提交:
交易 1:lane=0, seq=100 (支付)
交易 2:lane=2, seq=50 (投票)
交易 3:lane=1, seq=25 (意圖)
所有交易共同待處理。通道 0 的失敗不會阻塞通道 1 或 2。
詳細 Gas 計費示例
考慮示例 3(多模塊)的實際 Gas 成本:
場景:2-of-3 多簽 + 策略限制 + 託管贊助
區塊上下文:
base_fee = 50 gwei
payer_balance = 100 ETH
relayer_credit_limit = 5 ETH
提交的 EXEC_TX:
gasLimit = 500_000
maxFeePerGas = 100 gwei
Gas 拆解(執行順序):
-
固有開銷 (Intrinsic):21_000 + calldata_cost
- Calldata 約 400 字節:~4,800
- 小計:25_800
-
isAuthorizedExecHook (固定 5,000)
- 小計:30_800
-
通過調度器執行 PRE_VALIDATION
- AuthModule.verify (簽名恢復):6,000
- 小計:36_800
-
通過調度器執行 PRE_EXECUTION
- PolicyModule.validate (金額檢查):3,500
- EscrowModule.lockEscrow (SSTORE):20_000
- 小計:60_300
-
核心調用 (Uniswap swap)
- Uniswap 邏輯:95_000
- 小計:155_300
-
通過調度器執行 POST_EXECUTION
- EscrowModule.releaseEscrow (退款計算 + 轉帳):12_000
- 小計:167_300
-
最終結算
- gas_used = 167_300
- cost = 167_300 × 100 gwei = 16.73 ETH
支付者扣費:
- 支付者為中繼器,現有 100 ETH
- 扣除:16.73 ETH
- 剩餘:83.27 ETH
- 執行後退款:escrowModule 計算實際成本為 16.73 ETH,無需退款
若支付者僅有 10 ETH:
- PRE_EXECUTION 將失敗 (lockEscrow 失敗)
- 整筆交易回滾
- POST_EXECUTION 不執行
- 交易被拒絕,報錯 "Escrow lock failed"
- 回退至 from 地址扣費... (但 Safe 多簽帳戶無 ETH)
- 回退失敗,交易在執行前失敗
驗證模式:常見陷阱與強制執行
驗證模式中禁用的操作碼
嚴格禁用(9 個操作碼):
SLOAD 0x54 (狀態讀取)
SSTORE 0x55 (狀態寫入)
TIMESTAMP 0x42 (block.timestamp)
BLOCKHASH 0x40 (歷史區塊)
GASLIMIT 0x45 (區塊 Gas 限制)
BLOBBASEFEE 0x4a (EIP-4844)
PREVRANDAO 0x44 (隨機性)
COINBASE 0x41 (礦工地址)
BASEFEE 0x48 (基礎費用)
儘管有表面風險但仍允許的操作碼:
BALANCE 0x31 (支付者餘額檢查)
SELFBALANCE 0x19 (支付者餘額檢查)
為什麼允許 BALANCE:
- 對支付者流動性檢查至關重要
- TOCTOU 競爭通過 PRE_EXECUTION 的資金鎖定來緩解
- 如果餘額在 PRE_VALIDATION 和 PRE_EXECUTION 之間發生變化,lockEscrow 會捕捉到
客戶端強制執行
每個 EVM 客戶端必須以相同方式實現驗證模式。實現方式:
-
使用追蹤器(tracer)檢測禁用的操作碼(SLOAD, SSTORE, TIMESTAMP 等)
-
檢查 PRE_VALIDATION 返回值(必須為 0 或 1)
-
若檢測到禁用操作碼則拒絕交易
客戶端測試:使用故意設計的惡意 Hook 對 EXEC_TX 進行模糊測試。所有客戶端必須以相同方式拒絕。這對內存池的一致性至關重要。
安全考量
合約帳戶的 Nonce 唯一性
協議強制執行順序;合約帳戶必須防止重放:
易受攻擊的示例:
contract VulnerableHook {
function preValidation(bytes calldata txData, bytes calldata hookData)
external view returns (uint256) {
return isValidSignature(txData, hookData) ? 1 : 0; // 無 Nonce 檢查!
}
}
同一筆交易可以執行兩次。正確模式:
contract SafeHook {
mapping(address => mapping(uint64 => bool)) executed;
function preValidation(bytes calldata txData, bytes calldata hookData)
external view returns (uint256) {
(address from, uint64 nonceSeq, ) = decodeTx(txData);
require(!executed[from][nonceSeq], "Nonce already used");
require(isValidSignature(txData, hookData), "Bad signature");
return 1;
}
function preExecution(bytes calldata txData, bytes calldata hookData)
external returns (uint256) {
(address from, uint64 nonceSeq, ) = decodeTx(txData);
executed[from][nonceSeq] = true; // 在核心調用前標記,與其原子化
return 1;
}
}
在 PRE_EXECUTION 而非 POST 中標記:確保如果核心調用失敗,整筆交易回滾,Nonce 標誌也會回退。
支付者回退風險
如果支付者在 PRE_VALIDATION 和執行之間資金被抽乾,lockEscrow 將失敗且整筆交易回滾。如果回退地址(from)也沒有餘額,交易將失敗。
緩解措施:
-
錢包 UI:突出顯示支付者風險
-
Hook 預檢查:在提交前驗證支付者餘額
-
託管合約:使用長期託管而非臨時的 EOA 支付者
集成指南:從 EIP-4337 遷移
對於目前使用 EIP-4337 UserOp 的團隊:
| UserOp (4337) | EXEC_TX |
|---|---|
| sender | from |
| nonce | nonceKey + nonceSeq |
| initCode | 無對應項 |
| callData | to + data |
| callGasLimit | hook.gasLimit |
| verificationGasLimit | N/A (協議處理) |
| preVerificationGas | intrinsic |
| maxFeePerGas | maxFeePerGas |
| maxPriorityFeePerGas | N/A |
| paymaster | payer |
| paymasterData | payerData |
| signature | hook.data (取決於驗證方案) |
| factory | N/A (無初始化) |
遷移的好處:
✅ 原生基礎層支持(無需捆綁器)
✅ 無需獨立的 UserOp 內存池(使用交易內存池)
✅ 2D Nonce 並行化(4337 是線性的)
✅ 原子化贊助(無需單獨的 Paymaster 調用)
✅ 執行後 Hook(託管結算,4337 無法做到)
遷移時間線:
-
將您的驗證器合約 Hook 化(將 onValidate 適配為 preValidation)
-
更新錢包:使用 EXEC_TX 編碼而非 UserOp 編碼
-
更新捆綁器/中繼器:提交至普通交易內存池,而非 UserOp 內存池
-
廢棄 Paymaster 合約(託管現在是 Hook 的責任)
性能與內存池效率
Gas 開銷(與傳統交易相比):
-
協議增加:5,000 gas (isAuthorizedExecHook 調用,固定)
-
典型應用 Hook:50–200K gas (驗證、策略、託管)
-
總計:對於高價值交易,開銷約為 0.1–0.5%
內存池優化(通過驗證模式的確定性):
-
緩存 PRE_VALIDATION 結果(以 hook.target + hook.data 為鍵)
-
費用估算:無需執行 Hook 即可準確估算
-
並行驗證:獨立的 CPU 通道(無共享狀態)
-
優先級:按 maxFeePerGas 排序(標準內存池邏輯)
未來方向
主網上線後(無需共識變更):
-
Hook 註冊表:用於錢包 UX 的鏈下元數據映射
-
標準化模塊:AuthModule, EscrowModule, PolicyModule 共享庫
-
Hook Gas 分析:用於準確預測成本的工具
-
2D Nonce UX 標準:錢包如何向用戶展示通道
潛在的協議 v2(未來 EIP):
-
用於鏈下協調的 Hook 元數據字段
-
條件執行:“僅當 state[addr] == value 時執行”
-
帶有順序保證的多 Hook 支持
Hook 設計如何解決問題
從“為每個功能升級”到“部署一個 Hook”
舊模式 (EIP-4337, 捆綁器中繼器):
-
需要新功能 → 新 EIP 提案 → 客戶端共識 → 主網升級 → 錢包集成 → Dapp 集成
-
每個功能耗時數月至數年
-
每個功能都會導致基礎設施碎片化
新模式 (EXEC_TX + Hooks):
-
需要新功能 → 編寫 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 支持 100 多個內部模塊。該模式已在 EIP-4337 規模下得到驗證。
權衡
| 權衡點 | 選擇 | 理由 |
|---|---|---|
| 協議 vs 應用 | 協議:Nonce/階段/Gas | 強制執行不變量。應用程序執行策略。 |
| 單一 vs 多個 Hook | 單一 Hook | 最小化共識規則。調度器模式已驗證。 |
| Nonce 唯一性 | EOA:強制,合約:委託 | 默認安全。為應用程序提供靈活性。 |
| 驗證嚴格性 | 嚴格(禁用 9 個操作碼) | 內存池一致性。允許 BALANCE 用於支付者檢查。 |
| 支付者回退 | 資金耗盡時回退 | 原子性。緩解措施:錢包 UI 警告。 |
與替代方案的比較
我們並非要取代 EIP-4337、EIP-8130 或 EIP-7702。不同的工具,不同的用途。
-
EIP-4337(捆綁器、UserOp):鏈下基礎設施。將共存。EXEC_TX 是基礎層原生的。
-
EIP-8130(帳戶配置):規範性強(多所有者、範圍)。EXEC_TX 是最小外殼;由合約實現策略。
-
EIP-7702(EOA 委託):為每個交易的 EOA 添加代碼。EXEC_TX 針對合約帳戶,提供原生協議支持。
核心價值:EXEC_TX 讓未來的創新不再是協議層的問題。驗證、隱私、委託、MEV 保護、量子安全——這一切都變成了“另一個 Hook”。多年內無需更改共識。
社區開放問題
-
Hook 接口:標準 ABI 還是應用程序特定?
-
驗證模式:客戶端分歧風險(geth, erigon, reth 的一致性)?
-
階段上下文:Hook 應如何檢測當前階段?
-
支付者回退:靜默處理 + 警告,還是嚴格失敗?
-
Nonce 安全:Hook 委託是否足夠?
-
Revert 數據:是否捕獲用於錢包錯誤消息?
-
帶有可選階段的 MIN_CORE_GAS?
-
2D Nonce UX:如何向用戶展示通道?
安全性:需要形式化驗證:驗證模式強制執行、Nonce 轉換、階段原子性、Gas 計量。
實現清單
EVM 客戶端:
-
解碼類型 0x08 交易外殼 (EIP-2718)
-
實現驗證模式操作碼追蹤器
-
通過各階段路由執行 (PRE_VALIDATION → PRE_EXECUTION → Core → POST_EXECUTION)
-
管理 2D Nonce 狀態:next[from][nonceKey]
-
計量並結算 Gas(實際使用量 + 固有開銷)
錢包:
-
EXEC_TX 編碼和 EIP-191 簽名
-
每個通道的 2D Nonce 追蹤
-
Hook 規範和 Gas 估算
-
每個階段的錯誤處理
部署順序:
-
部署參考 Hook 實現(多簽、託管、策略)
-
測試網升級 + 跨客戶端的驗證模式模糊測試
-
主網軟分叉:支持類型 0x08(非共識)
-
主網硬分叉:激活 EXEC_TX 規則
-
6–12 個月內完成錢包集成(移動端、桌面端、硬件錢包)
更新日誌
- 2026-04-08:初始 RFC 草案,PR 待處理
外部審查
截至 2026-04-08 尚無。
待解決問題
-
Hook 合約 ABI 和接口標準化
-
跨 EVM 客戶端的驗證模式實現一致性
-
Hook 的階段上下文傳遞機制
-
支付者回退語義和 UX 緩解
-
合約帳戶 Nonce 安全保證
-
Hook Revert 數據捕獲和錯誤消息
-
可選階段的 Gas 計量
-
2D Nonce 錢包 UX 設計
-
調度器模式數據編碼標準化
為什麼是現在?
帳戶抽象已碎片化為捆綁器 (4337)、批量系統 (8130)、單筆交易委託 (7702)。EXEC_TX 將它們統一起來:一個協議外殼,可插拔的 Hook,由合約掌控安全性。
作者:Louis Liu (@louisliu2048) 及貢獻者
相關項目:EIP-155, EIP-1559, EIP-1153, EIP-2718, EIP-2929, EIP-4844, EIP-6780
1 帖子 - 1 參與者
[閱讀完整話題](https://ethereum-magicians.org/t/extensible-execution-transaction-for-account-capabilities/28168)