Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mailbox credentials

See Addresses for how your wallet address is your mail identity. This page covers the two commands that derive a mail app’s login material from your wallet keypair, offline: sithbit mailbox credentials (who you are) and sithbit mailbox reading-secret (what opens your mail) — and their third neighbour, sithbit mailbox sign-text, the raw signer that answers a challenge the account service just handed you.

None of the three is needed in the webmail app or the Thunderbird/ Outlook extensions: they derive both login values in the page, sign you in with them, and have your wallet sign any challenge the account service asks for without your typing anything. These commands are for configuring a stock mail app by hand — and, for sign-text, for driving the account API from a shell or a script.

sithbit mailbox credentials

sithbit mailbox credentials \
  [--keypair <path>] \
  [--epoch <N>]

credentials prints the username/password pair a stock mail app uses to authenticate to the SithBit SMTP/IMAP/POP servers as your wallet — with no separate stored password. The username is your wallet address (the base58 public key); the password is a base58 wallet signature over a challenge that embeds that same public key and your account’s current auth epoch. The servers verify the signature against the username, and nothing is stored server-side — the signature is self-proving.

Read that binding precisely: the password is tied to your wallet and that epoch, so nobody can present it as a different wallet, and it stops working the moment you rotate the epoch. It is not otherwise single-use. Anyone who captures it can replay it as you, on any of the three protocols, until you rotate — which is what makes rotation the revocation lever rather than a housekeeping step, and TLS non-optional. See the threat model for what else follows from that.

The command is fully offline: it contacts no RPC endpoint, reads nothing on-chain, and writes no files. Both values are derived purely from the local wallet keypair, and because the signature is deterministic, re-running the command for the same keypair and epoch prints the identical pair every time — paste it into a mail app once and you are done.

Arguments

  • --keypair <path> (optional, -k) — the wallet keypair file the credentials are derived from. Defaults to the keypair in your Solana CLI config when omitted.
  • --epoch <N> (optional, default 0) — the auth epoch to sign for. 0 is an account that has never rotated; after a rotation, pass the epoch the account now reports. Offline means offline: the command cannot look this up for you, so a wrong value here is the one way to get a well-formed password the servers refuse.

The auth epoch, and rotation

A wallet signature never expires on its own — whoever copied your mail password holds it for good. The auth epoch is the handle that takes it back: a counter the account keeps, mixed into the challenge the wallet signs, so adding one to it changes the bytes every valid password must sign over and retires every copy of the old one at once, on every listener.

Rotating is done from the account, not from here — the webmail app’s Settings pane (the same control the Thunderbird/Outlook extensions and the operator’s enrollment page carry), or POST /v1/account/auth-epoch directly. Both report the new epoch; GET /v1/account reports the current one at any time. This command’s job is the other half: deriving the password that matches whatever epoch the account is on now.

That is what the output leads with, so a mismatch is visible before it becomes a mystery:

Auth epoch:         0
Mail auth username: <your wallet address>
Mail password:      <base58 signature>

These credentials are valid ONLY at auth epoch 0.
If your account shows a different auth epoch (webmail, or GET /v1/account),
re-run with --epoch <N> — otherwise this password will be rejected.

Three limits are worth knowing before you rotate, because a rotation is narrower than it sounds:

  • It retires the wallet-derived password only. A stored mail password, if the account has one, keeps working exactly as before, so rotation is not a way to shut that path. Closing it is a separate, equally deliberate gesture — Remove the stored mail password on the webmail Settings pane, or DELETE /v1/account/password — and it leaves the auth epoch, and so this command’s output, untouched.
  • It does not end web or add-in sessions. A session token is untouched and stays valid to its expiry; only mail apps have to be re-provisioned with the new password.
  • It does not revoke a client-certificate login. SASL EXTERNAL proves identity from the certificate in the TLS handshake rather than from a signature over the epoch, so a bump leaves it working; only the operator turning client_cert_auth off for the listener closes it.

The one compatibility note: a password derived before epochs existed still authenticates an account that has never rotated, which is why --epoch defaults to 0 and nothing broke on the day epochs landed. The first rotation ends that grace for that account permanently — from then on only a password derived at the account’s exact current epoch is accepted.

Using the credentials

Enter the printed pair in your mail client as its ordinary username and password — SASL PLAIN over TLS for IMAP/submission, or the plain POP3 PASS login. The password is bearer-equivalent for the connection, so always use TLS. The client walk-throughs show exactly where each value goes, and how the browser/mail-app extensions derive the same pair without the CLI: Thunderbird and Outlook.

Because the username here is your wallet address, the server looks nothing up: the signature proves the account directly. A client configured with an alias and a stored mail password instead — the password path the account service manages — does need that name resolved to a wallet, and aliases move: a transfer, a sale, or the settlement of an auction re-points one at a new holder, and nobody needs permission to make that happen. The guarantee is that the resolution happens once per login: the account your password authenticates is the account the session opens, so a name that changes hands in the instant between the two cannot hand your session to anyone else’s mail, or theirs to you. See a re-pointed alias cannot redirect a mail login for the full statement.

Examples

Print the credentials for the Solana CLI config’s default wallet:

sithbit mailbox credentials

Derive them for a specific keypair file:

sithbit mailbox credentials --keypair ~/.config/solana/id.json

Re-derive them after a rotation — for an account whose settings pane now reads auth epoch 3:

sithbit mailbox credentials --epoch 3

sithbit mailbox reading-secret

sithbit mailbox reading-secret \
  [--keypair <path>]

