Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2020-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Script for verifying Bitcoin Core release binaries.7This script attempts to download the sum file SHA256SUMS and corresponding8signature file SHA256SUMS.asc from bitcoincore.org and bitcoin.org and9compares them.11The sum-signature file is signed by a number of builder keys. This script12ensures that there is a minimum threshold of signatures from pubkeys that13we trust. This trust is articulated on the basis of configuration options14here, but by default is based upon local GPG trust settings.16The builder keys are available in the guix.sigs repo:18https://github.com/bitcoin-core/guix.sigs/tree/main/builder-keys20If a minimum good, trusted signature threshold is met on the sum file, we then21download the files specified in SHA256SUMS, and check if the hashes of these22files match those that are specified. The script returns 0 if everything passes23the checks. It returns 1 if either the signature check or the hash check24doesn't pass. If an error occurs the return value is >= 2.26Logging output goes to stderr and final binary verification data goes to stdout.28JSON output can by obtained by setting env BINVERIFY_JSON=1.29"""30import argparse31import difflib32import json33import logging34import os35import subprocess36import typing as t37import re38import sys39import shutil40import tempfile41import textwrap42import enum43from hashlib import sha25644from pathlib import PurePath, Path46# The primary host; this will fail if we can't retrieve files from here.47HOST1 = "https://bitcoincore.org"48HOST2 = "https://bitcoin.org"49VERSIONPREFIX = "bitcoin-core-"50SUMS_FILENAME = 'SHA256SUMS'51SIGNATUREFILENAME = f"{SUMS_FILENAME}.asc"54class ReturnCode(enum.IntEnum):55SUCCESS = 056INTEGRITY_FAILURE = 157FILE_GET_FAILED = 458FILE_MISSING_FROM_ONE_HOST = 559FILES_NOT_EQUAL = 660NO_BINARIES_MATCH = 761NOT_ENOUGH_GOOD_SIGS = 962BINARY_DOWNLOAD_FAILED = 1063BAD_VERSION = 1166def set_up_logger(is_verbose: bool = True) -> logging.Logger:67"""Set up a logger that writes to stderr."""68log = logging.getLogger(__name__)69log.setLevel(logging.INFO if is_verbose else logging.WARNING)70console = logging.StreamHandler(sys.stderr) # log to stderr71console.setLevel(logging.DEBUG)72formatter = logging.Formatter('[%(levelname)s] %(message)s')73console.setFormatter(formatter)74log.addHandler(console)75return log78log = set_up_logger()81def indent(output: str) -> str:82return textwrap.indent(output, ' ')85def bool_from_env(key, default=False) -> bool:86if key not in os.environ:87return default88raw = os.environ[key]90if raw.lower() in ('1', 'true'):91return True92elif raw.lower() in ('0', 'false'):93return False94raise ValueError(f"Unrecognized environment value {key}={raw!r}")97VERSION_FORMAT = "<major>.<minor>[.<patch>][-rc[0-9]][-platform]"98VERSION_EXAMPLE = "22.0 or 23.1-rc1-darwin.dmg or 27.0-x86_64-linux-gnu"100def parse_version_string(version_str):101# "<version>[-rcN][-platform]"102version_base, _, platform = version_str.partition('-')103rc = ""104if platform.startswith("rc"): # "<version>-rcN[-platform]"105rc, _, platform = platform.partition('-')106# else "<version>" or "<version>-platform"108return version_base, rc, platform111def download_with_wget(remote_file, local_file):112result = subprocess.run(['wget', '-O', local_file, remote_file],113stderr=subprocess.STDOUT, stdout=subprocess.PIPE)114return result.returncode == 0, result.stdout.decode().rstrip()117def verify_with_gpg(118filename,119signature_filename,120output_filename: t.Optional[str] = None121) -> tuple[int, str]:122with tempfile.NamedTemporaryFile() as status_file:123args = [124'gpg', '--yes', '--verify', '--verify-options', 'show-primary-uid-only', "--status-file", status_file.name,125'--output', output_filename if output_filename else '', signature_filename, filename]127env = dict(os.environ, LANGUAGE='en')128result = subprocess.run(args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env)130gpg_data = status_file.read().decode().rstrip()132log.debug(f'Result from GPG ({result.returncode}): {result.stdout.decode()}')133log.debug(f"{gpg_data}")134return result.returncode, gpg_data137class SigData:138"""GPG signature data as parsed from GPG stdout."""139def __init__(self):140self.key = None141self.name = ""142self.trusted = False143self.status = ""145def __bool__(self):146return self.key is not None148def __repr__(self):149return (150"SigData(%r, %r, trusted=%s, status=%r)" %151(self.key, self.name, self.trusted, self.status))154def parse_gpg_result(155output: list[str]156) -> tuple[list[SigData], list[SigData], list[SigData]]:157"""Returns good, unknown, and bad signatures from GPG stdout."""158good_sigs: list[SigData] = []159unknown_sigs: list[SigData] = []160bad_sigs: list[SigData] = []161total_resolved_sigs = 0163# Ensure that all lines we match on include a prefix that prevents malicious input164# from fooling the parser.165def line_begins_with(patt: str, line: str) -> t.Optional[re.Match]:166return re.match(r'^(\[GNUPG:\])\s+' + patt, line)168curr_sigs = unknown_sigs169curr_sigdata = SigData()171for line in output:172if line_begins_with(r"NEWSIG(?:\s|$)", line):173total_resolved_sigs += 1174if curr_sigdata:175curr_sigs.append(curr_sigdata)176curr_sigdata = SigData()177newsig_split = line.split()178if len(newsig_split) == 3:179curr_sigdata.name = newsig_split[2]181elif line_begins_with(r"GOODSIG(?:\s|$)", line):182curr_sigdata.key, curr_sigdata.name = line.split(maxsplit=3)[2:4]183curr_sigs = good_sigs185elif line_begins_with(r"EXPKEYSIG(?:\s|$)", line):186curr_sigdata.key, curr_sigdata.name = line.split(maxsplit=3)[2:4]187curr_sigs = good_sigs188curr_sigdata.status = "expired"190elif line_begins_with(r"REVKEYSIG(?:\s|$)", line):191curr_sigdata.key, curr_sigdata.name = line.split(maxsplit=3)[2:4]192curr_sigs = good_sigs193curr_sigdata.status = "revoked"195elif line_begins_with(r"BADSIG(?:\s|$)", line):196curr_sigdata.key, curr_sigdata.name = line.split(maxsplit=3)[2:4]197curr_sigs = bad_sigs199elif line_begins_with(r"ERRSIG(?:\s|$)", line):200curr_sigdata.key, _, _, _, _, _ = line.split()[2:8]201curr_sigs = unknown_sigs203elif line_begins_with(r"TRUST_(UNDEFINED|NEVER)(?:\s|$)", line):204curr_sigdata.trusted = False206elif line_begins_with(r"TRUST_(MARGINAL|FULLY|ULTIMATE)(?:\s|$)", line):207curr_sigdata.trusted = True209# The last one won't have been added, so add it now210assert curr_sigdata211curr_sigs.append(curr_sigdata)213all_found = len(good_sigs + bad_sigs + unknown_sigs)214if all_found != total_resolved_sigs:215raise RuntimeError(216f"failed to evaluate all signatures: found {all_found} "217f"but expected {total_resolved_sigs}")219return (good_sigs, unknown_sigs, bad_sigs)222def files_are_equal(filename1, filename2):223with open(filename1, 'rb') as file1:224contents1 = file1.read()225with open(filename2, 'rb') as file2:226contents2 = file2.read()227eq = contents1 == contents2229if not eq:230with open(filename1, 'r') as f1, \231open(filename2, 'r') as f2:232f1lines = f1.readlines()233f2lines = f2.readlines()235diff = indent(236''.join(difflib.unified_diff(f1lines, f2lines)))237log.warning(f"found diff in files ({filename1}, {filename2}):\n{diff}\n")239return eq242def get_files_from_hosts_and_compare(243hosts: list[str], path: str, filename: str, require_all: bool = False244) -> ReturnCode:245"""246Retrieve the same file from a number of hosts and ensure they have the same contents.247The first host given will be treated as the "primary" host, and is required to succeed.249Args:250filename: for writing the file locally.
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.