What Are Passkeys?
Passkeys use biometric authentication (Face ID, Touch ID, Windows Hello) to protect wallets. Instead of a PIN, a secure encryption key is derived from the biometric prompt. No passwords to remember, no PINs to leak.useCreateWallet supports three modes:
On the web the passkey’s wrap key is the WebAuthn PRF output, derived on the device: Chipi never sees it and cannot recompute it. If the device or browser can’t produce PRF output, wallet creation stops with
PRF_UNSUPPORTED instead of falling back to a server-derivable key (see When passkeys fail).
A PIN backup is optional. It is a weaker secret: the backup ciphertext is stored by the backend, and a short PIN can be brute-forced offline against it. Add one only if your users need a recovery path that doesn’t depend on passkey sync.
The passkey is bound to your site’s hostname (
rpId is window.location.hostname), so it unlocks the wallet only on that hostname. useCreateWallet names the passkey after the externalUserId you pass (user name externalUserId, display name "<externalUserId> - Wallet Key"), and that is what the OS passkey picker shows. Pass a value you are comfortable showing the user.Platform Support
Installation
Create a passkey-only wallet (recommended)
Create a wallet with passkey + PIN backup
Passkey is primary, PIN is backup:What happens internally
usePasskey: true→ callscreateWalletPasskey(externalUserId, externalUserId)→ biometric prompt. It throwsPrfUnsupportedError(code: "PRF_UNSUPPORTED") when the authenticator has no PRF; the hook rethrows it asError("Passkey creation failed: …")with the samecode.- Returns
encryptKey(passkey-derived) +credentialId+prfSupported - Without
encryptKey→ passkey-only: private key encrypted with the passkey key →encryptedPrivateKey,authMethod: "passkey" - With
encryptKey(PIN) → dual-key mode:- Private key encrypted with passkey key →
encryptedPrivateKey - Private key encrypted with PIN →
encryptedPrivateKeyBackup authMethod: "passkey+pin"
- Private key encrypted with passkey key →
credentialIdandprfSupportedare sent with the wallet, sogetWalletreturns them on any device
chipi-react/src/hooks/useCreateWallet.ts
Unlock the wallet on another device
The passkey syncs to the user’s other devices through their platform account. What doesn’t sync is the browser’slocalStorage, where createWalletPasskey keeps the credential metadata. getWalletEncryptKey() with no argument reads localStorage, so on a new device it throws "No wallet passkey found".
Pass the values stored on the wallet row instead:
getWalletEncryptKey(credentialId?: string, options?: { prfSupported?: boolean }). prfSupported tells it the wallet was created with PRF, so it never falls back to a different key derivation when a browser returns no PRF output; it throws "Passkey PRF unavailable…" instead.
The transaction hooks (useTransfer, useApprove, useCallAnyContract) accept the same credentialId param next to usePasskey: true.
Transfer with Passkey (Automatic PIN Fallback)
Transfer flow
usePasskey: true→ tries passkey authentication (biometric prompt)- If passkey succeeds → decrypts
encryptedPrivateKeywith passkey key → signs tx - If passkey fails (PRF unavailable, user cancelled, localStorage cleared):
- Checks if
wallet.encryptedPrivateKeyBackupexists - If yes → calls
onPinRequired()→ user enters PIN → decrypts backup key → signs tx - If no backup or no
onPinRequiredcallback → throws descriptive error
- Checks if
chipi-react/src/hooks/useTransfer.ts lines 65-110
UseTransferConfig
PIN-Only Mode (Legacy)
OmitusePasskey: true for the same PIN-only flow as before:
Migrate from PIN to Passkey
For existing PIN-only wallets:Check Passkey Status
Expo (Mobile) Passkeys
On mobile, the sameusePasskey: true flag works automatically — the Expo ChipiProvider injects a native biometric adapter.
onPinRequired callback in useTransfer handles the fallback — same as web.
Hooks Reference
Security
- Device-derived wrap key: On the web the passkey’s PRF output encrypts the private key. Without PRF, creation fails with
PRF_UNSUPPORTEDunless you passrequirePrf: falsetocreateWalletPasskey, which makes the wallet custodial. - Optional dual-key wrapping: With a PIN, the same private key is also encrypted with the PIN. Either recovers it, and the PIN is the weaker of the two.
- No silent fallback: If passkey was created with PRF, PBKDF2 fallback is blocked (prevents wrong-key decryption).
- Credential metadata on the wallet row:
credentialIdandprfSupportedare stored server-side and returned bygetWallet. IflocalStorageis cleared or the user is on a new device, pass them togetWalletEncryptKey(orcredentialIdto the transaction hooks). - Hardware-backed: Keys in Secure Enclave (iOS), Android Keystore, or WebAuthn authenticator.
Related
- Session Keys Guide — Combine passkeys with session keys
- useCreateWallet — Wallet creation hook reference
- useTransfer — Transfer hook reference
- useMigrateWalletToPasskey — Migration hook reference
