手机版
扫描查看手机站
首页 > 文章 > 加密世界 > 正文

元交易合约如何实现?智能合约开发实战:元交易(Metatransaction)系列二

时间:2025-07-02 11:43:13来源:525游

引言

上文中提到,普通的 ETH 交易并不能够做到让用户无需 gas 费,需要交易中嵌套一个交易,即元交易,来实现免 gas 费。

本文将分析开源库 OpenZeppelin/openzeppelin-contracts 中的元交易合约的实现,让你能够快速入门元交易实现细节,从而能够自己对后续更多的相关技术深入探索。

前置知识概述

元交易会涉及到 ECDSA 与 EIP712 等知识,如果你是熟手,可以跳过此节内容,直接浏览具体实现分析部分。

Hash

也称哈希、散列、数字摘要。通过哈希函数,可以将长短不一的信息转化为一段长度任意但可预测的(确定性的)结果。这是一类神奇的函数,可以将一大堆信息转变成一串短的,可作为摘要的数据 “指纹”。对于一个给定的输入而言,生成的 “指纹” 始终一致。如果你的原始数据中有任何细微的改动,生成的哈希值将大不相同。以太坊中采用的是 Keccak-256 算法。

ECDSA

在密码学中,ECDSA(Elliptic Curve Digital Signature Algorithm,椭圆曲线数字签名算法)是使用椭圆曲线密码学的数字签名算法(DSA)的一个变种。

主要用于对数据(比如一个文件)创建数字签名,以便于你在不破坏它的安全性的前提下对它的真实性进行验证。可以将它想象成一个实际的签名,你可以识别部分人的签名,但是你无法在别人不知道的情况下伪造它。

你不应该将ECDSA与用来对数据进行加密的AES(高级加密标准)相混淆。ECDSA不会对数据进行加密、或阻止别人看到或访问你的数据,它可以防止的是确保数据没有被篡改。

如图所示,在以太坊中,ECDSA 用于对原始数据的 hash 值进行签名及恢复。
 

元交易合约如何实现?智能合约开发实战:元交易(Metatransaction)系列二

将原始数据通过 hash 函数得到它的 hash 值后,用户 A 用自己的私钥对该 hash 值进行签名,得到 Signature(签名)。有了该签名与 hash 值,任何人都能够从中恢复出签名人的钱包地址,在这里用户 B 则恢复得到了用户 A 的钱包地址。

EIP712
Ethereum Improvement Proposals (EIPs),你可以在这里查看所有的 EIPs。EIP712 (Ethereum typed structured data hashing and signing)以太坊类型的结构化数据哈希与签名。

如果我们只关心字节字符串的话,签名数据是一个已经解决了的问题。但不幸的是,在现实世界中,我们关心的是复杂而有意义的信息,对结构化数据进行哈希是非常重要的,错误会导致系统安全属性的丢失。

此 EIP 旨在提高链上使用的链下消息签名的可用性。我们看到越来越多的人采用链下消息签名,因为它节省了 gas 费,减少了区块链上的交易数量。当前签名消息是一个不透明的十六进制字符串,显示给用户,关于组成消息的项目的上下文很少。
 

元交易合约如何实现?智能合约开发实战:元交易(Metatransaction)系列二

EIP712 概述了一个编码数据及其结构的方案,该方案允许在签名时将数据显示给用户进行验证。下面是一个用户在签署 EIP712 消息时显示的示例。

元交易合约如何实现?智能合约开发实战:元交易(Metatransaction)系列二

元交易合约的实现

此分析针对 openzeppelin-contracts v4.3.2 版本。

contract MinimalForwarder is EIP712 {
    using ECDSA for bytes32;

    struct ForwardRequest {
        address from;
        address to;
        uint256 value;
        uint256 gas;
        uint256 nonce;
        bytes data;
    }

	constructor() EIP712("MinimalForwarder", "0.0.1") {}
}

ECDSA 是 openzeppelin 实现的一个 solidity 库,它实现了从 hash 值中恢复钱包地址的方法,将它应用在 bytes32 上,就可以直接在 bytes32 上调用 recover 方法。recover 函数签名:function recover(bytes32 hash, bytes memory signature) internal pure returns (address) 。

ForwardRequest 结构体定义了一个交易中用于签名的基本组成成分。与以太坊交易不同的是没有 gasPrice,因为智能合约的执行只关心 gas 的消耗。ForwardRequest 中 的 nonce 概念与以太坊类似,都是为了避免双花攻击,但这里的 nonce 仅由智能合约维护,跟普通的以太坊交易中的 nonce 无关。

