The ERC-4337 EntryPoint revert AA23 reverted (or empty return data) occurs when smart account signature verification fails during WebAuthn secp256r1 (P-256) passkey validation. Primary triggers include an omitted User Verification (UV) bit in authenticatorData flags (flags & 0x04 == 0), non-canonical high-$s$ ECDSA malleability on precompiled curves, or mismatched clientDataJSON challenge hashes. Enforcing user verification and normalizing signature coordinates resolves AA23 instantly.
To debug WebAuthn authentication parameters and authenticatorData flags offline, launch our WebAuthn Passkey Debugger Tool. To unpack packed UserOperation calldata and prefund limits, use our ERC-4337 Calldata Decoder Tool.
Architectural Deep-Dive: WebAuthn Assertion to EVM Verifier
Passkey smart accounts do not sign Ethereum transactions using standard secp256k1 keys. Instead, the user’s secure enclave (Apple Secure Enclave, Windows Hello TPM, or Android Titan M2) signs a SHA-256 hash composed of the clientDataJSON and authenticatorData using the NIST P-256 (secp256r1) curve.

The Two Root Failure Vectors
Vector 1: authenticatorData Flag Mismatch (The UV Bit Trap)
The authenticatorData structure contains a critical 1-byte bitfield at byte offset 32:
Byte 32 Flags:
Bit 0: UP (User Presence) -> 0x01 (Must always be 1)
Bit 1: Reserved
Bit 2: UV (User Verification) -> 0x04 (1 if biometric/PIN verified, 0 if only presence)
Bit 6: AT (Attested Credential) -> 0x40
Bit 7: ED (Extension Data) -> 0x80
When the smart contract passkey verifier executes:
// On-chain verification constraint inside WebAuthnValidator.sol
bytes1 flags = authenticatorData[32];
require((flags & 0x01) != 0, "UP bit not set"); // User Presence
if (requireUserVerification) {
require((flags & 0x04) != 0, "UV bit not set"); // User Verification (Biometric)
}
If the frontend WebAuthn assertion was requested with userVerification: "discouraged", or if an external security key without biometric capability was tapped, flags & 0x04 evaluates to 0. The contract reverts with empty return data or custom error, prompting the EntryPoint to emit AA23.
Vector 2: High-$s$ Signature Malleability ($s > N/2$)
In ECDSA cryptography, the P-256 curve order is defined as:
$$N = \text{0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551}$$
If the authenticator emits a signature where $s > N / 2$, strict smart contracts and the RIP-7212 precompile will reject the signature to prevent transaction hash malleability.
Step-by-Step Resolution Protocol
To resolve AA23 reverts across all major mobile and desktop platforms, follow this implementation pipeline.
1. Enforce userVerification: "required" in Assertion Request
Update your dApp or agent client authentication request to strictly mandate hardware biometric verification from the browser:
// src/auth/passkeyAuth.ts
export async function generatePasskeySignature(userOpHash: Uint8Array, credentialId: string) {
const challenge = userOpHash; // UserOp hash formatted as buffer challenge
const assertion = await navigator.credentials.get({
publicKey: {
challenge: challenge.buffer,
allowCredentials: [
{
id: Buffer.from(credentialId, 'base64url'),
type: 'public-key',
transports: ['internal', 'hybrid'],
},
],
// CRITICAL: Must be "required" to guarantee the 0x04 UV bit is set in authenticatorData
userVerification: 'required',
timeout: 60000,
},
}) as PublicKeyCredential;
if (!assertion) {
throw new Error('WebAuthn assertion failed or canceled by user.');
}
return assertion;
}
2. Normalize Signature to Low-$s$ Canonical Form
Before packing the (r, s) values into the UserOperation.signature field, check whether $s > N / 2$ and invert it if necessary:
// src/auth/p256Canonicalizer.ts
const P256_N = BigInt("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
const P256_HALF_N = P256_N >> 1n;
export function normalizeP256Signature(rBytes: Uint8Array, sBytes: Uint8Array): { r: bigint; s: bigint } {
let r = BigInt('0x' + Buffer.from(rBytes).toString('hex'));
let s = BigInt('0x' + Buffer.from(sBytes).toString('hex'));
// If s is in the upper half of the curve order, flip it to the lower half
if (s > P256_HALF_N) {
s = P256_N - s;
}
return { r, s };
}
3. Verify authenticatorData Flags Client-Side Prior to Broadcast
Inspect the raw assertion buffer client-side to preempt bundler drops and give users actionable diagnostic feedback if their hardware key lacks biometrics:
// src/auth/validateAssertion.ts
export function inspectAuthenticatorData(authDataBuffer: ArrayBuffer) {
const authDataView = new DataView(authDataBuffer);
// Byte 32 holds the bitmask flags
const flags = authDataView.getUint8(32);
const userPresence = (flags & 0x01) !== 0;
const userVerified = (flags & 0x04) !== 0;
if (!userPresence) {
throw new Error('PASSKEY_ERROR: User presence flag (UP) is missing.');
}
if (!userVerified) {
throw new Error(
'PASSKEY_ERROR: User verification flag (UV) is missing. ' +
'Please use Touch ID, Face ID, or Windows Hello PIN rather than a plain security key tap.'
);
}
return { flags, userPresence, userVerified };
}
Production Diagnostic & Verification Table
| Test Scenario | Client Setting / Payload | Expected EntryPoint Result |
|---|---|---|
| Strict Biometric (TouchID/FaceID) | userVerification: 'required', Flag 0x05 (UP+UV) | simulateValidation returns success (Execution Gas estimated). |
| Plain Hardware Tap without PIN | Flag 0x01 (UP only, UV=0) | Contract reverts immediately client-side with actionable error; prevents bundler fee loss. |
| High-$s$ Signature Scalar | Unnormalized $s > N/2$ | Fails with AA23 on RIP-7212 precompile chains (Arbitrum, Optimism, Base). |
| Normalized Low-$s$ Scalar | Canonical $s \le N/2$ | Transaction included successfully on-chain. |
Frequently Asked Questions
Q: Why did the same passkey work on EntryPoint v0.6 but fail on EntryPoint v0.7?
EntryPoint v0.7 introduced tighter constraints on validation gas rules and stricter enforcement against non-canonical signature representation. Many older v0.6 contracts did not enforce low-$s$ canonicalization or permitted UV-less signatures, which modern v0.7 account implementations have hardened for regulatory and security compliance.
Q: Can I configure the smart account to allow userVerification: 'preferred'?
Yes, but you must modify the account’s on-chain validation module to check only the UP bit (flags & 0x01) and omit the strict UV bit (flags & 0x04) requirement. Note that this lowers security against physical device theft where an unauthorized actor taps a plugged-in USB key without PIN entry.
Q: Does RIP-7212 support all EVM chains?
RIP-7212 (secp256r1 precompile at address 0x0000000000000000000000000000000000000100) is deployed natively on networks like Arbitrum One, Base, and Optimism. On Ethereum mainnet or Polygon, smart accounts use highly optimized Solidity verifiers (such as Daimo’s P256 verifier) which enforce identical cryptographic checks.
ERC-4337 EntryPoint Error Code Reference Matrix
Cross-reference standard ERC-4337 EntryPoint revert codes across account abstraction and smart wallet layers:
| Error Code | Stage | Primary Failure Mechanism | Diagnostic Guide |
|---|---|---|---|
| AA13 | Factory Deployment | InitCode execution reverted or exceeded factory gas | ERC-4337 AA13 InitCode Failed Fix |
| AA21 | Prefund Validation | Sender account balance or deposit below required maxCost | ERC-4337 AA21 Prefund Payment Fix |
| AA22 | Timestamp Validity | Clock drift or expired session key validity window | ERC-4337 AA22 Clock Drift Fix |
| AA23 | User Verification | P-256 passkey signature or WebCrypto auth failed | ERC-4337 AA23 Signature Revert Fix |
| AA33 | Paymaster PostOp | Post-execution token transfer or context reverted | ERC-4337 AA33 Paymaster PostOp Fix |
| Paymaster | Gas Sponsorship | Paymaster rejected UserOp or sponsorship signature invalid | ERC-4337 Paymaster Revert Resolution |
[!TIP] Unpack packed UserOperations, calculate prefund requirements, and isolate simulation hazards in our air-gapped /tools/erc4337-calldata-decoder/.