Dependency-free path_proof package under a dedicated module. Bounded to this issue only: no changes to existing lab_loop tests or scripts. Deliverables: 1. path_proof/ package (receipt.py, keys.py, __init__.py) — verifier with canonical JSON, deterministic build/verify/verify_and_accept, in-memory KeyRegistry + SpentRegistry. No I/O, no wall clock, no randomness. 2. Each receipt binds action kind/target/request/result, model id+invocation, tool trace, policy bundle, code identity, evidence uri, plus action_id, verifier nonce, issued_at/expires_at, and signer. 3. Deterministic spent-nonce/idempotency registry: the same valid receipt is accepted exactly once; a replay is rejected on the nonce OR action_id axis. 4. Positive + negative tests: valid passes; replay fails; request/result substitution fails; wrong target fails; expired fails; unknown/revoked signer fails; malformed structures fail closed with no exception. Every negative case asserts rejection AND a specific reason. 5. docs/path-proof-threat-boundary.md documents the exact threat boundary: a verified receipt proves a bound, fresh, single-use, trusted-attested commitment — NOT runtime execution unless runtime evidence is supplied. Clean-checkout verification (commands actually run): - /home/vincent/seedvault-inventory/venv/bin/python3 -m pytest tests/ -q -> 36 passed (31 path_proof + 5 pre-existing lab_loop) - git diff --cached --name-only origin/main -> 6 files, zero .pyc - __pycache__ gitignored; clean checkout stays clean after tests Closes #28 Refs: #3 (Vincent path-proof critique), #19/PR #42 (canon validator r2) [HANDOFF] from=vincent to=timmy
85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
"""
|
|
Trusted-signer key registry for path-proof receipts.
|
|
|
|
Dependency-free. Keys are Ed25519 public keys (raw 32-byte bytes) or their
|
|
hex encoding. Verification here is a *simulated* cryptographic check: the
|
|
signature must equal the canonical commitment bound to the key id. In a
|
|
production recorder the signature would be a real Ed25519 signature and the
|
|
key a verified public key from a hardware/service-isolated key store; the
|
|
shape and the trust decision (known vs unknown vs revoked) are identical.
|
|
|
|
The trust decision is what the gate enforces:
|
|
- ``known``: (issuer, key_id) is present and not revoked
|
|
- ``unknown``: not present
|
|
- ``revoked``: present but revoked
|
|
|
|
Everything is deterministic and in-memory so tests are reproducible from a
|
|
clean checkout with no generated artifacts.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Dict, Optional, Tuple
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SignerKey:
|
|
"""A trusted runtime-recorder signing identity.
|
|
|
|
``public_key`` is the raw Ed25519 public key bytes (32 bytes) or hex text.
|
|
``valid_until`` is an explicit epoch-seconds bound (``None`` = no bound).
|
|
"""
|
|
|
|
issuer: str
|
|
key_id: str
|
|
public_key: str
|
|
valid_until: Optional[int] = None
|
|
revoked: bool = False
|
|
|
|
def is_trusted(self, now: int) -> bool:
|
|
if self.revoked:
|
|
return False
|
|
if self.valid_until is not None and now > self.valid_until:
|
|
return False
|
|
return True
|
|
|
|
|
|
class KeyRegistry:
|
|
"""In-memory registry of trusted signer keys, keyed by (issuer, key_id)."""
|
|
|
|
def __init__(self) -> None:
|
|
self._keys: Dict[Tuple[str, str], SignerKey] = {}
|
|
|
|
def add(self, key: SignerKey) -> None:
|
|
self._keys[(key.issuer, key.key_id)] = key
|
|
|
|
def add_hex(self, issuer: str, key_id: str, public_key_hex: str,
|
|
valid_until: Optional[int] = None, revoked: bool = False) -> SignerKey:
|
|
key = SignerKey(issuer=issuer, key_id=key_id, public_key=public_key_hex,
|
|
valid_until=valid_until, revoked=revoked)
|
|
self.add(key)
|
|
return key
|
|
|
|
def resolve(self, issuer: str, key_id: str) -> Optional[SignerKey]:
|
|
return self._keys.get((issuer, key_id))
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._keys)
|
|
|
|
def __contains__(self, item: Tuple[str, str]) -> bool:
|
|
return item in self._keys
|
|
|
|
|
|
def make_demo_registry() -> KeyRegistry:
|
|
"""A deterministic demo registry: two trusted recorders, one revoked."""
|
|
reg = KeyRegistry()
|
|
reg.add_hex("recorder:forge", "recorder:forge/key-1", "aa" * 32)
|
|
reg.add_hex("recorder:gateway", "recorder:gateway/key-1", "bb" * 32)
|
|
reg.add_hex("recorder:old", "recorder:old/key-1", "cc" * 32, revoked=True)
|
|
return reg
|
|
|
|
|
|
# Backwards-compatible alias used by the public package surface.
|
|
keys = make_demo_registry()
|