Charge a few seconds of arithmetic before an account exists #78

Merged
paulhitt merged 2 commits from feat/signup-proof-of-work into main 2026-09-03 00:07:55 +00:00
paulhitt commented 2026-09-02 01:25:05 +00:00 (Migrated from gitlab.com)

Closes #39. A CAPTCHA hands a third party every visitor's connection — a strange thing to
do in a product whose argument is that it collects almost nothing. This collects nothing at
all: no personal data, no third party, no crosswalk photos. Two seconds for a person, real
money for a farm of registrations.

OFF by default, and that is not timidity. The shipped Android client knows nothing
about challenges; requiring one by default would stop every existing app creating an
account the moment a relay updated, with no error it could explain. FN_SIGNUP_POW_BITS
turns it on when a deployment's clients can answer.

The challenge route answers even when the feature is off, reporting zero bits. A 404
would tell an anonymous caller how the relay is configured, and a client that asks "how
much work?" and hears "none" needs no special case for older servers.

Stateless. The challenge carries its own expiry and a MAC over both, so nothing is
stored and a restart does not invalidate work in progress. The cost: a challenge is
replayable until it expires — bounded by a 10-minute window and the rate limits already in
front of sign-up. A trade, not an oversight, and smaller than a table of live challenges to
keep and sweep.

Capped at 24 bits however configured. A number set absurdly high does not read as
spam protection to the person it hits — it reads as an app that hangs on the sign-up
button. Verification caps too, or a client could be asked for work the server would refuse.

Counted in bits, not bytes — bytes move in steps of 256×, giving no way to ask for
two seconds rather than half a second or two minutes.

One message for all four refusals (forged, stale, malformed, unsolved): telling somebody
which it was tells an attacker which knob to turn.

The MAC secret is derived from the issuer key, not the key itself — one secret doing
two jobs is one compromise doing two.

Tests: 11 new, proven able to fail — covering only the salt with the MAC, so a stale
challenge could be extended by editing its expiry, turns 12 red. 481 in 41 suites pass.

ℹNo public-page change: with the feature off, nothing user-visible differs. Client
support (fetch challenge, solve, send) is a separate MR per platform, and should land
before any deployment turns this on.

Closes #39. A CAPTCHA hands a third party every visitor's connection — a strange thing to do in a product whose argument is that it collects almost nothing. This collects nothing at all: no personal data, no third party, no crosswalk photos. Two seconds for a person, real money for a farm of registrations. **OFF by default, and that is not timidity.** The shipped Android client knows nothing about challenges; requiring one by default would stop every existing app creating an account the moment a relay updated, with no error it could explain. `FN_SIGNUP_POW_BITS` turns it on when a deployment's clients can answer. **The challenge route answers even when the feature is off**, reporting zero bits. A 404 would tell an anonymous caller how the relay is configured, and a client that asks "how much work?" and hears "none" needs no special case for older servers. **Stateless.** The challenge carries its own expiry and a MAC over both, so nothing is stored and a restart does not invalidate work in progress. The cost: a challenge is replayable until it expires — bounded by a 10-minute window and the rate limits already in front of sign-up. A trade, not an oversight, and smaller than a table of live challenges to keep and sweep. **Capped at 24 bits however configured.** A number set absurdly high does not read as spam protection to the person it hits — it reads as an app that hangs on the sign-up button. Verification caps too, or a client could be asked for work the server would refuse. **Counted in bits, not bytes** — bytes move in steps of 256×, giving no way to ask for two seconds rather than half a second or two minutes. One message for all four refusals (forged, stale, malformed, unsolved): telling somebody which it was tells an attacker which knob to turn. The MAC secret is **derived** from the issuer key, not the key itself — one secret doing two jobs is one compromise doing two. **Tests:** 11 new, proven able to fail — covering only the salt with the MAC, so a stale challenge could be extended by editing its expiry, turns 12 red. 481 in 41 suites pass. ℹNo public-page change: with the feature off, nothing user-visible differs. Client support (fetch challenge, solve, send) is a separate MR per platform, and should land before any deployment turns this on.
paulhitt commented 2026-09-02 01:59:08 +00:00 (Migrated from gitlab.com)

added 1 commit

  • 3181107e - The sign-up challenge is reachable from the app's own namespace

Compare with previous version

added 1 commit <ul><li>3181107e - The sign-up challenge is reachable from the app&#39;s own namespace</li></ul> [Compare with previous version](/paulhitt/flamenet-relay/-/merge_requests/15/diffs?diff_id=1998392950&start_sha=5a07719046b319859726537b052a837a6cebc185)
paulhitt commented 2026-09-02 02:41:18 +00:00 (Migrated from gitlab.com)

added 1 commit

  • 257ec212 - Document FN_SIGNUP_POW_BITS beside the other sign-up limits

Compare with previous version

added 1 commit <ul><li>257ec212 - Document FN_SIGNUP_POW_BITS beside the other sign-up limits</li></ul> [Compare with previous version](/paulhitt/flamenet-relay/-/merge_requests/15/diffs?diff_id=1998433197&start_sha=3181107ec37f8c2ee30c08d782222b34b9a12f8a)
paulhitt commented 2026-09-02 14:56:42 +00:00 (Migrated from gitlab.com)

mentioned in merge request !25

mentioned in merge request !25
paulhitt commented 2026-09-03 00:06:54 +00:00 (Migrated from gitlab.com)

added 26 commits

  • 257ec212...08894ef8 - 24 commits from branch main
  • 7040dce5 - Charge a few seconds of arithmetic before an account exists
  • 48ce3b16 - The sign-up challenge is reachable from the app's own namespace

Compare with previous version

added 26 commits <ul><li>257ec212...08894ef8 - 24 commits from branch <code>main</code></li><li>7040dce5 - Charge a few seconds of arithmetic before an account exists</li><li>48ce3b16 - The sign-up challenge is reachable from the app&#39;s own namespace</li></ul> [Compare with previous version](/paulhitt/flamenet-relay/-/merge_requests/15/diffs?diff_id=2001146131&start_sha=257ec2127436a61a736cb33b8cc2b34ed2f25c8c)
paulhitt commented 2026-09-03 00:07:55 +00:00 (Migrated from gitlab.com)

mentioned in commit f1f2618bdc

mentioned in commit f1f2618bdcbbaca045f95ce86fffab293f1b15eb
paulhitt (Migrated from gitlab.com) merged commit f1f2618bdc into main 2026-09-03 00:07:55 +00:00
paulhitt commented 2026-09-09 18:59:19 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
Sign in to join this conversation.