Alert Source Discuss
Standards Track: Core

EIP-152: 添加 BLAKE2 压缩函数 `F` 预编译

Authors Tjaden Hess <tah83@cornell.edu>, Matt Luongo (@mhluongo), Piotr Dyraga (@pdyraga), James Hancock (@MadeOfTin)
Created 2016-10-04

简述

本 EIP 将支持 BLAKE2b 哈希函数和其他更高轮次的 64 位 BLAKE2 变体在 EVM 上低成本运行,从而简化以太坊和 Zcash 以及其他基于 Equihash 的 PoW 币之间的互操作性。

摘要

本 EIP 引入了一个新的预编译合约,该合约实现了 BLAKE2 密码哈希算法中使用的压缩函数 F,目的是允许 EVM 和 Zcash 之间的互操作性,并向 EVM 引入更灵活的密码哈希原语。

动机

除了作为一种有用的密码哈希函数和 SHA3 决赛入围者之外,BLAKE2 还可以有效地验证 Zcash 中使用的 Equihash PoW,从而可以在以太坊上实现 BTC Relay 风格的 SPV 客户端。 单个 Equihash PoW 验证需要 512 次哈希函数迭代,如果使用 Solidity 实现的 BLAKE2,则验证 Zcash 区块头将非常昂贵。

BLAKE2b 是常见的 64 位 BLAKE2 变体,经过高度优化,并且比现代处理器上的 MD5 更快。

与 Zcash 的互操作性可以实现诸如链之间的无信任原子交换之类的合约,这可以为非常公开的以太坊区块链提供急需的隐私方面。

规范

我们建议在地址 0x09 处添加一个预编译合约,包装 BLAKE2 F 压缩函数

预编译需要紧密编码的 6 个输入,完全占用 213 字节,如下所述。 编码后的输入对应于 BLAKE2 RFC 第 3.2 节 中指定的输入:

  • rounds - 轮数 - 32 位无符号大端字
  • h - 状态向量 - 8 个无符号 64 位小端字
  • m - 消息块向量 - 16 个无符号 64 位小端字
  • t_0, t_1 - 偏移计数器 - 2 个无符号 64 位小端字
  • f - 最终块指示器标志 - 8 位字
[4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 byte for f]

如果 f 参数设置为 1,则将其视为 true。 如果 f 参数设置为 0,则将其视为 false。 所有其他值都会产生 f 错误的无效编码。

预编译应计算 RFC 中指定的F 函数,并返回更新后的状态向量 h,编码不变(小端)。

Solidity 中的示例用法

预编译可以很容易地包装在 Solidity 中,以提供更便于开发的 F 接口。

function F(uint32 rounds, bytes32[2] memory h, bytes32[4] memory m, bytes8[2] memory t, bool f) public view returns (bytes32[2] memory) {
  bytes32[2] memory output;

  bytes memory args = abi.encodePacked(rounds, h[0], h[1], m[0], m[1], m[2], m[3], t[0], t[1], f);

  assembly {
    if iszero(staticcall(not(0), 0x09, add(args, 32), 0xd5, output, 0x40)) {
      revert(0, 0)
    }
  }

  return output;
}

function callF() public view returns (bytes32[2] memory) {
  uint32 rounds = 12;

  bytes32[2] memory h;
  h[0] = hex"48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5";
  h[1] = hex"d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b";

  bytes32[4] memory m;
  m[0] = hex"6162630000000000000000000000000000000000000000000000000000000000";
  m[1] = hex"0000000000000000000000000000000000000000000000000000000000000000";
  m[2] = hex"0000000000000000000000000000000000000000000000000000000000000000";
  m[3] = hex"0000000000000000000000000000000000000000000000000000000000000000";

  bytes8[2] memory t;
  t[0] = hex"0300000000000000";
  t[1] = hex"0000000000000000";

  bool f = true;

  // Expected output:
  // ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1
  // 7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923
  return F(rounds, h, m, t, f);
}

Gas 成本和基准

每次操作将花费 GFROUND * rounds gas,其中 GFROUND = 1。 详细的基准测试在基准测试附录部分中提供。

