#!/usr/bin/env python3 """agent-revolution merkle-ledger v0.1 — self-contained Merkle log. Themed for the agent-revolution bounty (task_8ff4d9f210a80f7b) and the Merkle hunt: an append-only, SHA-256 Merkle ledger with inclusion proofs and a verifier. Standard library only. Anchored by a genesis seed. Usage: python3 merkle_ledger.py --seed append "" python3 merkle_ledger.py --seed prove python3 merkle_ledger.py --seed verify python3 merkle_ledger.py --seed root """ from __future__ import annotations import argparse import hashlib import json import sys from typing import Optional SEED_DEFAULT = "e8fe8011f3e4fff761fc754b783e51646ca50f32a66cbdcb084609ebe7faadde" def _sha256(raw: bytes) -> bytes: return hashlib.sha256(raw).digest() def hash_leaf(data: str) -> bytes: # 0x00 domain-separates leaves from internal nodes. return _sha256(b"\x00" + data.encode("utf-8")) def hash_node(left: bytes, right: bytes) -> bytes: return _sha256(b"\x01" + left + right) def _height(n: int) -> int: return n.bit_length() class MerkleLedger: """Append-only Merkle log. ``leaves[0]`` is genesis (the seed).""" def __init__(self, seed: Optional[str] = None): self.seed = seed or SEED_DEFAULT self.leaves: list[bytes] = [hash_leaf(f"genesis:{self.seed}")] def append(self, entry: str) -> int: self.leaves.append(hash_leaf(entry)) return len(self.leaves) - 1 def root(self) -> bytes: """Root over leaves [0..n); empty storage folded to genesis.""" items = list(self.leaves) while len(items) > 1: pairs = [(items[i], items[i + 1] if i + 1 < len(items) else items[i]) for i in range(0, len(items), 2)] items = [hash_node(*p) for p in pairs] return items[0] def proof(self, index: int) -> list[str]: """Merkle inclusion proof: list of hex sibling hashes, leaf→root.""" if not 0 <= index < len(self.leaves): raise IndexError(f"index {index} out of range") siblings: list[bytes] = [] level = list(self.leaves) i = index while len(level) > 1: nxt: list[bytes] = [] pos = 0 for j in range(0, len(level), 2): left = level[j] right = level[j + 1] if j + 1 < len(level) else level[j] nxt.append(hash_node(left, right)) if i == j: sib = right if j + 1 < len(level) else left siblings.append(sib.hex()) elif i == j + 1: siblings.append(left.hex()) pos += 1 i = i // 2 level = nxt return siblings def verify(index: int, leaf_data: str, root_hex: str, proof_hex: list[str]) -> bool: """Recompute the root for leaf ``index`` from its inclusion proof.""" cur = hash_leaf(leaf_data) i = index for sib_hex in proof_hex: sib = bytes.fromhex(sib_hex) cur = hash_node(cur, sib) if i % 2 == 0 else hash_node(sib, cur) i //= 2 return cur.hex() == root_hex def _cli(argv: list[str]) -> int: p = argparse.ArgumentParser(description="agent-revolution merkle-ledger") p.add_argument("--seed", default=SEED_DEFAULT, help="genesis seed hex") sub = p.add_subparsers(dest="cmd", required=True) a = sub.add_parser("append", help="append an entry, print its index + root") a.add_argument("entry") r = sub.add_parser("root", help="print current root") pg = sub.add_parser("prove", help="print inclusion proof JSON for index") pg.add_argument("index", type=int) v = sub.add_parser("verify", help="verify a leaf against root + proof") v.add_argument("index", type=int) v.add_argument("entry") v.add_argument("root") v.add_argument("proof_json") ns = p.parse_args(argv) log = MerkleLedger(ns.seed) if ns.cmd == "append": idx = log.append(ns.entry) print(json.dumps({"index": idx, "root": log.root().hex()})) elif ns.cmd == "root": print(log.root().hex()) elif ns.cmd == "prove": print(json.dumps({"index": ns.index, "siblings": log.proof(ns.index)})) elif ns.cmd == "verify": ok = verify(ns.index, ns.entry, ns.root, json.loads(ns.proof_json)) print("OK" if ok else "FAIL") return 0 if __name__ == "__main__": sys.exit(_cli(sys.argv[1:]))