import hashlib from hdk.core.hash_engine import HashEngine from hdk.models import HDKNode, NodeLevel, EventType def test_content_hash_deterministic(): engine = HashEngine() h1 = engine.hash_content("hello world") h2 = engine.hash_content("hello world") assert h1 == h2 assert len(h1) == 64 # SHA-256 hex def test_content_hash_different_input(): engine = HashEngine() h1 = engine.hash_content("hello") h2 = engine.hash_content("hello!") assert h1 != h2 def test_hierarchical_hash_includes_parents(): engine = HashEngine() content = engine.hash_content("my query") h_no_parent = engine.hash_with_parents(content, []) h_with_parent = engine.hash_with_parents(content, ["aaa111"]) assert h_no_parent != h_with_parent def test_changing_ancestor_breaks_hash(): """Core HDK property: modifying any ancestor invalidates descendants.""" engine = HashEngine() root = engine.hash_content("project_1") tab = engine.hash_with_parents(engine.hash_content("tab_1"), [root]) conv = engine.hash_with_parents(engine.hash_content("conv_1"), [tab, root]) query = engine.hash_with_parents(engine.hash_content("prompt"), [conv, tab, root]) # Now change root root_tampered = engine.hash_content("project_1_MODIFIED") query_with_tampered = engine.hash_with_parents( engine.hash_content("prompt"), [conv, tab, root_tampered] ) assert query != query_with_tampered def _leaf(x: str) -> bytes: return hashlib.sha256(b"\x00" + x.encode()).digest() def _node(left: bytes, right: bytes) -> bytes: return hashlib.sha256(b"\x01" + left + right).digest() def test_merkle_root_single(): """A single leaf is hashed with the leaf prefix, never returned bare.""" engine = HashEngine() root = engine.compute_merkle_root(["aaa"]) assert root != "aaa" assert root == _leaf("aaa").hex() def test_merkle_root_pair(): engine = HashEngine() root = engine.compute_merkle_root(["aaa", "bbb"]) assert root == _node(_leaf("aaa"), _leaf("bbb")).hex() def test_merkle_root_odd_count(): """RFC 6962 shape for 3 leaves: H(0x01 || H(0x01||La||Lb) || Lc).""" engine = HashEngine() a, b, c = "a" * 64, "b" * 64, "c" * 64 root3 = engine.compute_merkle_root([a, b, c]) assert root3 == _node(_node(_leaf(a), _leaf(b)), _leaf(c)).hex() # no duplication of the odd leaf assert root3 != engine.compute_merkle_root([a, b, c, c]) def test_merkle_root_matches_rfc6962_split(): """Bottom-up result equals RFC 6962's recursive split at the largest power of two.""" def mth(d): if len(d) == 1: return _leaf(d[0]) k = 1 while k * 2 < len(d): k *= 2 return _node(mth(d[:k]), mth(d[k:])) engine = HashEngine() for n in range(1, 18): data = [f"leaf{i}" for i in range(n)] assert engine.compute_merkle_root(data) == mth(data).hex(), n def _chain(engine): root = HDKNode(id="r", content_hash=engine.hash_content("project"), parent_chain=[]) root.self_hash = engine.hash_with_parents(root.content_hash, []) root.node_hash = engine.create_node_hash(root) child = HDKNode(id="c", content_hash=engine.hash_content("query"), parent_chain=[root.node_hash]) child.self_hash = engine.hash_with_parents(child.content_hash, child.parent_chain) child.node_hash = engine.create_node_hash(child) return root, child def test_verify_chain_valid(): """parent_chain holds the parent's node_hash, and verify_chain accepts that.""" engine = HashEngine() root_node, child_node = _chain(engine) assert engine.verify_chain(child_node, [root_node]) is True def test_verify_chain_tampered(): engine = HashEngine() root_node, child_node = _chain(engine) # wrong parent hash in the chain child_node.parent_chain = ["TAMPERED_HASH"] assert engine.verify_chain(child_node, [root_node]) is False def test_verify_chain_tampered_parent(): engine = HashEngine() root_node, child_node = _chain(engine) root_node.model = "forged" # parent's fields no longer match its node_hash assert engine.verify_chain(child_node, [root_node]) is False def test_verify_chain_missing_parent(): engine = HashEngine() root_node, child_node = _chain(engine) assert engine.verify_chain(child_node, []) is False