当前位置: 首页 > news >正文

【大白话解析】 OpenZeppelin 的 ECDSA 库:以太坊签名验证安全工具箱(附源代码)

OpenZeppelin 的 ECDSA 库,简单说就是 ​​“以太坊签名的验真工具”​​ —— 专门用来确认“某条消息是不是某个以太坊地址的主人签的”,核心解决了区块链世界里“身份验证”的问题(毕竟没有中心化机构,只能靠密码学证明身份)。

一、先搞懂基础:什么是 ECDSA?

为什么需要 ECDSA?

在去中心化的区块链世界中,不存在中心化机构(如银行)为账户提供信任背书。当用户发起交易、授权操作或提交链上声明时,必须通过密码学手段证明“该操作确实由对应私钥的持有者发起”。ECDSA 就是实现这一目标的标准化算法:​​私钥持有者可通过私钥对任意消息生成唯一签名,而任何人均能通过公钥(或衍生出的以太坊地址)验证该签名是否合法​​。

现实类比:从“银行印章”到“数字签名”

在传统场景中,若你收到一封自称来自银行的邮件,可通过邮件末尾的“专属印章”(如银行公章)判断其真实性——印章是银行私有的,只有银行能生成,他人难以伪造。

在以太坊中,这一逻辑被密码学实现:

  • ​“专属印章”​​ = 私钥对特定消息生成的数字签名(通常为 65 字节,由 rsv三个关键参数组成);

  • ​“验印章”​​ = 通过 ECDSA 算法及对应的公钥(或由公钥推导的以太坊地址),验证该签名是否由指定私钥生成,从而确认消息的真实来源。


二、库的核心作用:3 件事

这个库本质就是把复杂的密码学逻辑封装成简单函数,帮开发者做 3 件关键事:

  1. ​恢复签名者地址​​:给“消息哈希”和“签名”,算出是谁签的字(返回地址);

  2. ​校验签名合法性​​:排除“假签名”“篡改过的签名”;

  3. ​解析签名格式​​:支持标准签名(65字节)和短签名(64字节,ERC-2098 标准),把签名拆成 r、s、v 三个基础部分。


三、关键概念:签名的 3 个零件(r、s、v)

不管是标准签名还是短签名,最终都要拆成这三个零件才能验证,先搞懂它们是啥:

  • ​r​​:32字节,签名的“随机分量”(椭圆曲线上的一个点的 x 坐标);

  • ​s​​:32字节,签名的“证明分量”(用来证明私钥确实参与了签名);

  • ​v​​:1字节,“恢复标识”(只有 27 或 28 两个值,用来解决椭圆曲线的“二义性”——同一个 x 坐标可能对应两个 y 坐标,v 告诉我们是哪一个)。

两种签名格式

  • ​标准签名(65字节)​​:直接存 r(32) + s(32) + v(1),简单直接;

  • ​短签名(64字节,ERC-2098)​​:把 v 嵌到 s 的“最高位”(因为 s 的最高位本来就是 0,浪费可惜),变成 r(32) + vs(32)(vs 里包含了 v 和 s),省 1 字节 gas。


四、核心函数

库的函数看似多,其实都是围绕“恢复地址”和“校验签名”展开,按常用程度排序:

1. recover(bytes32 hash, bytes memory signature)→ 直接恢复签名者(失败就报错)

功能

给“消息哈希”和“签名”,直接返回签名者地址;如果签名是假的、长度不对,就直接抛出错误(比如“签名无效”“签名长度不对”)。

例子

假设你是 DApp 开发者,用户提交了一个“签名后的转账授权”,你要确认这个授权是不是用户本人签的:

