Skip to content

tools: receipt-signed decision logs for post-incident verification#37843

Open
tomjwxf wants to merge 3 commits intocommaai:masterfrom
tomjwxf:feat/receipt-signed-decision-logs
Open

tools: receipt-signed decision logs for post-incident verification#37843
tomjwxf wants to merge 3 commits intocommaai:masterfrom
tomjwxf:feat/receipt-signed-decision-logs

Conversation

@tomjwxf
Copy link
Copy Markdown

@tomjwxf tomjwxf commented Apr 16, 2026

What

Adds an optional receipt signer for cereal decision events in `tools/lib/`. Each event (controlsState, lateralPlan, etc.) produces an HMAC-signed, hash-chained receipt. The chain is independently verifiable without trusting the device operator.

Zero new dependencies. Stdlib only (hashlib, hmac, json). Nothing touches selfdrive or cereal.

Why

Post-incident investigation. If a comma device is involved in an incident, the log files are mutable (stored on the device filesystem). Someone with access can edit them. A signed receipt chain means:

  • Editing any log entry breaks its signature
  • Deleting a receipt breaks the hash chain (the next receipt points to a hash that no longer exists)
  • Inserting a receipt between two legitimate ones is detectable (the parent hashes don't match)
  • Anyone with the verification key can confirm the chain offline, without trusting comma's servers

This matters for insurance claims, regulatory inquiries, and legal discovery where the question is "prove the log hasn't been altered since the drive."

How it works

```python
from tools.lib.receipt_signer import ReceiptChain

chain = ReceiptChain(device_id="comma-3x-abc123")

Sign any decision event

chain.sign_event("controlsState", {"enabled": True, "vEgo": 25.3})
chain.sign_event("lateralPlan", {"dPath": [0.0, 0.1, 0.3]})

Verify the chain

assert chain.verify_all() # True

Tamper with a receipt

chain._receipts[0].payload["event_type"] = "TAMPERED"
assert not chain.verify_all() # False, tampering detected

Export for offline verification

chain.export_jsonl("/data/receipts/route_abc.jsonl")
```

Privacy-preserving: receipts store `event_hash` (SHA-256 of the event data), not raw sensor values. The receipt proves the event existed with specific content without exposing the content in the receipt itself.

Integration sketch (not in this PR)

```python
chain = create_receipt_logger(device_id=Params().get("DongleId"))
sm = messaging.SubMaster(['controlsState', 'lateralPlan'])
while True:
sm.update()
if sm.updated['controlsState']:
chain.sign_event("controlsState", sm['controlsState'].to_dict())
```

This is deliberately a tools/lib utility, not wired into any control path. Integration into loggerd or a daemon would be a separate PR if there's interest.

Production upgrade path

This PR uses HMAC-SHA256 (symmetric key, zero deps). The receipt envelope format is designed for Ed25519 upgrade:

  • Swap `"alg": "HS256"` to `"alg": "EdDSA"`
  • Key moves to a secure element (e.g., ATECC608B in a future hardware revision)
  • Verification becomes asymmetric (anyone verifies, only the device signs)

The envelope format follows IETF draft-farley-acta-signed-receipts. Offline verification CLI: `npx @veritasacta/verify`.

Tests

10 tests in `tools/lib/test_receipt_signer.py`:

```
PYTHONPATH=. python3 tools/lib/test_receipt_signer.py

All 10 tests passed.

```

Covers: JCS canonicalization, sign/verify, tamper detection, chain linking, chain break detection, JSONL export, sequence numbering, privacy (raw data not in receipt).

Files changed

File Lines What
`tools/lib/receipt_signer.py` 174 ReceiptChain class + create_receipt_logger
`tools/lib/test_receipt_signer.py` 119 10 tests

No existing files modified.

Context

Same receipt format used by Microsoft Agent Governance Toolkit and AWS Cedar for Agents for AI agent decision logging. This extends it to autonomous vehicle decisions. The format is standards-based (IETF draft) so receipts from any implementation verify with the same CLI.

@adeebshihadeh, happy to adjust scope or location if tools/lib isn't the right place.

Adds an optional receipt signer for cereal decision events. Each event
(controlsState, lateralPlan, etc.) produces an HMAC-signed, hash-chained
receipt. The chain is independently verifiable without trusting the device
operator.

Use case: post-incident investigation. If a comma device is involved in
an incident, the log files are mutable (stored on the device filesystem).
A signed receipt chain means that even if someone edits the logs, the
signatures break. Insurance companies, regulators, and legal teams can
verify the chain independently.

Files:
  tools/lib/receipt_signer.py      - ReceiptChain class + sign_event API
  tools/lib/test_receipt_signer.py - 10 tests (sign, verify, tamper, chain)

Properties:
  - Zero new dependencies (stdlib only: hashlib, hmac, json)
  - Privacy-preserving: receipts store event_hash, not raw sensor data
  - Hash-chained: insertions/deletions/modifications all detectable
  - Offline verifiable: npx @veritasacta/verify (separate CLI)
  - Non-invasive: tools/lib only, no changes to selfdrive or cereal

Integration point (not in this PR):
  chain = create_receipt_logger(device_id=Params().get("DongleId"))
  sm.update() -> chain.sign_event("controlsState", sm[...].to_dict())

Receipt format follows IETF draft-farley-acta-signed-receipts.
Production upgrade path: Ed25519 via secure element (replaces HMAC).
@github-actions github-actions Bot added the tools label Apr 16, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

Process replay diff report

Replays driving segments through this PR and compares the behavior to master.
Please review any changes carefully to ensure they are expected.

✅ 0 changed, 66 passed, 0 errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants