Append-only signed event log; persist Dingtian input pushes
Implement the core anti-fraud primitive: an append-only, hash-chained, signed event log (the schema + types predated this; the writer/signer are new). - EventLog (apps/server): serialized append, monotonic index, prevHash chain, signature; verifyChain() detects tamper/reorder/delete. No update/delete paths. - Signer abstraction (packages/shared) over the ATECC608 secure element, with a SoftwareSigner (HMAC, EVENT_SIGNING_KEY) shipped now since the chip is still open-question #6. Documented: software signer is tamper-evident but NOT unforgeable-by-owner. - Add ParkingEventType "input_received" for raw device inputs (not yet a vehicle_entry, which the entry flow will append later). - Read API: GET /api/events; integrity self-check: GET /api/events/verify (admin). Verified on hardware: shorting the Dingtian inputs produced signed, chained input_received events; verifyChain ok; direct DB tamper/delete detected. NOTE: the log captures host-originated actions only. Out-of-band relay actuation (sniffed relay_pw, string protocol, ip_watchdog) produces no event by design -- the control is reconciliation vs. an independent witness, which is not yet built. See wiki/concepts/append-only-event-chain.md.
This commit is contained in:
@@ -34,6 +34,10 @@ export interface ParkingEvent {
|
||||
}
|
||||
|
||||
export type ParkingEventType =
|
||||
// A raw device input (e.g. a Dingtian button press) was received and recorded.
|
||||
// NOT a confirmed entry — the richer `vehicle_entry` is appended later by the
|
||||
// entry flow once a ticket prints and the barrier is commanded.
|
||||
| "input_received"
|
||||
| "vehicle_entry"
|
||||
| "vehicle_exit"
|
||||
| "void"
|
||||
@@ -48,3 +52,26 @@ export const ROLES: readonly Role[] = [
|
||||
"cashier",
|
||||
"readonly",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Signs the canonical bytes of an event for the append-only chain. This is the
|
||||
* abstraction over the [[atecc608]] secure element: the real, non-extractable
|
||||
* hardware key is ONE implementation. Whether the chip is wired is still
|
||||
* open-question #6, so the server ships a software signer in the meantime —
|
||||
* same interface, swappable with no business-logic change (the device-adapter
|
||||
* philosophy applied to signing). See wiki/concepts/append-only-event-chain.md.
|
||||
*
|
||||
* IMPORTANT: a software signer makes the chain self-consistent and detectably
|
||||
* tamper-evident, but NOT unforgeable by someone who owns the machine — only the
|
||||
* ATECC608 provides that. Don't conflate the two.
|
||||
*/
|
||||
export interface Signer {
|
||||
/** Stable id of the signer/key (e.g. "sw-hmac-v1", "atecc608-slot0"). Stored
|
||||
* alongside events so verification knows which key to check against. */
|
||||
readonly keyId: string;
|
||||
/** Sign the canonical payload; returns a hex signature. */
|
||||
sign(payload: string): string;
|
||||
/** Verify a signature over the payload (software signers can; the ATECC608
|
||||
* verifies via its public key). */
|
||||
verify(payload: string, signature: string): boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user