合理依据

BLAKE2 是预编译的绝佳候选者。 BLAKE2 针对现代 64 位 CPU 进行了大量优化,专门利用 24 位和 63 位旋转来通过 SIMD 指令和小端算术实现并行性。 这些特性在本地 CPU 上提供了卓越的速度:每字节 3.08 个周期,或在 Intel i5 上每秒 1 gibibyte。

相比之下,EVM 的大端 32 字节语义不利于 BLAKE2 的有效实现,因此,在 EVM 上计算哈希的 gas 成本与本地计算函数的真实成本不成比例。

一个显而易见的实现是直接的 BLAKE2b 哈希函数预编译。 乍一看,BLAKE2b 预编译满足了 EVM 上的大多数哈希和互操作性要求。 然而,一旦我们开始深入研究,很明显,任何 BLAKE2b 实现都需要基于不同项目的需求和库的特定功能和内部修改。

一个 与 Zcash 团队的讨论 使问题变得清晰。

ZEC-ETH 中继所需的最低限度是预编译中 BLAKE2b 压缩 F 的实现。

BLAKE2b 压缩函数 F 预编译也足以满足 Filecoin 和 Handshake 的互操作性目标。

完整的 BLAKE2b 预编译足以满足 ZEC-ETH 中继,前提是该实现提供了我们需要的 BLAKE2 API 的部分(个性化,可能还有其他东西 - 我不确定)。

我不 100% 确定完整的 BLAKE2b 预编译是否也足以满足 Filecoin 和 Handshake 的目标。 几乎肯定可以,前提是它支持他们需要的所有 API。

BLAKE2s - 无论是压缩函数 F 还是完整哈希 - 对于 ZEC-ETH 中继来说只是一个锦上添花的功能。

通过这次以及与该领域团队的其他对话,我们认为我们应该首先关注 F 预编译,因为它是互操作性项目绝对必需的部分。 BLAKE2b 预编译是一个锦上添花的功能,我们支持任何添加它的努力 - 但目前尚不清楚是否可以在伊斯坦布尔及时找到完整的要求和灵活的 API。

仅实现核心 F 压缩功能还可以实现相当大的灵活性和可扩展性,同时将协议级别的更改降至最低。 这将允许诸如树哈希、增量哈希、键控哈希、加盐哈希和个性化哈希以及可变长度摘要等功能,这些功能目前在 EVM 上都不可用。

向后兼容性

通过此 EIP 破坏向后兼容性的风险很小,唯一的问题是如果有人构建一个依赖于 0x09 处地址为空的合约。 这种情况发生的可能性很低,并且如果出现特定实例,则可以选择该地址为任何任意值,而碰撞风险可以忽略不计。

测试用例

测试向量 0

  • 输入:(空)
  • 输出:错误“BLAKE2 F 预编译的输入长度应为 213 字节”

测试向量 1

  • 输入: 00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001
  • 输出:错误“BLAKE2 F 预编译的输入长度应为 213 字节”

测试向量 2

  • 输入: 000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001
  • 输出:错误“BLAKE2 F 预编译的输入长度应为 213 字节”

测试向量 3

  • 输入: 0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002
  • 输出:错误“不正确的最终块指示器标志”

测试向量 4

  • 输入: 0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001
  • 输出: 08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b

测试向量 5

  • 输入: 0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001
  • 输出: ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923

测试向量 6

  • 输入: 0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000
  • 输出: 75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735

测试向量 7

  • 输入: 0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001
  • 输出: b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421

测试向量 8

  • 输入: ffffffff48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b6162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001
  • 输出: fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615

实现

在我们的 Golang BLAKE2 库 fork 中可以找到 F 函数的 Go 中的初始实现,该实现改编自标准库。 在我们的 go-ethereum fork 中也有预编译的实现。

参考

作为参考,有关此 EIP 的进一步讨论也发生在以下 PR 和问题中

附录 - 基准

假设 ecRecover 预编译的价格是完美的,我们执行了一组基准测试,比较了 Blake2b F 压缩函数预编译与 ecRecover 预编译。 对于基准测试,我们使用了 3.1 GHz Intel Core i7 64 位机器。

