Precompiled contracts
Precompiles are contracts implemented natively by the client rather than in EVM bytecode. They live at fixed low addresses, take raw bytes with no ABI encoding and no function selector, and are priced by their own gas formulas instead of by opcode.
Frost runs the Osaka rules from genesis, so it has every precompile Ethereum mainnet has, plus 2 of its own: the ML-DSA post-quantum signature verifiers at 0x14 and 0x15. Those two are what make post-quantum accounts affordable — verifying a lattice signature in Solidity would cost millions of gas, while the precompile does it for the price of a handful of ECDSA recoveries.
| Address | Name | Min gas | Input | Output | Description |
|---|---|---|---|---|---|
| 0x01 | ECREC | 3,000 | 128 bytes | 32 bytes, or empty on failure | Recover the address that produced an ECDSA signature over a 32-byte hash. |
| 0x02 | SHA256 | 60 | variable | 32 bytes | SHA2-256 hash of the input. |
| 0x03 | RIPEMD160 | 600 | variable | 32 bytes | RIPEMD-160 hash of the input, left-padded to 32 bytes. |
| 0x04 | ID | 15 | variable | same as input | Return the input unchanged — the cheapest way to copy memory. |
| 0x05 | MODEXP | 500 | variable, 96-byte header + operands | modLen bytes | Arbitrary-precision modular exponentiation: base^exponent mod modulus. |
| 0x06 | BN254_ADD | 150 | 128 bytes | 64 bytes | Point addition on the alt_bn128 (BN254) curve. |
| 0x07 | BN254_MUL | 6,000 | 96 bytes | 64 bytes | Scalar multiplication on the alt_bn128 (BN254) curve. |
| 0x08 | BN254_PAIRING | 45,000 | a multiple of 192 bytes | 32 bytes | Optimal ate pairing check on alt_bn128 — the workhorse of most zkSNARK verifiers. |
| 0x09 | BLAKE2F | 0 | 213 bytes | 64 bytes | The BLAKE2b compression function F, run for a caller-specified number of rounds. |
| 0x0a | KZG_POINT_EVALUATION | 50,000 | 192 bytes | 64 bytes | Verify a KZG proof that a blob polynomial evaluates to a claimed value at a point. |
| 0x0b | BLS12_G1ADD | 375 | 256 bytes | 128 bytes | Point addition in the BLS12-381 G1 group. |
| 0x0c | BLS12_G1MSM | 12,000 | a multiple of 160 bytes | 128 bytes | Multi-scalar multiplication in the BLS12-381 G1 group. |
| 0x0d | BLS12_G2ADD | 600 | 512 bytes | 256 bytes | Point addition in the BLS12-381 G2 group. |
| 0x0e | BLS12_G2MSM | 22,500 | a multiple of 288 bytes | 256 bytes | Multi-scalar multiplication in the BLS12-381 G2 group. |
| 0x0f | BLS12_PAIRING_CHECK | 37,700 | a multiple of 384 bytes | 32 bytes | Pairing check on BLS12-381. |
| 0x10 | BLS12_MAP_FP_TO_G1 | 5,500 | 64 bytes | 128 bytes | Map a base field element to a point in G1. |
| 0x11 | BLS12_MAP_FP2_TO_G2 | 23,800 | 128 bytes | 256 bytes | Map an extension field element to a point in G2. |
| 0x14 | VERIFY_MLDSA65 PQ | 16,000 | 5293 bytes (exact) | 32 bytes, always | Verify an ML-DSA-65 (FIPS 204) post-quantum signature. |
| 0x15 | VERIFY_MLDSA44 PQ | 10,500 | 3764 bytes (exact) | 32 bytes, always | Verify an ML-DSA-44 (FIPS 204) post-quantum signature. |
| 0x0100 | P256VERIFY | 6,900 | 160 bytes (exact) | 32 bytes, or empty on failure | Verify a secp256r1 (NIST P-256) ECDSA signature. |
How precompile calls work
A precompile is invoked like any other contract, usually with staticcall. Three things differ from a normal call:
- No ABI. The input is a flat byte string laid out at fixed offsets. There is no 4-byte selector and no 32-byte padding of arguments unless the layout says so.
- Gas is charged up front. The client computes the cost from the input before running anything, and the call fails with out-of-gas if you supplied less. You pay the full price whether or not the operation succeeds.
- Failure is not uniform. Some precompiles revert, some return a zero word, and some return empty data with no error. That last group is the dangerous one — see below.
Failure conventions
Three different conventions coexist, and mixing them up is a real source of bugs:
| Convention | Precompiles | What a naive caller does wrong |
|---|---|---|
| Reverts | 0x05, 0x06–0x09, 0x0a–0x11 | Nothing — the call bubbles up a failure you cannot miss. |
| Returns empty data, no error | 0x01 ECREC, 0x0100 P256VERIFY | Reads a stale or zeroed memory slot as a successful result. You must check the returned data length. |
| Returns a 32-byte 0 or 1, always | 0x14, 0x15 (Frost) | Nothing. This is deliberate: there is no empty-return case to misread. |
Frost's verifiers were specified into the third group on purpose. A verifier that can return empty on malformed input invites a caller to treat "no output" as "verified", which is the difference between an account that checks a signature and an account that anyone can spend from. 0x14 and 0x15 return exactly one 32-byte word on every path, including wrong-length input.
Address space
Frost leaves a deliberate gap: standard Ethereum precompiles occupy 0x01–0x11, Frost's post-quantum verifiers sit at 0x14 and 0x15, and 0x0100 holds P256VERIFY per EIP-7951. The gap at 0x12–0x13 is reserved and unoccupied.
Precompiles are not the whole address map. Frost also ships several predeploys and system contracts — real accounts with real bytecode at fixed addresses, including the CREATE2 factory that counterfactual account addresses are derived against.