mirror of
https://github.com/saymrwulf/proof-aware-crypto-tooling-agent.git
synced 2026-09-04 20:03:40 +00:00
79 lines
3.4 KiB
Python
79 lines
3.4 KiB
Python
|
|
from datetime import datetime, timezone
|
||
|
|
|
||
|
|
from pacta.sthstore import check_sth_against_store, check_sth_freshness
|
||
|
|
from pacta.transparency import consistency_proof, merkle_root, proof_to_hex
|
||
|
|
|
||
|
|
|
||
|
|
def _sth(size, root, log_id="log-1", timestamp="2026-07-06T00:00:00Z"):
|
||
|
|
return {"log_id": log_id, "tree_size": size, "root_hash": root, "timestamp": timestamp}
|
||
|
|
|
||
|
|
|
||
|
|
def _tree(n):
|
||
|
|
return [f"leaf-{i}".encode() for i in range(n)]
|
||
|
|
|
||
|
|
|
||
|
|
def test_first_use_pins_then_same_size_must_match(tmp_path):
|
||
|
|
store = tmp_path / "sth.json"
|
||
|
|
root = merkle_root(_tree(3)).hex()
|
||
|
|
first = check_sth_against_store(_sth(3, root), store)
|
||
|
|
assert first.ok and first.action == "pinned_first_use"
|
||
|
|
again = check_sth_against_store(_sth(3, root), store)
|
||
|
|
assert again.ok and again.action == "matched"
|
||
|
|
evil = check_sth_against_store(_sth(3, merkle_root(_tree(4)).hex()), store)
|
||
|
|
assert not evil.ok
|
||
|
|
assert any("EQUIVOCATION" in d for d in evil.diagnostics)
|
||
|
|
|
||
|
|
|
||
|
|
def test_growth_requires_consistency_proof_and_advances(tmp_path):
|
||
|
|
store = tmp_path / "sth.json"
|
||
|
|
leaves = _tree(5)
|
||
|
|
old_root = merkle_root(leaves[:2]).hex()
|
||
|
|
new_root = merkle_root(leaves).hex()
|
||
|
|
assert check_sth_against_store(_sth(2, old_root), store).ok
|
||
|
|
# growth without proof: rejected
|
||
|
|
bare = check_sth_against_store(_sth(5, new_root), store)
|
||
|
|
assert not bare.ok and any("consistency proof" in d for d in bare.diagnostics)
|
||
|
|
# growth with a valid proof: pin advances
|
||
|
|
proof = proof_to_hex(consistency_proof(leaves, 2))
|
||
|
|
good = check_sth_against_store(_sth(5, new_root), store, consistency_proof_hex=proof)
|
||
|
|
assert good.ok and good.action == "advanced"
|
||
|
|
# and the pin really moved
|
||
|
|
assert check_sth_against_store(_sth(5, new_root), store).action == "matched"
|
||
|
|
|
||
|
|
|
||
|
|
def test_receipt_embedded_consistency_anchor_is_checked(tmp_path):
|
||
|
|
store = tmp_path / "sth.json"
|
||
|
|
leaves = _tree(4)
|
||
|
|
old_root = merkle_root(leaves[:3]).hex()
|
||
|
|
new_root = merkle_root(leaves).hex()
|
||
|
|
assert check_sth_against_store(_sth(3, old_root), store).ok
|
||
|
|
# anchor size matches the pin but anchor ROOT lies about history
|
||
|
|
lying_anchor = {
|
||
|
|
"from_tree_size": 3,
|
||
|
|
"from_root_hash": merkle_root(_tree(9)).hex(),
|
||
|
|
"proof": proof_to_hex(consistency_proof(leaves, 3)),
|
||
|
|
}
|
||
|
|
out = check_sth_against_store(_sth(4, new_root), store, consistency_from=lying_anchor)
|
||
|
|
assert not out.ok and any("EQUIVOCATION" in d for d in out.diagnostics)
|
||
|
|
honest_anchor = dict(lying_anchor, from_root_hash=old_root)
|
||
|
|
assert check_sth_against_store(_sth(4, new_root), store, consistency_from=honest_anchor).ok
|
||
|
|
|
||
|
|
|
||
|
|
def test_rollback_is_rejected(tmp_path):
|
||
|
|
store = tmp_path / "sth.json"
|
||
|
|
assert check_sth_against_store(_sth(7, merkle_root(_tree(7)).hex()), store).ok
|
||
|
|
rolled = check_sth_against_store(_sth(3, merkle_root(_tree(3)).hex()), store)
|
||
|
|
assert not rolled.ok and any("ROLLBACK" in d for d in rolled.diagnostics)
|
||
|
|
|
||
|
|
|
||
|
|
def test_freshness_policy():
|
||
|
|
now = datetime(2026, 7, 6, 12, 0, 0, tzinfo=timezone.utc)
|
||
|
|
fresh, _ = check_sth_freshness(_sth(1, "aa", timestamp="2026-07-06T11:59:30Z"), 60, now=now)
|
||
|
|
assert fresh
|
||
|
|
stale, error = check_sth_freshness(_sth(1, "aa", timestamp="2026-07-06T10:00:00Z"), 60, now=now)
|
||
|
|
assert not stale and "freshness policy" in error
|
||
|
|
future, error = check_sth_freshness(_sth(1, "aa", timestamp="2026-07-06T13:00:00Z"), 60, now=now)
|
||
|
|
assert not future and "future" in error
|
||
|
|
missing, error = check_sth_freshness({"log_id": "x"}, 60, now=now)
|
||
|
|
assert not missing
|