#!/usr/bin/env python3 """Check that the published Eddycoin downloads were built from THIS source tree. The Windows programs are PyInstaller 6 onefile exes (Python 3.12). They contain the project's own code as compiled Python bytecode. This script opens each exe, takes every eddycoin_* code object (entry script + modules) and compares it with the code object that Python 3.12 compiles from the matching .py file here. Code objects are compared with ==, which covers the bytecode, constants (all strings/docstrings), names, line numbers and nested functions. The rest of each exe is the unmodified PyInstaller bootloader + Python runtime + standard library. The Mac app contains plain .py files, which are compared byte for byte. python3.12 tools/verify_exes.py DOWNLOAD_FOLDER (folder with the 4 zips from https://eddycoin.pages.dev/downloads.html) Needs exactly Python 3.12 (bytecode differs between Python versions). Standard library only. Exit 0 = all match.""" import io, os, sys, zipfile, importlib.util HERE = os.path.dirname(os.path.abspath(__file__)); ROOT = os.path.dirname(HERE) sys.path.insert(0, HERE) from exe_modules import own_code if sys.version_info[:2] != (3, 12): sys.exit("run this with Python 3.12 (found %d.%d) - bytecode is version specific" % sys.version_info[:2]) EXES = { # exe name inside the zip -> folder with its sources "Eddycoin CPU Miner.exe": "miners", "Eddycoin GPU Miner.exe": "miners", "Eddycoin Wallet.exe": "wallet"} MAC = {"eddycoin_echain_cpu_miner.py": "miners/eddycoin_echain_cpu_miner.py", "eddycoin_miner_gui.py": "miners/eddycoin_miner_gui.py", "eddycoin_chain.py": "miners/eddycoin_chain.py", "eddycoin_old_keys.py": "miners/eddycoin_old_keys.py", "eddycoin_mac_start.py": "miners/mac/eddycoin_mac_start.py", "seed/eddycoin_chain.json": "miners/eddycoin_chain.json", "seed/eddycoin_miner_defaults.json": "miners/eddycoin_miner_defaults.json"} fails = 0 def res(ok, msg): global fails print(("MATCH " if ok else "DIFFERS ") + msg); fails += 0 if ok else 1 def check_exe(label, data, folder): tmp = os.path.join(ROOT, ".verify_tmp.exe") with open(tmp, "wb") as f: f.write(data) try: code = own_code(tmp) finally: os.remove(tmp) if not code: res(False, label + ": no eddycoin code found"); return for key, c in sorted(code.items()): src = os.path.join(ROOT, folder, c.co_filename) if not os.path.exists(src): res(False, "%s: %s has no source file %s" % (label, key, os.path.relpath(src, ROOT))); continue cc = compile(open(src, "rb").read(), c.co_filename, "exec") res(cc == c, "%s: %s == %s" % (label, key, os.path.relpath(src, ROOT))) def main(folder): zips = [os.path.join(folder, f) for f in sorted(os.listdir(folder)) if f.lower().endswith(".zip")] if not zips: sys.exit("no zip files in " + folder) for z in zips: with zipfile.ZipFile(z) as zf: for n in zf.namelist(): b = os.path.basename(n) if b in EXES: check_exe(os.path.basename(z) + "!" + b, zf.read(n), EXES[b]) if ".app/Contents/Resources/app/" in n and not n.endswith("/"): rel = n.split(".app/Contents/Resources/app/", 1)[1] if rel in MAC: res(zf.read(n) == open(os.path.join(ROOT, MAC[rel]), "rb").read(), "%s!%s == %s (bytes)" % (os.path.basename(z), rel, MAC[rel])) else: res(False, "%s!%s: not in the source tree" % (os.path.basename(z), rel)) print("\nALL PUBLISHED PROGRAM CODE MATCHES THIS SOURCE" if not fails else "\n%d MISMATCH(ES)" % fails) sys.exit(1 if fails else 0) if __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else ".")