tests/test_merkle.py
1.9 KB · 70 lines · python Raw
1 """Tests for the SHA3-256 Merkle tree."""
2
3 from __future__ import annotations
4
5 import hashlib
6
7 import pytest
8
9 from pqc_audit_log_fs.errors import AuditLogError
10 from pqc_audit_log_fs.merkle import (
11 build_merkle_proof,
12 compute_merkle_root,
13 verify_inclusion,
14 )
15
16
17 def _hex(data: bytes) -> str:
18 return hashlib.sha3_256(data).hexdigest()
19
20
21 def test_empty_raises() -> None:
22 with pytest.raises(AuditLogError):
23 compute_merkle_root([])
24
25
26 def test_single_leaf_root() -> None:
27 leaf = _hex(b"only-one")
28 root = compute_merkle_root([leaf])
29 # A single leaf's root is SHA3-256(0x00 || leaf_bytes)
30 expected = hashlib.sha3_256(b"\x00" + bytes.fromhex(leaf)).hexdigest()
31 assert root == expected
32
33
34 def test_proof_roundtrip_even_count() -> None:
35 leaves = [_hex(f"leaf-{i}".encode()) for i in range(8)]
36 root = compute_merkle_root(leaves)
37 for idx in range(len(leaves)):
38 proof = build_merkle_proof(leaves, idx, root)
39 assert proof.root == root
40 assert proof.tree_size == 8
41 assert verify_inclusion(proof)
42
43
44 def test_proof_roundtrip_odd_count() -> None:
45 leaves = [_hex(f"leaf-{i}".encode()) for i in range(7)]
46 root = compute_merkle_root(leaves)
47 for idx in range(len(leaves)):
48 proof = build_merkle_proof(leaves, idx, root)
49 assert verify_inclusion(proof)
50
51
52 def test_tampered_sibling_fails() -> None:
53 leaves = [_hex(f"leaf-{i}".encode()) for i in range(5)]
54 proof = build_merkle_proof(leaves, 2)
55 assert verify_inclusion(proof)
56 # Mutate first sibling
57 tampered = proof.siblings[:]
58 tampered[0] = "00" * 32
59 from pqc_audit_log_fs.merkle import InclusionProof
60
61 bad = InclusionProof(
62 leaf_hash=proof.leaf_hash,
63 index=proof.index,
64 tree_size=proof.tree_size,
65 root=proof.root,
66 siblings=tampered,
67 directions=proof.directions,
68 )
69 assert not verify_inclusion(bad)
70