tests/test_merkle.py
1.9 KB · 71 lines · python Raw
1 """Tests for Merkle tree primitives."""
2
3 from __future__ import annotations
4
5 import hashlib
6
7 import pytest
8
9 from pqc_reasoning_ledger import (
10 ReasoningLedgerError,
11 build_proof,
12 compute_merkle_root,
13 verify_inclusion,
14 )
15
16
17 def _leaf(i: int) -> str:
18 return hashlib.sha3_256(f"leaf-{i}".encode()).hexdigest()
19
20
21 def test_empty_raises() -> None:
22 with pytest.raises(ReasoningLedgerError):
23 compute_merkle_root([])
24
25
26 def test_single_leaf_root() -> None:
27 leaf = _leaf(0)
28 root = compute_merkle_root([leaf])
29 # Single-leaf Merkle root is just the domain-separated leaf hash
30 expected = hashlib.sha3_256(b"\x00" + bytes.fromhex(leaf)).hexdigest()
31 assert root == expected
32
33
34 def test_build_and_verify_even_count() -> None:
35 leaves = [_leaf(i) for i in range(4)]
36 root = compute_merkle_root(leaves)
37 for i in range(4):
38 proof = build_proof(leaves, i, root)
39 assert verify_inclusion(proof)
40 assert proof.root == root
41 assert proof.tree_size == 4
42
43
44 def test_build_and_verify_odd_count() -> None:
45 leaves = [_leaf(i) for i in range(5)]
46 root = compute_merkle_root(leaves)
47 for i in range(5):
48 proof = build_proof(leaves, i, root)
49 assert verify_inclusion(proof), f"failed at index {i}"
50 assert proof.root == root
51
52
53 def test_tampered_sibling_fails() -> None:
54 leaves = [_leaf(i) for i in range(4)]
55 root = compute_merkle_root(leaves)
56 proof = build_proof(leaves, 1, root)
57 # Corrupt a sibling hash
58 tampered_siblings = list(proof.siblings)
59 tampered_siblings[0] = "0" * 64
60 from pqc_reasoning_ledger import InclusionProof
61
62 bad = InclusionProof(
63 leaf_hash=proof.leaf_hash,
64 index=proof.index,
65 tree_size=proof.tree_size,
66 root=proof.root,
67 siblings=tampered_siblings,
68 directions=list(proof.directions),
69 )
70 assert not verify_inclusion(bad)
71