Solidity 代币和金融工具 2| 多签钱包(Multi Sig Wallet)

多签钱包(Multi-Signature Wallet)是一种需要多个私钥持有者共同批准才能执行交易的智能合约。它广泛用于提高资金安全性,避免单点故障(如单个私钥泄露)。在 Solidity 中实现多签钱包,核心思路是:预设一组所有者(owners)和一个阈值(threshold),只有当足够多的所有者确认一笔交易后,该交易才能被执行。

1. 基本原理

  • 所有者(Owners): 有权批准交易的地址列表,通常不可更改(或通过多签本身修改)。
  • 阈值(Threshold): 执行交易所需的最少确认数,通常≤ 所有者数量。
  • 交易(Transaction): 包括i目标地址、发送的以太币数量、调用数据(data)以及一个唯一标识(如交易ID或nonce)。
  • 确认(Confirmations): 所有者调用合约方法对某笔交易表示同意。当确认数达到阈值时,任何人均可触发执行。

2. 核心功能与实现要点

2.1 数据结构

  • transactions 数组存储所有待处理或已执行的交易(包含交易目标地址、发送金额、交易数据和执行状态)。
  • isConfirmed 记录每个地址对每笔交易的确认状态(记录每个地址对每笔交易的确认状态)。
  • numConfirmations 可动态计数,也可通过遍历计算(但遍历消耗gas)。
solidity 复制代码
address[] public owners;
mapping(address => bool) public isOwner;
uint public threshold;

struct Transaction {
    address to;
    uint value;
    bytes data;
    bool executed;
    uint numConfirmations;
}
mapping(uint => mapping(address => bool)) public isConfirmed;
Transaction[] public transactions;

2.2 提交交易

  • 通常只允许所有者提交交易,但也可开放给任何人(需防止垃圾交易)。
solidity 复制代码
function submitTransaction(address _to, uint _value, bytes memory _data) public onlyOwner returns (uint txIndex) {
    txIndex = transactions.length;
    transactions.push(Transaction({
        to: _to,
        value: _value,
        data: _data,
        executed: false,
        numConfirmations: 0
    }));
    emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);
}

2.3 确认交易

solidity 复制代码
function confirmTransaction(uint _txIndex) public onlyOwner {
    require(_txIndex < transactions.length, "tx does not exist");
    Transaction storage txn = transactions[_txIndex];
    require(!txn.executed, "tx already executed");
    require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");

    isConfirmed[_txIndex][msg.sender] = true;
    txn.numConfirmations += 1;

    emit ConfirmTransaction(msg.sender, _txIndex);
}

2.4 执行交易

  • 调用 call 可同时发送 ETH 并调用目标合约函数。
  • 注意重入攻击:在状态更改后再进行外部调用,并使用 executed 标志防止重复执行。
solidity 复制代码
function executeTransaction(uint _txIndex) public {
    require(_txIndex < transactions.length, "tx does not exist");
    Transaction storage txn = transactions[_txIndex];
    require(!txn.executed, "tx already executed");
    require(txn.numConfirmations >= threshold, "not enough confirmations");

    txn.executed = true;
    (bool success, ) = txn.to.call{value: txn.value}(txn.data);
    require(success, "tx failed");
    emit ExecuteTransaction(msg.sender, _txIndex);
}

2.5 撤销确认(可选)

solidity 复制代码
function revokeConfirmation(uint _txIndex) public onlyOwner {
    require(_txIndex < transactions.length, "tx does not exist");
    Transaction storage txn = transactions[_txIndex];
    require(!txn.executed, "tx already executed");
    require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");

    isConfirmed[_txIndex][msg.sender] = false;
    txn.numConfirmations -= 1;

    emit RevokeConfirmation(msg.sender, _txIndex);
}

2.6 修改所有者与阈值(高级)

可以通过多签本身来实现,即提交一笔调用 addOwnerremoveOwnerchangeThreshold 的交易,由现有所有者批准后执行。

3. 安全注意事项

  • 重入攻击 :执行交易时使用"检查-生效-交互"模式,先标记 executed = true 再调用外部地址。
  • Gas 限制:交易数组可能很大,避免遍历所有确认;可使用计数器优化。
  • 所有权管理:初始所有者需在构造函数中设定,后续修改需谨慎。
  • 拒绝服务:恶意所有者可无限提交垃圾交易,但可通过限制提交权限或收取费用缓解。
  • 使用事件:所有关键操作(提交、确认、执行、撤销)都应记录事件,便于链下监听。

