Browse Source

doc: Add event graph security invariants

x 1 month ago
parent
commit
c431ed81b2
3 changed files with 136 additions and 1 deletions
  1. 1 0
      doc/src/SUMMARY.md
  2. 16 1
      doc/src/misc/darkirc/darkirc.md
  3. 119 0
      doc/src/misc/event_graph/security_invariants.md

+ 1 - 0
doc/src/SUMMARY.md

@@ -142,6 +142,7 @@
 - [tau](misc/tau.md)
 - [event_graph](misc/event_graph/event_graph.md)
   - [Network Protocol](misc/event_graph/network_protocol.md)
+  - [Security Invariants](misc/event_graph/security_invariants.md)
   - [Recovery](misc/event_graph/recovery.md)
 - [dnetview](misc/dnetview.md)
 - [Zero2darkfi](zero2darkfi/zero2darkfi.md)

+ 16 - 1
doc/src/misc/darkirc/darkirc.md

@@ -359,10 +359,25 @@ Note that your nick is temporary. If you want to claim a nick, you will need to
 
 ## Troubleshooting
 
-If you encounter connectivity issues refer to 
+If you encounter connectivity issues refer to
 [Network troubleshooting](../network-troubleshooting.md)
 for further troubleshooting resources.
 
+## Operator Security Notes
+
+DarkIRC's datastore contains both public event graph state and local account
+secrets. Back up the whole `--datastore` directory before recovery work, and
+export important local accounts with `NickServ INFO <account_name>` before
+discarding a store. A clean event graph resync can recover public DAG state from
+peers, but it cannot recover nullifiers and trapdoors that were only stored
+locally.
+
+Do not edit RLN counters or static DAG state by hand. DarkIRC persists the next
+RLN `message_id` before proof creation so a crash cannot roll the counter back
+and self-slash the account on restart. The static DAG is also the source of
+truth for network registrations and slashes. For the full set of invariants, see
+[Event Graph Security Invariants](../event_graph/security_invariants.md).
+
 ## Hosting Instances
 
 The main thing is that all participating nodes must have a separate clean config

+ 119 - 0
doc/src/misc/event_graph/security_invariants.md

@@ -0,0 +1,119 @@
+# Event Graph Security Invariants
+
+This page records the invariants that applications must preserve when they
+integrate `EventGraph`. It is written for developers and operators reviewing
+changes around sync, RLN admission, and DarkIRC account handling.
+
+## Event admission
+
+Non-genesis rotating DAG events are not just event bodies. They are the tuple:
+
+- header and content
+- a stored body in the rotating DAG tree
+- an aligned RLN signal blob in `dag-blobs`
+
+The safe public insertion path is `insert_signal_with_blob()` for local events
+and `dag_insert_with_blobs()` for sync or peer-supplied events. Do not insert a
+non-genesis body with `dag_insert()` unless the caller is a test or another
+explicitly proofless path. A body without its blob cannot be served honestly to
+late joiners because peers must re-verify the signal proof during sync.
+
+Rotating event admission has two parent-closure checks:
+
+1. Before RLN verification, every non-genesis candidate must have parent bodies
+   already committed locally or accepted earlier in the same batch.
+2. Before the final body write, the same condition is checked again under the
+   DAG write lock.
+
+These checks are separate on purpose. Header sync can learn structure before
+body sync, but body admission must never create a body DAG whose parent body is
+missing.
+
+## Static DAG admission
+
+The static DAG stores RLN registrations and slashes. It is authoritative for
+identity-tree state and for the historical-root tables. Static events must be
+committed through `commit_verified_static_event()` after `rln_verify_static_event()`
+accepts the RLN node and blob.
+
+Static events also require parent closure. During static sync, a node must not
+commit a static event until the parent chain is known locally or accepted earlier
+in the same sync batch. This matters because historical roots are indexed by
+canonical static-event order, using `(layer, event_id)`, and every accepted
+static event creates a historical-root entry even if the SMT mutation is a
+soft no-op.
+
+Static event blobs are durable consensus evidence:
+
+- pregenerated registrations may use the deterministic `GENESIS_BLOB_GUARD`
+- slash blobs are not reconstructible
+- future staked-registration blobs are not reconstructible
+
+Operators must not fabricate missing static blobs. If `static-dag-blobs` loses a
+slash or future staked-registration blob, the node must restore from backup or
+resync from peers that still have the original blob.
+
+## RLN roots
+
+A live RLN signal may verify against the current identity-tree root. A
+historical signal may verify against a historical root only if that root was
+valid at the signal timestamp, within the event time-drift window.
+
+This distinction prevents stale-root replay after slashing. Recent roots kept in
+memory are not enough by themselves: any non-current root must pass the
+historical timestamp-window lookup in `is_root_valid_at()`.
+
+## Slashing
+
+Slash evidence is static DAG state. A valid slash removes the commitment from
+the identity tree, records a new historical root, and keeps the slash visible to
+future syncers. A slashed identity must not be allowed to re-register through the
+pregenerated path or the future staked path.
+
+Slash replay is a soft no-op for the identity tree, but it must not be treated
+as a fresh registration or a way to resurrect an identity. Duplicate or
+repackaged slash events still participate in static-DAG ordering only after
+they pass verification. Invalid slash blobs are rejected and must not mutate
+identity state or historical-root indexes.
+
+## Range sync
+
+`RangeReq` is the lazy body-fetch API. It exists so clients can sync headers
+first, render the newest messages, and then request older bodies while the user
+scrolls backward.
+
+The request is scoped to one rotating DAG and uses an exclusive `(timestamp,
+event_id)` cursor. The responder serves events and aligned blobs from its local
+time index, up to `MAX_RANGE_PAGE_SIZE`. Callers must treat every returned event
+as untrusted: the same `dag_insert_with_blobs()` body, parent, and RLN checks
+still apply.
+
+Range sync must not become a cross-DAG disclosure oracle. Keep requests scoped
+to a DAG name, keep cursors exclusive, and keep page sizes bounded. A node that
+is not synced does not serve range pages.
+
+## P2P limits
+
+Event graph protocol messages have item-count limits for expensive vectors:
+`EventReq`, `EventRep`, `HeaderReq`, `HeaderRep`, `TipRep`, and `RangeReq` /
+`RangeRep`. These limits are part of the DoS boundary and should not be raised
+without a matching review of memory use and verification cost.
+
+All event graph messages also pass through the network metering layer. Dedicated
+per-message byte caps for event graph payloads are still future hardening work;
+do not assume count limits alone bound encoded byte size for arbitrary future
+payload fields.
+
+## DarkIRC RLN counters
+
+DarkIRC stores local RLN identities under `darkirc_account_*` and mirrors the
+active identity under `darkirc_account_default`. Before creating a signal proof,
+DarkIRC reserves the next per-epoch `message_id` with
+`IrcServer::reserve_rln_message_id()`. The reservation is persisted before proof
+creation.
+
+This write order can burn a message slot if the process crashes after
+reservation, but it prevents a restart from reusing a message ID and
+self-slashing the identity. Do not move proof creation before counter
+persistence. Do not copy `darkirc_account_default` without the matching account
+tree unless the operator understands they are copying counter state and secrets.