IAMUVIN

Web3 Development

Passkey Wallets That Work on Mainnet: Building on EIP-7951

Uvin Vindula·September 15, 2026·15 min read
Share

TL;DR

Ethereum's Fusaka upgrade activated on 3 December 2025 at epoch 411392, slot 13,164,544, 21:49:11 UTC. It carried thirteen EIPs: nine core and four supporting. The coverage was almost entirely about EIP-7594 and blob counts. The one that changes what wallet developers can ship is EIP-7951, which added native secp256r1 — P-256 — curve support.

P-256 is the curve behind Apple Secure Enclave, Android Keystore, WebAuthn and passkeys. Before EIP-7951, verifying a passkey signature on-chain meant either a Solidity implementation costing roughly 300,000 gas or a trusted relayer. Priced at the L1 base fee I measured on 14 September 2026 — 0.183 to 0.436 gwei, median 0.252, with ETH at $2,504 — that verifier cost between $0.14 and $0.33 per signature. A plain 21,000-gas ETH transfer on the same day cost about $0.013. Verification cost 14.3 times the transaction it was authorising, because the ratio is fixed by the gas, not by the fee.

That is why passkey accounts needed a sponsor, and why sponsorship never scaled: lifetime paymaster volume across the chains BundleBear indexes is $14,118,556 over 1,280,867,896 UserOperations, about a cent each. A cent does not cover a $0.19 verification, and on the pre-Fusaka L1 gas prices of 2025 it did not come close.

With the precompile, plus EIP-7702 delegation or an ERC-4337 account, a seedless wallet works on mainnet without a subsidy. Here is how to build one.


What EIP-7951 changed, and what Fusaka coverage missed

Fusaka's EIP list, from the Ethereum Foundation's mainnet announcement: 7594 (PeerDAS), 7823, 7825 (transaction gas cap), 7883, 7917, 7918, 7934, 7939 (CLZ), 7951 (secp256r1 precompile), plus the supporting set — 7892 (blob-parameter-only forks), 7642 (eth/69), 7910 (eth_config) and 7935 (default gas limit 60M).

Twelve of the thirteen are protocol plumbing you will never touch directly. The blob work got the attention because it had a number attached to it — target and max went from 6/9 to 10/15 at BPO1 on 9 December 2025, and to 14/21 at BPO2 on 7 January 2026. That is a good story and I have written about what happened to blob economics after the upgrade.

EIP-7951 got almost nothing, and it is the only Fusaka item that directly changes the set of products a wallet team can ship.

Ethereum has always used secp256k1. Every EOA private key, every ecrecover call, every hardware wallet. Secure elements in consumer devices do not use secp256k1. Apple's Secure Enclave, Android's hardware-backed Keystore and ES256, the dominant WebAuthn algorithm, all use secp256r1, also called P-256 or prime256v1. Not every authenticator does: Windows Hello's TPM-backed credentials are commonly RS256 (RSA), and WebAuthn also defines Ed25519. A P-256-only account contract cannot verify those, so restrict pubKeyCredParams to ES256 at registration and tell the user why.

That mismatch is the whole reason "sign in with your fingerprint" never became the default way to hold crypto. The key material that consumer devices protect properly was the wrong shape for the chain.

The three options before December 2025

Option one: verify P-256 in Solidity. Modular arithmetic over a 256-bit prime field, in the EVM, at roughly 300,000 gas per verification. That is the figure I am working from and it is the one the ecosystem has quoted for years.

Option two: the L2 precompile. RIP-7212 shipped a secp256r1 precompile at about 3,450 gas on Optimism, Base, Arbitrum One and Nova, zkSync Era and Polygon zkEVM well before Fusaka. That is the real reason passkey wallets appeared on those chains first, and it never existed on L1.

Option three: a trusted relayer. Check the WebAuthn signature off-chain on a server you control, then have that server submit a transaction signed with a normal secp256k1 key. This works, it is cheap, and it means the user's device is not actually in control of the funds. Your server is. Every product that did this had a custody story it did not want to put on the marketing page.

EIP-7951 brings that precompile to L1, at 6900 gas rather than RIP-7212's 3,450.

