技术 › 密码学

哈希:Keccak、Hs 与 Hp

门罗币如何把任意字节变成一个数或一个点

简而言之

门罗币使用 Keccak-256 哈希函数(原始 Keccak,不是标准化的 SHA3-256)。由它派生出两个函数:Hs——“哈希到标量”,以及 Hp——“哈希到点”。

Keccak-256

哈希函数接收任意数据,输出 32 个看起来随机的字节。输入改变一个比特,整个输出都会改变;从输出反推输入,或者找到两个输出相同的输入,在实践中都是不可能的。

门罗币(和以太坊一样)使用的是赢得 SHA-3 竞赛时的 Keccak——那时 NIST 还没有修改填充字节。由于这个细节,标准库里的 sha3_256() 会给出不同的结果。如果你的代码算出的结果与门罗币不一致,先检查这一点。

函数空字符串的哈希
Keccak-256(门罗币)c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
SHA3-256(NIST)a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a

在门罗币的代码中,这个函数叫做 cn_fast_hash。

Hs:哈希到标量

我们常常需要的不只是字节,而是一个标量——模 l(基点的阶)的一个数。Hs 直接完成这件事:

Hs(data) = Keccak256(data) mod l 把 32 字节的哈希按小端序读作一个数,再对 l 取模(代码中为 sc_reduce32)

Hs 的用途:

Hp:哈希到点

有时我们需要一个没有人知道其离散对数的点。不能直接用 Hs(data)·G——那样离散对数就是 Hs(data),人人都知道。Hp 通过一种特殊映射直接从哈希构造一个点(门罗币中使用 Elligator 2 的一个变体,即函数 ge_fromfe_frombytes_vartime),然后乘以余因子 8,使这个点落在正确的子群中。

Hp(data) = 8 · map_to_curve( Keccak256(data) )

Hp 的用途:

  • key image:I = x·Hp(P)——这是主要用途;
  • Pedersen 承诺的第二个生成元 H:H = Hp(G)(忽略编码细节)。没有人知道满足 H = k·G 的 k——这一点至关重要,否则金额就可能被伪造。
域分离

好的做法是在哈希之前给数据加上一个标签字符串前缀("SubAddr"、"view_tag"、"amount"),这样为某个用途计算的哈希就无法被冒用于另一个用途。旧的 CryptoNote 代码很少使用标签;较新的部分(view tag、Carrot、FCMP++)则系统地使用它们。