第 2 章:门限签名所需的数学基础
学习定位:前置为导读与实验 0;配套完成实验 1:玩具有限域。本章产物是能手算并实现有限域、群和拉格朗日插值。
门限签名的公式通常不长,真正容易出错的是:一个数属于哪个集合、一次运算在哪个集合里完成。本章不会试图压缩一门完整的抽象代数课程,而是建立后续实现所需的“数学类型系统”。
本手册中的算法用于学习。即使测试全部通过,也不代表实现已经具备抵抗侧信道、恶意参与者或真实网络攻击的能力。
学习目标
完成本章后,你应该能够:
- 熟练进行模加、模乘、求逆和模除法;
- 解释有限域、循环群、生成元和群阶;
- 区分椭圆曲线的基域与签名算法使用的标量域;
- 判断一个公式两边的数学类型是否一致;
- 用拉格朗日插值恢复多项式在零点的值;
- 理解离散对数困难性为何能保护公钥对应的私钥。
2.1 先建立一张符号表
不同论文经常复用同一个字母。为了减少歧义,本手册采用以下约定:
| 符号 | 含义 | 类型 |
|---|---|---|
| \(p\) | 椭圆曲线坐标所在基域的模数 | 素数 |
| \(q\) | 所用素数阶子群的阶 | 素数 |
| \(\mathbb F_p\) | 椭圆曲线坐标使用的有限域 | 域 |
| \(\mathbb Z_q\) | 私钥、nonce、挑战值和签名响应所在的标量域 | 域 |
| \(\mathbb G\) | 阶为 \(q\) 的加法循环群 | 群 |
| \(G\) | \(\mathbb G\) 的生成元 | 群元素(点) |
| \(x,r,c,z\) | 私钥、nonce、挑战、响应 | 标量,属于 \(\mathbb Z_q\) |
| \(X,R\) | 公钥、nonce 承诺 | 群元素,属于 \(\mathbb G\) |
| \(m\) | 待签名消息 | 字节串 |
从实现角度看,最好把它们想象成完全不同的 Rust 类型:
Scalar + Scalar -> Scalar
Scalar * Point -> Point
Point + Point -> Point
Hash(bytes) -> bytes
HashToScalar(bytes) -> Scalar
下面这些表达式则没有意义:
Point + Scalar
Point * Point
message mod q
把一个任意字节串直接当成合法曲线点
数学类型意识是密码学实现中最便宜、也最有效的一道防线。
2.2 模运算:结果只关心余数类
若整数 \(a-b\) 能被 \(n\) 整除,就写作:
\[ a \equiv b \pmod n \]
例如:
\[ 20 \equiv 3 \pmod {17} \]
因为 \(20-3=17\)。在模 \(17\) 的世界里,\(20\)、\(3\)、\(-14\) 表示同一个余数类。
加法和乘法可以先运算再取模:
\[ (a+b)\bmod n \]
\[ (ab)\bmod n \]
例如在模 \(17\) 下:
\[ 13+9=22\equiv5\pmod {17} \]
\[ 7\cdot8=56\equiv5\pmod {17} \]
负数不是异常情况
在模 \(17\) 下:
\[ -3\equiv14\pmod {17} \]
实现时应把结果规范化到库约定的唯一表示,而不是让同一个标量存在多种字节编码。
模除法其实是乘逆元
有限域里没有普通意义上的除法。表达式
\[ \frac ab\pmod p \]
表示:
\[ a\cdot b^{-1}\pmod p \]
其中 \(b^{-1}\) 满足:
\[ b\cdot b^{-1}\equiv1\pmod p \]
例如在 \(\mathbb F_{17}\) 中,\(2^{-1}=9\),因为:
\[ 2\cdot9=18\equiv1\pmod {17} \]
于是:
\[ 3/2\equiv3\cdot9\equiv10\pmod {17} \]
若模数是素数 \(p\),每个非零元素都有乘法逆元。利用费马小定理还可写成:
\[ a^{-1}\equiv a^{p-2}\pmod p \]
真实实现应优先使用密码学库提供的常数时间求逆方法,而不是照搬一个会泄漏分支或执行时长的教学算法。
2.3 有限域:可安全做四则运算的有限集合
当 \(p\) 是素数时,
\[ \mathbb F_p=\{0,1,\ldots,p-1\} \]
在模 \(p\) 的加、减、乘、除(除数非零)下构成有限域。
“域”的关键性质是:
- 加法和乘法都封闭;
- 每个元素都有加法逆元;
- 每个非零元素都有乘法逆元;
- 运算满足结合律、交换律和分配律。
Shamir 秘密共享之所以选择在域上定义多项式,正是因为插值中的分母只要非零就一定可逆。
如果模数不是素数,非零元素未必有逆元。例如模 \(8\) 下不存在 \(2^{-1}\),因为 \(2k\) 不可能与 \(1\) 模 \(8\) 同余。随意把复合数当作域模数,会让插值公式失效。
2.4 多项式与“点决定曲线”
在域 \(\mathbb F\) 上,一个次数不超过 \(d\) 的多项式可以写成:
\[ f(X)=a_0+a_1X+a_2X^2+\cdots+a_dX^d \]
注意这里的大写 \(X\) 是形式变量,不是 Schnorr 公钥。多项式的常数项为:
\[ f(0)=a_0 \]
直觉上:
- 一条一次多项式(直线)需要两个不同点才能唯一确定;
- 一条二次多项式需要三个不同点;
- 一般地,次数至多为 \(d\) 的多项式需要 \(d+1\) 个横坐标互不相同的点。
这个事实正是 Shamir \(t\)-of-\(n\) 方案选择“次数 \(t-1\)”的原因。
2.5 拉格朗日插值
给定 \(t\) 个横坐标互不相同的点:
\[ (x_1,y_1),\ldots,(x_t,y_t) \]
存在唯一一个次数不超过 \(t-1\) 的多项式穿过这些点。拉格朗日插值写作:
\[ f(X)=\sum_{i=1}^{t} y_i L_i(X) \]
其中:
\[ L_i(X)=\prod_{\substack{j=1\\j\ne i}}^{t} \frac{X-x_j}{x_i-x_j} \]
秘密共享最关心 \(f(0)\),因此通常直接计算零点处的拉格朗日系数:
\[ \lambda_i=L_i(0)= \prod_{\substack{j\in S\\j\ne i}} \frac{-x_j}{x_i-x_j} \]
于是:
\[ f(0)=\sum_{i\in S}\lambda_i y_i \]
所有加、减、乘、求逆都在同一个域中进行。
小例子:在 \(\mathbb F_{17}\) 中恢复常数项
假设有两个点:
\[ (1,8),\quad(2,11) \]
零点处的系数为:
\[ \lambda_1=\frac{0-2}{1-2}=2 \]
\[ \lambda_2=\frac{0-1}{2-1}=-1\equiv16\pmod {17} \]
因此:
\[ f(0)=8\cdot2+11\cdot16=192\equiv5\pmod {17} \]
可以反推这两个点位于直线 \(f(X)=5+3X\) 上。
两个重要观察:
- 系数依赖于参与插值的横坐标集合 \(S\);更换集合后必须重算。
- 如果两个横坐标相同,就会出现 \(x_i-x_j=0\),而零没有乘法逆元。
2.6 群、标量乘法与离散对数
Schnorr 签名在一个阶为素数 \(q\) 的循环群 \(\mathbb G\) 中工作。采用加法记号时:
- 单位元写作 \(\mathcal O\);
- 群加法写作 \(P+Q\);
- 点 \(P\) 的逆元写作 \(-P\);
- 标量乘法 \(aP\) 表示把 \(P\) 相加 \(a\) 次。
若 \(G\) 是生成元,那么群中每个元素都能写成:
\[ P=aG,\qquad a\in\mathbb Z_q \]
群阶为 \(q\) 意味着:
\[ qG=\mathcal O \]
因此标量按 \(q\) 约简:
\[ (a+q)G=aG \]
由标量 \(a\) 和点 \(G\) 计算 \(P=aG\) 很容易;已知 \(G\) 与 \(P\),反求 \(a\) 被称为离散对数问题。在合适的群和参数下,这个反向问题被认为在计算上不可行。
这给出了公私钥关系:
\[ \text{私钥 }x\in\mathbb Z_q,\qquad \text{公钥 }X=xG\in\mathbb G \]
公钥可以公开,因为从 \(X\) 恢复 \(x\) 被离散对数困难性保护。
2.7 最重要的区别:基域不是标量域
以短 Weierstrass 形式为例,椭圆曲线方程可能写成:
\[ y^2=x^3+ax+b\pmod p \]
这里点坐标 \(x,y\) 和曲线参数位于基域 \(\mathbb F_p\)。
但私钥、nonce 和 Schnorr 响应是乘在群元素上的系数,位于标量域 \(\mathbb Z_q\):
\[ x_{\text{secret}},k,c,z\in\mathbb Z_q \]
虽然两个集合里的值在程序中最后都可能表现为若干字节,但它们不是同一种数学对象。通常 \(p\ne q\),也不能互换取模。
| 操作 | 正确模数或对象 |
|---|---|
| 曲线点坐标运算 | \(p\) |
| 私钥加法、nonce 加法 | \(q\) |
| Shamir 共享 Schnorr 私钥 | \(q\) |
| Schnorr 挑战和响应 | \(q\) |
| 点加法、点压缩与解压 | 由曲线/群库处理 |
在本项目采用 Ristretto255 一类抽象时,库会把底层 Edwards 曲线的坐标、余因子和编码细节封装起来。应用层仍然必须区分 Scalar 与 RistrettoPoint。
一个抽象群的小例子
假设 \(G\) 生成一个阶为 \(11\) 的群,则:
\[ 8G+7G=15G=4G \]
因为标量在 \(\mathbb Z_{11}\) 中相加,\(15\equiv4\pmod {11}\)。这不是在对点坐标逐项模 \(11\);点运算由群法则定义。
2.8 哈希到标量与域分离
普通哈希函数输出字节串,而签名公式需要 \(c\in\mathbb Z_q\)。“哈希到标量”是一个明确的转换过程,例如:
\[ c=H_{\mathrm{scalar}}(\text{domain}\parallel \operatorname{enc}(R)\parallel \operatorname{enc}(X)\parallel m) \]
实现必须规定:
- 使用哪个哈希函数;
- 点和长度如何编码;
- 如何把摘要映射到 \(\mathbb Z_q\);
- 使用哪个域分离标签;
- 是否有协议版本与上下文字符串。
域分离标签可以理解为“给哈希输入加上不可混淆的用途名称”。例如,生成挑战和生成 nonce 必须使用不同标签,防止同一串字节在两个协议位置被误作同一种对象。
不要用 hash[0..32] 后直接解释为标量,也不要用不带规范说明的“随手 % q”。成熟曲线库一般提供安全的宽字节约简或协议指定的映射接口。
2.9 随机性也是数学假设的一部分
当协议说“均匀随机选择 \(a\leftarrow\mathbb Z_q\)”时,它不只是建议调用任意随机函数,而是要求分布不可预测且偏差可忽略。
实践中应注意:
- 使用操作系统提供的密码学安全随机源;
- 避免简单的
random_u64 % q带来的模偏差; - 私钥、Shamir 系数和 nonce 不得来自时间戳或普通伪随机生成器;
- 测试中可以注入固定随机源以复现结果,生产接口必须阻止误用测试随机源。
2.10 常见误区
误区一:“所有大整数都可以统一 % p”
错误。曲线坐标通常模 \(p\),签名标量模 \(q\)。用错模数会破坏协议等式,某些情况下还会引入安全漏洞。
误区二:“点就是两个整数,所以自己操作坐标即可”
错误。合法性检查、单位元、子群、余因子、编码唯一性都很容易处理错。学习签名协议不等于自行实现曲线算术。
误区三:“除法就是整数先除再取模”
错误。模除法必须乘以分母在域中的逆元。
误区四:“两个份额的拉格朗日系数永远一样”
错误。系数由本次参与集合的标识符决定。
误区五:“哈希输出天然就是合法标量或合法点”
错误。两种转换都有协议规定的映射过程,并需要规范编码和域分离。
误区六:“公式正确就等于实现安全”
错误。侧信道、随机数质量、序列化、状态持久化、重放和恶意输入都不体现在一行代数等式中。
2.11 章节检查题
先独立作答,再展开参考答案。
- 在 \(\mathbb F_{17}\) 中,\(5/2\) 等于多少?
- 为什么 Shamir 的参与者标识符必须互不相同?
- 已知 \(P=xG\),为什么不能通过“点除以 \(G\)”轻易得到 \(x\)?
- 椭圆曲线坐标使用的模数 \(p\) 与私钥使用的模数 \(q\) 有什么区别?
- 在阶为 \(11\) 的群中,\(9G+8G\) 等于多少个 \(G\)?
- 为什么
Hash(bytes)的结果不能不经转换就当作Scalar? - 给定点 \((1,8)\)、\((2,11)\),在 \(\mathbb F_{17}\) 中求 \(f(0)\)。
参考答案
- \(2^{-1}=9\),所以 \(5/2=5\cdot9=45\equiv11\pmod {17}\)。
- 相同标识符会使拉格朗日插值的分母 \(x_i-x_j\) 为零,而零不可逆;它们本质上也只是同一个横坐标上的重复信息。
- 群没有这种普通除法;从 \(P=xG\) 反求 \(x\) 是离散对数问题。
- \(p\) 定义曲线坐标的基域;\(q\) 是所用子群的阶,并定义私钥、nonce、挑战和响应所在的标量域。二者通常不相等。
- \(17G=(17\bmod11)G=6G\)。
- 哈希输出是字节串,可能超出标量范围,编码也未必规范;必须使用协议指定的哈希到标量过程。
- \(f(0)=5\),推导见本章的拉格朗日示例。
本章小结
后续所有协议都可以用三类对象来阅读:字节串、标量和群元素。看到一个公式时,先检查类型,再检查它在哪个域中运算,最后才检查代数是否相等。这个习惯会贯穿整个门限签名实现。