多签钱包简介
多签钱包(Multisig wallet),就是钱包的所有权归多人所有,对钱包的的操作需要多人进行签名授权才能执行。与之对应的就是单签钱包,例如我们平时使用的 metamask
,我们独立拥有着该钱包的所有权。转账时只需要自己同意即可转账成功。
当使用多签钱包时,可以指定 m-n 模式,n 是钱包拥有者的数量,m 是钱包执行操作时需要 m 个人同意即可。
例如 1-2 模式, 钱包的两个拥有者中 ,只要有一个人同意就可以进行操作了。也就意味着这两个人都可以独自发起交易。但在 2-3 模式中,则需要任意两个拥有者同意才能对钱包执行操作。
综上,可以看到多签钱包的一些优点:
-
提高资金的安全性
在单签钱包中,一旦你的私钥泄露了,也就意味着你的资产不再安全。但在多签钱包中,即使你泄露了一个私钥,只要你的钱包模式中 m ≠ 1,也能很大程度上保证资产的安全
-
防止资金挪用
例如有些 Defi 项目方的资金如果使用单签钱包的话,可以很轻易的转移资产。但如果使用多签钱包,那么对资金的挪用就需要大多数人或者社区成员的同意,大大降低了资金被挪用的风险。
实现原理
多签钱包的实现依赖于智能合约,主流的实现方式有以下两种:
-
多个钱包所有者线下对转账信息进行签名,并把各自的签名结果交由任意某一钱包所有者,由该所有者发起交易。(仅需要交易发起者调用智能合约)
-
钱包的任一所有者发起交易,其他所有者各自调用多签钱包合约的方法对交易进行确认,当确认数满足条件时,则该笔交易可以顺利进行,否则无法进行。
Gnosis Safe
正是采用的这种方法。(需要多个钱包所有者都调用智能合约)
第一种实现
多个钱包所有者线下对即将执行的交易进行签名(即这个过 程不需要向以太坊发送交易),生成签名结果(r、v、s)
:
function generateMessageToSign(address erc20Contract, address destination, uint256 value) private view returns (bytes32) {
bytes32 message = keccak256(abi.encodePacked(address(this), erc20Contract, destination, value, spendNonce));
return message;
}
任意一方(甚至可以是多签参与方以外的其他人)发送交易,调用合约方法,并将以上签名结果作为参数传入:
function spendERC20(address destination, address erc20contract, uint256 value, uint8[] vs, bytes32[] rs, bytes32[] ss) external {
// 验证签名信息
require(_validSignature(erc20contract, destination, value, vs, rs, ss), "invalid signatures");
spendNonce = spendNonce + 1;
// 执行交易
Erc20(erc20contract).transfer(destination, value);
}
第二种实现
- 某一所有者调用
submitTransaction
发起交易 - 其他所有者调用
confirmTransaction
对发起的交易进行确认操作 - 当确认数量满足条件时则调用
executeTransaction
执行交易
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract MultiSigWallet {
// 交易结构体
struct Transaction {
address destination;
uint256 value;
bytes data;
bool executed;
}
// 交易ID, 每次添加交易时其值 +1
uint256 public transactionCount;
// 交易信息 交易ID => Transaction
mapping(uint256 => Transaction) public transactions;
// 确认信息 交易ID => (确认地址 => 是否确认)
mapping(uint256 => mapping(address => bool)) public confirmations;
// 记录某地址是否是钱包所有者
mapping(address => bool) public isOwner;
// 钱包的所有者地址列表
address[] public owners;
// 需要确认的所有者地址数量
uint256 public required;
fallback () external payable {}
constructor(address[] memory _owners, uint _required) {
require(_required <= _owners.length, 'required exceed');
for (uint256 i = 0; i < _owners.length; i++) {
if (isOwner[_owners[i]] || _owners[i] == address(0)) revert('exist owner');
isOwner[_owners[i]] = true;
}
// 设置钱包所有者
owners = _owners;
// 设置需要确认的数量
required = _required;
}
// 提交交易申请
// destination 交易的目标地址
// value 交易数量
function submitTransaction(address destination, uint value, bytes calldata data) public returns(uint transactionId){
// 确保余额充足
require(address(this).balance >= value, 'value not enough');
// 添加交易并返回交易ID, 交易ID是自增的,每次成功添加都会自增1
transactionId = addTransaction(destination, value, data);
// 确认交易
confirmTransaction(transactionId);
}
// 添加交易
function addTransaction(address destination, uint value, bytes calldata data) internal returns (uint transactionId) {
transactionId = transactionCount;
// transactions 记录该笔交易
transactions[transactionId] = Transaction({
destination: destination,
value: value,
data: data,
executed: false
});
transactionCount += 1;
}
// 供其他拥有者确认该笔交易,只有确认了才能执行
function confirmTransaction(uint transactionId) public {
// 确保调用者是钱包的拥有者
require(isOwner[msg.sender], 'you are not in owners');
// 确保交易的目标地址不为空
require(transactions[transactionId].destination != address(0), 'transaction destination is none');
// 确认交易还未被确认
require(!confirmations[transactionId][msg.sender], 'you has confirmed');
// 调用者确认了该笔交易
confirmations[transactionId][msg.sender] = true;
// 执行交易
executeTransaction(transactionId);
}
// 执行交易
function executeTransaction(uint transactionId) public {
// 确保交易未被执行
require(!transactions[transactionId].executed, 'transactionId has been executed');
// 已确认足够的数量
if (isConfirmed(transactionId)) {
Transaction memory trans = transactions[transactionId];
trans.executed = true;
// 执行交易
(bool sended, ) = trans.destination.call{value: trans.value}(trans.data);
// 交易失败
if (!sended) {
trans.executed = false;
}
}
}
// 判断 owners 中确认的数量是否满足 required
function isConfirmed(uint transactionId) public view returns(bool) {
uint count = 0;
for (uint i = 0; i < owners.length; i++) {
if (confirmations[transactionId][owners[i]]) count += 1;
if (count == required) return true;
}
return false;
}
}