Skip to content

Latest commit

 

History

History
1151 lines (787 loc) · 24.2 KB

File metadata and controls

1151 lines (787 loc) · 24.2 KB

椭圆曲线密码学 - 深度数学基础

从问题到解决:数学知识的产生过程


序言:为什么这份文档不同

这份文档不是简单地告诉你"什么是椭圆曲线",而是:

展示问题 - 为什么古人需要解决这个问题 ✅ 推导过程 - 数学家们怎么一步步推导出来的 ✅ 失败的尝试 - 为什么早期方法不行 ✅ 历史背景 - 这些想法是在什么时代产生的 ✅ 相互联系 - 各个概念怎么串联起来的


第一部分:数字、计数和模运算的故事

1.1 最古老的问题:分配

场景:公元前 5000 年的美索不达米亚

一个牧羊人有 23 只羊要分给 5 个儿子。

每个儿子可以得:23 ÷ 5 = 4 只
剩余:3 只

他该怎么办?
- 选项A:3只羊拿去卖掉
- 选项B:一个儿子多给1只,另外两个也多给1只
- 选项C:杀掉剩余的3只?

这个问题很简单,但它衍生出了"余数"的概念。

关键洞察:任何数都可以表示为:

被除数 = 除数 × 商 + 余数

23 = 5 × 4 + 3

1.2 余数的威力:模运算诞生

问题的升级

古埃及人发现了更有趣的事:

如果我每 7 天休息一次,在一个周期中,我的日期永远循环于 0-6。

这启发了一个想法:我们可以忽略"完整的周期",只看余数

例子:星期计算

今天是星期二(第2天)
问:50天后是星期几?

古老方法:50 ÷ 7 = 7 个完整周 + 1 天
所以 50 天后是:星期三(第 2+1 = 3 天)

数学表示:
50 ≡ 1 (mod 7)
2 + 50 ≡ 2 + 1 ≡ 3 (mod 7)

为什么模运算这么有用?

  1. 简化计算:我们不需要处理无限大的数,只需处理有限的"余数集合"

  2. 循环结构:时钟、日历、季节都是循环的——它们本质上是模运算

  3. 加密直觉:如果我只告诉你 50 mod 7 = 1,你无法反推出 50(可能是 1, 8, 15, 22, 29...)

1.3 模运算的规则(为什么这样设计)

加法在模下保持

如果 a ≡ x (mod p) 且 b ≡ y (mod p)
那么 (a + b) ≡ (x + y) (mod p)

为什么?
a = kp + x  (某个k倍的p加x)
b = lp + y  (某个l倍的p加y)

a + b = (k+l)p + (x+y)
     = (k+l)p + (x+y) mod p

"完整周期"被抵消了,只剩余数相加

乘法也保持

如果 a ≡ x (mod p) 且 b ≡ y (mod p)
那么 (a×b) ≡ (x×y) (mod p)

例子:
23 ≡ 3 (mod 10)
17 ≡ 7 (mod 10)

23 × 17 = 391
3 × 7 = 21 ≡ 1 (mod 10)

391 mod 10 = 1 ✓ 一致!

这为什么重要?

这意味着我们可以在 mod 的世界里做完整的算术,规则完全一样。这是解锁密码学的关键。


第二部分:质数——数学的原子

2.1 问题:什么是"不可再分"?

场景:古希腊,约公元前 300 年

欧几里得(Euclid)问了一个基础问题:

所有正整数都是怎么"组成"的?

发现:质数

2 = 只能是 2
3 = 只能是 3
4 = 2 × 2
5 = 只能是 5
6 = 2 × 3
7 = 只能是 7
8 = 2 × 2 × 2
...

定义:质数是大于 1 的自然数,只能被 1 和自己整除。

欧几里得定理(最伟大的数学定理之一):

质数有无穷多个。

证明思路

假设质数只有有限个:p1, p2, p3, ..., pn

那么考虑数 N = (p1 × p2 × ... × pn) + 1

N 有什么性质?
- N 不能被 p1 整除(因为 p1 × 某个数 + 1)
- N 不能被 p2 整除
- ...
- N 不能被 pn 整除

