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. Dual-key architecture: Every passkey wallet also has a mandatory PIN backup. If biometrics fail (browser change, device reset), the PIN recovers the wallet. Two independent keys, same private key.Platform Support
| Platform | Technology | Package |
|---|---|---|
| Web (Next.js, React) | WebAuthn PRF | @chipi-stack/chipi-passkey |
| Mobile (Expo) | Face ID / Touch ID + SecureStore | @chipi-stack/chipi-expo |
Installation
Create Wallet with Passkey + PIN
The recommended flow — passkey is primary, PIN is backup:What happens internally
usePasskey: true→ callscreateWalletPasskey(userId, userId)→ biometric prompt- Returns
encryptKey(passkey-derived) +credentialId+prfSupported - Since
encryptKey(PIN) was also provided → dual-key mode:- Private key encrypted with passkey key →
encryptedPrivateKey - Private key encrypted with PIN →
encryptedPrivateKeyBackup
- Private key encrypted with passkey key →
- Both sent to backend with
authMethod: "passkey+pin"
chipi-react/src/hooks/useCreateWallet.ts lines 62-100
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
| Option | Type | Description |
|---|---|---|
onPinRequired | () => Promise<string | null> | Called when passkey fails and backup exists. Return PIN string or null to cancel. |
PIN-Only Mode (Backward Compatible)
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
| Hook | Purpose |
|---|---|
usePasskeySetup | Create a new passkey manually (advanced — prefer usePasskey: true on useCreateWallet) |
usePasskeyAuth | Authenticate with existing passkey manually (advanced — prefer usePasskey: true on useTransfer) |
usePasskeyStatus | Check WebAuthn/biometric support and stored passkey validity |
verifyWalletPasskeyDetailed | Detailed check with reason: "prf_unavailable", "no_passkey", "error" |
useMigrateWalletToPasskey | Migrate existing PIN wallet to passkey |
Security
- Dual-key wrapping: Same private key encrypted by two independent keys. Either recovers.
- No silent fallback: If passkey was created with PRF, PBKDF2 fallback is blocked (prevents wrong-key decryption).
- Backend credential recovery:
credentialIdstored server-side. If localStorage cleared, SDK recovers from backend. - 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
