【从0学习Solidity】37. 数字签名 Signature

news2024/12/24 2:23:19

【从0学习Solidity】37. 数字签名 Signature

在这里插入图片描述

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

这一讲,我们将简单的介绍以太坊中的数字签名ECDSA,以及如何利用它发放NFT白名单。代码中的ECDSA库由OpenZeppelin的同名库简化而成。

数字签名

如果你用过opensea交易NFT,对签名就不会陌生。下图是小狐狸(metamask)钱包进行签名时弹出的窗口,它可以证明你拥有私钥的同时不需要对外公布私钥。

37-1.png

以太坊使用的数字签名算法叫双椭圆曲线数字签名算法(ECDSA),基于双椭圆曲线“私钥-公钥”对的数字签名算法。它主要起到了三个作用:

  1. 身份认证:证明签名方是私钥的持有人。
  2. 不可否认:发送方不能否认发送过这个消息。
  3. 完整性:消息在传输过程中无法被修改。

ECDSA合约

ECDSA标准中包含两个部分:

  1. 签名者利用私钥(隐私的)对消息(公开的)创建签名(公开的)。
  2. 其他人使用消息(公开的)和签名(公开的)恢复签名者的公钥(公开的)并验证签名。
    我们将配合ECDSA库讲解这两个部分。本教程所用的私钥公钥消息以太坊签名消息签名如下所示:
私钥: 0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b
公钥: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2
消息: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
以太坊签名消息: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
签名: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c

创建签名

1. 打包消息: 在以太坊的ECDSA标准中,被签名的消息是一组数据的keccak256哈希,为bytes32类型。我们可以把任何想要签名的内容利用abi.encodePacked()函数打包,然后用keccak256()计算哈希,作为消息。我们例子中的消息是由一个address类型变量和一个uint256类型变量得到的:

    /*
     * 将mint地址(address类型)和tokenId(uint256类型)拼成消息msgHash
     * _account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
     * _tokenId: 0
     * 对应的消息msgHash: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
     */
    function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){
        return keccak256(abi.encodePacked(_account, _tokenId));
    }

37-2.png

2. 计算以太坊签名消息: 消息可以是能被执行的交易,也可以是其他任何形式。为了避免用户误签了恶意交易,EIP191提倡在消息前加上"\x19Ethereum Signed Message:\n32"字符,并再做一次keccak256哈希,作为以太坊签名消息。经过toEthSignedMessageHash()函数处理后的消息,不能被用于执行交易:

    /**
     * @dev 返回 以太坊签名消息
     * `hash`:消息
     * 遵从以太坊签名标准:https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * 以及`EIP191`:https://eips.ethereum.org/EIPS/eip-191`
     * 添加"\x19Ethereum Signed Message:\n32"字段,防止签名的是可执行交易。
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 哈希的长度为32
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

处理后的消息为:

以太坊签名消息: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b

37-3.png

3-1. 利用钱包签名: 日常操作中,大部分用户都是通过这种方式进行签名。在获取到需要签名的消息之后,我们需要使用metamask钱包进行签名。metamaskpersonal_sign方法会自动把消息转换为以太坊签名消息,然后发起签名。所以我们只需要输入消息签名者钱包account即可。需要注意的是输入的签名者钱包account需要和metamask当前连接的account一致才能使用。

因此首先把例子中的私钥导入到小狐狸钱包,然后打开浏览器的console页面:Chrome菜单-更多工具-开发者工具-Console。在连接钱包的状态下(如连接opensea,否则会出现错误),依次输入以下指令进行签名:

ethereum.enable()
account = "0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2"
hash = "0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c"
ethereum.request({method: "personal_sign", params: [account, hash]})

在返回的结果中(PromisePromiseResult)可以看到创建好的签名。不同账户有不同的私钥,创建的签名值也不同。利用教程的私钥创建的签名如下所示:

0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c

37-4.jpg

3-2. 利用web3.py签名: 批量调用中更倾向于使用代码进行签名,以下是基于web3.py的实现。

from web3 import Web3, HTTPProvider
from eth_account.messages import encode_defunct

private_key = "0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b"
address = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
rpc = 'https://rpc.ankr.com/eth'
w3 = Web3(HTTPProvider(rpc))

#打包信息
msg = Web3.solidityKeccak(['address','uint256'], [address,0])
print(f"消息:{msg.hex()}")
#构造可签名信息
message = encode_defunct(hexstr=msg.hex())
#签名
signed_message = w3.eth.account.sign_message(message, private_key=private_key)
print(f"签名:{signed_message['signature'].hex()}")

运行的结果如下所示。计算得到的消息,签名和前面的案例一致。

消息:0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
签名:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c

验证签名

为了验证签名,验证者需要拥有消息签名,和签名使用的公钥。我们能验证签名的原因是只有私钥的持有者才能够针对交易生成这样的签名,而别人不能。

4. 通过签名和消息恢复公钥:签名是由数学算法生成的。这里我们使用的是rsv签名签名中包含r, s, v三个值的信息。而后,我们可以通过r, s, v以太坊签名消息来求得公钥。下面的recoverSigner()函数实现了上述步骤,它利用以太坊签名消息 _msgHash签名 _signature恢复公钥(使用了简单的内联汇编):

    // @dev 从_msgHash和签名_signature中恢复signer地址
    function recoverSigner(bytes32 _msgHash, bytes memory _signature) internal pure returns (address){
        // 检查签名长度,65是标准r,s,v签名的长度
        require(_signature.length == 65, "invalid signature length");
        bytes32 r;
        bytes32 s;
        uint8 v;
        // 目前只能用assembly (内联汇编)来从签名中获得r,s,v的值
        assembly {
            /*
            前32 bytes存储签名的长度 (动态数组存储规则)
            add(sig, 32) = sig的指针 + 32
            等效为略过signature的前32 bytes
            mload(p) 载入从内存地址p起始的接下来32 bytes数据
            */
            // 读取长度数据后的32 bytes
            r := mload(add(_signature, 0x20))
            // 读取之后的32 bytes
            s := mload(add(_signature, 0x40))
            // 读取最后一个byte
            v := byte(0, mload(add(_signature, 0x60)))
        }
        // 使用ecrecover(全局函数):利用 msgHash 和 r,s,v 恢复 signer 地址
        return ecrecover(_msgHash, v, r, s);
    }

