【从0学习Solidity】 50. 多签钱包

news2024/11/19 19:36:06

【从0学习Solidity】50. 多签钱包

在这里插入图片描述

  • 博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。
  • 关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和创造!
  • 本文收录于 不写代码没饭吃 的学习汇报系列,大家有兴趣的可以看一看。
  • 欢迎访问我们的微信公众号:不写代码没饭吃,获取更多精彩内容、实用技巧、行业资讯等。您关注的是我们前进的动力!

V神曾说过,多签钱包要比硬件钱包更加安全(推文)。这一讲,我们将介绍多签钱包,并且写一个极简版多签钱包合约。教学代码(150行代码)由gnosis safe合约(几千行代码)简化而成。

50-1.png

多签钱包

多签钱包是一种电子钱包,特点是交易被多个私钥持有者(多签人)授权后才能执行:例如钱包由3个多签人管理,每笔交易需要至少2人签名授权。多签钱包可以防止单点故障(私钥丢失,单人作恶),更加去中心化,更加安全,被很多DAO采用。

Gnosis Safe多签钱包是以太坊最流行的多签钱包,管理近400亿美元资产,合约经过审计和实战测试,支持多链(以太坊,BSC,Polygon等),并提供丰富的DAPP支持。更多信息可以阅读我在21年12月写的Gnosis Safe使用教程。

多签钱包合约

在以太坊上的多签钱包其实是智能合约,属于合约钱包。下面我们写一个极简版多签钱包MultisigWallet合约,它的逻辑非常简单:

  1. 设置多签人和门槛(链上):部署多签合约时,我们需要初始化多签人列表和执行门槛(至少n个多签人签名授权后,交易才能执行)。Gnosis Safe多签钱包支持增加/删除多签人以及改变执行门槛,但在咱们的极简版中不考虑这一功能。

  2. 创建交易(链下):一笔待授权的交易包含以下内容

    • to:目标合约。
    • value:交易发送的以太坊数量。
    • data:calldata,包含调用函数的选择器和参数。
    • nonce:初始为0,随着多签合约每笔成功执行的交易递增的值,可以防止签名重放攻击。
    • chainid:链id,防止不同链的签名重放攻击。
  3. 收集多签签名(链下):将上一步的交易ABI编码并计算哈希,得到交易哈希,然后让多签人签名,并拼接到一起的到打包签名。对ABI编码和哈希不了解的,可以看WTF Solidity极简教程第27讲和第28讲。

    交易哈希: 0xc1b055cf8e78338db21407b425114a2e258b0318879327945b661bfdea570e66
    
    多签人A签名: 0x014db45aa753fefeca3f99c2cb38435977ebb954f779c2b6af6f6365ba4188df542031ace9bdc53c655ad2d4794667ec2495196da94204c56b1293d0fbfacbb11c
    
    多签人B签名: 0xbe2e0e6de5574b7f65cad1b7062be95e7d73fe37dd8e888cef5eb12e964ddc597395fa48df1219e7f74f48d86957f545d0fbce4eee1adfbaff6c267046ade0d81c
    
    打包签名:
    0x014db45aa753fefeca3f99c2cb38435977ebb954f779c2b6af6f6365ba4188df542031ace9bdc53c655ad2d4794667ec2495196da94204c56b1293d0fbfacbb11cbe2e0e6de5574b7f65cad1b7062be95e7d73fe37dd8e888cef5eb12e964ddc597395fa48df1219e7f74f48d86957f545d0fbce4eee1adfbaff6c267046ade0d81c
    
  4. 调用多签合约的执行函数,验证签名并执行交易(链上)。对验证签名和执行交易不了解的,可以看WTF Solidity极简教程第22讲和第37讲。

事件

MultisigWallet合约有2个事件,ExecutionSuccessExecutionFailure,分别在交易成功和失败时释放,参数为交易哈希。

    event ExecutionSuccess(bytes32 txHash);    // 交易成功事件
    event ExecutionFailure(bytes32 txHash);    // 交易失败事件

状态变量

