纯Python比特币挖矿实验:区块头构造与nonce扫描详解
2026/9/16 23:52:28 网站建设 项目流程

用 2013 年的 USB 矿机、一台 Jalapeño 矿机,再加上手机,跑一套纯 Python 的比特币挖矿程序,听起来像是旧货市场的考古项目。真正把流程走完你会发现,它比任何解释比特币挖矿原理的 PPT 都直观:区块头 80 个字节怎么构造,难度目标怎么比较,nonce 在哪一段范围内扫描,Stratum 协议如何在矿机和矿池之间交换作业。这篇文章要完成的实验,就是用纯 Python 写一个能连接 USB 矿机、模拟器或手机的矿工程序,配合本地模拟矿池,把一轮完整的工作量证明流程跑通。它不是为了盈利,而是为了把比特币协议中容易被忽略的细节全部暴露出来。

整个过程可以在普通 PC、树莓派或者 Android 手机的 Termux 里复现。旧矿机不是必需品,因为驱动程序、供电和散热都会额外增加不确定性,所以文中会先给一个软件模拟器,再说明接入真实 2013 年 USB 矿机时需要补哪些依赖。无论哪种方式,核心逻辑都一样:构造区块头,做双重 SHA-256,拿结果和难度目标比较,找到合适 nonce 后提交给矿池。下面从最底层的区块头结构开始。

1. 为什么还要折腾 2013 年的 USB 矿机、Jalapeño 和手机

1.1 十年前的设备与今天比特币网络的差距

2013 年前后,市面上出现过一批用于比特币挖矿的初代 ASIC 矿机,其中 USB 形式的矿机(例如 Block Erupter)因为体积小、接口简单,成为很多人接触挖矿硬件的起点。这类矿机的算力普遍在数百 MH/s 级别,而 Butterfly Labs 的 Jalapeño 矿机算是当时定位更接近“台式机外设”的产品,标称算力在 GH/s 量级。用今天的眼光看,这些设备已经没有任何经济价值,纯粹是比特币挖矿史上的标本。

今天的比特币网络难度来自全网算力。全网算力通常以 EH/s 为单位,也就是每秒执行 10 的 18 次方次 SHA-256。相比之下,一台 2013 年 USB 矿机的 300 多 MH/s,只是全网算力的十亿分之一以下。这意味着即使旧设备 24 小时开机,也几乎不可能碰撞出满足当前难度目标的 nonce。更不用说用 Python 的纯解释器循环去跑,哈希率通常只有几万到几十万 H/s。

所以这篇文章的出发点不是“把旧设备重新变成印钞机”,而是“把有价值的协议细节重新捡起来”。设备越旧,性能越低,越能让你感受到比特币工作量证明在真实网络里有多困难。

1.2 这本质上是一堂比特币协议实验课

比特币挖矿可以拆成两层:一层是矿工和矿池之间的通信协议,另一层是矿机内部执行的 SHA-256 搜索。很多资料把这两层混在一起讲,导致你既没看懂区块头,也没看懂 Stratum。纯 Python 的写法有一个好处:所有行为都发生在看得见的代码里,没有 C 语言指针、没有编译优化、也没有闭源 SDK,每一步都能打印出来确认。

实验课的目标很具体:

  • 构造一个 80 字节的比特币区块头。
  • 用 Python 计算双重 SHA-256,验证一个真实区块的哈希。
  • 把 bits 字段解析成难度目标 target。
  • 在 low nonce 区间内扫描,找到满足hash < target的 nonce。
  • 用本地模拟矿池跑一轮完整的工作分发、提交 share、接受 share 流程。
  • 在手机上用 Termux 运行同一个纯 Python 脚本,验证程序不依赖特定平台。

这些步骤完成后,你对“矿机到底在算什么”这个问题会有一个非常确定的答案。

1.3 可用硬件清单与算力参考

先列一个硬件参考表。下面表格里的算力数字来自当时公开资料,实际设备可能因为固件、供电和散热不同有出入,落地前还是要以你自己的设备为准。

设备接口典型算力量级在本文中的角色
普通 PC CPU5 万到 50 万 H/s(纯 Python)跑纯 Python 矿工和模拟矿池
2013 年 USB 矿机(如 Block Erupter)USB数百 MH/s接入真实硬件,或先由模拟器代替
Butterfly Labs JalapeñoUSB 或网口数千 MH/s 到 10 GH/s演示更高算力设备如何接入软件
Android 手机 Termux几千到几万 H/s(纯 Python)手机端纯 Python CPU 矿工
本地模拟矿池回环网口无真实算力下发 job、验证 share、打印协议日志

