67460ea6b286a7c387ac89cb62dc2dcc34f5eff3
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| bf4d63bc4d |
feat: per-channel hashcash proof-of-work for PRIVMSG anti-spam (#79)
Some checks failed
check / check (push) Failing after 1m48s
closes #12 ## Summary Implements per-channel hashcash proof-of-work requirement for PRIVMSG as an anti-spam mechanism. Channel operators set a difficulty level via `MODE +H <bits>`, and clients must compute a proof-of-work stamp bound to the channel name and message body before sending. ## Changes ### Database - Added `hashcash_bits` column to `channels` table (default 0 = no requirement) - Added `spent_hashcash` table with `stamp_hash` unique key and `created_at` for TTL pruning - New queries: `GetChannelHashcashBits`, `SetChannelHashcashBits`, `RecordSpentHashcash`, `IsHashcashSpent`, `PruneSpentHashcash` ### Hashcash Validation (`internal/hashcash/channel.go`) - `ChannelValidator` type for per-channel stamp validation - `BodyHash()` computes hex-encoded SHA-256 of message body - `StampHash()` computes deterministic hash of stamp for spent-token key - `MintChannelStamp()` generates valid stamps (for clients) - Stamp format: `1:bits:YYMMDD:channel:bodyhash:counter` - Validates: version, difficulty, date freshness (48h), channel binding, body hash binding, proof-of-work ### Handler Changes (`internal/handlers/api.go`) - `validateChannelHashcash()` + `verifyChannelStamp()` — checks hashcash on PRIVMSG to protected channels - `extractHashcashFromMeta()` — parses hashcash stamp from meta JSON - `applyChannelMode()` / `setHashcashMode()` / `clearHashcashMode()` — MODE +H/-H support - `queryChannelMode()` — shows +nH in mode query when hashcash is set - Meta field now passed through the full dispatch chain (dispatchCommand → handlePrivmsg → handleChannelMsg → sendChannelMsg → fanOut → InsertMessage) - ISUPPORT updated: `CHANMODES=,H,,imnst` (H in type B = parameter when set) ### Replay Prevention - Spent stamps persisted to SQLite `spent_hashcash` table - 1-year TTL (per issue requirements) - Automatic pruning in cleanup loop ### Client Support (`internal/cli/api/hashcash.go`) - `MintChannelHashcash(bits, channel, body)` — computes stamps for channel messages ### Tests - **12 unit tests** in `internal/hashcash/channel_test.go`: happy path, wrong channel, wrong body hash, insufficient bits, zero bits skip, bad format, bad version, expired stamp, missing body hash, body hash determinism, stamp hash, mint+validate round-trip - **10 integration tests** in `internal/handlers/api_test.go`: set mode, query mode, clear mode, reject no stamp, accept valid stamp, reject replayed stamp, no requirement works, invalid bits range, missing bits arg ### README - Added `+H` to channel modes table - Added "Per-Channel Hashcash (Anti-Spam)" section with full documentation - Updated `meta` field description to mention hashcash ## How It Works 1. Channel operator sets requirement: `MODE #general +H 20` (20 bits) 2. Client mints stamp: computes SHA-256 hashcash bound to `#general` + SHA-256(body) 3. Client sends PRIVMSG with `meta.hashcash` field containing the stamp 4. Server validates stamp, checks spent cache, records as spent, relays message 5. Replayed stamps are rejected for 1 year ## Docker Build `docker build .` passes clean (formatting, linting, all tests). Co-authored-by: user <user@Mac.lan guest wan> Co-authored-by: Jeffrey Paul <sneak@noreply.example.org> Reviewed-on: #79 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org> |
|||
| 75cecd9803 |
feat: implement hashcash proof-of-work for session creation (#63)
All checks were successful
check / check (push) Successful in 1m2s
## Summary Implement SHA-256-based hashcash proof-of-work for `POST /session` to prevent abuse via rapid session creation. closes #11 ## What Changed ### Server - **New `internal/hashcash` package**: Validates hashcash stamps (format, difficulty bits, date/expiry, resource, replay prevention via in-memory spent set with TTL pruning) - **Config**: `NEOIRC_HASHCASH_BITS` env var (default 20, set to 0 to disable) - **`GET /api/v1/server`**: Now includes `hashcash_bits` field when > 0 - **`POST /api/v1/session`**: Validates `X-Hashcash` header when hashcash is enabled; returns HTTP 402 for missing/invalid stamps ### Clients - **Web SPA**: Fetches `hashcash_bits` from `/server`, computes stamp using Web Crypto API (`crypto.subtle.digest`) with batched parallelism (1024 hashes/batch), shows "Computing proof-of-work..." feedback - **CLI (`neoirc-cli`)**: `CreateSession()` auto-fetches server info and computes a valid hashcash stamp when required; new `MintHashcash()` function in the API package ### Documentation - README updated with full hashcash documentation: stamp format, computing stamps, configuration, difficulty table - Server info and session creation API docs updated with hashcash fields/headers - Roadmap updated (hashcash marked as implemented) ## Stamp Format Standard hashcash: `1:bits:YYMMDD:resource::counter` The SHA-256 hash of the entire stamp string must have at least `bits` leading zero bits. ## Validation Rules - Version must be `1` - Claimed bits ≥ required bits - Resource must match server name - Date within 48 hours (not expired, not too far in future) - SHA-256 hash has required leading zero bits - Stamp not previously used (replay prevention) ## Testing - All existing tests pass (hashcash disabled in test config with `HashcashBits: 0`) - `docker build .` passes (lint + test + build) <!-- session: agent:sdlc-manager:subagent:f98d712e-8a40-4013-b3d7-588cbff670f4 --> Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de> Co-authored-by: clawbot <clawbot@noreply.eeqj.de> Co-authored-by: user <user@Mac.lan guest wan> Co-authored-by: Jeffrey Paul <sneak@noreply.example.org> Reviewed-on: #63 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org> |