sirius-press/docs/accounts.md
Silent Mode 5465b65756 feat(sirius-press): a WordPress where the account is a key, not a mailbox
WordPress makes two assumptions this project cannot accept: that identity
comes from an email address, and that a site lives at one server. Both are
things somebody else can take away — a mailbox is rented from a provider who
can close it or be compelled to open it, and a server is one seizure from
being gone.

Sirius Press replaces the first and hedges the second.

Signing in means signing a challenge with the key that controls a CashAddress.
The address is recovered from the signature, so nothing is typed but the
signature itself, and the result is an ordinary WordPress session cookie —
roles, capabilities, nonces and the REST API never learn the login was
different. Three ways to produce one: a wallet the browser already exposes, a
phrase used once in the page and wiped, or a signature pasted in from any
BIP-137 wallet, which needs no JavaScript and lets the key stay on a machine
that never touches the web.

There is no password reset, and the recovery page says so plainly rather than
offering a form that cannot work. A reset mechanism is by construction a way
to take an account from its owner, and it is always easier to attack than the
cryptography it bypasses.

Publishing a post also exports it as static HTML to the name's storage on Sia,
signed by the key that owns the name, so the site keeps answering when the
server does not.

Email as a feature is untouched. wp_mail() still works, SMTP still sends, and
contact forms still deliver to addresses real people typed. Only mail to the
site's own unroutable placeholder addresses is diverted to an in-app inbox.
The objection was to email as identity, not to email.

Core is pinned and patched rather than vendored. WordPress 7.1.1 is 149 MB and
5,008 files; the fork's entire core diff is 75 lines in wp-admin/install.php.
Carrying the former to express the latter would bury the patch where nobody
reviews it and make every clone of the monorepo pay for it. Upstream releases
still merge through tools/update-wordpress.sh, which reapplies the series and
says exactly which hunk needs a human.

The cryptography is implemented twice — PHP on the server, JavaScript in the
page — because the server must verify and the browser must sign. Both are
pinned against libauth, the library the Sirius portal wallet and the BNS
gateway already use, so a disagreement of one byte fails the test suite rather
than presenting as a rejected login at three in the morning.

132 checks, no framework, about a second.
2026-09-21 01:39:38 +02:00

6.9 KiB

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:

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:

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:

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