Fix: WebAuthn Resident Key (Discoverable Credential) Overflow in Hardware Authenticators
When deploying or creating ERC-4337 Smart Accounts powered by WebAuthn passkeys (such as Coinbase Smart Wallet, ZeroDev Kernel, or Safe), hardware security keys like YubiKey (5 Series, 5Ci, Bio), Google Titan, or Feitian ePass provide the highest tier of private key security.
However, power users who secure multiple dApps, exchanges, and enterprise logins frequently encounter an abrupt failure during the passkey enrollment ceremony:
DOMException: The operation failed for an unknown reason.
[Status: OperationError]
or in Chromium developer consoles:
NotAllowedError: The operation either timed out or was not allowed.
[CTAP2 error: 0x3A - CTAP2_ERR_KEY_STORE_FULL]
If your passkey issues are instead related to Windows Hello biometric lockout, see our dedicated guide on Fixing Windows Hello TPM Passkey Enclave Lock.
Technical Cause: FIDO2 Discoverable Credential NVRAM Constraints
Unlike software passkeys (iCloud Keychain, Google Password Manager, 1Password) which synchronize credentials to virtually limitless cloud databases, hardware security keys utilize isolated cryptographic microcontrollers with dedicated Non-Volatile RAM (NVRAM).
In the FIDO2 / CTAP2.1 specification, credentials fall into two categories:
- Non-Resident Keys (Server-Side Credentials): The private key is encrypted inside the
credentialIdusing a master secret on the key. The key stores zero bytes on the device, allowing unlimited credentials. - Resident Keys (Discoverable Credentials): The private key, username, and Relying Party ID are written directly into physical hardware flash memory so that the user can sign in without typing their username first.
Hardware Storage Capacities
| Hardware Device | Firmware Version | Resident Key Slot Limit |
|---|---|---|
| YubiKey 5 Series | $< 5.7.0$ | 25 passkeys |
| YubiKey 5 Series | $\ge 5.7.0$ | 100 passkeys |
| YubiKey Bio | All | 25 passkeys |
| Google Titan Key | 2023+ Edition | 250 passkeys |
| Feitian ePass K9/K40 | Standard | 32 passkeys |
Because Account Abstraction smart wallets require user-less authentication (the user taps their hardware key to retrieve their smart contract account), dApps specify:
{
"authenticatorSelection": {
"residentKey": "required",
"requireResidentKey": true,
"userVerification": "required"
}
}
Once you reach the 25th credential on older YubiKey firmware, any subsequent navigator.credentials.create() request returns CTAP2_ERR_KEY_STORE_FULL (0x3A).
Step-by-Step Diagnostic & Key Management
Method 1: Inspect & Delete Passkeys via YubiKey Manager (ykman)
The fastest way to diagnose storage saturation is via the open-source Yubico CLI:
# 1. Install ykman (macOS: brew install ykman | Windows: winget install Yubico.YubiKeyManager)
ykman --version
# 2. List all resident FIDO2 credentials (requires FIDO2 PIN)
ykman fido credentials list
Sample Output:
Enter your FIDO2 PIN: ****
Credential ID RP ID User name
------------------------------------------------ ------------------ ----------------------
3f4a1c2e8b... coinbase.com 0x9B2...7A1
a7c8d9e0f1... layerzerofault.site victor.vance
... (25 total entries)
To free up slots for your Web3 smart wallet:
# Delete a specific obsolete credential using its Credential ID
ykman fido credentials delete <CREDENTIAL_ID>
Method 2: Manage Passkeys via Windows 11 Settings
Windows 11 includes a native passkey management dashboard for external security keys:
- Open Windows Settings $\to$ Accounts $\to$ Passkeys.
- Scroll to Security Key Settings and click Manage.
- Insert your hardware key into the USB port and enter your Security Key PIN.
- Review the list of stored credentials and remove old, deprecated dApp test credentials.
Developer Defense: Graceful Fallback Handling in Web3 dApps
When writing client-side WebAuthn onboarding code with @simplewebauthn/browser or Wagmi / Viem passkey connectors, never let OperationError or 0x3A fail silently.
Catch the error and prompt the user with clear hardware-capacity instructions:
// passkey-registration-guard.ts
import { startRegistration } from '@simplewebauthn/browser';
import type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/types';
export async function registerPasskeyWithStorageCheck(
options: PublicKeyCredentialCreationOptionsJSON
) {
try {
const credential = await startRegistration({ optionsJSON: options });
return credential;
} catch (error: any) {
// Detect FIDO2 hardware storage overflow
const errorMessage = error?.message || '';
const isStorageFull =
errorMessage.includes('OperationError') ||
errorMessage.includes('0x3A') ||
errorMessage.includes('KEY_STORE_FULL') ||
(error.name === 'NotAllowedError' && errorMessage.includes('unknown reason'));
if (isStorageFull) {
throw new Error(
'Hardware Security Key Full: Your device (YubiKey/Titan) has reached its maximum resident key limit (25–100 credentials). Please delete unused passkeys using YubiKey Manager or use a platform passkey (Windows Hello / Touch ID).'
);
}
throw error;
}
}
Best Practice Architecture: Use residentKey: "preferred"
If your smart contract architecture can accept an address or username during the initial lookup phase, change residentKey from "required" to "preferred":
// Config that allows non-resident fallback when hardware key is full
const options = {
authenticatorSelection: {
authenticatorAttachment: 'cross-platform',
residentKey: 'preferred', // Allows non-resident key creation if storage is full!
requireResidentKey: false,
userVerification: 'preferred',
},
};
This configuration permits the hardware key to fall back to generating a non-resident key wrapped inside the credentialId, completely bypassing physical NVRAM slot exhaustion.