// 1. 先把用户要授权的内容(比如“给地址A转1个ETH”)算成哈希
bytes32 messageHash = keccak256("给0x123...转1 ETH");
// 2. 用户提交的签名(65字节)
bytes memory userSignature = "0x..."; // 用户用私钥签出来的字符串
// 3. 用ECDSA库恢复签名者
address signer = ECDSA.recover(messageHash, userSignature);
// 4. 确认签名者是当前用户(比如连接钱包的地址)
if (signer != msg.sender) {revert("不是本人签名,拒绝授权!");
}
注意
  • 必须先把“原始消息”算成哈希(比如用 keccak256),不能直接传原始消息——否则攻击者能伪造签名;

  • 签名必须是 65 字节(标准)或 64 字节(短签名),其他长度会报错。


2. tryRecover(bytes32 hash, bytes memory signature)→ 尝试恢复地址(失败不报错,返回错误原因)

功能

recover一样恢复地址,但更“温和”——不会直接报错,而是返回「恢复的地址 + 错误类型 + 错误详情」,适合需要自定义错误处理的场景。

例子

比如你做一个“批量验证签名”的功能,不想因为一个无效签名导致整个批量任务失败,就用这个函数:

// 遍历批量提交的签名
for (uint i = 0; i < signatures.length; i++) {bytes32 hash = hashes[i];bytes memory sig = signatures[i];// 尝试恢复,不直接报错(address signer, ECDSA.RecoverError err, bytes32 errArg) = ECDSA.tryRecover(hash, sig);// 根据错误类型处理if (err == ECDSA.RecoverError.NoError) {// 恢复成功,记录签名者validSigners.push(signer);} else if (err == ECDSA.RecoverError.InvalidSignatureLength) {// 签名长度不对,记录错误(比如长度是63字节)errors.push(string(abi.encodePacked("签名", i, "长度不对:", uint256(errArg))));} else {// 其他错误(比如签名无效)errors.push(string(abi.encodePacked("签名", i, "无效")));}
}
错误类型说明

函数返回的 RecoverError是个枚举,包含 4 种情况:

  • NoError:没问题,恢复成功;

  • InvalidSignature:签名无效(比如恢复出的地址是 0x0);

  • InvalidSignatureLength:签名长度不对(比如传了 60 字节);

  • InvalidSignatureS:s 值不合法(s 必须小于一个固定大数字,避免“签名篡改”)。


3. parse(bytes memory signature)→ 拆分签名成 r、s、v

功能

把签名(不管是 65 字节还是 64 字节)拆成 r、s、v 三个基础零件,方便后续自定义处理(比如手动调用 ecrecover预编译合约)。

例子

假设你需要手动处理短签名的 r 和 vs,就可以先用 parse拆分:

// 用户提交的64字节短签名
bytes memory shortSignature = "0x..."; // 64字节,r + vs
// 拆分签名
(uint8 v, bytes32 r, bytes32 s) = ECDSA.parse(shortSignature);
// 之后可以手动用 r、s、v 恢复地址
address signer = ECDSA.recover(messageHash, v, r, s);

4. 其他辅助函数

  • recoverCalldata/ tryRecoverCalldata/parseCalldata:和 recover/ tryRecover/parse功能一样,只是签名存在 calldata里(适合外部传入的临时数据,比 memory更省 gas);

  • recover(bytes32 hash, bytes32 r, bytes32 v,bytes32 s)/tryrecover(...):专门处理标准签名的恢复(直接传 r、v和s,不用先拆分)。

  • recover(bytes32 hash, bytes32 r, bytes32 vs)/tryrecover(...):专门处理短签名的恢复(直接传 r 和 vs,不用先拆分)。


五、安全细节:库帮你避坑

这个库最贴心的地方是“帮你处理了密码学里的坑”,不用你自己写安全逻辑:

  1. ​拒绝“可篡改签名”​​:强制 s 值必须小于「secp256k1 曲线阶的一半」(那个很长的数字 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0),避免攻击者把一个签名改成另一个有效的签名;

  2. ​校验 v 值​​:确保 v 只能是 27 或 28(有些旧库可能生成 0/1,库会自动转换成 27/28);

  3. ​处理短签名兼容​​:自动识别 64 字节的 ERC-2098 短签名,不用你手动判断格式。

