#!/usr/bin/env python3
"""
Independent runner for https://aere.network/verifiable-claims.json

Runs every claim in that file against the live Aere Network endpoints and prints
PASS or FAIL for each. It trusts nothing in the file except the addresses and the
expected values, which is the point: if Aere is lying, this script says FAIL.

  python verifiable-claims-check.py

No wallet, no key, no gas, no account. All calls are read-only.
Requires only the Python standard library.
"""
import json, urllib.request, hashlib, sys

ENDPOINTS = ["https://rpc.aere.network", "https://rpc2.aere.network"]
UA = "aere-claims-check/1"          # NOTE: default Python-urllib UA is 403'd by the CDN
PASS, FAIL = [], []

def rpc(method, params, url=ENDPOINTS[0]):
    body = json.dumps({"jsonrpc": "2.0", "method": method, "params": params, "id": 1}).encode()
    req = urllib.request.Request(url, data=body,
                                 headers={"content-type": "application/json", "User-Agent": UA})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read())

def check(name, got, want, note=""):
    ok = (got == want)
    (PASS if ok else FAIL).append(name)
    print(f"  [{'PASS' if ok else 'FAIL'}] {name}")
    if not ok:
        print(f"         expected {want!r}")
        print(f"         got      {got!r}")
    elif note:
        print(f"         {note}")
    return ok

# ---------------------------------------------------------------- RLP (headers)
def rlp(b, i=0):
    p = b[i]
    if p <= 0x7f:  return ("s", b[i:i+1], i+1)
    if p <= 0xb7:  L = p-0x80; return ("s", b[i+1:i+1+L], i+1+L)
    if p <= 0xbf:
        n = p-0xb7; L = int.from_bytes(b[i+1:i+1+n], 'big')
        return ("s", b[i+1+n:i+1+n+L], i+1+n+L)
    if p <= 0xf7: n, L = 0, p-0xc0
    else:
        n = p-0xf7; L = int.from_bytes(b[i+1:i+1+n], 'big')
    start = i+1+n; end = start+L; items = []; j = start
    while j < end:
        t, v, j = rlp(b, j); items.append((t, v))
    return ("l", items, end)

def decode_extra(hexstr):
    _, items, _ = rlp(bytes.fromhex(hexstr[2:]))
    return items[0][1], ["0x"+v[1].hex() for v in items[1][1]], items[4][1]

# ------------------------------------------------------------------ 1. identity
print("\n1. Chain identity")
check("chainId == 0xaf0", rpc("eth_chainId", [])["result"], "0xaf0")
check("net_version == 2800", rpc("net_version", [])["result"], "2800")
check("not syncing", rpc("eth_syncing", [])["result"], False)
cv = rpc("web3_clientVersion", [])["result"]
check("client is Besu", cv.startswith("besu/"), True, cv)

# --------------------------------------------------------------- 2. block time
print("\n2. Block interval over 100,000 blocks")
head = rpc("eth_getBlockByNumber", ["latest", False])["result"]
n = int(head["number"], 16)
old = rpc("eth_getBlockByNumber", [hex(n-100000), False])["result"]
dt = (int(head["timestamp"], 16) - int(old["timestamp"], 16)) / 100000
check("0.50 <= mean block interval <= 0.55 s", 0.50 <= dt <= 0.55, True, f"measured {dt:.3f} s/block")

# ------------------------------------------------- 3. validator set from header
print("\n3. QBFT validator set and quorum, decoded from the block header")
vanity, vals, seals = decode_extra(head["extraData"])
check("validator count == 7", len(vals), 7)
check("committed seals == 5", len(seals), 5)
check("every seal is 65 bytes", {len(s[1]) for s in seals}, {65})
sets, sealcounts = set(), set()
for k in range(0, 200, 10):
    b = rpc("eth_getBlockByNumber", [hex(n-k), False])["result"]
    _, vv, ss = decode_extra(b["extraData"])
    sets.add(tuple(vv)); sealcounts.add(len(ss))
check("validator set identical across 20 sampled blocks", len(sets), 1)
check("seal count >= 5 on every sampled block", min(sealcounts) >= 5, True, f"counts seen: {sealcounts}")

# ------------------------------------------------------ 4. precompiles declared
print("\n4. Post-quantum precompiles declared by the node")
cfg = rpc("eth_config", [])["result"]["current"]
pc = cfg["precompiles"]
for nm, ad in [("AereFalcon512", "ae1"), ("AereFalcon1024", "ae2"), ("AereMLDSA44", "ae3"),
               ("AereSLHDSA128s", "ae4"), ("AereSHAKE256", "ae5")]:
    check(f"{nm} at 0x0{ad.upper()}", pc.get(nm), "0x"+"0"*37+ad)
