# 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.