Ed25519SigVerify111111111111111111111111111: Solana's signature-verification workhorse
The Ed25519 precompile verifies arbitrary signed messages inside a transaction — no accounts, no CPI, just instruction introspection. The wire format, the sysvar pattern, the security footguns, and when to use it.
devrels.xyz/a/131short linkSolana already verifies Ed25519 signatures on every transaction — that's how fee payers and signers work. But those signatures cover the transaction message. The moment your program needs to verify a signature over arbitrary bytes — a signed airdrop voucher, an oracle price update, an intent signed by a key that isn't a transaction signer — you need Ed25519SigVerify111111111111111111111111111, the Ed25519 precompile.
It's one of the oldest and most-used pieces of Solana's crypto plumbing, and also one of the most misused. Here's the full picture.
What a precompile is
Precompiles aren't BPF programs. They execute natively in the validator, outside the SVM, before your transaction's instructions run. They take no accounts — everything is in instruction data — and if verification fails, the entire transaction fails before any program executes. Solana has three signature-verification precompiles:
Ed25519SigVerify111111111111111111111111111 Ed25519 (Solana-native curve)
KeccakSecp256k11111111111111111111111111111 secp256k1 (Ethereum/Bitcoin keys)
Secp256r1SigVerify1111111111111111111111111 secp256r1 / P-256 (passkeys, SIMD-0075)Same architecture, three curves. The secp256k1 one lets you verify Ethereum-signed messages (bridges use this); the r1 one powers passkey wallets. Everything below about the Ed25519 wire format and introspection pattern applies to all three.
The wire format
One precompile instruction can verify multiple signatures. Instruction data is a count, padding, then an array of offset structs telling the runtime where to find each (signature, pubkey, message) triple:
byte 0 num_signatures (u8)
byte 1 padding
bytes 2+ Ed25519SignatureOffsets[num_signatures], each 14 bytes:
signature_offset u16 // 64-byte signature
signature_instruction_index u16
public_key_offset u16 // 32-byte pubkey
public_key_instruction_index u16
message_data_offset u16
message_data_size u16
message_instruction_index u16
instruction_index = 0xFFFF → "this instruction"
anything else → that instruction's data, by indexThe instruction-index fields are the interesting part: the signature, pubkey, and message don't have to live in the precompile instruction itself — they can point into any instruction in the transaction. That enables compact layouts (point at data your program instruction already carries) and, as we'll see, it's also where the security bugs live.
Fees: precompile signatures are priced like transaction signatures — each verified signature adds to the transaction's signature fee. Cheap, but not free.
The introspection pattern
Here's the counterintuitive part: your program cannot call the precompile via CPI. Precompiles only run as top-level transaction instructions. So the pattern is always two instructions in one transaction:
ix 0 Ed25519SigVerify… verifies sig(pubkey, message)
ix 1 your program introspects ix 0, then acts on the messageYour program reads instruction 0 through the Instructions sysvar (Sysvar1nstructions1111111111111111111111111):
use solana_program::sysvar::instructions::{load_instruction_at_checked};
let ix = load_instruction_at_checked(0, &instructions_sysvar)?;
// 1. It must actually be the precompile
require_keys_eq!(ix.program_id, solana_program::ed25519_program::ID);
// 2. Parse the offsets header and verify it references the
// pubkey + message you expect — not just "a valid signature"
let (pubkey, message) = parse_ed25519_ix_data(&ix.data)?;
require_keys_eq!(pubkey, expected_signer);
require!(message == expected_message, ErrorCode::SigMismatch);The logic: if your program's instruction is executing at all, the precompile instruction in the same transaction must have succeeded (a failed precompile kills the whole transaction). So "the precompile ran, on this exact pubkey and message" implies "this signature is valid." Your program never does curve math — it does bookkeeping.
The footguns
Every one of these is a real exploit class, not a theoretical one:
- Checking that a precompile ran, but not what it verified. The attacker submits a valid signature from their own key over their own message. If your program only checks "instruction 0 is the Ed25519 program," you approved it. You must parse the offsets and compare pubkey and message byte-for-byte.
- Trusting offsets blindly. The attacker builds the precompile instruction, so they control every offset — including instruction indexes pointing at other instructions. Parse defensively: exact header sizes, expected
num_signatures, offsets pointing where your layout says they must (most programs require0xFFFF/self-contained data and reject anything else). - Hardcoding instruction position without checking. Use
load_instruction_at_checkedagainst the sysvar and verify the program id — don't assume "the instruction before mine" is the precompile. A transaction can contain arbitrary extra instructions. - No replay protection. The precompile proves the message was signed — not that it was signed for this transaction. A signed voucher without a nonce/expiry/ domain-separator can be replayed forever. Put a nonce or a one-time account in the signed message and mark it consumed.
- Verifying in-program instead. Doing Ed25519 verification inside your program with a dalek-style crate costs on the order of tens of thousands of CUs (~11k for the curve work plus ~8k per SHA-512 block of message) and bloats your program. The precompile exists so you don't do this.
What people build with it
- Signed vouchers / claims — backend signs "wallet X may claim Y," program verifies via precompile. No per-user on-chain allowlist needed.
- Oracle-style attestations — any off-chain service with an Ed25519 key can feed signed data to programs without being a transaction signer.
- Relayed intents / session keys — user signs a message once; a relayer submits it. Same skeleton as the passkey smart-wallet pattern, just on Solana's native curve.
- Cross-system verification — anything that signs Ed25519 off-chain (TEEs, other chains, hardware) can be verified on Solana for the price of a signature fee.
TL;DR
Ed25519SigVerify111…verifies signatures over arbitrary messages; transaction signatures only cover the transaction. One of three curve precompiles (k1 for Ethereum keys, r1 for passkeys).- Not CPI-able. The pattern is: precompile instruction + your instruction, glued by the Instructions sysvar.
- The precompile proves "a valid signature exists in this transaction." Your program must prove it's the right signer, the right message, and not a replay — that's where audits find bugs.
- Never verify Ed25519 in-program; the precompile does it at native speed for a signature fee.
Keep reading
Quantum computers don't break hashes — they break elliptic curves. That single fact shapes Solana's entire post-quantum roadmap: PDAs are already safe, Winternitz one-time signatures ship today via Winterwallet, and Falcon-512 plus bigger transactions finish the job. A walk through the threat model and the practical moves, building on Blueshift's research.
Zero-knowledge on Solana is two parallel stacks that share almost nothing: a curve25519 sigma-protocol stack purpose-built for confidential transfers, and a BN254 SNARK stack (alt_bn128 syscalls + Groth16) that powers ZK compression and general-purpose verifiers. Here's the full map — syscalls, programs, provers, costs, and the mid-2025 soundness bug that reshaped the roadmap.
Your wallet is your identity. Sign In With Solana turns that into a real auth protocol: a structured message, a wallet signature, and a server that verifies the Ed25519 sig and issues a session. No email required. Here's how to implement the full stack — frontend signIn call, backend nonce management, signature check, and JWT — plus the security edge cases that trip people up.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.