Bitcoin Core 提交签名验证:verify-commits 工具的原理、配置与安全使用指南

发布时间:2026/9/7 23:53:12
Bitcoin Core 提交签名验证:verify-commits 工具的原理、配置与安全使用指南 Bitcoin Core 提交签名验证verify-commits 工具的原理、配置与安全使用指南【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoinBitcoin Core 的contrib/verify-commits/目录提供了一套用于验证 Git 提交是否由可信维护者 PGP 签名的工具链其核心是 Python 脚本 verify-commits.py。本文基于 contrib/verify-commits/README.md 的完整说明结合仓库中的实际脚本、数据文件与 CI 集成代码讲解如何安全地验证origin/master的整条签名链、各配置文件信任根、受信密钥、异常豁免列表的格式与作用以及密钥过期/吊销场景下的处理机制。读完本文你将能够独立运行该工具、理解它检查的三类问题签名有效性、Tree-SHA512 一致性、合并整洁性并知道在 CI 中如何低成本的做签名健全性检查。为什么要验证提交的 PGP 签名从 Git 历史看维护者通过签名合并提交merge commit来表明我确认这段内容进入了主线。但签名本身只有配合一份哪些密钥是可信的清单才有意义而且还需要回答两个问题信任从哪开始仓库历史很长不可能要求所有历史提交都有签名因此需要一个信任根root of trust只验证该提交之后的历史历史文件内容如何防伪仅仅验证提交对象被签名还不够攻击者理论上可以构造内容不同的提交。Bitcoin Core 的做法是让合并提交的提交信息里携带一行Tree-SHA512: hash对提交对应的完整文件树做 SHA-512 摘要承诺验证工具会重新计算并与提交信息比对。verify-commits.py 就是围绕这两点实现的它沿第一父提交first parent链从目标提交一路回溯到信任根逐提交检查签名、树摘要和合并整洁性。安全使用 verify-commits.py不能先 checkout 再验证这是 README 中最强调的一点你不能用一个不可信的脚本来验证它自己。如果你先git checkout再运行刚 checkout 下来的verify-commits.py那么你所运行的脚本本身可能已经被投毒backdoor验证结果毫无意义。正确顺序是先在你本地保留一份可信版本的 verify-commits用它去验证远端分支确认整条历史都被可信密钥签名之后再进行 checkoutgit fetch origin \ ./contrib/verify-commits/verify-commits.py origin/master \ git checkout origin/master三步分别对应拉取远端引用不切换工作区→ 用当前可信的脚本验证origin/master→ 验证通过后再检出。README 同时坦诚说明这一流程的 UI/UX 目前还不够好欢迎通过 pull request 改进降低出错概率。需要注意的一个前提除非指定--clean-merge 0脚本会尝试验证每个合并提交都能干净地重新合并这要求git 版本不低于 2.38.0git merge-tree --write-tree是 2.38 引入的行为。数据配置文件信任根、受信密钥与豁免列表脚本启动时从自身所在目录读取一批数据文件见 verify-commits.py 第 100-108 行这是整个机制的配置面。trusted-git-rootPGP 信任根文件 trusted-git-root 应包含单个 Git 提交哈希它是第一个未签名的提交即信任链的起点。当前仓库中该文件内容为88a7294356e75bbaa136c9427c64e239f7c6fd40验证循环的终止条件就是走到这个提交verify-commits.py 第 126-133 行如果当前提交等于信任根输出 There is a valid path from ... to ... where all commits are signed! 并成功退出如果当前提交可证明早于信任根predates判断也视为合法停止点。注意predates()会拒绝与信任根历史分叉的提交——即既不是其祖先、也不是其后代的提交会直接报错退出防止验证一条看起来对但实际分叉的历史。trusted-sha512-root-commitTree-SHA512 信任根文件 trusted-sha512-root-commit 包含第一个没有 SHA512 根承诺的提交哈希当前为309bf16257b2395ce502017be627186b749ee749。回溯到该提交时Tree-SHA512 检查关闭All Tree-SHA512s matched up to ...因为该提交之前的历史没有树摘要承诺可验。trusted-keys受信签名者指纹清单文件 trusted-keys 是换行分隔的PGP 主密钥指纹primary key不是子密钥列表。当前仓库包含 5 个受信指纹。脚本解析git verify-commit --raw输出中的[GNUPG:] VALIDSIG记录取其中密钥指纹与该清单比对verify-commits.py 第 151-158 行而[GNUPG:] REVKEYSIG已吊销和[GNUPG:] EXPKEYSIG已过期记录默认会导致验证失败除非该提交出现在allow-revsig-commits中。三个豁免列表文件allow-revsig-commits换行分隔的提交哈希清单用于放行那些被已过期/已吊销密钥签名的历史提交下一节详述。当前仓库中该文件为空。allow-unclean-merge-commits放行无法干净重放的合并提交例如合并后有人为修改解决冲突的场景。当前为空。allow-incorrect-sha512-commits放行 Tree-SHA512 不匹配的提交。当前为空。README 只列出了前两个文件从 verify-commits.py 的实际读取逻辑可以看到完整的五个数据文件。验证循环脚本到底检查了什么verify-commits.py的主循环从目标提交默认HEAD可通过位置参数指定如origin/master开始每轮取该提交的第一父提交继续回溯直到到达信任根。对每个提交依次执行以下检查签名检查调用git -c gpg.program目录/gpg.sh verify-commit --raw commit通过本目录自带的 gpg.sh 包装 GPG 调用详见下文禁用 SHA1。要求出现VALIDSIG且指纹在trusted-keys中。Tree-SHA512 检查可关闭见 CLI 参数tree_sha512sum() 用git ls-tree -r递归列出整棵树的全部 blob再以git cat-file --batch批量流式读取所有文件内容对每个文件先算 SHA-512再按摘要 文件名\n的形式折叠进总摘要README 注明该算法取自 bitcoin-maintainer-tools 的 github-merge.py。然后把Tree-SHA512: hash与提交信息逐行比对不匹配即报错。拒绝章鱼合并octopus merge任何超过 2 个父提交的合并直接失败。干净合并检查对双父提交用git merge-tree --write-tree parent1 parent2重放合并比对重建出的树是否与合并提交实际树一致不一致则打印 diff 并失败。该检查受--clean-merge天数限制控制且旧版本 git 报 128 退出码时提示需要 git 2.38。gpg.sh为什么额外禁用 SHA1 签名脚本没有直接调用系统 gpg而是通过-c gpg.program.../gpg.sh注入 gpg.sh。其核心逻辑始终使用--trust-model always信任判定交给脚本自己用指纹清单完成当环境变量BITCOIN_VERIFY_COMMITS_ALLOW_SHA1 ! 1默认路径时追加--weak-digest sha1拒绝使用 SHA1 的签名包括自签名对 gpg 1.4.1/2.0.x 等过旧版本直接报错要求升级到 2.1.10 以上。脚本注释解释了动机签名对象是提交对象而提交内容部分由不受信任的输入pull request决定理论上攻击者可以构造一个自己预先做出 SHA1 碰撞的提交对象因此禁用 SHA1 是一种双保险措施。另外回溯到信任根没有 SHA1 承诺要求的旧历史之前脚本会把BITCOIN_VERIFY_COMMITS_ALLOW_SHA1设为 1 放宽此限制见no_sha1变量verify-commits.py 第 116、146 行。命令行参数verify-commits.py的完整用法为verify-commits.py [options] [commit id]argparse 定义参数默认值作用[commit id]HEAD验证回溯的终点提交如origin/master。提交引用不能包含空格--clean-merge NUMBERinf无限即全部检查只对最近 N 天内的提交检查干净合并。传0则完全跳过该检查此时不再强依赖 git 2.38--disable-tree-check未指定默认开启关闭 Tree-SHA512 检查注意它是actionstore_false默认状态即为检查开启导入受信 PGP 密钥验证前提是本机 GPG 密钥环里能拿到受信签名者的公钥。README 给出的命令是gpg --keyserver hkps://keys.openpgp.org --recv-keys $(contrib/verify-commits/trusted-keys)它把 trusted-keys 中的指纹列表展开为--recv-keys的参数逐个从密钥服务器导入。CI 环境的等价写法见下文。在 CI 中做签名健全性检查Bitcoin Core 的 CI 并不在每次构建时做全量历史验证而是用了一个低成本策略实现在 ci/lint/06_script.sh 第 27-38 行由LINT_CI_SANITY_CHECK_COMMIT_SIG1触发# 仅对最后 10 个提交做健全性检查及时发现缺签名、缺密钥或密钥过期 git log HEAD~10 -1 --format%H ./contrib/verify-commits/trusted-sha512-root-commit git log HEAD~10 -1 --format%H ./contrib/verify-commits/trusted-git-root mapfile -t KEYS contrib/verify-commits/trusted-keys git config user.email cici.ci git config user.name ci gpg --keyserver hkps://keys.openpgp.org --recv-keys ${KEYS[]} \ ./contrib/verify-commits/verify-commits.py思路是把两个信任根临时改写为HEAD~10于是验证范围收缩为最近 10 个提交再导入受信密钥并运行脚本。注释说明master 分支每次 push 通常只有一个新合并提交发布分支也只有少量提交所以抽查最近 10 个提交就足以低成本地暴露缺签名、缺密钥、密钥过期三类问题。GPG 依赖的安装则见 ci/lint/01_install.sh。密钥过期或吊销怎么办这是 README 专门用一节讨论的运维问题当某个曾为旧提交签名过的密钥或子密钥过期/被吊销后verify-commits会对所有用该密钥签过的历史提交开始报错。此时不需要把信任根trusted-git-root向后推进那样会放弃对更早历史的验证正确做法是找出所有被该密钥签名、且因过期/吊销而校验失败的提交哈希将它们逐行加入 allow-revsig-commits。这样历史提交的签名仍被验证确认REVKEYSIG/EXPKEYSIG对应的确实是当初那个密钥签的但新提交无法再用已过期/吊销的密钥通过验证——因为allow_revsig current_commit in revsig_allowed只对该清单中的提交生效verify-commits.py 第 147、156-157 行。README 还给出一种构建清单的土办法编辑verify-commits.py让每个提交分别用BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG置 1 和置 0 各测一遍把只有在置 1 时才通过的提交打印出来。需要说明的是当前仓库版本的脚本中该环境变量已不在源码中出现改为读取allow-revsig-commits数据文件README 描述的是一种针对旧版脚本的手工调试手段属于 README 自述的work in progress的一部分。已知局限与注意事项README 自身声明这是一个未完成的工作incomplete work in progress。README 提到的pre-push-hook.sh维护者推送前自查签名钩子在当前仓库的contrib/verify-commits/目录中并不存在实际目录内文件为README.md、verify-commits.py、gpg.sh、trusted-git-root、trusted-sha512-root-commit、trusted-keys及三个allow-*豁免清单。版本前提干净合并检查需要 git ≥ 2.38.0gpg 建议 ≥ 2.1.10旧版本会被 gpg.sh 明确拒绝。调试日志设置环境变量CItrue可开启 debug 级日志脚本在处理长历史时用它输出进度防止 CI 超时verify-commits.py 第 89-91、124 行。信任模型验证结果的可信度上限是你手中那份verify-commits.py与数据文件的可信度因此先用可信版本验证、再 checkout的顺序是整个安全模型的基石不可颠倒。小结contrib/verify-commits/展示了 Bitcoin Core 如何把签名信任链工程化用trusted-git-root划定验证起点用trusted-sha512-root-commit与提交信息中的Tree-SHA512行绑定文件树内容用trusted-keys收敛有效签名者用三个allow-*清单优雅地处理过期密钥、非干净合并与摘要异常等历史包袱再配合 CI 中只抽查最近 10 个提交的低成本健全性检查使签名验证既能全量兜底、又能日常化运行。理解这套机制后你可以把同样的信任根 受信密钥 内容承诺思路迁移到其他需要防历史篡改的 Git 项目中。【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考