#!/usr/bin/env python3 """Check that vCISO Lite's public transparency log was only ever appended to. Usage: python3 verify_consistency.py FIRST_SIZE [SECOND_SIZE] (with one size, the second is the latest published head) Fetches both published heads and the consistency proof between them from https://api.vcisolite.com/.well-known/transparency/, then verifies the proof with the RFC 9162 section 2.1.4.2 algorithm. Python 3 standard library only. """ import hashlib, json, sys, urllib.request BASE = "https://api.vcisolite.com/.well-known/transparency" import urllib.error class Refused(Exception): pass def fetch(path): req = urllib.request.Request(f"{BASE}/{path}", headers={"User-Agent": "transparency-verifier/1"}) try: with urllib.request.urlopen(req, timeout=30) as r: return json.load(r) except urllib.error.HTTPError as e: if e.code == 404: raise Refused(f"{path}: no head was published at that size. Heads exist only at the sizes the log " "published; use sizes you kept, or the current one from log.json.") if e.code == 422: raise Refused(f"{path}: this pair isn't served (its proof would reveal a leaf). " "A pair of EVEN sizes is always served.") raise def node(left, right): return hashlib.sha256(b"\x01" + left + right).digest() def verify_consistency(first_size, first_root, second_size, second_root, proof): """RFC 9162 2.1.4.2. Roots and proof elements are raw 32-byte values.""" if first_size == second_size: return not proof and first_root == second_root if not proof or first_size > second_size: return False if first_size & (first_size - 1) == 0: # exact power of two proof = [first_root] + proof fn, sn = first_size - 1, second_size - 1 while fn & 1: fn >>= 1; sn >>= 1 fr = sr = proof[0] for c in proof[1:]: if sn == 0: return False if fn & 1 or fn == sn: fr, sr = node(c, fr), node(c, sr) while fn and not fn & 1: fn >>= 1; sn >>= 1 else: sr = node(sr, c) fn >>= 1; sn >>= 1 return fr == first_root and sr == second_root and sn == 0 def main(): if len(sys.argv) == 2: # one size given: prove it against the latest head first, second = int(sys.argv[1]), fetch("log.json")["tree_size"] elif len(sys.argv) == 3: first, second = int(sys.argv[1]), int(sys.argv[2]) else: sys.exit("usage: verify_consistency.py FIRST_SIZE [SECOND_SIZE]") doc = fetch(f"consistency?first={first}&second={second}") # Take each size and root from one head document, never mixed. a, b = doc["first"], doc["second"] # Cross-check against the standalone head documents we would have kept. for h in (a, b): standalone = fetch(f"{h['tree_size']}.json") assert standalone["root_hash"] == h["root_hash"], "head differs from the one published at that size" proof = [bytes.fromhex(x) for x in doc["consistency_proof"]] ok = verify_consistency(a["tree_size"], bytes.fromhex(a["root_hash"]), b["tree_size"], bytes.fromhex(b["root_hash"]), proof) print(f"size {a['tree_size']} -> {b['tree_size']}: {'CONSISTENT (append-only)' if ok else 'NOT CONSISTENT'}") sys.exit(0 if ok else 1) if __name__ == "__main__": try: main() except Refused as e: sys.exit(str(e))