前言借用维塔利克·布特林的观点:大家对硬件钱包高估了,相对于硬件钱包,多签钱包更加安全.本文快速实现一个简洁版的多签钱包合约。多签钱包定义:一种需要多个私钥签名才能完成交易的加密钱包,需要多个授权方共同签名才能执行交易。这种设计大大提高了钱包的安全性,降低了单点故障和私钥被盗的风险;工作
借用维塔利克·布特林的观点:大家对硬件钱包高估了,相对于硬件钱包,多签钱包更加安全.本文快速实现一个简洁版的多签钱包合约。
多签钱包
定义:一种需要多个私钥签名才能完成交易的加密钱包,需要多个授权方共同签名才能执行交易。这种设计大大提高了钱包的安全性,降低了单点故障和私钥被盗的风险;
工作原理
- 设置多签钱包:创建多签钱包时,用户需要指定多个参与者(例如3人)和最低签名数量(例如2个)。这种配置通常被表示为“m-of-n”,其中m是最少签名数量,n是总参与者数量。
- 生成公私钥对:每个参与者生成一对公私钥,并将公钥提交给多签钱包。
- 创建多签地址:多签钱包根据所有公钥生成一个多签地址,所有资金将存储在这个地址中。
- 发起交易:当需要发起交易时,交易信息将被广播给所有参与者。
- 签名和广播:达到最低签名数量的参与者使用各自的私钥对交易进行签名。所有必要的签名完成后,交易将被广播到区块链网络,并最终被矿工确认。
优点
- 提高安全性:多签钱包的设计大大增强了资金安全性,即使某个私钥被盗,黑客也无法单独完成交易。
- 防范单点故障:由于需要多个签名才能执行交易,多签钱包有效防止了因单个私钥丢失或损坏而导致的资金不可用。
- 增强透明度和信任:在团队或机构中使用多签钱包,可以确保所有交易都经过多个成员的同意,增加了操作的透明度和信任度。
- 访问控制和权限管理:多签钱包可以灵活地设置签名规则,满足不同场景的需求,如家庭理财、企业资金管理等。
场景
- 资金安全:多签钱包可以有效防止因单个私钥丢失或被盗而导致的资金损失。例如,2/3多签模式中,即使其中一个私钥丢失或被盗,只要有其他两个私钥完成签名授权,资金仍然安全。
- 企业财务管理:企业可以使用多签钱包来管理公司资金,确保资金的安全性和合规性。多个管理者共同控制钱包,任何一笔交易都需要经过多个管理者的同意。
- 众筹项目:在众筹项目中,多签钱包可以确保资金的安全性和透明度,防止项目方单方面挪用资金。
- 去中心化自治组织(DAO) :DAO 可以使用多签钱包来管理社区资金,确保所有重要决策都经过社区成员的共同同意。
合约开发
// SPDX-License-Identifier: MIT // author: @0xAA_Science from wtf.academy pragma solidity ^0.8.21; import "hardhat/console.sol"; /// 基于签名的多签钱包 contract MultiSigWallet { event ExecutionSuccess(bytes32 txHash); // 交易成功事件 event ExecutionFailure(bytes32 txHash); // 交易失败事件 address[] public owners; // 多签持有人数组 mapping(address => bool) public isOwner; // 记录一个地址是否为多签 uint256 public ownerCount; // 多签持有人数量 uint256 public threshold; // 多签执行门槛,交易至少有n个多签人签名才能被执行。 uint256 public nonce; // nonce,防止签名重放攻击
receive() external payable {}
// 构造函数,初始化owners, isOwner, ownerCount, threshold
constructor(
address[] memory _owners,
uint256 _threshold
) {
_setupOwners(_owners, _threshold);
}
/// @dev 初始化owners, isOwner, ownerCount,threshold
/// @param _owners: 多签持有人数组
/// @param _threshold: 多签执行门槛,至少有几个多签人签署了交易
function _setupOwners(address[] memory _owners, uint256 _threshold) internal {
// threshold没被初始化过
require(threshold == 0, "WTF5000");
// 多签执行门槛 小于或等于 多签人数
require(_threshold <= _owners.length, "WTF5001");
// 多签执行门槛至少为1
require(_threshold >= 1, "WTF5002");
for (uint256 i = 0; i < _owners.length; i++) {
address owner = _owners[i];
// 多签人不能为0地址,本合约地址,不能重复
require(owner != address(0) && owner != address(this) && !isOwner[owner], "WTF5003");
owners.push(owner);
isOwner[owner] = true;
}
ownerCount = _owners.length;
threshold = _threshold;
}
/// @dev 在收集足够的多签签名后,执行交易
/// @param to 目标合约地址
/// @param value msg.value,支付的以太坊
/// @param data calldata
/// @param signatures 打包的签名,对应的多签地址由小到达,方便检查。 ({bytes32 r}{bytes32 s}{uint8 v}) (第一个多签的签名, 第二个多签的签名 ... )
function execTransaction(
address to,
uint256 value,
bytes memory data,
bytes memory signatures
) public payable virtual returns (bool success) {
// 编码交易数据,计算哈希
bytes32 txHash = encodeTransactionData(to, value, data, nonce, block.chainid);
nonce++; // 增加nonce
checkSignatures(txHash, signatures); // 检查签名
// 利用call执行交易,并获取交易结果
(success, ) = to.call{value: value}(data);
//require(success , "WTF5004");
if (success) emit ExecutionSuccess(txHash);
else emit ExecutionFailure(txHash);
}
/**
* @dev 检查签名和交易数据是否对应。如果是无效签名,交易会revert
* @param dataHash 交易数据哈希
* @param signatures 几个多签签名打包在一起
*/
function checkSignatures(
bytes32 dataHash,
bytes memory signatures
) public view {
// 读取多签执行门槛
uint256 _threshold = threshold;
require(_threshold > 0, "WTF5005");
// 检查签名长度足够长
require(signatures.length >= _threshold * 65, "WTF5006");
// 通过一个循环,检查收集的签名是否有效
// 大概思路:
// 1. 用ecdsa先验证签名是否有效
// 2. 利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增)
// 3. 利用 isOwner[currentOwner] 确定签名者为多签持有人
address lastOwner = address(0);
address currentOwner;
uint8 v;
bytes32 r;
bytes32 s;
uint256 i;
for (i = 0; i < _threshold; i++) {
(v, r, s) = signatureSplit(signatures, i);
// 利用ecrecover检查签名是否有效
currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s);
console.log(currentOwner > lastOwner && isOwner[currentOwner]);
console.log(currentOwner);
console.log(lastOwner);
console.log(currentOwner > lastOwner);
console.log(isOwner[currentOwner]);
console.log("----",currentOwner);
require(currentOwner > lastOwner && isOwner[currentOwner], "WTF5007");
lastOwner = currentOwner;
console.log(lastOwner);
}
}
/// 将单个签名从打包的签名分离出来
/// @param signatures 打包的多签
/// @param pos 要读取的多签index.
function signatureSplit(bytes memory signatures, uint256 pos)
internal
pure
returns (
uint8 v,
bytes32 r,
bytes32 s
)
{
// 签名的格式:{bytes32 r}{bytes32 s}{uint8 v}
assembly {
let signaturePos := mul(0x41, pos)
r := mload(add(signatures, add(signaturePos, 0x20)))
s := mload(add(signatures, add(signaturePos, 0x40)))
v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff)
}
}
/// @dev 编码交易数据
/// @param to 目标合约地址
/// @param value msg.value,支付的以太坊
/// @param data calldata
/// @param _nonce 交易的nonce.
/// @param chainid 链id
/// @return 交易哈希bytes.
function encodeTransactionData(
address to,
uint256 value,
bytes memory data,
uint256 _nonce,
uint256 chainid
) public pure returns (bytes32) {
bytes32 safeTxHash =
keccak256(
abi.encode(
to,
value,
keccak256(data),
_nonce,
chainid
)
);
return safeTxHash;
}
}
# 合约测试
**说明**:1.先启动一下本地的网络节点,npx hardhat node,2.先给多签钱包合约转入一定量的代币,3.进行多签测试
const {ethers,getNamedAccounts,deployments} = require("hardhat"); const { assert,expect } = require("chai"); describe("MultisigWallet",function(){ let MultisigWallet;//合约 let firstAccount//第一个账户 let secondAccount//第二个账户 let owner, signer1, signer2, signer3; const amount = ethers.utils.parseEther('10'); const provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545'); beforeEach(async function(){ await deployments.fixture(["MultiSigWallet"]); [owner, signer1, signer2, signer3]=await ethers.getSigners(); firstAccount=(await getNamedAccounts()).firstAccount; secondAccount=(await getNamedAccounts()).secondAccount; const MultisigWalletDeployment = await deployments.get("MultiSigWallet"); MultisigWallet = await ethers.getContractAt("MultiSigWallet",MultisigWalletDeployment.address);//已经部署的合约交互 });
describe("多签钱包", function () {
it("测试", async function () {
//向多签钱包合约转100eth
console.log(amount)
// 创建一个交易对象 转账 1 ETH 到多签钱包
// const tx =owner.sendTransaction( {
// to: signer1.address,
// value: amount,
// // gasLimit: ethers.BigNumber.from('50000'),
// // gasPrice: ethers.utils.parseUnits('100', 'gwei')
// });
// await tx.wait();
// console.log(await provider.getBalance(signer1.address))
// console.log("Transaction 10 ETH MultisigWallet合约:", MultisigWallet.address);
//
//私有转账
const privateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
// 创建 Wallet 实例
const wallet = new ethers.Wallet(privateKey, provider);
const balance = await wallet.getBalance();
console.log("当前余额:", ethers.utils.formatEther(balance), "ETH");
const tx1 ={
to: signer1.address,
value: amount,
}
// 发送交易
const txResponse = await wallet.sendTransaction(tx1);
console.log("交易发送中...", txResponse.hash);
// 等待交易确认
const txReceipt = await txResponse.wait();
console.log("交易确认:", txReceipt.transactionHash);
console.log("查看signer1余额",`${ethers.utils.formatEther(await provider.getBalance(signer1.address))} ETH`)
// 获取交易
const hash= await MultisigWallet.encodeTransactionData(owner.address,amount,"0x",0,31337);
console.log("Hash",hash)
// 提交交易
//owner3交易签名
const signature1 = await signer2.signMessage(ethers.utils.arrayify(hash));
console.log("signer2 Signature:", signature1);
//owner2交易签名
const signature2 = await signer1.signMessage(ethers.utils.arrayify(hash))
console.log("signer1 Signature:", signature2)
//打包签名
let Signatures=signature1+signature2.slice(2);
console.log("signer1signer2Signatures",Signatures)
await MultisigWallet.execTransaction(owner.address,amount,"0x",Signatures)
console.log("转账成功")
});
});
})
# 合约部署
**说明**:3个多签地址,交易执行门槛设为2
module.exports = async function({getNamedAccounts,deployments}) { const firstAccount= (await getNamedAccounts()).firstAccount; const secondAccount= (await getNamedAccounts()).secondAccount; const [addr1,addr2,addr3]=await ethers.getSigners(); const {deploy,log}=deployments; let MultiSigArray=[addr1.address,addr2.address,addr3.address]; let MultiSigValue=2; const MultiSigWallet=await deploy("MultiSigWallet",{ from:firstAccount, args: [MultiSigArray,MultiSigValue],//参数 log: true, }) console.log('MultiSigWallet合约地址',MultiSigWallet.address) } module.exports.tags = ["all", "MultiSigWallet"];
# 总结
以上就是多签钱包合约开发、测试、部署全流程以及多签钱包的相关介绍,`注意`:测试时,打包签名的账号从后向前拼接;
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!