newsence

草案 ERC:支付門檻交易中繼協議

Ethereum Magicians·26 天前

本提案引入了支付門檻交易中繼(PGTR),這是一種允許代理或設備使用鏈上支付收據而非加密簽名來授權鏈上操作的協議。它旨在透過信任的中繼器實現代幣授權,為自主 AI 和物聯網設備解決金鑰抽象化問題。

摘要

支付門檻交易中繼(Payment-Gated Transaction Relay, PGTR)定義了一種代表主體(代理、合約或 EOA)轉發鏈上交易的協議,其授權證明是鏈上支付收據,而非加密簽名。PGTR 轉發器(Forwarder) 接收來自付款人的 X402 式支付,根據預期金額和目標驗證支付,並調用目標合約,同時將付款人地址作為已驗證的發送者提供。目標合約透過轉發器的 pgtrSender() 讀取已驗證的付款人地址,而不是從 msg.sender 讀取。

PGTR 是一種新的原語。它不是 ERC-2771 的配置文件。ERC-2771 解決的是 Gas 抽象——簽名者仍需要密鑰,且在中繼執行前必須產生離線簽名。PGTR 解決的是密鑰抽象(Key Abstraction)——任何控制代幣並能訪問 HTTP 端點的各方,都可以在無需管理私鑰的情況下授權鏈上操作。

動機

自主 AI 代理、物聯網(IoT)設備和其他無密鑰參與者需要一種無需密鑰管理負擔即可授權鏈上操作的方法。現有的元交易(Meta-transaction)標準(ERC-2771, ERC-4337)預設主體能夠產生有效的加密簽名。這一假設在以下情況下失效:

  • 無密鑰代理 — 運行在沙盒環境中的軟體代理,在該環境中存儲密鑰是不切實際或不可取的。
  • 微支付門檻 API — 接收 X402 支付並需要將其轉換為鏈上操作,且無需中間密鑰託管的 HTTP 服務。
  • 代幣驗證的機器帳戶 — 授權依據是「我付過錢了」而非「我簽過名了」的系統。

PGTR 建立了一個最小化接口,目標合約透過實現該接口來信任 PGTR 轉發器,而轉發器實現該接口以便能被 ERC-165 檢測。

規範

本文件中的關鍵詞「必須 (MUST)」、「不得 (MUST NOT)」、「要求 (REQUIRED)」、「應當 (SHALL)」、「不應當 (SHOULD NOT)」、「建議 (RECOMMENDED)」、「可以 (MAY)」和「可選 (OPTIONAL)」應按照 RFC 2119 中的描述進行解釋。

概述

PGTR 流程分為三個步驟:

  • 支付 — 付款人透過原子的 ERC-3009 transferWithAuthorization 或等效機制將代幣轉移給轉發器(或轉發器指定的託管處)。
  • 驗證 — 轉發器驗證:(a) 支付金額達到請求操作的門檻;(b) 支付收據之前未被使用過;(c) 收據未過期。
  • 中繼 — 轉發器調用目標合約。目標合約透過轉發器上的 pgtrSender() 讀取已驗證的付款人地址。

ITMPForwarder 接口

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/// @title ITMPForwarder — PGTR 轉發器接口
/// @notice 由 PGTR 轉發器合約實現。目標合約在讀取 pgtrSender() 之前,
///         必須檢查 isTrustedForwarder(msg.sender)。
interface ITMPForwarder is IERC165 {
    /// @notice 返回 true。允許 ERC-165 檢測 PGTR 轉發器。
    function isPGTRForwarder() external view returns (bool);

    /// @notice 授權當前轉發調用的付款人地址。
    ///         類似於 EIP-2771 附加的 msg.sender。
    ///         如果在活動的轉發調用之外調用,必須回退(revert)。
    function pgtrSender() external view returns (address payer);

    /// @notice 如果 addr 被此合約信任為 PGTR 轉發器,則返回 true。
    ///         鏡像了 EIP-2771 接收端的模式,用於對稱檢測。
    function isTrustedForwarder(address addr) external view returns (bool);

    /// @notice 每次成功轉發支付門檻調用時觸發。
    /// @param payer          已驗證的付款人地址。
    /// @param target         接收調用的目標合約。
    /// @param selector       轉發調用的 4 字節函數選擇器。
    /// @param paymentAmount  支付的代幣數量(以代幣的最小單位計)。
    event PaymentGatedCall(
        address indexed payer,
        address indexed target,
        bytes4  indexed selector,
        uint256         paymentAmount
    );
}

目標合約要求

