把哈希值变成曲线上的点:RFC 9380 的 Hash-to-Curve 在防什么 图 1
把哈希值变成曲线上的点:RFC 9380 的 Hash-to-Curve 在防什么 · 图 1

椭圆曲线密码里有个反直觉的事实:给定任意一个哈希值,它大概率不是曲线上的点。签名协议需要“从哈希出发得到一个合法点”时,外行写法——取哈希当 x 坐标、解 y——成功率只有对半,失败还得换盐重试;再省一步,可能悄悄丢掉整个协议的安全证明。2023 年 8 月发布的 RFC 9380《Hashing to Elliptic Curves》就是为这件事定的标准:怎么把任意字节串确定性地、安全地映射到曲线点上。本文讲它防的错误、给出的方法族,以及它出现在哪些你已经在用的协议里。

曲线点不是拼出来的

以最常见的短形曲线为例,点 (x, y) 必须满足 y² = x³ + ax + b。你随手取一个哈希当 x,右端算出的值有约一半概率不是域上的平方,方程无解——这就是“重试”写法的来源。看起来只是麻烦,问题在重试引入的分支:循环次数依赖输入内容,攻击者能从时间或失败模式里读出信息;更糟的是若盐值选择不当,同一输入可能映射到不同的点,破坏协议要求的一致性。历史上一些草案和实现曾在“怎么把哈希变点”这一步上出过设计缺陷,标准化正是为把这类差异清零。

把哈希值变成曲线上的点:RFC 9380 的 Hash-to-Curve 在防什么 图 2
把哈希值变成曲线上的点:RFC 9380 的 Hash-to-Curve 在防什么 · 图 2

RFC 9380 的两步框架

标准把映射拆成两步。第一步:expand_message,把任意长输入用基于哈希的消息扩展函数(基于 SHA-256 或 SHAKE 两条路线)扩展成固定长、均匀分布的域元素——这一步顺带提供域分离:协议名、上下文串会喂进扩展函数,保证“用于 A 协议的哈希点”永远不会等于“用于 B 协议的哈希点”。第二步:把域元素映到曲线,标准收录了几类映射算法,共同点是把“映射到曲线”写成一个纯粹的代数表达式:给定输入域元素,输出的坐标由多项式直接算出,天然落在曲线上,全程无分支、无重试。RFC 自己给出的选型建议是:求性能优先走 SSWU 路线,求实现简单走 SVDW(Shallue-van de Woestijne)方法;每条曲线在附录里都配好了参数表。文档本身是 IRTF 研究组的成果,定性为信息性文档,不代表互联网标准轨道。

与 BLS 签名的关系

BLS 是最直接的受益者:它的哈希到曲线步骤发生在签名侧——把消息哈希成 G1 群的一个点再乘私钥。早期方案与草案里常见“try-and-increment”式的土办法(往输入里加计数器,反复哈希直到落进曲线),实现间互不兼容。RFC 9380 之后,BLS 签名草案明确把哈希映射委托给这份标准,并强调域分离串必须写全:同为“哈希进 G1”,用于签名和用于其他协议的输出必须不可互用。零知识侧同样依赖它——把 challenge 变成曲线点参与承诺协议时,若映射不均匀,证明系统可能被参数攻击。

工程自查三件事

第一,别手写映射。所有主流密码库(如各语言的 blst、bls12-381 实现)已按标准实现哈希到曲线,自己写的“取 x 解 y”循环就是缺陷。第二,核对域分离串:同样输入、不同 salt 与上下文,结果应完全不同;跨项目复用代码时这条最容易漏。第三,跨曲线核对:每条曲线的参数表不同,把以太坊 BLS 12-381 的参数带到 P-256 上等于换个协议。

快速问答

问:Schnorr 签名(BIP340)用 Hash-to-Curve 吗? 答:不用。它选的是另一条避免任意“哈希→点”的路(哈希只当纯量参与点运算,靠 nonce 与 aux 的哈希组合构造点),这正说明存在多种绕开任意点映射的设计。

问:随机预言和哈希进曲线是一回事吗? 答:不是。随机预言是对哈希函数的理想化模型,Hash-to-Curve 处理的是“目标对象是曲线点”时的构造问题,一个在函数层,一个在映射层。

问:怎么确认我的库合规? 答:查其文档是否声明符合 RFC 9380 与对应曲线的 suite id,跑标准附带的测试向量是最硬的证据。

风险提示

把哈希映射到曲线属于密码原语层:自行实现或参数错配可能直接使签名与承诺方案失去不可伪造性。集成时请使用维护活跃的密码库并核对测试向量,勿基于本文描述手写实现。本文不构成任何投资与安全处置建议。