ElligatorSwift for secp256k1 深入解析:原理、编码解码算法与实现细节
【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash
导读
本文基于 Zcash 仓库内 vendored 的 secp256k1 库(路径src/secp256k1)中的官方文档 doc/ellswift.md,系统讲解 ElligatorSwift 这一将 secp256k1 公钥编码为与均匀随机字节串不可区分的 64 字节格式的完整算法。文章从解码函数 $F_u(t)$、编码函数 $F_u^{-1}(x)$ 的数学构造出发,逐步推导 secp256k1($a=0, b=7$ 曲线)上的特化公式,并结合模块源码 main_impl.h 与公开 API 头文件 secp256k1_ellswift.h 说明每个数学步骤对应的实际实现函数。读完本文,你将掌握 ElligatorSwift 的核心原理、编码/解码/密钥生成/ECDH 的完整调用方式,以及它在抵抗公钥指纹检测场景(如 BIP324 传输加密)中的设计动机。
1. 引言:ElligatorSwift 解决什么问题
ellswift模块引入了一种新的64 字节公钥格式:它能够把(均匀随机的)secp256k1 公钥编码成与均匀随机字节数组计算上不可区分的 64 字节数组。这一性质对抵抗"公钥指纹检测"至关重要——在未加密的 P2P 协议中,攻击者可以通过检查公钥是否落在曲线上来识别加密流量,而 ElligatorSwift 编码后的字节在外观上与随机数完全一致,从而隐藏了"这里存在一个公钥"这一事实。
该模块不仅提供公钥与此格式之间的互转函数,还提供直接作用于编码后密钥的密钥生成与 ECDH 便捷函数,用于 BIP324 等传输层加密场景。官方头文件 secp256k1_ellswift.h 开篇即说明:
"This module provides an implementation of ElligatorSwift as well as a version of x-only ECDH using it (including compatibility with BIP324)."
在仓库中,该模块由 configure.ac 的--enable-module-ellswift开关控制(默认开启),并被 CHANGELOG.md 列为新增模块,配套提供 API 头文件与本文所依据的数学背景文档。
1.1 编码的组成结构
编码结果是两个(32 字节大端序)域元素 $u$ 和 $t$ 的拼接。二者共同编码曲线上的一个 x 坐标 $x$(进一步扩展后还可编码完整点 $(x, y)$,见第 4 节)。
- 解码(Decoding):将 $u$、$t$ 解码为域元素(大于域大小 $p$ 的值取模 $p$),然后计算 $F_u(t)$。对任意 $u$、$t$,$F_u(t)$ 都产生曲线上的一个合法 x 坐标。
- 编码(Encoding):给定 x 坐标,按以下流程寻找 $(u, t)$:
循环: 1. 均匀随机选取域元素 u 2. 计算集合 L = F_u^{-1}(x),即满足 F_u(t) = x 的所有 t(最多 8 个) 3. 以概率 1 - #L/8 重新开始循环 4. 从 L 中均匀随机选取 t,返回 (u, t)这就是ElligatorSwift 算法(此处仅针对 x 坐标,扩展到完整 $(x,y)$ 点见第 4 节)。算法在满足 $F_u(t)=x$ 的(几乎所有)$(u,t)$ 对中均匀随机取样。论文第 3.2 节证明:对曲线上几乎所有 x 坐标(至多 39 个例外),这种编码的数量接近域大小的两倍——精确地说落在 $2q \pm (22\sqrt{q} + O(1))$ 范围内,其中 $q$ 是域大小。正是这种"每个点对应编码数量近似均匀"的计数性质,保证了均匀采样的编码结果在统计上不可区分于随机字节。
2. 解码函数(Decoding Function)
2.1 数学定义与记号
首先给出论文中的记号体系:
- $\mathbb{F}$:大小为 $q$ 的有限域,特征为 5 或更大,且 $q \equiv 1 \mod 3$。
- 对secp256k1:$q = 2^{256} - 2^{32} - 977$,满足上述要求。
- $E$:满足 $y^2 = x^3 + ax + b$ 的椭圆曲线,$a$、$b$ 为公开常数,且要求判别式 $\Delta_E = -16(4a^3 + 27b^2)$ 为平方数、$(-b \pm \sqrt{-3\Delta_E}/36)/2$ 至少有一个为平方数。这蕴含 $E$ 的阶为奇数或是 4 的倍数。若 $a=0$,该条件恒成立。
- 对secp256k1:$a=0$,$b=7$。
- $g(x) = x^3 + ax + b$,曲线方程等价于 $y^2 = g(x)$。
- $h(x) = 3x^3 + 4a$。
- $V$:方程 $z^2 = g(x_1)g(x_2)g(x_3)$ 的解集 $(x_1, x_2, x_3, z)$。
- $S_u$:方程 $X^2 + h(u)Y^2 = -g(u)$ 且 $Y \neq 0$ 的解集 $(X, Y)$。
- $P_u$:从 $\mathbb{F}$ 到 $S_u$ 的函数(下文定义)。
- $\psi_u$:从 $S_u$ 到 $V$ 的函数(下文定义)。
与论文的对应关系(论文记号对照):
- 论文中的 $F_{0,u}$ 即本文的 $F_u$;
- 论文中的 $P$ 即本文的 $P_u(t)$;
- 所有 $S_u$ 集合的并集对应论文中的 $S$;
- 所有 $\psi_u$ 函数(作用在 $S$ 元素上)对应论文中的 $\psi$。
一个关键观察:对 $V$ 而言,等式左侧 $z^2$ 是平方数,因此右侧也必须是平方数。由于域中两个非平方数相乘得到平方数,三个右端因子 ${g(x_1), g(x_2), g(x_3)}$ 中必须恰好有 1 个或恰好 3 个是平方数。这意味着对任意 $(x_1,x_2,x_3,z) \in V$,${x_1, x_2, x_3}$ 中至少有一个是 $E$ 上的合法 x 坐标(唯一的例外是 $z=0$,但此时三个值中仍有一个是合法 x 坐标)。
2.2 解码函数的定义
定义解码函数 $F_u(t)$为:
- 计算 $(x_1, x_2, x_3, z) = \psi_u(P_u(t))$。
- 返回 $(x_3, x_2, x_1)$ 中第一个满足"是 $E$ 上合法 x 坐标"(即 $g(x)$ 为平方数)的元素。
其中 $P_u(t) = (X(u, t), Y(u, t))$,具体公式为:
$$ \begin{array}{lcl} X(u, t) & = & \left{\begin{array}{ll} \dfrac{g(u) - t^2}{2t} & a = 0 \ \dfrac{g(u) + h(u)(Y_0(u) - X_0(u)t)^2}{X_0(u)(1 + h(u)t^2)} & a \neq 0 \end{array}\right. \ Y(u, t) & = & \left{\begin{array}{ll} \dfrac{X(u, t) + t}{u \sqrt{-3}} = \dfrac{g(u) + t^2}{2tu\sqrt{-3}} & a = 0 \ Y_0(u) + t(X(u, t) - X_0(u)) & a \neq 0 \end{array}\right. \end{array} $$
$P_u(t)$ 在以下情形未定义:
- $a=0$ 时:$u=0$ 或 $t=0$(除零);$g(u) = -t^2$(会导致 $Y=0$)。
- $a \neq 0$ 时:$X_0(u) = 0$ 或 $h(u)t^2 = -1$(除零);$Y_0(u)(1 - h(u)t^2) = 2X_0(u)t$(会导致 $Y=0$)。
其中 $X_0(u)$、$Y_0(u)$ 定义于论文附录 A,依赖于曲线的具体性质。
而 $\psi_u$ 对所有曲线都是一样的:$\psi_u(X, Y) = (x_1, x_2, x_3, z)$,其中
$$ \begin{array}{lcl} x_1 & = & \dfrac{X}{2Y} - \dfrac{u}{2} \ x_2 & = & -\dfrac{X}{2Y} - \dfrac{u}{2} \ x_3 & = & u + 4Y^2 \ z & = & \dfrac{g(x_3)}{2Y}(u^2 + ux_1 + x_1^2 + a) = \dfrac{-g(u)g(x_3)}{8Y^3} \end{array} $$
注意 $x_1 + x_2 = -u$,这一关系在编码时的 round-trip 校验中被反复使用。
2.3 secp256k1 特化的解码($a=0$)
将所有公式代入并针对 $a=0$ 曲线化简,解码 $(u, t)$ 到 x 坐标的流程为:
定义 $F_u(t)$为:
- 令 $X = \dfrac{u^3 + b - t^2}{2t}$。
- 令 $Y = \dfrac{X + t}{u\sqrt{-3}}$。
- 返回 $(u + 4Y^2,\ \dfrac{-X}{2Y} - \dfrac{u}{2},\ \dfrac{X}{2Y} - \dfrac{u}{2})$ 中第一个使 $g(x)$ 为平方数的元素。
输入重映射:为保证每个输入都能解码到合法 x 坐标,在 $P_u$ 未定义的情形($u=0$、$t=0$ 或 $g(u) = -t^2$)下需要对输入做重映射:
定义 $F_u(t)$为:
- $u'=u$(若 $u \neq 0$),否则 $u'=1$(保证 $u' \neq 0$)。
- $t'=t$(若 $t \neq 0$),否则 $t'=1$(保证 $t' \neq 0$)。
- $t''=t'$(若 $g(u') \neq -t'^2$),否则 $t''=2t'$(保证 $t'' \neq 0$ 且 $g(u') \neq -t''^2$)。
- $X = \dfrac{u'^3 + b - t''^2}{2t''}$。
- $Y = \dfrac{X + t''}{u'\sqrt{-3}}$。
- 返回 $(u' + 4Y^2,\ \dfrac{-X}{2Y} - \dfrac{u'}{2},\ \dfrac{X}{2Y} - \dfrac{u'}{2})$ 中第一个使 $x^3 + b$ 为平方数的元素。
文档特别说明:这些选择并非严格必要——在任意未定义情形下返回固定常量也能满足正确性,但上述做法实现简单且在特殊情形下输出也足够均匀。
与论文的差异:论文中这些条件因使用射影坐标而输出无穷远点 $\infty$,但实现希望避免调用方处理这一特殊情况,因此改为重映射输入。
实现对应:这一逻辑分别实现为:
secp256k1_ellswift_xswiftec_frac_var——解码为用分数(分子/分母)表示的 x 坐标;secp256k1_ellswift_xswiftec_var——输出真正的 x 坐标。
两个函数都定义在 main_impl.h 中(xswiftec_frac_var在第 24 行,xswiftec_var在第 135 行)。在 secp256k1_ellswift.h 的注释中,解码函数 $f(u,t)$ 被以常量形式给出:$C = 0xa2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f852$ 是 $\sqrt{-3}$ 的一个平方根,配合 $u=0 \to 1$、$t=0 \to 1$、$u^3 + t^2 + 7 = 0 \to t$ 加倍三步重映射,然后计算 $X = (u^3 + 7 - t^2)/(2t)$、$Y = (X+t)/(C \cdot u)$,返回 $[u+4Y^2,\ (-X/Y - u)/2,\ (X/Y - u)/2]$ 中第一个落在曲线上的值。
3. 编码函数(Encoding Function)
要实现 $F_u^{-1}(x)$(找出所有满足 $F_u(t) = x$ 的 $t$ 集合),需要逆向整个流程:
- 找出所有可能通过 $\psi_u$ 中 $x_1$、$x_2$ 或 $x_3$ 公式产生 $x$ 的 $(X, Y) \in S_u$;
- 用 $P_u^{-1}(X, Y)$ 将这些 $(X, Y)$ 映射回 $t$ 值;
- 对每个 $t$ 验证 $F_u(t) = x$;
- 返回验证通过的 $t$ 集合。
其中 $P_u^{-1}$(已知 $(X,Y) \in S_u$ 求 $t$)比 $P_u$ 简单得多:
$$ P_u^{-1}(X, Y) = \left{\begin{array}{ll} Yu\sqrt{-3} - X & a = 0 \ \dfrac{Y-Y_0(u)}{X-X_0(u)} & a \neq 0 \land X \neq X_0(u) \ \dfrac{-X_0(u)}{h(u)Y_0(u)} & a \neq 0 \land X = X_0(u) \land Y = Y_0(u) \end{array}\right. $$
为什么需要第 3 步验证:通过 $x_1$、$x_2$ 表达式找到的 $(X, Y)$,其解码结果有可能在 $x_3$ 位置上恰好是合法曲线点,而解码器对 $x_3$ 有优先权,此时这些 $(X, Y)$ 必须被拒绝。
简化的 round-trip 检查:由于对任意 $t$,${x_1, x_2, x_3}$ 中恰好有 1 个或 3 个是合法 x 坐标,因此"$x_1$ 或 $x_2$ 合法且同时 $x_3$ 也合法"必然意味着三者全部合法。于是可以用一个更简单的检查替代"$x_3$ 是否在曲线上":检查 $x_1$、$x_2$ 中另一个是否在曲线上。
利用 $\psi_u$ 保证的 $x_1 + x_2 = -u$:给定 $x = x_1$ 或 $x = x_2$,另一个值即为 $-u-x$。因此,当通过 $x_1$ 或 $x_2$ 表达式编码 $x$ 时,只需检查 $g(-u-x)$ 是否为平方数,若是则不把对应的 $t$ 值放入返回集合。该条件不依赖 $X$、$Y$ 或 $t$,可以在计算这些值之前就确定。
类似地,通过 $x_1$ 表达式得到的编码不可能解码到另一个合法 x 坐标(经 $x_2$)——因为若 $x_1$、$x_2$ 解码都有效,则 $x_3$ 也有效并优先返回。因此对 $x_1$、$x_2$ 而言,$g(-u-x)$ 是否为平方数是保证 round-trip 正确所需的唯一检查。这正是解码器选择 $(x_3, x_2, x_1)$ 优先顺序的原因;任何不把 $x_3$ 放在首位的顺序都需要在编码器中做更复杂的 round-trip 检查。
3.1 切换到 $v, w$ 坐标
为简化公式推导,对 $S_u$ 换元:令 $v = (X/Y - u)/2$,$w = 2Y$;反解为 $X = w(u/2 + v)$、$Y = w/2$。于是:
- $S_u'$ 成为满足 $w^2(u^2 + uv + v^2 + a) = -g(u)$ 且 $w \neq 0$ 的 $(v, w)$ 集合。
- 对 $a=0$ 曲线,$P_u^{-1}$ 在 $(v,w)$ 下可写为 $P_u^{'-1}(v, w) = w\left(\frac{\sqrt{-3}-1}{2}u - v\right)$。
- $\psi_u$ 在 $(v,w)$ 下写为 $\psi_u'(v, w) = (x_1, x_2, x_3, z)$:
$$ \begin{array}{lcl} x_1 & = & v \ x_2 & = & -u - v \ x_3 & = & u + w^2 \ z & = & \dfrac{g(x_3)}{w}(u^2 + uv + v^2 + a) = \dfrac{-g(u)g(x_3)}{w^3} \end{array} $$
现在可以显式写出已知 $x$ 求 $(v, w)$ 的表达式:分别把 ${x_1, x_2, x_3}$ 三个表达式对 $v$ 或 $w$ 求解,再用 $S_u'$ 方程求另一变量:
- 假设 $x = x_1$:得 $v = x$,$w = \pm\sqrt{-g(u)/(u^2 + uv + v^2 + a)}$(两个解)。
- 假设 $x = x_2$:得 $v = -u-x$,$w = \pm\sqrt{-g(u)/(u^2 + uv + v^2 + a)}$(两个解)。
- 假设 $x = x_3$:得 $w = \pm\sqrt{x-u}$,$v = -u/2 \pm \sqrt{-w^2(4g(u) + w^2h(u))}/(2w^2)$(四个解)。
合计最多 8 个候选 $(v, w)$,与第 1 节中 $F_u^{-1}(x)$ 至多 8 个元素的事实吻合。
3.2 避免计算全部逆元素
第 1 节的 ElligatorSwift 算法要求完整计算 $L = F_u^{-1}(x)$,这其实没有必要。
观察:以概率 $(1 - #L/8)$ 重启、否则均匀返回 $L$ 中一个元素的过程,等价于:始终把 $L$ 用 $\bot$ 占位符填充到长度 8,均匀选取一个元素,选到 $\bot$ 就重启:
定义ElligatorSwift(x)为:
循环: 1. 均匀随机选取域元素 u 2. 计算集合 L = F_u^{-1}(x) 3. 构造 8 元素向量 T = L 的元素 + (8 - #L) 个 ⊥ 4. 均匀随机选取 t ∈ T 5. 若 t ≠ ⊥,返回 (u, t);否则重启循环由于 $T$ 中元素顺序无关紧要(反正只做均匀选取),无需把所有 $\bot$ 放在末尾。既然有 8 个不同的 $(v, w)$ 求解公式(含 $\pm$ 变体),可以让 $T$ 的每个下标对应恰好一个公式,并保证:
- 无解(除零或平方根不存在)或非法解的情形返回 $\bot$;
- 对 $x_1$、$x_2$ 情形,若 $g(-u-x)$ 是平方数则返回 $\bot$(round-trip 检查);
- 若多个公式返回相同的非 $\bot$ 结果,除一个外其余都必须改为 $\bot$,以避免引入偏差。
最后一个条件在密码学规模的曲线上发生概率可忽略,但值得考虑,因为它允许在小群上做穷举测试(见 3.4 节对所有这些可忽略情形的分析)。
定义 $T = (G_{0,u}(x), G_{1,u}(x), \ldots, G_{7,u}(x))$,每个 $G_{i,u}$ 对应一个公式,循环可简化为只计算一个逆元素:
定义ElligatorSwift(x)为:
循环: 1. 均匀随机选取域元素 u 2. 均匀随机选取整数 c ∈ [0, 8) 3. 计算 t = G_{c,u}(x) 4. 若 t ≠ ⊥,返回 (u, t);否则重启循环该实现对应secp256k1_ellswift_xelligatorswift_var(main_impl.h)。
3.3 求解逆元素 $G_{c,u}$
$c$ 到公式的映射:$c=0$ 对应 $x_1$ 公式,$c=1$ 对应 $x_2$ 公式,$c=2,3$ 对应 $x_3$ 公式;$c=4$ 到 $c=7$ 是上述公式的 $w$ 取相反符号的变体(注意每个公式中 $w$ 都是某个表达式的平方根)。忽略可忽略情形,有:
定义 $G_{c,u}(x)$为:
- 若 $c \in {0, 1, 4, 5}$($x_1$、$x_2$ 公式):
- 若 $g(-u-x)$ 是平方数,返回 $\bot$(因为 $x_3$ 会合法并优先)。
- 若 $c \in {0, 4}$($x_1$ 公式),令 $v = x$;否则令 $v = -u-x$($x_2$ 公式)。
- 令 $s = -g(u)/(u^2 + uv + v^2 + a)$(下文用 $s = w^2$)。
- 否则($c \in {2, 3, 6, 7}$,$x_3$ 公式):
- 令 $s = x-u$。
- 令 $r = \sqrt{-s(4g(u) + sh(u))}$。
- 若 $c \in {3, 7}$,令 $v = (r/s - u)/2$;否则 $v = (-r/s - u)/2$。
- 令 $w = \sqrt{s}$。
- 按 $c$ 返回:
- $c \in {0, 1, 2, 3}$:返回 $P_u^{'-1}(v, w)$;
- $c \in {4, 5, 6, 7}$:返回 $P_u^{'-1}(v, -w)$。
失败情形:对非平方数开平方根时返回 $\bot$——对随机输入,两个平方根各有约 50% 概率失败;除零时同样返回 $\bot$,但这只以可忽略概率发生。第一个分支中的除零其实不可能发生:$u^2 + uv + v^2 + a = 0$ 蕴含 $g(-u-x) = g(x)$,意味着 $g(-u-x)$ 为平方数的条件已触发、早已返回 $\bot$。
与论文的差异:论文中的case变量大致对应上述 $c$,但只有 4 个取值(1 到 4);其最后的 $w$ 条件取反是随机决定的,效果等价但不利于测试。本实现让 $G_{c,u}$确定化,把所有随机选择都收进 $c$ 中。
进一步化简:$c \in {1, 5}$ 与 $c \in {3, 7}$ 实际执行的是同一个 $v \to -u-v$ 变换;且该变换在第一个分支中不改变 $s$(因为 $u^2 + ux + x^2 + a = u^2 + u(-u-x) + (-u-x)^2 + a$)。于是可以把它提取出来并下移:
定义 $G_{c,u}(x)$为:
- 若 $c \in {0, 1, 4, 5}$:
- 若 $g(-u-x)$ 是平方数,返回 $\bot$。
- 令 $s = -g(u)/(u^2 + ux + x^2 + a)$;令 $v = x$。
- 否则($c \in {2, 3, 6, 7}$):
- 令 $s = x-u$;令 $r = \sqrt{-s(4g(u) + sh(u))}$;令 $v = (r/s - u)/2$。
- 令 $w = \sqrt{s}$。
- 按 $c$ 返回:
- $c \in {0, 2}$:$P_u^{'-1}(v, w)$;
- $c \in {1, 3}$:$P_u^{'-1}(-u-v, w)$;
- $c \in {4, 6}$:$P_u^{'-1}(v, -w)$;
- $c \in {5, 7}$:$P_u^{'-1}(-u-v, -w)$。
这揭示了重要性质:给定 $(u, x)$,$t$ 的数量总是恰好 0、4 或 8 个。调用 $P_u^{'-1}$ 之前可能有 0、1 或 2 个 $(v, w)$ 对,每对产生 4 个不同的 $t$ 值。
3.4 特殊情形处理
下列情形只在可忽略的子集输入中发生。对密码学规模的域,若只考虑随机输入,不处理它们也是可以的;文档仍为完备性逐一分析。它们大体分为两类:编码器产生的 $t$ 值不能(或不能保证能)解码回 $x$ 的情形;以及编码器对多个 $c$ 可能产生相同 $t$ 值(从而引入偏差)的情形:
- 在 $x_1$、$x_2$ 分支($c \in {0, 1, 4, 5}$):
- $g(u) = 0$ 时,会有 $s=w=Y=0$,不在 $S_u$ 上。这只在偶数阶曲线上可能出现。排除它同时消除了简化 $x_3$ 检查失效的唯一条件(即 $g(x_1)=g(x_2)=0$ 但 $g(x_3)$ 非平方)。这会排除一些合法编码:当 $g(u)=0$ 且 $u^2+ux+x^2+a=0$(蕴含 $g(x)=0$)时,$S_u'$ 方程退化为 $0=0$,可能存在大量合法 $t$ 值。但编码器反正无法均匀覆盖它们,因为数量通常超过 8。
- $g(x) = 0$ 时,会产生与 $x_3$ 分支($c \in {2, 3, 6, 7}$)相同的 $t$,后者被赋予优先权因为它能处理 $g(u)=0$。同样只可能在偶数阶曲线上出现。
- 在 $x_3$ 分支($c \in {2, 3, 6, 7}$):
- $s=0$ 时发生除零。
- $c \in {3, 7}$ 且 $v = -u-v$ 时,会返回与 $c \in {2, 6}$ 情形相同的 $t$。这等价于检查 $r=0$。它在 $x_1$、$x_2$ 分支中不会出现,因为那会触发"$g(-u-x)$ 是平方数"条件。$w = -w$ 的类似顾虑不存在:$w=0$ 在两个分支中都已不可能——第一个分支需要 $g(u)=0$(偶数阶曲线已排除,其他曲线不可能),第二个分支会触发除零。
- 曲线相关的特殊情形也需拒绝,因为它们会产生解码器不接受的 $(u,t)$,或导致编码器除零:
- 对 $a=0$ 曲线:$u=0$ 或 $t=0$。后者只能由编码器在 $g(u)=0$ 时达到,需要偶数阶曲线。
- 对 $a \neq 0$ 曲线:$X_0(u)=0$;$h(u)t^2 = -1$;或 $w(u + 2v) = 2X_0(u)$ 且同时 $w \neq 2Y_0(u)$ 或 $h(u)=0$。
处理所有这些情形的完整版 $G_{c,u}(x)$:
- 若 $a=0$ 且 $u=0$,返回 $\bot$。
- 若 $a \neq 0$ 且 $X_0(u)=0$,返回 $\bot$。
- 若 $c \in {0, 1, 4, 5}$:
- 若 $g(u)=0$ 或 $g(x)=0$,返回 $\bot$(仅偶数阶曲线)。
- 若 $g(-u-x)$ 是平方数,返回 $\bot$。
- 令 $s = -g(u)/(u^2 + ux + x^2 + a)$(不会除零);令 $v = x$。
- 否则($c \in {2, 3, 6, 7}$):
- 令 $s = x-u$。
- 令 $r = \sqrt{-s(4g(u) + sh(u))}$;若非平方数返回 $\bot$。
- 若 $c \in {3, 7}$ 且 $r=0$,返回 $\bot$。
- 若 $s = 0$,返回 $\bot$。
- 令 $v = (r/s - u)/2$。
- 令 $w = \sqrt{s}$;若非平方数返回 $\bot$。
- 若 $a \neq 0$ 且 $w(u+2v) = 2X_0(u)$ 且($w \neq 2Y_0(u)$ 或 $h(u)=0$),返回 $\bot$。
- 按 $c$ 计算 $t$:$c \in {0,2} \to P_u^{'-1}(v, w)$;$c \in {1,3} \to P_u^{'-1}(-u-v, w)$;$c \in {4,6} \to P_u^{'-1}(v, -w)$;$c \in {5,7} \to P_u^{'-1}(-u-v, -w)$。
- 若 $a=0$ 且 $t=0$,返回 $\bot$(仅偶数阶曲线)。
- 若 $a \neq 0$ 且 $h(u)t^2 = -1$,返回 $\bot$。
- 返回 $t$。
完备性结论:对任意 $u$,对全部 $x$、$c$ 运行上述算法,每个满足 $F_u(t) = x$ 的 $t$ 值都会被恰好到达一次,除以下不可达情形:
- 所有 $P_u(t)$ 未定义的情形:
- $a=0$ 曲线:$u=0$、$t=0$ 或 $g(u) = -t^2$。
- $a \neq 0$ 曲线:$h(u)t^2 = -1$、$X_0(u)=0$ 或 $Y_0(u)(1 - h(u)t^2) = 2X_0(u)t$。
- 当 $g(u)=0$ 时,可能存在的、通过 $x_2$ 公式解码到满足 $g(x)=0$ 的 $x$ 的大量 $t$ 值(被 $c \in {0, 1, 4, 5}$ 分支的 $g(u)=0$ 条件排除)。
这些情形在密码学规模曲线上构成 $(u,t)$ 全空间的可忽略子集。
3.5 secp256k1 特化的编码($a=0$ 奇数阶曲线)
针对奇数阶 $a=0$ 曲线特化:
定义 $G_{c,u}(x)$为:
- 若 $u=0$,返回 $\bot$。
- 若 $c \in {0, 1, 4, 5}$:
- 若 $(-u-x)^3 + b$ 是平方数,返回 $\bot$。
- 令 $s = -(u^3 + b)/(u^2 + ux + x^2)$(不会除零);令 $v = x$。
- 否则($c \in {2, 3, 6, 7}$):
- 令 $s = x-u$。
- 令 $r = \sqrt{-s(4(u^3 + b) + 3su^2)}$;若非平方数返回 $\bot$。
- 若 $c \in {3, 7}$ 且 $r=0$,返回 $\bot$。
- 若 $s = 0$,返回 $\bot$。
- 令 $v = (r/s - u)/2$。
- 令 $w = \sqrt{s}$;若非平方数返回 $\bot$。
- 按 $c$ 返回:
- $c \in {0, 2}$:$w(\frac{\sqrt{-3}-1}{2}u - v)$;
- $c \in {1, 3}$:$w(\frac{\sqrt{-3}+1}{2}u + v)$;
- $c \in {4, 6}$:$w(\frac{-\sqrt{-3}+1}{2}u + v)$;
- $c \in {5, 7}$:$w(\frac{-\sqrt{-3}-1}{2}u - v)$。
该实现对应secp256k1_ellswift_xswiftec_inv_var(main_impl.h)。而 x-only 的 ElligatorSwift 编码算法仍为:
定义ElligatorSwift(x)为:
循环: 1. 均匀随机选取域元素 u 2. 均匀随机选取整数 c ∈ [0, 8) 3. 计算 t = G_{c,u}(x) 4. 若 t ≠ ⊥,返回 (u, t);否则重启循环注意:该逻辑不处理解码器中的 $u=0$、$t=0$、$g(u) = -t^2$ 重映射情形,只是回避它们。虽然并非不可能让编码器瞄准这些情形,但这会把给定 $(u,x)$ 的 $t$ 数量上限推到 8 以上,按比例拖慢 ElligatorSwift 循环,却只为均匀性带来可忽略的增益,得不偿失。
4. 完整 $(x, y)$ 坐标的编码与解码
此前只处理 x 坐标;但有些场景需要编码完整点 $(x, y)$。这些信息可以一并编进 $t$ 中。
关键观察:对任意 $(X, Y) \in S_u$,$(\pm X, \pm Y)$ 也都在 $S_u$ 上,且都映射到同一个 x 坐标。对 $X$ 或 $Y$ 取负只会交换 $x_1$、$x_2$,不影响 $x_3$,也不改变最终 x 坐标(因为 $x_1$、$x_2$ 的顺序只在两者都合法时才有意义,而那种情况下会改用 $x_3$)。然而,这四个 $(X, Y)$ 组合对应四个不同的 $t$ 值,因此可以在 $X$ 或 $Y$ 的符号中编码 y 坐标的符号。它们正好对应 $G_{u,c}$ 定义中的四次 $P_u^{'-1}$ 调用。
与论文的差异:论文把 y 坐标的符号编进一个独立的编码位,而本实现把符号编进 $Y$ 的符号(secp256k1 特化版本则编进 $t$ 的符号,见 4.1 节)。
用 $Y$ 的符号编码 $y$ 的符号:
定义Decode(u, t)(完整 $(x,y)$)为:
- 计算 $(X, Y) = P_u(t)$。
- 令 $x$ 为 $(u + 4Y^2,\ \frac{-X}{2Y} - \frac{u}{2},\ \frac{X}{2Y} - \frac{u}{2})$ 中第一个使 $g(x)$ 为平方数的值。
- 令 $y = \sqrt{g(x)}$。
- 若 $sign(y) = sign(Y)$,返回 $(x, y)$;否则返回 $(x, -y)$。
编码使用 $G_{c,u}(x, y)$ 函数:
定义 $G_{c,u}(x, y)$为:
- 若 $c \in {0, 1}$:
- 若 $g(u)=0$ 或 $g(x)=0$,返回 $\bot$(仅偶数阶曲线)。
- 若 $g(-u-x)$ 是平方数,返回 $\bot$。
- 令 $s = -g(u)/(u^2 + ux + x^2 + a)$(不会除零);令 $v = x$。
- 否则($c \in {2, 3}$):
- 令 $s = x-u$;令 $r = \sqrt{-s(4g(u) + sh(u))}$;若非平方数返回 $\bot$。
- 若 $c = 3$ 且 $r = 0$,返回 $\bot$。
- 令 $v = (r/s - u)/2$。
- 令 $w = \sqrt{s}$;若非平方数返回 $\bot$。
- 令 $w' = w$(若 $sign(w/2) = sign(y)$),否则 $w' = -w$。
- 按 $c$ 返回:$c \in {0, 2} \to P_u^{'-1}(v, w')$;$c \in {1, 3} \to P_u^{'-1}(-u-v, w')$。
注意 $c$ 现在只取 $[0, 4)$,因为 $w'$ 的符号由 $y$ 的符号决定而非由 $c$ 决定。这一改变使部分合法编码不可达:当 $y = 0$ 且 $sign(Y) \neq sign(0)$ 时。
关于 $sign$ 的实现:$sign$ 可以有多种实现方式,例如域元素整数表示的奇偶性(对素数阶域),或二次剩余性(对 $-1$ 非平方的域)。只要它只取两个值、且对 $x \neq 0$ 满足 $sign(x) \neq sign(-x)$,具体选择不影响正确性。
4.1 secp256k1 的完整 $(x, y)$ 坐标编码
对 $a=0$ 曲线还有另一种做法。注意此时 $P_u(t)$ 会把 $t$ 的取负翻译成 $X$ 和 $Y$(两者)同时取负。因此可以直接用 $sign(t)$ 编码 y 坐标。结合前面保证所有输入都落在曲线上的重映射,得到解码器:
定义Decode(u, t)为:
- $u'=u$(若 $u \neq 0$),否则 $u'=1$。
- $t'=t$(若 $t \neq 0$),否则 $t'=1$。
- $t''=t'$(若 $u'^3 + b + t'^2 \neq 0$),否则 $t''=2t'$。
- $X = \dfrac{u'^3 + b - t''^2}{2t''}$。
- $Y = \dfrac{X + t''}{u'\sqrt{-3}}$。
- 令 $x$ 为 $(u' + 4Y^2,\ \frac{-X}{2Y} - \frac{u'}{2},\ \frac{X}{2Y} - \frac{u'}{2})$ 中第一个使 $g(x)$ 为平方数的值。
- 令 $y = \sqrt{g(x)}$。
- 若 $sign(y) = sign(t)$,返回 $(x, y)$;否则返回 $(x, -y)$。
该实现对应secp256k1_ellswift_swiftec_var(main_impl.h),使用的 $sign(x)$ 是 $x$ 表示为 $[0, q)$ 内整数时的奇偶性(parity)。
对应的编码器只需调用 x-only 编码器,然后在 $sign(t) \neq sign(y)$ 时对输出 $t$ 取负。该实现对应secp256k1_ellswift_elligatorswift_var(main_impl.h)。
重要使用限制:此方案仅适用于 x 坐标与 y 坐标都不可预测的点。当编码 x-only 点且 y 坐标被隐式规定(如隐式为偶数、隐式为平方数、或隐式落在 $[0, q/2]$)时,必须使用 3.5 节 的编码器,否则会重新引入偏差,抵消使用 ElligatorSwift 的全部收益。
5. 模块 API 与在 Zcash 仓库中的使用
5.1 公开 API 一览
ellswift模块的公开接口定义在 include/secp256k1_ellswift.h,核心函数如下:
| 函数 | 作用 | 备注 |
|---|---|---|
secp256k1_ellswift_encode(ctx, ell64, pubkey, rnd32) | 将给定公钥编码为 64 字节 ElligatorSwift 格式 | 恒返回 1;rnd32需为 32 字节均匀随机数(16 字节足够,其余可补零),且不得是公钥的确定性函数(可以从私钥派生);变时运行;不保证跨版本稳定 |
secp256k1_ellswift_decode(ctx, pubkey, ell64) | 将 64 字节编码解码回公钥 | 恒返回 1;变时运行 |
secp256k1_ellswift_create(ctx, ell64, seckey32, auxrnd32) | 直接由私钥生成 ElligatorSwift 公钥 | 私钥无效返回 0;在seckey32与auxrnd32上常数时间;auxrnd32可选(即使缺省编码也不可区分于均匀);比"先secp256k1_ec_pubkey_create再encode"更安全,因为它用私钥本身作为编码熵源 |
secp256k1_ellswift_xdh(ctx, output, ell_a64, ell_b64, seckey32, party, hashfp, data) | 基于编码密钥的 x-only ECDH | 比"先解码再做 ECDH"更高效;在seckey32上常数时间;party指示本方是 A(0)还是 B(非 0) |
secp256k1_ellswift_xdh_hash_function_prefix | 内置哈希函数:SHA256(prefix64 \|\| ell_a64 \|\| ell_b64 \|\| x32) | prefix64由data指向 |
secp256k1_ellswift_xdh_hash_function_bip324 | 与 BIP324 兼容的哈希函数:H_tag(ell_a64 \|\| ell_b64 \|\| x32) | 标签为"bip324_ellswift_xonly_ecdh"的 BIP340 带标签哈希,等价于prefix64 = SHA256(tag)\|\|SHA256(tag) |
头文件注释还给出了模块的逐字解码定义(全部运算模 $p = 2^{256} - 2^{32} - 977$):
f(u,t): - 令 C = 0xa2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f852(√-3 的一个平方根) - 若 u=0,改为 u=1 - 若 t=0,改为 t=1 - 若 u³ + t² + 7 = 0,把 t 乘 2 - 令 X = (u³ + 7 - t²) / (2t) - 令 Y = (X + t) / (C·u) - 返回 [u + 4Y², (-X/Y - u)/2, (X/Y - u)/2] 中第一个是曲线上 X 坐标的值(对任意 u、t 至少有一个成立)ElligatorSwift 对 $x$ 的编码就是 $u$、$t$ 两个 32 字节大端域元素拼接,满足 $f(u,t) = x$;若涉及 y 坐标,则约定其与 $t$ 同奇偶性。
5.2 在 Zcash 仓库中的构建配置与测试
- 构建开关:模块默认启用,可由 configure.ac 的
--enable-module-ellswift选项控制;CI 脚本 ci.sh 会将该开关透传给 configure。 - 源码组成:实现位于 src/modules/ellswift/main_impl.h(核心算法),配套 tests_impl.h(单元测试)、tests_exhaustive_impl.h(小群穷举测试,正是 3.2 节提到"允许在小群上穷举测试"的落地)、bench_impl.h(基准测试)。模块头文件经 secp256k1.c 汇总导出,并通过 Makefile.am 的
Makefile.am.include纳入构建。 - 测试覆盖:测试验证编码/解码往返(round-trip)、全空间可达性、y 符号编码、ECDH 与 BIP324 哈希函数兼容性等;穷举测试在小群上验证"每个 $t$ 恰好被到达一次"的完备性结论。
5.3 使用约束与安全建议(来自官方头文件)
secp256k1_ellswift_encode的rnd32建议为 32 字节均匀随机数且不被任何试图检测编码的敌手知晓;16 字节随机性(填充到 32 字节)足以使结果不可区分于均匀。secp256k1_ellswift_create的auxrnd32可选但推荐提供;它比两步式(创建 + 编码)更安全,因为编码熵来自私钥本身。- 编码结果不保证跨库版本稳定,即使参数完全相同。
- 对 x-only 点编码,若 y 坐标隐式固定(偶/平方/下半个区间),必须使用 x-only 编码器,否则将重新引入可检测偏差。
6. 总结
ElligatorSwift 为 secp256k1 提供了一种可证明均匀的 64 字节公钥编码:解码方向由代数函数 $F_u(t)$ 保证任意输入都映射到合法曲线点(配合 $u=0$、$t=0$、$g(u)=-t^2$ 三种情形的输入重映射,避免输出无穷远点);编码方向通过 8 路公式 $G_{c,u}$ 均匀采样满足 $F_u(t)=x$ 的 $(u,t)$ 对,利用 $x_1+x_2=-u$ 的代数关系把 round-trip 校验简化为一次平方性检查,并依靠 $(x_3, x_2, x_1)$ 的优先顺序保证解码正确性。完整点编码把 y 坐标符号编入 $t$(secp256k1 特化版)或 $Y$(通用版),并配套提供直接作用于编码密钥的 ECDH(含 BIP324 兼容哈希)。在 Zcash 仓库中,该模块以默认启用的可配置模块形式存在,其数学文档 doc/ellswift.md、API 头文件 secp256k1_ellswift.h 与实现 main_impl.h 三者一一对应,读者可从任一入口深入验证本文所述的全部构造细节。
【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考