Skip to content
Back to skills

2594 Signature Malleability 2fd4c4c6

ASecurity

- Contract uses ECDSA signatures for authorization or deduplication - Signatures are tracked by their raw bytes (e.g., `mapping(bytes => bool)`) to prevent replay - No enforcement that the `s` value is in the lower half of the curve order

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
securityrust

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2594-signature-malleability_2fd4c4c6 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2594 Signature Malleability 2fd4c4c6?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2594 Signature Malleability 2fd4c4c6
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2594-signature-malleability-2fd4c4c6/badge)](https://www.skillsdirectory.com/skills/tools-only-2594-signature-malleability-2fd4c4c6)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Signature Malleability

## Preconditions
- Contract uses ECDSA signatures for authorization or deduplication
- Signatures are tracked by their raw bytes (e.g., `mapping(bytes => bool)`) to prevent replay
- No enforcement that the `s` value is in the lower half of the curve order

## Vulnerable Pattern
```solidity
mapping(bytes => bool) public usedSignatures;

function claimReward(bytes memory signature, uint256 amount) external {
    // Deduplication by raw signature bytes
    require(!usedSignatures[signature], "already used");

    bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount));
    address signer = ecrecover(hash, v, r, s);
    require(signer == trustedSigner);

    usedSignatures[signature] = true; // Attacker submits (r, n-s, flipped_v)
    // to bypass this check with a valid but different signature
    _payout(msg.sender, amount);
}
```

## Detection Heuristics
1. Search for `mapping(bytes => bool)` or any mapping keyed by raw signature bytes
2. If signatures are used as mapping keys for deduplication, flag it — an attacker can compute the complementary `(r, n-s)` signature
3. Check if `ecrecover` is called directly without an `s`-value range check
4. Check if OpenZeppelin's ECDSA library is used (it enforces lower-s normalization)
5. If neither a library nor a manual `s < secp256k1n/2` check is present, flag it

## False Positives
- Signatures are deduplicated by message hash or nonce, not by raw signature bytes
- OpenZeppelin's `ECDSA.recover` is used, which rejects high-s signatures
- Manual check enforces `s <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0`

## Remediation
- Track used signatures by message hash or nonce, not by raw signature bytes
- Use OpenZeppelin's `ECDSA.recover` which enforces `s` in the lower half of the curve order
- If using raw `ecrecover`, add a manual `s`-value check
```solidity
// Use nonce-based deduplication instead of signature bytes
mapping(address => uint256) public nonces;

function claimReward(uint8 v, bytes32 r, bytes32 s, uint256 amount) external {
    uint256 nonce = nonces[msg.sender]++;
    bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount, nonce));
    address signer = ECDSA.recover(hash, v, r, s); // Rejects malleable sigs
    require(signer == trustedSigner);
    _payout(msg.sender, amount);
}
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.