File size: 2,513 Bytes
63f41a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/env python
"""Print the hash that goes to the external time-stamp store for the SOTA pre-registration.

    .venv/bin/python scripts/p2/prereg_hash.py            # manifest + overall sha256
    .venv/bin/python scripts/p2/prereg_hash.py --json     # machine-readable
    .venv/bin/python scripts/p2/prereg_hash.py --check HEX  # exit 0 iff HEX is the current overall hash

The overall hash covers the registration text (docs/PREREG_SOTA.md) AND the analysis code that
implements it (paired statistics, the report script, the shared contract helpers, this script),
byte for byte, so the time-stamp commits to both what was promised and the code that will compute it.
Any edit to any of them changes the hash; the report prints the current hash next to the registered one.

Where the hash is stored is a user decision (docs/IMPLEMENTATION_LANES.md); this script only computes it.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import sys
from pathlib import Path

REPO = Path(__file__).resolve().parents[2]

#: Order is part of the registration.
FILES = (
    "docs/PREREG_SOTA.md",
    "align/eval/paired.py",
    "align/eval/proof_stats.py",
    "scripts/p2/eval_report.py",
    "scripts/p2/p2_common.py",
    "scripts/p2/prereg_hash.py",
)


def compute(repo: Path = REPO, files=FILES) -> dict:
    entries = []
    for rel in files:
        p = Path(repo) / rel
        if not p.is_file():
            raise FileNotFoundError(f"registration input missing: {rel}")
        entries.append({"path": rel, "sha256": hashlib.sha256(p.read_bytes()).hexdigest()})
    manifest = "".join(f"{e['sha256']}  {e['path']}\n" for e in entries)
    return {"files": entries, "manifest": manifest, "sha256": hashlib.sha256(manifest.encode()).hexdigest()}


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--check", metavar="HEX")
    a = ap.parse_args(argv)
    r = compute()
    if a.check is not None:
        ok = a.check.strip().lower() == r["sha256"]
        print("MATCH" if ok else f"MISMATCH (current {r['sha256']})")
        return 0 if ok else 1
    if a.json:
        print(json.dumps({"files": r["files"], "sha256": r["sha256"]}, indent=1))
    else:
        print(r["manifest"], end="")
        print(f"\nPREREG_OVERALL_SHA256: {r['sha256']}")
    return 0


if __name__ == "__main__":
    sys.exit(main())