HDK-audit / tests /test_hash_engine.py
JakCwi's picture
Initial release: code, docs, tests (CC BY-NC 4.0)
977ccde
Raw History Blame Contribute Delete
4.4 kB
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