#!/usr/bin/env python3 # Falcon-512 verify gas benchmark: WITH vs WITHOUT the native HashToPoint precompile. # Deploys the stock in-EVM verifier (V0) and the precompile-backed variant (V1) on # the isolated fork node, runs identical real Falcon-512 KAT vectors through both, # and reports receipt gasUsed. Also confirms genuine sigs accept / tampered reject # on BOTH (integration KAT for the precompile). import json import subprocess import sys from web3 import Web3 from eth_account import Account RPC = "http://127.0.0.1:8545" KEY = "0x0000000000000000000000000000000000000000000000000000000000000001" acct = Account.from_key(KEY) w3 = Web3(Web3.HTTPProvider(RPC)) # QBFT is proof-of-authority: extraData > 32 bytes, needs the POA middleware. try: from web3.middleware import ExtraDataToPOAMiddleware w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) except Exception: from web3.middleware import geth_poa_middleware # older web3 w3.middleware_onion.inject(geth_poa_middleware, layer=0) assert w3.is_connected(), "no RPC" CHAIN = w3.eth.chain_id def compile_all(): out = subprocess.check_output([ "solc", "--combined-json", "abi,bin", "--optimize", "--optimize-runs", "200", "/root/staging/bench/AereFalcon512Verifier.sol", "/root/staging/bench/AereFalcon512VerifierHTP.sol", ]) j = json.loads(out) res = {} for name, c in j["contracts"].items(): short = name.split(":")[-1] res[short] = {"abi": c["abi"] if isinstance(c["abi"], list) else json.loads(c["abi"]), "bin": c["bin"]} return res def gas_price(): blk = w3.eth.get_block("latest") base = blk.get("baseFeePerGas", 0) or 0 return int(base) * 2 + 10 ** 9 def send(tx): tx.setdefault("chainId", CHAIN) tx.setdefault("nonce", w3.eth.get_transaction_count(acct.address)) tx.setdefault("gas", 40_000_000) tx.setdefault("gasPrice", gas_price()) signed = acct.sign_transaction(tx) raw = getattr(signed, "raw_transaction", None) or signed.rawTransaction h = w3.eth.send_raw_transaction(raw) return w3.eth.wait_for_transaction_receipt(h, timeout=120) def deploy(art): c = w3.eth.contract(abi=art["abi"], bytecode=art["bin"]) tx = c.constructor().build_transaction({"from": acct.address, "gas": 12_000_000, "gasPrice": gas_price(), "nonce": w3.eth.get_transaction_count(acct.address), "chainId": CHAIN}) r = send(tx) assert r.status == 1, "deploy failed" return w3.eth.contract(address=r.contractAddress, abi=art["abi"]), r.gasUsed def parse_sm(sm: bytes): siglen = (sm[0] << 8) | sm[1] nonce = sm[2:42] msglen = len(sm) - 2 - 40 - siglen message = sm[42:42 + msglen] return nonce, message def main(): vec_path = sys.argv[1] if len(sys.argv) > 1 else "/root/staging/kat/falcon512_bench_vectors.txt" out_path = sys.argv[2] if len(sys.argv) > 2 else "/root/staging/bench/bench-results.json" # --- precompile liveness sanity --- sample = (bytes([9]) + b"\x00" * 40 + b"abc") htp_out = w3.eth.call({"to": "0x0000000000000000000000000000000000000AE7", "data": "0x" + sample.hex()}) assert len(htp_out) == 1024, f"0x0AE7 not active (got {len(htp_out)} bytes)" print(f"precompile 0x0AE7 live: returned {len(htp_out)} bytes for logn=9") arts = compile_all() v0, g0_deploy = deploy(arts["AereFalcon512Verifier"]) v1, g1_deploy = deploy(arts["AereFalcon512VerifierHTP"]) print(f"deployed V0(in-EVM)={v0.address} gas={g0_deploy} V1(precompile)={v1.address} gas={g1_deploy}") rows = [] for line in open(vec_path): line = line.strip() if not line: continue tc, pk_hex, sm_hex = line.split("|") pk = bytes.fromhex(pk_hex) sm = bytes.fromhex(sm_hex) nonce, message = parse_sm(sm) # correctness (eth_call) — genuine must accept on both ok0 = v0.functions.verifySignedMessage(pk, sm).call() ok1 = v1.functions.verifySignedMessage(pk, sm).call() # tampered must reject on both bad = bytearray(sm); bad[-1] ^= 0x01 bad0 = v0.functions.verifySignedMessage(pk, bytes(bad)).call() bad1 = v1.functions.verifySignedMessage(pk, bytes(bad)).call() # gas via state-changing verifyAndRecord (identical calldata both sides) r0 = send(v0.functions.verifyAndRecord(pk, sm).build_transaction( {"from": acct.address, "gas": 40_000_000, "gasPrice": gas_price(), "nonce": w3.eth.get_transaction_count(acct.address), "chainId": CHAIN})) r1 = send(v1.functions.verifyAndRecord(pk, sm).build_transaction( {"from": acct.address, "gas": 40_000_000, "gasPrice": gas_price(), "nonce": w3.eth.get_transaction_count(acct.address), "chainId": CHAIN})) # component gas: standalone hashToPoint estimate on each htp0 = v0.functions.hashToPoint(nonce, message).estimate_gas({"from": acct.address}) htp1 = v1.functions.hashToPoint(nonce, message).estimate_gas({"from": acct.address}) row = {"tc": tc, "accept_v0": ok0, "accept_v1": ok1, "reject_tampered_v0": not bad0, "reject_tampered_v1": not bad1, "verify_gas_in_evm": r0.gasUsed, "verify_gas_precompile": r1.gasUsed, "verify_gas_drop": r0.gasUsed - r1.gasUsed, "hashToPoint_gas_in_evm": htp0, "hashToPoint_gas_precompile": htp1} rows.append(row) print(f"tc{tc} accept v0={ok0} v1={ok1} tamperReject v0={not bad0} v1={not bad1} " f"| verify gas {r0.gasUsed} -> {r1.gasUsed} (drop {r0.gasUsed - r1.gasUsed}) " f"| HTP {htp0} -> {htp1}") n = len(rows) avg = lambda k: sum(r[k] for r in rows) // n all_ok = all(r["accept_v0"] and r["accept_v1"] and r["reject_tampered_v0"] and r["reject_tampered_v1"] for r in rows) summary = { "chainId": CHAIN, "vectors": n, "integration_all_pass": all_ok, "avg_verify_gas_in_evm": avg("verify_gas_in_evm"), "avg_verify_gas_precompile": avg("verify_gas_precompile"), "avg_verify_gas_drop": avg("verify_gas_drop"), "avg_verify_pct_drop": round(100 * avg("verify_gas_drop") / avg("verify_gas_in_evm"), 1), "avg_hashToPoint_gas_in_evm": avg("hashToPoint_gas_in_evm"), "avg_hashToPoint_gas_precompile": avg("hashToPoint_gas_precompile"), "deploy_gas_in_evm": g0_deploy, "deploy_gas_precompile": g1_deploy, "rows": rows, } json.dump(summary, open(out_path, "w"), indent=2) print("\n=== SUMMARY ===") print(json.dumps({k: v for k, v in summary.items() if k != "rows"}, indent=2)) if not all_ok: print("INTEGRATION_FAILED"); sys.exit(3) print("BENCH_DONE") if __name__ == "__main__": main()