iktok90-design/ai-smart-contract-auditor/signature-malleability

Claude Code and agent skill by iktok90-design/ai-smart-contract-auditor

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

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.

Install

npx vskill@latest install iktok90-design/ai-smart-contract-auditor/signature-malleability

SKILL.md

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)
solidity
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)
solidity
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)
solidity
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

PatternSeverityNotes
Malleable sig used as replay/dedup keyHighTwin sig bypasses guard
ecrecover==address(0) authenticates attackerHighAuth bypass
Missing EIP-712 domain → cross-chain replayHighSignature reuse across deployments
No nonce → straight replayHighRepeated execution
Unconstrained v / EIP-2098 confusionMediumFormat-dependent
Low-s missing but sig never used as a keyLowDefense-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

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
37
Repository forks
5
7-day trend score
18

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/iktok90-design/ai-smart-contract-auditor/signature-malleability/badge)](https://verified-skill.com/skills/iktok90-design/ai-smart-contract-auditor/signature-malleability)

Something wrong with this skill or its page? Tell us.