check("0x0AE6 / 0x0AE7 NOT declared (testnet only)",
      [k for k in pc if k in ("AereMLKEM", "AereHashToPoint")], [])
check("fork activationTime", cfg["activationTime"], 1783820272)

# ------------------------------------------------------------ 5. SHAKE256 KAT
print("\n5. NIST SHAKE256 known-answer test at 0x0AE5")
want = "0x" + hashlib.shake_256(b"").hexdigest(32)   # computed locally, not taken from Aere
for ep in ENDPOINTS:
    got = rpc("eth_call", [{"to": "0x"+"0"*37+"AE5",
                            "data": "0x"+"0"*62+"20"}, "latest"], ep)["result"]
    check(f"SHAKE256(empty,32) matches local computation [{ep.split('//')[1]}]", got, want)

# ------------------------------------------- 6. precompiles execute (gas probe)
#
# One address literal, so there is nothing to substitute twice:
#   GAS; STATICCALL(0x0f4240, target, args 0..32, ret 0..32); POP; GAS; SWAP1; SUB;
#   PUSH1 0; MSTORE; RETURN 32
#
# The probe's own overhead is NOT one number. EIP-2929 pre-warms precompiles 0x01
# to 0x09 (access cost 100); every other address is cold (access cost 2600). The
# Aere band at 0x0AE1+ is NOT in the pre-warmed set, so it pays the cold price.
# Compare each target only against a control of the same warmth, or you will read
# the identity precompile as 2,482 gas CHEAPER than an empty address and conclude
# something silly.
print("\n6. Precompiles consume gas (positive proof of execution)")
def gas(addr20, ep=ENDPOINTS[0]):
    code = ("0x5a" "6020" "6000" "6020" "6000" "73" + addr20 + "620f4240" "fa"
            "50" "5a" "9003" "600052" "60206000f3")
    r = rpc("eth_call", [{"to": "0x"+"0"*36+"b0b0", "data": "0x", "gas": "0x1c9c380"},
                         "latest", {"0x"+"0"*36+"b0b0": {"code": code}}], ep)
    return int(r["result"], 16)

WARM, COLD = 125, gas("0"*36+"dead")
check("codeless control (cold address) costs the bare probe overhead", COLD, 2625)
# Calibration. These two costs are fixed by the EVM specification, not by Aere.
# A probe nobody has calibrated is not a measurement, it is a number.
check("IDENTITY 0x04 costs exactly 18 (EVM spec: 15+3*1)",  gas("0"*39+"4") - WARM, 18)
check("SHA-256  0x02 costs exactly 72 (EVM spec: 60+12*1)", gas("0"*39+"2") - WARM, 72)
for nm, ad, exp in [("Falcon-512 0x0AE1",   "0"*37+"ae1", 40000),
                    ("Falcon-1024 0x0AE2",  "0"*37+"ae2", 75000),
                    ("ML-DSA-44 0x0AE3",    "0"*37+"ae3", 55000),
                    ("SLH-DSA-128s 0x0AE4", "0"*37+"ae4", 350000),
                    ("SHAKE256 0x0AE5",     "0"*37+"ae5", 60)]:
    check(f"{nm} consumes {exp} gas above the cold control", gas(ad) - COLD, exp)
# Honest scope. A zero here is CONSISTENT with absence and is not proof of it: a
# precompile that charged nothing would read the same. Revision 1 of the claims
# file called this proof of absence; that was an overstatement and is withdrawn.
# The positive statement of absence is the eth_config enumeration in section 4.
for ad in ("0"*37+"ae6", "0"*37+"ae7"):
    check(f"0x{ad[-3:].upper()} costs 0 above control (consistent with absence, NOT proof)",
          gas(ad) - COLD, 0, "absence is asserted from eth_config in section 4, not from this")

# ------------------------------------------------------- 7. EVM opcode parity
print("\n7. Cancun opcode set, with a negative control")
A = "0x" + "0"*38 + "c0"
def run(code):
    r = rpc("eth_call", [{"to": A, "data": "0x"}, "latest", {A: {"code": code}}])
    return r.get("result"), r.get("error", {}).get("message", "")
