多签钱包(Multi-Signature Wallet)是一种需要多个私钥共同授权才能执行交易的安全机制。它在资产管理、企业治理和 DAO 组织中广泛应用。
1. 多签钱包的核心概念
solidity/* 多签钱包原理: - 设置多个所有者(Owners) - 设定确认阈值(Threshold):需要多少个签名才能执行交易 - 例如:3/5 多签表示需要 5 个所有者中的 3 个确认 应用场景: - 企业资产管理 - DAO 财库 - 项目方资金托管 - 冷钱包安全存储 */
2. 基础多签钱包实现
soliditycontract MultiSigWallet { // 事件 event Deposit(address indexed sender, uint256 amount); event SubmitTransaction( address indexed owner, uint256 indexed txIndex, address indexed to, uint256 value, bytes data ); event ConfirmTransaction(address indexed owner, uint256 indexed txIndex); event RevokeConfirmation(address indexed owner, uint256 indexed txIndex); event ExecuteTransaction(address indexed owner, uint256 indexed txIndex); event OwnerAdded(address indexed owner); event OwnerRemoved(address indexed owner); event RequirementChanged(uint256 required); // 状态变量 address[] public owners; // 所有者列表 mapping(address => bool) public isOwner; // 是否是所有者 uint256 public numConfirmationsRequired; // 需要的确认数 struct Transaction { address to; // 目标地址 uint256 value; // 转账金额 bytes data; // 调用数据 bool executed; // 是否已执行 uint256 numConfirmations; // 当前确认数 } Transaction[] public transactions; // 交易索引 => 所有者 => 是否已确认 mapping(uint256 => mapping(address => bool)) public isConfirmed; // 修饰器 modifier onlyOwner() { require(isOwner[msg.sender], "Not owner"); _; } modifier txExists(uint256 _txIndex) { require(_txIndex < transactions.length, "Transaction does not exist"); _; } modifier notExecuted(uint256 _txIndex) { require(!transactions[_txIndex].executed, "Transaction already executed"); _; } modifier notConfirmed(uint256 _txIndex) { require(!isConfirmed[_txIndex][msg.sender], "Transaction already confirmed"); _; } // 构造函数 constructor(address[] memory _owners, uint256 _numConfirmationsRequired) { require(_owners.length > 0, "Owners required"); require( _numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length, "Invalid number of confirmations" ); for (uint i = 0; i < _owners.length; i++) { address owner = _owners[i]; require(owner != address(0), "Invalid owner"); require(!isOwner[owner], "Owner not unique"); isOwner[owner] = true; owners.push(owner); } numConfirmationsRequired = _numConfirmationsRequired; } // 接收 ETH receive() external payable { emit Deposit(msg.sender, msg.value); } // 提交交易 function submitTransaction( address _to, uint256 _value, bytes memory _data ) public onlyOwner { uint256 txIndex = transactions.length; transactions.push(Transaction({ to: _to, value: _value, data: _data, executed: false, numConfirmations: 0 })); emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data); } // 确认交易 function confirmTransaction(uint256 _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) { Transaction storage transaction = transactions[_txIndex]; transaction.numConfirmations += 1; isConfirmed[_txIndex][msg.sender] = true; emit ConfirmTransaction(msg.sender, _txIndex); } // 执行交易 function executeTransaction(uint256 _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) { Transaction storage transaction = transactions[_txIndex]; require( transaction.numConfirmations >= numConfirmationsRequired, "Not enough confirmations" ); transaction.executed = true; (bool success, ) = transaction.to.call{value: transaction.value}( transaction.data ); require(success, "Transaction failed"); emit ExecuteTransaction(msg.sender, _txIndex); } // 撤销确认 function revokeConfirmation(uint256 _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) { require(isConfirmed[_txIndex][msg.sender], "Transaction not confirmed"); Transaction storage transaction = transactions[_txIndex]; transaction.numConfirmations -= 1; isConfirmed[_txIndex][msg.sender] = false; emit RevokeConfirmation(msg.sender, _txIndex); } // 查询函数 function getOwners() public view returns (address[] memory) { return owners; } function getTransactionCount() public view returns (uint256) { return transactions.length; } function getTransaction(uint256 _txIndex) public view returns ( address to, uint256 value, bytes memory data, bool executed, uint256 numConfirmations ) { Transaction storage transaction = transactions[_txIndex]; return ( transaction.to, transaction.value, transaction.data, transaction.executed, transaction.numConfirmations ); } }
3. 高级多签钱包(支持动态管理)
soliditycontract AdvancedMultiSigWallet { // 事件 event Deposit(address indexed sender, uint256 amount, uint256 balance); event TransactionSubmitted( uint256 indexed txId, address indexed proposer, address indexed to, uint256 value, bytes data ); event TransactionConfirmed(uint256 indexed txId, address indexed owner); event TransactionRevoked(uint256 indexed txId, address indexed owner); event TransactionExecuted(uint256 indexed txId); event OwnerAdded(address indexed owner); event OwnerRemoved(address indexed owner); event RequirementChanged(uint256 oldRequired, uint256 newRequired); event PauseStatusChanged(bool paused); // 交易类型枚举 enum TransactionType { Transfer, // 转账 ContractCall, // 合约调用 AddOwner, // 添加所有者 RemoveOwner, // 移除所有者 ChangeRequirement // 修改阈值 } struct Transaction { TransactionType txType; address to; uint256 value; bytes data; bool executed; uint256 confirmations; uint256 submissionTime; uint256 executionTime; } // 状态变量 address[] public owners; mapping(address => bool) public isOwner; mapping(address => uint256) public ownerIndex; uint256 public required; // 需要的确认数 Transaction[] public transactions; mapping(uint256 => mapping(address => bool)) public confirmations; mapping(uint256 => mapping(address => uint256)) public confirmationTime; bool public paused; uint256 public constant TIMELOCK_DURATION = 24 hours; // 时间锁 uint256 public constant MAX_OWNERS = 50; uint256 public constant MAX_TRANSACTIONS = 1000; // 修饰器 modifier onlyWallet() { require(msg.sender == address(this), "Only wallet"); _; } modifier onlyOwner() { require(isOwner[msg.sender], "Not owner"); _; } modifier ownerExists(address _owner) { require(isOwner[_owner], "Owner does not exist"); _; } modifier ownerDoesNotExist(address _owner) { require(!isOwner[_owner], "Owner exists"); _; } modifier notNull(address _address) { require(_address != address(0), "Null address"); _; } modifier validRequirement(uint256 _ownerCount, uint256 _required) { require( _required <= _ownerCount && _required != 0 && _ownerCount != 0, "Invalid requirement" ); _; } modifier whenNotPaused() { require(!paused, "Wallet is paused"); _; } // 构造函数 constructor( address[] memory _owners, uint256 _required ) validRequirement(_owners.length, _required) { require(_owners.length <= MAX_OWNERS, "Too many owners"); for (uint i = 0; i < _owners.length; i++) { address owner = _owners[i]; require(!isOwner[owner] && owner != address(0), "Invalid owner"); isOwner[owner] = true; ownerIndex[owner] = owners.length; owners.push(owner); } required = _required; } // 接收 ETH receive() external payable { emit Deposit(msg.sender, msg.value, address(this).balance); } // 暂停功能 function setPaused(bool _paused) external onlyWallet { paused = _paused; emit PauseStatusChanged(_paused); } // 提交转账交易 function submitTransfer( address _to, uint256 _value ) external onlyOwner whenNotPaused returns (uint256 txId) { require(_to != address(0), "Invalid recipient"); require(_value > 0 && _value <= address(this).balance, "Invalid value"); txId = addTransaction(TransactionType.Transfer, _to, _value, ""); confirmTransaction(txId); } // 提交合约调用 function submitContractCall( address _to, uint256 _value, bytes memory _data ) external onlyOwner whenNotPaused returns (uint256 txId) { require(_to != address(0), "Invalid contract"); txId = addTransaction(TransactionType.ContractCall, _to, _value, _data); confirmTransaction(txId); } // 提交添加所有者 function submitAddOwner( address _owner ) external onlyOwner ownerDoesNotExist(_owner) notNull(_owner) whenNotPaused returns (uint256 txId) { require(owners.length < MAX_OWNERS, "Max owners reached"); txId = addTransaction( TransactionType.AddOwner, _owner, 0, "" ); confirmTransaction(txId); } // 提交移除所有者 function submitRemoveOwner( address _owner ) external onlyOwner ownerExists(_owner) whenNotPaused returns (uint256 txId) { require(owners.length > required, "Cannot remove owner"); txId = addTransaction( TransactionType.RemoveOwner, _owner, 0, "" ); confirmTransaction(txId); } // 提交修改阈值 function submitChangeRequirement( uint256 _newRequired ) external onlyOwner whenNotPaused returns (uint256 txId) { require(_newRequired > 0 && _newRequired <= owners.length, "Invalid requirement"); txId = addTransaction( TransactionType.ChangeRequirement, address(uint160(_newRequired)), 0, "" ); confirmTransaction(txId); } // 添加交易到列表 function addTransaction( TransactionType _txType, address _to, uint256 _value, bytes memory _data ) internal returns (uint256 txId) { require(transactions.length < MAX_TRANSACTIONS, "Too many transactions"); txId = transactions.length; transactions.push(Transaction({ txType: _txType, to: _to, value: _value, data: _data, executed: false, confirmations: 0, submissionTime: block.timestamp, executionTime: 0 })); emit TransactionSubmitted(txId, msg.sender, _to, _value, _data); } // 确认交易 function confirmTransaction(uint256 _txId) public onlyOwner whenNotPaused { require(_txId < transactions.length, "Invalid txId"); require(!transactions[_txId].executed, "Already executed"); require(!confirmations[_txId][msg.sender], "Already confirmed"); confirmations[_txId][msg.sender] = true; confirmationTime[_txId][msg.sender] = block.timestamp; transactions[_txId].confirmations++; emit TransactionConfirmed(_txId, msg.sender); // 自动执行 if (transactions[_txId].confirmations >= required) { executeTransaction(_txId); } } // 撤销确认 function revokeConfirmation(uint256 _txId) external onlyOwner whenNotPaused { require(_txId < transactions.length, "Invalid txId"); require(!transactions[_txId].executed, "Already executed"); require(confirmations[_txId][msg.sender], "Not confirmed"); confirmations[_txId][msg.sender] = false; transactions[_txId].confirmations--; emit TransactionRevoked(_txId, msg.sender); } // 执行交易 function executeTransaction(uint256 _txId) public onlyOwner whenNotPaused { Transaction storage txn = transactions[_txId]; require(!txn.executed, "Already executed"); require(txn.confirmations >= required, "Not enough confirmations"); require( block.timestamp >= txn.submissionTime + TIMELOCK_DURATION, "Timelock not expired" ); txn.executed = true; txn.executionTime = block.timestamp; // 根据交易类型执行 if (txn.txType == TransactionType.Transfer || txn.txType == TransactionType.ContractCall) { (bool success, ) = txn.to.call{value: txn.value}(txn.data); require(success, "External call failed"); } else if (txn.txType == TransactionType.AddOwner) { addOwnerInternal(txn.to); } else if (txn.txType == TransactionType.RemoveOwner) { removeOwnerInternal(txn.to); } else if (txn.txType == TransactionType.ChangeRequirement) { changeRequirementInternal(uint256(uint160(txn.to))); } emit TransactionExecuted(_txId); } // 内部函数:添加所有者 function addOwnerInternal(address _owner) internal { isOwner[_owner] = true; ownerIndex[_owner] = owners.length; owners.push(_owner); emit OwnerAdded(_owner); } // 内部函数:移除所有者 function removeOwnerInternal(address _owner) internal { uint256 index = ownerIndex[_owner]; address lastOwner = owners[owners.length - 1]; owners[index] = lastOwner; ownerIndex[lastOwner] = index; owners.pop(); delete isOwner[_owner]; delete ownerIndex[_owner]; emit OwnerRemoved(_owner); } // 内部函数:修改阈值 function changeRequirementInternal(uint256 _newRequired) internal { uint256 oldRequired = required; required = _newRequired; emit RequirementChanged(oldRequired, _newRequired); } // 批量确认 function confirmMultiple(uint256[] calldata _txIds) external onlyOwner whenNotPaused { for (uint i = 0; i < _txIds.length; i++) { confirmTransaction(_txIds[i]); } } // 查询函数 function getOwners() external view returns (address[] memory) { return owners; } function getTransactionCount() external view returns (uint256) { return transactions.length; } function getTransaction(uint256 _txId) external view returns (Transaction memory) { return transactions[_txId]; } function isConfirmedBy(uint256 _txId, address _owner) external view returns (bool) { return confirmations[_txId][_owner]; } function getConfirmationCount(uint256 _txId) external view returns (uint256) { return transactions[_txId].confirmations; } function getConfirmations(uint256 _txId) external view returns (address[] memory) { address[] memory confirmed = new address[](owners.length); uint256 count = 0; for (uint i = 0; i < owners.length; i++) { if (confirmations[_txId][owners[i]]) { confirmed[count] = owners[i]; count++; } } // 调整数组大小 assembly { mstore(confirmed, count) } return confirmed; } function getPendingTransactions() external view returns (uint256[] memory) { uint256[] memory pending = new uint256[](transactions.length); uint256 count = 0; for (uint i = 0; i < transactions.length; i++) { if (!transactions[i].executed) { pending[count] = i; count++; } } assembly { mstore(pending, count) } return pending; } function getBalance() external view returns (uint256) { return address(this).balance; } }
4. 使用 Gnosis Safe 模式
solidity// 模拟 Gnosis Safe 的核心功能 contract GnosisSafeStyle { // 常量 bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); bytes32 private constant SAFE_TX_TYPEHASH = keccak256("SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"); // 状态变量 mapping(address => bool) public isOwner; address[] public owners; uint256 public threshold; uint256 public nonce; // 交易哈希 => 是否已执行 mapping(bytes32 => bool) public executed; // 交易哈希 => 签名者 => 是否已签名 mapping(bytes32 => mapping(address => bool)) public signed; // 事件 event SafeSetup(address indexed initiator, address[] owners, uint256 threshold); event ApproveHash(bytes32 indexed approvedHash, address indexed owner); event SignMsg(bytes32 indexed msgHash); event ExecutionSuccess(bytes32 indexed txHash); event ExecutionFailure(bytes32 indexed txHash); // 操作类型 enum Operation { Call, DelegateCall } constructor(address[] memory _owners, uint256 _threshold) { 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; emit SafeSetup(msg.sender, _owners, _threshold); } // 接收 ETH receive() external payable {} // 获取交易哈希 function getTransactionHash( address to, uint256 value, bytes calldata data, Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes32) { bytes32 safeTxHash = keccak256( abi.encode( SAFE_TX_TYPEHASH, to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce ) ); return keccak256( abi.encodePacked( bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash ) ); } // 获取域分隔符 function domainSeparator() public view returns (bytes32) { return keccak256( abi.encode( DOMAIN_SEPARATOR_TYPEHASH, block.chainid, address(this) ) ); } // 执行交易(带签名) function execTransaction( address to, uint256 value, bytes calldata data, Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures ) public payable returns (bool success) { bytes32 txHash = getTransactionHash( to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce ); nonce++; checkSignatures(txHash, signatures); // 执行交易 if (operation == Operation.DelegateCall) { (success, ) = to.delegatecall(data); } else { (success, ) = to.call{value: value}(data); } if (success) { emit ExecutionSuccess(txHash); } else { emit ExecutionFailure(txHash); } return success; } // 检查签名 function checkSignatures(bytes32 dataHash, bytes memory signatures) public view { require(signatures.length >= threshold * 65, "Not enough signatures"); address lastOwner = address(0); for (uint i = 0; i < threshold; i++) { bytes memory signature = slice(signatures, i * 65, 65); address signer = recoverSigner(dataHash, signature); require(isOwner[signer], "Invalid signer"); require(signer > lastOwner, "Signers not ordered"); lastOwner = signer; } } // 恢复签名者 function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) internal pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } // 分割签名 function splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "Invalid signature length"); assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } if (v < 27) { v += 27; } } // 切片函数 function slice(bytes memory data, uint256 start, uint256 length) internal pure returns (bytes memory) { bytes memory result = new bytes(length); for (uint i = 0; i < length; i++) { result[i] = data[start + i]; } return result; } }
5. 多签钱包的安全考虑
soliditycontract MultiSigSecurity { /* 安全最佳实践: 1. 阈值设置 - 推荐 2/3、3/5 等多签配置 - 避免 1/1(单签)或 n/n(需要所有人签名) 2. 所有者管理 - 使用硬件钱包作为所有者 - 定期轮换密钥 - 地理分散存储 3. 交易限制 - 设置每日限额 - 大额交易延迟执行 - 白名单地址 4. 应急机制 - 紧急暂停功能 - 资金回收机制 - 升级能力 */ } // 带安全限制的多签钱包 contract SecureMultiSig { address[] public owners; mapping(address => bool) public isOwner; uint256 public required; // 安全限制 uint256 public dailyLimit; uint256 public spentToday; uint256 public lastDay; mapping(address => bool) public whitelist; bool public whitelistEnabled; modifier onlyOwner() { require(isOwner[msg.sender], "Not owner"); _; } constructor( address[] memory _owners, uint256 _required, uint256 _dailyLimit ) { // ... 初始化代码 dailyLimit = _dailyLimit; } // 检查并更新每日限额 function checkDailyLimit(uint256 _value) internal { if (block.timestamp > lastDay + 1 days) { lastDay = block.timestamp; spentToday = 0; } require( spentToday + _value <= dailyLimit, "Daily limit exceeded" ); spentToday += _value; } // 检查白名单 function checkWhitelist(address _to) internal view { if (whitelistEnabled) { require(whitelist[_to], "Address not whitelisted"); } } // 添加白名单 function addToWhitelist(address _addr) external onlyOwner { whitelist[_addr] = true; } // 移除白名单 function removeFromWhitelist(address _addr) external onlyOwner { whitelist[_addr] = false; } // 设置白名单开关 function setWhitelistEnabled(bool _enabled) external onlyOwner { whitelistEnabled = _enabled; } }
6. 测试和部署建议
javascript// 使用 Hardhat 测试多签钱包 const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("MultiSigWallet", function () { let multiSig; let owners; let required = 2; beforeEach(async function () { [owner1, owner2, owner3, nonOwner] = await ethers.getSigners(); owners = [owner1.address, owner2.address, owner3.address]; const MultiSig = await ethers.getContractFactory("MultiSigWallet"); multiSig = await MultiSig.deploy(owners, required); await multiSig.deployed(); // 向多签钱包发送 ETH await owner1.sendTransaction({ to: multiSig.address, value: ethers.utils.parseEther("10") }); }); it("Should submit and confirm transaction", async function () { const to = nonOwner.address; const value = ethers.utils.parseEther("1"); // 提交交易 await multiSig.connect(owner1).submitTransaction(to, value, "0x"); // 确认交易 await multiSig.connect(owner2).confirmTransaction(0); // 验证交易已执行 const tx = await multiSig.transactions(0); expect(tx.executed).to.be.true; // 验证余额 const balance = await ethers.provider.getBalance(to); expect(balance).to.equal(value); }); it("Should require enough confirmations", async function () { const to = nonOwner.address; const value = ethers.utils.parseEther("1"); await multiSig.connect(owner1).submitTransaction(to, value, "0x"); // 只有一个确认,不应该执行 const tx = await multiSig.transactions(0); expect(tx.executed).to.be.false; }); });
7. 总结
多签钱包是重要的安全工具:
-
核心机制:
- 多个所有者共同管理
- 阈值确认机制
- 交易生命周期管理
-
实现要点:
- 完整的交易流程(提交-确认-执行)
- 权限控制
- 事件记录
- 查询功能
-
安全考虑:
- 合理的阈值设置
- 时间锁机制
- 交易限额
- 应急处理
-
生产建议:
- 使用经过审计的库(如 Gnosis Safe)
- 充分测试
- 考虑使用代理模式升级
- 实施监控和报警