kaysentinel / scripts /verify_ssr.py
karanjahpn
Harden verify_ssr: dedicated exceptions, input validation, constant-time compare
df423b1
Raw
History Blame Contribute Delete
5.14 kB
"""
verify_ssr.py
Validates byte-level convergence between two client-side SSR (Structural
Sufficient Representation) extractions, as produced by a Geth-side and a
Reth-side Kaysentinel extractor implementation.
Usage:
from verify_ssr import verify_differential_ssr, SSRVerificationError
try:
verify_differential_ssr(geth_bytes, reth_bytes)
except SSRVerificationError:
# This branch is the ONLY correct way to handle a failed check.
# Deny / block here. Do NOT catch a broader exception type
# (e.g. `except Exception`) elsewhere in the call chain and
# treat it as success -- that turns this authorization check
# fail-open.
deny()
Note: this script only checks the *comparison* logic. It does not itself
implement the Geth or Reth extractors -- those still need to be built
(see docs/framework.md, section 8).
"""
import hashlib
import hmac
# Defensive upper bound on input size. SSR payloads are bounded by protocol
# design; anything wildly larger than that is either a bug or an attempt to
# force excessive hashing/memory work. Adjust to the real max SSR size once
# the SSZ schema (Phase 1) is finalized.
MAX_SSR_BYTES = 16 * 1024 * 1024 # 16 MiB
class SSRVerificationError(Exception):
"""Base class for all SSR verification failures.
Deliberately a dedicated exception hierarchy (not ValueError /
AssertionError) so that callers cannot accidentally swallow a failed
authorization check inside a generic `except (ValueError, ...)` block
written for unrelated input-validation errors elsewhere in the
pipeline. Any code that catches this exception is expected to deny
the action being authorized, not to continue as if verification had
succeeded.
"""
class SSRLengthMismatch(SSRVerificationError):
"""Raised when the two SSR payloads differ in length."""
class SSRDivergenceError(SSRVerificationError):
"""Raised when the two SSR payloads differ in content."""
def _validate_input(name: str, data: bytes) -> None:
if not isinstance(data, (bytes, bytearray)):
raise TypeError(f"{name} must be bytes, got {type(data).__name__}")
if len(data) == 0:
raise ValueError(f"{name} must not be empty")
if len(data) > MAX_SSR_BYTES:
raise ValueError(
f"{name} exceeds MAX_SSR_BYTES ({len(data)} > {MAX_SSR_BYTES})"
)
def verify_differential_ssr(geth_ssz_bytes: bytes, reth_ssz_bytes: bytes) -> bool:
"""
Validates absolute byte-level convergence between client SSR extractions.
Ensures that client-internal structures produce zero semantic drift.
Returns True only on successful verification. On any mismatch, raises
a subclass of SSRVerificationError -- it never returns False. Fail
closed: if this function is called and its exception is not
explicitly handled by denying the associated authorization, the
surrounding program will halt rather than silently proceed.
"""
_validate_input("geth_ssz_bytes", geth_ssz_bytes)
_validate_input("reth_ssz_bytes", reth_ssz_bytes)
# 1. Assert identical payload sizing
if len(geth_ssz_bytes) != len(reth_ssz_bytes):
raise SSRLengthMismatch(
f"Consensus Divergence Detected: Byte length mismatch. "
f"Geth: {len(geth_ssz_bytes)} bytes, Reth: {len(reth_ssz_bytes)} bytes."
)
# 2. Compute SHA-256 root hashes
geth_root = hashlib.sha256(geth_ssz_bytes).digest()
reth_root = hashlib.sha256(reth_ssz_bytes).digest()
# Constant-time comparison. Not strictly required here since neither
# value is secret, but it costs nothing and removes any need to
# reason about timing behavior if this function is ever reused in a
# context where one side's hash is confidential.
if not hmac.compare_digest(geth_root, reth_root):
# Trace byte variance to identify the first layout or alignment
# fault. This is diagnostic only -- the authorization decision
# was already made above by the hash comparison.
first_diff = None
for idx, (b_g, b_r) in enumerate(zip(geth_ssz_bytes, reth_ssz_bytes)):
if b_g != b_r:
first_diff = idx
break
detail = (
f"first differing byte at offset {hex(first_diff)}"
if first_diff is not None
else "hash mismatch with no byte-level difference found "
"(unexpected; investigate encoding/comparison logic)"
)
raise SSRDivergenceError(
f"Structural divergence detected. Geth root: 0x{geth_root.hex()}, "
f"Reth root: 0x{reth_root.hex()}. {detail}."
)
print(f"SSR Verification Success. Canonical Root: 0x{geth_root.hex()}")
return True
if __name__ == "__main__":
# Minimal smoke test with matching dummy payloads.
dummy = b"\x00" * 32
verify_differential_ssr(dummy, dummy)
# Smoke test for the failure path.
try:
verify_differential_ssr(dummy, b"\x01" * 32)
except SSRDivergenceError as e:
print(f"Expected failure raised correctly: {e}")