Skip to main content
The package ships two parallel surfaces. Pick the one that matches your wallet type. Mixing them is fine — both can live on the same device under different localStorage keys. The React hooks (usePasskeySetup, usePasskeyAuth, usePasskeyStatus) wrap the PRF surface (the default STARK-signer wallets) today. SHHH equivalents ship in a follow-on release; until then, call createShhhPasskey / signShhhMessage directly inside your own effects.

SHHH passkey (modern path)

createShhhPasskey

Registers a new P-256 platform passkey. No PRF extension, no PIN derivation. The private key stays inside the platform authenticator.
Defaults: rpId = window.location.hostname, rpName = "Chipi Wallet". Persists { credentialId, publicKey, userId, rpId, transports } to localStorage under the key chipi_wallet_shhh_passkey_credential. Throws on NotAllowedError (user cancelled) or InvalidStateError (passkey already exists for this account on this device).

signShhhMessage

Runs navigator.credentials.get() with messageHash encoded as the 32-byte big-endian WebAuthn challenge. Returns the raw assertion bytes ready to feed into buildWebAuthnEnvelopeFromAssertion from @chipi-stack/backend.

hasShhhPasskey

Synchronous check for a stored SHHH credential. Use it to branch between “Sign up” and “Sign in” UI without prompting.

getStoredShhhCredential

Reads the persisted credential metadata.

removeStoredShhhCredential

Clears the stored credential id from localStorage. Use at logout, account reset, or when the user opts to re-register. The platform authenticator’s stored credential is NOT deleted — only your app’s reference to it.

Extraction utilities

Exported for tests and for advanced callers who want to verify a passkey’s pubkey against a separately-stored copy.
extractP256PubkeyFromRegistration is the dispatch helper — it tries the SPKI fast path first (Chrome, Safari, modern Edge), then falls back to decoding the CBOR attestationObject for older browsers. You only need these utilities if you’re writing your own registration-response handler.

PRF passkey [#legacy-prf-passkey]

The PRF passkey path encrypts a STARK private key with a key derived from the WebAuthn PRF extension. It backs useCreateWallet({ usePasskey: true }), which creates an SHHH wallet with a STARK signer by default (the kind that supports session keys), and CHIPI v29 wallets. A PIN backup (dual-key, passkey primary + PIN backup) is optional. SHHH wallets with a WEBAUTHN_P256 signer use createShhhPasskey above instead. To unlock on a device where this browser did not create the passkey, pass the credentialId and prfSupported stored on the wallet row: getWalletEncryptKey(wallet.credentialId ?? undefined, { prfSupported: wallet.prfSupported ?? undefined }). With no argument it reads localStorage and throws "No wallet passkey found" on a new device.

createWalletPasskey

Registers a passkey with the PRF extension enabled, derives an encryption key from the PRF output, and stores the credential metadata.
Persists { credentialId, createdAt, userId, transports, prfSupported } to localStorage under chipi_wallet_passkey_credential. Separate storage key from the SHHH path — both surfaces coexist on the same device. requirePrf (default true) makes creation refuse a non-self-custodial wallet. On an authenticator that cannot produce a WebAuthn PRF secret, the only fallback is a key derived from PBKDF2(credentialId, userId); both are values your backend stores, so the derived wrap key is server-recomputable and the wallet is effectively custodial. With requirePrf at its default, createWalletPasskey throws PrfUnsupportedError (.code === "PRF_UNSUPPORTED") instead of creating that wallet. Catch it and tell the user their device can’t secure a wallet. Pass { requirePrf: false } only to knowingly opt into the custodial fallback (never for self-custodial consumer wallets). Also throws on NotAllowedError (cancelled) and InvalidStateError (passkey already exists). Unlocking existing non-PRF wallets via getWalletEncryptKey is unaffected.

getWalletEncryptKey

Authenticates with an existing PRF passkey and returns the same encryptKey derived at registration time. Use it before every wallet operation that needs the STARK key (transfer, session-key registration, encryption rotation).
The PRF output is deterministic — same passkey, same PRF salt, same encryption key. If PRF is unavailable (Firefox before 122, or a browser switch after registering on Chrome), the function falls back to PBKDF2 derivation only if the credential was originally registered without PRF. Otherwise it throws — see When passkeys fail.

Status checks

Credential management

React hooks (PRF passkey path) [#react-hooks-legacy-passkey-path]

The hooks today wrap the PRF surface — usePasskeySetup calls createWalletPasskey, usePasskeyAuth calls getWalletEncryptKey. Import from the /hooks subpath so Next.js bundles get the right "use client" directives.

usePasskeySetup

usePasskeyAuth

usePasskeyStatus

TypeScript types