avatar
原创2022/06/02

多签钱包的原理

AI 总结

系统介绍多签钱包(Multisig Wallet)的设计原理与 m-n 签名模型,深入解析链上多签合约的实现方式,包括提交交易、多人签名确认、执行交易的完整流程,并结合 Gnosis Safe 实际案例说明多签钱包在 DAO 资金管理与合约权限控制中的安全价值。

多签钱包简介

多签钱包(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;
  }
}
 
点击播放