
用 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 CPU无5 万到 50 万 H/s纯 Python跑纯 Python 矿工和模拟矿池2013 年 USB 矿机如 Block ErupterUSB数百 MH/s接入真实硬件或先由模拟器代替Butterfly Labs JalapeñoUSB 或网口数千 MH/s 到 10 GH/s演示更高算力设备如何接入软件Android 手机 Termux无几千到几万 H/s纯 Python手机端纯 Python CPU 矿工本地模拟矿池回环网口无真实算力下发 job、验证 share、打印协议日志学习环境里设备算力不是重点重点是让每个参与方都能稳定运行。真实设备可以先放一边用模拟器把软件链路串通再考虑把硬件接进来。2. 先搞懂比特币挖矿最核心的数学区块哈希、难度目标和 nonce2.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_hash和merkle_root在区块头里以小端序存储所以从十六进制字符串构造字节数组时需要先bytes.fromhex(...)再[::-1]反转。version、time、bits、nonce都是无符号 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))例如创世区块的bits是0x1d00ffff用这个函数解析后会得到一个很大的 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(fhashes{hashes}) print(felapsed{elapsed:.3f}s) print(fhashrate{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(ffound nonce{nonce}) print(fhash{digest[::-1].hex()}) else: print(not found, try larger max_nonce or higher target)示例输出你机器上的具体 nonce 和哈希率会不同hashes73801 elapsed0.419s hashrate176136 H/s found nonce33824 hash0000b1e7f3d1a9c2e6f0a8d3c4b5a6e7f80123456789abcdef0123456789abcdef3.3 运行结果和哈希率观察这个实验至少能确认三件事区块头构造正确时nonce 扫描才可能成功。target 越小越难找调大target可以更快看到结果。纯 Python 的哈希率非常低一般只有每秒几十 kH/s 到几百 kH/s取决于 CPU 和 hash 库实现。在 MacBook、普通 Linux PC 或树莓派上结果会明显不同但数量级基本都在万到百万 H/s 之间。如果你想观察不同 target 下找到 nonce 的耗时可以循环测试多个 targetfor shift in [248, 244, 240, 236]: target 1 shift print(ftarget1{shift}) nonce, digest mine(header76, target, max_nonce) if nonce is not None: print(fnonce{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 矿机并不是把矿池