With the deprecation of native precompiles on Solana (as outlined in SIMD-0568), support for the native KeccakSecp256k11111111111111111111111111111 precompile is being removed.
To support protocols that rely on secp256k1 ECDSA signature verification, this repository provides a configurable BPF-compatible alternative. It is split into two core components:
solana-secp256k1-verify: A fast, stateless, zero-allocation library for in-program verification.solana-secp256k1-program: A standalone SBF program to verify signatures on-chain.
If your protocol currently relies on the secp256k1 precompile, you can migrate to use this library and program in one of the following three approaches.
The most efficient approach is to bundle the verification logic directly into your own program using the solana-secp256k1-verify crate.
The library utilizes a builder pattern to configure the verifier prior to execution. By default, the verifier adheres strictly to Ethereum (EIP-2) standards by enforcing low-s signatures to prevent malleability.
Usage:
use solana_secp256k1_verify::{Secp256k1Verifier, EvmAddress};
// 1. Initialize the stateless verifier with standard EVM defaults
let verifier = Secp256k1Verifier::default();
// 2. Wrap your expected 20-byte Ethereum address
let expected_address = EvmAddress([0xab; 20]);
// 3. Execute the verification pipeline
verifier.verify_signature(
expected_address,
&signature, // &[u8; 64]
recovery_id, // u8 (0, 1, 2, or 3)
&message // &[u8] dynamic message payload
)?;Note: To support legacy systems that produce high-s signatures, the verifier can be configured to automatically mutate high-s signatures into valid low-s signatures using .auto_normalize_s(). You can also completely disable malleability checks using .allow_high_s().
If you prefer to offload the verification to a separate program, you can invoke solana-secp256k1-program via a Cross-Program Invocation (CPI).
Instruction Data Layout: The program expects a minimum of 85 bytes of instruction data, formatted as follows:
[0..20]: The 20-byte Ethereum address.[20..84]: The 64-byte signature.[84]: The 1-byte recovery ID.[85..]: The dynamic message payload.
You can easily construct this instruction using the provided SDK helper:
use solana_secp256k1_program::{verify, ID as SECP256K1_PROGRAM_ID};
use solana_cpi::invoke;
let instruction = verify(
&SECP256K1_PROGRAM_ID,
evm_address, // [u8; 20]
signature, // [u8; 64]
recovery_id, // u8
message, // &[u8]
);
// Submit via CPI
invoke(&instruction, &[])?;Historically, many legacy applications relied on placing the precompile invocation alongside their program's invocation in the transaction, and using the Instructions sysvar to inspect the sibling instruction.
You can replicate this legacy pattern by pushing the solana-secp256k1-program instruction to the transaction instead of the old precompile. Your on-chain program will need to:
- Load the
Instructionssysvar. - Find the sibling instruction and verify that its
program_idmatches the newsolana-secp256k1-program. - Parse its instruction data according to the layout described in the CPI section (
[0..20]for address,[20..84]for signature, etc.).
This repository is optimized for ultra-low compute unit consumption by leveraging a stateless, zero-allocation architecture (#![no_std]) and direct native syscall pipelines.
Because the underlying sol_secp256k1_recover syscall consumes a flat physical baseline of 25,000 CUs, this library aims to add as close to zero overhead as possible.
- Strict EVM (Default) -- ~25,316 CUs: Full Ethereum compliance (Keccak-256, 20-byte address match, Low-S enforced).
- Bare-Minimum Preset -- ~25,046 CUs: Pre-hashed payload, raw 64-byte pubkey match, and malleability checks bypassed.
For a precise breakdown of how each builder configuration flag affects the compute unit budget (such as allowing high-S signatures or mutating malleability), please see the detailed breakdown in the solana-secp256k1-verify documentation.