The gas comparison: a Solidity P-256 verifier priced across nine chains

Here is the cost of the pre-Fusaka approach, using measured gas prices from 14 September 2026 and scaling a 300,000-gas verifier from the measured cost of a 21,000-gas transfer on each chain. ETH at $2,504.

Chain21,000-gas transfer (fee reading, 14 Sep 2026)300,000-gas P-256 verifier (derived)
Ethereum L1$0.010 – $0.023$0.14 – $0.33
Scroll$0.00001$0.00014
OP Mainnet$0.00005$0.00071
Ink$0.00005$0.00071
Unichain$0.00008$0.00114
Base$0.00032$0.0046
Arbitrum One$0.00106$0.015
zkSync Era$0.00238$0.034
Linea$0.02104$0.301

The L1 range comes from the base fee running 0.183 to 0.436 gwei across the eth_feeHistory window I sampled on 14 September 2026, with a median of 0.252 gwei. At that median a 21,000-gas transfer is about $0.013 and a 300,000-gas verifier about $0.19. The L2 rows are execution gas only and exclude the L1 data fee that OP-stack chains add per transaction — one OP Mainnet sample gave an all-in median fee of $0.001115, so the real numbers on those chains are higher than the table shows. growthepie's transaction cost data is not a cross-check on this table and should not be read as one: it had Arbitrum Nova cheapest by median transaction cost at $0.06 on the same date, higher than every L2 row above, because it measures the median transaction of any kind, contract calls included, not a 21,000-gas transfer.

Read the top row again. On mainnet, signature verification alone cost 14.3 times the value transfer it was authorising — 300,000 gas against 21,000, at the same base fee. That is not a wallet you ship to consumers.

Read the bottom rows too, because they explain why passkey wallets have existed on L2s for a while and not on L1. On Base, execution gas for a Solidity verifier is under half a cent, and the L1 data fee pushes the all-in figure above it. But the reason teams shipped there is simpler: Base, like OP Mainnet, Arbitrum and zkSync Era, already had the RIP-7212 precompile at about 3,450 gas. Teams shipped there and the story became "passkey wallets work," which was true on Base and false on mainnet.

What the precompile costs

EIP-7951 is Final and fixes both constants: the precompile sits at address 0x100 and costs 6900 gas. The input is 160 bytes — 32 each of message hash, r, s, public key x and public key y — and the output is 32 bytes containing 1 on success, or zero bytes on failure. Note that 6900 is double RIP-7212's 3,450: the EIP says the higher figure "matches closer the benchmarks of actual implementations" and deliberately gives up price compatibility with the L2 deployments. Budget accordingly on chains that already had RIP-7212.

What I can tell you is the shape of the change: the 300,000-gas line comes out of your per-transaction budget and is replaced by a single static call to a precompile. That is the difference between needing a sponsor and not needing one.

Why the sponsorship workaround never scaled

The standard answer to "passkey verification is expensive" was "run a paymaster." Measured against the whole history of ERC-4337, that answer does not survive contact with the numbers.

BundleBear, read on 14 September 2026: 1,280,867,896 total UserOperations, 841,608,996 bundle transactions, 66,518,891 accounts with at least one UserOp, and total paymaster volume — gas actually covered by sponsors — of $14,118,556.

That is roughly $0.011 of sponsored gas per UserOperation across the lifetime of the standard.

A cent per operation is a rounding error against a $0.19 verification cost on L1, and against the far higher figure that same verifier carried when L1 gas was expensive. Sponsorship at the scale the ecosystem actually runs it could never have carried mainnet passkey verification. It could carry the L2 numbers in that table comfortably, which is exactly where it got used. EIP-7951 removes the dependency instead of subsidising it.

The three pieces of a seedless wallet on mainnet

A wallet with no seed phrase needs three things to work. The precompile is one of them.

The key: a passkey in hardware you do not control

The user's device generates a P-256 keypair inside its secure element. The private key never leaves it. The user authorises each use with a biometric or a device PIN. You get a public key at registration and a signature at each use, and that is all you ever get.

This is the part that makes seedless worth doing. The key is protected by hardware most users already carry, backed up by the platform's own sync mechanism, and cannot be phished the way a seed phrase can.

