179 lines
6.9 KiB
Markdown
179 lines
6.9 KiB
Markdown
|
|
# Accounts, sign-in and recovery
|
||
|
|
|
||
|
|
## What an account is
|
||
|
|
|
||
|
|
A CashAddress, and a WordPress user row attached to it.
|
||
|
|
|
||
|
|
The address lives in user meta, not in a new column on `wp_users`. That is a
|
||
|
|
deliberate departure from the original design sketch, which proposed replacing
|
||
|
|
`user_email` with a `wallet_addr` column: a schema change to a core table makes
|
||
|
|
every future upstream merge and every `dbDelta()` run a negotiation, and it
|
||
|
|
buys nothing here. Meta lookups are indexed, and a plugin reading
|
||
|
|
`$user->user_email` keeps getting a string instead of crashing.
|
||
|
|
|
||
|
|
Everything else about the user is ordinary WordPress. Roles, capabilities,
|
||
|
|
nonces, sessions, the REST API — none of it knows the login was different.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Signing in
|
||
|
|
|
||
|
|
The login page issues a challenge: a few lines of readable text containing the
|
||
|
|
site, the purpose, a nonce and a timestamp. You sign it; the server recovers
|
||
|
|
the public key from the signature, derives the address, and finds the account.
|
||
|
|
|
||
|
|
You never type your address. It comes out of the signature.
|
||
|
|
|
||
|
|
### The three ways to sign
|
||
|
|
|
||
|
|
**A wallet the browser already has.** Theseus exposes `window.bitcoincash` on
|
||
|
|
`.x` origins. The button appears when it is there. The key stays in the
|
||
|
|
browser's wallet and never touches the page.
|
||
|
|
|
||
|
|
**A phrase typed into the form.** Derived in the page, used once, wiped. The
|
||
|
|
phrase is never transmitted — only the signature is. This is the path most
|
||
|
|
people will use, and it is the one worth being clear-eyed about: you are typing
|
||
|
|
a recovery phrase into a web page. It is the same trust you place in the Sirius
|
||
|
|
portal, and less than you place in a browser extension, but it is not nothing.
|
||
|
|
|
||
|
|
**A signature made somewhere else.** The challenge is a BIP-137 message — the
|
||
|
|
format Electron Cash's "Sign message" box has produced for a decade. Copy the
|
||
|
|
text out, sign it on a machine that never touches this site, paste the result
|
||
|
|
back. **This path needs no JavaScript at all**, which is why the paste box is
|
||
|
|
in the markup from the start and the buttons are revealed by script rather than
|
||
|
|
the other way round.
|
||
|
|
|
||
|
|
### The challenge
|
||
|
|
|
||
|
|
```
|
||
|
|
Sign in to Example Site
|
||
|
|
|
||
|
|
Site: https://example.bch/
|
||
|
|
Purpose: sign in
|
||
|
|
Nonce: 1758412800000.3f9ab2c1d4e5f607.9a1b…
|
||
|
|
Issued: 2026-09-21T10:00:00Z
|
||
|
|
|
||
|
|
Signing this proves you control this wallet. It moves no coins.
|
||
|
|
```
|
||
|
|
|
||
|
|
Written to be readable in a wallet's approval dialog, because that dialog is
|
||
|
|
the only place a user gets to check what they are agreeing to. Anything a
|
||
|
|
wallet renders as a wall of hex is a security control the user cannot exercise.
|
||
|
|
|
||
|
|
Issuing one writes nothing: the nonce carries its own timestamp and an HMAC
|
||
|
|
under the site's salts, so a forged nonce fails arithmetic rather than a
|
||
|
|
database lookup. Only a *successful* verification writes — a short-lived marker
|
||
|
|
that burns the nonce so the signature cannot be replayed.
|
||
|
|
|
||
|
|
The purpose is inside the signed bytes, which is what stops a login signature
|
||
|
|
being replayed to create an account.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Registration
|
||
|
|
|
||
|
|
One step. Sign the challenge, optionally pick a username, done — there is no
|
||
|
|
confirmation link because the signature *is* the confirmation.
|
||
|
|
|
||
|
|
No pending state, no expiring link, no way for a typo to strand an account.
|
||
|
|
|
||
|
|
Turn it on or off at **Sirius Press → Sign-in**.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Recovery, and the absence of it
|
||
|
|
|
||
|
|
**If you lose your phrase, the account is gone.** Nobody can reset it, and
|
||
|
|
nothing in the software is trying to.
|
||
|
|
|
||
|
|
This is not an oversight to be worked around later. A reset mechanism is, by
|
||
|
|
construction, a way to take an account from its owner — and every such
|
||
|
|
mechanism ends up being the thing attacked, because it is easier than the
|
||
|
|
cryptography it bypasses. Password reset by email is exactly that: a promise
|
||
|
|
that whoever controls a mailbox controls the account.
|
||
|
|
|
||
|
|
The fork's "lost password" page says this to your users in those terms, and
|
||
|
|
offers the two things that genuinely help: if they still have a phrase, they
|
||
|
|
are not locked out at all and should just sign in; and if they have several
|
||
|
|
phrases and do not know which is which, the page derives the address from a
|
||
|
|
phrase in the browser so they can check.
|
||
|
|
|
||
|
|
### The one route back
|
||
|
|
|
||
|
|
An administrator can point an account at a different address from **Users →
|
||
|
|
Edit**.
|
||
|
|
|
||
|
|
That is intentionally a human decision about a person somebody recognises,
|
||
|
|
rather than a capability an attacker triggers. It also means an administrator
|
||
|
|
can take any account on the site, which is true of WordPress administrators
|
||
|
|
generally and is worth knowing rather than discovering.
|
||
|
|
|
||
|
|
If nobody has an administrator account any more, nothing in the software can
|
||
|
|
help — but the database can. Attach a wallet to user 1 directly:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
INSERT INTO wp_usermeta (user_id, meta_key, meta_value)
|
||
|
|
VALUES (1, 'sirius_wallet_address', 'bitcoincash:qq…')
|
||
|
|
ON DUPLICATE KEY UPDATE meta_value = VALUES(meta_value);
|
||
|
|
```
|
||
|
|
|
||
|
|
Or with WP-CLI, which handles the prefix for you:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
wp user meta update 1 sirius_wallet_address 'bitcoincash:qq…'
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Passwords
|
||
|
|
|
||
|
|
Left on by default, and the reason is unglamorous: a fresh install has exactly
|
||
|
|
one account, created by the installer, with no wallet attached yet. Shipping
|
||
|
|
with passwords off would mean the first thing a new site does is lock out its
|
||
|
|
own administrator.
|
||
|
|
|
||
|
|
Turn them off at **Sirius Press → Sign-in** once every account that needs
|
||
|
|
access has a wallet. That screen refuses to do it until *your* account has one,
|
||
|
|
and lists every account that would be stranded.
|
||
|
|
|
||
|
|
With passwords off, core's password checks are removed from the `authenticate`
|
||
|
|
chain entirely rather than left to run and fail — a site that has turned
|
||
|
|
passwords off should not still have a password oracle on its login page.
|
||
|
|
|
||
|
|
Application passwords and WP-CLI are unaffected either way. They do not go
|
||
|
|
through the interactive login path.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Asking for a fresh signature
|
||
|
|
|
||
|
|
A capability check proves what a session is allowed to do. It cannot prove the
|
||
|
|
person holding the key is still at the keyboard. For something irreversible —
|
||
|
|
transferring a name, moving money, deleting a site — a plugin can demand a new
|
||
|
|
signature:
|
||
|
|
|
||
|
|
```php
|
||
|
|
$challenge = sirius_press_confirmation_challenge();
|
||
|
|
// Render $challenge['message'] for the user to sign, then POST
|
||
|
|
// { nonce, signature } to /wp-json/sirius-press/v1/confirm
|
||
|
|
```
|
||
|
|
|
||
|
|
It returns 403 if the signature belongs to a different wallet than the one
|
||
|
|
signed in, so a caller that forgets to check the response body still fails
|
||
|
|
closed.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## The REST surface
|
||
|
|
|
||
|
|
| Endpoint | Method | Auth | Does |
|
||
|
|
|---|---|---|---|
|
||
|
|
| `/sirius-press/v1/challenge` | GET | none | Hands out something to sign. `?purpose=login\|register\|link\|confirm` |
|
||
|
|
| `/sirius-press/v1/login` | POST | none | `{nonce, signature}` → sets a session cookie |
|
||
|
|
| `/sirius-press/v1/register` | POST | none | `{nonce, signature, user_login?}` → creates an account |
|
||
|
|
| `/sirius-press/v1/confirm` | POST | logged in | `{nonce, signature}` → re-proves the current user's key |
|
||
|
|
|
||
|
|
Everything that does curve maths is rate limited per client, because signature
|
||
|
|
recovery is the most expensive thing a stranger can make the server do — a few
|
||
|
|
hundred milliseconds of CPU each on a BCMath host.
|