feat: per-channel hashcash proof-of-work for PRIVMSG anti-spam
All checks were successful
check / check (push) Successful in 1m11s

Add per-channel hashcash requirement via MODE +H <bits>. When set,
PRIVMSG to the channel must include a valid hashcash stamp in the
meta.hashcash field bound to the channel name and message body hash.

Server validates stamp format, difficulty, date freshness, channel
binding, body hash binding, and proof-of-work. Spent stamps are
persisted to SQLite with 1-year TTL for replay prevention.

Stamp format: 1:bits:YYMMDD:channel:bodyhash:counter

Changes:
- Schema: add hashcash_bits column to channels, spent_hashcash table
- DB: queries for get/set channel hashcash bits, spent token CRUD
- Hashcash: ChannelValidator, BodyHash, StampHash, MintChannelStamp
- Handlers: validate hashcash on PRIVMSG, MODE +H/-H support
- Pass meta through fanOut chain to store in messages
- Prune spent hashcash tokens in cleanup loop (1-year TTL)
- Client: MintChannelHashcash helper for CLI
- Tests: 12 new channel_test.go + 10 new api_test.go integration tests
- README: document +H mode, stamp format, and usage
This commit is contained in:
user
2026-03-17 02:37:14 -07:00
committed by clawbot
parent e36bd99ef6
commit 68558ce5b5
10 changed files with 1451 additions and 42 deletions

View File

@@ -1,4 +1,4 @@
.PHONY: all build lint fmt fmt-check test check clean run debug docker hooks
.PHONY: all build lint fmt fmt-check test check clean run debug docker hooks ensure-web-dist
BINARY := neoircd
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
@@ -7,10 +7,21 @@ LDFLAGS := -X main.Version=$(VERSION) -X main.Buildarch=$(BUILDARCH)
all: check build
build:
# ensure-web-dist creates placeholder files so //go:embed dist/* in
# web/embed.go resolves without a full Node.js build. The real SPA is
# built by the web-builder Docker stage; these placeholders let
# "make test" and "make build" work outside Docker.
ensure-web-dist:
@if [ ! -d web/dist ]; then \
mkdir -p web/dist && \
touch web/dist/index.html web/dist/style.css web/dist/app.js && \
echo "==> Created placeholder web/dist/ for go:embed"; \
fi
build: ensure-web-dist
go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY) ./cmd/neoircd
lint:
lint: ensure-web-dist
golangci-lint run --config .golangci.yml ./...
fmt:
@@ -20,7 +31,7 @@ fmt:
fmt-check:
@test -z "$$(gofmt -l .)" || (echo "Files not formatted:" && gofmt -l . && exit 1)
test:
test: ensure-web-dist
go test -timeout 30s -v -race -cover ./...
# check runs all validation without making changes