技术分析以太坊多重签名:以 Ownbit 与 Gnosis 为例
发布:中币网 时间:2020-12-16 15:30:00 加入收藏 打赏
通过合约源码理解 Ownbit 与 Gnosis 两种以太坊多签方式实现原理和优缺点。
原文标题:《深入剖析 Ownbit 和 Gnosis 多签》
撰文:谈国鹏,Ownbit 创始人
近期 Ownbit 多签增长迅速,单单 ETH/ERC20 多签一项,管理的资金总额已经超过了 1 亿美金。Gnosis 是另一个使用较为广泛的 ETH/ERC20 多签钱包。
Ownbit 和 Gnosis 均通过合约账户实现以太坊多签,但是其实现的逻辑却迥然不同。分别代表了当前两种主流的实现方式,我们通过合约源码来讲解实现原理和各自的优缺点。
查看多签合约源码
首先,我们在 etherscan 上分别选取一个 Ownbit 和 Gnosis 多签地址,并在「Contract」页面上查看相应的源码。Ownbit 多签合约名称为:OwnbitMultiSig,而 Gnosis 多签合约名称为:MultiSigWallet。
Ownbit 多签地址例一
Gnosis 多签地址例一
注:etherscan 上的合约源码是可以自由上传的。关于如何上传合约源码到 etherscan 可以参考下面的 网址。
实现原理介绍
一个 M-N 多签的含义,以 3-5 多签为例,是指 5 个人管理资产,3 个人同意的情况下,可以花费该笔资产。在以太坊中,一个地址(私钥)代表一个人。如何表示你同意花费某笔资产?有两种方式:
- 用你的私钥对相应的花费(金额、目标地址等等)进行签名,并给出签名结果;
- 用你的私钥发送一笔以太坊交易,去调用某个特定接口,并给予特定参数;
Ownbit 多签使用了第一种方法,而 Gnosis 多签使用了第二种方法。
构造函数
Ownbit 多签和 Gnosis 多签在构造函数上几乎一致,只是在一些细节处理上 Ownbit 做了一些优化。
constructor(address[]_owners, uint_required) public validRequirement(_owners.length,_required) { for (uint i = 0; i <_owners.length; i++) { //onwer should be distinct, and non-zero if (isOwner[_owners[i]] ||_owners[i] == address(0x0)) { revert(); // Gnosis 此处为 throw } isOwner[_owners[i]] = true; } owners =_owners; required =_required; }
构造函数验证传入的 onwer 地址的唯一性和非零,以及 owner 人数和最少签名人数的常规检查。
throw 作为关键字和 revert 功能一致,只是 revert 会退还剩余的气,而 throw 会消耗掉剩余的气(气的花费上,throw 类似于 assert)。并且,throw 已经不推荐使用,而且在未来的版本中将被彻底去除。因此,新开发的合约,应使用 revert 替换 throw。
Gnosis 实现多签逻辑
Gnosis 实现多签逻辑的过程如下:
1. 任意一方通过 submitTransaction 方法提交交易,得到一个交易号(transactionId,该交易号并非我们常见的交易哈希,而是一个自增长的 uint256):
constructor(address[]_owners, uint_required) public validRequirement(_owners.length,_required) { for (uint i = 0; i <_owners.length; i++) { //onwer should be distinct, and non-zero if (isOwner[_owners[i]] ||_owners[i] == address(0x0)) { revert(); // Gnosis 此处为 throw } isOwner[_owners[i]] = true; } owners =_owners; required =_required; }
value 是多签即将执行的交易所要转移的 ether 数量(以 wei 为单位),data 是该交易的数据。bytes 类型意为 byte[],表示任意数组。因此该交易可以传入任何 data ,以实现任意功能。
几种 data 的写法和功能:
普通转出 ETH,value = 0,data = [](空);
转出 Erc20 代币(例如转出 USDT-ERC20),value = 0,data 为 Erc20 transfer 方法的哈希和参数(如图):
data = a9059cbb000000000000000000000000859d2cda0310007f050516a9f02559b3755a87cc000000000000000000000000000000000000000000000000000000012a05f200
- 调用任意合约的任何方法,例如调用 UniswapV2 合约,买入某个 Erc20 代币,data 生成方式和上面类似。
2. 其他参与方提交 ETH 交易,调用合约的 confirmTransaction 方法,来表示他们对某个交易执行的认可:
function confirmTransaction(uint transactionId) public ownerExists(msg.sender) transactionExists(transactionId) notConfirmed(transactionId, msg.sender) { confirmations[transactionId][msg.sender] = true; Confirmation(msg.sender, transactionId); executeTransaction(transactionId); }
当 confirm 的人数达到最低(_required)要求,executeTransaction 的内部逻辑将被触发,从而执行第一步用户所提交的逻辑(value 和 data):
function executeTransaction(uint transactionId) public notExecuted(transactionId) { if (isConfirmed(transactionId)) { Transaction tx = transactions[transactionId]; tx.executed = true; if (tx.destination.call.value(tx.value)(tx.data)) Execution(transactionId); else { ExecutionFailure(transactionId); tx.executed = false; } } }
当 executeTransaction 内部逻辑被触发,即完成了多签合约的真正调用,如上所述,value 和 data 可以控制多签执行任意逻辑(转移 ether 或 Erc20 代币等)。
Ownbit 实现多签逻辑
Ownbit 实现多签的逻辑和 Gnosis 不同。可以认为 Gnosis 的实现逻辑为线上方式,而 Ownbit 的实现逻辑为线下方式。
1. 相关参与方(满足 _required 个数)线下对即将执行的交易进行签名(所谓线下,即这个过程不需要向以太坊发送交易),生成签名结果(r、v、s):
function generateMessageToSign(address erc20Contract, address destination, uint256 value) private view returns (bytes32) { //the sequence should match generateMultiSigV2 in JS bytes32 message = keccak256(abi.encodePacked(address(this), erc20Contract, destination, value, spendNonce)); return message; }
参与签名的参数有:多签合约地址、Erc20 代币合约地址(对于转移 ether 使用 0x0)、转移的目标地址、金额、控制重放的合约内部 spendNonce。
对以上参数签名,表示参与方同意对指定合约转移指定金额。
2. 任意一方(甚至可以是多签参与方以外的其他人)发送 ETH 交易,调用合约的 spend 或 spendERC20 方法,并将以上签名结果作为参数传入:
function spendERC20(address destination, address erc20contract, uint256 value, uint8[] vs, bytes32[] rs, bytes32[] ss) external { require(destination != address(this), "Not allow sending to yourself"); //transfer erc20 token //uint256 tokenValue = Erc20(erc20contract).balanceOf(address(this)); require(value > 0, "Erc20 spend value invalid"); require(_validSignature(erc20contract, destination, value, vs, rs, ss), "invalid signatures"); spendNonce = spendNonce + 1; // transfer tokens from this contract to the destination address Erc20(erc20contract).transfer(destination, value); emit SpentERC20(erc20contract, destination, value); }
_validSignature 将对签名的有效性进行验证。验证通过的情况下,相关转币逻辑即被执行。
以上便完成了 Ownbit 多签合约的调用。Ownbit 将不同目的分解到不同的方法中,例如:spend 进行 ether 转移,spendERC20 进行 Erc20 代币转移,spendAny 进行任意功能的调用。
两种方式的优缺点
以上两种实现 ETH 多签的不同方式,具有很好的代表性。这也是目前实现 ETH 多签最常用的两种手段。
Gnosis 方式的优点:
- 采用发送交易来表示参与方同意某个花费或调用,避免了复杂的签名计算;
- 全程线上,具有更好的审计性(参与方的 Reject 的态度也保留在区块链上);
Gnosis 方式的主要缺点:
- 每个参与方都需向线上发送交易,多次花费手续费,不经济;
- 每个参与方所花费的手续费不均等,使 confirm 人数刚好等于 _required 的交易将花费更大的手续费以执行 executeTransaction 内部逻辑;
- 交易逻辑隐藏在 data 里,可欺骗性大;
Gnosis 方法的优点正是 Ownbit 方法的缺点,Gnosis 方法的缺点也是 Ownbit 方法的优点。总体而言,Ownbit 的方法因为其经济性,使用得更多。
结语
Ownbit 和 Gnosis 代表了两种不同实现 ETH 多签的方式,了解它们的原理对理解 ETH 多签有非常大的帮助。
多签资产的安全就在这两三百行代码之间,因此读懂并理解它们是开发和使用 ETH 多签必要的技能,也是对智能合约编程能力的一个提升!
来源:
来源:中币网 https://www.zhongbi.net/news/blocknews/161826.html 声明:登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。 此文如侵犯到您的合法权益,请联系我们3111859717@qq.com,我们将第一时间处理。