"""Aha demo: an inclusion proof against a Merkle root proves less than it
looks like it does.

Act 1 — an honest proof verifies.
Act 2 — a *forged* proof for a block that was never in the file also
        verifies, built with no cryptography, from the honest proof alone.
Act 3 — two different block lists produce one identical root.
Act 4 — the same three under RFC 6962's tagged hashing, where only the
        honest one survives.
"""

from merkle import NaiveTree, TaggedTree, hexes

CHUNK = 64  # bytes per block — deliberately equal to two SHA-256 digests


def block(text):
    """A fixed-size storage block: 64 bytes, dot-padded. Fixed-size chunking
    is what real content-addressed stores do, and 64 = 2 x 32 is the size
    that makes act 2 work.
    """
    raw = text.encode()
    assert len(raw) <= CHUNK, text
    return raw.ljust(CHUNK, b".")


BLOCKS = [
    block("blk0: alice -> bob 10 coins "),
    block("blk1: bob -> carol 4 coins "),
    block("blk2: carol -> dave 1 coin "),
    block("blk3: dave -> alice 7 coins "),
]

THREE = [block("aaa"), block("bbb"), block("ccc")]
FOUR = THREE + [THREE[-1]]  # the last block, repeated


def act1(tree):
    print(f"  root                 {tree.root.hex()}")
    proof0 = tree.proof(0)
    print(f"  proof(0)             {hexes(proof0)}  (len {len(proof0)})")
    print(f"  verify(blk0)         {tree.verify(tree.root, BLOCKS[0], proof0)}")
    return proof0


def act2(tree, proof0):
    """Forge a block that was never in BLOCKS, using only the published
    proof for blk0 and a copy of blk0 itself. No tree internals, no key,
    no collision search — two concatenations and a list slice.

    proof0[0] is blk1's leaf hash. Concatenating blk0's leaf hash with it
    gives 64 bytes — a legal block — whose *leaf* hash is, under the naive
    scheme, byte-for-byte the tree's *internal* node above blk0 and blk1.
    Drop the now-redundant first step and the shortened proof lands on the
    same root.
    """
    scheme = type(tree)
    forged = scheme.leaf_hash(BLOCKS[0]) + proof0[0][0]
    forged_proof = proof0[1:]
    print(f"  forged block         {forged.hex()[:32]}... ({len(forged)} bytes)")
    print(f"  in BLOCKS?           {forged in BLOCKS}")
    print(f"  forged proof         {hexes(forged_proof)}  (len {len(forged_proof)})")
    print(f"  verify(forged)       {tree.verify(tree.root, forged, forged_proof)}")


def act3(cls):
    three, four = cls(THREE).root, cls(FOUR).root
    print(f"  root([a,b,c])        {three.hex()}")
    print(f"  root([a,b,c,c])      {four.hex()}")
    print(f"  same root?           {three == four}")


def run():
    for name, cls in (("NAIVE  leaf=H(b)  node=H(l||r)", NaiveTree),
                      ("TAGGED leaf=H(00||b)  node=H(01||l||r)", TaggedTree)):
        tree = cls(BLOCKS)
        print(f"\n=== {name} ===")
        print(" [1] honest inclusion proof for blk0")
        proof0 = act1(tree)
        print(" [2] forged proof for a block that was never in BLOCKS")
        act2(tree, proof0)
        print(" [3] two different block lists, one root?")
        act3(cls)


if __name__ == "__main__":
    run()
