79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""demo_ids.py -- real-data demo: 128-bit human-transcribable IDs with check symbols.
|
|
|
|
Takes a real file's SHA-256, folds it to a 128-bit integer, and emits a hyphenated
|
|
Crockford Base32 ID with a check symbol (transcription-safe: O/0 and I/L/1 confusions
|
|
are folded, hyphens ignored, one bad symbol caught). Verifies and decodes it back,
|
|
then proves the check symbol catches every single-symbol typo in the printed ID.
|
|
|
|
Run: python3 demo_ids.py [FILE ...]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
if HERE not in sys.path:
|
|
sys.path.insert(0, HERE)
|
|
|
|
import base32c # noqa: E402
|
|
|
|
DEFAULT_TARGETS = ["/usr/share/common-licenses/GPL-3", os.path.join(HERE, "base32c.py")]
|
|
|
|
|
|
def make_id(path: str) -> dict:
|
|
digest = hashlib.sha256(open(path, "rb").read()).digest()
|
|
value = int.from_bytes(digest[:16], "big") # 128-bit id space
|
|
ident = base32c.encode_checked(value, min_length=26, hyphenate=4)
|
|
decoded = base32c.decode_checked(ident)
|
|
typos = tested = caught = 0
|
|
body = ident.replace("-", "")
|
|
for pos in range(len(body)):
|
|
alternatives = base32c.CHECK_ALPHABET if pos == len(body) - 1 else base32c.ALPHABET
|
|
for alt in alternatives:
|
|
if alt == body[pos]:
|
|
continue
|
|
mutated = body[:pos] + alt + body[pos + 1 :]
|
|
tested += 1
|
|
if not base32c.verify(mutated):
|
|
caught += 1
|
|
return {
|
|
"path": path,
|
|
"sha256": digest.hex(),
|
|
"value": value,
|
|
"id": ident,
|
|
"roundtrip_ok": decoded == value,
|
|
"typos_tested": tested,
|
|
"typos_caught": caught,
|
|
}
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
targets = argv or DEFAULT_TARGETS
|
|
all_ok = True
|
|
for path in targets:
|
|
if not os.path.isfile(path):
|
|
print(f"SKIP {path}: not a file")
|
|
continue
|
|
row = make_id(path)
|
|
print(f"file {row['path']}")
|
|
print(f"sha256 {row['sha256']}")
|
|
print(f"id value {row['value']}")
|
|
print(f"public id {row['id']}")
|
|
print(f"round-trip {'OK' if row['roundtrip_ok'] else 'BROKEN'} (decode_checked == value)")
|
|
print(
|
|
f"typo sweep {row['typos_caught']}/{row['typos_tested']} single-symbol typos rejected"
|
|
f" ({row['typos_caught'] / row['typos_tested']:.1%})"
|
|
)
|
|
print()
|
|
all_ok &= row["roundtrip_ok"] and row["typos_caught"] == row["typos_tested"]
|
|
print("DEMO OK" if all_ok else "DEMO FAILED")
|
|
return 0 if all_ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main(sys.argv[1:]))
|