> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peaq.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# P-256 Signature Verification

> Verify hardware-backed P-256 signatures from robots and machines on peaq.

P-256, also called `secp256r1`, is widely supported by secure elements, TPMs, passkeys, mobile secure enclaves, and industrial hardware. On peaq, you can use it to verify that a machine action was signed by the private key associated with an enrolled P-256 public key.

<Note>
  P-256 verification is available on **peaq mainnet (chain ID 3338)** today. The audited Daimo verifier is deployed at `0xc2b78104907F722DABAc4C69f826a522B2754De4`.
</Note>

<img src="https://mintcdn.com/peaq/SD4WUj6RIPNCtCCy/assets/img/p256-verification-on-peaq.png?fit=max&auto=format&n=SD4WUj6RIPNCtCCy&q=85&s=699d24f586a0dc94828141c366024d27" alt="P-256 signatures move from machine hardware through application policy to onchain verification and authorization." width="3680" height="1440" data-path="assets/img/p256-verification-on-peaq.png" />

## How verification works

1. A machine hashes an action and signs the hash with a P-256 private key held by its secure hardware.
2. The application checks its policy, including the active public key, chain ID, nonce, expiry, and revocation status.
3. A smart contract verifies the signature onchain.
4. The application authorizes the requested payment, command, access, or data operation.

P-256 proves possession of the private key. It does not prove that the key came from genuine hardware; use trusted enrollment or hardware attestation when provenance matters.

## Choose an integration path

| Path                           | Availability         |        Approximate gas | Use when                                                                                                  |
| ------------------------------ | -------------------- | ---------------------: | --------------------------------------------------------------------------------------------------------- |
| OpenZeppelin `P256.verify()`   | Available now        |        330,000-400,000 | You want a portable integration that can use the future precompile automatically.                         |
| Daimo verifier singleton       | Available now        |          About 330,000 | You want to call the deployed verifier directly or preserve the same address across supported EVM chains. |
| RIP-7212 precompile at `0x100` | Not live on peaq yet | 3,450 after activation | Wait until a peaq runtime upgrade activates it.                                                           |

Gas usage varies with compiler settings and input data. Do not call `0x100` on peaq until the precompile is announced as live.

## Use OpenZeppelin P256

[OpenZeppelin Contracts 5.1 or later](https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#P256) provides a portable `P256.verify()` function. It checks for the RIP-7212 precompile and falls back to its Solidity implementation when the precompile is unavailable.

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
npm install @openzeppelin/contracts
```

```solidity P256Example.sol theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {P256} from "@openzeppelin/contracts/utils/cryptography/P256.sol";

contract P256Example {
    function verifyMachineSignature(
        bytes32 messageHash,
        bytes32 r,
        bytes32 s,
        bytes32 publicKeyX,
        bytes32 publicKeyY
    ) external view returns (bool) {
        return P256.verify(messageHash, r, s, publicKeyX, publicKeyY);
    }
}
```

The same deployed contract will use the lower-cost native path after the RIP-7212 precompile becomes available.

## Call the mainnet verifier directly

The Daimo singleton accepts the RIP-7212-shaped 160-byte payload directly. There is no function selector or ABI wrapper.

```solidity PeaqP256Verifier.sol theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library PeaqP256Verifier {
    address internal constant P256_VERIFIER =
        0xc2b78104907F722DABAc4C69f826a522B2754De4;

    function verify(
        bytes32 messageHash,
        bytes32 r,
        bytes32 s,
        bytes32 publicKeyX,
        bytes32 publicKeyY
    ) internal view returns (bool) {
        (bool ok, bytes memory output) = P256_VERIFIER.staticcall(
            abi.encodePacked(messageHash, r, s, publicKeyX, publicKeyY)
        );

        return ok && output.length == 32 && abi.decode(output, (uint256)) == 1;
    }
}
```

The result check handles both verifier outputs safely: the singleton returns a 32-byte `0` for an invalid signature, while a conforming RIP-7212 precompile returns empty output.

## Build the 160-byte input

Concatenate five 32-byte, big-endian fields in this exact order:

| Byte range | Field         | Description                               |
| ---------- | ------------- | ----------------------------------------- |
| `0-31`     | `messageHash` | The 32-byte hash that the machine signed. |
| `32-63`    | `r`           | Signature `r`, padded to 32 bytes.        |
| `64-95`    | `s`           | Signature `s`, padded to 32 bytes.        |
| `96-127`   | `publicKeyX`  | Affine public-key X coordinate.           |
| `128-159`  | `publicKeyY`  | Affine public-key Y coordinate.           |

<Warning>
  The verifier does not hash the message for you. For a plain ECDSA-SHA256 flow, pass `sha256(message)`. For WebAuthn, construct the signed hash according to the WebAuthn assertion format.
</Warning>

Before submitting the payload:

* Decode DER signatures into raw `r` and `s` values.
* Remove the `0x04` prefix from an uncompressed SEC1 public key. Decompress compressed keys offchain.
* Confirm the stored public key is active for the machine and the requested action.

## Verify through an RPC call

Use `eth_call` when verification is needed offchain. It executes the verifier without submitting a transaction or spending gas.

```typescript ethers.ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
import { JsonRpcProvider, concat } from "ethers";

const provider = new JsonRpcProvider("https://peaq.api.onfinality.io/public");
const verifier = "0xc2b78104907F722DABAc4C69f826a522B2754De4";

const payload = concat([
  messageHash,
  signatureR,
  signatureS,
  publicKeyX,
  publicKeyY,
]);

const output = await provider.call({ to: verifier, data: payload });
const valid = output.length === 66 && BigInt(output) === 1n;
```

Each input value in the example must be a 32-byte hex string.

## Production checklist

* Bind each public key to the intended machine identity through an authenticated enrollment flow.
* Include the application domain, peaq chain ID, action, nonce, and expiry in the signed data.
* Store or invalidate nonces to prevent replay attacks.
* Define key rotation and revocation before accepting signatures in production.
* Do not use signature bytes as a unique identifier; P-256 signatures can be malleable.
* Require hardware attestation when you need proof of device provenance, not only proof of key possession.

## References

* [OpenZeppelin P256](https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#P256)
* [Daimo P256 verifier and audit](https://github.com/daimo-eth/p256-verifier)
* [RIP-7212 specification](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md)
