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

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

时间:2025-11-07 23:33:10来源: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)系列二的详细内容,更多关于元交易合约实现的资料请关其它相关文章!


宝可梦阿尔宙斯金属膜在哪(阿尔宙斯z纯晶) EOS价格今日行情最新消息,柚子币(EOS/CNY)实时汇率历走势图 有什么好玩的单机游戏(单机游戏大作排行榜) 第五人格天赋点怎么获取(第五人格天赋点怎么获取不了) shib柴犬币用哪个钱包?什么钱包支持shib? Tokenomy是什么交易所?Tokenomy交易所怎么样? 狗狗币在哪个平台上买最正规? 数字货币交易app排行榜前十名 2022年最新世界十大虚拟货币钱包排名汇总 王者荣耀召唤师技能有哪些(王者荣耀里面的召唤师技能详解) 比特交易所下载官网app更新内容,gate.io官网比特儿官网登录入口 MetaMask小狐狸钱包测试RPC网络配置以及测试币获取 有没有不烧钱的古风武侠游戏(不花钱的古风手游) DEX数字货币交易所APP都有哪些?DEX交易所官网网址入口 暗区突围机密文件在哪刷(暗区突围机密文件在哪刷新概率高) BMJ是什么币种?BMJ币合法吗? 我的世界烟花如何增加飞行时间(我的世界如何加长烟花的飞行时间) 创造与魔法雅安河的辣椒在哪里(创造与魔法贝雅河) c盘的垃圾文件在哪个文件夹?如何清除电脑c盘的垃圾文件 王者荣耀司空震怎么玩 司空震玩法攻略(王者荣耀司空震怎么玩才厉害) 消逝的光芒2等级怎么提升(消逝的光芒2有什么变化) fil币价格今日行情_fil币最新消息及前景 清除c盘的垃圾文件会怎么样?如何清除c盘除系统外所有垃圾 为什么电脑老是弹出脚本错误,老是提示的解决方法 什么是区块链?区块链金融是什么意思? 区块链DAG是什么意思?区块链DAG有什么优势? XCH币2025年会前景爆发吗?Chia币奇亚币2025-2030未来价格预测 币在(BitZ)是什么交易所?BitZ交易所怎么样? 和平精英极限追猎卫星楼的门怎么开(和平精英极限追猎探测仪怎么用) IOST币值得长期持有吗?IOST币未来前景2025-2030年价格预测 抹茶ASS驴币如何买入和卖出? SHIB(柴犬币、狗屎币)是空气币吗? 柴犬币是不是主流币? 手机即时类游戏有哪些好玩(有什么即时手游好玩) 通俗解释元宇宙是什么概念?元宇宙的应用有哪些? 狗狗币今日行情最新价格,DOGE狗狗币最新消息历史走势 星图交易所怎么样?SAEX星图交易所安全靠谱吗? 做空比特币教学:在加密货币熊市如何从空头赚币?看这一篇就够了 HKEx.one手续费是多少?hkex.one交易所怎么样 王者荣耀紫星币怎么买(王者荣耀紫星币买的皮肤能直接使用吗?) 如何将交易所的币提到钱包 Metamask钱包连接DOTC使用教程 宝可梦阿尔宙斯桃发在哪(宝可梦传说阿尔宙斯新宝可梦) 比特派钱包使用教程:如何提取云钱包中的BTM 和平精英怎么发表情包(和平精英怎么发表情包动态) dnf几级二次觉醒(地下城几级二次觉醒) 中国数字资产交易平台如何交易?中国人怎么进入数字交易所 暗区突围体验服怎么申请(暗区突围体验服资格申请) 欧易活期挖矿可以一直挖吗?欧易挖矿怎么样? 大型在线网游游戏哪一款免费(大型在线网络游戏) 币信钱包怎么提现?币信钱包提现操作教程 电脑无权限访问网络是什么问题 电脑显示无网络访问权限怎么解决 CAKE币怎么挖矿?CAKE币挖矿详细图解教程 OKB币值得投资吗?OKB币未来能涨到多少2025年价格预测 okex如何提现?okex提现详细图文教程 WRX是什么币种?WRX币前景及价值深度分析 王者荣耀生日福利怎么设置(王者荣耀生日福利怎么设置?) 有哪些手游冒险类游戏(冒险类型手游) 火币网币币如何转法币?usdt兑换提现人民币教程 有没有ios可以玩的h5页游(苹果手机玩h5用哪个浏览器) 消逝的光芒2集市保险箱密码是多少(消逝的光芒2彩蛋) 王者荣耀怎么改低分战区(王者荣耀怎么改低分战区定位) 生化危机系列游戏有几部(生化危机游戏系列顺序) 江南百景图猫爪怎么获得(江南百景图限时活动找小猫) 和平精英id前面性别怎么隐藏(和平精英id前面的性别怎么改) 哈利波特魔法觉醒入口庭院在哪(哈利波特魔法觉醒 礼堂门口) 柴犬币在哪里购买?SHIB币在哪个交易所买卖? 宝可梦阿尔宙斯眷恋云怎么抓(阿尔宙斯固执) HT是什么币,值不值得投资?HT币在哪个交易所有卖的 王者荣耀互动技能在哪设置(王者荣耀游戏互动语在哪里设置) 合成资产有哪些币?合成资产币盘点汇总 比特币每天都能交易吗?比特币适合长期持有吗? 问道手游平民玩什么职业好(问道手游平民玩什么职业好2020) 宝可梦阿尔宙斯传说裙儿小姐怎么获得(神奇宝贝阿尔宙斯娘化) 外媒评M站近十年最佳RPG游戏:《艾尔登法环》登榜第二 我们好多人买了雷达币还有救吗?2025雷达币价格预测 Moomex交易所怎么样?Moomex交易所安全靠谱吗? 我的世界创艺修仙异火怎么炼化(我的世界创艺修仙功法有什么用) cf手游戒指属性哪个好(cf手游戒指所有属性) 求职必看!靠谱网站有哪些(比较好的求职网站) 诛仙3百灵飞什么阵营(诛仙3灵阵灵组合攻略) cf灵狐的约定怎么领vip(cf灵狐的约定怎么领体验卡) 和平精英解除亲密关系对方知道吗(和平精英解除情侣关系对方知道吗) 区块高度高代表什么?区块高度的作用 我的世界奥法指环有什么用(我的世界奥法戒指怎么用) 冰原守卫者蜘蛛丝如何获取(冰原守卫者武器大全) dnf魔道学者上衣选择什么技能(dnf魔道学者穿什么套装) 中国最大的炒币平台Binance交易所怎么样?币安Binance数字货币交易app下载地址 三国志幻想大陆刘关张阵容搭配(三国志幻想大陆刘关张阵容搭配) Tidebit是什么交易所?Tidebit交易所怎么样? 币安Binance交易所是最好的炒币平台吗?揭秘全球最大的BTC期货交易平台 币圈是什么意思?币圈十大交易所排名 比特币涨了多少?历史最低价格?比特币未来的价格预测 王者荣耀免流量怎么退订(王者荣耀免流量怎么退订会员) 创造与魔法陆行鸟羽毛怎么生产(创造与魔法陆行鸟羽毛怎么生产的) 有什么打怪升级爆装备的手游(打怪升级爆装备的网络游戏) 如何提币到另一个交易所?如何提币到不同的交易所? 哪个卡牌游戏好玩不氪金(好玩不氪的卡牌手游) 科普:去中心化钱包和银行的不同 简单3步教你怎么设置家庭wifi路由器(如何设置家庭无线路由器) KuCoin是什么意思?库币/KuCoin是合法的吗? 使用预言机将数据和api变现的方法

热门文章

推荐专题

更多>>

游戏推荐

更多>>