The account: EIP-7702 delegation or a deployed ERC-4337 account

The chain needs an account whose validation logic checks a P-256 signature rather than a secp256k1 one. Two routes.

EIP-7702 delegation points an existing EOA's code at an implementation that validates passkey signatures. The address survives, the balance survives, the approvals survive. There is no migration step. The cost is that the original EOA private key still controls the account outright and can re-delegate at any time — so a 7702 passkey wallet is only as secure as the key underneath it.

A deployed ERC-4337 account has no EOA key beneath it. The passkey is the only authority, enforced by the EntryPoint's validation phase. That is the stronger security model and the heavier lift: you are running or paying for a bundler, and every tool in the user's life has to understand UserOperations.

I go through that choice with measured traffic numbers in what the chain actually shows about 7702 and 4337. The short version for passkeys: if the user already has an EOA with funds in it, delegate; if you are onboarding someone with nothing, deploy a 4337 account.

The submission path

A delegated EOA sends ordinary type-2 transactions after the one-time setcode. A 4337 account sends UserOperations through a bundler to the EntryPoint. EntryPoint v0.8 exists in part to make a delegated EOA work with the 4337 validation interface, which is the combination you want if you have existing users.

The verification call

The precompile is a static call. The pattern looks like this.

solidity
// EIP-7951 fixes the precompile address, the input encoding and the
// return convention, all Final. Verified against the EIP text: 0x100,
// 6900 gas, 160-byte input, 32 bytes of 0x01 on success, 0 bytes on failure.
address constant P256_VERIFY = address(0x100); // EIP-7951

function verifyP256(
    bytes32 messageHash,
    bytes32 r,
    bytes32 s,
    bytes32 pubKeyX,
    bytes32 pubKeyY
) internal view returns (bool) {
    (bool ok, bytes memory out) = P256_VERIFY.staticcall(
        abi.encodePacked(messageHash, r, s, pubKeyX, pubKeyY)
    );
    return ok && out.length == 32 && uint256(bytes32(out)) == 1;
}

Three things that will cost you a day each if you skip them.

A precompile that does not exist returns success with empty output. On a chain that has not enabled EIP-7951, a static call to that address behaves like a call to an empty account: ok is true and out is empty. If your check is only if (ok), every signature verifies. That is a total authorisation bypass, and it is not fork-specific: EIP-7951 returns 32 bytes on success and zero bytes on failure, so on a Fusaka-enabled chain an invalid signature also comes back with ok true and out empty. One negative test with a deliberately bad signature catches it anywhere. Check the output length and the returned value, every time.

Test on a fork of the real chain, at a real block. Not on a bare local devnet, which will not have the precompile unless you configured it.

Have the verification path audited. A signature check in an account contract is the highest-value target in your codebase. The general smart contract security checklist applies, but the precompile-returns-empty case above is specific to this pattern and belongs in your test suite as a named test.

WebAuthn does not hand you a raw signature

This is where most implementations go wrong, and it is not about the curve at all.

A WebAuthn assertion does not give you a signature over the message you asked for. It gives you a signature over a structure the authenticator built, and you have to reconstruct that structure on-chain to check it.

What comes back from the browser or the platform API:

  • `authenticatorData` — a byte string describing the authenticator and its flags, including whether the user was verified.
  • `clientDataJSON` — a JSON document containing the challenge you issued, base64url-encoded, along with the origin and the operation type.
  • `signature` — an ECDSA signature over the concatenation of authenticatorData and the SHA-256 hash of clientDataJSON.

So the message your contract must verify is not your transaction hash. It is a hash built from those two blobs. Your contract receives both, rebuilds the signed message, and checks that your intended hash is the challenge sitting inside clientDataJSON at the position you expect.

Four failure modes to plan for:

  • The signature arrives DER-encoded. WebAuthn returns ASN.1 DER. The precompile wants raw r and s. Convert off-chain and pass raw values on-chain; DER parsing in Solidity is a liability you do not need.
  • Signature malleability. For any valid (r, s) there is an equally valid (r, n - s). If you use the signature bytes as a replay-protection key, both forms pass. Normalise to low-s and reject high-s.
  • Challenge extraction. clientDataJSON is a JSON document whose exact byte layout varies by platform. Parsing JSON on-chain to find the challenge is expensive and fragile. Pass the offset from the client and verify the bytes at that offset match your expected base64url challenge.
  • Origin binding. clientDataJSON carries the origin. A signature produced for your web app is valid bytes anywhere unless you check it. Check it.

None of this is new — it is the same WebAuthn assertion structure every FIDO2 relying party handles. What is new is that the final curve operation is now cheap enough that the parsing around it is the dominant cost. Confirm every byte offset against the WebAuthn specification itself rather than against a reference implementation you found.

Is the ecosystem ready? What EntryPoint versions say

If you are going the ERC-4337 route, the version split matters, and it is not what the release notes imply.

I sampled mainnet and Base directly over JSON-RPC on 14 September 2026, around 15:00 UTC, counting EntryPoint calls by version.

VersionMainnet calls (25 blocks)Base calls (40 blocks)
v0.69162
v0.771175
v0.84827

On mainnet, v0.8 is about 38% of EntryPoint flow. On Base it is about 7%, while v0.6 — the original — is about 44%. A second Base sample of 30 blocks and 7,354 transactions gave the same shape: 117 v0.6, 149 v0.7, 18 v0.8.

Base carries the most account abstraction traffic of the two by a wide margin — EntryPoint calls were 3.105% of 11,723 transactions there against 1.404% of 9,116 on mainnet — and it is the chain furthest behind on EntryPoint versions.

The practical reading: if your passkey account is built for v0.8 only, check that the bundler you are paying actually supports it on the chain you are deploying to. Do not assume.

For the delegation route, the baseline is smaller than the marketing suggests. Between 3% and 9% of distinct mainnet senders in my sample windows were live EIP-7702 delegated EOAs, and 4% on Base. I counted delegations across about a hundred distinct addresses sampled out of those windows, not across every sender in them, so read that as single-digit percent and nothing more precise. Seedless is early. That is an opportunity, not a warning, but do not build a revenue model on it being the default.

Recovery is the hard part, not signing

The precompile solves signature verification. It does not solve the thing that actually kills seedless wallets in production.

A passkey lives in a platform keychain. If the user loses every device on that account and has no platform backup, the key is gone. There is no seed phrase to write down. That is the entire point and it is also the entire risk.

Your account contract has to answer, before launch, what happens when the passkey is unrecoverable. The options are all tradeoffs and none of them is free:

  • Multiple passkeys registered to one account. The user enrols a phone and a laptop. Simple, and it fails the moment both are lost together.
  • A guardian set that can rotate the key after a delay. Well-trodden ground — the same reasoning as multi-signature wallet design, with a time delay so a compromised guardian set does not act instantly.
  • A cold secp256k1 key as a fallback authority. Defeats the seedless premise for the user who wants it most, but is the honest answer for a high-value account.
  • The 7702 case, where the original EOA key is already the fallback. Whether that is a feature or a hole depends on how that key was generated and stored.

Whatever you choose, write it down in the product before you write the contract. I have watched teams ship the signing path and discover the recovery question during a support ticket.

Passkey-based authentication also changes your off-chain flows, because the signer is no longer a key your backend can reason about. If you are doing wallet-based login, the signature verification path on your server needs the same treatment as the one in the contract.

What I could not verify

Four things I want to be explicit about.

The precompile's behaviour under production load. The address, gas cost and encoding above come from the Final EIP-7951 text. What I have not done is run the precompile through a production bundler at volume, so treat the Solidity above as correct in shape and unproven at scale.

The 300,000-gas figure for a Solidity verifier is the ecosystem's long-standing estimate, not a measurement I took. Implementations vary. The dollar column in that table scales directly from it, so if your verifier is 250,000 gas, scale the column down.

Fusaka coverage being dominated by PeerDAS and blobs is my impression of what was written at the time, not a content analysis with a methodology behind it, and I am not attaching a percentage to it.

My on-chain samples are point samples — 25 to 40 blocks inside a ten-minute window on 14 September 2026, taken against public Ethereum and Base nodes. They are not daily averages and the percentages will have moved.

Where this ends: EIP-8141 and the 2029 deadline

