How 2FA Is Possible Thanks to EIP-7951
EIP-7951 Allows For Two-Factor Authentication On EVM Chains Like Berachain
How 2FA Is Possible Thanks to EIP-7951
EIP-7951 Allows For Two-Factor Authentication On EVM Chains Like Berachain

How 2FA Is Possible Thanks to EIP-7951
Whenever a finger touches a MacBook or Face ID recognises a face, your identity is confirmed in milliseconds. This is possible thanks to the Secure Enclave, a dedicated security chip that generates and stores a P-256 private key that never leaves the device. The same cryptographic primitive secures billions of phones, laptops, and hardware tokens worldwide. Until recently, Ethereum & Berachain had no efficient way to verify it on-chain. ***EIP-7951*** changes that.
What Is EIP-7951 and P-256?
EIP-7951 introduces native support for understanding signatures that come from devices like your phone or laptop, in the form of a precompile. A precompile is a special contract built directly into the ***Ethereum and [Berachain](https://github.com/berachain/BRIPs/blob/main/meta/BRIP-0010.md#8-proof-of-liquidity-consensus-layer-parameter-updates-1)*** protocol rather than deployed in Solidity, which means it runs fast and cheap compared to equivalent on-chain code.
That precompile is called P256VERIFY and lives at address 0x100. It performs ECDSA signature verification over the secp256r1 elliptic curve, also known as P-256 or prime256v1. This differs from secp256k1, the curve native to EMV chains that your EOA already uses for signing transactions. P-256 is a NIST-standardised curve built into nearly every piece of modern secure hardware: Apple Secure Enclave, Android Keystore, YubiKeys, HSMs, and FIDO2/WebAuthn authenticators. Both curves provide approximately 128 bits of security, but only secp256k1 was verifiable on EVM chains efficiently until now.

Comparison of secp256k1 vs secp256r1 (Scaled down)
The precompile takes exactly 160 bytes of input, packed as five 32-byte fields:
input = hash (32 bytes)
| r (32 bytes)
| s (32 bytes)
| qx (32 bytes)
| qy (32 bytes)
# Example
# hash = 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
# r = 0xa9f2f9a9c6b1e2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
# s = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
# qx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
# qy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
On successful verification it returns a single 32-byte word. On failure or invalid input it returns empty bytes:
// Valid signature
0x0000000000000000000000000000000000000000000000000000000000000001
// Invalid signature or bad input
0x (empty)
The cost is 6900 gas. Before EIP-7951, verifying a P-256 signature required a full Solidity elliptic curve library, which typically consumed 200,000 to 300,000 gas per verification. The precompile reduces that by roughly 97%.

Comparing Ethereum and Berachain P-256 Verification Costs Before & After EIP-7951
The Problem Before EIP-7951
Before this precompile, verifying a P-256 signature on Ethereum required implementing the full elliptic curve arithmetic in Solidity. This was not just expensive in gas terms; it was often prohibitively slow, error-prone, and impractical for production use. Hardware-backed signing through devices like the Secure Enclave or WebAuthn authenticators was theoretically possible but had no viable path to production on L1.
The problems ran deeper than gas costs. Every aspect of the user experience was built around assumptions that excluded mainstream users: you needed a seed phrase, you needed ETH in your wallet before you could do anything, and the only signing key Ethereum understood was one you managed yourself. There was no path from Touch ID to a transaction.

UX Before & After EIP-7951
What EIP-7951 Unlocks
Native P-256 verification on EVM chains opens up a category of use cases that were previously either impossible or impractical.
Passkey-native wallets. Users can sign transactions using device biometrics (Face ID, Touch ID, Windows Hello) without ever managing a seed phrase. The signing key lives in hardware and cannot be exported.
WebAuthn as on-chain authentication. FIDO2/WebAuthn devices generate P-256 signatures by default. These can now be verified directly in smart contracts, enabling hardware security keys as account signers.
Multi-factor authentication on-chain. A contract can require both a standard secp256k1 signature and a P-256 signature before executing sensitive operations. This is the basis for the 2FA pattern described below.
Enterprise and institutional signing. Hardware Security Modules (HSMs) and Trusted Execution Environments (TEEs) commonly use P-256. Organizations can now integrate these into their on-chain key management strategies without custom cryptography libraries.
Example 2FA Solidity Contract
The following contract holds funds and executes calls on behalf of its owner. It acts as the account: ETH/BERA lives in the contract, and all transactions originate from it. The owner never sends transactions directly. Instead, anyone (a relayer, a bundler, or the owner themselves) can submit a call to execute, and the contract verifies two things before proceeding: that the request was authorised by the owner's EOA key, and that the owner's hardware device confirmed it with a P-256 signature.

2FA Solidity Contract Sequence Diagram
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract TwoFactorAccount {
address public constant P256VERIFY = address(0x100);
// secp256k1 curve order — used for low-s malleability check
uint256 constant SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
address public owner;
uint256 public p256PublicKeyX;
uint256 public p256PublicKeyY;
uint256 public nonce;
constructor(
address _owner,
uint256 _p256x,
uint256 _p256y
) {
owner = _owner;
p256PublicKeyX = _p256x;
p256PublicKeyY = _p256y;
}
function execute(
address target,
uint256 value,
bytes calldata data,
uint8 v,
bytes32 r,
bytes32 s,
bytes32 p256R,
bytes32 p256S
) external {
// Contract computes the intent hash from calldata it already has.
// Binds target, value, calldata, nonce and chain to prevent replay.
bytes32 intentHash = keccak256(
abi.encodePacked(target, value, data, nonce, block.chainid)
);
bytes32 ethHash = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", intentHash)
);
// Signature malleability: enforce canonical low-s for the EOA sig.
// For any valid (r, s), (r, n-s) is also valid. Requiring s <= n/2
// ensures only one form is accepted, preventing replays via the
// alternate form.
require(uint256(s) <= SECP256K1_N / 2, "Non-canonical s");
// Verify owner EOA signature over the intent hash
address recovered = ecrecover(ethHash, v, r, s);
require(recovered == owner, "Invalid owner signature");
// Signature malleability: bind p256Hash to intentHash so the hardware
// sig is scoped to the same nonce-bound payload as the EOA sig.
// Prevents a stale P-256 sig from a previous tx being recycled.
bytes memory p256Input = abi.encodePacked(
intentHash,
p256R,
p256S,
p256PublicKeyX,
p256PublicKeyY
);
(bool success, bytes memory result) = P256VERIFY.staticcall(p256Input);
require(
success && result.length == 32 && uint256(bytes32(result)) == 1,
"Invalid P-256 signature"
);
// Both factors verified — execute from the contract
nonce++;
(bool executed,) = target.call{value: value}(data);
require(executed, "Execution failed");
}
receive() external payable {}
}
The contract holds the ETH and initiates every call via target.call. The submitter of the execute transaction pays gas but has no ability to alter what gets called: the target, value, and calldata are all bound into the signed intent hash. The owner signs that intent offline with their EOA key and confirms it on their hardware device, then hands both signatures to whoever is submitting. The nonce prevents the same pair of signatures from being replayed.
Taking It Further with EIP-7702
The contract above works well for newly deployed smart accounts. But what about existing EOAs that already hold assets and have history?
EIP-7702 solves this. It introduces a new transaction type that lets an EOA temporarily delegate its code to a smart contract implementation. In a single transaction, an EOA can point to the TwoFactorAccount implementation above, making it behave like a smart contract while keeping the same address and balance. The delegation is reversible: the EOA can remove it with another EIP-7702 transaction.
This means a user with an existing MetaMask wallet can upgrade it to require 2FA for high-value operations without migrating funds, without changing addresses, and without deploying a new contract. Their wallet address stays the same. Their ETH and tokens stay put. They simply gain additional validation logic.
A practical flow looks like this: the user submits an EIP-7702 authorization that delegates their EOA to the TwoFactorAccount implementation and registers their Secure Enclave public key. From that point on, sensitive transactions require both their existing private key and a Touch ID confirmation. The P-256 signature from the Secure Enclave is verified on-chain by the P256VERIFY precompile.
One important caveat: EIP-7702 does not make transactions gasless. The transaction still needs to be submitted by someone who can pay gas. In practice this means relying on a bundler or relayer service, which introduces a trust dependency that developers and users should account for in their threat model.
Security Caveats
Signature malleability. Unlike secp256k1 on Ethereum, P-256 signatures under NIST FIPS 186–5 are not required to be non-malleable. A valid signature (r, s) has a counterpart (r, n - s) that is also valid. Applications that require non-malleability, for example to prevent transaction replays in edge cases, must implement additional checks at the application layer. The nonce pattern shown in the example contract above handles replay protection, but developers should be explicit about this requirement.
Threats can include Mempool front-running or re-used P-256 signatures.

