Passkey and PRF
One passkey ceremony yields one 32 byte secret. Everything else in satchel is derived from it and nothing derived is stored.
What the passkey gives you
The WebAuthn PRF extension lets a relying party ask an authenticator for a pseudo-random value that only that credential can compute for a given input. Satchel asks once per session, through Mera's getPasskeyPrfOutput or createPasskeyWithPrfOutput, and holds the 32 byte result as the root in memory.
root = PRF output, 32 bytes, from the passkey
nsKey = HKDF-SHA-256(root, salt "satchel/v1", info "satchel/ns/" + path)
dataKey = HKDF-SHA-256(nsKey, salt "satchel/v1", info "satchel/dek/" + epoch)
owner key = HKDF-SHA-256(root, salt "satchel/v1", info "satchel/owner/secp256k1")Why the raw output is never a cipher key
A raw PRF output used directly as an AES key would be the same key for every app and every record, and it would be the same bytes a wallet derivation might reuse. Passing it through HKDF with a distinct info string per purpose gives each purpose its own independent key, and a leak of one derived key says nothing about the root or its siblings.
What is stored
The root exists in memory between open() and close(). Derived keys are computed on demand and zeroed after use. Browser storage holds ciphertext and a public credential id, which openOrEnrol remembers so a later visit is one prompt. The proof page scans localStorage, sessionStorage, IndexedDB and cookies in your own browser and flags any value that is neither.
Which passkeys work
Which authenticators expose PRF differs by platform and changes over time, so satchel does not keep its own list. A passkey without PRF makes open() throw PRF_UNAVAILABLE, with the authenticators known to work in the fix text, instead of guessing at a key.
A person holding the passkey holds every namespace under it. Namespaces separate apps and data from each other; the passkey is the root.