参数分别为:

_msgHash:0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
_signature:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c

37-8.png

5. 对比公钥并验证签名: 接下来,我们只需要比对恢复的公钥与签名者公钥_signer是否相等:若相等,则签名有效;否则,签名无效:

    /**
     * @dev 通过ECDSA,验证签名地址是否正确,如果正确则返回true
     * _msgHash为消息的hash
     * _signature为签名
     * _signer为签名地址
     */
    function verify(bytes32 _msgHash, bytes memory _signature, address _signer) internal pure returns (bool) {
        return recoverSigner(_msgHash, _signature) == _signer;
    }

参数分别为:

_msgHash:0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b
_signature:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c
_signer:0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2

37-9.png

利用签名发放白名单

NFT项目方可以利用ECDSA的这个特性发放白名单。由于签名是链下的,不需要gas,因此这种白名单发放模式比Merkle Tree模式还要经济。方法非常简单,项目方利用项目方账户把白名单发放地址签名(可以加上地址可以铸造的tokenId)。然后mint的时候利用ECDSA检验签名是否有效,如果有效,则给他mint

SignatureNFT合约实现了利用签名发放NFT白名单。

状态变量

合约中共有两个状态变量:

  • signer公钥,项目方签名地址。
  • mintedAddress是一个mapping,记录了已经mint过的地址。

函数

合约中共有4个函数:

  • 构造函数初始化NFT的名称和代号,还有ECDSA的签名地址signer
  • mint()函数接受地址addresstokenId_signature三个参数,验证签名是否有效:如果有效,则把tokenIdNFT铸造给address地址,并将它记录到mintedAddress。它调用了getMessageHash()ECDSA.toEthSignedMessageHash()verify()函数。
  • getMessageHash()函数将mint地址(address类型)和tokenIduint256类型)拼成消息
  • verify()函数调用了ECDSA库的verify()函数,来进行ECDSA签名验证。
contract SignatureNFT is ERC721 {
    address immutable public signer; // 签名地址
    mapping(address => bool) public mintedAddress;   // 记录已经mint的地址

    // 构造函数,初始化NFT合集的名称、代号、签名地址
    constructor(string memory _name, string memory _symbol, address _signer)
    ERC721(_name, _symbol)
    {
        signer = _signer;
    }

    // 利用ECDSA验证签名并mint
    function mint(address _account, uint256 _tokenId, bytes memory _signature)
    external
    {
        bytes32 _msgHash = getMessageHash(_account, _tokenId); // 将_account和_tokenId打包消息
        bytes32 _ethSignedMessageHash = ECDSA.toEthSignedMessageHash(_msgHash); // 计算以太坊签名消息
        require(verify(_ethSignedMessageHash, _signature), "Invalid signature"); // ECDSA检验通过
        require(!mintedAddress[_account], "Already minted!"); // 地址没有mint过
        _mint(_account, _tokenId); // mint
        mintedAddress[_account] = true; // 记录mint过的地址
    }

    /*
     * 将mint地址(address类型)和tokenId(uint256类型)拼成消息msgHash
     * _account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
     * _tokenId: 0
     * 对应的消息: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c
     */
    function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){
        return keccak256(abi.encodePacked(_account, _tokenId));
    }

    // ECDSA验证,调用ECDSA库的verify()函数
    function verify(bytes32 _msgHash, bytes memory _signature)
    public view returns (bool)
    {
        return ECDSA.verify(_msgHash, _signature, signer);
    }
}

