哈希链与校验和如何工作?hashsigs-ts的WOTS+签名验证机制逐行拆解
【免费下载链接】hashsigs-tsHash-based signatures in typescript, WOTS+项目地址: https://gitcode.com/gh_mirrors/ha/hashsigs-ts
hashsigs-ts 是一个用 TypeScript 实现的后量子哈希签名库,核心是 WOTS+(Winternitz One-Time Signature Plus)一次性签名算法。本文带你逐行拆解 src/wotsplus.ts:哈希链如何被“续完”、校验和如何防止节点替换、以及签名验证最后一步的哈希比对究竟在验证什么。
🧩 为什么“哈希链”能当签名用?
传统数字签名依赖大数分解、椭圆曲线等“难题”;而哈希签名只依赖哈希函数本身的安全性,即使量子计算机也难以攻破,因此被称为后量子签名的有力候选。
WOTS+ 是最经典的哈希签名算法之一,三个关键词先记住:
- 哈希链:从一个秘密起点反复哈希,终点是公开的;链可以“往前走”,但不能“往回走”
- 一次性:一对密钥只能安全地签一次消息,所以叫 One-Time Signature(上层 XMSS 树结构负责密钥复用,本库专注底层 WOTS+)
- Plus:WOTS+ 的“加号”指每一步哈希前先与一个随机化元素做异或(XOR),防止跨链碰撞等结构性攻击
📌 一句话原理:签名 = 出示哈希链的某个中间节点;验证 = 从该节点继续哈希,能走到公钥终点即通过。
⚙️ 关键参数速览:32 字节哈希如何变成 2144 字节签名
构造器 WOTSPlus 会根据哈希函数输出长度和链长w自动推导全部参数。以默认的 keccak256(32 字节输出、w = 16)为例:
| 参数 | 含义 | 默认值 |
|---|---|---|
hashLen | 哈希输出长度(字节) | 32 |
chainLen | 链长 w,必须取 4 或 16 | 16 |
numMessageChunks | 消息分段数 len₁ = ⌈256/4⌉ | 64 |
numChecksumChunks | 校验和分段数 len₂ | 3 |
numSignatureChunks | 签名总分段数 len₁+len₂ | 67 |
signatureSize | 签名总字节数 67×32 | 2144 字节 |
publicKeySize | 公钥长度(32 种子 + 32 哈希) | 64 字节 |
validateParameters 会强制链长为 2 的幂且只能是 4 或 16——这是为了与 XMSS 标准(RFC 8391)保持兼容。
🔗 验证第一步:把消息“解码”成链索引
验证开始前,消息必须先翻译成“每条哈希链取第几个节点”。这一步由 computeMessageHashChainIndexes 完成,包含两个动作:
- base-w 编码(toBaseW):把 32 字节消息按每字节拆成高、低 4 比特,得到 64 个 0~15 的数字——每个数字就是对应哈希链上的取点位置
- 计算校验和(checksum):
sum = Σ(15 − 索引ᵢ),最大值为 64×15=960,再把 sum 用 base-16 表示,得到 3 个额外的链索引
校验和为什么不可或缺?
假设只有 base-w 编码、没有校验和:攻击者把某条链的签名节点换成更靠前的节点,继续往前哈希仍然能命中公钥终点——伪造的签名照样验证通过!
校验和正是所有索引的“互补和”:任何一次索引替换都会让 sum 对不上,验证立刻失败。这就是校验和存在的唯一目的,也是 WOTS+ 比原始 Winternitz 签名更安全的核心原因。
🪝 验证第二步:从公钥种子重建随机化元素
公钥并没有保存 67 条链的全部终点(那要 2144 字节),而是压缩成 64 字节:前 32 字节是公开种子,后 32 字节是所有链终点拼接后的哈希。
入口函数 verify 逐行看:
- L281:校验公钥长度必须为 64 字节
- L285:切出前 32 字节作为
publicSeed - L286:切出后 32 字节作为
publicKeyHash - L288:调用 generateRandomizationElements 从种子重建 67 个随机化元素
内部通过伪随机函数 prf 生成:哈希(0x03 ‖ seed ‖ 序号)。开头的0x03是域分离前缀,保证这批随机化元素不会和其他用途的哈希输出混淆。
🔁 验证第三步:把哈希链“续完”
核心循环在 verifyWithRandomizationElements 中,精简后只有三行逻辑:
const chainIdx = chainSegments[i]; // base-w 编码得到的链索引 const numIterations = this.chainLen - chainIdx - 1; // 距离链终点还差几跳 const segment = this.chain(signature[i], randomizationElements, chainIdx, numIterations);chain 函数负责一跳一跳地续链:先把当前值与该位置的随机化元素异或,再哈希——对应 WOTS+ 公式 c_k(i)。签名者只“走完”了 chainIdx 跳,验证者替它走完剩下的 chainLen − chainIdx − 1 跳,两者恰好拼接成同一条链。
✅ 最后一步:比对公钥哈希
拿到 67 条链重建出的终点后,全部拼接再哈希一次,最后用 BufferUtil.equals 做逐字节比较:相等则返回true,不相等返回false。一次完整的 WOTS+ 签名验证到此结束。
⚠️ 提醒:WOTS+ 是一次性签名,同一密钥对不能重复使用。生产环境中需搭配 XMSS 等树状结构做多签名,本库(见 package.json 中的关键词
pqcrypto/post-quantum)提供的是最底层的安全原语。
🧪 用测试向量交叉验证
test 文件基于 Vitest 覆盖了:密钥对生成、空签名必须被拒绝、签名→验证闭环、以及批量验证。更重要的是它还加载了固定测试向量 wotsplus_keccak256.json——其中包含 67 个公钥链段、消息与签名等数据,用于跨语言实现的一致性比对,这是确认“哈希每一步都没算错”的标准做法。
本地运行方式:
git clone https://gitcode.com/gh_mirrors/ha/hashsigs-ts cd hashsigs-ts npm install npm test📚 总结:五步看懂 WOTS+ 验证
| 步骤 | 动作 | 对应代码 |
|---|---|---|
| 1 | 拆分公钥(种子 + 哈希) | verify |
| 2 | 重建随机化元素 | generateRandomizationElements |
| 3 | 消息 → base-w 索引 + 校验和 | computeMessageHashChainIndexes |
| 4 | 异或随机元素后续完哈希链 | chain |
| 5 | 比对公钥哈希 | verifyWithRandomizationElements |
WOTS+ 的签名验证可以浓缩成一句话:把消息解码成链索引,从每个签名节点续完哈希链,再确认所有链终点拼出的哈希等于公钥。其中,哈希链提供“不可逆”,校验和防“节点替换”,随机化元素防“结构性攻击”——三者缺一不可,这就是 hashsigs-ts 这套后量子哈希签名的完整机制。
更多细节可参考模块导出文件 src/index.ts 与 README.md。
【免费下载链接】hashsigs-tsHash-based signatures in typescript, WOTS+项目地址: https://gitcode.com/gh_mirrors/ha/hashsigs-ts
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考