跳转到主要内容
Change page

Ethash

页面最后更新: 2026年4月15日

Ethash 是以太坊的工作量证明挖矿算法。 工作量证明现在已经被完全关闭,取而代之,以太坊现在使用权益证明来保证安全。 阅读更多关于合并权益证明质押的信息。 此页面是为了满足对历史的兴趣!

Ethash 是 Dagger-Hashimoto 算法的修改版本。 Ethash 工作量证明是内存困难 (opens in a new tab)的,这被认为使该算法具有抗 ASIC 的特性。 Ethash 专用集成电路最终被开发出来,但在工作量证明被关闭之前,图形处理单元挖矿仍然是一个可行的选择。 Ethash 仍然用于在其他非以太坊工作量证明网络上挖掘其他币。

Ethash 是如何工作的?

内存硬度通过工作量证明算法实现,需要根据随机数和区块头选择固定资源子集。 该资源(大小为几 GB)称为有向无环图。 有向无环图每 30000 个区块更改一次(大约 125 小时的窗口,称为一个时段(大约 5.2 天)),需要一段时间才能生成。 由于有向无环图仅依赖于区块高度,因此可以预先生成,但如果没有,则客户端需要等到此过程结束才能生成区块。 如果客户端没有提前预生成和缓存有向无环图,网络可能会在每个时段过渡时遇到严重的区块延迟。 请注意,不需要生成有向无环图即可验证,工作量证明本质上允许使用低端中央处理器和小内存进行验证。

该算法采取的一般路线如下:

  1. 存在一个种子,它可以通过扫描截止到该区块的区块头来计算得出。
  2. 从种子可以计算出一个 16 MB 的伪随机缓存。 轻量级客户端存储缓存。
  3. 从缓存中,我们可以生成一个 1 GB 的数据集,其特性是,数据集中的每个项目仅依赖于缓存中的少量项目。 全客户端和矿工存储数据集。 数据集随着时间的流逝而呈线性增长。
  4. 采矿会抢走数据集的随机片段并将它们散列在一起。 可以通过使用缓存来重新生成你需要的数据集中的特定区块,以较低的内存进行验证,以使你只需要存储缓存。

每隔 30000 个区块更新一次大数据集,因此,矿工的绝大部分工作都是读取数据集,而不是对其进行修改。

定义

我们采用以下定义:

使用“SHA3”

以太坊的开发恰逢 SHA3 标准的制定, 标准进程对最终确定的哈希算法的填充做了后期改动,使得以太坊的 “sha3_256”和“sha3_512”哈希值不是标准的 sha3 哈希值,而是在其他情况下 常被称为“Keccak-256”和“Keccak-512”的变量。 例如,请参阅此处 (opens in a new tab)此处 (opens in a new tab)此处 (opens in a new tab)的讨论。

请记住这一点,因为下面的算法描述中提到了“sha3”哈希值。

参数

Ethash 的缓存和数据集的参数取决于区块号。 缓存大小和数据集大小都呈线性增长;然而,我们总是取低于线性增长阈值的最高素数,以降低意外规律导致循环行为的风险。

附录中提供了数据集和缓存大小值表。

缓存生成

现在,我们来指定生成缓存的函数:

缓存生成过程首先需要按顺序填满 32 MB 内存,然后执行两轮 Sergio Demian Lerner 的 RandMemoHash 算法,该算法出自 Strict Memory Hard Hashing Functions (2014) (opens in a new tab)。 输出一组 524288 个 64 字节值。

数据聚合函数

在某些情况下,我们使用一种受 FNV 哈希 (opens in a new tab) 启发的算法,作为异或运算 (XOR) 的非关联替代。 请注意,我们使用全 32 位输入乘以素数,与之相对地,FNV-1 spec 用 1 个字节(8 个字节)依次乘以素数。

FNV_PRIME = 0x01000193

def fnv(v1, v2):
    return ((v1 * FNV_PRIME) ^ v2) % 2**32

请注意,即使黄皮书也指出 fnv 为 v1*(FNV_PRIME ^ v2),所有当前实现始终采用上述定义。

完整数据集计算

整个 1 GB 数据集中每个 64 字节项目的计算如下:

基本上,我们将来自 256 个伪随机选择的缓存节点的数据聚集起来求哈希值,以计算数据集节点。 然后生成整个数据集:

def calc_dataset(full_size, cache):
    return [calc_dataset_item(cache, i) for i in range(full_size // HASH_BYTES)]

主循环

现在,我们指定了类似“hashimoto”的主要循环。在此循环中,我们聚合整个数据集的数据,以生成特定区块头和随机数的最终值。 在下面的代码中,header 代表一个_截断_区块头的 RLP 编码的 SHA3-256 哈希,也就是移除了 mixHashnonce 字段的区块头。 nonce 是一个 64 位无符号整数的八个字节,采用大端序。 因此 nonce[::-1] 是该值的八字节小端序表示:

从本质上讲,我们维护一个 128 字节宽的 "mix",并从完整数据集中重复顺序获取 128 字节,然后使用 fnv 函数将其与 mix 合并。 使用 128 字节的序列访问,以便每轮算法总是能从随机访问内存获取完整的页面,从而尽量减少转译后备缓冲区的疏忽,而专用集成电路在理论上能够避免这些疏忽。

如果此算法的输出低于所需目标,即证明随机数是有效的。 请注意,末尾额外应用的 sha3_256 确保了中间随机数的存在,可以提供该随机数来证明至少完成了少量的工作;这种快速的外部 PoW 验证可用于抵御 DDoS 攻击。 也可提供统计保证,说明结果是一个无偏 256 位数字。

挖矿

挖矿算法定义如下:

def mine(full_size, dataset, header, difficulty):
    # 将目标补零,以便与相同位数的哈希进行比较
    target = zpad(encode_int(2**256 // difficulty), 64)[::-1]
    from random import randint
    nonce = randint(0, 2**64)
    while hashimoto_full(full_size, dataset, header, nonce) > target:
        nonce = (nonce + 1) % 2**64
    return nonce

定义种子哈希

为了计算用于在给定区块上挖掘的种子哈希值,我们使用以下算法:

 def get_seedhash(block):
     s = '\x00' * 32
     for i in range(block.number // EPOCH_LENGTH):
         s = serialize_hash(sha3_256(s))
     return s

请注意,为了顺利挖矿和验证,我们建议在单个线程中预先计算未来的种子哈希值和数据集。

扩展阅读{#further-reading}

你还知道哪些对你有帮助的社区资源? 请编辑本页面并添加进来!

附录

如果你有兴趣将上述 python spec 作为代码运行,则应在头部添加以下代码。

数据大小

以下查找表列表显示了大约 2048 个数据大小和缓存大小时段。

这篇文章对您有帮助吗?