#!/usr/bin/env python3 """Independently verify a Syntropic API dev log. Don't trust us: check it. pip install cryptography opentimestamps # signature check + timestamp proofs python verify_devlog.py https://syntropicapi.com/apis//devlog.json Checks: 1. Every entry's hash is sha256 of its canonical JSON, and each entry links to the previous one (hash chain). 2. The public key in the log matches the one published in DNS (TXT _devlog., fetched via DNS-over-HTTPS from Cloudflare) and at https:///.well-known/syntropic-devlog-key.txt. 3. The chain head is signed by that key. 4. Every OpenTimestamps anchor hash is part of the chain. Its proof must commit to that hash, and each Bitcoin attestation in it must equal the merkle root of the stated block (fetched from the public blockstream.info API, no Bitcoin node needed). That proves the chain up to the entry existed by the block's time. Proofs not yet in a block are reported as pending (they confirm within hours). """ import base64 import hashlib import json import sys import urllib.request from urllib.parse import urlparse GENESIS = "0" * 64 def fetch(url: str, accept: str = "*/*") -> bytes: req = urllib.request.Request(url, headers={"Accept": accept, "User-Agent": "syntropic-devlog-verifier/1"}) with urllib.request.urlopen(req, timeout=20) as r: return r.read() def canonical(obj: dict) -> bytes: return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode() def check(label: str, ok: bool, detail: str = "") -> bool: print(f" {'โœ“' if ok else 'โœ—'} {label}{(' โ€” ' + detail) if detail else ''}") return ok def dns_key(domain: str) -> str | None: url = f"https://cloudflare-dns.com/dns-query?name=_devlog.{domain}&type=TXT" answers = json.loads(fetch(url, "application/dns-json")).get("Answer", []) for a in answers: txt = a["data"].replace('" "', "").strip('"') fields = dict(f.strip().split("=", 1) for f in txt.split(";") if "=" in f) if fields.get("v") == "SYNDEVLOG1": return fields.get("p") return None def main(url: str) -> int: log = json.loads(fetch(url)) domain = urlparse(url).hostname base = url.rsplit("/", 1)[0] ok = True print(f"Dev log of '{log['api']}' ({len(log['entries'])} entries) from {url}\n") print("1. Hash chain") prev = GENESIS for e in log["entries"]: body = {k: e[k] for k in ("n", "date", "commit", "tree", "message", "prev")} good = e["prev"] == prev and hashlib.sha256(canonical(body)).hexdigest() == e["hash"] ok &= good if not good: check(f"entry {e['n']}", False, "hash or link mismatch") prev = e["hash"] ok &= check("all entries hash-linked", ok) ok &= check("head is the last entry", log["head"]["hash"] == prev) print("2. Public key") key = log["public_key"]["key"] in_dns = dns_key(domain) ok &= check(f"DNS TXT _devlog.{domain}", in_dns == key, "matches" if in_dns == key else f"found {in_dns!r}") try: wk = fetch(f"https://{domain}/.well-known/syntropic-devlog-key.txt").decode() ok &= check("/.well-known/syntropic-devlog-key.txt", key in wk) except Exception as exc: # noqa: BLE001 ok &= check("/.well-known/syntropic-devlog-key.txt", False, str(exc)) print("3. Signature") try: from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey Ed25519PublicKey.from_public_bytes(base64.b64decode(key)).verify( base64.b64decode(log["head"]["signature"]), bytes.fromhex(log["head"]["hash"]) ) ok &= check("head signed by the published key", True) except ImportError: print(" - skipped: pip install cryptography") except Exception: # noqa: BLE001 ok &= check("head signed by the published key", False, "invalid signature") print("4. OpenTimestamps anchors") chain = {e["hash"]: e for e in log["entries"]} try: from opentimestamps.core.notary import BitcoinBlockHeaderAttestation from opentimestamps.core.serialize import BytesDeserializationContext from opentimestamps.core.timestamp import DetachedTimestampFile except ImportError: DetachedTimestampFile = None print(" - proofs not checked: pip install opentimestamps (or verify at https://opentimestamps.org)") for a in log["anchors"]: ok &= check(f"anchor for entry {a['entry']} is in the chain", a["hash"] in chain, f"stamped {a['stamped_at']}") if DetachedTimestampFile is None: continue proof = DetachedTimestampFile.deserialize(BytesDeserializationContext(fetch(f"{base}/devlog/{a['proof']}"))) ok &= check(" proof commits to this hash", proof.file_digest == hashlib.sha256(a["hash"].encode()).digest()) blocks = [(m, att) for m, att in proof.timestamp.all_attestations() if isinstance(att, BitcoinBlockHeaderAttestation)] if not blocks: print(" ยท pending: not yet in a Bitcoin block (normally confirms within a few hours)") continue msg, att = min(blocks, key=lambda b: b[1].height) block_hash = fetch(f"https://blockstream.info/api/block-height/{att.height}").decode().strip() block = json.loads(fetch(f"https://blockstream.info/api/block/{block_hash}")) when = __import__("datetime").datetime.fromtimestamp(block["timestamp"], __import__("datetime").timezone.utc) ok &= check(f" in Bitcoin block {att.height}", msg[::-1].hex() == block["merkle_root"], f"existed by {when:%Y-%m-%d %H:%M} UTC") print("\nRESULT:", "VERIFIED" if ok else "FAILED") return 0 if ok else 1 if __name__ == "__main__": if len(sys.argv) != 2: sys.exit(__doc__) sys.exit(main(sys.argv[1]))