Signature Replay Analysis logo

Signature Replay Analysis

Organization
quillai-network
signature-replay-analysis

Detects signature replay vulnerabilities in smart contracts — affecting 19.63% of signature-using contracts. Covers five replay types (same-chain, cross-chain, cross-contract, nonce-skip, expired-signature), EIP-712 domain separator verification, nonce management analysis, ecrecover edge cases (address(0), malleability, s-value), permit/permit2 safety, ERC-1271 contract wallet support, and meta-transaction security. Use when auditing contracts with ecrecover, ECDSA, EIP-712, permit, meta-transactions, multi-sig, or any off-chain signature verification.

Overview

Publisherquillai-network
Repositoryquillshield_skills
Skill namesignature-replay-analysis
Stars
131
Forks
28
Bundled files
2
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 2 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by quillai-network on GitHub. Read the source before you install it.

Installation

Install the Signature Replay Analysis AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/quillai-network/quillshield_skills/tree/main/plugins/signature-replay-analysis/skills/signature-replay-analysis
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/quillai-network/quillshield_skills.git /tmp/quillshield_skills
mkdir -p .claude/skills
cp -r /tmp/quillshield_skills/plugins/signature-replay-analysis/skills/signature-replay-analysis .claude/skills/signature-replay-analysis
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Signature Replay Analysis in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Signature Replay Analysis on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Signature Replay Analysis is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Signature & Replay Analysis

Detect vulnerabilities where cryptographic signatures can be reused, replayed across chains/contracts, or exploited through implementation flaws. Research shows 19.63% of Ethereum contracts using signatures contain replay vulnerabilities.

When to Use

  • Auditing contracts that verify signatures (ecrecover, ECDSA, EIP-712)
  • Reviewing ERC-20 permit() / Uniswap Permit2 implementations
  • Analyzing meta-transaction / gasless relay systems
  • Verifying multi-sig signature aggregation
  • Checking off-chain order books or signed message execution

When NOT to Use

  • Contracts without any signature verification
  • Pure on-chain access control (use semantic-guard-analysis)
  • Token standard compliance (use external-call-safety)

Core Concept: The Signature Trust Model

A signature proves that a specific private key holder authorized a specific action. For this to be secure, the signature must be:

  1. Bound to context — specific chain, contract, and version (domain separation)
  2. Used exactly once — nonce prevents replay
  3. Time-limited — deadline/expiry prevents late execution
  4. Correctly verified — ecrecover edge cases handled

Any gap in this model creates a replay vulnerability.

The Five Replay Types

Type 1: Same-Chain Replay

The exact same signature is submitted multiple times to the same contract on the same chain.

solidity
// VULNERABLE: No nonce — same signature works forever
function executeWithSig(address to, uint256 amount, bytes memory signature) external {
    bytes32 hash = keccak256(abi.encodePacked(to, amount));
    address signer = ECDSA.recover(hash, signature);
    require(signer == admin, "Invalid signer");
    token.transfer(to, amount);
    // Attacker can submit this same signature again and again!
}

// SAFE: Use nonce
mapping(address => uint256) public nonces;

function executeWithSig(address to, uint256 amount, uint256 nonce, bytes memory signature) external {
    require(nonce == nonces[admin], "Invalid nonce");
    bytes32 hash = keccak256(abi.encodePacked(to, amount, nonce));
    address signer = ECDSA.recover(hash, signature);
    require(signer == admin, "Invalid signer");
    nonces[admin]++;
    token.transfer(to, amount);
}

Type 2: Cross-Chain Replay

A signature valid on one chain (e.g., Ethereum) is replayed on another chain (e.g., Polygon, Arbitrum) where the same contract is deployed.

solidity
// VULNERABLE: No chainId in signed message
bytes32 hash = keccak256(abi.encodePacked(to, amount, nonce));
// This hash is identical on Ethereum, Polygon, Arbitrum, etc.

// SAFE: Include chainId (via EIP-712 domain separator)
bytes32 DOMAIN_SEPARATOR = keccak256(abi.encode(
    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
    keccak256(bytes("MyContract")),
    keccak256(bytes("1")),
    block.chainid,
    address(this)
));

Type 3: Cross-Contract Replay

A signature for Contract A is replayed on Contract B (same chain) if both accept the same message format without contract-specific binding.

solidity
// VULNERABLE: No contract address in signed message
bytes32 hash = keccak256(abi.encodePacked(to, amount, nonce, block.chainid));
// Same hash for any contract on this chain

// SAFE: Include verifyingContract (via EIP-712)
// The domain separator includes address(this), binding to this specific contract

Type 4: Nonce-Skip Replay

Nonce implementation allows gaps or out-of-order execution, enabling skipped nonces to be replayed later.

