在以太坊区块链的世界里,每一笔交易的背后都涉及到一系列复杂的地址和角色,它们共同确保了交易的安全性、可追溯性和去中心化特性。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 的关键区别与联系

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

假设:

在这个场景中:

核心区别总结:

特性 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 追溯的是交易的最初源头。

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

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

返回栏目