概述

P256 预编译合约为验证 secp256r1(P-256)椭圆曲线签名提供了原生支持,并实现了 EIP-7212。这使智能合约能够验证来自 WebAuthn 认证器、安全硬件模块以及其他使用 P-256 曲线系统的签名。 地址:0x0000000000000000000000000000000000000100 相关标准:RIP-7212

Gas 成本

固定成本:3,450 gas

方法

签名验证

该预编译合约暴露了一个未命名函数,用于验证 P-256 签名。 输入格式(160 字节):
  • 字节 0-31:message_hash(32 字节)- 消息的哈希值
  • 字节 32-63:r(32 字节)- 签名的 r 分量
  • 字节 64-95:s(32 字节)- 签名的 s 分量
  • 字节 96-127:x(32 字节)- 公钥的 x 坐标
  • 字节 128-159:y(32 字节)- 公钥的 y 坐标
输出格式(32 字节):
  • 如果签名有效,返回 0x0000...0001(1)
  • 如果签名无效,返回 0x0000...0000(0)

示例用法

// 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;
}

实现细节

曲线参数

该预编译合约使用 secp256r1(NIST P-256)椭圆曲线,参数如下:
  • 有限域素数:p = 2^256 - 2^224 + 2^192 + 2^96 - 1
  • 曲线方程:y² = x³ + ax + b,其中:
    • a = -3
    • b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b

输入校验

该预编译合约会执行以下校验:
  1. 输入必须严格为 160 字节
  2. 公钥坐标(x, y)必须是曲线上的有效点
  3. 签名分量(r, s)必须位于有效范围 [1, n-1] 内,其中 n 为曲线阶

安全注意事项

  • 该预编译合约只验证签名对于给定公钥在数学上是否有效
  • 应用还必须实现额外检查,例如:
    • 公钥认证(例如 WebAuthn 凭证验证)
    • 消息格式校验
    • 防止重放攻击

使用场景

WebAuthn 集成

P256 预编译合约使智能合约能够验证 WebAuthn 断言,从而支持:
  • 无密码认证
  • 硬件安全密钥支持
  • 通过兼容设备进行生物特征认证

安全硬件模块

许多安全元件和硬件安全模块使用 P-256 执行签名操作:
  • Apple Secure Enclave
  • Android Keystore(配置为 P-256 时)
  • TPM 2.0 模块
  • 智能卡

示例:WebAuthn 验证

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 优化

由于 gas 成本固定为 3,450,优化应重点关注:
  • 尽量减少签名验证次数
  • 在可能的情况下进行批量处理
  • 在适当时缓存验证结果

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 Related Standards: RIP-7212

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

// 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;
}

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