150 行代码创建一个多签钱包,智能合约实战项目
什么是多签钱包?就是需要多个人同时签名才能操作的钱包。大家用过钱包都知道,在你进行买卖或者转账操作的时候,正常的钱包只需要一次签名就可以了,但是多签钱包需要多个人授权签名,才能进行操作,这样安全性就大大增加。
更为重要的是,如果是普通钱包,可能私钥丢失了就没办法了。但是对于多签钱包来说,如果其中一人失了私钥,其他人仍然可以访问钱包和资金。所以 V 神曾说过,多签钱包要比硬件钱包更加安全。
不过很多人不太了解的是,多签钱包的原理是什么?如何开发一个多签钱包合约呢?今天就给大家解答一下这个问题,同时把相关的合约代码也分享出来。
一、创建多签钱包合约
1、设置多签人和门槛(链上):部署多签合约时,我们需要初始化多签人列表和执行门槛(至少 n 个多签人签名授权后,交易才能执行)。Gnosis Safe 多签钱包支持增加/删除多签人以及改变执行门槛,但在咱们的极简版中不考虑这一功能。
2、创建交易(链下):一笔待授权的交易包含以下内容
to:目标合约。
value:交易发送的以太坊数量。
data:calldata,包含调用函数的选择器和参数。
nonce:初始为 0,随着多签合约每笔成功执行的交易递增的值,可以防止签名重放攻击。
chainid:链 id,防止不同链的签名重放攻击。
3、收集多签签名(链下):将上一步的交易 ABI 编码并计算哈希,得到交易哈希,然后让多签人签名,并拼接到一起的到打包签名。对 ABI 编码和哈希不了解的,可以看 WTF Solidity 极简教程第 27 讲和第 28 讲。
交易哈希: 0xc1b055cf8e78338db21407b425114a2e258b0318879327945b661bfdea570e66
多签人 A 签名: 0xd6a56c718fc16f283512f90e16f2e62f888780a712d15e884e300c51e5b100de2f014ad71bcb6d97946ef0d31346b3b71eb688831abedaf41b33486b416129031c
多签人 B 签名: 0x2184f70a17f14426865bda8ebe391508b8e3984d16ce6d90905ae8beae7d75fd435a7e51d837881d820414ebaf0ff16074204c75b33d66928edcf8dd398249861b
打包签名:0xd6a56c718fc16f283512f90e16f2e62f888780a712d15e884e300c51e5b100de2f014ad71bcb6d97946ef0d31346b3b71eb688831abedaf41b33486b416129031c2184f70a17f14426865bda8ebe391508b8e3984d16ce6d90905ae8beae7d75fd435a7e51d837881d820414ebaf0ff16074204c75b33d66928edcf8dd398249861b
4、调用多签合约的执行函数,验证签名并执行交易(链上)。、
二、事件分析
MultisigWallet 合约有 2 个事件,ExecutionSuccess 和 ExecutionFailure,分别在交易成功和失败时释放,参数为交易哈希。
三、状态变量
MultisigWallet 合约有 5 个状态变量:
owners:多签持有人数组
isOwner:address => bool 的映射,记录一个地址是否为多签持有人。
ownerCount:多签持有人数量
threshold:多签执行门槛,交易至少有 n 个多签人签名才能被执行。
nonce:初始为 0,随着多签合约每笔成功执行的交易递增的值,可以防止签名重放攻击。
四、函数编写
MultisigWallet 合约有 6 个函数:
1、构造函数:调用_setupOwners(),初始化和多签持有人和执行门槛相关的变量。
2、_setupOwners():在合约部署时被构造函数调用,初始化 owners,isOwner,ownerCount,threshold 状态变量。传入的参数中,执行门槛需大于等于 1 且小于等于多签人数;多签地址不能为 0 地址且不能重复。
3、execTransaction():在收集足够的多签签名后,验证签名并执行交易。传入的参数为目标地址 to,发送的以太坊数额 value,数据 data,以及打包签名 signatures。打包签名就是将收集的多签人对交易哈希的签名,按多签持有人地址从小到大顺序,打包到一个[bytes]数据中。这一步调用了 encodeTransactionData()编码交易,调用了 checkSignatures()检验签名是否有效、数量是否达到执行门槛。
4、checkSignatures():检查签名和交易数据的哈希是否对应,数量是否达到门槛,若否,交易会 revert。单个签名长度为 65 字节,因此打包签名的长度要长于 threshold * 65。调用了 signatureSplit()分离出单个签名。这个函数的大致思路:
用 ecdsa 获取签名地址.
利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增)
利用 isOwner[currentOwner]确定签名者为多签持有人。
5、signatureSplit():将单个签名从打包的签名分离出来,
参数分别为打包签名 signatures 和要读取的签名位置 pos。利用了内联汇编,将签名的 r,s,和 v 三个值分离出来。
6、encodeTransactionData():将交易数据打包并计算哈希,利用了 abi.encode()和 keccak256()函数。这个函数可以计算出一个交易的哈希,然后在链下让多签人签名并收集,再调用 execTransaction()函数执行。
/// @dev 编码交易数据 /// @param to 目标合约地址 /// @param value msg.value,支付的以太坊 /// @param data calldata /// @param _nonce 交易的nonce. /// @param chainid 链id /// @return 交易哈希bytes. function encodeTransactionData( address to, uint256 value, bytes memory data, uint256 _nonce, uint256 chainid ) public pure returns (bytes32) { bytes32 safeTxHash = keccak256( abi.encode( to, value, keccak256(data), _nonce, chainid ) ); return safeTxHash; }
综上,就是此次极简版多签钱包合约的编写过程,仅用了不到 150 行代码,就完成了。其实多签钱包现在更多的应用在 DAO 组织里面,方便管理社区的基金。而 Gnosis Safe 多签钱包是以太坊最流行的多签钱包,管理近 400 亿美元资产,合约经过审计和实战测试,支持多链(以太坊,BSC,Polygon 等)。大家如果有兴趣,也可以去使用一下。
大家如果有兴趣,也可以找我开发多签钱包,wx:btc6540,tg:@btc6540
评论