构造函数中直接使用 EIP712 的构造函数进行初始化,EIP712 的构造函数签名为:constructor(string memory name, string memory version) ,其中 name 是合约名称,version 是合约版本,这将作为 EIP712 签名验证的一部分,它在部署时,将自动获取合约的地址、chainId 等信息。意味着,即便有相同的 ForwardRequest 结构体数据,但合约地址或区块链网络不同,也会导致签名无效。
 

mapping(address => uint256) private _nonces;

function getNonce(address from) public view returns (uint256) {
	return _nonces[from];
}

为了避免双花攻击,在智能合约中维护 nonce 是必要的。

bytes32 private constant _TYPEHASH =
	keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)");

function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
	address signer = _hashTypedDataV4(
            keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data)))
    ).recover(signature);
	return _nonces[req.from] == req.nonce && signer == req.from;
}

看到 verify 函数,我们知道,要将钱包地址恢复,至少需要经过 ECDSA 的签名以及用于签名的原始数据,而此处,ECDSA 签名的原始数据就是经过 abi 编码的 keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data))) ForwardRequest 结构体数据的哈希值。再通过调用 ECDSA 库中的 recover 函数,传入签名,就能够恢复得到签名者的钱包地址。

通过 _nonces[req.from] == req.nonce 来确保交易的调用是顺序的,且不会遭受双花攻击。signer == req.from 避免签名者与实际元交易发送者不匹配。

接下来看,如何执行元交易。
 

function execute(ForwardRequest calldata req, bytes calldata signature)
	public
	payable
	returns (bool, bytes memory)
{
	require(verify(req, signature), "MinimalForwarder: signature does not match request");
	_nonces[req.from] = req.nonce + 1;

	(bool success, bytes memory returndata) = req.to.call{gas: req.gas, value: req.value}(
		abi.encodePacked(req.data, req.from)
	);
	// Validate that the relayer has sent enough gas for the call.
	// See https://ronan.eth.link/blog/ethereum-gas-dangers/
	assert(gasleft() > req.gas / 63);

	return (success, returndata);
}

在使用 Address.call 方法的时候,根据元交易参数,指定了 call 的 gas 与 value 值。需要注意的是,这里并不直接将元交易的 data 字段当作 call 操作的 data,而是将 data 与 from 进行 abi 编码后一起作为 call 操作的参数,这在目标合约(也就是 req.to)中会被解析,从而得到交易的发送者,在下面会详细讲解。

assert(gasleft() > req.gas / 63) 简单理解为避免中继器(代为执行元交易的人)恶意地或无意地使用足够低的 gas 使得交易执行成功,而元交易执行失败。详情可以在 ethereum gas dangers 中学习。

ERC2771

要支持元交易,仅实现元交易智能合约是不够的,因为目标合约无法知道实际的元交易 from 是谁。如果没有额外的措施,它将只能够从 msg.sender 中获取,由于在元交易合约实现中,是通过 Address.call 调用的,因此将得到的发送者是元交易合约的地址。ERC2771 则解决了该问题。
 

abstract contract ERC2771Context is Context

ERC2771Context 继承了 Context,而 Context 中简单封装了从 msg.sender 与 msg.data ,以便规范这两个功能的使用,且能够让其在子合约中修改其行为。要求使用 Context 合约获取 msg 相关的数据,而不是直接使用 msg.sender 等。
 

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

ERC2771Context 就修改了 Context 合约的方法。

function _msgSender() internal view virtual override returns (address sender) {
	if (isTrustedForwarder(msg.sender)) {
		// The assembly code is more direct than the Solidity version using `abi.decode`.
		assembly {
		sender := shr(96, calldataload(sub(calldatasize(), 20)))
		}
	} else {
		return super._msgSender();
	}
}

先通过 isTrustedForwarder(msg.sender) 验证元交易的调用方是期望的元交易合约地址。assembly 代码将上文的元交易合约中 req.to.call{...}(abi.encodePacked(req.data, req.from)) 编码进的 data 部分内容的 req.from 获取到,然后再返回该值。

元交易使用概览

让我们来尝试简单使用元交易合约,要支持元交易,你所编写的合约必须继承 ERC2771Context。在这里简单实现一个 NFT 合约,在部署它之前,你必须先部署元交易合约,将元交易合约地址作为参数传递给 NFT 合约构造函数。
 