MultisigWallet合约有5个状态变量:

  1. owners:多签持有人数组
  2. isOwneraddress => bool的映射,记录一个地址是否为多签持有人。
  3. ownerCount:多签持有人数量
  4. threshold:多签执行门槛,交易至少有n个多签人签名才能被执行。
  5. nonce:初始为0,随着多签合约每笔成功执行的交易递增的值,可以防止签名重放攻击。
    address[] public owners;                   // 多签持有人数组 
    mapping(address => bool) public isOwner;   // 记录一个地址是否为多签持有人
    uint256 public ownerCount;                 // 多签持有人数量
    uint256 public threshold;                  // 多签执行门槛,交易至少有n个多签人签名才能被执行。
    uint256 public nonce;                      // nonce,防止签名重放攻击

函数

MultisigWallet合约有6个函数:

  1. 构造函数:调用_setupOwners(),初始化和多签持有人和执行门槛相关的变量。

    // 构造函数,初始化owners, isOwner, ownerCount, threshold 
    constructor(        
        address[] memory _owners,
        uint256 _threshold
    ) {
        _setupOwners(_owners, _threshold);
    }
    
  2. _setupOwners():在合约部署时被构造函数调用,初始化ownersisOwnerownerCountthreshold状态变量。传入的参数中,执行门槛需大于等于1且小于等于多签人数;多签地址不能为0地址且不能重复。

    /// @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;
    }
    
  3. execTransaction():在收集足够的多签签名后,验证签名并执行交易。传入的参数为目标地址to,发送的以太坊数额value,数据data,以及打包签名signatures。打包签名就是将收集的多签人对交易哈希的签名,按多签持有人地址从小到大顺序,打包到一个[bytes]数据中。这一步调用了encodeTransactionData()编码交易,调用了checkSignatures()检验签名是否有效、数量是否达到执行门槛。

    /// @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);
    }
    
  4. checkSignatures():检查签名和交易数据的哈希是否对应,数量是否达到门槛,若否,交易会revert。单个签名长度为65字节,因此打包签名的长度要长于threshold * 65。调用了signatureSplit()分离出单个签名。这个函数的大致思路:

    • 用ecdsa获取签名地址.
    • 利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增)。
    • 利用isOwner[currentOwner]确定签名者为多签持有人。
    /**
     * @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);
            require(currentOwner > lastOwner && isOwner[currentOwner], "WTF5007");
            lastOwner = currentOwner;
        }
    }
    
  5. signatureSplit():将单个签名从打包的签名分离出来,参数分别为打包签名signatures和要读取的签名位置pos。利用了内联汇编,将签名的rs,和v三个值分离出来。

    /// 将单个签名从打包的签名分离出来
    /// @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)
        }
    }
    
  6. encodeTransactionData():将交易数据打包并计算哈希,利用了abi.encode()keccak256()函数。这个函数可以计算出一个交易的哈希,然后在链下让多签人签名并收集,再调用execTransaction()函数执行。

    /// @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;
    }
    

Remix演示

  1. 部署多签合约,2个多签地址,交易执行门槛设为2

    多签地址1: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    多签地址2: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
    

50-2.png

  1. 转账1 ETH到多签合约地址。

50-3.png

  1. 调用encodeTransactionData(),编码并计算向多签地址1转账1 ETH的交易哈希。

    参数
    to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    value: 1000000000000000000
    data: 0x
    _nonce: 0
    chainid: 1
    
    结果
    交易哈希: 0xb43ad6901230f2c59c3f7ef027c9a372f199661c61beeec49ef5a774231fc39b
    

50-4.png

  1. 利用Remix中ACCOUNT旁边的笔记图案的按钮进行签名,内容输入上面的交易哈希,获得签名,两个钱包都要签。
    多签地址1的签名: 0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b
    
    多签地址2的签名: 0x6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c
    
    讲两个签名拼接到一起,得到打包签名:  0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c
    

50-5.png

  1. 调用execTransaction()函数执行交易,将第3步中的交易参数和打包签名作为参数传入。可以看到交易执行成功,ETH被转出多签。

50-6.png

总结

这一讲,我们介绍了多签钱包,并写了一个极简版的多签钱包合约,仅有不到150行代码。

在这里插入图片描述

如果这份博客对大家有帮助,希望各位给作者一个免费的点赞👍作为鼓励,并评论收藏一下⭐,谢谢大家!!!
制作不易,如果大家有什么疑问或给作者的意见,欢迎评论区留言。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mac磁盘空间满了怎么办?Mac如何清理磁盘空间

你是不是发现你的Mac电脑存储越来越满&#xff0c;甚至操作系统本身就占了100多G的空间&#xff1f;这不仅影响了电脑的性能&#xff0c;而且也让你无法存储更多的重要文件和软件。别担心&#xff0c;今天这篇文章将告诉你如何清除多余的文件&#xff0c;让你的Mac重获新生。 一…

【kafka实战】01 3分钟在Linux上安装kafka

本节采用docker安装Kafka。采用的是bitnami的镜像。Bitnami是一个提供各种流行应用的Docker镜像和软件包的公司。采用docker的方式3分钟就可以把我们想安装的程序运行起来&#xff0c;不得不说真的很方便啊&#xff0c;好了&#xff0c;开搞。使用前提&#xff1a;Linux虚拟机&…

找不到msvcp110dll,无法继续执行代码,msvcp110dll丢失是什么意思

MSVCP110.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2012 Redistributable package的一部分。这个文件通常用于支持许多Microsoft Visual Studio 2012开发的应用程序。当您在运行某些程序时遇到“找不到msvcp110.dll”的错误时&#xff0c;这意味着您的计算机…

PHY6230低成本遥控灯控芯片国产蓝牙BLE5.2 2.4G SoC

高性价比的低功耗高性能蓝牙5.2系统级芯片&#xff0c;适用多种PC/手机外设连接场景。 高性能多模射频收发机&#xff1a; 通过硬件模块的充分复用实现高性能多模数字收发机。发射机&#xff0c;最大发射功率10dBm&#xff1b;BLE 1Mbps速率接收机灵敏度达到-96dBm&#xff1…

查准率(precision,也叫精确率)和查全率(recall,也叫召回率)

精确率和召回率是广泛用于信息检索和统计学分类领域的两个度量值&#xff0c;用来评价结果的质量。其中精确率是检索出相关文档数与检索出的文档总数的比率&#xff0c;衡量的是检索系统的查准率&#xff1b;召回率是指检索出的相关文档数和文档库中所有的相关文档数的比率&…

【算法小课堂】滑动窗口

滑动窗口 基本概念&#xff1a; 滑动窗口本质是双指针算法的一种演变 本质上就是同向双指针&#xff0c;窗口的范围就是[left,right&#xff09; 滑动窗口大致可以分为两类 窗口大小不变的窗口大小变化的 滑动窗口遇到一些验证重复性的问题的时候可以用哈希表来优化 核心思想…

​旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一书作想象和世界一样宽广

​旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一书作想象和世界一样宽广

DAZ To UMA⭐二.设置DAZ导出的形态键 和 Daz贴图位置

文章目录 🟧 形态键介绍及在Unity3D中的用途1️⃣ Daz中的形态键2️⃣ Blender 中的形态键3️⃣ 形态键在Unity中的作用🟩 设置DAZ导出的形态键1️⃣ 找到要导出的形态键名称2️⃣ 打开导出面板3️⃣ 设置导出规则举例 : 导出身体Morphs举例:导出嘴部Morphs🟦 获取模型纹…

uni-app 之 去掉顶部导航

uni-app 之 去掉顶部导航 uniapp怎么样去掉顶部导航 uniapp去掉顶部导航的方法&#xff1a; 1、去掉所有导航栏&#xff1b; 2、单一页面去掉顶部导航栏。 image.png uniapp去掉顶部导航的方法&#xff1a; 1、去掉所有导航栏 "globalStyle": {"navigationBar…

个人电脑怎么搭建服务器 花生壳内网穿透 设置路由器虚拟服务器

在个人电脑上搭建服务器并使用花生壳进行内网穿透&#xff0c;以及在路由器上设置虚拟服务器&#xff0c;需要一些步骤&#xff1a; 内网穿透设置&#xff1a; 配置内网穿透&#xff0c;选择服务器软件的端口号&#xff0c;然后将其映射到您的服务器的内部IP地址和端口号。这将…

web信息收集

1.绕过cdn方法 2.搜索引擎语法 3.whois查询 4.端口探查 5.网站架构 6.其他 7.网站部署结构

Unity3D 检测鼠标位置的Sprite像素颜色

思路 获取鼠标所在屏幕坐标(Vector2)通过相机ScreenToWorldPoint(Vector3)转为世界坐标 (注意Vector3的z是距离相机的距离&#xff0c;相机需要正交)通过SpriteRenderer访问边界Bounds通过Bounds.Contain检测世界坐标是否在SpriteBounds内通过比例计算来确定在Sprite内的UV坐标…

企业sdwan组网要求:企业使用SD-WAN组网时有哪些要求?

在当今信息时代&#xff0c;企业对于网络的稳定性和高效性要求越来越高。SD-WAN(软件定义广域网)作为 一种新兴的网络技术&#xff0c;为企业提供了更加灵活、可靠和安全的广域网解决方案。在组建企业SD- WAN网络时&#xff0c;有一些关键要求需要考虑。 1. 高可用性&#xff…

全球首发搭载“舱驾一体”的智能座舱,诺博汽车如何引领未来出行?

智能座舱升级战已经全面打响。 一方面&#xff0c;智能座舱已经进入了3.0时代&#xff0c;车企对于差异化要求越来越高&#xff0c;如何进一步提升单一功能体验并进行深度融合&#xff0c;已经成为了智能座舱市场比拼的重点。 另一方面&#xff0c;在5G、车联网、大数据、人工…

Web安全扫描工具:Appscan安装和使用,无偿分享安装包与教程

Appscan 1、简介2、具体使用规则1、常用界面2、工作原理3、设置小技巧 3、实例扫描1、验证码绕过2、越权扫描3、手动绕过验证码扫描 1、简介 AppScan是一款商业化的web安全扫描工具&#xff0c;web扫描领域十分受欢迎 点击此处&#xff0c;先领取资料再阅读&#xff0c;附安装…

LPA*算法图文详解

之前我们看过了A* 算法&#xff0c;知道了A* 算法的基本原理&#xff0c;但是A* 算法的缺陷也很明显&#xff1a;它是离线的路径规划算法&#xff0c;只能一次规划出路径&#xff0c;但是后面路径被改变的话就无法生效了。针对这个问题&#xff0c;人们研究出了D* 算法。D* 算法…

ORAS轻松地在 OCI 注册表中分发工件

什么是ORAS? 词汇大全 ORAS 实际上是使用 OCI Artifacts 的工具。它将媒体类型视为难题的关键部分。容器镜像永远不会被认为是有问题的工件。ORAS 提供 CLI 和客户端库来跨 OCI 兼容的注册表分发工件。 ORAS的特点 ORAS是如何运作的? 能够跨OCI兼容的注册表或OCI图像布局分…

十大服装店收银系统有哪些 好用的服装收银软件推荐

服装店收银系统对于门店和服装卖场来说非常重要&#xff0c;可以提高工作效率。下面是推荐的十大服装店收银系统&#xff0c;供开设服装店的企业选择合适的收银软件用于经营管理。 1、核货宝收银系统 支持快速收银&#xff0c;同时适用于服装行业&#xff0c;能够支持多规格多…

在 React 表单开发时,有时没有必要使用State 数据状态

说到在React中处理表单&#xff0c;最流行的方法是将输入值存储在状态变量中。遵循这种方法的原因之一是因为毕竟它是React&#xff0c;每个人都倾向于使用它附带的hooks。使用hooks可以解决React中的许多问题&#xff0c;但是在处理表单时是否必需呢&#xff1f;让我们来看看。…

基于UWB技术高精度人员定位系统源码

一、UWB定位技术有什么优势&#xff1f; UWB技术解决了困扰传统无线通信技术多年的有关传播方面的重大难题&#xff0c;具有对信道衰落不敏感、发射信号功率谱密度低、截获率低、系统复杂度低、能提供数厘米的定位精度等优点。 1.系统结构简单 UWB系统中的发射器直接用脉冲小…