Skip to content
Security
Skill

/signature-malleability

Detect ECDSA signature malleability and ecrecover pitfalls — missing low-s (EIP-2) enforcement, unconstrained v, unchecked address(0) from ecrecover, replay across chainId/contract from a missing EIP-712 domain separator, EIP-2098 compact-signature confusion, and signature

From plugin
rugproof
1052 skills23 agents45 commands4 hooks

Claude Code

$ npx -y skills add omermaksutii/RugProof --skill signature-malleability --agent claude-code

SKILL.md

signature-malleability.SKILL.md
name: signature-malleability
description: Detect ECDSA signature malleability and ecrecover pitfalls — missing low-s (EIP-2) enforcement, unconstrained v, unchecked address(0) from ecrecover, replay across chainId/contract from a missing EIP-712 domain separator, EIP-2098 compact-signature confusion, and signature reuse. Activate whenever code calls ecrecover directly, parses (r,s,v) from bytes, or verifies signed messages without OpenZeppelin ECDSA.

Signature malleability detection

When this applies

Trigger on any of:

  • Direct `ecrecover(hash, v, r, s)` calls (not via OpenZeppelin `ECDSA.recover`)
  • Manual `(r, s, v)` decoding from a `bytes` signature with assembly
  • Permit / meta-tx / order-book / claim flows that verify a signed digest
  • Signatures used as nonces or dedup keys (e.g. `usedSig[sig] = true`)
  • EIP-712 typed-data verification, or its absence where one is needed

Detection patterns

Missing low-s enforcement / malleable sig (HIGH if sig is a key)

address signer = ecrecover(hash, v, r, s);   // no s-range check
require(signer == expected);
usedSignature[keccak256(abi.encode(r,s,v))] = true;  // ← dedup keyed on sig bytes

**Signal:** for any valid `(r,s,v)` the "flipped" signature `(r, n - s, v ^ 1)` recovers the *same* signer (the classic Bitcoin/Ethereum transaction-malleability class). If the signature itself is the replay key, an attacker submits the twin and bypasses dedup. Enforce `s <= secp256k1n/2` (EIP-2).

Unchecked ecrecover returning address(0) (HIGH)

address signer = ecrecover(hash, v, r, s);
require(signer == owner);  // if owner could ever be address(0)... or no check at all

**Signal:** malformed inputs make `ecrecover` return `address(0)`. Any code path where the compared-against value can be `address(0)` (uninitialized mapping slot, default) authenticates an attacker with garbage. Always `require(signer != address(0))`.

Unconstrained v (MEDIUM)

Accepting arbitrary `v` (not 27/28) or trusting a caller-supplied `v` for EIP-2098 compact sigs, mixing the 64-byte and 65-byte formats. EIP-2098 packs `yParity` into the high bit of `s`; decoding it as a raw 65-byte sig corrupts recovery.

Cross-domain / cross-chain replay (HIGH)

bytes32 digest = keccak256(abi.encode(ORDER_TYPEHASH, maker, amount, nonce));

**Signal:** no EIP-712 domain separator binding `chainId` and `verifyingContract`. A signature is replayable on another chain (post-fork) or a sibling deployment. Build the digest with `_hashTypedDataV4` (`\x19\x01` + domainSeparator + structHash) and recompute the domain separator if `block.chainid` changes.

Signature reuse / no nonce (HIGH)

Same digest accepted twice because there's no per-signer nonce or no marking of consumed digests — meta-tx and claim flows drain repeatedly.

Severity rubric

| Pattern | Severity | Notes | |---|---|---| | Malleable sig used as replay/dedup key | **High** | Twin sig bypasses guard | | ecrecover==address(0) authenticates attacker | **High** | Auth bypass | | Missing EIP-712 domain → cross-chain replay | **High** | Signature reuse across deployments | | No nonce → straight replay | **High** | Repeated execution | | Unconstrained v / EIP-2098 confusion | **Medium** | Format-dependent | | Low-s missing but sig never used as a key | **Low** | Defense-in-depth |

Remediation patterns

1. **Use OpenZeppelin `ECDSA.recover`** — it enforces `s <= secp256k1n/2`, rejects bad `v`, and reverts on `address(0)` (it also handles EIP-2098). 2. **Bind digests with EIP-712** (`EIP712` base + `_hashTypedDataV4`), including `chainId` and `verifyingContract`; recompute the domain separator on chainid change. 3. **Use per-signer nonces** (incrementing) rather than signature-bytes dedup; consume by `(signer, nonce)` not `(r,s,v)`. 4. **Explicit `require(signer != address(0))`** even when using libraries, for any hand-rolled path. 5. Prefer **`SignatureChecker`** when signers may be ERC-1271 contract wallets.

False-positive notes

  • Code already routing through OpenZeppelin `ECDSA`/`SignatureChecker` with EIP-712 and nonces is safe — don't re-flag the underlying `ecrecover`.
  • A sig verified against a *non-zero, always-set* signer where the digest is never reused is malleability-tolerant; note, don't escalate.

Related

  • [[signature-replay]] — nonce/chainId replay is the sibling concern
  • [[erc1271-contract-signatures]] — smart-wallet signers need `isValidSignature`
  • [[permit2-patterns]] — Permit2 / EIP-2612 digests and nonces
Read more
@omermaksutii
Ships withrugproof

Rugproof your code before someone else does. 🌐 Live site: omermaksutii.github.io/RugProof 📦 Latest: v1.0.0 — 45 commands · 23 agents · 45 skills · 13 MCP servers · tested, offline-first, with rule packs, a benchmark, non-EVM coverage, and post-deploy

1052 skills23 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
10
Star history of omermaksutii/RugProof on GitHub, a chart by star-history.com

Other skills on rugproof.

More skills from other plugins.

api-security

api-security

Use for authorized security assessment of REST, GraphQL, WebSocket, or SOAP APIs, including discovery, authentication,…

@zhaoxuya520@zhaoxuya520Install Skill
nemoclaw-user-guide

nemoclaw-user-guide

Find official NemoClaw documentation for installation, configuration, operation, or troubleshooting. Also handles requested docs…

nemoclaw11d23k
@nvidia@nvidiaInstall Skill

Questions about signature-malleability.

Which coding agents can use signature-malleability?
signature-malleability installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.