// SPDX-License-Identifier: GPL3.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract NFT is ERC2771Context, ERC721 {
    using SafeMath for uint256;

    uint256 private _currentTokenId = 0;

    constructor(
        string memory name,
        string memory symbol,
        address trustedForwarder
    ) ERC721(name, symbol) ERC2771Context(trustedForwarder) {}

    function safeMint() public virtual {
        safeMint("");
    }

    function safeMint(bytes memory _data) internal virtual {
        uint256 tokenId = _getNextTokenId();
        _incrementTokenId();
        _safeMint(_msgSender(), tokenId, _data);
    }
    
    function getCurrTokenId() public virtual view returns (uint256) {
        return _currentTokenId;
    }

    /**
     * @dev calculates the next token ID based on value of _currentTokenId
     * @return uint256 for the next token ID
     */
    function _getNextTokenId() internal virtual view returns (uint256) {
        return _currentTokenId.add(1);
    }

    /**
     * @dev increments the value of _currentTokenId
     */
    function _incrementTokenId() internal virtual {
        _currentTokenId++;
    }

    function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address) {
        return ERC2771Context._msgSender();
    }

    function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) {
        return ERC2771Context._msgData();
    }
}

在这个示例中,如果 Alice 没有足够的 ETH 支付 gas 费,来铸造一个 NFT,她可以签署一个元交易,元交易的 data 是由 abi.encodeWithSignature(functionSelector, parmas...) 得到的,将该元交易递交给具有足够 ETH 的 Bob,Bob 调用元交易合约 MinimalForwarder.execute(req, signature),从而让 Alice 的元交易成功执行。

以上就是元交易合约如何实现?智能合约开发实战:元交易(Metatransaction)系列二的详细内容,更多关于元交易合约实现的资料请关其它相关文章!


