多签名钱包

相交于硬件钱包的物理上丢失和密钥遗忘,多签名钱包在保证安全不易被破解的基础上,解决了部分密钥丢失的问题。

相比于BTC之前讲到的脚本钱包(BTC—脚本),ETH的多签钱包设计可以通过智能合约来实现

设计思路

工作原理

多签名钱包,可以是多个人使用多个签名,共同决议;也可以一个人使用多个签名,防止丢失。为了方便理解,下面的工作流程都按照多个人来解释

进账

首先,向钱包转账不需要任何条件,来者不拒(dog)

转出

如果是取钱就需要执行以下几个阶段的行动:

  1. 提交交易申请: 有一个账户发起交易,提交申请(申请记录到一个表中,等到申请通过销毁)

  2. 批准交易或撤销交易: 这一步执行是将申请列表的交易进行处理,所有的人负责决议是否通过该交易,而与此同时,所有人也可以撤销自己的决定

  3. 执行交易: 和单签名交易一样,将交易信息上链

代码实现

代码实现是先总体介绍数据类型,事件和构造函数,再按照功能块解释修饰器和函数

总体内容

事件
//存款
event Deposit(address indexed sender, uint amount);//存款
​
取款
//第一步
event Submit(uint indexed txId);//提交交易 等待其他人批准
//第二步
event Approve(address indexed owner, uint indexed txId);//通过交易
event Revoke(address indexed owner, uint indexed txId);//撤销交易
//第三步
event Execute(uint indexed txId);//执行交易
数据类型
//1.交易和交易列表  只要交易被提交(第一步),就记录下来
struct Transaction {
        address to; //转账地址
        uint value; //转账金额
        bytes data; //转账数据
        bool executed; //是否已执行
        //bool approved; //是否批准
    }
Transaction[] public transactions; //交易列表
​
//2.钱包所有者信息
    address[] public owners; //合约所有者
    mapping(address => bool) public isOwner; //合约所有者地址
​
​
//3.要求至少签名数
    uint public required; //最少签名数
​
​
//4.待审批交易数据结构
mapping(uint => mapping(address => bool)) public approved; //交易编号->批准账户 二重调用!
构造函数
 //构造函数
    constructor(address[] memory _owners, uint _required) {
        //判断存在钱包的所有人
        require(
            _owners.length > 0, 
            "the address is empty"
        );
        //判断钱包的要求支付者数量是合理的
        require(
            _required > 0 && _required <= _owners.length,
            "the required number is invalid"
        );
    
        //使用循环将用户地址数组中的用户插入到owners数组中
        for (uint i;i<_owners.length;i++){
            address owner = _owners[i];
            require(owner!= address(0), "owner address cannot be empty");
            require(!isOwner[owner], "owner address cannot be duplicated");
​
            isOwner[owner] = true;
            owners.push(owner);//插入新用户,这个数据结构用于存储用户账户
        }
​
        required = _required;//创建合约时设置最少签名数
​
    }
  1. 构造函数在合约部署之初就写好了,也就是意味着其中需要记录钱包在上链时就要确定的信息!

  2. 首先,要记下的是钱包所有者和要求签名数。两个判断语句就是这样想出来的

  3. 之后,将传入的用户数组写进isOwner中,以便下方检验签名合法性使用

功能函数

收款函数
receive() external payable {
        emit Deposit(msg.sender, msg.value);
    }//receive 在接收以太币时自动调用

receive函数在接收以太币时会自动调用,就是一个简单的自动记录功能

提交申请函数
    modifier onlyOwner() {
       require(isOwner[msg.sender], "only owner can call this function");
       _; 
    }
    //外部可调用函数 只有合约所有者可以调用
    function submit(address _to, uint _value, bytes calldata _data)
        external
    onlyOwner
    {
        transactions.push(Transaction({
            to: _to,
            value: _value,
            data: _data,
            executed: false//尚未被审核的交易
        }));
    
        emit Submit(transactions.length - 1);//提交序号 原理和数组一样
    }
  1. 修饰器,只允许创建账户之初的所有者(也就是账户的拥有者之一)调用这个函数

  2. 函数中,输入一笔交易对应的信息,并将状态置于false,并且将这个交易提交到“交易列表“这个数据类型中

  3. 记录日志