for nm, code, exp in [("PUSH0 (EIP-3855)",           "0x602a5f5260205ff3",            0x2a),
                      ("TSTORE/TLOAD (EIP-1153)",    "0x604260015d60015c60005260206000f3", 0x42),
                      ("MCOPY (EIP-5656)",           "0x60426020526020602060005e60206000f3", 0x42),
                      ("CHAINID (EIP-1344)",         "0x4660005260206000f3",          2800)]:
    res, err = run(code)
    check(nm, int(res, 16) if res else err, exp)
for nm, code in [("BLOBBASEFEE (EIP-7516) executes", "0x4a60005260206000f3"),
                 ("BLOBHASH (EIP-4844) executes",    "0x60004960005260206000f3")]:
    res, err = run(code)
    check(nm, "Invalid opcode" not in err and res is not None, True, "no Invalid-opcode error")
for op in ("0c", "21", "a5"):
    res, err = run("0x"+op+"60005260206000f3")
    check(f"NEGATIVE CONTROL: undefined opcode 0x{op} must fail", "Invalid opcode" in err, True)

# --------------------------------------------------- 8. contract code + proxies
print("\n8. Deployed bytecode and EIP-1967 proxy slots")
SLOTS = {"implementation": "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
         "admin":          "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103",
         "beacon":         "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"}
