spec.md 11 KB

Purpose

Defines anonymous-communication-friendly, client-enforced moderation for darkirc public channels: channel registration and owner-signed action chains in the static DAG, a hardcoded policy model with owner-set defaults and local user overrides, admin hide actions in the rotating DAG, and the ChanServ command interface used by channel owners and admins.

ADDED Requirements

Requirement: Public channel registration

A user SHALL be able to register a public # channel by publishing a registration event to the static DAG that names the channel and embeds an owner public key. Registration is first-come-first-served: when two valid registrations for the same name exist, every node MUST resolve the same winner using deterministic canonical ordering of the static DAG. Only public channels are supported in this version; channels requiring decryption (saltbox channels and direct messages) are out of scope.

Scenario: Register a channel

  • WHEN a user publishes a signed registration for a channel that has no valid registration
  • THEN every synced node resolves that user's owner key as the channel owner

Scenario: Registration race resolves identically everywhere

  • WHEN two registrations for the same channel name are published before either node sees the other
  • THEN all nodes that eventually hold both events pick the same registration as authoritative

Requirement: Owner action chain

Ownership-changing and policy-defining actions SHALL form a per-channel chain: each action names the previous action's static event id and is signed by the current owner key. Resolution MUST yield exactly one authoritative chain per channel: the longest chain of correctly linked, correctly signed actions starting from the winning registration, with deterministic tie-breaking. A transfer of ownership makes subsequent actions valid only under the new key.

Scenario: Transfer changes signing authority

  • WHEN an owner publishes a transfer to a new key and the new key later signs a policy update
  • THEN all nodes accept the policy update and reject any further action signed by the old key

Scenario: Invalid signature cannot extend the chain

  • WHEN an action is signed by a key that is not the current owner
  • THEN the action is ignored during resolution and the previous chain tip remains authoritative

Scenario: Competing chains resolve deterministically

  • WHEN two valid chains exist for one channel (e.g. after key compromise)
  • THEN every node selects the same chain by longest-valid-chain rule with canonical ordering as tie-break

Requirement: Hardcoded policy enum and default policy list

Policies SHALL be identified by a u8 enum from a hardcoded, shared registry (initially: posting allow-list, admin hide set, regex filter). The owner SHALL be able to publish, as a chain action, the channel's default policy list: for each policy, its id, opaque parameters, and a default enabled flag. The serialized format MUST allow future policy ids and parameter shapes without changing stored history.

Scenario: Publish a default policy list

  • WHEN an owner publishes a policy list naming the admin hide policy with a set of admin public keys, enabled by default
  • THEN synced nodes resolving the channel expose that policy with those parameters as the channel default

Scenario: Unknown policy id in a list

  • WHEN a resolved policy list contains an id the client does not know
  • THEN the client ignores that policy entry without failing resolution of the rest of the list

Requirement: Admin hide actions in the rotating DAG

A key named in an enabled admin hide policy's parameters SHALL be able to publish signed hide or unhide actions as rotating-DAG events referencing the event id of a target message. Clients MUST mark matching messages as hidden rather than removing them from storage; hidden state MUST be reversible in the UI (e.g. reveal control). Resolution MUST apply the last action per target in canonical rotating-DAG order across the retained window. Hide actions expire with the rotation window exactly as messages do; no permanent record of a hide is created.

Scenario: Hide is applied on other nodes

  • WHEN an admin publishes a hide action for a message in a channel a user has joined
  • THEN the user's client marks that message hidden and indicates that hidden messages exist

Scenario: Unhide reverses a hide

  • WHEN a later action by an authorized key unhides the same target
  • THEN the message is shown again for clients applying policy

Scenario: Unauthorized hide ignored

  • WHEN a hide action is signed by a key not in the channel's admin hide set, or the admin hide policy is disabled
  • THEN clients ignore the action

Requirement: Posting allow-list policy

When the posting allow-list policy is enabled for a channel, clients SHALL render only messages that carry a valid signature by a key in the policy's parameter set. Compliant senders in such channels attach their signer public key and signature to the message. Enforcement is client-side: unsigned messages still propagate on the network but are hidden for clients applying the policy.

Scenario: Signed message renders

  • WHEN a message in an allow-listed channel carries a valid signature from an allowed key
  • THEN clients applying the policy render it