通过和撤销交易
     modifier txExists(uint _txId){
        require(_txId < transactions.length, "transaction does not exist");
        _;
    }//交易编号小于交易列表数组长度
​
    modifier nonApproed(uint _txId){
        //mapping(uint => mapping(address => bool)) public approved;
        require(!approved[_txId][msg.sender],"only nonApproved TX can call this function" );//?
        _;
    }//使用到当时存储的待批准交易列表,判断当前用户是否已经批准过该交易
​
    modifier notExcuted(uint _txId){
       require(!transactions[_txId].executed, "transaction has been executed"); 
       _;
    }
    
    function approve(uint _txId)
        external
    onlyOwner
    txExists(_txId)
    nonApproed(_txId)
    notExcuted(_txId)
    {
        approved[_txId][msg.sender] = true;
        emit Approve(msg.sender, _txId);
    }
    
    //撤销交易
    function revoke(uint _txId) external 
    onlyOwner 
    txExists(_txId)  
    notExcuted(_txId)
    {
        require(approved[_txId][msg.sender], "only approved TX can be revoked");
        approved[_txId][msg.sender] = false;
        emit Revoke(msg.sender, _txId);
    } 
}
  1. 首先,能够进行”第二步“的交易需要以下的状态:

    1. 被记录在交易列表中(使用编号和长度对比判断),说明这个交易被提交了

    2. 还未被大家审议通过(这里使用approved双重映射表来实现)

    3. 还没被执行(这里使用的是交易里面的executed状态来实现)

  2. 对于批准函数,如果发现这个交易在交易列表(transactions)中,函数调用者是钱包所有人之一,交易未执行(最后一步)就可以将交易的该所有人一项记为true。

  3. 对于撤销函数,每个人可以撤销自己的审批决定

执行函数
function approve(uint _txId)
        external
    onlyOwner
    txExists(_txId)
    nonApproed(_txId)
    notExcuted(_txId)
    {
        approved[_txId][msg.sender] = true;
        emit Approve(msg.sender, _txId);
    }
​
    function _getApprovalCount(uint _txId) private view returns(uint count)    {
        for(uint i;i < owners.length;i++){
            if(approved[_txId][owners[i]]){
                count++;
            }
        }
        //隐式返回值 不需要return语句
    }
​
    //执行交易
    function excute(uint _txId) external txExists(_txId) notExcuted(_txId){
​
        require(_getApprovalCount(_txId) >= required, "not enough approvals");     
        Transaction storage transaction = transactions[_txId];
    
        transaction.executed = true;
​
        (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);//检查交易是否发送执行成功
        require(success, "transfer failed");
​
        emit Execute(_txId);
​
    }
  1. 首先,写一个函数计算某个交易拥有的通过者的数量,满足required就可以执行;

  2. 检查交易是否发送执行成功