CONTRACTS = {
    "AereFeeBurnVault":       ("0x696afDF4f814e6Fd6aa45CE14C498ed9375fB2c6", 1313),
    "AereSink":               ("0x69581B86A48161b067Ff4E01544780625B231676", 4164),
    "AereCoinbaseSplitter":   ("0xb4b0eCe9011613A5b84248a9B42a0f309E6F01Ec", 2614),
    "AereFalcon512Verifier":  ("0x4E8e9682329e646784fB3bd01430aA4bA54D8fFC", 7333),
    "AereFalcon1024Verifier": ("0xF0aFA59BaB2058e4B6e6B424b7f76750F1F66e36", 7334),
    "AereMLDSA44Verifier":    ("0xf1F7A6Acd82D5DAf9AF3166a2F736EE52C5F85AE", 8654),
    "AereCancunCanary":       ("0x8DbFC002bB23124cBeCd7B4916c179D2AFd65498", 1541),
    "AereKZGVerifier":        ("0x6596307BD8f54d9A91FE364EBC3e594F200AC862", 1988),
    "AerePQCAttestation":     ("0x465d9e3b476bf98aa1393079e240db5d2a9bea6a", 4402),
}
zero = "0x" + "0"*64
for nm, (ad, size) in CONTRACTS.items():
    code = rpc("eth_getCode", [ad, "latest"])["result"]
    check(f"{nm} code is {size} bytes", len(code)//2 - 1, size)
    slots = [rpc("eth_getStorageAt", [ad, s, "latest"])["result"] for s in SLOTS.values()]
    check(f"{nm} is not an EIP-1967 proxy", set(slots), {zero})

# ------------------------------------------------- 9. burn vault has no owner
print("\n9. Burn vault administration surface")
V = "0x696afDF4f814e6Fd6aa45CE14C498ed9375fB2c6"
code = rpc("eth_getCode", [V, "latest"])["result"]
def push4(c):
    b = bytes.fromhex(c[2:]); out = set(); i = 0
    while i < len(b):
        op = b[i]
        if op == 0x63 and i+5 <= len(b): out.add(b[i+1:i+5].hex()); i += 5; continue
        if 0x60 <= op <= 0x7f: i += 1 + (op-0x5f); continue
        i += 1
    return out
sels = push4(code)
for nm, s in [("owner()", "8da5cb5b"), ("transferOwnership(address)", "f2fde38b"),
              ("renounceOwnership()", "715018a6"), ("upgradeTo(address)", "3659cfe6"),
              ("admin()", "f851a440")]:
    check(f"burn vault does NOT expose {nm}", s in sels, False)
check("burn vault DOES expose sweepToZero()", "579663e2" in sels, True)
r = rpc("eth_call", [{"to": V, "data": "0x579663e2",
                      "from": "0x"+"0"*36+"dEaD"}, "latest"])
check("sweepToZero() is permissionless (no revert from an arbitrary caller)",
      "error" not in r, True)

# -------------------------------------- 10. burn rate is a parameter, not a law
print("\n10. Burn rate is owner-settable (this REFUTES 'no admin' copy)")
SP = "0xb4b0eCe9011613A5b84248a9B42a0f309E6F01Ec"
spc = rpc("eth_getCode", [SP, "latest"])["result"]
ssel = push4(spc)
check("splitter EXPOSES owner()", "8da5cb5b" in ssel, True)
check("splitter EXPOSES transferOwnership()", "f2fde38b" in ssel, True)
own = rpc("eth_call", [{"to": SP, "data": "0x8da5cb5b"}, "latest"])["result"]
owner = "0x" + own[-40:]
check("splitter owner is 0x0243a4f4..f3c3", owner, "0x0243a4f47d44b40b65d33f20329de20d00c6f3c3")
ocode = rpc("eth_getCode", [owner, "latest"])["result"]
check("splitter owner is a plain EOA, NOT a multisig or timelock", ocode, "0x",
      "a single key can change the burn rate")

# ------------------------------------------- 11. burn is not currently flowing
print("\n11. Is the burn engine actually burning? (we claim it is NOT)")
# The state window is 512 blocks and its edge is a RACE: `n` was captured when this
# script started, so n-500 may already be out of window by the time we get here.
# Re-read the head NOW and step back 400, safely inside the window.
hnow = int(rpc("eth_blockNumber", [])["result"], 16)
now = int(rpc("eth_getBalance", [V, "latest"])["result"], 16)
was = rpc("eth_getBalance", [V, hex(hnow-400)])["result"]
if was is None:
    check("burn vault balance comparison", "state pruned, NOT MEASURED", "a value", "")
else:
    check("burn vault balance UNCHANGED over 400 blocks",
          now - int(was, 16), 0,
          f"balance {now} wei at both ends; nothing is being routed in")
check("block proposer is a validator, not the splitter",
      head["miner"].lower() == SP.lower(), False, f"miner = {head['miner']}")

# -------------------------------------------------------- 12. receipts survive
print("\n12. Historical receipts (full history, beyond the 512-block state window)")
for h, blk in [("0x3a1838deb9f1b4b26f7af5ef1b4609a8aea77a3ebe654e38151caeadfff4fd5c", 8735205),
               ("0xcd1350372f0a61e26e8a3228da599be9211839f378eef0ea49f2025a20920660", 9200532)]:
    r = rpc("eth_getTransactionReceipt", [h])["result"]
    check(f"receipt {h[:12]}... status success at block {blk}",
          (r["status"], int(r["blockNumber"], 16)) if r else None, ("0x1", blk))

# ------------------------------------- 13. the chain is idle, and the TPS number
# Added in revision 2, after a hostile read of revision 1 pointed out that every
# performance-adjacent figure here was measured on an empty chain and never said
# so. These checks PASS by confirming things that are bad for Aere. That is the
# point: a file that can only produce good news cannot be used to catch us.
print("\n13. Load and throughput (we claim the chain is IDLE and the TPS number is not derivable)")
h13 = int(rpc("eth_blockNumber", [])["result"], 16)
tot, empty, sampled = 0, 0, 0
for i in range(0, 3000, 250):
    b = rpc("eth_getBlockByNumber", [hex(h13-i), False])["result"]
    sampled += 1; tot += len(b["transactions"])
    if b["gasUsed"] == "0x0": empty += 1
check("sampled blocks carry ZERO transactions", tot, 0,
      f"{sampled} headers across the most recent 3,000 blocks, {empty} with gasUsed 0x0")
tp = rpc("txpool_status", [])["result"]
check("a pending backlog exists and is NOT draining into blocks",
      (tp["pending"] != "0x0", tot == 0), (True, True),
      f"pending={int(tp['pending'],16)}, queued={int(tp['queued'],16)}, yet blocks are empty")
gl = int(rpc("eth_getBlockByNumber", ["latest", False])["result"]["gasLimit"], 16)
check("gasLimit is 2^53-1, a JavaScript artifact and not an engineering parameter",
      gl, 2**53-1,
      "so the published '273,000 TPS from gas limit / block time' derivation does "
      "not produce 273,000; it produces ~8e11. Neither number is claimed.")

# ------------------------------------------------------------------ verdict
print("\n" + "="*66)
print(f"  PASS {len(PASS)}   FAIL {len(FAIL)}")
if FAIL:
    print("  FAILED:", ", ".join(FAIL))
print("="*66)
print("\n  Sections 10, 11 and 13 are deliberately unflattering to Aere: they PASS by")
print("  confirming that the burn rate is settable by one key, that nothing is being")
print("  burned, that the chain is idle, and that the published TPS derivation does")
print("  not produce the published number. A claims file that can only produce good")
print("  news is a brochure. This one is checkable in both directions.\n")
sys.exit(1 if FAIL else 0)