接受來自 PGTR 轉發器轉發調用的合約必須:

  • 維護一組受信任的轉發器地址:
    mapping(address => bool) public trustedForwarders;

  • 公開 isTrustedForwarder(address) 並返回 trustedForwarders[addr]

  • 當需要確定有效調用者時,檢查 msg.sender 是否在 trustedForwarders 中。如果受信任,則透過 ITMPForwarder(msg.sender).pgtrSender() 讀取已驗證的付款人,而不是直接使用 msg.sender

function _effectiveSender() internal view returns (address) {
    if (trustedForwarders[msg.sender]) {
        return ITMPForwarder(msg.sender).pgtrSender();
    }
    return msg.sender;
}
  • 當透過轉發器調用時,不得將 msg.sender 用於僅限請求者操作的授權檢查。轉發器必須在轉發前將 pgtrSender() 設置為正確的請求者。

轉發器要求

PGTR 轉發器必須:

  • 實現 ITMPForwarder 並在 supportsInterface(type(ITMPForwarder).interfaceId) 中返回 true。
  • 在單個交易中原子地執行支付轉移和目標調用。如果其中任何一個失敗,整個交易必須回退。
  • 在轉發之前,驗證支付收據未被使用過:
mapping(bytes32 => bool) public consumedReceipts;

bytes32 receiptHash = keccak256(abi.encode(
    payer, amount, nonce, expiry, target, selector
));
require(!consumedReceipts[receiptHash], "Receipt already consumed");
consumedReceipts[receiptHash] = true;
  • 在轉發之前驗證收據未過期:
    require(block.timestamp <= expiry, "Receipt expired");

  • 在轉發調用期間將 pgtrSender() 設置為已驗證的付款人地址,並在調用返回或回退後將其重置。

  • 在成功轉發後觸發 PaymentGatedCall 事件。

PGTR 轉發器應當:

  • 在生產環境中使用時,應為多簽(Multisig)或治理控制的地址。單一 EOA 轉發器必須披露為中心化風險。
  • 針對每個(目標 + 選擇器)組合實現可配置的最低支付金額。
  • 在繼續操作前,驗證付款人的代幣授權(Allowance)或簽名授權涵蓋了所需金額。

重放保護

支付收據必須是單次使用的。規範的收據雜湊(Hash)為:

keccak256(abi.encode(payer, amount, nonce, expiry, target, selector))

其中:

  • payer — 支付主體的地址
  • amount — 以代幣最小單位計的支付金額
  • nonce — 每個付款人的唯一值以防止重放(可以是隨機的 bytes32 或單調遞增的計數器)
  • expiry — 超過此 Unix 時間戳後,收據必須被拒絕
  • target — 目標合約地址
  • selector — 預期調用的 4 字節函數選擇器

轉發器必須存儲已使用的收據雜湊並拒絕任何重複項。

ERC-165 接口檢測

ITMPForwarder interfaceId = 0xTBD

計算方式為以下項的 XOR:

  • isPGTRForwarder() — 0xTBD
  • pgtrSender() — 0xTBD
  • isTrustedForwarder(address) — 0xTBD

確切的計算值請參見附錄 A。

與 ERC-2771 的關係

PGTR 和 ERC-2771 是解決不同問題的不同原語:

維度ERC-2771PGTR
授權證明離線 ECDSA 簽名鏈上支付收據
主體要求必須持有私鑰必須持有代幣
發送者檢測在 calldata 中附加 20 字節後綴調用轉發器的 pgtrSender()
Gas 支付中繼者支付 Gas轉發器(伺服器)支付 Gas
重放保護轉發器中的簽名 nonce轉發器中的收據雜湊
使用場景為密鑰持有者提供 Gas 抽象為代幣持有者提供密鑰抽象

目標合約可以同時支持 ERC-2771 和 PGTR,方法是先檢查 msg.sender 是否為 ERC-2771 受信任轉發器,然後再檢查是否為 PGTR 受信任轉發器。

PGTR 並不聲稱是 ERC-2771 的「配置文件」。它使用了不同的發送者檢測機制(顯式函數調用 vs. 附加 calldata 字節)和根本不同的授權模型(支付驗證 vs. 簽名驗證)。僅實現 ITMPForwarder 的實現不得聲稱符合 ERC-2771。

原理闡述

為什麼使用 pgtrSender() 而不是附加 calldata?