ANB是什么币种?ANB币发展历程介绍 王者荣耀安卓账号可以转苹果吗(王者荣耀安卓账号可以转苹果吗,不是都微信登录的吗) chia农场是什么?chia播种以及chia工作原理介绍? cf巅峰卡牌怎么获得(cf巅峰期) Gate.io最新版本的功能介绍,Gate.io官网下载最新版入口 梦幻西游手游狐美人能加入什么门派(梦幻西游手游狐美人时装搭配) 狗狗币在哪个平台上买最正规?狗狗币数字货币交易app排行榜 魔兽世界精金粉怎么获得(魔兽世界精金棒怎么做) 宝可梦传说阿尔宙斯冰六尾怎么进化(宝可梦冰六尾怎么得) 以太经典ETC上涨原因是什么? 有哪些回合制策略卡牌网游好玩(回合制卡牌类游戏) 不肝不氪!0氪不花钱的放置类游戏推荐 CEEX是正规交易平台吗?西易CEEX交易平台官网入口 创造与魔法中怎么获得青青猪(创造与魔法哪里刷青青猪几率大) 2025年虚拟币交易哪个平台好?2025最新虚拟币交易app下载安装 王者荣耀S27赛季皮肤怎么领(王者荣耀s27赛季皮肤领不到) bybit中国人能注册吗?bybit中国大陆账户如何登陆 魔兽世界黑暗神殿项链怎么拿(wow黑暗神殿掉落) dnf固伤职业有哪些(dnf固伤职业有哪些2021) 梦幻西游怎么玄学挖宝图(梦幻挖宝图技巧) 梦幻西游手游泡泡跟谁合宠(梦幻西游手游泡泡宝宝好不) 一个USDT等于多少人民币?USDT今日最新价格人民币汇率 上古诸神魔尊表现如何 魔尊强度一览(上古诸神官网) Wallstreet是什么交易所?Wallstreet华尔街交易所全面介绍 ZOON代币游戏是什么?ZOON币合约地址详解 imToken钱包如何进行转账/收款? TRIBE币怎么样?TRIBE币前景及价值深度分析 USDT属于什么币种_usdt属于什么币种合法不 奇迹暖暖搭配师联盟活跃度怎么提升(奇迹暖暖的搭配师联盟怎么加) MANA币是什么币?MANA币未来前景怎么样? Coinsbit是什么交易所?Coinsbit交易所安全吗? 王者荣耀公孙离怎么获得(怎么获得公孙离皮肤) 加速器市场规模、竞争状况、盈利模式分析运营方案(转载) 盘点2023热门的回合制手游有哪些(2020年值得期待的回合制手游) 王者荣耀代练异地登录怎么解决(王者代打异地登录会封号吗) 和平精英兰博基尼怎么免费获得(和平精英的兰博基尼怎么获得) 放开那三国3吴国阵容推荐(放开那三国3吴国强吗) Hitbtc注册充值提现教程图文分享?Hitbtc交易平台怎么样? trc20和erc20有什么区别(主要是转账效率与手续费) 雷达币多少钱一个币?雷达币最近为何暴涨利好消息 在昆明租房用什么软件找房会比较好?(网上租房app推荐) 4月武林纷争再起!2021手游逆水寒国产武侠新势力登场 哈利波特魔法觉醒魔咒试炼第四关怎么打(哈利波特魔法觉醒魔咒试炼生物克制怎么过) Localbitcoins交易所怎么样?Localbitcoins安全吗 香港交易所正规平台有哪些?香港数字货币交易所排名 造梦西游3苍龙戒怎么获得(造梦西游3苍龙戒怎么用) ave交易所是哪个国家?ave交易所app下载安卓入口 Pepe币在哪里买?用什么软件交易?Pepe币数字货币交易所App下载 王者荣耀按键皮肤所有英雄都能用吗(王者 按键) 我的世界神奇宝贝gs球怎么获得(我的世界神奇宝贝go) 王者荣耀禁止改名怎么解除(王者被禁止改名) dnf属性攻击可以双属性吗(dnf属性攻击能多种生效吗) 比特币交易所币安(Binance)介绍 如何在币安做空比特币BTC操作教程 TITAN是什么币种?TITAN币怎么样? 和平精英呼救塔有什么用(和平精英呼救器用不了) dnf点券比例是多少(dnf点券上限是多少) 二次元动漫ACG游戏有哪些(二次元game) dnf武器幻化有什么用2022(dnf武器幻化有什么效果) 币赢是正规平台吗安全吗?币赢交易所官网入口地址 SCRT是什么币种?一文读懂SCRT币前景和价值 GEAR币发行量多少?GEAR币发行总量介绍 玩客币行情最新价格,(玩客币(链克)今日价格行情,最新消息 冰原守卫者寒霜剑士领主怎么打(寒冰护卫者值得做吗) 奥比岛手游隐藏成就会走路的维纳斯怎么做(奥比岛走秀换东西的地方在哪) 什么游戏不用实名认证也不用登录(什么游戏不用实名认证也不用登录的射击游戏) PancakeSwap交易所下载与官方介绍,PancakeSwap交易所官网地址入口 dnf装备锻造在哪2023(dnf在哪锻造装备会失败吗) 英雄联盟金克丝S12怎么出装(金克丝出装) 蜀门手游百花仙子怎么加点(蜀门手游百花技能怎么加才好) 正规比特币交易所究竟有哪些平台?十大比特币交易平台盘点 和平精英高光时刻怎么看(吃鸡怎么看高光时刻) 有什么免费的gm后台游戏(免费gm游戏资源) 火币上怎么把币卖成钱?火币网买币提现教程 什么是区块链ICO、IFO? 阴阳师逢魔之时是什么时候(阴阳师逢魔之时阵容攻略) 和平精英礼包码在哪里兑换(和平精英礼包兑换码怎么用) 帝国战纪游戏船坞怎么得到(帝国战纪战役攻略) 创造与魔法狗子资质怎么算(创造与魔法狗怎么当宠物?) 无神之界平民职业怎么选择(无神之界手游职业) 星露谷物语海莉在哪里(星露谷物语海莉在哪里 海莉全出现位置一览) CoinBene币/满币网交易所交易手续费明细一览表 看风景去哪里旅游最好?策划一次完美旅行 梦幻西游奇经八脉怎么开启(梦幻西游奇经八脉在哪里点) gta5利他教送4个人位置在哪(gta5利他教徒送去后) (复古龙城霸业1·80手游)新手详细攻略 比特币如何提现人民币?比特币提现教程 阴阳师神乐疾风自动给哪个目标(阴阳师神乐自动技能顺序) 地下城堡3魂之诗20年后推图顺序是什么(地下城堡3魂之诗任务攻略) Chia奇亚常见问题解答,CHIA奇亚到底怎么玩? 外媒评10款最佳动漫RPG,动漫rpg类手游大全推荐 泰拉瑞亚精金锭怎么做(泰拉瑞亚精金锭怎么做手机) 魔兽世界元帅的计划任务怎么做(魔兽世界元帅多久刷新) Curve交易所怎么样?Curve交易所安全吗? 一文看懂PolarFox和The Graph的区别 英雄联盟吸血鬼叫什么名字(英雄联盟吸血鬼是什么英雄) 十大回合游戏(十大回合游戏,最好玩的回合手游) 创造与魔法农耕管理台有什么用(创造与魔法农业) 问道手游哪个职业好(问道手游哪个职业好杀地宫) 虚拟货币交易平台APP有哪些?数字货币交易所App排行榜 我有10个比特币怎么卖掉?比特币怎么买卖交易赚钱

热门文章

推荐专题

更多>>

游戏推荐

更多>>