链下签名实现

news2024/11/18 11:39:04

什么是签名

比如我们在使用 opensea 的时候,经常会提示我们进行数字签名,如下图:
在这里插入图片描述
用户进行 sign 确认,即用自己的私钥对一段数据进行签名,得到一个 signature,其他人可以使用你私钥对应的公钥,对 signature 进行验证,从而证明你是私钥的持有者。签名后的数据有如下作用:

  • 验证身份:验证私钥持有人
  • 完整性:防止数据被篡改
  • 不可否认:持有人无法否认签名

我们在区块链中发起的每一笔交易(转账、对合约写操作)都是使用私钥签名过的,矿工会在打包前对每笔交易进行校验。具体逻辑如图:
在这里插入图片描述
签名的核心:

  • 使用私钥进行签名,公钥进行验证
  • 不对原文进行签名,而是对原文的hash进行签名(为什么这样做呢?主要是因为 hash 计算的不可逆可以防篡改,如果是对原文签名,中间人攻击可以将密文和原文都改了,最后解密出来数据还是一致的,但其实数据已经被篡改了)

ECDSA 合约

我们将对 openzeppelin 中的 ECDSA 标准合约进行拆解学习,整个签名验证过程可分为四个阶段:

  • 阶段一:打包原始消息,生成 hash
  • 阶段二:添加前缀,生成以太坊签名 hash,用于最终校验
  • 阶段三:解析签名,获得解析的地址 1
  • 阶段四:校验地址 1 与实际签名的地址是否一致
    在这里插入图片描述

阶段一:打包原始消息

在以太坊的 ECDSA 标准中,被签名的消息为一组数据的 hash 值(由 keccak256 算法生成的 byte32 类型的数据),我们可以使用abi.encodePacked(打包函数)将任意多个参数进行打包,此处为:address 和uint256 类型

function getMessageHash(address _to, uint _amount) public pure returns(bytes32) {
    return keccak256(abi.encodePacked(_to, _amount));
}

输入参数:0xc783df8a850f42e7f7e57013759c285caa701eb6, 100
输出:0xcfb170482914a76ca8521405f52699df67c7ebb8e3899f27cc8265ebdab98a36
在这里插入图片描述

阶段二:生成以太坊签名 hash

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

function getEthSignedMessageHash(bytes32 _messageHash) public pure returns(bytes32) {
        return keccak256(
            // 这是标准字符串: \x19Ethereum Signed Message:\n
            // 32 表示后面的哈希内容长度
            abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
        );
}

输入参数:0xcfb170482914a76ca8521405f52699df67c7ebb8e3899f27cc8265ebdab98a36
输出:0x60a7e355f6d1a5885594e145ce67bd165a3e63337806f576b7b417d31cdb20da
在这里插入图片描述
接着生成签名,这里有两种方式:

  1. metamask 生成签名
    复制 metamask 账户地址,F12 打开控制台 -> console,输入如下内容然后回车(注意这里的 hash 使用的是“消息 hash”):
ethereum.send('eth_requestAccounts')
account = "0xFA172d92bC2A12bD780757927B31E3B2CEdE9950"
hash = "0xcfb170482914a76ca8521405f52699df67c7ebb8e3899f27cc8265ebdab98a36"
ethereum.request({method: "personal_sign", params: [account, hash]})

点击签名
在这里插入图片描述
签名成功后,得到签名:
在这里插入图片描述
2. etherjs 生成签名
在hardhat的test文件夹下创建sign.ts

const { expect } = require("chai")
const { ethers } = require("hardhat")

describe("Signature", function () {
  it("signature", async function () {
    // 0xc783df8a850f42e7f7e57013759c285caa701eb6
    let privateKey = '0xc5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122'
    console.log('private:', privateKey);

    const signer = new ethers.Wallet(privateKey);
    console.log('address :', signer.address);

    const amount = 100

    let msgHash = ethers.utils.solidityKeccak256(
      ["address", "uint256"], [signer.address, amount]
    )

    console.log('msgHash:', msgHash);
    const sig = await signer.signMessage(ethers.utils.arrayify(msgHash))

    console.log('signature:', sig);
  })
})

运行单元测试:npx hardhat test,可以得到相同的签名

阶段三:恢复地址

先对 signature 签名分割得到 r, s, v ,然后结合以太坊签名消息,利用内联汇编得出公钥(即 metamask 账户地址),下面的 recoverSigner() 函数实现了上述步骤:

