Schnorr 签名验证 ecrecover 破解

本文探讨了如何利用以太坊的 ecrecover 函数验证 Schnorr 签名。通过将 Schnorr 签名的验证过程与 ecrecover 的处理相结合,提供了一种低成本的签名验证方法,并提供了具体的 Solidity 实现代码。文中还引用了 Chainlink 的相关实现与安全性的注意事项。

这个想法基于 Vitalik 在这里的帖子:https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384

然而,它不是使用 ecrecover 来执行 ECMUL,而是可以被黑客攻击以便廉价验证 Schnorr 签名。

背景:Schnorr

签名

给定消息 m、私钥 x 和 hash-to-scalar 函数 h,从字段中随机选择值 kG 是曲线生成元。

还定义函数 address(),该函数返回给定一个点的 20 字节以太坊地址。 (注意:这仅在黑客攻击中需要)

R = G*k
e = h(address(R) || m)
s = k + x*e
签名 = (e, s) 或 (R, s)

验证

给定签名 (R, s)、消息 m 和公钥 P

e = h(address(R) || m)
R' = G*s - P*e
检查 R == R'

或者,给定签名 (e, s)

R = G*s - P*e
e' = h(address(R) || m)
检查 e' == e

背景:ecrecover

以太坊 ecrecover 返回一个地址(公钥的哈希),给定 ECDSA 签名。

给定消息 m 和 ECDSA 签名 (v, r, s),其中 v 表示点的 y 坐标的奇偶性,该点的 x 坐标为 r

ecrecover(m, v, r, s):
R = 从 r 和 v 派生的点
a = -G*m
b = R*s
Qr = a + b
Q = Qr * (1/r)
Q = (1/r) * (R*s - G*m) // 恢复的公钥

以太坊的 ecrecover 返回 64 字节公钥的 keccak256 哈希的最后 20 字节(请参见 https://github.com/ethereum/go-ethereum/blob/eb948962704397bb861fd4c0591b5056456edd4d/crypto/crypto.go#L275

黑客攻击

给定签名 (R, s)、消息 m 和公钥 P,我们可以将值输入到 ecrecover 中,以便返回的地址可以用于与挑战进行比较。

计算 e = H(address(R) || m)P_x = P 的 x 坐标

传入:

m = -s*P_x

v = P 的奇偶性

r = P 的 x 坐标

s = -e*P_x

然后:

ecrecover(m=-s*P_x, v=0/1, r=P_x, s=-e*P_x):
P = 从 r 和 v 派生的点(公钥)
a = -G*(-s*P_x) = G*s*P_x
b = P*(-m*P_x) = -P*e*P_x
Q = (1/P_x) (a+b)
Q = (1/P_x)(G*s*P_x - P*e*P_x)
Q = G*s - P*e  // 与上面的 schnorr 验证相同

返回值是 address(Q)

  • 计算 e' = h(address(Q) || m)
  • 检查 e' == e 来验证签名。

solidity

// SPDX-License-Identifier: LGPLv3
pragma solidity ^0.8.0;

contract Schnorr {
  // secp256k1 群体顺序
  uint256 constant public Q =
    0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;

  // 奇偶性 := 公钥 y 坐标奇偶性 (27或28)
  // px := 公钥 x 坐标
  // 消息 := 32 字节消息
  // e := schnorr 签名挑战
  // s := schnorr 签名
  function verify(
    uint8 parity,
    bytes32 px,
    bytes32 message,
    bytes32 e,
    bytes32 s
  ) public pure returns (bool) {
    // ecrecover 输入为 (m, v, r, s);
    bytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q));
    bytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q));

    require(sp != 0);
    // ecrecover 预编译实现检查 `r` 和 `s`
    // 输入不为零(在这种情况下,`px` 和 `ep`),因此我们不需要
    // 检查它们是否为零。
    address R = ecrecover(sp, parity, px, ep);
    require(R != address(0), "ecrecover 失败");
    return e == keccak256(
      abi.encodePacked(R, uint8(parity), px, message)
    );
  }
}

参考/注释

  • 原文链接: hackmd.io/@nZ-twauPRISEa...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
nZ-twauPRISEa6G9zg3XRw
nZ-twauPRISEa6G9zg3XRw
江湖只有他的大名,没有他的介绍。