solidity
// VULNERABLE: Bitmap nonce without invalidation
mapping(uint256 => bool) public usedNonces;

function execute(uint256 nonce, ...) external {
    require(!usedNonces[nonce], "Used");
    usedNonces[nonce] = true;
    // If nonces 1, 2, 3 are used but 4 is skipped,
    // nonce 4 can be used anytime in the future
    // This may be intentional OR a vulnerability depending on context
}

// SAFER for strict ordering: Sequential nonce
mapping(address => uint256) public nonces;

function execute(uint256 nonce, ...) external {
    require(nonce == nonces[signer], "Invalid nonce");
    nonces[signer]++;
}

Type 5: Expired-Signature Replay

A signature without a deadline can be held and executed at an arbitrary future time when conditions have changed.

solidity
// VULNERABLE: No deadline — signature valid forever
function permit(address owner, address spender, uint256 value, uint8 v, bytes32 r, bytes32 s) external {
    bytes32 hash = keccak256(abi.encodePacked(owner, spender, value, nonces[owner]++));
    require(ecrecover(hash, v, r, s) == owner, "Invalid");
    allowance[owner][spender] = value;
    // This permit can be executed weeks later when user doesn't expect it
}

// SAFE: Include deadline
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
    require(block.timestamp <= deadline, "Expired");
    // ... rest of verification
}

ecrecover Safety

Edge Case 1: Returns address(0)

ecrecover returns address(0) for invalid signatures instead of reverting.

solidity
// VULNERABLE: address(0) accepted as valid signer
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "Invalid");
// If owner == address(0) AND signature is invalid → passes!

// SAFE: Explicit zero check
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "Invalid signature");
require(signer == owner, "Wrong signer");

// SAFEST: Use OpenZeppelin's ECDSA.recover() — reverts on address(0)
address signer = ECDSA.recover(hash, signature);

Edge Case 2: Signature Malleability

For every valid ECDSA signature (r, s, v), there exists a second valid signature (r, s', v') for the same message. This allows anyone to create an alternate valid signature without the private key.

solidity
// The Ethereum standard: s must be in the lower half of the curve
// s' = secp256k1n - s (the "flipped" signature)

// VULNERABLE: Accepts both s values
address signer = ecrecover(hash, v, r, s); // Works for both s and s'
// If used as a unique identifier, the same message has TWO valid signatures

// SAFE: Enforce lower-s (OpenZeppelin's ECDSA library does this)
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "Invalid s");

Edge Case 3: v Value

solidity
// v should be 27 or 28 (Ethereum standard)
// Some implementations use 0 or 1 (subtract 27)
// Not normalizing v can cause signature verification to fail

require(v == 27 || v == 28, "Invalid v");

EIP-712 Domain Separator Verification

Complete Domain

