以太坊交易中的发起者与执行者,深入解析 sender 与 originer

投稿 2026-02-16 1:21 点击数: 2

在以太坊区块链的世界里,每一笔交易的背后都涉及到一系列复杂的地址和角色,它们共同确保了交易的安全性、可追溯性和去中心化特性。sender(发送者)和 originer(发起者,通常以 tx.origin 表示)是两个至关重要但又容易混淆的概念,理解它们的区别与联系,对于智能合约开发者、用户以及任何希望深入了解以太坊工作机制的人来说,都是必不可少的。

sender:交易的直接执行发起者

sender,顾名思义,指的是在当前交易上下文中,直接发起调用或执行智能合约函数的那个账户地址,这个地址可以是外部拥有账户(EOA,Externally Owned Account),也就是由用户通过私钥控制的普通钱包地址;也可以是另一个智能合约地址,如果是后者,那么意味着这笔交易是由另一个智能合约触发的。

在 Solidity 智能合约编程语言中,msg.sender 是一个全局变量,它始终返回当前函数调用者的地址,它是智能合约进行权限验证、资金转移等操作时最常用的身份标识之一。

核心特点:

  1. 上下文相关msg.sender 的值取决于当前执行的调用栈,如果合约 A 调用了合约 B 的函数,那么在合约 B 的函数执行上下文中,msg.sender 就是合约 A 的地址。
  2. 直接调用者:它代表了直接发起本次函数调用的实体。
  3. 常用性:在智能合约内部,msg.sender 被广泛用于检查调用者是否有权执行某些操作,
    function withdraw() public {
        require(msg.sender == owner, "Only owner can withdraw");
        payable(msg.sender).transfer(address(this).balance);
    }

originer (tx.origin):交易的最初发起者

originer,在以太坊中通过全局变量 tx.origin 来表示,它指的是整个交易链的最初发起者,也就是首先在网络上发起这笔交易的外部拥有账户(EOA)地址,无论这笔交易经过了多少层智能合约的调用,tx.origin 始终是最初发起交易的那个 EOA 地址。

核心特点:

  1. 全局固定tx.origin 在一笔交易的整个执行过程中保持不变,它指向的是交易的源头。
  2. 最初发起者:它必须是 EOA,因为只有 EOA 才能主动发起一笔交易(通过签名并广播),智能合约不能主动发起交易,只能响应调用。
  3. 用于防钓鱼(谨慎使用)tx.origin 通常用于防止恶意合约的钓鱼攻击,一个合约可以检查 msg.sender 是否等于 tx.origin,如果不相等,说明本次调用是由另一个合约转调过来的,可能存在风险,从而拒绝执行某些敏感操作。
    function dangerousFunction() public {
        require(msg.sender == tx.origin, "This function can only be called directly by EOA");
        // 执行敏感操作
    }

sender 与 originer 的关键区别与联系

随机配图

为了更清晰地理解两者的区别,我们可以通过一个典型的交易调用场景来说明:

假设:

  • 用户 Alice (EOA 地址:0xAlice) 发起了一笔交易,调用合约 B 的函数 foo()
  • 合约 B (地址:0xB) 的 foo() 函数内部,又调用了合约 C (地址:0xC) 的函数 bar()

在这个场景中:

  • tx.origin:始终是 0xAlice,因为 Alice 是这笔交易的最初发起者。
  • 在合约 B 的 foo() 函数中
    • msg.sender0xAlice(因为 Alice 直接调用了 B)。
  • 在合约 C 的 bar() 函数中
    • msg.sender0xB(因为合约 B 调用了合约 C)。

核心区别总结:

特性 msg.sender (sender) tx.origin (originer)
定义 当前函数的直接调用者地址 整笔交易的最初发起者(EOA)地址
可变性 随着调用栈变化而变化 在整个交易执行过程中保持不变
类型 可以是 EOA,也可以是智能合约地址 必须是 EOA 地址
主要用途 合约内权限控制、身份识别 防钓鱼攻击、识别交易源头(但需谨慎使用)

重要性与潜在风险

理解 senderoriginer 的区别至关重要,因为错误地使用 tx.origin 可能会导致严重的安全漏洞。

tx.origin 的主要风险:

tx.origin 最常见的误用场景是在授权模式中,假设有一个合约 Token,它有一个 approve() 函数,允许用户授权另一个地址 spender 来转移其代币。approve() 函数错误地使用了 tx.origin 来判断授权者:

// 错误的示例
function approve(address spender, uint256 amount) public {
    require(tx.origin == msg.sender, "Approval from EOA only"); // 错误的检查
    allowances[msg.sender][spender] = amount;
}

攻击者可以构造如下攻击:

  1. 攻击者部署一个恶意合约 MaliciousContract
  2. 诱骗用户 Alice(EOA)调用 MaliciousContract 中的一个函数,该函数内部会调用 Token 合约的 approve(攻击者地址, 大量代币)
  3. Tokenapprove() 函数中,tx.origin 是 Alice(EOA),msg.senderMaliciousContract(智能合约),如果错误地认为 tx.origin == msg.sender 才是授权,那么这里会失败,但如果逻辑是检查 tx.origin 是否为 EOA(如上例注释所示),那么它会通过。
  4. 更糟糕的是,如果后续的 transferFrom() 函数错误地依赖 tx.origin 来判断代币所有者,攻击者就可以利用 Alice 的授权转走其代币。

正确的做法是,approve() 函数应该直接使用 msg.sender 作为授权者,因为 msg.sender 才是直接调用 approve() 并发出授权指令的实体(无论是 EOA 还是合约,但授权的主体是 msg.sender)。

msg.sendertx.origin 以太坊交易中两个基础但至关重要的全局变量,它们共同标识了交易参与者的身份。msg.sender 关注的是当前调用的直接发起者,而 tx.origin 追溯的是交易的最初源头。

对于智能合约开发者而言:

  • 优先使用 msg.sender 进行权限控制和身份验证,因为它准确反映了当前调用上下文。
  • 谨慎使用 tx.origin,仅在需要明确识别交易最初发起者(如防钓鱼场景)时使用,并充分理解其潜在风险,避免授权相关的漏洞。

深入理解这两者的区别,不仅能帮助我们编写更安全、更健壮的智能合约,也能让我们更清晰地洞察以太坊交易执行的内在逻辑,在去中心化的世界里,每一个地址的“身份”都至关重要,而 senderoriginer 正是这些身份的核心体现。