#!/usr/bin/env python3
"""Verify a Valaxia priority certificate (v1) offline. Python 3, no dependencies.

    python3 verify.py certificate.json [file] [--pubkey HEX]

Checks: (1) sha256(file) == digest; (2) sha256(canonical payload) ==
payload_sha256; (3) entry hash; (4) Ed25519 signature over the entry hash
(pure-Python reference implementation: slow, ~1 s, fine for verifying).
Compare --pubkey with the key published at <site>/registry/key; without it,
the key inside the certificate is used (then you trust the certificate's
own key — check it against the published one yourself).
Exit code 0 = all checks passed.
"""
import hashlib, json, sys, base64

# ---- Ed25519 (reference implementation, public domain; verify only) ----
b = 256
q = 2 ** 255 - 19
l = 2 ** 252 + 27742317777372353535851937790883648493
def H(m): return hashlib.sha512(m).digest()
def inv(x): return pow(x, q - 2, q)
d = -121665 * inv(121666) % q
I = pow(2, (q - 1) // 4, q)
def xrecover(y):
    xx = (y * y - 1) * inv(d * y * y + 1)
    x = pow(xx, (q + 3) // 8, q)
    if (x * x - xx) % q != 0: x = (x * I) % q
    if x % 2 != 0: x = q - x
    return x
By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % q, By % q]
def edwards(P, Q):
    x1, y1 = P; x2, y2 = Q
    x3 = (x1 * y2 + x2 * y1) * inv(1 + d * x1 * x2 * y1 * y2)
    y3 = (y1 * y2 + x1 * x2) * inv(1 - d * x1 * x2 * y1 * y2)
    return [x3 % q, y3 % q]
def scalarmult(P, e):
    Q = [0, 1]
    while e:
        if e & 1: Q = edwards(Q, P)
        P = edwards(P, P); e >>= 1
    return Q
def encodepoint(P):
    x, y = P
    bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
    return bytes(sum(bits[i * 8 + j] << j for j in range(8)) for i in range(b // 8))
def bit(h, i): return (h[i // 8] >> (i % 8)) & 1
def decodeint(s): return sum(2 ** i * bit(s, i) for i in range(b))
def isoncurve(P):
    x, y = P
    return (-x * x + y * y - 1 - d * x * x * y * y) % q == 0
def decodepoint(s):
    y = sum(2 ** i * bit(s, i) for i in range(b - 1))
    x = xrecover(y)
    if x & 1 != bit(s, b - 1): x = q - x
    P = [x, y]
    if not isoncurve(P): raise ValueError("point not on curve")
    return P
def ed25519_verify(sig, msg, pk):
    if len(sig) != 64 or len(pk) != 32: return False
    try:
        R = decodepoint(sig[:32]); A = decodepoint(pk)
    except ValueError:
        return False
    S = decodeint(sig[32:])
    if S >= l: return False
    h = int.from_bytes(H(encodepoint(R) + pk + msg), "little")
    return scalarmult(B, S) == edwards(R, scalarmult(A, h))

# ---- certificate checks ----
def canonical(obj):
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")

def sha256_file(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()

def main(argv):
    args = [a for a in argv[1:] if not a.startswith("--")]
    pub = None
    for i, a in enumerate(argv):
        if a == "--pubkey" and i + 1 < len(argv): pub = argv[i + 1].strip().lower()
    if not args:
        print(__doc__); return 2
    cert = json.load(open(args[0], encoding="utf-8"))
    e, ok = cert["entry"], True
    def report(flag, text):
        nonlocal ok; ok = ok and flag
        print(("PASS " if flag else "FAIL ") + text)
    if len(args) > 1:
        report(sha256_file(args[1]) == cert["subject"]["digest"], "file sha256 matches the certificate digest")
    else:
        print("SKIP file check (no file given); digest in certificate: " + cert["subject"]["digest"])
    report(hashlib.sha256(canonical(e["payload"])).hexdigest() == e["payload_sha256"], "payload sha256 matches")
    exp = hashlib.sha256((e["prev_hash"] + "\n" + e["payload_sha256"] + "\n" + e["recorded_at"]).encode("ascii")).hexdigest()
    report(exp == e["entry_hash"], "entry hash matches")
    key = pub or cert["signer"]["public_key_ed25519_hex"].lower()
    if pub and pub != cert["signer"]["public_key_ed25519_hex"].lower():
        report(False, "certificate key differs from --pubkey")
    report(ed25519_verify(base64.b64decode(e["signature"]), e["entry_hash"].encode("ascii"), bytes.fromhex(key)),
           "Ed25519 signature verifies with key " + key[:16] + "…" + (" (from --pubkey)" if pub else " (from certificate; compare with the published key)"))
    print("recorded_at (UTC): " + e["recorded_at"] + "  entry #" + str(e["seq"]))
    a = cert.get("anchor", {})
    print("Bitcoin anchor (OpenTimestamps): " + str(a.get("opentimestamps")) + (" block " + str(a["bitcoin_block_height"]) if a.get("bitcoin_block_height") else "") + "  (verify the .ots with the ots client for independent proof)")
    print("RESULT: " + ("OK" if ok else "FAILED"))
    return 0 if ok else 1

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