离线重算 Bitcoin Merkle 证明:交易 ID 是否对应给定区块头
链小知 · 进阶 · 核验 · 已验证只读操作
读取两个公开浏览器的状态、Merkle 分支和 80 字节区块头,再离线重算。已完成六次公开 GET 和有限样本检查;未验证完整交易、工作量或最佳链。
开始前准备
- 准备可读取公开 HTTP 接口的客户端,以及能对二进制字节做 SHA-256 的本地环境;不需要钱包、签名或 API 密钥。
- 只使用 Bitcoin 主网已确认交易。交易 ID、区块哈希和分支哈希为 64 位十六进制字符串,区块头为 160 位十六进制字符串。
- 此任务验证给定交易 ID、分支与给定头之间的哈希关系。区块头和交易 ID 来自公开服务,尚无独立可信链锚点。
1. 取得已确认交易的区块身份
分别读取 https://blockstream.info/api/tx/交易ID/status 和 https://mempool.space/api/tx/交易ID/status。保存原始响应、完整 URL 与读取时刻;confirmed 必须为 true,且 block_height、block_hash 存在。未确认、404、429、超时或无法解析时停止,不用其他交易补作成功结果。
本次公开样本 txid 为 e47158e195abcb960741df336d0c39d3b687ddb4edcad9454384dbe519ff2039。两个服务均返回高度 970949、区块哈希 000000000000000000016be5d6cd00660bcbba5b6a55ef74c55af7465d93d5e9。它是历史样本,不是用户转账或平台入账凭据。
检查结果:两个状态响应可复查,网络、交易 ID、确认状态及区块身份一致;未把一致解释成独立节点证明。
2. 保存分支与原始区块头
在各服务同一 /api 根路径下读取 /tx/交易ID/merkle-proof,以及 /block/区块哈希/header。前者应有 block_height、pos 和 merkle 数组,后者是 80 字节头的十六进制文本。分支 block_height 与该服务交易状态必须相同;不要把 HTML 错误页、JSON 包装或整个区块正文当作区块头。
样本两服务返回相同的 pos=1 和 13 个分支哈希。pos 从 0 开始,1 表示索引为 1 的叶子;它不是输出编号 vout、当前确认数,也不是区块里只有两笔交易。数组从叶子附近的兄弟节点往根排列,不要排序或逆序。
检查结果:每份分支均配对自己的交易状态与区块头;保留 pos、数组顺序和恰好 80 字节的头。
3. 先核对区块头的显示哈希
将头的 160 位十六进制解码为 80 个字节。对这些字节做 SHA-256,再对第一次返回的 32 个原始字节做第二次 SHA-256;把第二次的 32 字节反序后转为十六进制显示,与 status.block_hash 比较。不能哈希十六进制文字的 UTF-8,也不能把第一次哈希转成文字再做第二次。
按从 0 开始的字节偏移,头里的 Merkle root 是 [36,68),即第 36 至 67 字节;后续比较使用这 32 个内部顺序字节。头哈希相同仅说明这份头的哈希与服务声称的区块 ID 相符,不验证 nBits 难度目标、工作量、前序头链或最佳链。
检查结果:头长度、二次哈希和显示字节顺序分别检查;重算头哈希与状态区块 ID 相同。
4. 按索引逐层重算 Merkle root
令 node 为 txid 十六进制解码后的 32 字节反序,index 为 pos。依次把 merkle 中每个哈希解码成 32 字节并反序,得到 sibling。index 为偶数时对 node || sibling 做两次 SHA-256;为奇数时对 sibling || node 做两次 SHA-256。每层用所得原始 32 字节替换 node,并令 index=floor(index÷2)。这里 || 是字节拼接,不是文本连接。
分支处理完后 index 应为 0,node 应与区块头 [36,68) 字节相同。样本按此规则重算的显示根为 29bde428346234017345c4cbd7368acc0aeb100f4694e5b00f5aa60e56c8dd8e;这是最终 node 反序后的十六进制。使用 TXID,不用 WTXID;本文直接采用给定 TXID,没有从原始交易序列化重新计算它,也未做整棵树的重复叶检查。
检查结果:索引奇偶、兄弟节点顺序与内部字节顺序均正确;最终 index=0 且重算根等于头内根。
5. 用离线负例确认比较确实会失败
保留原文件,另做两份本地副本:一份改变 TXID 的一个十六进制字符,另一份改变第一个分支哈希的一个字符;保持每个哈希仍为 64 位十六进制。分别重复重算,应都与原区块头的根不同。只修改本地输入,不请求服务、不发布交易,也不把坏副本覆盖原始回执。
本次两个服务样本的正确分支均匹配;两个改动副本均不匹配。另用一份独立 Python 离线实现读取保存的公开响应,再次核对两份头哈希、根与最终索引。负例通过说明此样本检查能发现这些改动,不是穷尽攻击场景或证明全部块有效。
检查结果:正确副本通过,两类被改动的副本失败;本地负例与原始公开响应分开保留。
6. 把包含关系与完整验证分开交付
最终记录两个服务各自的状态、分支、原始头、读取时刻、算法、计算根及负例结果。本次六次公开 GET 均返回 HTTP 200,头哈希及分支重算一致。可以说明“给定 TXID 与给定头的 Merkle root 匹配”,不能仅凭服务提供的同一套数据确认它属于独立验证的最佳链。
未取得完整原始交易、完整区块和独立头链,未验证脚本签名、共识规则、累计工作、重组、地址控制或支付用途。两个服务也未证明基础设施独立。交易所是否记账仍需平台自身证据;本教程未连接钱包、转账、广播或进行平台到账测试。
检查结果:报告给定哈希包含关系与实际检查结果,并保留完整交易、链验证、钱包及平台入账的未验证范围。
怎样确认完成
- 两份状态、分支和原始 80 字节头均保存,头显示哈希可重算。
- 给定 TXID 的分支根匹配给定头,改动叶子及分支的本地负例失败。
- 输出明确限定为公开历史样本的哈希关系;完整交易、链、钱包和平台到账未验证。
常见错误与边界
- 把 UTF-8 十六进制文字当原始字节,或每层多做、少做一次哈希。
- TXID 与 WTXID 混用、pos 当作从 1 开始、分支数组被排序或兄弟拼接顺序相反。
- 把服务给出的头哈希和根自洽,当成独立最佳链或完整共识验证。
- 只验一条分支却宣称整棵树无重复叶问题、完整交易有效或平台已经到账。