4. 简化示例代码

以下是一个极简多签钱包的核心合约(省略了事件、修饰器等):

solidity 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MultiSigWallet {
    address[] public owners;
    mapping(address => bool) public isOwner;
    uint public threshold;

    struct Transaction {
        address to;
        uint value;
        bytes data;
        bool executed;
        uint confirmCount;
    }
    Transaction[] public transactions;
    mapping(uint => mapping(address => bool)) public confirmed;

    modifier onlyOwner() {
        require(isOwner[msg.sender], "not owner");
        _;
    }

    constructor(address[] memory _owners, uint _threshold) {
        require(_owners.length > 0, "owners required");
        require(_threshold > 0 && _threshold <= _owners.length, "invalid threshold");
        for (uint i = 0; i < _owners.length; i++) {
            address owner = _owners[i];
            require(owner != address(0) && !isOwner[owner], "invalid owner");
            isOwner[owner] = true;
            owners.push(owner);
        }
        threshold = _threshold;
    }

    function submitTransaction(address _to, uint _value, bytes memory _data) public onlyOwner returns (uint txIndex) {
        txIndex = transactions.length;
        transactions.push(Transaction({
            to: _to,
            value: _value,
            data: _data,
            executed: false,
            confirmCount: 0
        }));
    }

    function confirmTransaction(uint _txIndex) public onlyOwner {
        require(_txIndex < transactions.length, "invalid tx");
        Transaction storage txn = transactions[_txIndex];
        require(!txn.executed, "executed");
        require(!confirmed[_txIndex][msg.sender], "already confirmed");

        confirmed[_txIndex][msg.sender] = true;
        txn.confirmCount += 1;
    }

    function executeTransaction(uint _txIndex) public {
        require(_txIndex < transactions.length, "invalid tx");
        Transaction storage txn = transactions[_txIndex];
        require(!txn.executed, "executed");
        require(txn.confirmCount >= threshold, "not enough confirmations");

        txn.executed = true;
        (bool success, ) = txn.to.call{value: txn.value}(txn.data);
        require(success, "tx failed");
    }

    // 接收 ETH 的函数
    receive() external payable {}
}

5. 现有成熟方案

  • Gnosis Safe:目前最流行的多签钱包,支持模块化、多种链、合约交互,并有完善的UI。其合约经过严格审计,推荐直接使用。
  • 其他实现 :如 OpenZeppelin 也提供 MultisigWallet 合约示例,但已较少维护。

6. 总结

实现多签钱包的核心是管理交易生命周期:提交 → 多方确认 → 达到阈值后执行。关键点在于确保只有所有者可操作,防止重入,并合理设计数据结构以节省 Gas。对于生产环境,建议直接采用经过实战检验的方案(如 Gnosis Safe),避免自行实现时的安全风险。

相关推荐
Web3VentureView1 天前
Web4的入口,即将打开 | SYNBO CLUB移动端亟待上线
大数据·人工智能·区块链·媒体·加密货币
庭前云落1 天前
Solidity 智能合约进阶 1| 安全性和验证 Keccak256 哈希函数 (Keccak256 Hash Function)
区块链·智能合约·哈希算法
电报号dapp1191 天前
公链浏览器:区块链世界的“数据透视镜”与哈希查询的艺术
算法·区块链·智能合约·哈希算法
Lao乾妈官方认证唯一女友:D2 天前
通过plasmo的wallet扩展添加新钱包
javascript·web3·区块链
cipher2 天前
Web3全栈学习与实战项目
前端·后端·区块链
庭前云落2 天前
Solidity 金融和支付 3| 发送以太币 (Send ETH)
金融·区块链
电报号dapp1192 天前
以交易所为基,构建下一代DApp生态
游戏·去中心化·区块链·智能合约
庭前云落2 天前
从零开始的OpenZeppelin学习 2| ERC20-permit、erc20pausable
学习·区块链
庭前云落2 天前
Solidity 智能合约进阶 3| 安全性和验证 访问控制 (Access Control)
区块链·智能合约