$ sysctl -n machdep.cpu.brand_string
Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz

12 轮

与 ecRecover 相比,具有 12 轮的 F 预编译调用的平均 gas 价格应为 6.74153,每轮 gas 价格为 0.5618

Name                                         Gascost         Time (ns)     MGas/S    Gasprice for 10MGas/S    Gasprice for ECDSA eq
-----------------------------------------  ---------  ----------------  ---------  -----------------------  -----------------------
PrecompiledEcrecover/                           3000  152636              19.6546                 1526.36               3000
PrecompiledBlake2F/testVectors2bX_0               12     338              35.503                     3.38                  6.64326
PrecompiledBlake2F/testVectors2bX_3               12     336              35.7143                    3.36                  6.60395
PrecompiledBlake2F/testVectors2bX_70              12     362              33.1492                    3.62                  7.11497
PrecompiledBlake2F/testVectors2bX_140             12     339              35.3982                    3.39                  6.66291
PrecompiledBlake2F/testVectors2bX_230             12     339              35.3982                    3.39                  6.66291
PrecompiledBlake2F/testVectors2bX_300             12     343              34.9854                    3.43                  6.74153
PrecompiledBlake2F/testVectors2bX_370             12     336              35.7143                    3.36                  6.60395
PrecompiledBlake2F/testVectors2bX_440             12     337              35.6083                    3.37                  6.6236
PrecompiledBlake2F/testVectors2bX_510             12     345              34.7826                    3.45                  6.78084
PrecompiledBlake2F/testVectors2bX_580             12     355              33.8028                    3.55                  6.97738

  • MGas/S - 显示当时在该机器上测量的 MGas 每秒数
  • Gasprice for 10MGas/S 显示了 gasprice 应该是什么,以便达到 10 MGas/秒
  • Gasprice for ECDSA eq 显示了 gasprice 应该是什么,以便具有与 ecRecover 相同的成本/周期

1200 轮

与 ecRecover 相比,具有 1200 轮的 F 预编译调用的平均 gas 价格应为 436.1288,每轮 gas 价格为 0.3634

Name                                         Gascost         Time (ns)     MGas/S    Gasprice for 10MGas/S    Gasprice for ECDSA eq
-----------------------------------------  ---------  ----------------  ---------  -----------------------  -----------------------
PrecompiledEcrecover/                           3000  156152              19.212                  1561.52               3000
PrecompiledBlake2F/testVectors2bX_0             1200   22642              52.9989                  226.42                434.999
PrecompiledBlake2F/testVectors2bX_3             1200   22885              52.4361                  228.85                439.668
PrecompiledBlake2F/testVectors2bX_70            1200   22737              52.7774                  227.37                436.824
PrecompiledBlake2F/testVectors2bX_140           1200   22602              53.0926                  226.02                434.231
PrecompiledBlake2F/testVectors2bX_230           1200   22501              53.331                   225.01                432.29
PrecompiledBlake2F/testVectors2bX_300           1200   22435              53.4879                  224.35                431.022
PrecompiledBlake2F/testVectors2bX_370           1200   22901              52.3995                  229.01                439.975
PrecompiledBlake2F/testVectors2bX_440           1200   23134              51.8717                  231.34                444.452
PrecompiledBlake2F/testVectors2bX_510           1200   22608              53.0786                  226.08                434.346
PrecompiledBlake2F/testVectors2bX_580           1200   22563              53.1844                  225.63                433.481

1 轮

与 ecRecover 相比,具有 1 轮的 F 预编译调用的平均 gas 价格应为 2.431701。 但是,在这种情况下,调用成本将完全掩盖动态成本。

``` Name

Citation

Please cite this document as:

Tjaden Hess <tah83@cornell.edu>, Matt Luongo (@mhluongo), Piotr Dyraga (@pdyraga), James Hancock (@MadeOfTin), "EIP-152: 添加 BLAKE2 压缩函数 `F` 预编译," Ethereum Improvement Proposals, no. 152, October 2016. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-152.