solidity
bytes32 constant DOMAIN_TYPEHASH = keccak256(
    "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);

bytes32 DOMAIN_SEPARATOR = keccak256(abi.encode(
    DOMAIN_TYPEHASH,
    keccak256(bytes(name)),        // Contract name
    keccak256(bytes(version)),     // Version string
    block.chainid,                 // Chain ID — prevents cross-chain replay
    address(this)                  // Contract address — prevents cross-contract replay
));

Required Fields

FieldPurposeMissing =
nameIdentifies the signing domainMEDIUM risk
versionPrevents replay across upgradesMEDIUM risk
chainIdPrevents cross-chain replayHIGH risk
verifyingContractPrevents cross-contract replayHIGH risk
salt (optional)Additional disambiguationLOW risk

Common Mistakes

solidity
// MISTAKE 1: Hardcoded chainId (doesn't update on chain forks)
uint256 immutable CHAIN_ID = 1;
// After a fork, signatures valid on both chains!

// SAFE: Use block.chainid at verification time, or recalculate domain separator
function DOMAIN_SEPARATOR() public view returns (bytes32) {
    if (block.chainid == INITIAL_CHAIN_ID) return _DOMAIN_SEPARATOR;
    return _calculateDomainSeparator(); // Recalculate for new chain
}

// MISTAKE 2: Empty name/version
keccak256(bytes("")) // Valid but weak — same across all contracts with empty name

// MISTAKE 3: Missing struct type hash in message
// EIP-712 requires: hashStruct(message) = keccak256(typeHash + encodeData(message))
// Omitting typeHash weakens the domain binding

Permit and Permit2 Verification

ERC-2612 Permit Checklist

- [ ] Uses EIP-712 domain separator with chainId and verifyingContract
- [ ] Includes per-user sequential nonce
- [ ] Includes deadline with block.timestamp check
- [ ] Uses ECDSA.recover (not raw ecrecover)
- [ ] Checks recovered address != address(0)
- [ ] Checks recovered address == owner parameter
- [ ] Nonce incremented BEFORE any state change
- [ ] Domain separator recalculated on chain fork

Permit2 Considerations

- Permit2 uses nonce-bitmap approach (unordered nonces)
- Supports batch permits and transfer-with-permit
- Still requires deadline, domain separator, nonce management
- Contracts integrating Permit2 must verify the permit2 contract address

Workflow

Task Progress:
- [ ] Step 1: Find all signature verification code (ecrecover, ECDSA.recover, EIP-712)
- [ ] Step 2: Check for same-chain replay protection (nonce management)
- [ ] Step 3: Check for cross-chain replay protection (chainId in domain/message)
- [ ] Step 4: Check for cross-contract replay protection (address(this) in domain/message)
- [ ] Step 5: Check deadline/expiry enforcement
- [ ] Step 6: Verify ecrecover safety (address(0) check, s-value, v-value)
- [ ] Step 7: Verify EIP-712 domain separator completeness
- [ ] Step 8: Check ERC-1271 support for contract wallets (if applicable)
- [ ] Step 9: Score findings and generate report

Output Format

markdown
## Signature & Replay Analysis Report

### Finding: [Title]

**Function:** `functionName()` at `Contract.sol:L42`
**Replay Type:** [Same-Chain | Cross-Chain | Cross-Contract | Nonce-Skip | Expired]
**Severity:** [CRITICAL | HIGH | MEDIUM]

**Issue:**
[Description of the replay vulnerability or signature verification flaw]

**Signed Message Fields:**
- [x] to/from addresses
- [x] amount/value
- [ ] chainId ← MISSING
- [ ] verifyingContract ← MISSING
- [x] nonce
- [ ] deadline ← MISSING

**Attack Scenario:**
1. User signs message for [intended purpose]
2. Attacker captures signature from [source]
3. Attacker replays on [target chain/contract/time]
4. [Unauthorized action occurs]

**Recommendation:**
[Add EIP-712 domain separator, add nonce, add deadline, use ECDSA.recover]

Quick Detection Checklist

  • Does every signature include a nonce? (Prevents same-chain replay)
  • Does the signed message include chainId? (Prevents cross-chain replay)
  • Does the signed message include address(this)? (Prevents cross-contract replay)
  • Is there a deadline/expiry with block.timestamp check? (Prevents late execution)
  • Is ecrecover result checked against address(0)?
  • Is the s-value enforced to be in the lower half? (Prevents malleability)
  • Is the domain separator recalculated on chain fork? (Prevents fork replay)
  • Is OpenZeppelin's ECDSA library used instead of raw ecrecover?
  • For permit: Is the nonce incremented before state changes?
  • For contract wallets: Is ERC-1271 isValidSignature supported?

For replay type details, see {baseDir}/references/replay-taxonomy.md. For EIP-712 checklist, see {baseDir}/references/eip712-checklist.md.

Rationalizations to Reject

  • "We use nonces so replay is impossible" → Check for cross-chain and cross-contract replay (nonce doesn't prevent those)
  • "No one would replay on another chain" → Attackers monitor all chains; automated bots scan for replayable signatures
  • "ecrecover is a built-in, so it's safe" → It returns address(0) on failure, not revert; it doesn't enforce s-value
  • "The signature includes all the parameters" → Without chainId and contract address, it's still replayable
  • "We hardcoded chainId = 1" → Chain forks create two live chains with the same chainId; use block.chainid
  • "Permit is a standard, so it's safe" → The standard defines the interface, not the implementation; bugs are in how it's coded

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Signature Replay Analysis AI skill do?

Detects signature replay vulnerabilities in smart contracts — affecting 19.63% of signature-using contracts. Covers five replay types (same-chain, cross-chain, cross-contract, nonce-skip, expired-signature), EIP-712 domain separator verification, nonce management analysis, ecrecover edge cases (address(0), malleability, s-value), permit/permit2 safety, ERC-1271 contract wallet support, and meta-transaction security. Use when auditing contracts with ecrecover, ECDSA, EIP-712, permit, meta-transactions, multi-sig, or any off-chain signature verification.

Why use Signature Replay Analysis on TypingMind?

Because you install it once and use it with any model. Signature Replay Analysis is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Signature Replay Analysis in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/quillai-network/quillshield_skills/tree/main/plugins/signature-replay-analysis/skills/signature-replay-analysis. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Signature Replay Analysis?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Signature Replay Analysis?

As many as you like. As long as a model supports skills, you can use Signature Replay Analysis with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Signature Replay Analysis AI skill free?

Yes. It is published on GitHub by quillai-network under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