Skip to content

Fix email registration failures: title sanitization, AA24 VGL bump/retry, diagnostics - #20

Merged
NIC619 merged 6 commits into
mainfrom
fix/email-registration-failures
Jun 6, 2026
Merged

NIC619 merged 6 commits into
mainfrom
fix/email-registration-failures

Conversation

@NIC619

@NIC619 NIC619 commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the three bugs behind the 2026-06-06 email registration failures on Base (full post-mortem with evidence artifacts in incidents/2026-06-06-email-registration-failures/):

  1. TitleHashMismatch — invisible Unicode in titles. Titles copied from Medium contain U+200A hair spaces; the circuit hashes raw subject bytes while parseEmail collapses Unicode whitespace, so the two sides disagreed. The Email Generator now sanitizes titles (strip zero-width chars, collapse Unicode whitespace) before building the subject and the ID: keccak.

  2. Spurious AA24 — Pimlico rejecting a valid op at VGL 3.2M. The user's exact failed op was proven valid (its proof verifies true on the deployed verifier, its baked userOpHash matches EntryPoint getUserOpHash, and a fork replay of validateUserOp at VGL 3.2M returns 0), yet eth_sendUserOperation rejected it; the identical flow with VGL 4M landed (tx). PREDEFINED_VGL_BASE is raised to 4M and the email flow now retries once with +1M on AA24 (regenerating the proof, since the userOpHash commits to gas limits).

  3. Misleading VGL estimation. Because validateUserOp checks the userOpHash last and EmailVerifier swallows OOG in try/catch, the bundler's binary search can settle on the cheap OOG path (observed raw 1,327,210 with a real proof vs the ~3.0M real verify cost). The estimation script now also bisects the faithful-EVM minimum via eth_call and recommends 1.4× of the max of both numbers.

Also included

  • frontend/scripts/verify-email-proof.ts + frontend/scripts/check-userop-hash.ts — generalized diagnostic scripts from the investigation, with a decision guide in docs/email-op-diagnostics.md
  • register.ts gains a chain-id argument and --vgl override
  • incidents/ folder with the post-mortem, the failed op + proof artifacts (credential-free), and fork tests (test/DebugUserOpReplay.t.sol, test/DebugEmail3VGL.t.sol) pinned to a pre-registration block

Test plan

  • forge fmt --check clean; fork tests pass at --fork-block-number 46967000
  • bun run check — no new errors (26 pre-existing .vue resolution errors)
  • New/changed scripts transpile via bun build
  • End-to-end validation on Base mainnet: registration with VGL 4M accepted and landed
  • After merge/deploy: have a user register a fresh submission through the site

🤖 Generated with Claude Code

NIC619 and others added 6 commits June 6, 2026 20:35
Titles copied from Medium contain invisible U+200A hair spaces around
dashes. The circuit hashes the raw subject bytes while parseEmail
collapses all Unicode whitespace, so the callData title and the proof's
titleHash disagreed -> TitleHashMismatch on registration. Normalize the
title (strip zero-width chars, collapse Unicode whitespace) before
building the subject, the ID keccak, and validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pimlico's send-time simulation rejected a provably valid registration
UserOp with AA24 at verificationGasLimit 3.2M (fork replay of
validateUserOp returns 0 at 3.2M; the same flow with 4M was accepted
and landed: tx 0x5ecbbc764f0a8adc9dd1e89c6a23f91057f394924f7dfb8aa4b736cbe67b66b7).

- raise PREDEFINED_VGL_BASE 3.2M -> 4M
- email flow retries once with PREDEFINED_VGL + 1M on AA24, regenerating
  the proof since the userOpHash commits to the gas limits

See incidents/2026-06-06-email-registration-failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s for register.ts

The bundler's VGL estimate is unreliable for this account: validateUserOp
checks the userOpHash last and EmailVerifier swallows OOG in try/catch,
so the binary search can terminate on the cheap "verify OOG'd ->
SIG_VALIDATION_FAILED" path (observed raw 1,327,210 on Base with a real
proof, far below the ~3.0M verify cost). The estimation script now also
bisects the faithful-EVM minimum via eth_call of validateUserOp as the
EntryPoint and recommends 1.4x of the max of both numbers.

register.ts gains a chain-id argument (8453 support) and a --vgl
override for testing bundler gas headroom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalized from the 2026-06-06 incident investigation:

- verify-email-proof.ts: generate a proof locally and verify it against
  the deployed EmailVerifier via eth_call, bypassing the bundler --
  separates proof/circuit/verifier issues from 4337-layer issues
- check-userop-hash.ts: compare sendop's op.hash() against EntryPoint
  v0.8 getUserOpHash for an op built exactly like the frontend flow
- docs/email-op-diagnostics.md: decision guide for TitleHashMismatch /
  AA24 failures, incl. how to replay a failed op from a user console log

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rtem

Full post-mortem of the email registration failures (TitleHashMismatch
from invisible hair spaces; spurious AA24 from Pimlico rejecting a valid
op at VGL 3.2M; misleading VGL estimation), with evidence artifacts:
the user's exact failed op (its proof verifies true on-chain and its
baked userOpHash matches the EntryPoint), console log, and fork tests
replaying validateUserOp success at VGL 3.2M pinned to a
pre-registration block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The incident replay tests require a Base mainnet fork; CI runs forge
test without one, failing with "call to non-contract address". Skip
when no code is deployed at the RAC address; forked runs still execute.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NIC619
NIC619 merged commit f43e779 into main Jun 6, 2026
1 check passed
@NIC619
NIC619 deleted the fix/email-registration-failures branch June 6, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant