mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2030 字
5 分钟
关于一次EOA钱包的签名验证及其相关内容
2026-06-10

下文只代表笔者当前的认识

========

差不多是对于上一篇文章的理论补充,大概算是把一次签名验证的流程和理论证明理通了,也顺便补了一下群的知识(

dreaife.tokyo
一个对于EOA的EVM钱包登陆界面
以 React/wagmi 实现的 EVM 钱包登录界面为例,拆解连接钱包、服务端签发 nonce、构造 SIWE 消息、钱包签名与后端恢复地址验签的完整流程,并说明“连接地址”与“证明控制权”为何必须分开。
dreaife.tokyo/evm-wallet-login/

预备知识:secp256k1、有限域与椭圆曲线点运算#

因为大量涉及模运算和椭圆曲线的点运算,所以稍微准备了点预备知识(顺便把自己补的内容放上去www。当然如果已经知道的话也可以跳过。

预备知识

Ethereum 钱包签名使用的是 ECDSA,底层曲线是 secp256k1。在理解 r/s/v、公钥恢复、地址生成之前,需要先理解三个对象:

  • 有限域 F_p
  • 椭圆曲线点群 E(F_p)
  • 基点 G 以及它的阶 n

1. secp256k1 的曲线#

secp256k1 定义在一个有限域 F_p 上。它的曲线方程是:

y2x3+7(modp)y^2 \equiv x^3 + 7 \pmod p

其中:

p=2256232977p = 2^{256} - 2^{32} - 977

也就是说,点的坐标不是普通实数,而是模 p 的整数:x,yFpx, y \in \mathbb{F}_p

所以曲线上的点集合是:E(Fp)={(x,y)y2x3+7(modp)}{O}E(\mathbb{F}_p) = \{(x,y) \mid y^2 \equiv x^3 + 7 \pmod p\} \cup \{O\}

这里的 O 是无穷远点,可以理解为点加法里的零元。

2. 椭圆曲线上的点加法#

椭圆曲线点之间可以定义一种“加法”:

P+Q=RP + Q = R

注意,这不是坐标直接相加。也就是说:

(x1,y1)+(x2,y2)(x1+x2,y1+y2)(x_1, y_1) + (x_2, y_2) \neq (x_1 + x_2, y_1 + y_2)

而是通过椭圆曲线群规则计算出另一个曲线上的点。

设:

P=(x1,y1),Q=(x2,y2)P = (x_1, y_1), \quad Q = (x_2, y_2)

P != Q 时,先计算斜率:

λy2y1x2x1(modp)\lambda \equiv \frac{y_2 - y_1}{x_2 - x_1} \pmod p

这里的除法是模 p 下的除法,也就是乘以模逆元。

然后:

x3λ2x1x2(modp)y3λ(x1x3)y1(modp) \begin{aligned} x_3 \equiv \lambda^2 - x_1 - x_2 \pmod p \\ y_3 \equiv \lambda(x_1 - x_3) - y_1 \pmod p \end{aligned}

得到:

P+Q=(x3,y3)P + Q = (x_3, y_3)

P = Q 时,叫点倍加:

2P=P+P2P = P + P

tips<需要注意>,当前加法同样为椭圆曲线加法,而非标量域加法。

此时斜率为:

λ3x122y1(modp)\lambda \equiv \frac{3x_1^2}{2y_1} \pmod p

因为 secp256k1 的曲线是:

y2=x3+7y^2 = x^3 + 7

没有 ax 项,所以这里没有额外的 a

3. 标量乘法#

标量乘法就是重复点加法:

[k]P=P+P++Pk times[k]P = \underbrace{P + P + \cdots + P}_{k \text{ times}}

例如:

[3]P=P+P+P[3]P = P + P + P

在钱包里最重要的关系就是:

Q=[d]GQ = [d]G

其中:

  • d: 私钥,一个标量
  • G: secp256k1 规定好的基点
  • Q: 公钥,一个曲线点

从私钥 d 计算公钥 Q 很快,但从公钥 Q 反推私钥 d 极难。这就是椭圆曲线离散对数问题。

4. 基点 G 和阶 n#

G 是 secp256k1 标准中选定的生成点,也叫基点。它的阶是 n,意思是:

[n]G=O[n]G = O

并且:

G={O,G,[2]G,[3]G,,[n1]G}\langle G \rangle = \{O, G, [2]G, [3]G, \ldots, [n-1]G\}

其中 n 是一个接近 2^256 的大素数。

对于 secp256k1,有一个重要性质:

h=1h = 1

也就是 cofactor 为 1。因此,由 G 生成的阶为 n 的循环群就是整条曲线的点群:

#E(Fp)=n\#E(\mathbb{F}_p) = n

5. p 和 n 的区别#

这里最容易混淆的是 pn

p 是坐标域大小:

x,y(modp)x, y \pmod p

点加法、点倍加里的坐标计算都在 mod p 下进行。

n 是基点 G 的阶:

[n]G=O[n]G = O

私钥、nonce、签名里的 r/s 等标量计算都在 mod n 下进行。

所以可以这样记:

  • 点坐标计算:mod p
  • 标量计算:mod n

Ethereum 钱包里的私钥满足:

1dn11 \le d \le n-1

公钥为:

Q=[d]GQ = [d]G

签名时的随机数或确定性 nonce k 也满足:

1kn11 \le k \le n-1

这套结构就是后续 ECDSA 签名公式、公钥恢复、Ethereum 地址生成的数学基础。

或者也可以看一下我和gemini的对话来补充(有一说一,有了ai,学习速度加速不少

gemini.google.com
gemini.google.com
gemini.google.com/share/f72d7ecbcf76

关于一次EOA钱包的签名和验证#

如上一次文章内容所述,当发生一次需要EOA钱包进行地址的归属验证的时候,会进行下面的流程:

  1. 一个符合SWIE的message被前端发给钱包请求签名

  2. 对于这样一个签名请求,当钱包接收到后,会弹出用户同意的提示

  3. 当用户确认后,此时开始了钱包的签名流程

    • 首先对于SIWE message通过keccak-256来计算出32-byte hash ee 来用于接下来的计算

    • 然后是对于钱包的私钥 ddsecp256k1的有限域 pp 和它的标准选定的基点 GGGG 的阶数 nn ,钱包通过生成一个[1,n-1]范围内的随机数 kk 来进行接下来的验证计算(这里需要注意因为只有n内的[1,n-1]是可用的不重复结果,所以在取点什么的计算的时候选择的是mod n,而整体secp256k1范围计算则是在其本身的规定范围p计算,即mod p

    • 最终生成的签名实际为r/s/vr/s/v这三个计算结果的拼接,分别为32-byte/32-byte/1-byte的内容。对于它们的计算内容:

      • R=kG=(Rx,Ry)R = kG = (R_{x}, R_{y})

      • r=Rxmodnr = R_{x} \bmod n

      • s=k1(z+rd)modns = k^{-1} * (z + r * d) \bmod n

      • vv 则是由于对于一个 secp256k1椭圆曲线

        y2=x3+7modpy^2 = x^3 + 7 \bmod p

        而言,在x=Rxx=R_x上的解,除了无穷远点00以外,还会有两个对于x轴对称的解,vv就是这里通过提供0/1来指定应该选择哪个解的 yParity (当然,对于secp256k1,还有可能存在r+n<pr+n<p的情况,不过由于概率太小,EVM在实际计算时,并没有考虑这种情况的恢复

    • 这样,就生成出来最终钱包返回给服务端的签名r/s/vr/s/v

  4. 然后是服务端对于取到的r/s/vr/s/v进行验证,此时服务端知道的是secp256k1的基点 GG / 发送给钱包的SIWE message和它的keccak-256计算出的hash ee / 来自钱包发回的签名 r/s/vr/s/v

  5. 然后开始服务端的验证:

    • 对于服务端,现在已有 GG / ee / rr / ss / vv ,此时需要知道通过这里的数据计算出的钱包地址是否和开始接受到的钱包地址相同,而根据

      address=keccak256(QxQy)[1232]address = keccak256(Q_x || Q_y)[12\:32]

      (即地址为公钥 QQ 经过keccak-256计算结果hash的后20位),可以知道目前的目标为通过当前条件计算出公钥 QQ

    • 此时除了上述数据外,我们还能知道的是 ss 的变换过程(一个标量域的整数计算),即

      s=k1(z+rd)modns = k^{-1} * (z + r * d) \bmod n

      所以我们同样可以得到一个变形,即

      d=r1(skz)modnd = r^{-1} * (s*k - z) \bmod n

      然后计算进入椭圆曲线点运算的领域,将上式两边同乘基点 GG

      dG=(r1(skz)modn)G=r1(skGeG)\begin{aligned} dG &= \left(r^{-1}(sk - z) \bmod n\right)G \\ &= r^{-1}(skG - eG) \end{aligned}

      同时我们知道,公钥 Q=dGQ = dGR=kGR=kG ,而我们知道r=Rxr=R_x ,通过 vv 可以恢复出 RR。由此,我们可以把上述转换为一个由已知量组成的公钥 QQ 的求解式

      Q=r1(sReG)Q = r^{-1}(sR - eG)

      从而,我们得到了一个经由计算解得的该签名钱包的公钥 QQ

    • 然后再对于解得的公钥 QQ 进行keccak-256和取末尾20位的计算,即可得到验证用的钱包地址。然后再将此地址和之前提供地址进行对比,如果两者相同,即可在不用知道私钥 dd 的情况下,仅依靠已有信息对当前钱包控制权进行验证。同时,由于 Q=dGmodnQ=dG \bmod n作为椭圆曲线(mod n下),从 dGdG (d为在 nn (接近 22562^{256} 的大素数)中)的计算结果可以通过累加法快速到达;而从 Q 反推出 d 属于 secp256k1 上的椭圆曲线离散对数问题。目前没有可行算法能在现实资源内完成<通用攻击复杂度约为> 21282^{128} 级别,而根据热力学,22562^{256}的计算即使用完宇宙所有能量也不可能完成,从而达成在工程上的不可解。

      tips:对于实际上的EVM验证,还有对于SIWE信息内部的domain/address/chainId等验证,此处主要讨论数学上的实现,所以就此跳过

  6. 由此,服务器和钱包间仅通过签名和SIWE message和一些定义的数学信息,而不用通过私钥的暴露,就完成了钱包归属的确认

结语#

上述大概就是一次钱包签名和验证时,在数学上的证明,虽然貌似随着google在量子计算上的发展,可能马上就要变了,不过应该还有不少时间,学一下经典也没啥问题(

以上,大概算是对于钱包签名验证工程实践上的理论基础的一个补充。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

关于一次EOA钱包的签名验证及其相关内容
https://dreaife.tokyo/eoa-sign-verify/
作者
dreaife
发布于
2026-06-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录