Argon2id 工作量证明详解

· 7 分钟阅读

本文介绍 Argon2id 是什么、Blocknet 如何把它用作工作量证明、它为什么偏爱大容量高速内存,以及 seine 如何在它运行的每颗 CPU 上算出尽可能多的哈希。

Argon2id 是什么?

简短回答

Argon2id 是一种内存密集型(memory-hard)哈希函数,在 RFC 9106 中定义,用于密码哈希和工作量证明。它用 1 KiB 的数据块填满一大片内存,每个块由前一个块和内存中另一处更早的块计算得出,因此计算一次哈希既要花处理器时间,也要占用实实在在的内存。

RFC 9106 由 IRTF 的密码论坛研究组(CFRG)于 2021 年发布,描述了“用于密码哈希和工作量证明应用的 Argon2 内存密集型函数”。Argon2 有三个变体:

在内部,Argon2 把内存看作一长串 1,024 字节的块。每个新块都是压缩函数 G 作用于两个输入的结果:紧挨在它前面的块,以及内存中更早位置的一个参考块。G 基于 BLAKE2b 哈希函数的混合轮函数构建,对 64 位字进行加法、异或和循环移位运算。

Blocknet 如何把 Argon2id 用作工作量证明?

Blocknet 在共识代码中固定了 Argon2id 的每一项设置,因此每个矿工和每个节点计算的都是完全相同的函数:

参数 Blocknet 取值 含义
变体 Argon2id,1.3 版 RFC 9106 的主要变体
内存 2 GiB(2,097,152 KiB) 每次哈希填满 2,097,152 个 1 KiB 的块
迭代次数 1 对内存只处理一遍
并行度 1 只有一条通道,一次哈希在一个线程上运行
输出 32 字节 与目标值比较的结果
密码 8 字节 nonce,小端序 矿工每次尝试时改变的数字
盐值 不含 nonce 的区块头 把哈希绑定到某一个区块模板

挖矿就是一种搜索。矿工拿着想要发布的区块头,选一个 nonce,计算 Argon2id 哈希。如果这 32 字节的结果作为数字小于或等于全网的目标值,这个区块就是有效的;否则矿工就换下一个 nonce 继续尝试。

目标值直接由难度得出:它等于 (2^256 − 1) 除以难度。因此,难度大致等于全网找到一个区块预计需要计算的哈希次数。Blocknet 的目标是每五分钟出一个块,所以全网算力约等于难度除以 300 秒。难度本身通过 60 个区块的加权移动平均来跟随最近的出块时间。

矿池的原理相同,只是目标更容易达到。在 bntpool.com 上,seine 提交满足较低的单连接难度的份额,其中同时满足全网目标的份额就是找到的区块。

为什么内存密集型工作量证明适合普通电脑?

上面的设置让内存成为每次哈希的核心。由于只处理一遍、只有一条通道,这一遍的后半段是数据相关的:每个块的参考块位置取自紧挨在它之前计算出的块的第一个 64 位字。矿工直到需要的那一刻才知道要用哪个更早的块,所以必须把整块 2 GiB 区域留在内存中。

矿工也可以尝试少存一些块,需要时再重新计算缺失的块。RFC 9106 把这称为时间-内存权衡:它们节省内存,“代价是更多次调用内部压缩函数”。从区域中丢掉的每一个块,都要用额外的计算再付一次代价。

这正是这种设计对普通硬件友好的原因。台式机、笔记本和服务器本来就配有数 GB 内存,以及为供给这些内存而设计的内存控制器。专为哈希打造的芯片,仍然需要在哈希核心旁边配备数 GB 高速内存。RFC 9106 的首选推荐设置是对 2 GiB 内存只处理一遍的 Argon2id,与 Blocknet 采用的处理遍数和内存大小相同,RFC 指出这一设置“使对手在专用暴力破解硬件上的成本最大化”。Blocknet 节点程序的代码则把这一选择描述为内存密集型工作量证明,“以抵御 ASIC 并确保公平挖矿”。Argon2id 的设计旨在抵御 ASIC,2 GiB 的设置让这一设计发挥作用,使人们已有的电脑都能参与挖矿。工作量证明与权益证明的区别一文介绍了它与质押相比有何不同。

为什么内存容量和内存带宽决定算力?

简短回答

每个挖矿线程都要占用自己的一块 2 GiB 区域,所以内存容量决定你能运行多少个线程。每次哈希还要写入 2 GiB 数据,并从分散的位置读回差不多同样多的数据,所以内存的速度和延迟决定这些线程完成得有多快。内存的影响与核心数量同样大。

