#!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Recompute content commitment vs 00_Cover/HASH_NOTARIZATION_ANCHOR.txt (matches build-transaction-package-zip.sh).""" from __future__ import annotations import hashlib import os import re import sys EXCLUDED_EXACT = frozenset( { "./00_Cover/HASH_NOTARIZATION_ANCHOR.txt", "./00_Cover/audit_and_hashes.txt", "./00_Cover/audit_manifest.json", } ) EXCLUDED_BASENAMES = frozenset( { "TSA_RFC3161_REQUEST.tsq", "TSA_RFC3161_RESPONSE.tsr", "TSA_RFC3161_RESPONSE.txt", "TSA_RFC3161_VERIFY.txt", "QES_CMS_ANCHOR_DETACHED.p7s", "QES_CMS_VERIFY_LOG.txt", } ) def posix_rel(package_root: str, full_path: str) -> str: rel = os.path.relpath(full_path, package_root).replace(os.sep, "/") return rel if rel.startswith("./") else "./" + rel def excluded(rel_posix: str) -> bool: if rel_posix in EXCLUDED_EXACT: return True return os.path.basename(rel_posix) in EXCLUDED_BASENAMES def recompute(package_root: str) -> str: lines: list[str] = [] for dirpath, dirnames, filenames in os.walk(package_root): dirnames.sort() filenames.sort() for fn in filenames: if fn == ".DS_Store": continue full = os.path.join(dirpath, fn) if not os.path.isfile(full): continue rel = posix_rel(package_root, full) if excluded(rel): continue h = hashlib.sha256() with open(full, "rb") as f: for chunk in iter(lambda: f.read(1 << 20), b""): h.update(chunk) lines.append(f"{h.hexdigest().lower()}\t{rel}") lines.sort(key=lambda s: s.encode("utf-8")) return hashlib.sha256(("\n".join(lines) + "\n").encode("utf-8")).hexdigest().lower() def main() -> int: if len(sys.argv) != 2: print("Usage: verify-transaction-package-commitment.py ", file=sys.stderr) return 2 root = os.path.abspath(sys.argv[1]) anchor = os.path.join(root, "00_Cover", "HASH_NOTARIZATION_ANCHOR.txt") if not os.path.isfile(anchor): print(f"ERROR: missing {anchor}", file=sys.stderr) return 1 text = open(anchor, encoding="utf-8").read() m = re.search(r"CONTENT COMMITMENT \(SHA-256, hex\):\s*([0-9a-fA-F]{64})", text) if not m: print("ERROR: bad anchor", file=sys.stderr) return 1 exp = m.group(1).lower() got = recompute(root) if exp != got: print(f"MISMATCH anchor={exp}\n actual={got}", file=sys.stderr) return 1 print(f"OK: {got}") return 0 if __name__ == "__main__": sys.exit(main())