(原文地址:https://github.com/tevador/RandomX/blob/master/doc/design.md, 总共有3章以及附录,本篇是第3章以及附录)
3.自定义函数
3.1 AesGenerator1R
AesGenerator1R被设计成能尽可能快地生成伪随机数据去填充暂存器。它利用了现代CPU中硬件加速的AES。每16字节的输出只执行一次AES轮,这导致在大多数现代CPU中吞吐量可超过20 GB/s。
AesGenerator1R提供了一个良好的输出分布,但前提是由足够“随机”的初状态进行初始化(参见附录F)。
3.2 AesGenerator4R
AesGenerator4R使用4个AES轮去生成伪随机数据,用于程序缓冲区初始化。由于2个AES轮足以对所有输入比特位产生完全的雪崩效应[28],AesGenerator4R有良好的统计特性(见附录F),同时保持非常出色的性能。
这个生成器的可逆特性不是问题,因为生成器状态总是使用不可逆哈希函数(Blake2b)的输出进行初始化。
3.3 AesHash1R
AesHash被设计成能尽可能快地计算出暂存器指纹。它将暂存器解释为一组AES轮密钥,因此它相当于32768轮的AES加密。结束时再执行两轮额外的AES运算,以确保暂存器每个lane中所有比特位都能产生雪崩效应。
AesHash1R的可逆特性不是问题,主要有两个原因:
3.4 超标量哈希
超标量哈希被设计成在CPU等待从DRAM加载数据时消耗尽可能多的能量。170周期的目标延迟对应普通DRAM 40-80 ns的延迟和2-4 GHz的时钟频率。被设计成用于轻量模式挖矿,并带有低延迟内存的ASIC设备,在计算数据集项时会受制于超标量哈希造成的瓶颈,它们的效率也会被超标量哈希的高功耗破坏。
超标量哈希函数平均包含450条指令,其中155条是64位乘法。平均而言,最长依赖链的长度是95条指令。设计用于轻量模式挖矿的ASIC设备,带有256MiB的片上内存,所有操作都是1周期的延迟。假设可无限并行,则平均需要95 * 8 = 760个周期来构建一个数据集项。它将对每个数据集项执行155 * 8 = 1240次64位乘法,这将消耗与从DRAM加载64字节内容差不多的能量。
附录
A. 链式VM执行的效果
第1.2章描述了为什么N个随机程序被串联起来,以防止搜索“简单”程序的挖矿策略。RandomX使用的值为N = 8.
让我们定义Q为使用过滤器的策略中可接受程序的比例。例如,Q = 0.75意味着25%的程序被拒绝。
对于N = 1,没有浪费掉的程序执行,唯一的成本是程序生成和过滤器本身。下面的计算假设这些成本为零,仅有的实际成本是程序执行。然而,这是一种简化,因为RandomX中的程序生成不是毫无代价的(第一个程序生成需要完全初始化暂存器),但它描述了对攻击者而言的最佳情况。
对于N > 1,第一个程序通常可以被过滤掉,但是在(第一个)程序执行之后,下一个程序有1- Q的机会被拒绝,于是我们浪费了一次程序的执行。
对于N个程序链式执行,链中所有程序都可以接受的概率只有Q^N。然而,在每次尝试寻找这样的链时,我们都会浪费一些程序的执行。对于N = 8,每次尝试浪费的程序数等于(1-Q)*(1+2*Q+3*Q^2+4*Q^3+5*Q^4+6*Q^5+7*Q^6) = (7*Q^8-8*Q^7+1)/(1-Q) (Q = 0.75时,约等于2.5)。
让我们考虑3种挖矿策略:
策略I
诚实的矿工不拒绝任何程序(Q = 1)。
策略II
矿工使用优化的定制硬件,无法执行25%的程序(Q = 0.75),但它支持的程序可以快50%地执行。
策略III
矿工可以执行所有的程序,但拒绝最慢的25%的程序作为链中第一个程序。这为链中第一个程序提供了5%的性能增长(与附录C中的运行时间分布相匹配)。
结果
下表列出了上述3种策略和不同N值时的结果。N(I)、N(II)和N(III)三栏列出了每种策略平均要执行多少个程序才能得到一个有效的哈希结果(包括在拒绝的链中浪费的程序)。速度(I)、速度(II)和速度(III)三栏列出了相对于策略I的平均挖矿性能。
N | N(I) | N(II) | N(III) | 速度(I) | 速度(II) | 速度(III) |
1 | 1 | 1 | 1 | 1.00 | 1.50 | 1.05 |
2 | 2 | 2.3 | 2 | 1.00 | 1.28 | 1.02 |
4 | 4 | 6.5 | 4 | 1.00 | 0.92 | 1.01 |
8 | 8 | 27.0 | 8 | 1.00 | 0.44 | 1.00 |
尽管对于挑选的程序有50%的性能优势,N = 8时,策略II的执行速度将不到诚实矿工速度的一半。策略III的统计优势很小,当N = 8时可以忽略不计。
B. 性能仿真
正如2.7章中所述,RandomX旨在利用现代高性能CPU的复杂设计。为了评估超标量、乱序和推测执行的影响,我们执行了一个简化的CPU仿真。源代码可以在perf-simulation.cpp中找到。
CPU模型
模型CPU使用一个3阶段流水线来实现每周期一条指令的理想吞吐量:
(1) (2) (3)
指令预取和解码 ---> 内存访问 ---> 执行
这三个阶段是:
1.指令预取和解码。这个阶段从程序缓冲区加载指令并解码指令操作和操作数。
2.内存访问。如果此指令使用内存操作数,此阶段它会从暂存器载入。这包括内存地址的计算。存储也在这个阶段执行。地址寄存器的值在这个阶段必须是可用的。
3.执行。此阶段使用前一阶段取到的操作数执行指令,并将结果写入寄存器文件。
请注意,这是一个乐观的短流水线,不允许非常高的时钟速度。使用更长流水线的设计将显著增加推测执行的好处。
超标量执行
我们的模型CPU包含两种组件:
超标量设计将包含多个执行或内存单元,以提高性能。
乱序执行
仿真模型支持两种设计:
1.顺序——所有指令都是按照它们在程序缓冲区中出现的顺序执行的。如果遇到依赖项或所需的EXU/MEM单元不可用,此设计将导致暂停。
2.乱序——不按程序顺序执行指令,但是当一条指令的操作数已准备好并且需要的EXU/MEM单元可用时,就可以执行该指令。
分支处理
仿真模型支持两种类型的分支处理:
非推测——当遇到分支时,流水线就会暂停。这通常会给每个分支增加3个周期的代价。
推测——所有分支都被预测为不会执行,如果发生错误预测(概率为1/256),流水线将被刷新。
结果
以下10种设计进行了模拟,并测量了执行一个RandomX程序(256条指令)的平均时钟周期数。
设计 | 超标量配置 | 重新排序 | 分支处理 | 执行时间 (周期数) |
IPC |
#1 | 1 EXU + 1 MEM | 顺序 | 非推测 | 293 | 0.87 |
#2 | 1 EXU + 1 MEM | 顺序 | 推测 | 262 | 0.98 |
#3 | 2 EXU + 1 MEM | 顺序 | 非推测 | 197 | 1.3 |
#4 | 2 EXU + 1 MEM | 顺序 | 推测 | 161 | 1.6 |
#5 | 2 EXU + 1 MEM | 乱序 | 非推测 | 144 | 1.8 |
#6 | 2 EXU + 1 MEM | 乱序 | 推测 | 122 | 2.1 |
#7 | 4 EXU + 2 MEM | 顺序 | 非推测 | 135 | 1.9 |
#8 | 4 EXU + 2 MEM | 顺序 | 推测 | 99 | 2.6 |
#9 | 4 EXU + 2 MEM | 乱序 | 非推测 | 89 | 2.9 |
#10 | 4 EXU + 2 MEM | 乱序 | 推测 | 64 | 4.0 |
超标量、无序和推测执行设计的好处被清楚地展现出来。
C. RandomX运行时间分布
运行时间的数值是在AMD Ryzen 7 1700上测量的,运行在3.0GHz下,使用一个核心。测量程序执行和验证时间的源代码可以在runtime-distr.cpp中找到。测量x86 JIT编译器性能的源代码可以在jit-performance.cpp中找到。
快速模式-程序执行
下图显示了单个VM程序(在快速模式下)的运行时间分布。这包括:程序生成、JIT编译、VM执行和寄存器文件的Blake2b哈希计算。测出程序生成和JIT编译要消耗3.6μs /程序。
AMD Ryzen 7 1700在快速模式下(使用1线程)每秒可以计算625次哈希,这意味着一个哈希结果需要1600μs(1.6ms)。这包括(近似):
这给出了头部的总占比为7.5% ((45+45+30)/1600=7.5%,不包括VM执行的每次哈希消耗时间)。
轻量模式-验证时间
下图显示了使用轻量模式计算1个哈希结果的时间分布。大多数时间花费在执行数据集项超标量哈希的计算上(14.8 ms中的13.2 ms)。平均验证时间正好匹配CryptoNight算法的性能。
D.暂存器熵分析
8次程序执行后的暂存器的平均熵由LZMA压缩算法粗略估算:
1.计算哈希结果,并将最终的暂存器内容以带“.spad”扩展名的文件写入磁盘 (源代码:scratchpad-entropy.cpp)
2.文件被7-Zip [29]在极限压缩模式下进行压缩:7z.exe a -t7z -m0=lzma2 -mx=9 scratchpads.7z*.spad
压缩文件的大小大约是未压缩暂存器文件大小的99.98%。这表明在VM执行期间,暂存器保持了较高的熵。
E.超标量哈希分析
超标量哈希是RandomX用来生成数据集项的自定义函数。它操作8个整数寄存器,并使用随机指令序列。大约1/3的指令是乘法。
下图显示了超标量哈希对于改变输入寄存器的单个比特位的敏感性:
这表明,超标量哈希对高比特位的敏感度较低,对最低比特位的敏感度更低。敏感度最高的是第3-53位(包括)。
在计算数据集项时,第一个超标量哈希的输入仅依赖于项的编号。为确保结果的良好分布,选择了本说明书第7.3节中描述的常量,以便为0-34078718范围内的所有项目编号(数据集包含34078719个项目)提供惟一的第3-53位值。所有编号的数据集项的所有初始寄存器值都经过了检查,以确保每个寄存器的3-53位是唯一的,没有碰撞(源代码:superscalar-init.cpp)。虽然这对于从超标量哈希函数中获得惟一输出而言,并不是严格必需的,但这是一种安全预防措施,可以减轻随机生成的超标量哈希实例的非完美雪崩特性。
F. 随机数生成器的统计检验
AesGenerator1R和AesGenerator4R均使用TestU01库[30]进行测试,该库用于随机数生成器的经验测试。源代码可以在rng-tests.cpp中找到。
该测试从每个生成器的输出中抽取大约200MB(“小碎片”测试)、500GB(“碎片”测试)或4TB(“大碎片”测试)的样本。这比RandomX中生成的数量要多得多(AesGenerator4R是2176字节,AesGenerator1R是2MiB),因此,未通过本测试并不一定意味着生成器不适合它们的使用场景。
AesGenerator4R
当使用Blake2b哈希函数初始化时,此生成器通过了“大碎片”套件中的所有测试:
$ bin/rng-tests 1
state0 = 67e8bbe567a1c18c91a316faf19fab73
state1 = 39f7c0e0a8d96512c525852124fdc9fe
state2 = 7abb07b2c90e04f098261e323eee8159
state3 = 3df534c34cdfbb4e70f8c0e1826f4cf7
…
========= Summary results of BigCrush ========
Version: TestU01 1.2.3
Generator: AesGenerator4R
Number of statistics: 160
Total CPU time: 02:50:18.34
All tests were passed
即使初始状态设置为全0,生成器也能通过“碎片”套件中的所有测试。
$ bin/rng-tests 0
state0 = 00000000000000000000000000000000
state1 = 00000000000000000000000000000000
state2 = 00000000000000000000000000000000
state3 = 00000000000000000000000000000000
…
========= Summary results of Crush =========
Version: TestU01 1.2.3
Generator: AesGenerator4R
Number of statistics: 144
Total CPU time: 00:25:17.95
All tests were passed
AesGenerator1R
使用Blake2b哈希函数初始化时,生成器通过了“碎片”套件中的所有测试。
$ bin/rng-tests 0
state0 = 67e8bbe567a1c18c91a316faf19fab73
state1 = 39f7c0e0a8d96512c525852124fdc9fe
state2 = 7abb07b2c90e04f098261e323eee8159
state3 = 3df534c34cdfbb4e70f8c0e1826f4cf7
…
========= Summary results of Crush =========
Version: TestU01 1.2.3
Generator: AesGenerator1R
Number of statistics: 144
Total CPU time: 00:25:06.07
All tests were passed
当初始状态设置为全0时,生成器在“碎片”套件的144个测试中有1个测试失败:
$ bin/rng-tests 0
state0 = 00000000000000000000000000000000
state1 = 00000000000000000000000000000000
state2 = 00000000000000000000000000000000
state3 = 00000000000000000000000000000000
…
========= Summary results of Crush =========
Version: TestU01 1.2.3
Generator: AesGenerator1R
Number of statistics: 144
Total CPU time: 00:26:12.75
The following tests gave p-values outside [0.001, 0.9990]:
(eps means a value < 1.0e-300):
(eps1 means a value < 1.0e-15):
Test p-value
----------------------------------------------
12 BirthdaySpacings, t = 3 1 - 4.4e-5
----------------------------------------------
All other tests were passed
参考资料:
[1] CryptoNote whitepaper - https://cryptonote.org/whitepaper.pdf
[2] ProgPoW: Inefficient integer multiplications - https://github.com/ifdefelse/ProgPOW/issues/16
[3] Cryptographic Hashing function - https://en.wikipedia.org/wiki/Cryptographic_hash_function
[4] randprog - https://github.com/hyc/randprog
[5] RandomJS - https://github.com/tevador/RandomJS
[6] μop cache - https://en.wikipedia.org/wiki/CPU_cache#Micro-operation_(%CE%BCop_or_uop)_cache
[7] Instruction-level parallelism - https://en.wikipedia.org/wiki/Instruction-level_parallelism
[8] Superscalar processor - https://en.wikipedia.org/wiki/Superscalar_processor
[9] Out-of-order execution - https://en.wikipedia.org/wiki/Out-of-order_execution
[10] Speculative execution - https://en.wikipedia.org/wiki/Speculative_execution
[11] Register renaming - https://en.wikipedia.org/wiki/Register_renaming
[12] Blake2 hashing function - https://blake2.net/
[13] Advanced Encryption Standard - https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
[14] Log-normal distribution - https://en.wikipedia.org/wiki/Log-normal_distribution
[15] CryptoNight hash function - https://cryptonote.org/cns/cns008.txt
[16] Dynamic random-access memory - https://en.wikipedia.org/wiki/Dynamic_random-access_memory
[17] Multi-channel memory architecture - https://en.wikipedia.org/wiki/Multi-channel_memory_architecture
[18] Obelisk GRN1 chip details - https://www.grin-forum.org/t/obelisk-grn1-chip-details/4571
[19] Biryukov et al.: Tradeoff Cryptanalysis of Memory-Hard Functions - https://eprint.iacr.org/2015/227.pdf
[20] SK Hynix 20nm DRAM density - http://en.thelec.kr/news/articleView.html?idxno=20
[21] Branch predictor - https://en.wikipedia.org/wiki/Branch_predictor
[22] Predication - https://en.wikipedia.org/wiki/Predication_(computer_architecture)
[23] CPU cache - https://en.wikipedia.org/wiki/CPU_cache
[24] Cortex-A55 Microarchitecture - https://www.anandtech.com/show/11441/dynamiq-and-arms-new-cpus-cortex-a75-a55/4
[25] AMD Zen+ Microarchitecture - https://en.wikichip.org/wiki/amd/microarchitectures/zen%2B#Memory_Hierarchy
[26] Intel Skylake Microarchitecture - https://en.wikichip.org/wiki/intel/microarchitectures/skylake_(client)#Memory_Hierarchy
[27] Biryukov et al.: Fast and Tradeoff-Resilient Memory-Hard Functions for Cryptocurrencies and Password Hashing - https://eprint.iacr.org/2015/430.pdf Table 2, page 8
[28] J. Daemen, V. Rijmen: AES Proposal: Rijndael - https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf page 28
[29] 7-Zip File archiver - https://www.7-zip.org/
[30] TestU01 library - http://simul.iro.umontreal.ca/testu01/tu01.html