Scenario: Unsigned message hidden

  • WHEN a message in an allow-listed channel lacks a signature or is signed by a key outside the set
  • THEN clients applying the policy hide it

Requirement: Regex filter policy

When the regex filter policy is enabled, clients SHALL hide messages whose decoded privmsg matches the filter's parameter rules. Rules are regular expressions evaluated against the privmsg, and MAY match the nick and/or the message content. Filter parameters are opaque to the wire format; invalid or non-compilable rules MUST be ignored during resolution without failing evaluation of the remaining rules.

Scenario: Matching message hidden

  • WHEN a message whose nick or content matches an enabled filter's regex rules arrives in a joined channel
  • THEN clients applying the policy hide it

Scenario: Invalid rule ignored

  • WHEN a filter's parameters contain a rule that fails to compile
  • THEN clients skip that rule and evaluate the remaining rules

Scenario: Encrypted messages filtered after decryption

  • WHEN an encrypted message matching the rules is decrypted locally
  • THEN the filter applies to the decoded plaintext identically to plaintext-channel messages

Requirement: Local policy overrides

A user SHALL be able to override the enabled flag of any policy in a channel's default list locally, per channel, without publishing anything. The initial app UI exposes enable/disable of provided policies only; adding user-defined policies is out of scope for this version.

Scenario: Override a default

  • WHEN a channel's policy is enabled by owner default and the user disables it locally
  • THEN that user's client applies the policy as disabled while other users remain unaffected

Scenario: Policy overlay from the channel label

  • WHEN the user taps the channel-name label shown at the top of a channel's chat screen
  • THEN the app opens an overlay for that channel listing the owner's current default policies with their default states, and toggling a policy in the overlay changes the user's local override and re-filters the channel view accordingly

Requirement: Owner-signed pins with encrypted snapshots

A channel owner SHALL be able to pin a message by publishing a chain action that embeds a snapshot of the message content and references its event id. For a channel with a shared key, the snapshot MUST be encrypted under that channel key so the static DAG never carries its plaintext. Pins MUST remain renderable after the original message has rotated out of the window.

Scenario: Pin outlives rotation

  • WHEN a pinned message's DAG window has expired
  • THEN clients can still render the pinned snapshot from the static DAG

Scenario: Encrypted channel pin carries no plaintext

  • WHEN an owner pins a message in a saltbox-encrypted channel
  • THEN the static-DAG event contains only ciphertext decryptable by channel members

Requirement: ChanServ command interface

The irc2 stack SHALL provide a ChanServ service addressed by IRC private message, mirroring the existing NickServ pattern, with commands for registration, info, ownership transfer, policy management, pinning, and hiding. Policy commands SHALL address policies by their registry name (e.g. FILTER), parsed to the hardcoded policy id; unknown names are rejected. Owner and admin actions MUST be authenticated by verifying the signing key configured locally; the service MUST refuse actions for which no matching key is configured.

Scenario: Register via ChanServ

  • WHEN a user with an owner key configured sends the registration command for an unregistered channel
  • THEN the registration event is signed with that key and published to the static DAG

Scenario: Policy addressed by name

  • WHEN an owner issues a policy command using a policy's name (e.g. FILTER) and valid parameters
  • THEN the command applies to that policy's entry in the channel's default list

Scenario: Unknown policy name rejected

  • WHEN a policy command names a policy the client does not know
  • THEN ChanServ replies with an error and the policy list is unchanged

Scenario: Action without key refused

  • WHEN a user issues an owner or admin command without the corresponding key configured
  • THEN ChanServ replies with an error and publishes nothing

Requirement: Signing keys are node-local secrets

Owner and admin schnorr keypairs SHALL be provisioned in node configuration (desktop TOML config; app settings store) and MUST NOT be transmitted or published; only public keys appear in DAG events. Signatures apply to action and message content only.

Scenario: Secret never leaves the node

  • WHEN any owner or admin action is published
  • THEN the corresponding event contains only the public key and signature

Requirement: RLN semantics unchanged

Moderation MUST NOT alter RLN rate-limiting or anonymity: hide actions and signed messages on RLN-enabled networks are ordinary rotating events subject to the same proof requirements as chat messages, and no policy data links an RLN identity to a signing key.

Scenario: Hide action is rate-limited

  • WHEN an admin publishes a hide action on an RLN-enabled network
  • THEN the event is admitted under the same RLN proof rules as any other rotating event