学习环境里,设备算力不是重点,重点是让每个参与方都能稳定运行。真实设备可以先放一边,用模拟器把软件链路串通,再考虑把硬件接进来。

2. 先搞懂比特币挖矿最核心的数学:区块哈希、难度目标和 nonce

2.1 区块头结构和 SHA-256d 计算

比特币的 PoW 算法不直接计算一笔交易的哈希,而是计算整个区块头的哈希。区块头固定长度 80 字节,字段布局如下:

字段大小说明
version4 字节区块版本,通常与软分叉规则相关
hashPrevBlock32 字节上一个区块头的哈希(内部存储为小端序)
hashMerkleRoot32 字节当前区块所有交易组成的 Merkle 根(内部存储为小端序)
time4 字节Unix 时间戳
bits4 字节压缩后的难度目标
nonce4 字节矿工不断改变的数字

挖矿的目标是让整个区块头经过两次 SHA-256 之后,得到的 256 位整数小于当前难度目标。为什么要做两次 SHA-256?这个设计最初是为了防止长度扩展攻击,也成为了比特币协议的一部分。代码上就是:

import hashlib def double_sha256(data: bytes) -> bytes: return hashlib.sha256(hashlib.sha256(data).digest()).digest()

这里第一层hashlib.sha256(data).digest()是 32 字节摘要,第二层再次对这个摘要做 SHA-256。最终结果就是矿工拿来进行难度比较的哈希值。

2.2 用纯 Python 验证一个真实区块哈希

学习区块头最有效的方式,是拿一个已知真实哈希的区块来验证。比特币创始区块的完整哈希是人尽皆知的公开数据,可以直接作为测试向量。

下面是构造创始区块头并计算真实哈希的 Python 示例:

import hashlib import struct def double_sha256(data: bytes) -> bytes: return hashlib.sha256(hashlib.sha256(data).digest()).digest() version = 1 prev_hash = "0000000000000000000000000000000000000000000000000000000000000000" merkle_root = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" time_ = 1231006505 bits = 0x1d00ffff nonce = 2083236893 header = ( struct.pack("<I", version) + bytes.fromhex(prev_hash)[::-1] + bytes.fromhex(merkle_root)[::-1] + struct.pack("<I", time_) + struct.pack("<I", bits) + struct.pack("<I", nonce) ) hash_bytes = double_sha256(header) block_hash = hash_bytes[::-1].hex() print(block_hash)

输出结果应当是:

000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

这个测试向量是确定的,你可以直接用来检查自己的区块头构造逻辑是否正确。如果输出不是这个哈希,大概率是字节序问题。

2.3 字节序最容易错:小端、无符号整数和切片

比特币协议里的字节序是新手最容易踩坑的地方。prev_hashmerkle_root在区块头里以小端序存储,所以从十六进制字符串构造字节数组时,需要先bytes.fromhex(...),再[::-1]反转。versiontimebitsnonce都是无符号 32 位整数,用struct.pack("<I", ...)以小端序打包。

哈希比较时又是另一套规则。SHA-256 的digest()可以按大端序解释成一个 256 位整数。也就是说,条件int.from_bytes(double_sha256(header), "big")小于 target 才表示有效工作量证明。但打印区块哈希时,为了显示成浏览器里常见的格式,又要把digest()反过来写成十六进制。

最容易混淆的几种情况:

场景正确做法常见错误
构造区块头bytes.fromhex(prev_hash)[::-1]直接使用原始 hex,忘记反转
打包整数struct.pack("<I", time_)使用>大端序
难度比较int.from_bytes(digest, "big")使用digest[::-1]后比较
打印哈希digest[::-1].hex()直接打印digest.hex()

字节序问题通常不会报错,只会让结果看起来像随机数。排查时先确认第一个测试向量能跑对,再去做后面的扫描实验。

3. 纯 Python 实现一个最小 CPU 矿工,先跑通“找到 nonce”的完整过程

3.1 构造低难度任务

真实比特币网络的难度比创始区块高得多,纯 Python 不可能在当前难度下找到 nonce。因此教学实验要主动降低难度。难度目标由bits字段编码,解析公式是:

