LayerZeroFault
passkey recovery

Fix: ERC-4337 Passkey P256 Signature Revert (AA23 User Verification Mismatch)

VV

Written by

Fact-Checked on September 11, 2026

Verified Expert

Fix: ERC-4337 Passkey P256 Signature Revert (AA23 User Verification Mismatch)

During the deployment and testing of ERC-4337 smart accounts utilizing native Passkey authentication (via WebAuthn and secp256r1 / P-256 curves), developers often encounter a fatal bundler simulation failure:

RPC Error: -32500: EntryPoint simulation failed: AA23 reverted (or empty return data)
    at EntryPoint.simulateValidation(UserOperation)
    at SmartAccount.validateUserOp(0x832a...912f)

The UserOperation simulates locally or passes client-side schema validation, yet bundlers (such as Pimlico, Biconomy, Alchemy, or ZeroDev) instantly drop the transaction with AA23 reverted.

The root cause of this failure mode stems from either a WebAuthn authenticatorData flag bitmask mismatch (specifically regarding the User Verification bit) or an unnormalized high-$s$ ECDSA scalar rejected by strict on-chain RIP-7212 precompiles or modular verifier contracts.

If you are diagnosing gas estimation reverts at the paymaster stage instead of account signature validation, consult our guide on ERC-4337 UserOperation Reverts at Paymaster: PostOp & Validation Failures.


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.

Placeholder: Flowchart of Client-Side WebAuthn Normalization and Canonical P256 Scalar Encoding

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.

Partner Spotlight: Gate.io

Trade Securely on Gate.io

Don't risk your assets on centralized silos or unverified endpoints. Trade securely on Gate.io with deep liquidity and institutional-grade security protocols.

Claim $100 Sign-up Bonus

Official Partner Referral Link

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. When using Passkeys (WebAuthn secp256r1 / P-256), AA23 almost always indicates that the on-chain P256 verifier rejected the cryptographic signature due to an authenticatorData flag mismatch (missing User Verification bit) or an invalid non-canonical s scalar.

Why does the WebAuthn authenticatorData UV flag cause signature verification failure?

Smart account passkey validators verify the raw authenticatorData byte string extracted from the browser assertion. If the smart contract requires the User Verification (UV) bit (bit 2, value 0x04) to be set, but the client invoked navigator.credentials.get() with userVerification: 'discouraged' or 'preferred' on a device that only verified User Presence (UP, bit 0, value 0x01), the bitmask validation fails on-chain.

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

EntryPoint v0.7 enforces strict signature canonicality. In secp256r1 ECDSA, for any signature (r, s), the pair (r, N - s) is also mathematically valid. If the browser WebAuthn authenticator outputs an s-value greater than N / 2, the smart contract's canonicality check rejects it. The client must normalize s to N - s before submitting the UserOperation.