Two Ethereum Foundation posts from 7 September 2026 tell you where signature schemes are heading, and passkeys sit right in the middle of it.

The first is the Hegotá EIP tier list. The EF graded all 62 candidates: 2 S-tier, 15 A-tier, 8 B-tier, 7 C-tier, 28 declined for inclusion, 2 undecided. Only two items were graded must-ship. One is FOCIL (EIP-7805) on the consensus layer. The other is EIP-8141, Frame Transactions, and the EF names it as its chosen path for both native account abstraction and post-quantum signatures.

The second is the protocol priorities post, which commits to making L1 quantum-resistant across execution, consensus and data layers by December 2029, and describes that deadline as non-negotiable at least until January 2027, when quantum progress will be reassessed with the guidance of outside experts. The plan runs five forks — Hegotá, I*, J*, K* and L* — and the post is explicit that hitting December 2029 needs an average cadence of 7.2 months per fork; a 12-month cadence reaches only the contingency milestone, with "minimum viable post-quantum" targeted at the J fork. EIP-8365, retiring BLS withdrawal credentials, is the single A-tier post-quantum item on the Hegotá list.

What that means for a passkey wallet: P-256 is an elliptic curve, and elliptic curves are exactly what a cryptographically relevant quantum computer breaks. A passkey account is not a post-quantum account. It is a better key-custody story on the same cryptographic assumption as an EOA, and it inherits the same migration problem. I go through what that migration involves in post-quantum migration for blockchain teams.

Design your account contract so the verification scheme is replaceable. Not configurable, not pluggable — just upgradeable through whatever governance you already have, with the curve identifier stored alongside the public key rather than hardcoded into the validation function. That one decision is the difference between a migration and a redeployment in 2029.

Key Takeaways

  • EIP-7951 shipped in Fusaka on 3 December 2025, at epoch 411392, slot 13,164,544, adding native secp256r1 verification — the curve behind Apple Secure Enclave, Android Keystore and WebAuthn.
  • A Solidity P-256 verifier at roughly 300,000 gas cost $0.14 to $0.33 on L1 at the 0.183 to 0.436 gwei base fee measured on 14 September 2026 with ETH at $2,504 — 14.3 times the cost of the 21,000-gas transfer it authorised, because the ratio is set by the gas, not by the fee.
  • Sponsorship was never going to cover that. Lifetime paymaster volume across ERC-4337 is $14,118,556 over 1,280,867,896 UserOperations, about a cent each.
  • Empty output means failure, not success. EIP-7951 returns 32 bytes for a valid signature and zero bytes for an invalid one, and a chain without the precompile returns zero bytes with ok true as well. Check the return length and the returned value or you ship a total authorisation bypass.
  • WebAuthn signs its own structure, not your hash. Rebuild the message from authenticatorData and the SHA-256 of clientDataJSON, convert DER to raw r/s, normalise to low-s, and bind the origin.
  • In a ten-minute sample, EntryPoint v0.8 was 48 of 128 mainnet EntryPoint calls and 27 of 364 on Base, where v0.6 still carried 162. Small samples, but the direction is clear: confirm bundler support on your target chain.
  • Passkeys are not post-quantum. The EF has committed to a quantum-resistant L1 by December 2029, so store the curve identifier alongside the public key and keep the verification scheme replaceable.

About the Author

I'm Uvin Vindula — a Web3 and AI engineer based between Sri Lanka and the UK. I build production smart contracts and wallet integrations, and I measured the mainnet and Base numbers in this article directly over JSON-RPC rather than reading them off a dashboard. You can see my work at iamuvin.com or reach out about a project at hello@iamuvin.com.

If you're building a seedless wallet or moving an existing account stack onto passkeys, let's talk about your project.

Working on a Web3 or AI project?

Share

More in Web3 Development

All Web3 Development articles
Uvin Vindula

Uvin Vindula

Web3 and AI engineer based in Sri Lanka and the UK. Author of The Rise of Bitcoin. Founder of ASI Research Labs. Director of Blockchain and Software Solutions at Terra Labz. Founder of uvin.lk — Sri Lanka's Bitcoin education platform with 10,000+ learners.