Ed25519 Signature Malleability
Detects Ed25519 signature verification without canonical signature checks, allowing signature malleability attacks.
Ed25519 Signature Malleability
Overview
The Ed25519 signature malleability detector identifies Solana programs that verify Ed25519 signatures without enforcing canonical form. Non-canonical signatures allow attackers to produce alternative valid signatures for the same message, bypassing deduplication or single-use signature assumptions. For remediation steps, see the Ed25519 Signature Malleability Remediation.
Why This Is an Issue
Ed25519 signatures have a malleability property: for any valid signature (R, S), the pair (R, L - S) where L is the curve order is also valid for the same message. This means each message can have at least two valid signatures. Programs that rely on signature uniqueness — such as tracking which signatures have been used, deduplicating transactions, or enforcing one-time authorization — can be bypassed.
In practice, an attacker can:
- Replay transactions by submitting the malleable variant of an already-used signature.
- Bypass deduplication in airdrop, claim, or voting systems that track used signatures.
- Forge alternative proofs that pass verification but differ from the original.
The canonical form requires S < L (where L is the Ed25519 curve order). Without this check, both S and L - S are accepted as valid.
How to Resolve
// Before: Vulnerable -- non-strict verification
let result = ed25519_verify(&pubkey, &message, &signature);
if !result {
return Err(ProgramError::InvalidArgument);
}
// Signature passes but may be non-canonical
// After: Fixed -- use strict verification
let result = ed25519_verify_strict(&pubkey, &message, &signature);
if !result {
return Err(ProgramError::InvalidArgument);
}
// Only canonical signatures accepted
Examples
Vulnerable Code
pub fn verify_and_store(
accounts: &[AccountInfo],
signature: &[u8; 64],
message: &[u8],
) -> ProgramResult {
let pubkey = accounts[0].key.to_bytes();
// Non-strict verification accepts both canonical and non-canonical
if !ed25519_verify(&pubkey, message, signature) {
return Err(ProgramError::InvalidArgument);
}
// Store signature as "used" -- attacker can bypass with malleable variant
let used_sigs = &mut accounts[1];
store_used_signature(used_sigs, signature)?;
Ok(())
}
Fixed Code
pub fn verify_and_store(
accounts: &[AccountInfo],
signature: &[u8; 64],
message: &[u8],
) -> ProgramResult {
let pubkey = accounts[0].key.to_bytes();
// Enforce canonical signature form: S < L
if signature[63] & 0xE0 != 0 {
return Err(ProgramError::InvalidArgument);
}
if !ed25519_verify(&pubkey, message, signature) {
return Err(ProgramError::InvalidArgument);
}
let used_sigs = &mut accounts[1];
store_used_signature(used_sigs, signature)?;
Ok(())
}
Example JSON Finding
{
"detector": "ed25519-signature-malleability",
"severity": "high",
"confidence": 0.80,
"title": "Ed25519 Signature Verification Without Canonical Check",
"description": "Ed25519 signature verification is performed without checking for canonical signature form.",
"cwe_ids": [347]
}
Detection Methodology
- Syscall identification: Identifies Ed25519 verification syscalls by name pattern matching (e.g.,
ed25519_verify,sol_ed25519_check). - Strict variant detection: Checks whether the program uses
verify_strictorcanonicalverification variants. - Canonical check search: Scans for manual canonical validation patterns such as bit mask checks (
signature[63] & 0xE0 == 0) or comparisons against the curve order. - Signature storage tracking: Detects when signatures are stored to account data without normalization, indicating deduplication vulnerability.
Limitations
False positives: Programs that implement canonical checks in external library calls or precompiled instructions may be flagged. False negatives: Custom signature verification that does not use recognized syscall names will not be detected. Programs using the Ed25519 precompile instruction via instruction introspection follow a different pattern that may not be fully covered.
Related Detectors
- Signature Replay — detects missing replay protection for signatures
- Weak Randomness — detects insecure cryptographic patterns