"""Canonical serialization, SHA-256 hash chaining, and Ed25519 signing. Working default Q14 (specs/OpenQuestions-WorkingDefaults.md): hash chain is SHA-256 over a canonical JSON serialization; signatures are Ed25519 over the same canonical bytes. This module is the single source of truth for what "canonical serialization" means so hashing and signing never drift. """ from __future__ import annotations import hashlib import json from typing import Any from cryptography.hazmat.primitives.asymmetric.ed25519 import ( Ed25519PrivateKey, Ed25519PublicKey, ) GENESIS = "GENESIS" def canonical_bytes(record: dict[str, Any]) -> bytes: """Deterministic byte representation of a record for hashing/signing. Sorted keys, no insignificant whitespace, UTF-8. The `signature` and `previous_entry_hash` fields are excluded because they are computed from (or alongside) this representation, not part of what they attest to. """ payload = { k: v for k, v in record.items() if k not in ("signature", "previous_entry_hash") } return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") def entry_hash(record: dict[str, Any]) -> str: """SHA-256 hex digest of a ledger entry's canonical bytes.""" return hashlib.sha256(canonical_bytes(record)).hexdigest() def verify_chain(entries: list[dict[str, Any]]) -> None: """Raise ValueError on the first broken link in an entry chain. Entries must already be in ledger (append) order. The first entry's `previous_entry_hash` must equal GENESIS; every subsequent entry's `previous_entry_hash` must equal the SHA-256 of the prior entry's canonical bytes. """ expected = GENESIS for i, entry in enumerate(entries): actual = entry.get("previous_entry_hash") if actual != expected: raise ValueError( f"hash chain broken at entry index {i} (id={entry.get('id')!r}): " f"expected previous_entry_hash={expected!r}, got {actual!r}" ) expected = entry_hash(entry) def sign_record(record: dict[str, Any], private_key: Ed25519PrivateKey) -> str: """Ed25519 signature (hex) over the record's canonical bytes.""" return private_key.sign(canonical_bytes(record)).hex() def verify_record_signature( record: dict[str, Any], signature_hex: str, public_key: Ed25519PublicKey ) -> bool: """True if signature_hex is a valid Ed25519 signature over record's canonical bytes.""" try: public_key.verify(bytes.fromhex(signature_hex), canonical_bytes(record)) return True except Exception: return False