Signature Malleability Example
Demo & Full Code Repository
Here is a demo of the entire project and the full source code.
Project Demo
See the full interaction of the app here:
[embed]
GitHub Full Source Code
Full source code can be found here: https://github.com/berachain/guides/tree/main/apps/eip7951
Next Steps
EIP-7951 is a small addition to the EVM chains with significant practical implications. A single precompile at 0x100 and 6900 gas bridges the gap between the hardware authentication infrastructure already in billions of devices and the on-chain verification that EVM smart contracts need.
Combined with EIP-7702, it enables existing EOAs to gain smart account capabilities including hardware-backed 2FA without migration, without new addresses, and without abandoning existing tooling.
The 2FA pattern explored in this article is one application. Others worth exploring include:
- Passkey-native onboarding where new users never see a seed phrase and authenticate entirely through device biometrics.
- WebAuthn session keys for dApps that want hardware-grade authentication without wallet popups on every interaction.
- HSM-backed institutional signers for DAOs and protocols that need auditable, hardware-bound signing authority.
- Cross-chain identity using P-256 keys that are already recognised by enterprise blockchains and interoperability protocols.
- Recovery mechanisms where a Secure Enclave key acts as a guardian for account recovery without a centralised custodian.
The authentication hardware already exists in your users’ pockets. EIP-7951 gives EVM chains the ability to trust it.
If you want to build more on Berachain and see more examples. Take a look at our ***Berachain GitHub Guides Repo*** for a wide variety of implementations.
❤️ Don’t forget to show some love for this article 👏🏼.
메타데이터
- post_id
- 42c3ecbd3d38
- slug
- how-2fa-is-possible-thanks-to-eip-7951-42c3ecbd3d38
- url
- https://medium.com/@codingwithmanny/how-2fa-is-possible-thanks-to-eip-7951-42c3ecbd3d38
- canonical_url
- https://medium.com/@codingwithmanny/how-2fa-is-possible-thanks-to-eip-7951-42c3ecbd3d38
- author_url
- https://medium.com/@codingwithmanny
- status
- ok
- fetched_at
- 2026-08-05 17:50:33