A passkey normally proves who you are. This one is the account. The WebAuthn PRF
extension returns 32 bytes that never leave the authenticator unless you verify, and those
bytes are expanded into an ed25519 seed. Since ed25519 is one of XRPL's two key types, that
seed is an account master key directly, with no wrapping layer in between.
PRF → HKDF-SHA256 (32 bytes) → pk = 0xED ‖ ed25519 key → base58(0x00 ‖ RIPEMD160(SHA256(pk)) ‖ checksum)
Every 32-byte string is a valid ed25519 seed, so there is no range to land in and no bias to
correct for. Address derivation has no network component, so the same passkey gives the same
address on every network.
Every payment takes a fresh passkey verification. The transaction's signing digest is used
as the assertion challenge, so the prompt approves that one payment rather than the whole
session: change the amount or the destination and the digest changes with it. The key is
re-derived for that signature and zeroed straight after, so between payments the wallet
holds only a credential id and a public key, unless it was restored from shares.
One credential controls the account. There is no rotation and no second
credential. What losing it means depends on where the passkey lives. On a hardware key it is
final unless you wrote down backup shares: the account stays on the ledger and nobody reaches it
again. In a synced passkey (iCloud Keychain, Google Password Manager) the seed travels with it,
so any device on that account can derive the same key, and your Apple or Google account becomes
the thing standing between an attacker and your XRP.
Backup shares are the seed, split. After creating an account, and from the
wallet's backup button later, the page cuts the seed into SLIP-39 shares (Trezor's Shamir
standard: 33 words each, any m of n rebuild it, fewer reveal nothing) and shows
them once, never stores them and never sends them. A share you copy is cleared from the clipboard
thirty seconds later, or when the backup ends. Anyone holding enough of them holds the
account with no passkey in the way, so they belong on paper, apart from each other. Every backup
is a fresh set and two sets do not mix. Restoring from shares opens a session that keeps the key
in this page's memory until you disconnect, with no prompt before a signature; the page says so
in red while it lasts. A Trezor can recombine the words, but it derives a different kind of key
from them, so it will not show this address.
The derived key does exist in page memory for the moment it signs. That is inherent to PRF,
not particular to this wallet: the extension exists to hand an application key material, and a
password manager unlocking a vault with PRF holds its key the same way. The passkey's own key
never leaves the authenticator; anything derived from it necessarily does. So what you are
trusting is this origin and the code it serves. Re-deriving per signature and zeroing straight
afterwards keeps that window as short as it can be. Zeroing clears every buffer this
code owns, but the derivation passes through immutable strings and bignums inside the curve
library that nothing can erase, so a heap snapshot taken after a payment still contains the
key. Erasing it would need a non-extractable CryptoKey. WebCrypto does support
ed25519, so that route exists; this page does not take it yet.
The derivation strip shows the shape of the two secret stages, never their value.
The PRF output derives the seed deterministically and the seed is the account key, so either
one on screen would be full spending control. Neither is printed, logged or stored. The only
thing that ever puts the key on screen is a backup, on request, as shares.
The raw tab signs transactions this wallet does not otherwise build. JSON
is serialized here and submitted; serialized hex is signed and submitted too, with the
signature spliced in at the offset canonical order requires. Nothing here signs without a
tick, once per paste. The preview tells you what it can: the destination, every amount the
transaction carries, the fee. For an AccountDelete, a
SetRegularKey, a signer list, a delegation, a batch or a flag change it names the
consequence outright. It is a courtesy and not a verdict: what you paste is yours to check. A
pasted payment still gets the destination-tag check the send form does. The serializer knows
every XRPL field type, so the JSON side builds anything the ledger accepts; a field it does not
recognise is refused by name, because guessing would mean a valid signature over the wrong
transaction. A stale Sequence is corrected to the account's current one before
anything is signed, a missing Fee is filled in from the node's quote and said so,
and the multi-signing fields the ledger does not hash are dropped and named: this wallet holds
one key and cannot multi-sign.
Colophon
No backend, no accounts, no analytics. Keys are derived in the page and never sent
anywhere. The only stored state is a credential id with its address, label, user handle,
timestamp and whether a backup was cut, plus the network you chose. All of it in this
browser.