源代码

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
​
contract MultiSigWallet {
    //创建事件
    event Deposit(address indexed sender, uint amount);//存款
​
    event Submit(uint indexed txId);//提交交易 等待其他人批准
    event Approve(address indexed owner, uint indexed txId);//
    event Revoke(address indexed owner, uint indexed txId);//撤销交易
    event Execute(uint indexed txId);//执行交易
​
​
    struct Transaction {
        address to; //转账地址
        uint value; //转账金额
        bytes data; //转账数据
        bool executed; //是否已执行
        //bool approved; //是否批准
    }
    Transaction[] public transactions; //交易列表
    
    
    address[] public owners; //合约所有者 
    mapping(address => bool) public isOwner; //合约所有者地址
    
    
    uint public required; //最少签名数
​
    
    mapping(uint => mapping(address => bool)) public approved; //交易编号->批准账户
​
    //构造函数
    constructor(address[] memory _owners, uint _required) {
        require(
            _owners.length > 0, 
            "the address is empty"
        );
        require(
            _required > 0 && _required <= _owners.length,
            "the required number is invalid"
        );
    
        //使用循环将用户地址数组中的用户插入到owners数组中
        for (uint i;i<_owners.length;i++){
            address owner = _owners[i];
            require(owner!= address(0), "owner address cannot be empty");
            require(!isOwner[owner], "owner address cannot be duplicated");
​
            isOwner[owner] = true;
            owners.push(owner);//插入新用户,这个数据结构用于存储用户账户
        }
​
        required = _required;//创建合约时设置最少签名数
​
    }
​
    modifier onlyOwner() {
       require(isOwner[msg.sender], "only owner can call this function");
       _; 
    }
​
​
    modifier txExists(uint _txId){
        require(_txId < transactions.length, "transaction does not exist");
        _;
    }//交易编号小于交易列表数组长度
​
    modifier nonApproed(uint _txId){
        //mapping(uint => mapping(address => bool)) public approved;
        require(!approved[_txId][msg.sender],"only nonApproved TX can call this function" );//?
        _;
    }//使用到当时存储的待批准交易列表,判断当前用户是否已经批准过该交易
​
    modifier notExcuted(uint _txId){
       require(!transactions[_txId].executed, "transaction has been executed"); 
       _;
    }
​
    receive() external payable {
        emit Deposit(msg.sender, msg.value);
    }//receive 在接收以太币时自动调用
​
    //外部可调用函数 只有合约所有者可以调用
    function submit(address _to, uint _value, bytes calldata _data)
        external
    onlyOwner
    {
        transactions.push(Transaction({
            to: _to,
            value: _value,
            data: _data,
            executed: false//尚未被审核的交易
        }));
    
        emit Submit(transactions.length - 1);//提交序号 原理和数组一样
    }
​
    function approve(uint _txId)
        external
    onlyOwner
    txExists(_txId)
    nonApproed(_txId)
    notExcuted(_txId)
    {
        approved[_txId][msg.sender] = true;
        emit Approve(msg.sender, _txId);
    }
​
    function _getApprovalCount(uint _txId) private view returns(uint count)    {
        for(uint i;i < owners.length;i++){
            if(approved[_txId][owners[i]]){
                count++;
            }
        }
        //隐式返回值 不需要return语句
    }
​
    //执行交易
    function excute(uint _txId) external txExists(_txId) notExcuted(_txId){
​
        require(_getApprovalCount(_txId) >= required, "not enough approvals");     
        Transaction storage transaction = transactions[_txId];
    
        transaction.executed = true;
​
        (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
        require(success, "transfer failed");
​
        emit Execute(_txId);
​
    }
​
    //撤销交易
    function revoke(uint _txId) external 
    onlyOwner 
    txExists(_txId) 
    //nonApproed(_txId) 
    notExcuted(_txId)
    {
        require(approved[_txId][msg.sender], "only approved TX can be revoked");
        approved[_txId][msg.sender] = false;
        emit Revoke(msg.sender, _txId);
    } 
}

合约审计

  1. 终端输入truffle compile,将合约编译成.json文件

  2. 书写脚本<test2.js>

    const Web3 = require('web3');
    const MultiSigWallet = artifacts.require('MultiSigWallet');
    ​
    // 配置web3连接
    const web3 = new Web3('http://localhost:8545');
    ​
    // 实例化合约
    const contractAddress = '0x123...'; // 合约地址
    const multiSigWallet = new web3.eth.Contract(MultiSigWallet.abi, contractAddress);
    ​
    // 测试用账户
    const account1 = '0xabc...'; // 账户1
    const account2 = '0xdef...'; // 账户2
    ​
    // 测试存款事件
    multiSigWallet.methods.deposit().send({
        from: account1,
        value: web3.utils.toWei('1', 'ether')
    }).on('receipt', function(receipt){
        console.log('Deposit event emitted:', receipt.events.Deposit);
    });
    ​
    // 测试提交交易
    multiSigWallet.methods.submit(account2, web3.utils.toWei('0.5', 'ether'), '0x').send({
        from: account1
    }).on('receipt', function(receipt){
        console.log('Submit event emitted:', receipt.events.Submit);
    });
    ​
    // 测试批准交易
    multiSigWallet.methods.approve(0).send({
        from: account2
    }).on('receipt', function(receipt){
        console.log('Approve event emitted:', receipt.events.Approve);
    });
    ​
    // 测试执行交易
    multiSigWallet.methods.execute(0).send({
        from: account1
    }).on('receipt', function(receipt){
        console.log('Execute event emitted:', receipt.events.Execute);
    });
    ​
    // 测试撤销交易
    multiSigWallet.methods.revoke(0).send({
        from: account1
    }).on('receipt', function(receipt){
        console.log('Revoke event emitted:', receipt.events.Revoke);
    });

  3. 输入truffle test,使用测试脚本检查合约

  4. 输入truffle develop,使用ganache创建账户和私钥

  5. 编写部署脚本

    const Web3 = require('web3');
    const MultiSigWallet = artifacts.require('MultiSigWallet');
    ​
    // 配置web3连接
    const web3 = new Web3('http://localhost:8545');
    ​
    // 测试用账户
    const account = '0xabc...'; // 使用的账户地址
    ​
    // 部署合约
    module.exports = function(deployer, network, accounts) {
      deployer.deploy(MultiSigWallet, [accounts[0], accounts[1], accounts[2]], 2, { from: account })
        .then(function(newMultiSigWallet) {
          console.log('MultiSigWallet deployed at:', newMultiSigWallet.address);
        });
    };

  6. 终端输入migrate --reset执行,查看本地部署的模拟结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/645593.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

辐射度技术在AI去衣中的魅力与科学

引言&#xff1a; 在当今的数字化时代&#xff0c;人工智能正逐渐渗透到我们生活的方方面面。其中&#xff0c;AI去衣技术作为一项颇具争议但又不失其科技创新的应用&#xff0c;正引起越来越多的关注和讨论。而在实现高质量图像渲染的过程中&#xff0c;辐射度技术凭借其卓越的…

移动端开发 笔记01

目录 01 移动端的概述 02 移动端的视口标签 03 开发中的二倍图 04 流式布局 05 弹性盒子布局 01 移动端的概述 移动端包括:手机 平板 便携式设备 目前主流的移动端开发: 安卓设备 IOS设备 只要移动端支持浏览器 那么就可以使用浏览器开发移动端项目 开发移动端 使用…

GNSS中的多路径效应原理及计算方法

1 多路径效应原理 图1 多路径效应原理图 2 计算方法 如需原文&#xff0c;可加多源融合定位与智能控制讨论群获取,QQ群号&#xff1a;51885949

6、phpjm混淆解密和php反序列化

题目&#xff1a;青少年雏形系统 1、打开链接也是一个登入面板 2、尝试了sqlmap没头绪 3、尝试御剑&#xff0c;发现一个www.zip 4、下载打开&#xff0c;有一个php文件打开有一段phpjm混淆加密 5、使用手工解混淆 具体解法链接&#xff1a;奇安信攻防社区-phpjm混淆解密浅谈…

页面<html>上多了一个滚动条,定位发现是<body>里面多了一个id为trans-tooltip的div

现象分析&#xff1a; 页面根标签html多了一个滚动条&#xff0c;发现body里面多了一个id为trans-tooltip的div&#xff0c;虽然width为0&#xff0c;height为0&#xff0c;但是其子元素还是有高度&#xff0c;占据了空间&#xff0c;最终导致了滚动条&#xff1b; 根本原因&…

SpringBoot注解--09--idea创建spring boot项目,java版本只能选择17和21

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 idea创建spring boot项目1.问题描述2.原因3.解决方法方案一&#xff1a;升级JDK版本至17或更高方案二&#xff1a;替换Spring初始化的源https://start.aliyun.com i…

可以在搜索结果中屏蔽指定网站的插件

可以在搜索结果中屏蔽指定网站的插件 | LogDict背景 在搜索引擎中搜索问题, 往往充斥各种无效内容 比如搜个技术类的问题, 前几页CSDN, 百度百家号, 百度经验, 百度知道, 腾讯云各类云爬的水文 CSDN基本都是复制粘贴的, 甚至格式都乱码了, 虽然我以前也干过 要复制粘贴无所谓, …

最小二乘法-超详细推导(转换为矩阵乘法推导,矩阵求导推导)

最小二乘法就是让均方误差最小。 下面是损失函数转换为矩阵方式的详解 如何让其最小&#xff0c;在导数为0的地方取极小值。 问&#xff1a;导数为0的地方可能去极大值&#xff0c;也可能是极小值&#xff0c;凭什么说导数为0就是极小值&#xff1f; 答&#xff1a;因为使用…

Linux 生产跑批脚本解读

1.查看定时任务 2.脚本-目录结构 1&#xff09;config.ini 2&#xff09;run.sh 3.命令解读 1&#xff09;ls -1 路径文件夹 含义&#xff1a;ls -1 /home/oracle/shell/config/ 将文件夹config内的文件全部列出 [oracleneptune config]$ ls -1 /home/oracle/shel…

【ROS机器人学习】--------1ROS工作空间和功能包创建

虚拟机工具和镜像链接: https://pan.baidu.com/s/1HDmpbMESiUA2nj3qFVyFcw?pwd8686 提取码: 8686 ROS工作空间是一个用于组织和管理ROS&#xff08;机器人操作系统&#xff09;包的目录结构&#xff0c;它通常包含多个子目录&#xff0c;用于存放源码、构建文件和安装文件。工…

Elastic Cloud 将 Elasticsearch 向量数据库优化配置文件添加到 Microsoft Azure

作者&#xff1a;来自 Elastic Serena Chou, Jeff Vestal, Yuvraj Gupta 今天&#xff0c;我们很高兴地宣布&#xff0c;我们的 Elastic Cloud Vector Search 优化硬件配置文件现已可供 Elastic Cloud on Microsoft Azure 用户使用。 此硬件配置文件针对使用 Elasticsearch 作…

CSS单位px、rem、em、vw、vh的区别

目录 前言 零.视口介绍 一.px 二.em 三.rem【重要】 3.1rem介绍 3.2rem与em的区别 四.vw、vh、vmax、vmin 五.注意问题 5.1如何使1rem 10px&#xff1f; 5.2如果父元素没有指定高度&#xff0c;那么子元素的百分比高度是多少&#xff1f; 5.3更多问题 前言 这几…

实现多级树形结构查询 比如分类(父分类、子分类)

实现多级树形结构查询 比如分类&#xff08;父分类、子分类&#xff09; 数据库表结构 CREATE TABLE course_category (id varchar(20) NOT NULL COMMENT 主键,name varchar(32) NOT NULL COMMENT 分类名称,label varchar(32) DEFAULT NULL COMMENT 分类标签默认和名称一样,p…

CLIP 论文的关键内容

CLIP 论文整体架构 该论文总共有 48 页&#xff0c;除去最后的补充材料十页去掉&#xff0c;正文也还有三十多页&#xff0c;其中大部分篇幅都留给了实验和响应的一些分析。 从头开始的话&#xff0c;第一页就是摘要&#xff0c;接下来一页多是引言&#xff0c;接下来的两页就…

qt-C++笔记之QThread使用

qt-C笔记之QThread使用 ——2024-05-26 下午 code review! 参考博文&#xff1a; qt-C笔记之使用QtConcurrent异步地执行槽函数中的内容&#xff0c;使其不阻塞主界面 qt-C笔记之QThread使用 文章目录 qt-C笔记之QThread使用一:Qt中几种多线程方法1.1. 使用 QThread 和 Lambda…

gnocchi学习小结

背景 总结gnocchi 4.4版本gnocchi-metricd工作流程 入口 gnocchi.cli.metricd metricd stop after processing metric默认为0&#xff0c;调servicemanager run MetricdServiceManager __init__ 服务逻辑封装到MetricdServiceManager初始化中 主要由MetricProcessor, Met…

火山引擎“奇袭”阿里云

图片&#xff5c;电影《美国队长3》剧照 ©自象限原创 作者丨程心 编辑丨罗辑 大模型价格战&#xff0c;已经不是什么新闻。 从OpenAI发布GPT-4o&#xff0c;将API价格下调50%&#xff0c;并宣布面向普通用户免费开始&#xff0c;就标志着大模型的竞争从性能进入到了成本…

Java设计模式 _行为型模式_迭代器模式

一、迭代器模式 1、迭代器模式 迭代器模式&#xff08;Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;用于顺序访问集合对象的元素&#xff0c;不需要关心集合对象的底层表示。如&#xff1a;java中的Iterator接口就是这个工作原理。 2、实现思路 &#xff0…

面试大杂烩之kafka

面试这个领域最近环境不行&#xff0c;所以卷起来流量挺大 搭建&#xff1a; 总体来说 比较简单&#xff0c;主要是配置文件&#xff0c;命令的话分开了producer /consumer/ topic 大概这么个意思。具体可以看里面的博客 #host配置 #安装包 wget https://archive.apache.or…