Skip to content
Bitcoin Roots

v30.3-roots.1 · Commit 7: [PATCH 07/68] build: adapt Roots maintenance and assurance to Core 30.3

ci/test/test_sign_manifest.py

Before: /dev/null · After: ci/test/test_sign_manifest.py

File metadata
new file mode 100755
index 0000000000000000000000000000000000000000..d02b41f995d735bf31712015da58d21c16cf3de7

Download original patch · Release notes

Diff rows 1–75 of 75. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

ci/test/test_sign_manifest.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2026-present The Bitcoin Roots developers
3
+ # Distributed under the MIT software license, see the accompanying
4
+ # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
+
6
+ import os
7
+ from pathlib import Path
8
+ import shutil
9
+ import subprocess
10
+ import tempfile
11
+ import unittest
12
+
13
+
14
+ ROOT = Path(__file__).resolve().parents[2]
15
+ SCRIPT = ROOT / "ci/release/sign-manifest.sh"
16
+
17
+
18
+ @unittest.skipUnless(shutil.which("gpg"), "gpg is required")
19
+ class SignManifestTest(unittest.TestCase):
20
+ def test_unsigned_path_succeeds_without_signature(self):
21
+ with tempfile.TemporaryDirectory() as temporary_dir:
22
+ work = Path(temporary_dir)
23
+ manifest = work / "SHA512SUMS"
24
+ manifest.write_text("manifest\n", encoding="utf-8")
25
+ result = subprocess.run(
26
+ [SCRIPT, manifest, work / "missing.asc", "0" * 40],
27
+ env={key: value for key, value in os.environ.items() if key != "BITCOIN_ROOTS_GPG_SK"},
28
+ capture_output=True, text=True,
29
+ )
30
+ self.assertEqual(result.returncode, 0)
31
+ self.assertIn("publishing an unsigned manifest", result.stderr)
32
+ self.assertFalse((work / "SHA512SUMS.asc").exists())
33
+
34
+ def test_signs_and_verifies_with_matching_key(self):
35
+ with tempfile.TemporaryDirectory() as temporary_dir:
36
+ work = Path(temporary_dir)
37
+ key_home = work / "key-home"
38
+ key_home.mkdir(mode=0o700)
39
+ identity = "Roots Release Test <roots-release@example.invalid>"
40
+ subprocess.run(
41
+ ["gpg", "--batch", "--homedir", key_home, "--passphrase", "", "--quick-generate-key", identity, "ed25519", "sign", "0"],
42
+ check=True, capture_output=True,
43
+ )
44
+ fingerprint = subprocess.run(
45
+ ["gpg", "--batch", "--homedir", key_home, "--with-colons", "--list-keys", identity],
46
+ check=True, capture_output=True, text=True,
47
+ ).stdout.split("fpr:::::::::", 1)[1].split(":", 1)[0]
48
+ public_key = work / "public.asc"
49
+ public_key.write_bytes(subprocess.run(
50
+ ["gpg", "--batch", "--homedir", key_home, "--armor", "--export", fingerprint],
51
+ check=True, capture_output=True,
52
+ ).stdout)
53
+ secret_key = subprocess.run(
54
+ ["gpg", "--batch", "--homedir", key_home, "--armor", "--export-secret-keys", fingerprint],
55
+ check=True, capture_output=True, text=True,
56
+ ).stdout
57
+ manifest = work / "SHA512SUMS"
58
+ manifest.write_text("manifest\n", encoding="utf-8")
59
+
60
+ subprocess.run(
61
+ [SCRIPT, manifest, public_key, fingerprint],
62
+ env={**os.environ, "BITCOIN_ROOTS_GPG_SK": secret_key},
63
+ check=True, capture_output=True,
64
+ )
65
+ signature = work / "SHA512SUMS.asc"
66
+ self.assertTrue(signature.is_file())
67
+
68
+ verify_home = work / "verify-home"
69
+ verify_home.mkdir(mode=0o700)
70
+ subprocess.run(["gpg", "--batch", "--homedir", verify_home, "--import", public_key], check=True, capture_output=True)
71
+ subprocess.run(["gpg", "--batch", "--homedir", verify_home, "--verify", signature, manifest], check=True, capture_output=True)
72
+
73
+
74
+ if __name__ == "__main__":
75
+ unittest.main()