ERC-2771 將原始發送者附加為 calldata 的最後 20 個字節。這種方法要求目標合約在每個需要已驗證發送者的函數中解析 calldata。PGTR 在轉發器上使用顯式的 pgtrSender() 視圖調用,這樣做:

  • 實現更簡單 — 無需操作 calldata。
  • 更具可審計性 — 身份驗證路徑是顯式且可搜索的。
  • 具備組合性 — 任何函數都可以在 msg.sender 上調用 pgtrSender(),不受 calldata 大小限制。

權衡是每次調用會增加一次額外的外部 STATICCALL(按當前價格約 700 Gas),相對於轉發操作的成本,這是可以忽略不計的。

為什麼使用支付收據而不是簽名授權?

簽名授權要求主體:

  • 擁有私鑰的訪問權限。
  • 在調用中繼之前計算並傳輸有效的簽名。

這一要求與在受限環境中運行的輕量級無密鑰代理不相容。支付收據僅要求主體持有代幣並能訪問支付端點——任何擁有資金錢包地址的自主代理都能滿足這些要求。

為什麼使用 mapping(address => bool) 而不是單一地址?

單一的受信任轉發器地址是單點故障。如果轉發器 EOA 被盜用或變得不可用,目標合約將會癱瘓。映射允許:

  • 無需重新部署合約即可進行密鑰輪換。
  • 多個並行轉發器以保證可用性。
  • 無停機時間的分階段過渡(添加新的,移除舊的)。

轉發器運營者的安全考慮

  • 收據過期窗口 — 短暫的過期窗口(約 60–300 秒)限制了收據在傳輸過程中被攔截後的利用窗口。
  • 按選擇器的最低金額 — 為每個(目標, 選擇器)對設置最低支付門檻,使重放收據在經濟上不具吸引力。
  • 搶跑(Frontrunning) — 由於收據雜湊包含付款人地址,攻擊者若要搶跑中繼調用,還必須控制代幣轉移。在單個交易中原子執行完全緩解了搶跑問題。
  • 轉發器被盜用 — 被盜用的轉發器可以代表之前支付過的付款人轉發任意調用。運營者應使用多簽或鏈上治理密鑰作為轉發器的所有者。目標合約應為所有轉發調用觸發事件,以便監控檢測異常。

向後兼容性

此 ERC 引入了一個新接口,未對現有標準進行修改。實現此標準及 ERC-2771 的目標合約仍與 ERC-2771 中繼器兼容,前提是它們按優先順序檢查轉發器類型。

安全考慮

  • 重入(Reentrancy) — 轉發器必須在轉發調用周圍使用重入保護。目標合約必須為所有狀態修改操作使用重入保護。
  • 收據重放 — 轉發器必須在轉發調用的交易中原子地消耗收據。收據不得跨鏈使用(如果擔心跨鏈收據的可移植性,請在雜湊中包含 chainId)。
  • 轉發器信任邊界 — 目標合約不得在沒有治理控制的情況下添加轉發器。不受限制的 addForwarder 在功能上等同於 selfdestruct
  • pgtrSender() 原子性 — 轉發器必須在轉發調用返回後重置 pgtrSender()。如果轉發器允許讀取 pgtrSender() 的重入調用,之前的付款人地址可能會被錯誤地歸因於重入調用。

參考實現

請參閱參考實現倉庫中的 src/interfaces/ITMPForwarder.sol

TaskMarket.sol 中提供了一個完整的 PGTR 兼容目標合約參考實現,它實現了 trustedForwarders 映射模式,並公開了用於 ERC-165 檢測的 supportsInterface(type(ITMP).interfaceId)

版權

透過 CC0 放棄版權及相關權利。


附錄 A:接口 ID 計算

ITMPForwarder 接口 ID 計算為接口中聲明的所有函數的 4 字節 Keccak256 選擇器的 XOR:

函數選擇器
isPGTRForwarder()部署時計算
pgtrSender()部署時計算
isTrustedForwarder(address)部署時計算

在 Solidity 中計算:
bytes4 id = type(ITMPForwarder).interfaceId;

在鏈下計算(JavaScript/viem):

import { toFunctionSelector, keccak256, toBytes } from 'viem';
const xor = (a: string, b: string) =>
  '0x' + (parseInt(a, 16) ^ parseInt(b, 16)).toString(16).padStart(8, '0');
const id = [
  toFunctionSelector('isPGTRForwarder()'),
  toFunctionSelector('pgtrSender()'),
  toFunctionSelector('isTrustedForwarder(address)'),
].reduce(xor);

1 則貼文 - 1 位參與者

閱讀完整主題

https://ethereum-magicians.org/t/draft-erc-payment-gated-transaction-relay/27934