remix验证

  • 链下通过以太坊签名获得signature,给_account地址发放tokenId = 0的白名单。所用的数据见<ECDSA合约>章节。

  • 部署SignatureNFT合约,参数分别为:

_name: WTF Signature
_symbol: WTF
_signer: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2

37-5.png

  • 调用mint()函数利用ECDSA验证签名并铸造,参数为:
_account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
_tokenId: 0
_signature: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c

37-6.png

  • 调用ownerOf()函数,可以看到tokenId = 0成功铸造给了地址_account,合约运行成功!

37-7.png

总结

这一讲,我们介绍了以太坊中的数字签名ECDSA,如何利用ECDSA创建和验证签名,还有ECDSA合约,以及如何利用它发放NFT白名单。代码中的ECDSA库由OpenZeppelin的同名库简化而成。

  • 由于签名是链下的,不需要gas,因此这种白名单发放模式比Merkle Tree模式还要经济;
  • 但由于用户要请求中心化接口去获取签名,不可避免的牺牲了一部分去中心化;
  • 额外还有一个好处是白名单可以动态变化,而不是提前写死在合约里面了,因为项目方的中心化后端接口可以接受任何新地址的请求并给予白名单签名。

在这里插入图片描述

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

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

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

相关文章

提示计算机丢失msvcp140.dll怎么办,缺少msvcp140.dll一键修复

在计算机使用过程中&#xff0c;我们可能会遇到各种稀奇古怪的问题。其中&#xff0c;msvcp140.dll 文件丢失算是比较常见的一种。那么&#xff0c;究竟什么是 msvcp140.dll 文件&#xff1f;它为什么会丢失&#xff1f;我们又该如何解决这个问题呢&#xff1f;本文将围绕这些问…

向量数据库库Milvus Cloud2.3 技术选型中性能、成本、扩展性是重点

技术选型中性能、成本、扩展性是重点 对于向量数据库来说,用户最关心的莫过于性能、成本和扩展性。 Milvus 2.x 从 Day 1 开始就将扩展性作为设计的第一优先级,在众多用户环境中落地了十亿至百亿级别场景。不止如此,对于 Milvus 来说,扩展性不仅仅意味着支持百亿级别向量,…

vue指令(代码部分)

注&#xff1a;此部分为学习uni-app时接触到的vue <template><view><view>当前标题&#xff1a;{{title}}</view><view> {{num}}</view><view>{{arr[2]}}</view><view>{{obj}}</view><view>{{obj.name}}&l…

导览软件定制开发方案

随着智能手机的普及和人们对文化、旅游等方面的需求不断增加&#xff0c;导览软件市场前景广阔。本文将围绕导览软件定制开发方案展开&#xff0c;包括以下部分&#xff1a; 一、行业现状及市场需求 导览软件市场发展迅速&#xff0c;各类导览软件层出不穷。通过对市场…

价值1000的情感爆文写作prompt,助你写出10万+阅读微信爆文

原文&#xff1a;价值1000的情感爆文写作prompt&#xff0c;助你写出10万阅读微信爆文 - 知乎 是否经常看到一些自媒体晒出这样的图片&#xff1f; 或者是这样的10w的阅读文章 那么这是真实能赚钱的吗&#xff1f;还是自媒体夸大其说&#xff0c;吸引流量。 我们先简单了解什…

ElementUI之登陆+注册->饿了吗完成用户登录界面搭建,axios之get请求,axios之post请求,跨域,注册界面

饿了吗完成用户注册登录界面搭建axios之get请求axios之post请求跨域 1.饿了吗完成用户注册登录界面搭建 将端口号8080改为8081 导入依赖&#xff0c;在项目根目录使用命令npm install element-ui -S&#xff0c;添加Element-UI模块 -g&#xff1a;将依赖下载node_glodal全局依…

网络上怎么赚点零花钱

现代物质社会中&#xff0c;我们常常会被琐碎的开销困扰。无论是衣食住行还是休闲娱乐&#xff0c;总有一些额外的花费&#xff0c;使我们不得不时常思索如何赚点零花钱。而现如今&#xff0c;随着互联网的飞速发展&#xff0c;我们有了更多的机会通过网络来实现这个目标。现在…

Centos7做回收站功能-防止误删除

Centos7做回收站功能&#xff0c;就算误删了文件&#xff0c;也可以还原回来 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.先创建一个回收站的目录&#xff08;我在/data下&#xff09; 在大磁盘的目录下创建.trash文件夹 mkdir -p /data/.trash 2.在/root/.bas…

