#!/usr/bin/env python3 """Reference verifier for the AI:MND Individual Identity standard, v0.1 (aimnd.identity/1, aimnd.life/1, aimnd.public/1). Anyone can check an individual's identity from public information with this one file: Python 3.8+, standard library only, no AI:MND software needed. Specification: https://aimndlabs.com/standard python3 aimnd_verify.py identity.json # the document alone python3 aimnd_verify.py identity.json life.jsonl # plus its whole life record python3 aimnd_verify.py public.json life.jsonl --heads heads.txt # a published record, and every head the owner ever published # (one per line): catches a copy that split off, or a rewrite python3 aimnd_verify.py --registry-log log.jsonl [--registry-key KEY] [--checkpoint checkpoint.json] [--witness KEY ...] [--min-witnesses N] [--id aimnd:...] # a registry's public log: the chain, the registry's signature on every # entry, a checkpoint and the witnesses who co-signed it, and (with --id) # what the log says about one individual Exit status 0 when everything verifies, 1 otherwise. As a library: verify(document, life=None, published_heads=None), verify_registry_log(entries, registry_key=None), verify_checkpoint(checkpoint, entries=None, witness_keys=None, min_witnesses=0), find_fork(entries_a, entries_b), status_from_log(entries, id). MIT License. Copyright (c) 2026 AI:MND Labs. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import math import base64 import hashlib import json import sys SCHEMA = "aimnd.identity/1" PUBLIC_SCHEMA = "aimnd.public/1" def unb64(s): s = str(s) return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) def canon(obj): """The bytes that get signed: sorted keys, no whitespace, UTF-8.""" return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode() def sha(b): return hashlib.sha256(b).hexdigest() def individual_id(public_key_b64): """aimnd: + the first 26 characters of the lowercase, unpadded base32 SHA-256 of the individual's public key.""" return "aimnd:" + base64.b32encode(hashlib.sha256(unb64(public_key_b64)).digest()).decode().lower().rstrip("=")[:26] # ---------------------------------------------------------------------------------- Ed25519 verification (RFC 8032, section 5.1.7) _P = 2 ** 255 - 19 _Q = 2 ** 252 + 27742317777372353535851937790883648493 _D = -121665 * pow(121666, _P - 2, _P) % _P _SQRT_M1 = pow(2, (_P - 1) // 4, _P) def _inv(x): return pow(x, _P - 2, _P) def _add(a, b): A = (a[1] - a[0]) * (b[1] - b[0]) % _P B = (a[1] + a[0]) * (b[1] + b[0]) % _P C = 2 * a[3] * b[3] * _D % _P D = 2 * a[2] * b[2] % _P E, F, G, H = B - A, D - C, D + C, B + A return (E * F % _P, G * H % _P, F * G % _P, E * H % _P) def _mul(s, pt): out = (0, 1, 1, 0) while s > 0: if s & 1: out = _add(out, pt) pt = _add(pt, pt) s >>= 1 return out def _equal(a, b): return (a[0] * b[2] - b[0] * a[2]) % _P == 0 and (a[1] * b[2] - b[1] * a[2]) % _P == 0 def _recover_x(y, sign): if y >= _P: return None x2 = (y * y - 1) * _inv(_D * y * y + 1) if x2 == 0: return None if sign else 0 x = pow(x2, (_P + 3) // 8, _P) if (x * x - x2) % _P != 0: x = x * _SQRT_M1 % _P if (x * x - x2) % _P != 0: return None if (x & 1) != sign: x = _P - x return x def _decompress(s): if len(s) != 32: return None y = int.from_bytes(s, "little") sign = y >> 255 y &= (1 << 255) - 1 x = _recover_x(y, sign) return None if x is None else (x, y, 1, x * y % _P) _GY = 4 * _inv(5) % _P _GX = _recover_x(_GY, 0) _G = (_GX, _GY, 1, _GX * _GY % _P) _STRONG = {} def _strong_key(public): """A key must decode to a point of the prime-order subgroup: a key of small order (or with a small-order part) lets one signature verify for several messages. Honest keys are multiples of the base point and always pass.""" if public not in _STRONG: A = _decompress(public) _STRONG[public] = A is not None and _equal(_mul(_Q, A), (0, 1, 1, 0)) and not _equal(A, (0, 1, 1, 0)) return _STRONG[public] def _verify_pure(public, msg, sig): if len(public) != 32 or len(sig) != 64 or not _strong_key(public): return False A, R = _decompress(public), _decompress(sig[:32]) if A is None or R is None: return False s = int.from_bytes(sig[32:], "little") if s >= _Q: return False h = int.from_bytes(hashlib.sha512(sig[:32] + public + msg).digest(), "little") % _Q return _equal(_mul(s, _G), _add(R, _mul(h, A))) def verify_sig(public_key_b64, obj, signature): """True when `signature` is a valid Ed25519 signature by `public_key_b64` over canon(obj). Never raises on bad input.""" try: return _verify_pure(unb64(public_key_b64), canon(obj), unb64(signature)) except (ValueError, TypeError): return False def verify(document, life=None, published_heads=None): """-> {"ok": bool, "problems": [str]}. Checks the document's signature, that the id comes from the key, both birth signatures, every transfer against the owner key at that time, every life entry's hash chain and signature, that the document's head matches the record, and that every head the owner published appears in it.""" probs = [] d = document or {} try: if d.get("schema") != SCHEMA: probs.append("not an aimnd.identity/1 document") body = {k: v for k, v in d.items() if k != "signature"} if not verify_sig(d["public_key"], body, d.get("signature")): probs.append("the document's signature does not match its key") if individual_id(d["public_key"]) != d["id"]: probs.append("the id is not derived from the key") b = d["birth"] st = b["statement"] if not verify_sig(d["public_key"], st, b.get("individual_signature")): probs.append("the individual did not sign its birth") if not b.get("owner_signature"): probs.append("the owner has not confirmed the birth yet") elif not verify_sig(st["owner_key"], st, b["owner_signature"]): probs.append("the birth owner signature is invalid") if st.get("kind") != "birth": probs.append("the birth statement is not a birth") if st.get("individual") != d["id"] or st.get("individual_key") != d["public_key"]: probs.append("the birth statement is for a different individual") born = d.get("born_at") if (not isinstance(born, (int, float)) or isinstance(born, bool) or not math.isfinite(born) or not 0 <= born <= 253402300799 or not isinstance(st.get("ts"), (int, float)) or isinstance(st.get("ts"), bool) or born != st.get("ts")): probs.append("the birth date does not match the owner-signed birth timestamp") owner_key = st["owner_key"] if not d.get("transfers") and (d["owner"].get("person") != st.get("owner_person")): probs.append("the owner's person reference does not match the birth") nonces = set() for i, t in enumerate(d.get("transfers", [])): ts_ = t["statement"] if not verify_sig(owner_key, ts_, t["signature"]): probs.append(f"transfer {i} was not signed by the owner at that time") if ts_.get("kind") != "transfer": probs.append(f"transfer {i} is not a transfer") if ts_.get("individual") != d["id"]: probs.append(f"transfer {i} is for a different individual") if ts_.get("nonce") in nonces: probs.append(f"transfer {i} repeats an earlier transfer") nonces.add(ts_.get("nonce")) owner_key = ts_["new_owner_key"] if owner_key != d["owner"]["public_key"]: probs.append("the current owner does not follow from the birth and transfers") if life is not None: prev, n = None, 0 for e in life: core = {k: v for k, v in e.items() if k not in ("hash", "sig")} if e.get("n") != n: probs.append(f"life entry {n} is missing or out of order") break if e.get("prev") != prev or sha(canon(core)) != e.get("hash"): probs.append(f"life entry {n} was changed") break if not verify_sig(d["public_key"], {"hash": e["hash"]}, e.get("sig")): probs.append(f"life entry {n} was not signed by the individual") break prev, n = e["hash"], n + 1 else: if (d.get("life") or {}).get("head") != prev or (d.get("life") or {}).get("entries") != n: probs.append("the document's life head does not match the record") seen = {e.get("hash") for e in life} for h in published_heads or []: if h not in seen: probs.append("a published head is missing from this record: it is a copy that split off, or a rewrite") break except (KeyError, TypeError, AttributeError, ValueError) as e: probs.append(f"malformed: {type(e).__name__}: {e}") return {"ok": not probs, "problems": probs} def verify_public(record, life=None, published_heads=None): """A published record (aimnd.public/1): the document verifies, and the head it announces is the document's own.""" if (record or {}).get("schema") != PUBLIC_SCHEMA: return {"ok": False, "problems": ["not an aimnd.public/1 record"]} out = verify(record.get("document"), life, published_heads) life_doc = (record.get("document") or {}).get("life") or {} if record.get("head") != life_doc.get("head") or record.get("entries") != life_doc.get("entries"): out["problems"].append("the published head does not match the document") out["ok"] = False return out REGISTRY_LOG_SCHEMA = "aimnd.registry.log/1" CHECKPOINT_SCHEMA = "aimnd.registry.checkpoint/1" def verify_registry_log(entries, registry_key=None): """-> {"ok": bool, "problems": [str]}. A registry's log is a hash chain; entry 0 (genesis) names the registry's key and every entry is signed by it. Pass `registry_key` to insist on a key you already trust.""" prev, key = None, registry_key for i, e in enumerate(entries): try: core = {k: v for k, v in e.items() if k not in ("hash", "sig")} if e["n"] != i or e["prev"] != prev: return {"ok": False, "problems": [f"log entry {i} is missing or out of order"]} if sha(canon(core)) != e["hash"]: return {"ok": False, "problems": [f"log entry {i} was changed"]} if i == 0: if e["kind"] != "genesis": return {"ok": False, "problems": ["the log does not start with a genesis entry"]} if key is not None and e["data"]["registry_key"] != key: return {"ok": False, "problems": ["the log is signed by a different registry key"]} key = e["data"]["registry_key"] if not verify_sig(key, {"hash": e["hash"]}, e.get("sig")): return {"ok": False, "problems": [f"log entry {i} is not signed by the registry"]} prev = e["hash"] except (KeyError, TypeError): return {"ok": False, "problems": ["a log entry is malformed"]} if not entries: return {"ok": False, "problems": ["the log is empty"]} return {"ok": True, "problems": []} def verify_checkpoint(checkpoint, entries=None, witness_keys=None, min_witnesses=0): """The registry's signed statement of its log's size and head. With `entries` it must describe that log exactly. Co-signatures from keys in `witness_keys` are counted; at least `min_witnesses` must verify.""" probs = [] try: body = {k: checkpoint[k] for k in ("schema", "registry_key", "size", "head", "ts")} if body["schema"] != CHECKPOINT_SCHEMA or not verify_sig(body["registry_key"], body, checkpoint.get("signature")): probs.append("the checkpoint is not signed by the registry") if entries is not None: res = verify_registry_log(entries, body["registry_key"]) probs += res["problems"] if not res["problems"] and (len(entries) != body["size"] or entries[-1]["hash"] != body["head"]): probs.append("the checkpoint does not describe this log") if min_witnesses > 0 and not witness_keys: probs.append("no trusted witness keys were given, so no co-signature can be counted: pin the keys you trust") h = sha(canon(body)) good = {w["witness_key"] for w in checkpoint.get("witnesses", []) if (bool(witness_keys) and w["witness_key"] in witness_keys) and verify_sig(w["witness_key"], {"cosign": h}, w.get("signature"))} if len(good) < min_witnesses: probs.append(f"{len(good)} trusted witnesses co-signed it; {min_witnesses} required") except (KeyError, TypeError, AttributeError): probs.append("the checkpoint is malformed") return {"ok": not probs, "problems": probs} def find_fork(entries_a, entries_b, registry_key=None): """Two views of one registry's log (say, what two mirrors were each shown). -> {"ok": bool, "problems": [...], "fork_at": n|None}. Each view must verify under the same registry key. If both are valid and they disagree at some entry, the registry signed two different logs: that pair of entries is proof of equivocation. One view being a prefix of the other is consistent.""" a, b = verify_registry_log(entries_a, registry_key), verify_registry_log(entries_b, registry_key) if not a["ok"] or not b["ok"]: return {"ok": False, "problems": ["view A: " + p for p in a["problems"]] + ["view B: " + p for p in b["problems"]], "fork_at": None} if entries_a[0]["data"]["registry_key"] != entries_b[0]["data"]["registry_key"]: return {"ok": False, "problems": ["the two logs belong to different registries"], "fork_at": None} for i in range(min(len(entries_a), len(entries_b))): if entries_a[i]["hash"] != entries_b[i]["hash"]: return {"ok": False, "fork_at": i, "problems": [f"the registry signed two different logs: they part at entry {i} (hashes {entries_a[i]['hash'][:16]} and {entries_b[i]['hash'][:16]}), and both are signed by its key"]} return {"ok": True, "problems": [], "fork_at": None} def status_from_log(entries, iid): """What a (verified) log says about one ID: active, flagged, revoked, retired, or None when it was never registered.""" status = None for e in entries: if e.get("id") != iid or status == "revoked": continue k, d = e.get("kind"), e.get("data") or {} if k == "registered": status = "active" elif k in ("head", "resolved"): status = d.get("status", "active") elif k == "flagged": status = "flagged" elif k == "revoked": status = "revoked" return status def _no_duplicates(pairs): out = {} for k, v in pairs: if k in out: raise ValueError(f"duplicate key {k!r}") out[k] = v return out def _load(path): with open(path) as f: return json.load(f, object_pairs_hook=_no_duplicates) def _load_lines(path): with open(path) as f: return [json.loads(ln, object_pairs_hook=_no_duplicates) for ln in f if ln.strip()] def _registry_main(argv): def opt(name, many=False): vals = [] while name in argv: i = argv.index(name) vals.append(argv[i + 1]) argv[i:i + 2] = [] return vals if many else (vals[-1] if vals else None) key, cp_path, iid = opt("--registry-key"), opt("--checkpoint"), opt("--id") witnesses, minw = opt("--witness", many=True), int(opt("--min-witnesses") or 0) wfile = opt("--witness-file") if wfile: # one witness key per line (anything after the key is a note); "#" starts a comment with open(wfile) as f: witnesses += [ln.split("#")[0].split()[0] for ln in f if ln.split("#")[0].strip()] path, other = opt("--registry-log"), opt("--compare-log") entries = _load_lines(path) if other: entries_b = _load_lines(other) res = find_fork(entries, entries_b, key) print(json.dumps(res, indent=2)) return 0 if res["ok"] else 1 res = verify_registry_log(entries, key) out = {"entries_checked": len(entries), **res} if cp_path: cp = _load(cp_path) c = verify_checkpoint(cp, entries, witnesses or None, minw) out["checkpoint"] = c res = {"ok": res["ok"] and c["ok"]} if iid: out["status"] = status_from_log(entries, iid) print(json.dumps(out, indent=2)) return 0 if res["ok"] else 1 def _main(argv): if "--registry-log" in argv: return _registry_main(list(argv)) if not argv or argv[0] in ("-h", "--help"): print(__doc__.split("MIT License")[0].strip()) return 0 heads = [] if "--heads" in argv: i = argv.index("--heads") with open(argv[i + 1]) as f: heads = [ln.strip() for ln in f if ln.strip()] argv = argv[:i] + argv[i + 2:] doc = _load(argv[0]) life = None if len(argv) > 1: life = _load_lines(argv[1]) res = verify_public(doc, life, heads) if doc.get("schema") == PUBLIC_SCHEMA else verify(doc, life, heads) d = doc.get("document", doc) print(json.dumps({"id": d.get("id"), "status": d.get("status"), "owner_key": (d.get("owner") or {}).get("public_key"), "life_entries_checked": len(life) if life is not None else None, **res}, indent=2)) return 0 if res["ok"] else 1 if __name__ == "__main__": sys.exit(_main(sys.argv[1:]))