优点总结

优点

说明

对开发者意味着什么

✅ 安全

经过安全审计,避免常见签名漏洞(比如 s 值不安全、签名伪造等)

你不用自己操心安全细节,官方已经帮你挡住了大部分攻击

✅ 标准化

提供统一的函数接口,比如 recover()tryRecover()parse()

不同项目、不同团队写法一致,代码可读性高,易于协作

✅ 功能全面

支持标准签名、短签名(ERC-2098)、错误处理、解析、恢复

一个库解决签名相关的所有常见问题

✅ 易用性

函数命名清晰,参数直观,返回值明确

你只需要传 hash 和签名,就能拿到地址,或者知道哪里出错了

✅ Gas 优化

支持短签名等优化格式,减少不必要的字节浪费

在高频或批量签名场景帮你省 gas

✅ 可扩展

清晰的模块化设计,易于集成与二次开发

你可以只用其中一部分,也可以继承扩展


六、实际应用场景举例

场景

用 ECDSA 库的哪个功能?

说明

用户用 MetaMask 签名登录

tryRecover()或 recover()

验证用户签名,确认身份

用户授权提币 / 操作

tryRecover()+ 地址比对

确保操作是用户本人发起

批量验证多个签名

tryRecover()循环调用

比如 DAO 投票、多签

前端生成签名,后端/合约验证

parse()tryRecover()

兼容不同格式的签名

想省 1 字节 gas(短签名)

原生支持 ERC-2098 格式

自动解析 r + vs

想友好提示用户签名错误

tryRecover()或 _throwError()

不直接炸,而是返回或提示错误原因