Vue之ElementUI实现登陆及注册

目录 ​编辑 前言 一、ElementUI简介 1. 什么是ElementUI 2. 使用ElementUI的优势 3. ElementUI的应用场景 二、登陆注册前端界面开发 1. 修改端口号 2. 下载ElementUI所需的js依赖 2.1 添加Element-UI模块 2.2 导入Element-UI模块 2.3 测试Element-UI是否能用 3.编…

Nodejs基于Vue.js编程语言在线学习平台的设计与实现5y4p2

本编程语言在线学习平台是为了提高用户查阅信息的效率和管理人员管理信息的工作效率&#xff0c;可以快速存储大量数据&#xff0c;还有信息检索功能&#xff0c;这大大的满足了用户和管理员这二者的需求。操作简单易懂&#xff0c;合理分析各个模块的功能&#xff0c;尽可能优…

【08】FISCOBCOS一键部署【07+08即可完成一键部署,默认生成两个节点的链】

官方文档https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE/install.html#id1 一键部署 ​ 一键部署可以在 同机 快速搭建WeBASE管理台环境&#xff0c;方便用户快速体验WeBASE管理平台。 ​ 一键部署会搭建&#xff1a;节点&#xff08;FISCO-BCOS 2.0&#xff09;…

SQL数据库基础

目录 1.SQL分类 2.SQL-DDL 2.1 数据库操作 查询 创建 删除 使用某个数据库 2.2 数据表操作 创建表 查询表 修改表 3.SQL-DML(增删改) 3.1 插入 3.2 修改 3.3 删除 4.SQL-DQL(查) 4.1 基础查询 4.2 条件查询 4.3 聚合函数查询 4.4 分组查询 4.5 排序查询 …

递归法求最大公约数

如果a或b不是不是正整数&#xff0c;则函数返回-1。 主程序样例&#xff1a; 这里给出主函数及对Gcd函数的调用样例&#xff1a; #include <stdio.h> int Gcd(int a, int b); int main() { int a, b, c; scanf(“%d %d”, &a, &b); c Gcd(a,b); if (c ! -1) {…

Python实战实例代码-网络爬虫-数据分析-机器学习-图像处理

Python实战实例代码-网络爬虫-数据分析-机器学习-图像处理 Python实战实例代码1. 网络爬虫1.1 爬取网页数据1.2 爬取图片1.3 爬取动态数据&#xff08;使用Selenium&#xff09; 2. 数据分析2.1 数据清洗2.2 数据变换2.3 数据聚合 3. 机器学习3.1 线性回归3.2 随机森林3.3 K-Me…

ElementUI之登陆+注册

一.什么是ElementUI 二.ElementUI完成用户注册登录界面搭建 使用命令npm install element-ui -S&#xff0c;添加Element-UI模块 导依赖 建立登录和注册页面 ​编辑 配置样式 编写登录页面&#xff08;Login&#xff09; 编写注册页面&#xff08;reginter&#xff09; …

结构体,联合体与位段

1.结构体的内存对齐(计算结构体的大小) 1.1 为什么需要结构体内存对齐? 原因1:平台原因 不是所有的硬件平台都能访问任意地址上的任意数据的&#xff1b;某些平台只能在某些地址处取得某些特定类型的数据&#xff0c;否则抛出硬件异常。 比如&#xff0c;当一个平台要取一个…

黑马程序员2023新版JavaWeb企业开发全流程学习笔记(涵盖Spring+MyBatis+SpringMVC+SpringBoot等)

目录 零、Web开发一、HTML-CSS初识Web前端Web前端课程安排小结 HTML、CSS介绍HTML快速入门HTML小结VS Code开发工具基础标签 & 样式-合集&#xff08;拟新浪微博为例&#xff09;标题排版样式超链接 正文排版布局 表格、表单标签表格标签表单标签表单项 二、JavaScriptJS基…

车载软件架构 —— AUTOSAR Vector SIP包(三)

车载软件架构 —— AUTOSAR Vector SIP包(三) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生在…

【计算机网络笔记四】应用层(一)DNS域名解析

因特网的域名空间是一棵倒着生长的树&#xff0c;各级域名由其上一级域名管理机构管理。 这种按等级管理的命名方法便于维护名字的唯一性&#xff0c;并且容易设计出一种高效的域名查询机制。 注意&#xff1a;域名只是个逻辑概念&#xff0c;并不代表计算机所在的物理地点 域…

10.mysql系统配置与启动选项

title: “mysql系统配置与启动选项” createTime: 2022-03-06T15:52:4108:00 updateTime: 2022-03-06T15:52:4108:00 draft: false author: “ggball” tags: [“mysql”] categories: [“db”] description: “” 启动选项与系统变量 启动选项 长格式与短格式 在执行启动脚…