def bits_to_target(bits: int) -> int: exponent = (bits >> 24) & 0xff mantissa = bits & 0x007fffff if exponent <= 3: return mantissa >> (8 * (3 - exponent)) return mantissa << (8 * (exponent - 3))

例如创世区块的bits0x1d00ffff,用这个函数解析后会得到一个很大的 target。实际扫描时,可以避免使用真实难度,而是直接把 target 设置成一个比较小的数,比如1 << 240,意思是要求哈希小于 2 的 240 次方。这样前 16 位左右必须为零,大约几十万次哈希内有机会找到。配合 Python 每秒几万次到几十万次的哈希率,实验可以在几秒到几十秒内完成。

区块头的前 76 字节是固定的,nonce 是最后 4 字节。扫描时只改变 nonce,不需要重复构造前面所有字段:

import struct import time from hashlib import sha256 def double_sha256(data: bytes) -> bytes: return sha256(sha256(data).digest()).digest() def build_header76(version, prev_hash, merkle_root, time_, bits): return ( struct.pack("<I", version) + bytes.fromhex(prev_hash)[::-1] + bytes.fromhex(merkle_root)[::-1] + struct.pack("<I", time_) + struct.pack("<I", bits) )

3.2 实现目标值比较和 nonce 扫描

下面是完整的纯 Python 扫描函数。它接收一个已经构造好的 76 字节 header 前缀、一个 target 整数,以及最大 nonce 值。每轮把 nonce 合并进 header,计算双重 SHA-256,然后比较大小。

def mine(header76: bytes, target: int, max_nonce: int): start = time.time() hashes = 0 for nonce in range(max_nonce): header = header76 + struct.pack("<I", nonce) digest = double_sha256(header) hashes += 1 if int.from_bytes(digest, "big") < target: elapsed = time.time() - start print(f"hashes={hashes}") print(f"elapsed={elapsed:.3f}s") print(f"hashrate={hashes / elapsed:.0f} H/s") return nonce, digest return None, None if __name__ == "__main__": version = 1 prev_hash = "00" * 32 merkle_root = "00" * 32 time_ = 0x5f3759d0 bits = 0x207fffff target = 1 << 240 max_nonce = 2_000_000 header76 = build_header76(version, prev_hash, merkle_root, time_, bits) nonce, digest = mine(header76, target, max_nonce) if nonce is not None: print(f"found nonce={nonce}") print(f"hash={digest[::-1].hex()}") else: print("not found, try larger max_nonce or higher target")

示例输出(你机器上的具体 nonce 和哈希率会不同):

hashes=73801 elapsed=0.419s hashrate=176136 H/s found nonce=33824 hash=0000b1e7f3d1a9c2e6f0a8d3c4b5a6e7f80123456789abcdef0123456789abcdef

3.3 运行结果和哈希率观察

这个实验至少能确认三件事:

  • 区块头构造正确时,nonce 扫描才可能成功。
  • target 越小越难找,调大target可以更快看到结果。
  • 纯 Python 的哈希率非常低,一般只有每秒几十 kH/s 到几百 kH/s,取决于 CPU 和 hash 库实现。

在 MacBook、普通 Linux PC 或树莓派上,结果会明显不同,但数量级基本都在万到百万 H/s 之间。如果你想观察不同 target 下找到 nonce 的耗时,可以循环测试多个 target:

for shift in [248, 244, 240, 236]: target = 1 << shift print(f"target=1<<{shift}") nonce, digest = mine(header76, target, max_nonce) if nonce is not None: print(f"nonce={nonce}")

这里的shift越大,target 越大,也就是难度越低,找到 nonce 的概率越高。

3.4 为什么纯 Python CPU 矿工只能用于教学

纯 Python 矿工不适合真实网络,原因不只是解释器慢。真实矿机使用 ASIC 芯片,在一个时钟周期内能计算大量 SHA-256 运算;Python 的hashlib虽然底层是 C 实现,但每轮都要解析头部、构造字节数组、调用哈希函数、转换整数,开销非常大。再加上 GIL 的限制,纯 Python 多线程也无法线性提升哈希率。

所以这个实验的定位是“协议学习”,不是“性能工程”。教学里最重要的结果是:你亲手看到了 nonce 扫描、哈希比较和目标值的概念,理解了矿机实际上是一台很会算 SHA-256 的单用途计算机。

4. 让 2013 年的 USB 矿机和 Jalapeño 工作:硬件通信思路与模拟器

4.1 旧矿机的通信链路

2013 年的 USB 矿机并不是把矿池

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询