LayerZeroFault
passkey recovery Updated:

ERC-4337 Passkey P256 AA23 Signature Revert Fix

VV

Written by

Updated:

Technical Guide

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.

WebAuthn Passkey Authentication for ERC-4337 Smart Accounts

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 ScenarioClient Setting / PayloadExpected EntryPoint Result
Strict Biometric (TouchID/FaceID)userVerification: 'required', Flag 0x05 (UP+UV)simulateValidation returns success (Execution Gas estimated).
Plain Hardware Tap without PINFlag 0x01 (UP only, UV=0)Contract reverts immediately client-side with actionable error; prevents bundler fee loss.
High-$s$ Signature ScalarUnnormalized $s > N/2$Fails with AA23 on RIP-7212 precompile chains (Arbitrum, Optimism, Base).
Normalized Low-$s$ ScalarCanonical $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 CodeStagePrimary Failure MechanismDiagnostic Guide
AA13Factory DeploymentInitCode execution reverted or exceeded factory gasERC-4337 AA13 InitCode Failed Fix
AA21Prefund ValidationSender account balance or deposit below required maxCostERC-4337 AA21 Prefund Payment Fix
AA22Timestamp ValidityClock drift or expired session key validity windowERC-4337 AA22 Clock Drift Fix
AA23User VerificationP-256 passkey signature or WebCrypto auth failedERC-4337 AA23 Signature Revert Fix
AA33Paymaster PostOpPost-execution token transfer or context revertedERC-4337 AA33 Paymaster PostOp Fix
PaymasterGas SponsorshipPaymaster rejected UserOp or sponsorship signature invalidERC-4337 Paymaster Revert Resolution

[!TIP] Unpack packed UserOperations, calculate prefund requirements, and isolate simulation hazards in our air-gapped /tools/erc4337-calldata-decoder/.

Interactive Diagnostic Sandbox

Local WebAuthn & Smart Account Sandboxes

Debug WebAuthn credential requests, PRF extensions, and simulate ERC-4337 passkey account validation in client WebCrypto.

Companion Diagnostic Guide ERC-4337 AA13 initCode Failed Factory Gas Revert Fix →

Fix ERC-4337 EntryPoint AA13 initCode failed or OOG errors. Resolve factory deployment gas exhaustion, CREATE2 salt collisions, and P-256 passkey initializers.

Read Guide
Contextual Architecture

Related Diagnostic Guides

View All in Silo →

Related Inquiries

What does the ERC-4337 AA23 reverted error mean?

AA23 is an EntryPoint specification error signifying that the smart account's validateUserOp() call reverted during simulation. In Passkey (WebAuthn secp256r1 / P-256) setups, AA23 almost always indicates that the on-chain verifier rejected the signature due to an authenticatorData flag mismatch (missing User Verification bit) or an invalid non-canonical s scalar.

How does ECDSA signature malleability trigger AA23 on EntryPoint v0.7?

To prevent signature malleability attacks, RIP-7212 and smart contract P256 verifiers require the signature s-scalar to be in the lower half of the curve order (s <= n/2). If WebCrypto or an authenticator produces high-s, the verifier rejects the signature with AA23.

Why does userVerification: 'discouraged' cause AA23?

If passkey authentication was requested with userVerification: 'discouraged', the authenticatorData flags byte omits the User Verification bit (0x04). If the on-chain account contract asserts this flag, validation reverts immediately.

How can I inspect WebAuthn challenge bytes and authenticatorData?

Use our interactive WebAuthn Passkey Debugger tool to inspect clientDataJSON hashes, flags, and public key coordinates offline.