01
Per-event, not batch
Every action (wrap / open / burn / message / file upload / copy-block / device-approval / 50+ types) creates one row in nd_operator_receipts with its own event_hash, Ed25519 signature, prev_receipt_id and chain_index. The hourly Merkle seal does NOT merge events, only their hashes.
source: lib/operator/types.ts · OperatorEventType
02
Verifiable hash chain
Each receipt commits to its predecessor via prev_receipt_id + chain_index. Modifying a single row breaks every receipt that follows. Same pattern as Certificate Transparency, applied to data-action events.
source: migration 057 · nd_operator_receipts schema
03
External Bitcoin anchor
Each hour, all event_hashes in the window roll into a sorted Merkle tree (order leakage neutralized), the root is Ed25519-signed and submitted to OpenTimestamps which anchors it to a Bitcoin block. Once confirmed, rewriting history requires a Bitcoin reorg.
source: migration 148 · nd_epoch_seals · ots_status
04
Customer-keyed (Sovereign tier)
On Sovereign tier the customer generates an Ed25519 keypair inside their local SQLCipher vault (Argon2id-derived). Every heartbeat / metrics / entry-exit Border Control proof is signed with that key. Server verifies on every receive. NoData physically cannot forge, we do not hold the key.
source: apps/desktop/src-tauri/src/border.rs + license.rs
05
Crypto-shred on burn
When you burn, we don't soft-flag the row. We destroy the content_key. A full DB backup cannot reconstruct the file. Where competitors offer 'retention deletion', we offer mathematical unrecoverability.
source: OperatorEventType *_burned · content_key destruction