P256

secp256r1 (P-256) signature verification precompile for WebAuthn and secure hardware

Overview

The P256 precompile provides native support for verifying secp256r1 (P-256) elliptic curve signatures, implementing EIP-7212. This enables smart contracts to verify signatures from WebAuthn authenticators, secure hardware modules, and other systems using the P-256 curve.

Address: 0x0000000000000000000000000000000000000100

Gas Costs

Fixed cost: 3,450 gas

Method

Signature Verification

The precompile exposes a single unnamed function that verifies P-256 signatures.

Input Format (160 bytes):

  • Bytes 0-31: message_hash (32 bytes) - The hash of the message

  • Bytes 32-63: r (32 bytes) - The r component of the signature

  • Bytes 64-95: s (32 bytes) - The s component of the signature

  • Bytes 96-127: x (32 bytes) - The x coordinate of the public key

  • Bytes 128-159: y (32 bytes) - The y coordinate of the public key

Output Format (32 bytes):

  • Returns 0x0000...0001 (1) if signature is valid

  • Returns 0x0000...0000 (0) if signature is invalid

Example Usage

```solidity Solidity // P256 signature verification address constant P256_PRECOMPILE = 0x0000000000000000000000000000000000000100;

function verifyP256Signature( bytes32 messageHash, bytes32 r, bytes32 s, bytes32 x, bytes32 y ) external view returns (bool) { bytes memory input = abi.encodePacked(messageHash, r, s, x, y); (bool success, bytes memory result) = P256_PRECOMPILE.staticcall(input); if (!success || result.length != 32) { return false; } return uint256(bytes32(result)) == 1;

}


```javascript Ethers.js
const ethers = require('ethers');

// P256 precompile address
const P256_ADDRESS = '0x0000000000000000000000000000000000000100';

async function verifyP256Signature(provider, messageHash, r, s, x, y) {
    // Encode the input data
    const input = ethers.utils.concat([
        messageHash,
        r,
        s,
        x,
        y
    ]);

    // Call the precompile
    const result = await provider.call({
        to: P256_ADDRESS,
        data: input
    });

    // Check if signature is valid (result should be 0x00...01)
    return result === '0x' + '00'.repeat(31) + '01';
}

Implementation Details

Curve Parameters

The precompile uses the secp256r1 (NIST P-256) elliptic curve with the following parameters:

  • Field prime: p = 2^256 - 2^224 + 2^192 + 2^96 - 1

  • Curve equation: y² = x³ + ax + b where:

    • a = -3

    • b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b

Input Validation

The precompile performs the following validations:

  1. Input must be exactly 160 bytes

  2. Public key coordinates (x, y) must be valid points on the curve

  3. Signature components (r, s) must be within the valid range [1, n-1] where n is the curve order

Security Considerations

  • The precompile only verifies that a signature is mathematically valid for the given public key

  • Applications must implement additional checks such as:

    • Public key authentication (e.g., WebAuthn credential verification)

    • Message format validation

    • Replay attack prevention

Use Cases

WebAuthn Integration

The P256 precompile enables smart contracts to verify WebAuthn assertions, allowing for:

  • Passwordless authentication

  • Hardware security key support

  • Biometric authentication via compatible devices

Secure Hardware Modules

Many secure elements and hardware security modules use P-256 for signing operations:

  • Apple Secure Enclave

  • Android Keystore (when configured for P-256)

  • TPM 2.0 modules

  • Smart cards

Example: WebAuthn Verification

contract WebAuthnWallet {
    using bytes for bytes;

    struct Credential {
        bytes32 credentialId;
        uint256 publicKeyX;
        uint256 publicKeyY;
    }

    mapping(address => Credential) public credentials;

    function verify(
        bytes calldata authenticatorData,
        bytes calldata clientDataJSON,
        bytes32 r,
        bytes32 s
    ) external view returns (bool) {
        Credential memory cred = credentials[msg.sender];

        // Compute challenge hash according to WebAuthn spec
        bytes32 clientDataHash = sha256(clientDataJSON);
        bytes32 messageHash = sha256(abi.encodePacked(authenticatorData, clientDataHash));

        // Verify P-256 signature
        bytes memory input = abi.encodePacked(
            messageHash,
            r,
            s,
            bytes32(cred.publicKeyX),
            bytes32(cred.publicKeyY)
        );

        (bool success, bytes memory result) = address(0x100).staticcall(input);
        return success && result.length == 32 && uint256(bytes32(result)) == 1;
    }
}

Gas Optimization

Since the gas cost is fixed at 3,450, optimizations should focus on:

  • Minimizing the number of signature verifications

  • Batch processing where possible

  • Caching verification results when appropriate