tools: receipt-signed decision logs for post-incident verification#37843
Open
tomjwxf wants to merge 3 commits intocommaai:masterfrom
Open
tools: receipt-signed decision logs for post-incident verification#37843tomjwxf wants to merge 3 commits intocommaai:masterfrom
tomjwxf wants to merge 3 commits intocommaai:masterfrom
Conversation
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).
Contributor
Process replay diff reportReplays driving segments through this PR and compares the behavior to master. ✅ 0 changed, 66 passed, 0 errors |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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:
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
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.