七、源代码

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;// 这个库用来处理以太坊常用的ECDSA签名验证
// 简单说就是用来确认"某个消息是不是某个地址的主人签的"
library ECDSA {// 定义签名恢复过程中可能出现的错误类型(枚举)enum RecoverError {NoError,                    // 没有错误InvalidSignatureLength,     // 签名长度不对InvalidSignatureS,          // s 值不安全(超出曲线范围)InvalidSignature            // 签名本身无效(格式错误、无法恢复地址等)}// 定义错误类型,用于 revert 抛出更清晰的错误信息// 这些是 Solidity 0.8.4+ 支持的原生 error 语法,比 require(msg) 更高效、更省 gaserror ECDSAInvalidSignature();                    // 签名无效error ECDSAInvalidSignatureLength(uint256 length); // 签名长度错误,并返回具体长度error ECDSAInvalidSignatureS(bytes32 s);          // s 值不合法,返回有问题的 s// 函数名:tryRecover// 功能:根据一个消息的哈希值和一段签名,尝试还原出签名者的地址(也就是谁签的名)// 输入://   - hash: 被签名的消息的哈希值(通常是用 keccak256 计算出来的)//   - signature: 签名数据,一般是以太坊标准的 65 字节签名(包含 r, s, v)// 返回值://   - recovered: 成功时是签名者的地址,失败时是 address(0)(零地址)//   - err: 错误类型(比如签名长度不对等等)//   - errArg: 和错误相关的一些额外信息(比如错误的签名长度值)function tryRecover(bytes32 hash,         // 消息的哈希值bytes memory signature // 签名数据(一般是65字节)) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {// 先检查传进来的签名数据长度是不是 65 字节// 为什么是 65 字节?因为以太坊标准的签名格式就是由 3 部分组成,总共 65 字节://   - r: 32 字节//   - s: 32 字节//   - v: 1 字节// 如果不是 65 字节,说明这个签名格式很可能不对,直接报错if (signature.length == 65) {// 定义三个变量,用来存放解析出来的签名组成部分bytes32 r; // 签名中的 r 部分,32 字节bytes32 s; // 签名中的 s 部分,32 字节uint8 v;   // 签名中的 v 部分,1 字节(通常取值 27 或 28,或者 EIP-155 后的其他值)// 下面的代码使用了 Solidity 的内联汇编(assembly),目的是更高效地从 bytes 类型的签名中提取 r, s, v// 因为 Solidity 对 bytes 的操作比较麻烦,尤其是按固定偏移量取数据,用汇编更直接、更高效assembly ("memory-safe") {// 从签名数据中提取 r(第1部分,32字节)// 签名在 memory 中的布局:// - 第 0x00 ~ 0x1F 字节:是 length(签名总长度,这里是 65,但我们不关心)// - 第 0x20 ~ 0x3F 字节:是 r(32字节)// 所以用 add(signature, 0x20) 找到 r 的起始位置,然后用 mload 读取 32 字节r := mload(add(signature, 0x20))// 从签名数据中提取 s(第2部分,32字节)// s 的起始位置是 0x20 + 0x20 = 0x40(即第64字节处,前64字节是 r)s := mload(add(signature, 0x40))// 从签名数据中提取 v(第3部分,1字节)// v 的起始位置是 0x60(即第96字节处,前面 95 字节是 r + s)// mload(add(signature, 0x60)) 会读取从 0x60 开始的 32 字节,但我们只需要其中的第1个字节// 所以用 byte(0, ...) 来提取该 32 字节中的第一个字节,也就是 vv := byte(0, mload(add(signature, 0x60)))}// 至此,我们已经成功从 65 字节的签名中提取出了 r, s, v 三个部分// 接下来,调用另一个同名的重载函数 tryRecover(hash, v, r, s),将解析出来的数据传给它// 这个重载函数才是真正利用 r, s, v 和 hash 去计算并尝试恢复出签名者地址的地方return tryRecover(hash, v, r, s);} else {// 如果签名长度不是 65 字节,说明这个签名格式不符合以太坊标准// 比如有人传了 64 字节、70 字节,或者其他奇怪长度,那肯定不能正常解析// 这种情况下,我们直接返回一个错误,告诉调用者:“签名长度不对”// 返回内容解释:// - recovered: address(0),无效地址// - err: RecoverError.InvalidSignatureLength,错误类型是“签名长度无效”// - errArg: bytes32(signature.length),把实际的签名长度作为额外错误信息传回去(打包成 bytes32)return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));}}/*** 和上面的函数功能一样,只是签名存在calldata里(更省gas)* calldata适合存储外部传入的临时数据*/function tryRecoverCalldata(bytes32 hash,bytes calldata signature) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {if (signature.length == 65) {bytes32 r;bytes32 s;uint8 v;// 从calldata里取r、s、v的值assembly ("memory-safe") {r := calldataload(signature.offset)s := calldataload(add(signature.offset, 0x20))v := byte(0, calldataload(add(signature.offset, 0x40)))}return tryRecover(hash, v, r, s);} else {return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));}}/*** 从哈希和签名中恢复签名者地址* 如果验证失败会直接报错(revert),而不是返回错误信息* * 适用场景举例:* - 当你确定签名一定是有效的,一旦无效就说明数据被篡改或非法,应该直接阻止后续逻辑(比如权限校验、提现签名等)* - 不需要向用户展示具体错误原因,出错就终止交易* * 对比 tryRecover():* - tryRecover(): 验证签名,出错时返回错误码,你可以自己决定如何处理(比如提示用户)* - recover(): 验证签名,出错时直接 revert() 抛异常,适合“必须成功否则失败”的严格场景*/function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {// 🔍 第一步:调用 tryRecover() 尝试从 hash 和签名中恢复出签名者地址// tryRecover() 是一个更安全的函数,它会:// 1. 检查签名格式对不对(比如是不是65字节)// 2. 解析出 v, r, s// 3. 检查 s 是否在安全范围内(防止签名伪造)// 4. 调用 ecrecover 预编译合约,根据哈希和签名恢复出地址// // 它的返回值有三个:// - recovered: 成功时是签名者地址,失败时是 address(0)// - error: 错误类型,比如 InvalidSignatureLength、InvalidSignatureS 等// - errorArg: 和错误相关的额外信息,比如错误的签名长度(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);// 🚨 第二步:调用 _throwError() 处理可能发生的错误// 如果 tryRecover() 返回的 error 不是 "NoError"(也就是签名验证失败了,比如格式错、v/r/s不对等),// 那么 _throwError() 会直接 revert(),也就是抛出异常,让整个交易回滚!// // 这就是这个函数叫做 "recover" 而不是 "tryRecover" 的原因:// 它不给你机会处理错误,而是遇到任何问题直接“炸掉”,适合必须校验通过的场景_throwError(error, errorArg);// ✅ 第三步:如果上面的 _throwError() 没有 revert,说明签名是有效的!// 此时 recovered 就是成功恢复出来的签名者地址(比如 0x742d...)// 我们把这个地址直接返回给调用者,比如用于权限判断、提现授权等return recovered;}/*** 和上面的recover功能一样,签名存在calldata里*/function recoverCalldata(bytes32 hash, bytes calldata signature) internal pure returns (address) {(address recovered, RecoverError error, bytes32 errorArg) = tryRecoverCalldata(hash, signature);_throwError(error, errorArg);return recovered;}/*** 处理ERC-2098短签名(64字节)的恢复* 短签名把 v 和 s 合并成了一个 32 字节的 vs,节省了 1 字节* * 传统签名格式是 65 字节:r (32) + s (32) + v (1)* 短签名格式是 64 字节:r (32) + vs (32),其中:*   - vs = s (255位) + v (1位,最高位)*   - 即:vs 是一个 256 位的值,其中:*       - 低 255 位(第1 ~ 255 位)存放的是原来的 s(和标准签名中的 s 完全一样)*       - 最高 1 位(第 256 位)存放的是 v(0 或 1,最终会转换为标准的 27 或 28)* * 为什么叫“低255位”?* - 一个 bytes32(即 32 字节 / 256 位)的数据,它的位编号可以从第 1 位到第 256 位* - “低255位” = 第 1 位 ~ 第 255 位,也就是除了最高位(第 256 位)之外的所有位* - 在这个 vs 中,低 255 位存的是 s,最高位(第 256 位)存的是 v* * 通过位运算,我们可以:*   - 用掩码 0x7ffffffff...(255个1 + 最后1个0)提取低255位 → 得到 s*   - 用 >> 255 把最高位移到最低位,再 + 27 → 得到标准 v(27 或 28)*/function tryRecover(bytes32 hash,bytes32 r,    // 签名中的 r 部分,32 字节,和标准签名保持一致bytes32 vs    // 签名中的 "vs" 部分,32 字节,是 v 和 s 的组合(ERC-2098 格式)) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {unchecked { // 关闭溢出检查,节省 gas。因为我们确定右移和位操作不会溢出// 从 vs 中提取 s(也就是原来的 s,256位中的低 255 位)// 我们用一个掩码:0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff// 这个掩码的二进制是:前面 255 个 1,最后一个 0(即 2^255 - 1)// 用 vs & 这个掩码,就可以把第 256 位(最高位,v)清零,只保留低 255 位 → 得到的就是 s!bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);// 从 vs 中提取 v(最高位,第 256 位)// 先将 vs 转成 uint256,然后右移 255 位 → 把第 256 位移到最低位// 然后取出该位的值(0 或 1),再加上 27,就得到了 EVM 标准中要求的 v 值:27 或 28uint8 v = uint8((uint256(vs) >> 255) + 27);// 至此,我们已经从 64 字节短签名(r + vs)中成功还原出了:// - r(原样传入)// - s(从 vs 的低 255 位提取)// - v(从 vs 的最高位提取并转换成标准值 27 或 28)// 然后调用标准的 tryRecover(hash, v, r, s) 去恢复签名者地址return tryRecover(hash, v, r, s);}}/*** 和上面的recover功能类似,但是是处理短签名的恢复,失败会直接报错*/function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);_throwError(error, errorArg);return recovered;}/*** @dev 通过哈希值 + 签名中的 v, r, s 三个参数,直接调用 ecrecover 预编译合约,尝试恢复签名者地址* 这是最底层的签名恢复函数,带有基本的安全检查(比如 s 值范围)* * 应用场景举例:* 比如你有一个消息 "Transfer 1 ETH",用私钥签名后得到了 v, r, s。* 现在别人给你 v, r, s 和这个消息的哈希值,你就可以用这个函数来验证到底是谁签的。*/function tryRecover(bytes32 hash,  // 🧩 被签名消息的哈希值(比如 keccak256("Hello") 的结果)uint8 v,       // 🧩 签名的一部分,1 字节,通常为 27 或 28,也可能因 EIP-155 而变化,用于正确恢复公钥和链 IDbytes32 r,     // 🧩 签名的一部分,32 字节,ECDSA 签名结果的其中一半bytes32 s      // 🧩 签名的一部分,32 字节,ECDSA 签名结果的另一半) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {// ---------------------------------------------// 第一步:安全检查 —— 检查 s 值是否在合法范围内// ---------------------------------------------// secp256k1 是以太坊使用的椭圆曲线,它有一个固定的“阶数”(n),是一个非常大的整数// s 必须小于 n 的一半(即 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0)// 这是为了防止“签名可塑性攻击”和保证签名在曲线的“安全区域”内// 如果 s 超过了这个最大允许值,说明这个签名不安全,可能是恶意构造的,直接拒绝if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {// 返回错误:InvalidSignatureS,表示 s 值不合法// 同时把有问题的 s 值也返回,方便调试return (address(0), RecoverError.InvalidSignatureS, s);}// ---------------------------------------------// 第二步:调用 EVM 原生预编译合约 ecrecover 来恢复签名者地址// ---------------------------------------------// ecrecover 是 EVM 提供的一个预编译合约(编号 1),功能非常强大:// 它能根据 “消息哈希 + v + r + s” 这 4 个参数,逆向计算出签名时所用的公钥,// 然后再从公钥推导出 Ethereum 地址(就是 20 字节的那个)//// 这是 Solidity / EVM 中最底层的“从签名还原地址”的方法address signer = ecrecover(hash, v, r, s);// 如果 ecrecover 返回的是零地址(0x000...000),说明:// - 签名格式错误// - v, r, s 不匹配// - 哈希值与签名不对应// - 或者其它原因导致无法恢复出有效公钥if (signer == address(0)) {// 返回错误:InvalidSignature,表示签名本身无效,无法恢复出地址return (address(0), RecoverError.InvalidSignature, bytes32(0));}// ---------------------------------------------// 第三步:一切正常,返回签名者地址// ---------------------------------------------// 如果 ecrecover 成功返回了一个非零地址,说明我们成功还原出了签名者// 返回:// - recovered: 签名者地址(比如 0x742d...)// - err: RecoverError.NoError,表示没有发生任何错误// - errArg: bytes32(0),无附加错误信息return (signer, RecoverError.NoError, bytes32(0));}/*** 和上面的recover功能类似,但是是直接用v、r、s恢复签名者,失败会报错*/function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);_throwError(error, errorArg);return recovered;}/*** 把签名解析成 v、r、s 三个分量* 支持两种签名格式:* 1. 65字节标准签名:r(32字节) + s(32字节) + v(1字节) —— 传统格式* 2. 64字节短签名:r(32字节) + vs(32字节) —— ERC-2098 格式,其中 vs = s(255位) + v(1位,最高位)* * 作用:根据签名的长度,自动判断格式,然后提取出 v、r、s,用于后续的签名恢复(比如传给 tryRecover)* * 例子:* - 如果你从用户那里拿到一个签名(比如前端用 ethers.js / web3.js 签的),你可以直接传给这个函数* - 它会帮你解析出 v、r、s,然后你就可以调用 tryRecover(hash, v, r, s) 去恢复签名者地址* * 如果签名长度不是 64 也不是 65,说明格式不对,函数会返回 v=0, r=0, s=0(相当于无效签名)*/function parse(bytes memory signature) internal pure returns (uint8 v, bytes32 r, bytes32 s) {assembly ("memory-safe") {// 🔍 第一步:读取签名的第一个 word(前 32 字节),它存储的是签名的总长度(比如 64 或 65)// mload(signature) → 读取 signature 这个 bytes 在内存中的第 0~31 字节,也就是它的 length 字段switch mload(signature)// ✅ 情况 1:签名长度是 65 字节(标准格式:r + s + v)case 65 {// r 是签名中的第 1 部分,位于内存中的第 0x20 ~ 0x3F 字节(即第32~63字节)// add(signature, 0x20) 是跳过前32字节的长度字段,指向数据起始位置// mload(add(signature, 0x20)) 读取第 0x20 ~ 0x3F 的 32 字节 → 就是 rr := mload(add(signature, 0x20))// s 是签名中的第 2 部分,位于第 0x40 ~ 0x5F 字节(第64~95字节)s := mload(add(signature, 0x40))// v 是签名中的第 3 部分,位于第 0x60 ~ 0x63 字节(第96~99字节),但只有第1个字节有效// mload(add(signature, 0x60)) 读取第 0x60 开始的 32 字节,但我们只需要第1个字节// byte(0, ...) 表示取该 32 字节中的第 0 个字节(也就是 v)v := byte(0, mload(add(signature, 0x60)))}// ✅ 情况 2:签名长度是 64 字节(ERC-2098 短签名:r + vs)case 64 {// vs 是签名中的第 2 部分,位于第 0x40 ~ 0x5F 字节,是一个 32 字节值// 它里面合并了 s(低255位)和 v(最高1位)let vs := mload(add(signature, 0x40))// r 是第 1 部分,和标准格式一样,位于第 0x20 ~ 0x3F 字节r := mload(add(signature, 0x20))// 🔧 提取 s(低255位):// vs 是一个 256 位的值,其中低 255 位是 s,最高位是 v// 我们用一个掩码:shr(1, not(0)),其实就是 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF(255个1)// 等价于:2^255 - 1,二进制是 255 个 1// 用 vs & 掩码,就可以把第 256 位清零,只保留低 255 位 → 得到 ss := and(vs, shr(1, not(0)))// 🔧 提取 v(最高1位):// 先将 vs 右移 255 位 → 把第 256 位移到最低位// 然后 + 27 → 转换成标准的 v 值(27 或 28),EVM 的 ecrecover 要求 v 是 27 或 28v := add(shr(255, vs), 27)}// ❌ 默认情况:签名长度既不是 64 也不是 65,说明格式不对,无法解析default {// 返回无效值:v = 0, r = 0, s = 0r := 0s := 0v := 0}}}/*** 和上面的parse功能一样,处理calldata里的签名*/function parseCalldata(bytes calldata signature) internal pure returns (uint8 v, bytes32 r, bytes32 s) {assembly ("memory-safe") {switch signature.lengthcase 65 {r := calldataload(signature.offset)s := calldataload(add(signature.offset, 0x20))v := byte(0, calldataload(add(signature.offset, 0x40)))}case 64 {let vs := calldataload(add(signature.offset, 0x20))r := calldataload(signature.offset)s := and(vs, shr(1, not(0)))v := add(shr(255, vs), 27)}default {r := 0s := 0v := 0}}}/*** 根据错误类型抛出对应的错误* 把错误处理集中在这里,避免重复代码* * 作用:* - 当签名验证过程中发生错误时(比如格式不对、长度不对、s 值不安全等),*   不是在每个函数里都写一遍 if (error) revert(...),*   而是统一调用这个 _throwError 函数,传入错误类型和附加信息,*   由这个函数来决定具体 revert 哪个错误。* * 优点:* - 避免代码重复* - 集中管理错误信息,便于后续国际化、日志、监控等扩展* - 提升代码可读性与可维护性* * 参数:* - error: 表示错误类型的枚举值,比如 RecoverError.InvalidSignature* - errorArg: 和错误相关的额外数据,比如错误的签名长度、s 值等,可能用于构造更详细的错误信息*/function _throwError(RecoverError error, bytes32 errorArg) private pure {// 如果错误类型是 NoError,说明一切正常,没有任何问题,直接返回,不抛出任何错误if (error == RecoverError.NoError) {return; // 没问题就啥也不做} // 如果错误类型是 InvalidSignature,说明签名本身无效(比如格式错误、无法恢复出地址等)else if (error == RecoverError.InvalidSignature) {// 直接 revert 一个标准的错误:ECDSAInvalidSignature()// 这通常是一个自定义的错误类型(enum 或 error keyword),在合约顶部定义,比如:// error ECDSAInvalidSignature();revert ECDSAInvalidSignature();} // 如果错误类型是 InvalidSignatureLength,说明传入的签名长度不对(比如不是64也不是65字节)else if (error == RecoverError.InvalidSignatureLength) {// revert 一个带参数的错误:ECDSAInvalidSignatureLength(uint256(errorArg))// errorArg 在这里通常是签名的实际长度,比如传进来的是 bytes32(66),我们转成 uint256 显示具体值revert ECDSAInvalidSignatureLength(uint256(errorArg));} // 如果错误类型是 InvalidSignatureS,说明签名中的 s 值不在安全范围内(比如大于曲线阶的一半)else if (error == RecoverError.InvalidSignatureS) {// revert 一个带参数的错误:ECDSAInvalidSignatureS(errorArg)// errorArg 在这里是导致问题的 s 值本身,可用于调试或记录revert ECDSAInvalidSignatureS(errorArg);}}
}

http://www.dtcms.com/a/350338.html

相关文章:

  • ElasticSearch数据库(ES数据库)是什么???
  • docker安装ros
  • 网络编程1-基本概念、函数接口
  • 页面中嵌入Coze的Chat SDK
  • Hazelcast
  • Docker:数据卷(挂载本地目录)
  • FFmpeg 8.0 “Huffman“ 全面评测:Vulkan 加速、AI 集成与编解码革新
  • 8月25日
  • UPROPERTY的再次学习
  • 高通SNPE测试:6、在开发板上运行Inception v3 Model(oe-linux)
  • vite + react + tailwind(2025-08-25)
  • C++贪吃蛇---详细步骤
  • 2.4 Flink运行时架构:Task、SubTask、ExecutionGraph的关系
  • OPcache 高级技术文档:原理、监控与优化实践
  • Unity使用Sprite切割大图
  • JavaScript 性能优化实战:从理论到落地的技术文章大纲
  • 基于长短期记忆网络的多变量时间序列预测 LSTM
  • Redis 哨兵 Sentinel
  • 【沉浸式解决问题】NVIDIA 显示设置不可用。 您当前未使用连接到NVIDIA GPU 的显示器。
  • 实时监测蒸汽疏水阀的工作状态的物联网实时监控平台技术解析
  • VLLM的加速原理
  • 基于MATLAB实现支持向量机(SVM)进行预测备
  • 大模型的多机多卡训练
  • 神经网络|(十五)概率论基础知识-协方差标准化和皮尔逊相关系数
  • 亚马逊AWD美西新仓上线:旺季备货的效率革命与策略升级
  • 真实应急响应案例记录
  • 机器学习笔记
  • Neumann Networks for Linear Inverse Problems in Imaging论文阅读
  • CF2133D 鸡骑士
  • 基于遗传算法优化BP神经网络的时间序列预测 GA-BP