function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

        // 返回解析出来的签名地址
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    // 分割签名
    function splitSignature(bytes memory sig) public pure returns(bytes32 r, bytes32 s, uint8 v) {
        require(sig.length == 65, "invalid signature length");

        // 通过读取内存数据 根据规则进行截取 返回 r, s, v 数据
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }
    }

阶段四:验证

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

function verify(bytes32 _ethSignedMessageHash, bytes memory _signature, address _signer) public pure returns(bool) {
        return recoverSigner(_ethSignedMessageHash, _signature) == _signer;
    }

链下签名实现白名单

核心逻辑

  • 将白名单用户地址和 tokenId 签名入库
  • 用户 mint 铸造时,传入签名,在 mint 中进行校验,只有校验为 true 的用户才可以 mint,从而完成白名单功能
function mint(uint256 _tokenId, bytes memory _signature) external {
        // 将用户地址和_tokenId打包消息
        bytes32 _msgHash = getMessageHash(msg.sener, _tokenId); 

        // 计算以太坊签名消息
        bytes32 _ethSignedMessageHash = getEthSignedMessageHash(_msgHash);

        // ECDSA检验通过
        require(verify(_ethSignedMessageHash, _signature), "Invalid signature");

        // 地址没有mint过
        require(!mintedAddress[_account], "Already minted!"); 
        // 铸造
        _mint(_account, _tokenId);
        // 铸造记录
        mintedAddress[_account] = true;
}

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

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

相关文章

推荐5款Windows桌面效率工具

今天我想分享一些自己比较喜欢的桌面端软件,还请大家包涵指正。如果你曾搜索过 Windows 效率工具推荐,对下文的软件或许有所了解。不过为了凑字数,我还是会再介绍一遍。 1.文件定位——Listary Listary 是我使用频率最高的软件之一&#xf…

Java重点源码回顾——HashMap1.8

1. 概述 在之前的文章中,我们介绍了HashMap1.7的源码,今天我们来看下HashMap1.8的源码。HashMap1.8相比于1.7最大的改变就是改变了1.7中采用数组链表的方式存储键值对,转而由数组链表红黑树的方式来存储键值对。HashMap1.8的底层结构如下图所…

RPC 好,还是 RESTful 好

OSI网络七层模型 RPC服务 RPC架构 同步调用与异步调用 流行的RPC框架 HTTP服务 总之 RPC主要是基于TCP/IP协议的,而HTTP服务主要是基于HTTP协议的,我们都知道HTTP协议是在传输层协议TCP之上的,所以效率来看的话,RPC当然是要更…

【python】py课后作业程序题5「PTA」

py字典合集7-1 Python猜数游戏7-2 jmu-python-分段函数7-3 循环求e的近似值(高教社,《Python编程基础及应用》习题6-7)7-4 jmu-python-判断是否构成三角形7-5 jmu-python-输入输出-计算字符串中的数7-6 jmu-python-随机生成密码7-7 jmu-pytho…

vscode使用CMake Tool插件构建第一个CMake的helloworld工程

vscode使用CMake Tool插件构建第一个CMake的helloworld工程一、linux环境准备1.1 CMake安装1.2 gcc/g的安装二、vscode 插件安装2.1 C扩展2.2 CMake Tool三、使用CMake构建第一个工程3.1 创建工程目录3.2 使用CMake Tool创建第一个Project3.3 Configure 第一个project四、构建工…

Java中序列化接口Serializable的serialVersionUID的作用

原文网址:Java中序列化接口Serializable的serialVersionUID的作用_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java中序列化接口Serializable的serialVersionUID的作用。 序列化与反序列化 含义 序列化:将java对象转化为字节序列。反序列化&#xff1a…

【mysql】索引的基本使用

文章目录1. 索引的声明与使用1.1 索引的分类1.2 创建索引1.2.1 创建表的时候创建索引(隐式)1.2.2 在已经存在的表上创建索引(显式)1、创建普通索引2、创建唯一索引3、主键索引4、创建单列索引5、创建联合索引6、创建全文索引7、创…

33、基于STM32的计时器(Proteus仿真+程序)

编号:33 基于STM32的计时器 功能描述: 使用proteus 软件设计一个基于STM32的家用计时器,该系统包含多个按键、LED运行灯和时间显示(时间显示是LCD1602) 其功能如下: 1、利用按键实现设置计时时间功能,时间格式:AB:CD:E 例如01:5…

【攻防世界】江苏工匠杯 Web easyphp

