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
< ? php
/**
* The one auth decision a site owner has to make .
*
* Passwords stay on by default , and that is not timidity — 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 .
*
* The screen therefore refuses to turn passwords off until the person doing
* it has a wallet on their own account . That check is the difference between
* a setting and a trapdoor .
*
* @ package SiriusPress
*/
defined ( 'ABSPATH' ) || exit ;
final class SPA_Settings {
const PAGE = 'sirius-press-auth' ;
public static function hooks () {
add_action ( 'admin_menu' , array ( __CLASS__ , 'menu' ), 20 );
}
public static function menu () {
add_submenu_page (
'sirius-press' ,
__ ( 'Sign-in' , 'sirius-press' ),
__ ( 'Sign-in' , 'sirius-press' ),
'manage_options' ,
self :: PAGE ,
array ( __CLASS__ , 'render' )
);
}
public static function render () {
if ( ! current_user_can ( 'manage_options' ) ) {
wp_die ( esc_html__ ( 'You are not allowed to configure this site.' , 'sirius-press' ) );
}
$errors = array ();
$notes = array ();
if ( isset ( $_POST [ 'sirius_auth_settings' ] ) && check_admin_referer ( 'sirius_auth_settings' ) ) {
$want_passwords = ! empty ( $_POST [ 'sirius_allow_passwords' ] );
if ( ! $want_passwords && '' === sirius_press_address () ) {
$errors [] = __ ( 'Attach a wallet to your own account first. Turning passwords off right now would lock you out of this site with no way back in.' , 'sirius-press' );
} else {
update_option ( SPA_Login :: OPT_ALLOW_PASSWORDS , $want_passwords );
$notes [] = $want_passwords
? __ ( 'Password sign-in is on.' , 'sirius-press' )
: __ ( 'Password sign-in is off. Only wallet signatures are accepted from now on.' , 'sirius-press' );
}
update_option ( SP_Settings :: OPT_OPEN_REG , ! empty ( $_POST [ 'sirius_open_registration' ] ) );
2026-10-03 18:42:45 +02:00
$raw = isset ( $_POST [ 'sirius_login_slug' ] ) ? sanitize_text_field ( wp_unslash ( $_POST [ 'sirius_login_slug' ] ) ) : '' ;
$slug = SPA_Login_URL :: sanitize ( $raw );
$slug_issues = SPA_Login_URL :: problems ( $raw );
if ( $slug_issues ) {
$errors = array_merge ( $errors , $slug_issues );
} elseif ( $slug !== SPA_Login_URL :: slug () ) {
if ( '' === $slug ) {
delete_option ( SPA_Login_URL :: OPT_SLUG );
$notes [] = __ ( 'The login screen is back at wp-login.php.' , 'sirius-press' );
} else {
update_option ( SPA_Login_URL :: OPT_SLUG , $slug );
$notes [] = sprintf (
/* translators: %s: the new login URL. */
__ ( 'The login screen has moved to %s. Save it somewhere — wp-login.php now answers 404.' , 'sirius-press' ),
home_url ( '/' . $slug )
);
}
}
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
}
$stranded = self :: accounts_without_wallets ();
echo '<div class="wrap"><h1>' . esc_html__ ( 'Sign-in' , 'sirius-press' ) . '</h1>' ;
foreach ( $errors as $e ) {
printf ( '<div class="notice notice-error"><p>%s</p></div>' , esc_html ( $e ) );
}
foreach ( $notes as $n ) {
printf ( '<div class="notice notice-success"><p>%s</p></div>' , esc_html ( $n ) );
}
echo '<form method="post">' ;
wp_nonce_field ( 'sirius_auth_settings' );
echo '<input type="hidden" name="sirius_auth_settings" value="1">' ;
echo '<table class="form-table" role="presentation">' ;
echo '<tr><th>' . esc_html__ ( 'Passwords' , 'sirius-press' ) . '</th><td><label>' ;
printf (
'<input type="checkbox" name="sirius_allow_passwords" value="1"%s> %s' ,
checked ( SPA_Login :: passwords_allowed (), true , false ),
esc_html__ ( 'Also accept username and password sign-in' , 'sirius-press' )
);
echo '</label><p class="description">' ;
esc_html_e ( 'Leave this on until every account that needs access has a wallet attached. With it off, a wallet signature is the only way in — which is the point of this fork, but it is also final.' , 'sirius-press' );
echo '</p></td></tr>' ;
echo '<tr><th>' . esc_html__ ( 'Registration' , 'sirius-press' ) . '</th><td><label>' ;
printf (
'<input type="checkbox" name="sirius_open_registration" value="1"%s> %s' ,
checked ( SP_Settings :: open_registration (), true , false ),
esc_html__ ( 'Anyone who can sign with a wallet may create an account' , 'sirius-press' )
);
echo '</label></td></tr>' ;
2026-10-03 18:42:45 +02:00
echo '<tr><th>' . esc_html__ ( 'Login address' , 'sirius-press' ) . '</th><td>' ;
printf (
'<code>%s</code> <input type="text" name="sirius_login_slug" value="%s" class="regular-text" placeholder="%s" autocomplete="off" spellcheck="false">' ,
esc_html ( trailingslashit ( home_url ( '/' ) ) ),
esc_attr ( SPA_Login_URL :: slug () ),
esc_attr__ ( 'wp-login.php' , 'sirius-press' )
);
echo '<p class="description">' ;
esc_html_e ( 'Move the sign-in screen somewhere other than wp-login.php. Leave it empty to keep the usual address.' , 'sirius-press' );
echo ' ' ;
esc_html_e ( 'This is not about brute force — there is no password to guess — but wp-login.php is the most-requested path on the internet, and answering it tells every scanner what is running here. Note that it does not move wp-admin: WordPress has no supported way to rename that, and pretending otherwise breaks plugins quietly.' , 'sirius-press' );
echo '</p>' ;
if ( SPA_Login_URL :: slug () ) {
echo '<p class="description"><strong>' ;
printf (
/* translators: %s: the current login URL. */
esc_html__ ( 'Signing in now happens at %s.' , 'sirius-press' ),
'<code>' . esc_html ( home_url ( '/' . SPA_Login_URL :: slug () ) ) . '</code>'
);
echo '</strong> ' ;
esc_html_e ( 'If you lose it, `wp option get sirius_press_login_slug` on the server will tell you, and `wp option delete sirius_press_login_slug` will put the login back.' , 'sirius-press' );
echo '</p>' ;
}
echo '</td></tr>' ;
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
echo '</table>' ;
submit_button ();
echo '</form>' ;
echo '<h2>' . esc_html__ ( 'Accounts without a wallet' , 'sirius-press' ) . '</h2>' ;
if ( ! $stranded ) {
echo '<p>' . esc_html__ ( 'Every account here can sign in with a wallet.' , 'sirius-press' ) . '</p>' ;
} else {
echo '<p>' . esc_html__ ( 'These accounts can only sign in with a password. Turning passwords off locks them out.' , 'sirius-press' ) . '</p>' ;
echo '<table class="widefat striped" style="max-width:640px"><thead><tr><th>'
. esc_html__ ( 'Account' , 'sirius-press' ) . '</th><th>'
. esc_html__ ( 'Role' , 'sirius-press' ) . '</th></tr></thead><tbody>' ;
foreach ( $stranded as $user ) {
printf (
'<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' ,
esc_url ( get_edit_user_link ( $user -> ID ) ),
esc_html ( $user -> user_login ),
esc_html ( implode ( ', ' , $user -> roles ) )
);
}
echo '</tbody></table>' ;
}
echo '<h2>' . esc_html__ ( 'For other plugins' , 'sirius-press' ) . '</h2>' ;
echo '<p>' . esc_html__ ( 'A plugin that wants a fresh signature before something irreversible can call sirius_press_confirmation_challenge() and post the answer to:' , 'sirius-press' ) . '</p>' ;
echo '<p><code>' . esc_html ( rest_url ( 'sirius-press/v1/confirm' ) ) . '</code></p>' ;
echo '</div>' ;
}
/** Accounts that would be locked out if passwords were turned off. */
private static function accounts_without_wallets () {
return get_users (
array (
'meta_query' => array (
'relation' => 'OR' ,
array (
'key' => SP_Identity :: META_ADDRESS ,
'compare' => 'NOT EXISTS' ,
),
array (
'key' => SP_Identity :: META_ADDRESS ,
'value' => '' ,
'compare' => '=' ,
),
),
'number' => 50 ,
)
);
}
}