今天我们要聊一个在区块链开发中超级重要且实用的主题——多签钱包(Multi-SignatureWallet)。如果你玩过DeFi、DAO或者团队管理的加密资产,肯定听说过多签钱包。它就像一个“多人保险箱”,需要多个签名者同意才能动用资金,极大地提高了安全性和去中心化特性。多签钱包是什么?为什么需
今天我们要聊一个在区块链开发中超级重要且实用的主题——多签钱包(Multi-Signature Wallet)。如果你玩过DeFi、DAO或者团队管理的加密资产,肯定听说过多签钱包。它就像一个“多人保险箱”,需要多个签名者同意才能动用资金,极大地提高了安全性和去中心化特性。
多签钱包(Multi-Signature Wallet)是一种智能合约,要求多方(而不是单一地址)共同签名才能执行关键操作,比如转账、修改配置等。它的核心思想是“分散信任”,避免单点故障。想象一下,一个DAO的资金由一个私钥控制,如果这个私钥丢了或被盗,整个项目就GG了!多签钱包通过要求M-of-N签名(比如3-of-5,5个签名者中至少3个同意)来降低风险。
在Solidity中,多签钱包通常用于:
多签钱包的核心功能包括:
接下来,我们会实现一个多签钱包合约,逐步分析每个功能。
为了让大家快速上手,我们来写一个多签钱包合约MultiSigWallet
,功能包括:
先来看合约的框架,包含核心状态变量和初始化逻辑:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MultiSigWallet {
address[] public owners;
uint public required;
mapping(address => bool) public isOwner;
uint public transactionCount;
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint confirmations;
mapping(address => bool) confirmedBy;
}
mapping(uint => Transaction) public transactions;
event TransactionSubmitted(uint indexed transactionId, address indexed sender, address to, uint value, bytes data);
event TransactionConfirmed(uint indexed transactionId, address indexed owner);
event TransactionExecuted(uint indexed transactionId, bool success);
event OwnerAdded(address indexed newOwner);
event OwnerRemoved(address indexed oldOwner);
event RequiredUpdated(uint newRequired);
constructor(address[] memory _owners, uint _required) {
require(_owners.length > 0, "At least one owner required");
require(_required > 0 && _required <= _owners.length, "Invalid required number");
for (uint i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner address");
require(!isOwner[owner], "Duplicate owner");
isOwner[owner] = true;
owners.push(owner);
}
required = _required;
}
}
代码分析:
owners
:存储签名者地址的动态数组。required
:需要的签名数(M-of-N)。isOwner
:映射检查地址是否为签名者,方便快速验证。transactionCount
:跟踪交易提案的总数,生成唯一ID。Transaction
:记录每个提案的细节,包括目标地址(to
)、金额(value
)、数据(data
)、是否执行(executed
)、确认数(confirmations
)和确认者(confirmedBy
)。transactions
:用交易ID映射到Transaction
结构体,存储所有提案。required
合法,签名者地址有效且不重复。owners
和isOwner
。这个框架为多签钱包打下了基础,接下来实现核心功能。
签名者可以提交交易提案(比如转ETH给某个地址)。我们写一个submitTransaction
函数:
modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner");
_;
}
function submitTransaction(address _to, uint _value, bytes memory _data) external onlyOwner {
uint transactionId = transactionCount++;
Transaction storage transaction = transactions[transactionId];
transaction.to = _to;
transaction.value = _value;
transaction.data = _data;
transaction.executed = false;
transaction.confirmations = 0;
emit TransactionSubmitted(transactionId, msg.sender, _to, _value, _data);
}
代码分析:
onlyOwner
确保只有签名者能调用。transactionCount++
生成唯一ID。transactions
映射中初始化Transaction
结构体,记录目标地址、金额和数据(data
支持调用其他合约)。TransactionSubmitted
,记录提案细节。data
参数允许提案调用其他合约的函数(比如转ERC20代币)。签名者通过confirmTransaction
投票支持提案:
function confirmTransaction(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(!transaction.confirmedBy[msg.sender], "Already confirmed");
transaction.confirmedBy[msg.sender] = true;
transaction.confirmations += 1;
emit TransactionConfirmed(_transactionId, msg.sender);
}
代码分析:
transactions[_transactionId]
会报错如果ID无效)。!transaction.executed
)。!transaction.confirmedBy[msg.sender]
)。TransactionConfirmed
,记录确认者。当确认数达到required
时,执行交易:
function executeTransaction(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmations >= required, "Not enough confirmations");
transaction.executed = true;
(bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
require(success, "Transaction execution failed");
emit TransactionExecuted(_transactionId, success);
}
代码分析:
confirmations >= required
)。call
低级调用执行交易,支持ETH转账或调用其他合约。success
确保调用成功,失败则回滚。TransactionExecuted
,记录结果。注意:call
比transfer
更灵活(支持动态Gas和调用合约),但需小心重入攻击(我们稍后优化)。
为了灵活性,允许签名者撤销确认(在交易未执行前):
function revokeConfirmation(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmedBy[msg.sender], "Not confirmed by sender");
transaction.confirmedBy[msg.sender] = false;
transaction.confirmations -= 1;
}
分析:
System: 用例:灵活性,允许签名者在执行前改变主意。
整合以上代码,得到基础版多签钱包:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MultiSigWallet {
address[] public owners;
uint public required;
mapping(address => bool) public isOwner;
uint public transactionCount;
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint confirmations;
mapping(address => bool) confirmedBy;
}
mapping(uint => Transaction) public transactions;
event TransactionSubmitted(uint indexed transactionId, address indexed sender, address to, uint value, bytes data);
event TransactionConfirmed(uint indexed transactionId, address indexed owner);
event Transaction executed(uint indexed transactionId, bool success);
event OwnerAdded(address indexed newOwner);
event OwnerRemoved(address indexed oldOwner);
event RequiredUpdated(uint newRequired);
modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner");
_;
}
constructor(address[] memory _owners, uint _required) {
require(_owners.length > 0, "At least one owner required");
require(_required > 0 && _required <= _owners.length, "Invalid required number");
for (uint i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner address");
require(!isOwner[owner], "Duplicate owner");
isOwner[owner] = true;
owners.push(owner);
}
required = _required;
}
function submitTransaction(address _to, uint _value, bytes memory _data) external onlyOwner {
uint transactionId = transactionCount++;
Transaction storage transaction = transactions[transactionId];
transaction.to = _to;
transaction.value = _value;
transaction.data = _data;
transaction.executed = false;
transaction.confirmations = 0;
emit TransactionSubmitted(transactionId, msg.sender, _to, _value, _data);
}
function confirmTransaction(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(!transaction.confirmedBy[msg.sender], "Already confirmed");
transaction.confirmedBy[msg.sender] = true;
transaction.confirmations += 1;
emit TransactionConfirmed(_transactionId, msg.sender);
}
function executeTransaction(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmations >= required, "Not enough confirmations");
transaction.executed = true;
(bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
require(success, "Transaction execution failed");
emit TransactionExecuted(_transactionId, success);
}
function revokeConfirmation(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmedBy[msg.sender], "Not confirmed by sender");
transaction.confirmedBy[msg.sender] = false;
transaction.confirmations -= 1;
}
}
这个版本已经功能完整,但还有优化空间,接下来我们会加入高级功能和安全措施。
基础版已经能用,但离生产环境还差一些。我们来优化以下方面:
允许动态添加/移除签名者和调整签名数(需要多签确认):
function submitOwnerAddition(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "Invalid owner address");
require(!isOwner[_newOwner], "Already an owner");
uint transactionId = transactionCount++;
Transaction storage transaction = transactions[transactionId];
transaction.to = address(this);
transaction.value = 0;
transaction.data = abi.encodeWithSignature("addOwner(address)", _newOwner);
transaction.executed = false;
transaction.confirmations = 0;
emit TransactionSubmitted(transactionId, msg.sender, address(this), 0, transaction.data);
}
function addOwner(address _newOwner) external {
require(isOwner[msg.sender], "Not an owner");
require(!isOwner[_newOwner], "Already an owner");
isOwner[_newOwner] = true;
owners.push(_newOwner);
emit OwnerAdded(_newOwner);
}
分析:
required
可以类似实现(略)。为提案添加超时机制,过期后自动失效:
uint public constant TRANSACTION_TIMEOUT = 7 days;
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint confirmations;
uint timestamp;
mapping(address => bool) confirmedBy;
}
function submitTransaction(address _to, uint _value, bytes memory _data) external onlyOwner {
uint transactionId = transactionCount++;
Transaction storage transaction = transactions[transactionId];
transaction.to = _to;
transaction.value = _value;
transaction.data = _data;
transaction.executed = false;
transaction.confirmations = 0;
transaction.timestamp = block.timestamp;
emit TransactionSubmitted(transactionId, msg.sender, _to, _value, _data);
}
function confirmTransaction(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(block.timestamp <= transaction.timestamp + TRANSACTION_TIMEOUT, "Transaction timed out");
require(!transaction.confirmedBy[msg.sender], "Already confirmed");
transaction.confirmedBy[msg.sender] = true;
transaction.confirmations += 1;
emit TransactionConfirmed(_transactionId, msg.sender);
}
分析:
TRANSACTION_TIMEOUT
)。transaction.timestamp
记录提交时间。executeTransaction
的call
可能触发目标合约的回调,需确保状态更新在调用前完成(已实现)。onlyOwner
修饰符。添加查询函数,方便查看提案和确认状态:
function getTransaction(uint _transactionId) external view returns (
address to,
uint value,
bytes memory data,
bool executed,
uint confirmations,
uint timestamp
) {
Transaction storage transaction = transactions[_transactionId];
return (
transaction.to,
transaction.value,
transaction.data,
transaction.executed,
transaction.confirmations,
transaction.timestamp
);
}
function hasConfirmed(uint _transactionId, address _owner) external view returns (bool) {
return transactions[_transactionId].confirmedBy[_owner];
}
分析:
getTransaction
返回提案详情,方便前端显示。hasConfirmed
检查某人是否确认过。整合优化后的代码(部分省略重复功能):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MultiSigWallet {
address[] public owners;
uint public required;
mapping(address => bool) public isOwner;
uint public transactionCount;
uint public constant TRANSACTION_TIMEOUT = 7 days;
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint confirmations;
uint timestamp;
mapping(address => bool) confirmedBy;
}
mapping(uint => Transaction) public transactions;
event TransactionSubmitted(uint indexed transactionId, address indexed sender, address to, uint value, bytes data);
event TransactionConfirmed(uint indexed transactionId, address indexed owner);
event TransactionExecuted(uint indexed transactionId, bool success);
event OwnerAdded(address indexed newOwner);
modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner");
_;
}
constructor(address[] memory _owners, uint _required) {
require(_owners.length > 0, "At least one owner required");
require(_required > 0 && _required <= _owners.length, "Invalid required number");
for (uint i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner address");
require(!isOwner[owner], "Duplicate owner");
isOwner[owner] = true;
owners.push(owner);
}
required = _required;
}
function submitTransaction(address _to, uint _value, bytes memory _data) external onlyOwner {
uint transactionId = transactionCount++;
Transaction storage transaction = transactions[transactionId];
transaction.to = _to;
transaction.value = _value;
transaction.data = _data;
transaction.executed = false;
transaction.confirmations = 0;
transaction.timestamp = block.timestamp;
emit TransactionSubmitted(transactionId, msg.sender, _to, _value, _data);
}
function confirmTransaction(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(block.timestamp <= transaction.timestamp + TRANSACTION_TIMEOUT, "Transaction timed out");
require(!transaction.confirmedBy[msg.sender], "Already confirmed");
transaction.confirmedBy[msg.sender] = true;
transaction.confirmations += 1;
emit TransactionConfirmed(_transactionId, msg.sender);
}
function executeTransaction(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmations >= required, "Not enough confirmations");
transaction.executed = true;
(bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
require(success, "Transaction execution failed");
emit TransactionExecuted(_transactionId, success);
}
function revokeConfirmation(uint _transactionId) external onlyOwner {
Transaction storage transaction = transactions[_transactionId];
require(!transaction.executed, "Transaction already executed");
require(transaction.confirmedBy[msg.sender], "Not confirmed by sender");
transaction.confirmedBy[msg.sender] = false;
transaction.confirmations -= 1;
}
function submitOwnerAddition(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "Invalid owner address");
require(!isOwner[_newOwner], "Already an owner");
uint transactionId = transactionCount++;
Transaction storage transaction = transactions[transactionId];
transaction.to = address(this);
transaction.value = 0;
transaction.data = abi.encodeWithSignature("addOwner(address)", _newOwner);
transaction.executed = false;
transaction.confirmations = 0;
transaction.timestamp = block.timestamp;
emit TransactionSubmitted(transactionId, msg.sender, address(this), 0, transaction.data);
}
function addOwner(address _newOwner) external {
require(isOwner[msg.sender], "Not an owner");
require(!isOwner[_newOwner], "Already an owner");
isOwner[_newOwner] = true;
owners.push(_newOwner);
emit OwnerAdded(_newOwner);
}
function getTransaction(uint _transactionId) external view returns (
address to,
uint value,
bytes memory data,
bool executed,
uint confirmations,
uint timestamp
) {
Transaction storage transaction = transactions[_transactionId];
return (
transaction.to,
transaction.value,
transaction.data,
transaction.executed,
transaction.confirmations,
transaction.timestamp
);
}
function hasConfirmed(uint _transactionId, address _owner) external view returns (bool) {
return transactions[_transactionId].confirmedBy[_owner];
}
receive() external payable {}
}
分析:
多签钱包不仅能管理ETH,还能管理ERC20代币。我们通过data
字段调用ERC20的transfer
函数:
function submitERC20Transfer(address _token, address _to, uint _amount) external onlyOwner {
uint transactionId = transactionCount++;
Transaction storage transaction = transactions[transactionId];
transaction.to = _token;
transaction.value = 0;
transaction.data = abi.encodeWithSignature("transfer(address,uint256)", _to, _amount);
transaction.executed = false;
transaction.confirmations = 0;
transaction.timestamp = block.timestamp;
emit TransactionSubmitted(transactionId, msg.sender, _token, 0, transaction.data);
}
分析:
abi.encodeWithSignature
生成transfer
调用的数据。data
字段支持任何ERC20代币的转账。call
未正确处理回调风险(状态先更新)。owners
数量合理(比如3-5个),避免Gas成本过高。多签钱包在以下场景广泛应用:
以Gnosis Safe为例,它是多签钱包的标杆,支持复杂权限、模块化扩展和用户友好的前端。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!