打开页面是一个代码审计的题目&#xff0c;是我不太熟悉的东西&#xff0c;但是没关系&#xff0c;我们可以学是吧&#xff0c;以下为源代码 <?php highlight_file(__FILE__); $key1 0; $key2 0;$a $_GET[a]; $b $_GET[b];if(isset($a) && intval($a) > 60…

【ES实战】ES集群节点迁移与缩容

ES集群节点迁移与缩容 文章目录ES集群节点迁移与缩容master节点迁移场景一场景二场景三data节点迁移数据迁移操作1、查询集群原来的配置2、清空节点数据3、检查是否排空数据迁移原则缩容前置检查项master节点迁移 场景一 集群上的master部署情况&#xff0c;一台机器上同时部…

智能化IT运维平台建设方案,基于智和信通运维体系的高敏捷二次开发

随着企业信息进程不断加速&#xff0c;运维人员需要面对越来越复杂的业务和越来越多样化的用户需求&#xff0c;不断扩展的应用需要越来越合理的模式、越来越智能的工具来保障运维能灵活便捷、安全稳定地开展。企业网络规模的不断扩大&#xff0c;从初期的几台服务器发展到庞大…

Python3 | vscode配置环境

vscode版本&#xff1a;1.74.2python版本&#xff1a;3.9.0win10系统 准备工作&#xff0c;在win10系统 1&#xff0c;安装python3&#xff0c;配置环境变量2&#xff0c;安装vscode 接下来&#xff1a;就可以在vscode配置python环境 1&#xff0c;下载和安装python插件 快…

2022年总结以及2023年的计划

2022年总结以及2023年的计划 文章目录2022年总结以及2023年的计划年终复盘投资理财学习方面前端方面&#xff1a;后端方面&#xff1a;数据库&#xff1a;读书&#xff1a;疫情工作爱情新的一年的展望按照每年的惯例&#xff0c;我每年的总结&#xff0c;复盘会在这几天完成。 …

软件测试精准定位BUG小技巧

目录 一、前置知识 二、定位技巧 一、前置知识 1. 熟透系统业务、团队成员情况 2. 熟悉使用F12或抓包工具 3. 了解HTTP/HTTPS协议&#xff0c;能够区分请求URL、请求头、请求体、入参、响应数据、响应码 4. 具备操作常规Linux命令&#xff0c;能否登入服务器查看Log日志&…

通信原理 | 波段的划分

波段(wave band) 在无线电技术中,波段(wave band)这个名词具有两种含义。 电磁波频谱的划分,例如长波、短波、超短波等波段。 发射机、接收机等设备的工作频率范围的划分。若把工作频率范围分成几个部分,这些部分也称为波段,例如三波段收音机等。 波段划分 波段通常是…

Python实现A股股市情感分析,含数据集可直接运行

Python实现A股股市情感分析&#xff0c;含数据集可直接运行 Stock Market Sentiment Analysis: 股市情感分析 完整代码下载地址&#xff1a;Python实现A股股市情感分析 情绪与股市 情绪与股市关系的研究由来已久&#xff0c;情绪是市场的一个重要影响因素已成为共识。 15年…

【设计模式】状态模式

状态模式 状态机 在操作系统的调度中会存在三种状态&#xff1a;运行、就绪、阻塞。 这是比较典型的状态机的例子。 做产品的时候&#xff0c;我们总能遇到一些比较复杂的逻辑问题&#xff0c;而普通的流程图&#xff0c;或时序图对于对象和状态的解读缺乏直观的描述。 这…

2.16 SPI协议的4种模式

文章目录1、简介2、SPI通讯模式2.1 模式02.2 模式12.3 模式22.4 模式32.5 总述1、简介 四线控制 SDO - 主设备数据输出&#xff0c;从设备输入 对应MOSI SDI - 主设备数据输入&#xff0c;从设备输出 对应MISO SCLK - 时钟信号&#xff0c;由主设备产生 CS - 从设备使能信号&a…

java接口的静态方法

目前java中已经支持定义静态方法 但需要注意一个点 我们先把代码写出来 我们创建一个包 下面创建一个接口 subInterface 接口参考代码如下 public interface subInterface {static void show2() {System.out.println("来自接口的静态方法");} }这里 我们就将 sho…

【C递归和迭代】兔子繁殖问题、青蛙跳台阶问题和汉诺塔问题

【C递归】前言一、兔子繁殖问题&#xff08;一&#xff09;题目描述&#xff08;二&#xff09;解题1.递归做法&#xff08;1&#xff09;成兔&#xff08;i&#xff09;分析&#xff08;ii&#xff09;代码&#xff08;iii&#xff09;代码分析&#xff08;2&#xff09;幼兔&…