Merge bitcoin/bitcoin#35980: contrib: reject divergent verify-commits history
What changed, and why it matters
This change fixes a bug in a Bitcoin Core developer tool called verify-commits.py, which checks whether a Git commit's history is properly signed before a developer trusts it. Previously, the tool could wrongly report success when Git encountered an error or when the commit came from a separate, untrusted branch that shared a root. The fix makes the tool explicitly prove the commit's relationship to trusted history before accepting it. This is a security-hardening fix for a supply-chain/verification tool, not a bug in the Bitcoin network protocol or wallet code itself.
Review and merge the fix; ensure developers and CI using verify-commits.py update to the patched version. No urgent network or node operator action is required because the change is isolated to a contributor verification script.
Security signals we found
Tool used in commit-trust verification chain
False success on Git error or divergent history
Responsible disclosure by external Red Team
Supply-chain verification hardening
No runtime/network/wallet impact
Evidence from the diff
verify-commits.py previously used git merge-base --is-ancestor and treated any non-zero exit code as ‘predates the trusted root’, which conflates three distinct cases: (1) the commit is genuinely older than the root, (2) the commit is on a divergent branch, and (3) Git failed. The patch introduces helper functions is_ancestor() and predates() that distinguish exit codes 0 (is ancestor), 1 (is not ancestor), and anything else (Git error), and also detect divergent history by checking ancestry in both directions. This prevents false-positive verification results on error or divergent commits.
Changed components
contrib/verify-commits/verify-commits.pyInspect captured patch +26 / −13
### contrib/verify-commits/verify-commits.py
@@ -14,6 +14,23 @@
GIT = os.getenv('GIT', 'git')
+def is_ancestor(older, newer, root_name):
+ """Return whether older is an ancestor of newer, rejecting Git errors."""
+ result = subprocess.run([GIT, "merge-base", "--is-ancestor", older, newer])
+ if result.returncode not in (0, 1):
+ print(f'Failed to determine ancestry between "{older}" and "{newer}" for the {root_name} (git merge-base exited with {result.returncode}).', file=sys.stderr)
+ sys.exit(1)
+ return result.returncode == 0
+
+def predates(commit, root, root_name):
+ """Return whether commit is provably older than root, rejecting divergent history."""
+ if is_ancestor(root, commit, root_name):
+ return False
+ elif is_ancestor(commit, root, root_name):
+ return True
+ print(f'"{commit}" diverges from the {root_name} "{root}", refusing to verify.', file=sys.stderr)
+ sys.exit(1)
+
def tree_sha512sum(commit='HEAD'):
"""Calculate the Tree-sha512 for the commit.
@@ -107,27 +124,23 @@ def main():
logging.debug("verify-commits: [in-progress] processing commit {}".format(current_commit[:8]))
if current_commit == verified_root:
+ # Ensure the trusted root identifies an existing commit.
+ is_ancestor(verified_root, current_commit, "trusted Git root")
print('There is a valid path from "{}" to {} where all commits are signed!'.format(initial_commit, verified_root))
sys.exit(0)
- else:
- # Make sure this commit isn't older than trusted roots
- check_root_older_res = subprocess.run([GIT, "merge-base", "--is-ancestor", verified_root, current_commit])
- if check_root_older_res.returncode != 0:
- print(f"\"{current_commit}\" predates the trusted root, stopping!")
- sys.exit(0)
+ elif predates(current_commit, verified_root, "trusted Git root"):
+ print(f"\"{current_commit}\" predates the trusted root, stopping!")
+ sys.exit(0)
if verify_tree:
if current_commit == verified_sha512_root:
print("All Tree-SHA512s matched up to {}".format(verified_sha512_root), file=sys.stderr)
verify_tree = False
no_sha1 = False
- else:
- # Skip the tree check if we are older than the trusted root
- check_root_older_res = subprocess.run([GIT, "merge-base", "--is-ancestor", verified_sha512_root, current_commit])
- if check_root_older_res.returncode != 0:
- print(f"\"{current_commit}\" predates the trusted SHA512 root, disabling tree verification.")
- verify_tree = False
- no_sha1 = False
+ elif predates(current_commit, verified_sha512_root, "trusted Tree-SHA512 root"):
+ print(f"\"{current_commit}\" predates the trusted SHA512 root, disabling tree verification.")
+ verify_tree = False
+ no_sha1 = False
os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_SHA1'] = "0" if no_sha1 else "1"Why this scored 48/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.