reading-secret prints the base58 reading key secret that opens mail sealed to this wallet address: the X25519 twin of your wallet keypair, which is what senders — and your operator’s at-rest sealing — wrap to whenever the mailbox publishes no delegated key. It is derived from the local keypair alone, so the command is fully offline, deterministic, and prints the same string every time. The address it belongs to goes to standard error and the secret alone to standard output, so the command pipes cleanly.

This one really is a secret. The credentials password above only authenticates — it proves you hold the wallet and is useless for anything else. The reading secret decrypts: whoever holds it reads every message ever sealed to that wallet, the delivered ones as much as the future ones, and no rotation takes that back for mail already sealed. Print it into a mail app’s password field and nothing else; never into a chat, a ticket, or a shell history you keep.

CLI build note: reading-secret is one of the commands gated behind the CLI’s rand feature — but rand is on by default, so a stock cargo build -p mail-client already has it and there is nothing to enable. Only a slimmed --no-default-features build that does not add rand back drops the command, together with the feature’s other surfaces: delegated-key generation and the local body decryption flags. Its two neighbours on this page, credentials and sign-text, are ungated and ship in every build.

Arguments

  • --keypair <path> (optional, -k) — the wallet keypair file the secret is derived from. Defaults to the keypair in your Solana CLI config when omitted.

Using the reading secret

A mail app that logs in with the wallet-signature password alone still sees your account, its folders and its message list — but on a deployment that seals mail at rest, the bodies it cannot open show up locked. To read them, append the reading secret to that password after a single .:

<password from `mailbox credentials`>.<secret from `mailbox reading-secret`>

. never appears in base58, so the two halves are unambiguous. Use the joined value for the incoming server only (IMAP or POP, over TLS). The submission (SMTP) server refuses a password carrying a reading secret: sending mail never needs your decryption key, so a misconfigured client fails loudly there instead of shipping the secret to a service that has no use for it. The secret is held for the session, used to unwrap that session’s messages, and dropped when the session ends; nothing stores it.

If your mailbox publishes a delegated key, that key — not this one — is what mail is sealed to; read it with the key file from Mailbox keys instead.

Examples

Print the reading secret for the Solana CLI config’s default wallet:

sithbit mailbox reading-secret

Derive it for a specific keypair file, keeping the address note out of the piped value:

sithbit mailbox reading-secret --keypair ~/.config/solana/id.json

sithbit mailbox sign-text

sithbit mailbox sign-text <TEXT> \
  [--keypair <path>]

sign-text prints the base58 ed25519 signature of your wallet keypair over the exact UTF-8 bytes of <TEXT> — the proof the account service’s challenges ask for. Fetch a challenge from POST /v1/auth/step-up, sign the nonce it hands back, and send the signature in the gated write’s x-sithbit-step-up header. Like its two neighbours above, the command is fully offline: no RPC endpoint, no chain read, no HTTP call, nothing written — so it runs on an air-gapped machine holding the wallet.

It is a raw signer: nothing is prepended, appended, hashed or trimmed. Domain separation is the challenge’s job — the account service’s nonces carry their own SithBit login nonce: or SithBit step-up nonce: prefix and are verified against exactly the string that was issued, which is why a signature collected at login can never be presented as a step-up proof, or the reverse. The rule that follows is short: sign only text a server just handed you, pasted verbatim. One stray trailing space is a different message, and the signature over it will not verify.

The browser clients never need this. Webmail and the Thunderbird/ Outlook/ Chrome extensions sign the same challenge in the page with the wasm wallet — the identical signer, byte for byte — so the settings pane’s buttons already run the whole dance, invisibly with an in-app wallet and as an approval prompt on a Phantom or Ledger one. sign-text is for the surfaces that have no button: a shell, a script, a provisioning job.

Arguments

  • <TEXT> (required, positional) — the string to sign, verbatim. Quote it: a challenge nonce contains spaces and a colon.
  • --keypair <path> (optional, -k) — the wallet keypair file that signs. Defaults to the keypair in your Solana CLI config when omitted.

Output, and capturing it

The signature alone goes to standard output; the wallet it signed as goes to standard error, where it is visible interactively without polluting the captured value:

Signed as <your wallet address>:
<base58 signature>

So a shell capture holds exactly the header value, with nothing to trim:

PROOF=$(sithbit mailbox sign-text "$NONCE" --keypair ./wallet.json)

One signature, one request

A challenge lives 300 seconds and is spent by the first request that carries it, whatever that request answers — a success, a wrong signature and an expired one all consume it. So sign the nonce you were just handed, send it once, and after any failure fetch a fresh challenge and sign that one; re-sending the same proof answers 428 for good. Two gated writes are two challenges, not one. See One proof, one mutation for the full contract.

Examples

Sign a step-up nonce with the Solana CLI config’s default wallet:

sithbit mailbox sign-text "SithBit step-up nonce: 0f9c2b1a-4d3e-4c5f-8a7b-6d5e4f3c2b1a"

Sign it with a specific keypair file:

sithbit mailbox sign-text "SithBit step-up nonce: 0f9c2b1a-4d3e-4c5f-8a7b-6d5e4f3c2b1a" --keypair ~/.config/solana/id.json

The per-recipient pin provider walkthrough is the worked example, end to end: challenge, signature, gated write.

  • Create a client certificate — the password-less alternative: a TLS client certificate that logs the same wallet in over SASL EXTERNAL, for servers with client-certificate auth enabled.
  • Create a mailbox — the credentials log you in to a server account; the mailbox is what receives your on-chain mail.
  • Looking up a mailbox — inspect a mailbox’s settings.
  • Mailbox keys — publish a delegated encryption key, which replaces the wallet twin above as what mail is sealed to.