那 N 要么本身是质数,要么被某个新的质数整除。
任何情况下,都存在之前列表中没有的质数!

矛盾!所以质数无穷多。

2.2 为什么质数对密码学这么特殊?

问题 1:因数分解困难性

知道两个质数很容易:
p = 13
q = 17
n = p × q = 221

但反过来困难得多:
n = 221
求因数分解... 是 13 × 17 吗? 11 × 20? ...

随着质数越来越大,反向计算变得不可能。

问题 2:模质数下的魔法性质

当模数 p 是质数时,有一些不可思议的性质:

在 mod p 的世界里(p 是质数)

对于任何 1 ≤ a < p,都存在唯一的 b 使得:
a × b ≡ 1 (mod p)

这个 b 就叫做 a 的"乘法逆元"或"倒数"

例子:p = 7
3 的逆元是多少?
3 × ? ≡ 1 (mod 7)
3 × 5 = 15 ≡ 1 (mod 7) ✓

所以在 mod 7 的世界里,3 的"倒数"就是 5!

这为什么如此特殊?

  • 如果模数不是质数,某些数没有逆元(会导致计算破裂)
  • 质数保证了完整性和可逆性
  • 这是设计密码系统的必需条件

2.3 费马小定理——隐藏的规律

发现:法国数学家费马(Fermat),约 1640 年

如果 p 是质数,a 是不被 p 整除的整数,那么: a^(p-1) ≡ 1 (mod p)

具体例子

p = 7, a = 3
3^(7-1) = 3^6 = 729

729 mod 7 = ?
729 ÷ 7 = 104 余 1

所以 3^6 ≡ 1 (mod 7) ✓

这对所有质数和不被它整除的数都成立!

为什么这很惊人?

这暗示了一个深层的结构:

mod p 的乘法世界有一个 p-1 的"周期"

从某个数开始,不断乘以自己,经过 p-1 步后会回到 1:

3^1 ≡ 3 (mod 7)
3^2 ≡ 2 (mod 7)
3^3 ≡ 6 (mod 7)
3^4 ≡ 4 (mod 7)
3^5 ≡ 5 (mod 7)
3^6 ≡ 1 (mod 7)  ← 回到1
3^7 ≡ 3 (mod 7)  ← 开始重复

这在密码学中的作用

这个周期性给了我们一个关键想法:

如果我想保护一个数 a:

暴露 a^k mod p(某个幂次)
对方想要反推 a 和 k...

这很困难!因为有多个 (a, k) 组合都能产生同样的结果。

第三部分:方程的困境与椭圆曲线的诞生

3.1 古代数学家的困境

问题:怎样解方程?

一次方程:ax + b = 0
→ 直线(Euclid 几何)
→ 有明确的解

二次方程:ax² + bx + c = 0
→ 抛物线
→ 求根公式可解

三次及以上方程:???
→ 1500年代意大利数学家才发现三次公式
→ 四次方程也有(复杂)
→ 五次及以上... 无通用公式!(1824年才证明)

3.2 椭圆方程:一个特殊的形式

17世纪的数学家发现了一个特殊的方程

y² = x³ + ax + b

这不是"通常的"方程形式。
为什么要研究它?

原始动力

古人在研究椭圆轨道(行星运动)时,发现了与椭圆方程相关的积分。

"椭圆积分"导致了对形如 y² = x³ + ax + b 的曲线的兴趣。
(不是椭圆本身,但与椭圆问题相关,所以叫"椭圆曲线")

3.3 几何直觉:曲线上的点

问题:什么是曲线上的"点"?

对于 y² = x³ + ax + b

任何满足这个方程的 (x, y) 对都在"曲线上"

例子:y² = x³ - x + 1
试试 x = 2:
y² = 8 - 2 + 1 = 7
y = ±√7

所以 (2, √7) 和 (2, -√7) 都在曲线上

几何形状

          y
          ↑
          |     •(x, y)
      •   |    •
     /    |   /
────/─────┼──/────→ x
   /      | /
  •   •   •

