说明
NFT项目中必不可少的是白名单的验证。白名单的验证一般都是有两种做法,Merkle Tree和签名验证。本篇文章主要展示签名验证的做法。
在此之前,我们需要明确一个概念。账户一般都有公钥和私钥。我们平时用作地址转账的就是公钥,而合约部署这种就需要用到我们的私钥。公钥和私钥是一一对应的。私钥可以推导出公钥,但是公钥却不能推导出公钥。
如何证明某个地址(公钥)是某个人的呢?我们给他一段信息,让他用自己的私钥进行签名。然后我们得到这个签名结合这个人的地址(公钥),即可判断这个签名的信息是不是正确的。概括下:
带验证的人: 私钥 + 明文 = 签名信息
验证方法: 明文 + 签名信息 = 公玥
如果 公玥 和 私钥对的上,那么就说明这个签名是由这个人签名的。
签名验证示例
关于合约验证的方法,参考 OpenZeppelin 的 ECDSA 库来进行示范。
1 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { |
这个方法是用来验证签名的。此方法接受两个参数:
- hash,需要验证的信息的hash值(可以理解为明文)
- signature,签名信息
通过(address recovered, RecoverError error) = tryRecover(hash, signature); 尝试还原出签名的地址。如果还原成功,那么就返回这个地址。如果还原失败,那么就抛出错误。
如果我们最后验证了这个签名的地址和私钥的地址所对应的公玥是一致的,那么就说明这个签名是由这个人签名的。
验证步骤
NFT实际项目中的白名单的验证步骤如下:
- 项目方后台数据库中保存所有的白名单用户
- 用户在网站连接钱包后,前端将用户地址发送给后端
- 后端检查该地址是否是白名单用户,如果是,则用后端的管理员私钥对地址进行签名
- 后端返回签名数据,同样,前端会将签名数据传给合约验证
- 合约验证通过,则用户可以白名单 mint
实际案例
配合前面说的验证步骤,下面的这张图片也更加容易理解。

在此过程中,我们假设需要假设以下信息:
- 用户的地址:
0x70997970c51812dc3a010c7d01b50e0d17dc79c8 - 合约的地址:
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - 合约owner的地址:
0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
整体的思路是,使用Python代码对用户进行签名,合约判断是不是对应的实际用户.一般情况下.如果签名信息仅仅是对合约发送者进行签名,这可能会造成签名信息被重复利用从而造成重放攻击。因此我们一般在实际项目开发中都会加上一些其他的信息,比如合约本身的地址(这样签名就确认只能应用于这个合约),或者盐(随机数确保随机)。在本例中会加上合约地址作为签名信息的一部分.
Python签名验证的代码:
1 | def sign_address(): |
得到最终的签名信息是: 0x125521c93d39fbed27f4df56e424c3dd8c8b26143276ee59525157e3582cac95067ab4008f7e82086ccd8eb73a48358d19faa39acab9ab6f7d9ae2e30b5d42341b
合约的代码是:
1 | pragma solidity 0.8.13; |
我们使用cast call的方式调用合约的verify(bytes)方法校验我们的签名是否正确:
1 | cast call --rpc-url your_rpc_url 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 "verify(bytes)(bool)" 0x125521c93d39fbed27f4df56e424c3dd8c8b26143276ee59525157e3582cac95067ab4008f7e82086ccd8eb73a48358d19faa39acab9ab6f7d9ae2e30b5d42341b --from 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 |
其中:
- 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512,是合约地址
- verify(bytes)(bool), 函数原型
- 0x125521c93d39fbed27f4df56e424c3dd8c8b26143276ee59525157e3582cac95067ab4008f7e82086ccd8eb73a48358d19faa39acab9ab6f7d9ae2e30b5d42341b 待校验的签名信息
- 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 表示msg.sender
调用的方法返回True,说明签名校验成功。
除了使用cast call的方式调用合约的方法外,还可以创建TestSig.t.sol,编写测试用例来验证签名是否正确。
1 | pragma solidity ^0.8.13; |
同样返回的结果也是true.
总结
本篇文章就是对于nft使用签名校验的原理说明以及一个简单的案例展示。
参考:
https://mirror.xyz/xyyme.eth/-e1FodE7HZcwhw60VuGnbUue2SfCN4kn6JVg0JjCFS4