diff --git a/contrib/verify-binaries/README.md b/contrib/verify-binaries/README.md index 0f3e16a5bc9d3..c2b30d9e47dfb 100644 --- a/contrib/verify-binaries/README.md +++ b/contrib/verify-binaries/README.md @@ -2,15 +2,15 @@ #### Preparation -As of Bitcoin Core v22.0, releases are signed by a number of public keys on the basis -of the [guix.sigs repository](https://github.com/bitcoin-core/guix.sigs/). When +As of Bitcoin Knots v21.x, releases are signed by a number of public keys on the basis +of the [guix.sigs repository](https://github.com/bitcoinknots/guix.sigs/). When verifying binary downloads, you (the end user) decide which of these public keys you trust and then use that trust model to evaluate the signature on a file that contains hashes of the release binaries. The downloaded binaries are then hashed and compared to the signed checksum file. First, you have to figure out which public keys to recognize. Browse the [list of frequent -builder-keys](https://github.com/bitcoin-core/guix.sigs/tree/main/builder-keys) and +builder-keys](https://github.com/bitcoinknots/guix.sigs/tree/knots/builder-keys) and decide which of these keys you would like to trust. For each key you want to trust, you must obtain that key for your local GPG installation. @@ -23,7 +23,8 @@ You can obtain these keys by #### Usage This script attempts to download the checksum file (`SHA256SUMS`) and corresponding -signature file `SHA256SUMS.asc` from https://bitcoincore.org and https://bitcoin.org. +signature file `SHA256SUMS.asc` from https://bitcoinknots.org and +https://github.com/bitcoinknots/bitcoin/releases. It first checks if the checksum file is valid based upon a plurality of signatures, and then downloads the release files specified in the checksum file, and checks if the @@ -42,37 +43,45 @@ See the `Config` object for various options. Validate releases with default settings: ```sh -./contrib/verify-binaries/verify.py pub 22.0 -./contrib/verify-binaries/verify.py pub 22.0-rc3 +./contrib/verify-binaries/verify.py pub 29.1.knots20250903 +./contrib/verify-binaries/verify.py pub 27.1.knots20240801 ``` Get JSON output and don't prompt for user input (no auto key import): ```sh -./contrib/verify-binaries/verify.py --json pub 22.0-x86 -./contrib/verify-binaries/verify.py --json pub 23.0-rc5-linux-gnu +./contrib/verify-binaries/verify.py --json pub 29.1.knots20250903-x86 +./contrib/verify-binaries/verify.py --json pub 27.1.knots20240801-win64 +``` + +Require all hosts (bitcoinknots.org / github.com) to provide identical +checksums and signature files: + +```sh +./contrib/verify-binaries/verify.py --json pub --require-all-hosts 29.1.knots20250903-x86 +./contrib/verify-binaries/verify.py --json pub --require-all-hosts 27.1.knots20240801-win64 ``` Rely only on local GPG state and manually specified keys, while requiring a threshold of at least 10 trusted signatures: ```sh ./contrib/verify-binaries/verify.py \ - --trusted-keys 74E2DEF5D77260B98BC19438099BAD163C70FBFA,9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C \ - --min-good-sigs 10 pub 22.0-linux + --trusted-keys 1A3E761F19D2CC7785C5502EA291A2C45D0C504A,F4FC70F07310028424EFC20A8E4256593F177720 \ + --min-good-sigs 10 pub 29.1.knots20250903-linux ``` If you only want to download the binaries for a certain architecture and/or platform, add the corresponding suffix, e.g.: ```sh -./contrib/verify-binaries/verify.py pub 25.2-x86_64-linux -./contrib/verify-binaries/verify.py pub 24.1-rc1-darwin -./contrib/verify-binaries/verify.py pub 27.0-win64-setup.exe +./contrib/verify-binaries/verify.py pub 25.1.knots20231115-x86_64-linux +./contrib/verify-binaries/verify.py pub 23.0.knots20220529-darwin +./contrib/verify-binaries/verify.py pub 27.1.knots20240801-win64-setup.exe ``` If you do not want to keep the downloaded binaries, specify the cleanup option. ```sh -./contrib/verify-binaries/verify.py pub --cleanup 22.0 +./contrib/verify-binaries/verify.py pub --cleanup 29.1.knots20250903 ``` Use the bin subcommand to verify all files listed in a local checksum file @@ -85,6 +94,6 @@ Verify only a subset of the files listed in a local checksum file ```sh ./contrib/verify-binaries/verify.py bin ~/Downloads/SHA256SUMS \ - ~/Downloads/bitcoin-24.0.1-x86_64-linux-gnu.tar.gz \ - ~/Downloads/bitcoin-24.0.1-arm-linux-gnueabihf.tar.gz + ~/Downloads/bitcoin-23.0.knots20220529-x86_64-linux-gnu.tar.gz \ + ~/Downloads/bitcoin-23.0.knots20220529-arm-linux-gnueabihf.tar.gz ``` diff --git a/contrib/verify-binaries/test.py b/contrib/verify-binaries/test.py index 875606ec22678..d7ae6f5c79988 100755 --- a/contrib/verify-binaries/test.py +++ b/contrib/verify-binaries/test.py @@ -10,38 +10,38 @@ def main(): """Tests ordered roughly from faster to slower.""" expect_code(run_verify("", "pub", '0.32'), 4, "Nonexistent version should fail") expect_code(run_verify("", "pub", '0.32.awefa.12f9h'), 11, "Malformed version should fail") - expect_code(run_verify('--min-good-sigs 20', "pub", "22.0"), 9, "--min-good-sigs 20 should fail") + expect_code(run_verify('--min-good-sigs 20', "pub", "29.1.knots20250903"), 9, "--min-good-sigs 20 should fail") - print("- testing verification (22.0-x86_64-linux-gnu.tar.gz)", flush=True) - _220_x86_64_linux_gnu = run_verify("--json", "pub", "22.0-x86_64-linux-gnu.tar.gz") + print("- testing verification (29.1.knots20250903-x86_64-linux-gnu.tar.gz)", flush=True) + _291knots_x86_64_linux_gnu = run_verify("--json", "pub --require-all-hosts", "29.1.knots20250903-x86_64-linux-gnu.tar.gz") try: - result = json.loads(_220_x86_64_linux_gnu.stdout.decode()) + result = json.loads(_291knots_x86_64_linux_gnu.stdout.decode()) except Exception: - print("failed on 22.0-x86_64-linux-gnu.tar.gz --json:") - print_process_failure(_220_x86_64_linux_gnu) + print("failed on 29.1.knots20250903-x86_64-linux-gnu.tar.gz --json:") + print_process_failure(_291knots_x86_64_linux_gnu) raise - expect_code(_220_x86_64_linux_gnu, 0, "22.0-x86_64-linux-gnu.tar.gz should succeed") + expect_code(_291knots_x86_64_linux_gnu, 0, "29.1.knots20250903-x86_64-linux-gnu.tar.gz should succeed") v = result['verified_binaries'] assert result['good_trusted_sigs'] assert len(v) == 1 - assert v['bitcoin-22.0-x86_64-linux-gnu.tar.gz'] == '59ebd25dd82a51638b7a6bb914586201e67db67b919b2a1ff08925a7936d1b16' + assert v['bitcoin-29.1.knots20250903-x86_64-linux-gnu.tar.gz'] == '3752cf932309cd98734eb20ebb6c7aea4b8a10eb329b3d8d8fbd00098ea674fb' - print("- testing verification (22.0)", flush=True) - _220 = run_verify("--json", "pub", "22.0") + print("- testing verification (29.1.knots20250903)", flush=True) + _291knots = run_verify("--json", "pub", "29.1.knots20250903") try: - result = json.loads(_220.stdout.decode()) + result = json.loads(_291knots.stdout.decode()) except Exception: - print("failed on 22.0 --json:") - print_process_failure(_220) + print("failed on 29.1.knots20250903 --json:") + print_process_failure(_291knots) raise - expect_code(_220, 0, "22.0 should succeed") + expect_code(_291knots, 0, "29.1.knots20250903 should succeed") v = result['verified_binaries'] assert result['good_trusted_sigs'] - assert v['bitcoin-22.0-aarch64-linux-gnu.tar.gz'] == 'ac718fed08570a81b3587587872ad85a25173afa5f9fbbd0c03ba4d1714cfa3e' - assert v['bitcoin-22.0-osx64.tar.gz'] == '2744d199c3343b2d94faffdfb2c94d75a630ba27301a70e47b0ad30a7e0155e9' - assert v['bitcoin-22.0-x86_64-linux-gnu.tar.gz'] == '59ebd25dd82a51638b7a6bb914586201e67db67b919b2a1ff08925a7936d1b16' + assert v['bitcoin-29.1.knots20250903-aarch64-linux-gnu.tar.gz'] == '505fe70d71609abc42d1d91c23fb0b4ac0c94bcb6fe6674bb6b936a4708576e9' + assert v['bitcoin-29.1.knots20250903-x86_64-apple-darwin.tar.gz'] == 'fd7b56abc611a5311dde3157346524ae4f03131df4f72948e1c9ba4d10d6cc33' + assert v['bitcoin-29.1.knots20250903-x86_64-linux-gnu.tar.gz'] == '3752cf932309cd98734eb20ebb6c7aea4b8a10eb329b3d8d8fbd00098ea674fb' def run_verify(global_args: str, command: str, command_args: str) -> subprocess.CompletedProcess: diff --git a/contrib/verify-binaries/verify.py b/contrib/verify-binaries/verify.py index 6c07b36c9d8cd..ddd1b81143840 100755 --- a/contrib/verify-binaries/verify.py +++ b/contrib/verify-binaries/verify.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 # Copyright (c) 2020-2021 The Bitcoin Core developers +# Modified for Bitcoin Knots Support # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Script for verifying Bitcoin Core release binaries. +"""Script for verifying Bitcoin Knots release binaries. This script attempts to download the sum file SHA256SUMS and corresponding -signature file SHA256SUMS.asc from bitcoincore.org and bitcoin.org and -compares them. +signature file SHA256SUMS.asc from bitcoinknots.org and github.com and compares +them. The sum-signature file is signed by a number of builder keys. This script ensures that there is a minimum threshold of signatures from pubkeys that @@ -15,7 +16,7 @@ The builder keys are available in the guix.sigs repo: - https://github.com/bitcoin-core/guix.sigs/tree/main/builder-keys + https://github.com/bitcoinknots/guix.sigs/tree/knots/builder-keys If a minimum good, trusted signature threshold is met on the sum file, we then download the files specified in SHA256SUMS, and check if the hashes of these @@ -46,9 +47,9 @@ from pathlib import PurePath, Path # The primary host; this will fail if we can't retrieve files from here. -HOST1 = "https://bitcoincore.org" -HOST2 = "https://bitcoin.org" -VERSIONPREFIX = "bitcoin-core-" +HOST1 = "https://bitcoinknots.org" +HOST2 = "https://github.com/bitcoinknots/bitcoin/releases" +VERSIONPREFIX = "bitcoin-" SUMS_FILENAME = 'SHA256SUMS' SIGNATUREFILENAME = f"{SUMS_FILENAME}.asc" @@ -96,8 +97,8 @@ def bool_from_env(key, default=False) -> bool: raise ValueError(f"Unrecognized environment value {key}={raw!r}") -VERSION_FORMAT = ".[.][-rc[0-9]][-platform]" -VERSION_EXAMPLE = "22.0 or 23.1-rc1-darwin.dmg or 27.0-x86_64-linux-gnu" +VERSION_FORMAT = ".[.].knots[-rc[0-9]][-platform]" +VERSION_EXAMPLE = "29.1.knots20250903 or 29.1.knots20250903-arm64-apple-darwin or 27.1.knots20240801-x86_64-linux-gnu" def parse_version_string(version_str): # "[-rcN][-platform]" @@ -106,8 +107,9 @@ def parse_version_string(version_str): if platform.startswith("rc"): # "-rcN[-platform]" rc, _, platform = platform.partition('-') # else "" or "-platform" + version_base, _, version_date = version_base.partition('.knots') - return version_base, rc, platform + return version_base, version_date, rc, platform def download_with_wget(remote_file, local_file): @@ -259,7 +261,7 @@ def files_are_equal(filename1, filename2): def get_files_from_hosts_and_compare( - hosts: list[str], path: str, filename: str, require_all: bool = False + hosts: list[str], paths: list[str], filename: str, require_all: bool = False ) -> ReturnCode: """ Retrieve the same file from a number of hosts and ensure they have the same contents. @@ -271,12 +273,14 @@ def get_files_from_hosts_and_compare( assert len(hosts) > 1 primary_host = hosts[0] other_hosts = hosts[1:] + primary_path = paths[0] + other_paths = paths[1:] got_files = [] - def join_url(host: str) -> str: + def join_url(host: str, path: str) -> str: return host.rstrip('/') + '/' + path.lstrip('/') - url = join_url(primary_host) + url = join_url(primary_host, primary_path) success, output = download_with_wget(url, filename) if not success: log.error( @@ -290,8 +294,8 @@ def join_url(host: str) -> str: log.info(f"got file {url} as {filename}") got_files.append(filename) - for i, host in enumerate(other_hosts): - url = join_url(host) + for i, (host, path) in enumerate(zip(other_hosts, other_paths)): + url = join_url(host, path) fname = filename + f'.{i + 2}' success, output = download_with_wget(url, fname) @@ -462,7 +466,7 @@ def cleanup(): # determine remote dir dependent on provided version string try: - version_base, version_rc, os_filter = parse_version_string(args.version) + version_base, version_date, version_rc, os_filter = parse_version_string(args.version) version_tuple = [int(i) for i in version_base.split('.')] except Exception as e: log.debug(e) @@ -470,11 +474,14 @@ def cleanup(): log.error(f" e.g. {VERSION_EXAMPLE}") return ReturnCode.BAD_VERSION - remote_dir = f"/bin/{VERSIONPREFIX}{version_base}/" + major_version_dir = f"{version_tuple[0]}.x" + exact_version_dir=f"{version_base}.knots{version_date}" + host1_remote_dir = f"/files/{major_version_dir}/{exact_version_dir}/" + host2_remote_dir = f"download/v{exact_version_dir}/" if version_rc: remote_dir += f"test.{version_rc}/" - remote_sigs_path = remote_dir + SIGNATUREFILENAME - remote_sums_path = remote_dir + SUMS_FILENAME + remote_sigs_paths = [ host1_remote_dir + SIGNATUREFILENAME, host2_remote_dir + SIGNATUREFILENAME ] + remote_sums_paths = [ host1_remote_dir + SUMS_FILENAME, host2_remote_dir + SUMS_FILENAME ] # create working directory os.makedirs(WORKINGDIR, exist_ok=True) @@ -483,7 +490,7 @@ def cleanup(): hosts = [HOST1, HOST2] got_sig_status = get_files_from_hosts_and_compare( - hosts, remote_sigs_path, SIGNATUREFILENAME, args.require_all_hosts) + hosts, remote_sigs_paths, SIGNATUREFILENAME, args.require_all_hosts) if got_sig_status != ReturnCode.SUCCESS: return got_sig_status @@ -494,7 +501,7 @@ def cleanup(): return ReturnCode.BAD_VERSION got_sums_status = get_files_from_hosts_and_compare( - hosts, remote_sums_path, SUMS_FILENAME, args.require_all_hosts) + hosts, remote_sums_paths, SUMS_FILENAME, args.require_all_hosts) if got_sums_status != ReturnCode.SUCCESS: return got_sums_status @@ -513,22 +520,11 @@ def cleanup(): log.error(f"No files matched the platform specified. Did you mean: {closest_match}") return ReturnCode.NO_BINARIES_MATCH - # remove binaries that are known not to be hosted by bitcoincore.org - fragments_to_remove = ['-unsigned', '-debug', '-codesignatures'] - for fragment in fragments_to_remove: - nobinaries = [i for i in hashes_to_verify if fragment in i[1]] - if nobinaries: - remove_str = ', '.join(i[1] for i in nobinaries) - log.info( - f"removing *{fragment} binaries ({remove_str}) from verification " - f"since {HOST1} does not host *{fragment} binaries") - hashes_to_verify = [i for i in hashes_to_verify if fragment not in i[1]] - # download binaries for _, binary_filename in hashes_to_verify: log.info(f"downloading {binary_filename} to {WORKINGDIR}") success, output = download_with_wget( - HOST1 + remote_dir + binary_filename, binary_filename) + HOST1 + host1_remote_dir + binary_filename, binary_filename) if not success: log.error( @@ -685,8 +681,7 @@ def main(): '--require-all-hosts', action='store_true', default=bool_from_env('BINVERIFY_REQUIRE_ALL_HOSTS'), help=( - f'If set, require all hosts ({HOST1}, {HOST2}) to provide signatures. ' - '(Sometimes bitcoin.org lags behind bitcoincore.org.)') + f'If set, require all hosts ({HOST1}, {HOST2}) to provide signatures.') ) bin_parser = subparsers.add_parser("bin", help="Verify local binaries.")