Spend a hashcash stamp only when the request succeeds (closes #158)
check / check (push) Waiting to run

A session request refused with 400 or 409, or a +H channel message
refused for membership, a ban or +m, used to spend its stamp, so a
retry with the same stamp was refused as already used.

hashcash.Validator.Validate is split into Check, which spends
nothing, and Claim, which records the stamp; Release undoes a claim.
Session creation checks the stamp first, claims it just before
CreateSession and releases it if CreateSession fails.
SendChannelMessage checks the stamp after the membership, ban and +m
checks.

Model: opus-5-5
This commit is contained in:
2026-10-21 01:39:03 +00:00
parent e7e408c93e
commit 1436d4e409
8 changed files with 393 additions and 51 deletions
+9 -3
View File
@@ -1404,7 +1404,8 @@ Include the hashcash stamp in the `meta` field:
**Server validation:** The server checks that the stamp is well-formed, meets
the required difficulty, is bound to the correct channel and message body, has a
recent date, and has not been previously used. Spent stamps are cached for 1
year to prevent replay attacks.
year to prevent replay attacks. The stamp is checked after membership, bans and
`+m`, so a message refused for one of those does not spend it.
**Error responses:** If the channel requires hashcash and the stamp is missing,
invalid, or replayed, the server returns `ERR_CANNOTSENDTOCHAN (404)` with a
@@ -3084,6 +3085,10 @@ limits that punish shared networks.
- Stamp has not been used before (replay prevention; the spent stamps are
kept in memory, so a restart forgets them)
The stamp is spent only when the session is created. A request refused with
400 or 409 leaves it unspent, and the client can send the same stamp again
with another nick or username.
### Stamp Format
Standard hashcash format:
@@ -3156,12 +3161,13 @@ creating one session pays once and keeps their session.
### Why Hashcash and Not Rate Limits?
- **Little state to track**: No IP tables, no token buckets, no sliding windows.
The server verifies a single hash and remembers the stamps already spent.
The server hashes the stamp and remembers the stamps already spent.
- **Works through NATs and proxies**: Doesn't punish shared IPs (university
campuses, corporate networks, Tor exits). Every client computes their own
proof independently.
- **Cost falls on the requester**: The server's verification cost is constant
(one SHA-256 hash) regardless of difficulty. Only the client does more work.
regardless of difficulty: at most two SHA-256 hashes of the stamp per session
request, one to check it and one to spend it. Only the client does more work.
- **Fits the "no accounts" philosophy**: Proof-of-work is the cost of entry. No
registration, no email, no phone number, no CAPTCHA. Just compute.
- **Language-agnostic**: SHA-256 is available in every programming language. The