内存容量决定线程数。 并行度为 1 意味着一次哈希在一个线程上运行,每个线程都需要一整块属于自己的 2 GiB 区域。一台 16 GB 内存的机器在为系统留出空间后,大约能运行六到七个线程。seine 会检测你的内存,预留 5%,并为你选择线程数。

带宽和延迟决定每个线程的速度。 一次哈希会生成 2,097,152 个新块,每个块都要从区域中的某处读取一个 1 KiB 的参考块。算下来,每次哈希大约写入 2 GiB、读取 2 GiB。以 10 H/s 计算,机器每秒要在内存中搬运大约 40 GiB 数据。在数据相关的后半段,每个读取地址要等前一个块算完才能知道,所以内存的响应时间与原始吞吐量同样重要。

这就是 seine 的性能页面说算力“既取决于核心数量,也同样取决于内存带宽和延迟”的原因。Blocknet 挖矿硬件指南列出了各代硬件的最佳结果。

大页内存有帮助也是同样的道理。一块 2 GiB 区域需要 524,288 个普通的 4 KiB 页面,或 1,024 个 2 MiB 页面。页面更少、更大,那些分散的读取就会更少发生地址转换未命中,在 seine 用于验证的测试机上带来了约 4–6% 的提升。设置方法见大页内存指南。

seine 的 CPU 计算内核如何充分利用 Argon2id?

计算内核是计算这些块的内层循环。seine 的 x86 版本带有标量、AVX2 和 AVX-512 计算内核以及一个流式变体,Arm 版本带有 NEON 及其 SHA3 快速路径。它们各自以不同方式处理受内存限制的工作:

在我们测试的硬件上,选对计算内核带来了很大提升。以下是同一台机器、相同线程数下的成对测试结果,来自 seine 的性能页面:

机器 计算内核变化 提升
AWS Graviton4(Neoverse V2),16 线程 NEON 改为带预取的 SHA3 +56%(从 13.80 到 21.50 H/s)
AWS Graviton3(Neoverse V1),16 线程 NEON 改为带预取的 SHA3 +45%(从 12.52 到 18.17 H/s)
AMD EPYC 9R45(Zen 5),24 线程 AVX2 改为流式 +30%(从 34.47 到 44.74 H/s)
Intel Xeon 8375C(Ice Lake),1–4 线程 AVX2 改为 AVX-512 +21–29%

这些都无需你自己选择。seine 会用真实的挖矿任务比较你的 CPU 支持的各个计算内核,只在两次确认有提升后才切换,并把胜出者缓存起来供下次启动使用。最佳计算内核取决于线程数、页面大小和 CPU 绑定方式,所以 seine 会在你的机器上实测,而不是写死某个选择。

想让你的内存发挥作用,就下载 seine,运行它,然后粘贴你的地址。其余步骤见挖矿指南。

常见问题

Blocknet 的工作量证明使用哪些 Argon2id 参数?

Blocknet 使用 Argon2id 1.3 版,内存 2 GiB,迭代 1 次,并行度 1,输出 32 字节。8 字节的 nonce 作为密码,不含 nonce 的区块头作为盐值。当哈希值小于或等于由全网难度决定的目标值时,就找到了一个区块。

为什么每个 Blocknet 挖矿线程需要 2 GB 内存?

每次 Argon2id 哈希都要填满一块由 1 KiB 数据块组成的 2 GiB 内存区域。在这一遍的后半段,每个新块都要读取一个更早的块,具体是哪一块取决于矿工无法预知的数据。线程必须把整块区域留在内存中,所以每运行一个线程大约需要 2 GB 内存。

为什么说 Argon2id 的设计旨在抵御 ASIC?

Argon2id 让内存成为每次哈希中成本最高的部分。每次 Blocknet 哈希需要 2 GiB 高速内存,并在其中搬运数 GB 数据,因此专为哈希打造的芯片仍需为每个核心配备大量内存。节点程序的代码把这一选择描述为用内存密集型工作量证明来抵御 ASIC,确保公平挖矿。

显卡能挖 Blocknet 的 Argon2id 吗?

可以。在 Windows 和 Linux 上,seine 会在 CPU 挖矿的同时使用机器上的每张 NVIDIA 显卡,两者的算力会叠加。计算内核在启动时编译,首次启动时会为每张显卡调优并缓存结果,所以开始之前无需任何配置。