"""Check an Artizar sealed record offline, with no Artizar software and no account.

A record file holds the three API responses for one verification: the sealed
verdict (POST /v1/verifications), the committed record (GET .../<record_id>),
and the witnessed inclusion proof (GET .../<record_id>/proof). This script runs
the three checks on them with standard Ed25519, SHA-256, and RFC 6962 Merkle
hashing, against the public keys published at
https://artizar.ai/.well-known/artizar-keys.json

    pip install cryptography
    python verify_record.py <record.json> artizar-keys.json

It exits 0 only when every check passes.
"""
import hashlib, json, sys, unicodedata
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey


def canonical(obj):
    """Sorted keys, compact separators, NFC, UTF-8: the one canonical form Artizar signs."""
    text = json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
    return unicodedata.normalize("NFC", text).encode("utf-8")


def ed25519_ok(public_hex, signature_hex, message):
    try:
        Ed25519PublicKey.from_public_bytes(bytes.fromhex(public_hex)).verify(bytes.fromhex(signature_hex), message)
        return True
    except Exception:
        return False


def inclusion_root(leaf_hex, index, size, path):
    """RFC 6962 inclusion proof: recompute the tree root from one leaf and its audit path."""
    node = hashlib.sha256(b"\x00" + bytes.fromhex(leaf_hex)).digest()
    fn, sn = index, size - 1
    for sibling_hex in path:
        sibling = bytes.fromhex(sibling_hex)
        if fn & 1 or fn == sn:
            node = hashlib.sha256(b"\x01" + sibling + node).digest()
            while not fn & 1 and fn != 0:
                fn, sn = fn >> 1, sn >> 1
        else:
            node = hashlib.sha256(b"\x01" + node + sibling).digest()
        fn, sn = fn >> 1, sn >> 1
    return node.hex() if sn == 0 else None


def main(record_path, keys_path):
    item, keys = json.load(open(record_path)), json.load(open(keys_path))
    sealed, envelope, proof = item["sealed"], item["record"]["record"], item["proof"]

    # 1. The seal: Artizar's seal key signed exactly this verdict, record, and log position.
    unsigned = {k: v for k, v in sealed.items() if k != "seal_signature"}
    seal = ed25519_ok(keys["seal_public_key_hex"], sealed["seal_signature"], canonical(unsigned))

    # 2. The commitment: the published commitment is the hash of the full record you hold.
    digest = hashlib.sha256(canonical(envelope["record"])).hexdigest()
    commitment = digest == envelope["commitment"] == sealed["commitment"]

    # 3. The public log: the log key signed a tree head, a known witness cosigned it,
    #    and the record's entry is a leaf of that tree.
    sth, p = proof["checkpoint"]["sth"], proof["inclusion_proof"]
    head = ed25519_ok(keys["log_public_key_hex"], sth["signature"], canonical({k: v for k, v in sth.items() if k != "signature"}))
    witnessed = any(
        ed25519_ok(keys["witness_public_keys"].get(c["witness_id"], "00" * 32), c["signature"], canonical({
            "root_hash": sth["root_hash"], "tree_size": sth["tree_size"], "sth_key_id": sth["key_id"],
            "witness_id": c["witness_id"], "cosigned_at": c["cosigned_at"],
        }))
        for c in proof["checkpoint"]["cosignatures"]
    )
    included = (
        p["leaf"] == sealed["entry_hash"] == proof["entry"]["entry_hash"]
        and p["tree_size"] == sth["tree_size"] and p["root"] == sth["root_hash"]
        and inclusion_root(p["leaf"], p["leaf_index"], p["tree_size"], p["audit_path"]) == sth["root_hash"]
    )

    v = sealed["verdict"]
    print(f"record {sealed['record_id']}: {v['metric']} {v['period']}, claimed {v['claimed_value']} {v['claimed_unit']}, {v['outcome']}")
    for name, ok in [("seal", seal), ("commitment", commitment), ("tree head signed", head),
                     ("witness cosigned", witnessed), ("included in the log", included)]:
        print(f"  {'ok  ' if ok else 'FAIL'} {name}")
    return 0 if all([seal, commitment, head, witnessed, included]) else 1


if __name__ == "__main__":
    sys.exit(main(*sys.argv[1:3]))
