#!/usr/bin/env python3 """Verify the Eddycoin mainnet genesis (eddycoin-mainnet-1). Python 3.8+, standard library only. python3 verify_genesis.py # verify the files in this folder python3 verify_genesis.py --online # also compare with the copies served by the seed node Checks: 1. emission-signing.pub matches the pinned key fingerprint (SHA-256 of the raw 32-byte Ed25519 key), 2. SHA-256 of genesis.json equals genesis.json.sha256 and the value pinned below, 3. genesis.json.sig is a valid detached Ed25519 signature (RFC 8032) of the exact genesis.json bytes, 4. the genesis content: network eddycoin-mainnet-1, chain id EDDY1, no allocations (zero premine), 900,000,000 EDDY cap, emission spec hash = SHA-256 of ../emission/kompromis-120b.json, key fingerprint, 5. the genesis checkpoint id recomputed exactly like the node does equals GENESIS_ID, 6. the node source (../node/eddycoin_node.py) embeds byte-identical genesis bytes, signature and GENESIS_ID. Exit code 0 = all checks passed.""" import hashlib, json, os, re, sys, urllib.request HERE = os.path.dirname(os.path.abspath(__file__)) PINNED_KEY_SHA256 = "b62ce8abf98a9c3d2416bee74766ed4d847afb5f3a9cd999bf4f07c4f3d591a5" PINNED_GENESIS_SHA256 = "980e4a20a507899699d614f5593b16900c6fbba471c2e1eccaddee862c5249bc" GENESIS_ID = "f720818b202072d10b8b694d39eba3f17bb8a82d8597a92688057a5016f4746a" SEED = "https://129-159-188-73.sslip.io/emission/" # ---- Ed25519 verify (RFC 8032 section 5.1.7) ---- p = 2**255 - 19; L = 2**252 + 27742317777372353535851937790883648493 d = -121665 * pow(121666, p - 2, p) % p; I = pow(2, (p - 1) // 4, p) def _add(P, Q): x1, y1, z1, t1 = P; x2, y2, z2, t2 = Q a = (y1 - x1) * (y2 - x2) % p; b = (y1 + x1) * (y2 + x2) % p; c = 2 * t1 * t2 * d % p; dd = 2 * z1 * z2 % p e, f, g, h = b - a, dd - c, dd + c, b + a return (e * f % p, g * h % p, f * g % p, e * h % p) def _mul(s, P): Q = (0, 1, 1, 0) while s: if s & 1: Q = _add(Q, P) P = _add(P, P); s >>= 1 return Q def _eq(P, Q): return (P[0] * Q[2] - Q[0] * P[2]) % p == 0 and (P[1] * Q[2] - Q[1] * P[2]) % p == 0 def _xrec(y, sign): if y >= p: return None x2 = (y * y - 1) * pow(d * y * y + 1, p - 2, p) % p if x2 == 0: return None if sign else 0 x = pow(x2, (p + 3) // 8, p) if (x * x - x2) % p: x = x * I % p if (x * x - x2) % p: return None if (x & 1) != sign: x = p - x return x def _dec(b): y = int.from_bytes(b, "little"); sign = y >> 255; y &= (1 << 255) - 1; x = _xrec(y, sign) return None if x is None else (x, y, 1, x * y % p) _gy = 4 * pow(5, p - 2, p) % p; G = (_xrec(_gy, 0), _gy, 1, _xrec(_gy, 0) * _gy % p) def ed25519_verify(pub, msg, sig): if len(pub) != 32 or len(sig) != 64: return False A = _dec(pub); R = _dec(sig[:32]); s = int.from_bytes(sig[32:], "little") if A is None or R is None or s >= L: return False h = int.from_bytes(hashlib.sha512(sig[:32] + pub + msg).digest(), "little") % L return _eq(_mul(s, G), _add(R, _mul(h, A))) def sha(s): return hashlib.sha256(s.encode("utf-8")).hexdigest() def canon(o): return json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=False) fails = [] def check(name, ok): print(("PASS " if ok else "FAIL ") + name) if not ok: fails.append(name) def main(): rd = lambda n: open(os.path.join(HERE, n), "rb").read() g, sig_hex, sha_line, pub_hex = rd("genesis.json"), rd("genesis.json.sig").decode().strip(), rd("genesis.json.sha256").decode().split()[0], rd("emission-signing.pub").decode().strip() pub = bytes.fromhex(pub_hex) check("public key fingerprint = pinned " + PINNED_KEY_SHA256[:16] + "...", hashlib.sha256(pub).hexdigest() == PINNED_KEY_SHA256) h = hashlib.sha256(g).hexdigest() check("genesis.json SHA-256 = .sha256 file = pinned value", h == sha_line == PINNED_GENESIS_SHA256) check("Ed25519 signature of genesis.json is valid", ed25519_verify(pub, g, bytes.fromhex(sig_hex))) try: G = json.loads(g) except ValueError: check("genesis.json is valid JSON", False); print("\n%d CHECK(S) FAILED" % len(fails)); sys.exit(1) check("canonical JSON (sorted keys, no whitespace, ASCII, no newline)", json.dumps(G, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode() == g) check("network eddycoin-mainnet-1, chain id EDDY1", G["network"] == "eddycoin-mainnet-1" and G["chain_id"] == "EDDY1") check("zero premine: allocations == []", G["allocations"] == []) check("max supply 900,000,000 EDDY (8 decimals)", G["max_supply"] == str(900000000 * 10 ** 8)) check("key fingerprint inside genesis = pinned key", G["emission_signing_pubkey_fingerprint"] == "sha256:" + PINNED_KEY_SHA256) spec = os.path.join(HERE, "..", "emission", "kompromis-120b.json") if os.path.exists(spec): check("emission.spec_sha256 = SHA-256 of ../emission/kompromis-120b.json", G["emission"]["spec_sha256"] == hashlib.sha256(open(spec, "rb").read()).hexdigest()) # genesis checkpoint id, exactly as eddycoin_node.py genesis_checkpoint() D = G["domains"]; ZERO = "0" * 64 sroot = sha(D["state"] + "|" + canon({"height": 0, "issued": "0", "utxos": []})) ev = "GENESIS|%s|%s|%s" % (G["network"], G["chain_id"], h) gid = sha(D["checkpoint"] + "|" + ZERO + "|" + sroot + "|" + ev + "|" + ev) check("genesis checkpoint id = " + GENESIS_ID[:16] + "...", gid == GENESIS_ID) node = os.path.join(HERE, "..", "node", "eddycoin_node.py") if os.path.exists(node): t = open(node, encoding="utf-8").read() m = re.search(r"^GENESIS_JSON = b'(.*)'$", t, re.M); s = re.search(r"^GENESIS_SIG = '([0-9a-f]+)'$", t, re.M) check("node embeds the same genesis bytes, signature and GENESIS_ID", bool(m and s) and m.group(1).encode() == g and s.group(1) == sig_hex and ('GENESIS_ID_HARDCODED = "%s"' % GENESIS_ID) in t) if "--online" in sys.argv: for n in ("genesis.json", "genesis.json.sig", "genesis.json.sha256"): try: with urllib.request.urlopen(SEED + n, timeout=20) as r: b = r.read() check("online copy %s is identical" % n, b.strip() == rd(n).strip()) except Exception as ex: check("online copy %s (%s)" % (n, ex), False) print("\nALL CHECKS PASSED" if not fails else "\n%d CHECK(S) FAILED" % len(fails)) sys.exit(1 if fails else 0) if __name__ == "__main__": main()