这条曲线有特殊的性质:
- 关于 x 轴对称
- 有"无穷远点"

3.4 革命性的想法:定义"加法"

问题:我们能在椭圆曲线上"加"点吗?

17-18世纪的数学家开始思考一个激进的想法:

如果我们定义一个特殊的"加法",使得"两个曲线上的点相加仍然在曲线上",会怎样?

几何加法的定义

要计算 P + Q(P 和 Q 是曲线上的两个不同的点):

步骤 1:画出过 P 和 Q 的直线
步骤 2:这条直线必定与曲线相交于第三个点 R'
步骤 3:P + Q 定义为 R' 关于 x 轴的镜像点 R

P + Q = R(R' 关于 x 轴的镜像)

为什么这样定义?
- 几何上优雅
- 满足交换律:P + Q = Q + P
- 满足结合律:(P + Q) + R = P + (Q + R)
- 存在单位元(无穷远点 O)
- 每个点有逆元(关于 x 轴的镜像)

数学验证(为什么这确实有效):

这个定义实际上创造了一个"群"结构。

群的定义需要:
1. 封闭性:a + b 也在群里 ✓(直线与三次曲线总相交于三点)
2. 结合律:(a+b)+c = a+(b+c) ✓(通过代数验证)
3. 单位元:存在 e 使 a+e = a ✓(无穷远点)
4. 逆元:∀a ∃b 使 a+b = e ✓(关于x轴镜像)

所以椭圆曲线上的点形成一个"阿贝尔群"!

第四部分:有限域上的椭圆曲线

4.1 从连续到离散的转变

问题:椭圆曲线在实数上很优美,但密码学需要什么?

真实数学:y² = x³ + ax + b  (实数解)
密码数学:y² ≡ x³ + ax + b (mod p)  (整数模p解)

为什么转换?

  1. 可计算性:实数涉及无限精度,计算机无法精确处理
  2. 有限性:在 mod p 下,只有有限个点,可以枚举
  3. 安全性:有限群上的离散对数问题是困难的

4.2 有限域中的椭圆曲线

例子:y² ≡ x³ + 2x + 3 (mod 13)

我们逐个检查 x = 0, 1, 2, ..., 12

x = 0: y² ≡ 0 + 0 + 3 ≡ 3 (mod 13)
       3 不是二次剩余(无解)

x = 1: y² ≡ 1 + 2 + 3 ≡ 6 (mod 13)
       6 也不是二次剩余

x = 2: y² ≡ 8 + 4 + 3 ≡ 15 ≡ 2 (mod 13)
       2 不是二次剩余

x = 3: y² ≡ 27 + 6 + 3 ≡ 36 ≡ 10 (mod 13)
       10 不是二次剩余

x = 4: y² ≡ 64 + 8 + 3 ≡ 75 ≡ 10 (mod 13)
       不是

...(继续所有 x)

结果:曲线上有大约 p 个点(p = 素数)

关键发现(Hasse定理,1936):

在 mod p 椭圆曲线上,点数 N 满足:

|N - (p + 1)| ≤ 2√p

这意味着,点数大约是 p 个(±√p 的误差)。
这个定理保证了椭圆曲线有"足够多"的点!

4.3 点的数目为什么重要?

密码学的需求

我们需要一个足够大的有限群,使得:

1. 群有足够的元素(N 要大)
   → 暴力破解困难

2. 群的结构是"随机的"
   → 没有快速攻击算法

椭圆曲线满足这两个条件!

第五部分:离散对数问题的诞生

5.1 问题的根源

1976年的密码困境

在RSA发明之前,密码学有一个基本问题:

公开密钥加密需要什么?

需要一个函数 f,满足:
1. f(x) 易于计算
2. f^(-1) 难以计算
3. 但知道某个"秘密信息"时,f^(-1) 又容易计算

如何找到这样的函数?

5.2 质数域中的指数

发现:Diffie-Hellman,1976年

在 mod p(p是质数)的乘法群中:

g^x mod p 很容易计算
但要从 g^x 反推 x("离散对数")很困难!

为什么困难?
- g^1, g^2, g^3, ..., g^(p-1) 看起来很随机
- 没有已知的快速算法
- 等价于分解大整数的困难性(在某种意义上)

例子

p = 13, g = 2

2^1 ≡ 2 (mod 13)
2^2 ≡ 4 (mod 13)
2^3 ≡ 8 (mod 13)
2^4 ≡ 3 (mod 13)
2^5 ≡ 6 (mod 13)
2^6 ≡ 12 (mod 13)
2^7 ≡ 11 (mod 13)
2^8 ≡ 9 (mod 13)
2^9 ≡ 5 (mod 13)
2^10 ≡ 10 (mod 13)
2^11 ≡ 7 (mod 13)
2^12 ≡ 1 (mod 13)

这个序列看起来很随机!

5.3 椭圆曲线上的离散对数

关键洞察:1985年独立发现(Koblitz和Miller)

同样的困难性在椭圆曲线上也存在!

给定点 P 和 Q = k×P(标量乘法):

易于计算:Q = k×P
困难计算:给定 Q 和 P,求 k

这就是"椭圆曲线离散对数问题"(ECDLP)

为什么更好?

RSA 需要 2048 位密钥(p, q 都是 1024 位)
ECC 只需 256 位密钥

安全性相同,但密钥小 8 倍!

原因:
- RSA 基于因数分解(有亚指数算法)
- ECC 基于离散对数(只有指数算法)

第六部分:标量乘法的高效计算

6.1 问题:怎样计算 k×P?

朴素方法

5 × P = P + P + P + P + P
需要 4 次点加法

但如果 k 很大呢?
k = 2^256(256 位数)

则需要 2^256 - 1 次点加法...
永远计算不完!

6.2 发现:二进制展开方法

关键想法:将 k 写成二进制

k = 5 = 101₂ = 4 + 0 + 1 = 2² + 2⁰

所以:
5 × P = (1×2² + 0×2¹ + 1×2⁰) × P
      = 1×(2² × P) + 0×(2¹ × P) + 1×(2⁰ × P)
      = 4P + P
      = P + 4P

现在的问题变成:怎样快速计算 P, 2P, 4P, 8P, ..., 2^(n-1)P?

答案:每次倍增!
P → 2P(点加法)
2P → 4P(点加法)
4P → 8P(点加法)
...

总共只需要约 2×log₂(k) 次操作!

具体算法

算法:Double-and-Add(倍增相加)

输入:k = 5 = 101₂(二进制), P

result = ∞(无穷远点,加法单位元)

对于 k 的每一位(从高到低):
  bit = 第 i 位
  result = 2 × result
  if bit == 1:
    result = result + P

步骤追踪(k = 5 = 101₂):
第1位(1): result = 2×∞ = ∞,     result = ∞ + P = P
第2位(0): result = 2×P = 2P
第3位(1): result = 2×2P = 4P,   result = 4P + P = 5P ✓

总操作数:2 次倍增 + 2 次加法 = 4 次
相比 4 次加法,差不多,但对大数优势明显

复杂度对比

k = 256 位数(2^256)

朴素方法:需要 2^256 次操作(不可能)
二进制方法:需要约 2×256 = 512 次操作(可行!)

这是 3 × 10^77 倍的加速!

6.3 窗口方法优化

进一步的想法

如果我们预计算 P, 3P, 5P, 7P, ..., 15P,
我们可以一次处理 4 位,而不是 1 位。

这又可以加速约 4 倍。

速度和预计算存储之间的权衡。

第七部分:为什么需要签名——信任的问题

7.1 古代的困境

问题(公元前):

一个重要的文件(遗嘱、契约、法令)

危险 1:伪造
       坏人声称"这是国王的命令"

危险 2:否认
       国王说"我从没下过这个命令"

解决方案:签名(印章)

但...
- 印章可以被复制
- 在电子世界中,复制变得完美而容易

7.2 数字签名的想法(1976年)

Diffie-Hellman 的想法

如果我们有一对特殊的函数:

私函数 S(只有我知道):可以"签名"消息
公函数 V(所有人知道):可以"验证"签名

要求:
1. V(V(消息), 签名) 很容易验证
2. 没有私函数 S,伪造签名几乎不可能
3. S 和 V 必须不同(否则所有人都能签名)

这就是"非对称"密码学的核心想法。

7.3 RSA签名(1978年)

Rivest, Shamir, Adleman 的解决方案

公钥:(e, n)   (n = p×q,两个大质数的乘积)
私钥:(d, n)

签名消息 m:
  签名 s = m^d mod n  (用私钥)

验证签名:
  验证 s^e mod n == m  (用公钥)

为什么有效?
  (m^d)^e = m^(de) = m^(φ(n)k+1) ≡ m (mod n)
  (基于欧拉定理)

但 RSA 在椭圆曲线上能用吗?

不能!RSA 需要乘法结构(模乘法)
椭圆曲线的是加法结构(点加法)

我们需要设计一个专门为椭圆曲线的签名方案。

第八部分:ECDSA的诞生和设计

8.1 转移想法到椭圆曲线

问题:如何在椭圆曲线上实现签名?

Koblitz 和 Miller(1985)的想法

我们能把 RSA 的想法转移到椭圆曲线吗?

RSA 用的是:m^d mod n
ECC 能用的是:d × P(标量乘法)

那签名能是:
签名 = d × hash(m) + d × hash(随机数)?

8.2 ECDSA的正式发明(1992)

美国国家安全局提出,最终成为标准(FIPS 186-2)

关键洞察:使用一个不同的机制

不是直接暴露私钥的作用,而是设计一个"挑战-响应"系统。

设计原理

签名的数学形式:

签名 = (r, s),其中:
  k = 随机数
  (x, y) = k × G  (基点的标量倍)
  r = x mod n     (第一部分:x坐标模曲线阶)
  s = k^(-1) × (hash(m) + d × r)  (第二部分:涉及私钥和哈希)

验证时反推:
  从 (r, s) 和公钥 Q = d×G,能反推出 k×G 吗?

  是的!通过精妙的数学:
  u1 = hash(m) / s mod n
  u2 = r / s mod n
  (x', y') = u1×G + u2×Q

  验证 x' mod n == r

8.3 为什么这个设计有效?

第一:正确性

如果签名生成正确,验证必然成功:

s = k^(-1) × (hash(m) + d × r)
ks = hash(m) + d × r

u1×G + u2×Q
= (hash(m)/s)×G + (r/s)×(d×G)
= (hash(m)/s)×G + (r×d/s)×G
= ((hash(m) + r×d) / s) × G
= ((ks) / s) × G        (代入 ks = hash(m) + d×r)
= k × G

所以 x' = x,验证通过!✓

第二:安全性

为什么攻击者无法伪造签名?

如果攻击者想伪造签名 (r, s),他需要:
  伪造 s,使得验证方程成立

但要这样做,他需要知道 k(随机数)
而 k 是随机选择的,对他隐藏的

或者,他需要从 Q = d×G 反推 d
但这就是椭圆曲线离散对数问题(已知不可解)

所以伪造签名在计算上是不可行的。

第三:不可否认性

为什么签名者无法否认签名?

签名证明签名者知道私钥 d。
(因为 s 的计算涉及 d)

只有知道 d 的人才能创造有效的 (r, s)。

所以签名者不能说"这不是我签的"。

8.4 关键的设计选择

为什么选择这个特定的形式?

设计者面对的选择:

选项1:直接形式
  签名 = 私钥操作的结果
  → 简单但容易泄露私钥信息

选项2:挑战-响应形式(ECDSA)
  签名 = 对随机挑战的响应
  → 复杂但保护私钥

数学家选择了选项2,因为:
- 随机性 k 提供了额外的安全层
- 即使哈希泄露了,k 的隐藏也保护了私钥
- 多个签名用不同的 k,更难通过分析攻击

第九部分:RFC 6979——确定性k的诞生

9.1 问题:随机数的陷阱

危险:ECDSA 中的随机数 k 必须严格保密

1993 年 DSA 泄露事件:
- 索尼的 PlayStation 使用了不安全的随机数生成
- 结果被破解,私钥泄露

原因:k 被重复使用了两次

数学上的灾难

假设两个签名用了同一个 k:

签名1:s1 = k^(-1) × (hash(m1) + d × r)
签名2:s2 = k^(-1) × (hash(m2) + d × r)  (同一个 r,因为同一个 k)

两个方程相减:
s1 - s2 = k^(-1) × (hash(m1) - hash(m2))

攻击者可以解出:
k = (hash(m1) - hash(m2)) / (s1 - s2)

一旦知道 k,从 s1 方程能解出:
d = (s1 × k - hash(m1)) / r

私钥被完全破解!

9.2 RFC 6979 的想法

2011年 Yannick Seurin 的论文提出

如果我们从消息哈希和私钥推导 k(而不是随机选择),会怎样?

k = HMAC(私钥, 哈希(消息))

优点:
1. k 仍然对每个消息不同
2. 攻击者无法预测 k(因为不知道私钥)
3. 即使没有好的随机数生成器,也安全
4. 签名变得"确定的"(相同消息 + 私钥 = 相同签名)

缺点:
1. 签名中失去了"随机性"(虽然不是缺点,反而更好)

9.3 RFC 6979 的推导过程

具体算法(简化版):

输入:私钥 d,消息 m,哈希函数 H

步骤1:计算消息哈希
  h1 = H(m)

步骤2:初始化 HMAC
  V = 0x01 0x01 ... (一串1)
  K = 0x00 0x00 ... (一串0)

步骤3:更新 K 和 V(使用 HMAC)
  K = HMAC(K, V || 0x00 || d || h1)
  V = HMAC(K, V)
  K = HMAC(K, V || 0x01 || d || h1)
  V = HMAC(K, V)

步骤4:生成 k
  while True:
    T = 空
    while len(T) < 需要的位数:
      V = HMAC(K, V)
      T = T || V

    k = T mod n  (n 是曲线的阶)

    if 1 ≤ k < n:
      return k  ← 找到了!

    K = HMAC(K, V || 0x00)
    V = HMAC(K, V)

为什么这样设计?

1. 使用消息哈希:k 对不同消息不同 ✓
2. 使用私钥:只有知道私钥才能计算 k ✓
3. HMAC 的安全性保证 k 不可预测 ✓
4. 反复迭代直到 k 在有效范围内 ✓

第十部分:从理论到 bECCsh 实现

10.1 实现的关键决策

在纯 Bash 中实现 ECC 的挑战

问题1:大整数运算
  解决:用字符串逐位实现加、乘、除、mod

问题2:有限域算术
  解决:所有运算都在 mod p 下进行

问题3:椭圆曲线点运算
  解决:实现点加法和点倍增的公式

问题4:ECDSA 算法
  解决:实现签名和验证的完整流程

问题5:RFC 6979 确定性 k
  解决:用 HMAC-SHA256 从消息推导 k

问题6:标准兼容性
  解决:输出 ASN.1 DER 格式的签名

10.2 bECCsh 的数学实现

大整数加法(mod p)

# 从右到左逐位相加,处理进位
for ((i = 1; i <= max_len; i++)); do
    digit_a="${a: -$i:1}"
    digit_b="${b: -$i:1}"
    sum=$((digit_a + digit_b + carry))
    result="$((sum % 10))${result}"
    carry=$((sum / 10))
done

模逆元(扩展欧几里得算法)

# 找到 a 在 mod m 下的逆元
# 即找到 x 使得 a × x ≡ 1 (mod m)

extgcd() {
    local a=$1 b=$2

    if [[ $b -eq 0 ]]; then
        echo "$a 1 0"
        return
    fi

    local result=$(extgcd $b $((a % b)))
    # 递归求解,然后回溯计算逆元
    ...
}

点加法(在有限域上)

ec_point_add() {
    local p1_x=$1 p1_y=$2
    local p2_x=$3 p2_y=$4
    local a=$5 p=$6

    # 计算斜率 λ
    local dx=$((p2_x - p1_x))
    local dy=$((p2_y - p1_y))

    lambda=$(mod_inverse $dx $p)
    lambda=$(mod_mult $dy $lambda $p)

    # 计算结果点
    local x3=$((lambda * lambda - p1_x - p2_x))
    local y3=$((lambda * (p1_x - x3) - p1_y))

    x3=$((x3 % p))
    y3=$((y3 % p))

    echo "$x3 $y3"
}

标量乘法(二进制方法)

ec_scalar_mult() {
    local k=$1  # 标量
    local px=$2 py=$3  #

    local result_x="0" result_y="0"  # 无穷远点
    local cur_x=$px cur_y=$py

    # 逐位处理 k 的二进制
    while [[ $k -gt 0 ]]; do
        if [[ $((k % 2)) -eq 1 ]]; then
            # 当前位是1,加上当前点
            result=$(ec_point_add $result_x $result_y $cur_x $cur_y ...)
        fi

        # 倍增当前点
        cur=$(ec_point_double $cur_x $cur_y ...)

        k=$((k / 2))  # 右移一位
    done

    echo "$result_x $result_y"
}

10.3 签名和验证的实现

ECDSA 签名

ecdsa_sign() {
    local message=$1
    local private_key=$2

    # 步骤1:哈希消息
    hash=$(sha256 "$message")

    # 步骤2:生成确定性 k(RFC 6979)
    k=$(rfc6979_derive_k "$hash" "$private_key")

    # 步骤3:计算 (x, y) = k × G
    point=$(ec_scalar_mult $k $Gx $Gy)
    x=${point% *}

    # 步骤4:计算 r = x mod n
    r=$((x % n))

    # 步骤5:计算 s = k^(-1) × (hash + d × r)
    k_inv=$(mod_inverse $k $n)
    s=$(mod_mult $k_inv $((hash + d * r)) $n)

    # 步骤6:输出 (r, s)
    echo "$r $s"
}

ECDSA 验证

ecdsa_verify() {
    local message=$1
    local r=$2 s=$3
    local public_key_x=$4 public_key_y=$5

    # 步骤1:哈希消息
    hash=$(sha256 "$message")

    # 步骤2:计算 w = s^(-1) mod n
    w=$(mod_inverse $s $n)

    # 步骤3:计算 u1 = hash × w mod n
    u1=$(mod_mult $hash $w $n)

    # 步骤4:计算 u2 = r × w mod n
    u2=$(mod_mult $r $w $n)

    # 步骤5:计算 (x', y') = u1×G + u2×Q
    p1=$(ec_scalar_mult $u1 $Gx $Gy)
    p2=$(ec_scalar_mult $u2 $public_key_x $public_key_y)
    result=$(ec_point_add $p1 $p2 ...)

    # 步骤6:验证
    x=${result% *}
    if [[ $((x % n)) -eq $r ]]; then
        echo "VALID"
    else
        echo "INVALID"
    fi
}

总结:知识的进化链

问题 ↓
↓
古人的计数 → 模运算的发明
↓
解决什么分配不均匀的问题? → 质数和互质的发现
↓
为什么某些数有特殊性质? → 费马小定理
↓
如何解高次方程? → 椭圆曲线的研究
↓
在有限域上怎样做代数? → 模 p 椭圆曲线
↓
如何在有限群上实现密码系统? → 离散对数问题
↓
怎样高效计算 k×P? → 二进制标量乘法
↓
如何证明消息真实性? → 数字签名
↓
怎样在椭圆曲线上签名? → ECDSA 的设计
↓
怎样让签名安全,避免随机数泄露? → RFC 6979
↓
实现 ↓
↓
bECCsh:完整的纯 Bash ECC

每一个概念都解决了前面发现的问题。 每一个问题都来自实际的需求。 这就是数学和密码学的真实进化过程。


这份文档展示的是:不是一堆公式,而是一个思想的演进过程。

每当你看到一个算法或定义时,问问自己:

✓ 为什么古人需要这个? ✓ 之前的方法有什么问题? ✓ 这个解决方案如何改进了? ✓ 下一个问题会是什么?

这就是深层理解数学的方式。