Zokyo Auditing Tutorials
  • 🔐Zokyo Auditing Tutorials
  • 📚Tutorials
    • 🏃Tutorial 1: Front-Running
      • 🚀Prerequisites
      • 📘Understanding Front-Running
      • 👓Examples
      • ⚒️Mitigation Steps
      • 🏦Resource Bank to more front running examples
      • 🤝Front-Running Conclusion
    • 🧱Tutorial 2: Unsafe Casting
      • 🚀Prerequisites
      • 📘Understanding Casting
      • 👓Examples
      • 🤝Unsafe Casting Conclusion
    • 👍Tutorial 3: Approvals and Safe Approvals
      • 🚀Prerequisites
      • 📘Understanding Approvals
      • 👓Vulnerability Examples
        • 🔁ERC20 Approval Reset Requirement
        • 😴Ignoring Return Values from ERC20 approve() Function: Potential Miscount of Successful Approvals
        • 🚫Improper use of Open Zeppelins safeApprove() for Non-zero Allowance Increments
        • 🥾Omitted Approval for Contract Interactions Within a Protocol
        • 🤦‍♂️Failing to Reset Token Approvals in Case of Failed Transactions or other actions
        • 💭Miscellaneous
        • ERC20 Approve Race Condition Vulnerability
      • ⚒️Spot the Vulnerability
      • 🤝Approvals and Safe Approvals Conclusion
    • ⛓️Tutorial 4: Block.chainid, DOMAIN_SEPARATOR and EIP-2612 permit
      • 🚀Prerequisites
      • 📘Understanding Block.chainid and DOMAIN_SEPARATOR
      • 👓Examples
      • ⚒️General Mitigation Steps
      • 🤝Tutorial 4 Conclusion
  • 💰Tutorial 5: Fee-On-Transfer Tokens
    • 🚀Prerequisites
    • 📘Understanding Fee-On-Transfer
    • 👓Examples
    • 📘Links to more fee-on-transfer vulnerability examples
    • 🤝Fee-On-Transfer Tokens: Conclusion
  • 🌴Tutorial 6: Merkle Trees
    • 🚀Prerequisites
    • 📘Understanding Merkle Trees
    • 🔎Verification within a Merkle Tree:
    • 📜Merkle Proofs Within Smart Contracts
    • 🖋️Merkle Proof Solidity Implementation
    • 🛑Vulnerabilities When Using Merkle Trees
    • 💀Example Vulnerabilities
    • 🧠Exercise
    • 🤝Merkle Trees Conclusion
  • 🌳Tutorial 7: Merkle-Patricia Trees
    • 🚀Prerequisites
    • 📘Understanding Merkle-Patricia Trees
    • 📕Understanding Merkle-Patrica Trees pt.2
    • 🔎Verification within a Merkle-Patricia Tree
    • 🛑Vulnerabilities When Using Merkle-Patricia Trees
    • 💀Example Vulnerability
    • 🤝Merkle-Patricia Trees: Conclusion
  • 🔁Tutorial 8: Reentrancy
    • 🚀Prerequisites
    • 📘Understanding Reentrancy
    • ⚒️Mitigation
    • 💀The DAO Hack: An In-depth Examination
    • 👓Examples
    • 🏦Resource Bank To More Reentrancy Examples
    • 🤝Conclusion: Reflecting on the Reentrancy Vulnerability
  • 🔂Tutorial 9: Read-Only Reentrancy
    • 🚀Prerequisites
    • 📘Understanding Read-Only Reentrancy
    • 🔨Mitigating Read-Only Reentrancy
    • 👓Real World Examples
    • 🏦Resource Bank To More Reentrancy Examples
    • 🤝Read-Only Reentrancy: Conclusion
  • 🚆Tutorial 10: ERC20 transfer() and safeTransfer()
    • 🚀Prerequisites
    • 📘Understanding ERC20 transfer() and safeTransfer()
    • 👓Examples
    • 🤝Conclusion
  • 📞Tutorial 11: Low level .call(), .transfer() and .send()
    • 🚀Prerequisites
    • 📘Understanding .call, .transfer, and .send
    • 🛑Understanding the Vulnerabilities of .transfer and .send
    • 👓Examples
    • 🤝Low level .call(), .transfer() and .send() conclusion
  • ☎️Tutorial 12: Delegatecall Vulnerabilities in Precompiled Contracts
    • 🚀Prerequisites
    • 📳Understanding Delegatecall
    • ⛰️EVM, L2s, Bridges, and the Quest for Scalability
    • 🏗️Understanding Precompiles in the Ethereum Virtual Machine (EVM)
    • 💻Custom Precompiles
    • 💀Potential Vulnerabilities in EVM Implementations: Overlooked DelegateCall in Precompiled Contracts
    • 👓Real World Examples
    • 🤝Delegatecall and Precompiles: Conclusion
  • 🌊Tutorial 13: Liquid Staking
    • 🚀Prerequisites
    • 📘Understanding Liquid Staking
    • 💀Understanding Liquid Staking Vulnerabilities
    • 🛑Example Vulnerability
    • 🐜Example Vulnerability 2
    • 🕷️Example Vulnerability 3
    • 🤝Liquid Staking: Conclusion
  • 🚿Tutorial 14: Slippage
    • 🚀Prerequisites
    • 📘Understanding Slippage in Automated Market Makers (AMMs)
    • 💀Understanding the "Lack of Slippage Check" Vulnerability in Automated Market Makers (AMMs) and DEXs
    • 😡On-Chain Slippage Calculations Vulnerability
    • 📛0 slippage tolerance vulnerability
    • 👓Real World Examples
    • 🏦Resource bank to more slippage vulnerabilities
    • 🤝Slippage Conclusion
  • 📉Tutorial 15: Oracles
    • 🚀Prerequisites
    • 📘Understanding Oracles
    • 📈Types of price feeds
    • 😡Flash Loans
    • 💀Understanding Oracle Vulnerabilities
      • ⛓️The Danger of Single Oracle Dependence
      • ⬇️Using Deprecated Functions
      • ❌Lack of return data validation
      • 🕐Inconsistent or Absent Price Data Fetching/Updating Intervals
    • 🔫Decentralized Exchange (DEX) Price Oracles Vulnerabilities
    • 🛑Found Vulnerabilities In Oracle Implementations
      • ⚖️Newly Registered Assets Skew Consultation Results
      • ⚡Flash-Loan Oracle Manipulations
      • ⛓️Relying Only On Chainlink: PriceOracle Does Not Filter Price Feed Outliers
      • ✍️Not Validating Return Data e.g Chainlink: (lastestRoundData)
      • 🗯️Chainlink: Using latestAnswer instead of latestRoundData
      • 😭Reliance On Fetching Oracle Functionality
      • 🎱Wrong Assumption of 18 decimals
      • 🧀Stale Prices
      • 0️⃣Oracle Price Returning 0
      • 🛶TWAP Oracles
      • 😖Wrong Token Order In Return Value
      • 🏗️miscellaneous
    • 🤝Oracles: Conclusion
  • 🧠Tutorial 16: Zero Knowledge (ZK)
    • 🚀Prerequisites
    • 📚Theory
      • 🔌Circom
      • 💻Computation
      • 🛤️Arithmetic Circuits
      • 🚧Rank-1 Constraint System (R1CS)
      • ➗Quadratic Arithmetic Program
      • ✏️Linear Interactive Proof
      • ✨ZK-Snarks
    • 🤓Definitions and Essentials
      • 🔑Key
      • 😎Scalar Field Order
      • 🌳Incremental Merkle Tree
      • ✒️ECDSA signature
      • 📨Non-Interactive Proofs
      • 🏝️Fiat-Shamir transformation (or Fiat-Shamir heuristic)
      • 🪶Pedersen commitment
    • 💀Common Vulnerabilities in ZK Code
      • ⛓️Under-constrained Circuits
      • ❗Nondeterministic Circuits
      • 🌊Arithmetic Over/Under Flows
      • 🍂Mismatching Bit Lengths
      • 🌪️Unused Public Inputs Optimized Out
      • 🥶Frozen Heart: Forging of Zero Knowledge Proofs
      • 🚰Trusted Setup Leak
      • ⛔Assigned but not Constrained
    • 🐛Bugs In The Wild
      • 🌳Dark Forest v0.3: Missing Bit Length Check
      • 🔢BigInt: Missing Bit Length Check
      • 🚓Circom-Pairing: Missing Output Check Constraint
      • 🏹Semaphore: Missing Smart Contract Range Check
      • 🔫Zk-Kit: Missing Smart Contract Range Check
      • 🤖Aztec 2.0: Missing Bit Length Check / Nondeterministic Nullifier
      • ⏸️Aztec Plonk Verifier: 0 Bug
      • 🪂0xPARC StealthDrop: Nondeterministic Nullifier
      • 😨a16z ZkDrops: Missing Nullifier Range Check
      • 🤫MACI 1.0: Under-constrained Circuit
      • ❄️Bulletproofs Paper: Frozen Heart
      • 🏔️PlonK: Frozen Heart
      • 💤Zcash: Trusted Setup Leak
      • 🚨14. MiMC Hash: Assigned but not Constrained
      • 🚔PSE & Scroll zkEVM: Missing Overflow Constraint
      • ➡️PSE & Scroll zkEVM: Missing Constraint
      • 🤨Dusk Network: Missing Blinding Factors
      • 🌃EY Nightfall: Missing Nullifier Range Check
      • 🎆Summa: Unconstrained Constants Assignemnt
      • 📌Polygon zkEVM: Missing Remainder Constraint
    • 💿ZK Security Resources
  • 🤝Tutorial 17 DEX's (Decentralized Exchanges)
    • 🚀Prerequisites
    • 📚Understanding Decentralized Exchanges
    • 💀Common Vulnerabilities in DEX Code
      • 🔎The "Lack of Slippage Check" Vulnerability in Automated Market Makers (AMMs) a
      • 😡On-Chain Slippage Calculations Vulnerability
      • 📛Slippage tolerance vulnerability
      • 😵How Pool Implementation Mismatches Pose a Security Risk to Decentralized Exchanges (DEXs)
      • 🏊‍♂️Vulnerabilities in Initial Pool Creation - Liquidity Manipulation Attacks
      • 🛑Vulnerabilities In Oracle Implementations
        • ⚖️Newly Registered Assets Skew Consultation Results
        • ⚡Flash-Loan Oracle Manipulations
        • ⛓️Relying Only On Chainlink: PriceOracle Does Not Filter Price Feed Outliers
        • ✍️Not Validating Return Data e.g Chainlink: (lastestRoundData)
        • 🗯️Chainlink: Using latestAnswer instead of latestRoundData
        • 😭Reliance On Fetching Oracle Functionality
        • 🎱Wrong Assumption of 18 decimals
        • 🧀Stale Prices
        • 0️⃣Oracle Price Returning 0
        • 🛶TWAP Oracles
        • 😖Wrong Token Order In Return Value
        • 🏗️miscellaneous
      • 🥶Minting and Burning Liquidity Pool Tokens
      • 🎫Missing Checks
      • 🔞18 Decimal Assumption
        • 📌Understanding ERC20 Decimals
        • 💀Examples Of Vulnerabilities To Do With Assuming 18 Decimals
      • 🛣️Incorrect Swap Path
      • The Importance of Deadline Checks in Swaps
    • 🤝Conclusion
  • 🤖Tutorial 18: Proxies
    • 🚀Prerequisites
    • 📥Ethereum Storage and Memory
    • 📲Ethereum Calls and Delegate Calls
    • 💪Upgradability Patterns in Ethereum: Enhancing Smart Contracts Over Time
    • 🔝Proxy Upgrade Pattern in Ethereum
    • 🌀Exploring the Landscape of Ethereum Proxies
      • 🪞Transparent Proxies
      • ⬆️UUPS Proxies
      • 💡Beacon Proxies
      • 💎Diamond Proxies
  • 🔞Tutorial 19: 18 Decimal Assumption
    • 🚀Prerequisites
    • 📌Understanding ERC20 Decimals
    • 💀Examples Of Vulnerabilities To Do With Assuming 18 Decimals
    • 🤝Conclusion
  • ➗Tutorial 20: Arithmetic
    • 🚀Prerequisites
    • 🕳️Arithmetic pitfall 1: Division by 0
    • 🔪Arithmetic pitfall 2: Precision Loss Due To Rounding
    • 🥸Arithmetic pitfall 3: Erroneous Calculations
    • 🤝Conclusion
  • 🔁Tutorial 21: Unbounded Loops
    • 🚀Prerequisites
    • ⛽Gas Limit Vulnerability
    • 📨Transaction Failures Within Loops
    • 🤝Conclusion
  • 📔Tutorial 22: `isContract`
    • 🚀Prerequisites
    • 💀Understanding the 'isContract()` vulnerability
    • 🤝Conclusion
  • 💵Tutorial 23: Staking
    • 🚀Prerequisites
    • 💀First Depositor Inflation Attack in Staking Contracts
    • 🌪️Front-Running Rebase Attack (Stepwise Jump in Rewards)
    • ♨️Rugability of a Poorly Implemented recoverERC20 Function in Staking Contracts
    • 😠General Considerations for ERC777 Reentrancy Vulnerabilities
    • 🥏Vulnerability: _lpToken and Reward Token Confusion in Staking Contracts
    • 🌊Slippage Checks
    • 🌽The Harvest Functionality in Vaults: Issues and Best Practices
  • ⛓️Tutorial 24: Chain Re-org Vulnerability
    • 🚀Prerequisites
    • ♻️Chain Reorganization (Re-org) Vulnerability
    • 🧑‍⚖️Chain Re-org Vulnerability in Governance Voting Mechanisms
  • 🌉Tutorial 25: Cross Chain Bridges Vulnerabilities
    • 🚀Prerequisites
    • ♻️ERC777 Bridge Vulnerability: Reentrancy Attack in Token Accounting
      • 🛑Vulnerability: Withdrawals Can Be Locked Forever If Recipient Is a Contract
    • 👛The Dangers of Not Using SafeERC20 for Token Transfers
    • Uninitialized Variable Vulnerability in Upgradeable Smart Contracts
    • Unsafe External Calls and Their Vulnerabilities
    • Signature Replay Attacks in Cross-Chain Protocols
  • 🚰Tutorial 26: Integer Underflow and Overflow Vulnerabilities in Solidity (Before 0.8.0)
    • 🚀Prerequisites
    • 💀Understanding Integer Underflow and Overflow Vulnerabilities
    • 🤝Conclusion
  • 🥏Tutorial 27: OpenZeppelin Vulnerabilities
    • 🚀Prerequisites
    • 🛣️A Guide on Vulnerability Awareness and Management
      • 🤝Conclusion
  • 🖊️Tutorial 28: Signature Vulnerabilities / Replays
    • 🚀Prerequisites
    • 🔏Reusing EIP-712 Signatures in Private Sales
    • 🔁Replay Attacks on Failed Transactions
    • 📃Improper Token Validation in Permit Signature
  • 🤝Tutorial 29: Solmate Vulnerabilities
    • 🔏Lack of Code Size Check in Token Transfer Functions in Solmate
  • 🧱Tutorial 30: Inconsistent block lengths across chains
    • 🕛Incorrect Assumptions about Block Number in Multi-Chain Deployments
  • 💉Tutorial 31: NFT JSON and XSS injection
    • 📜Vulnerability: JSON Injection in tokenURI Functions
    • 📍Cross-Site Scripting (XSS) Vulnerability via SVG Construction in Smart Contracts
  • 🍃Tutorial 32: Merkle Leafs
    • 🖥️Misuse of Merkle Leaf Nodes
  • 0️Tutorial 33: Layer 0
    • 📩Lack of Force Resume in LayerZero Integrations
    • ⛽LayerZero-Specific Vulnerabilities in Airdropped Gas and Failure Handling
    • 🔓Understanding the Vulnerability of Blocking LayerZero Channels
    • 🖊️Copy of Understanding the Vulnerability of Blocking LayerZero Channels
  • ♻️Tutorial 34: Forgetting to Update the Global State in Smart Contracts
  • ‼️Tutorial 35: Wrong Function Signature
  • 🛑Tutorial 36: Handling Edge Cases of Banned Addresses in DeFi
  • Tutorial 37: initializer and onlyInitializing
  • ➗Tutorial 38: Eigen Layer
    • 📩Denial of Service in NodeDelegator Due to EigenLayer's maxPerDeposit Check
    • 📈Incorrect Share Issuance Due to Strategy Updates in EigenLayer Integrations
    • 🔁nonReentrant Vulnerability in EigenLayer Withdrawals
  • ⚫Tutorial 39: Wormhole
    • 📩Proposal Execution Failure Due to Guardian Set Change
  • 💼Tutorial 40: Uniswap V3
    • 📩Understanding and Mitigating Partial Swaps in Uniswap V3
    • 🌊Underflow Vulnerability in Uniswap V3 Position Fee Growth Calculations
    • ➗Handling Decimal Discrepancies in Uniswap V3 Price Calculations
  • 🔢Tutorial 41: Multiple Token Addresses in Proxied Tokens
    • 🔓Understanding Vulnerabilities Arising from Tokens with Multiple Entry Points
  • 🤖Tutorial 42: abiDecoder v2
    • 🥥Vulnerabilities from Manipulated Token Interactions Using ABI Decoding
  • ❓Tutorial 43: On-Chain Randomness
    • Vulnerabilities in On-Chain Randomness and How It Can Be Exploited
  • 😖Tutorial 44: Weird ERC20 Tokens
    • Weird Token List
  • 🔨Tutorial 45: Hardcoded stable coin values
  • ❤️Tutorial 46: The Risks of Chainlink Heartbeat Discrepancies in Smart Contracts
  • 👣Tutorial 47: The Risk of Forgetting a Withdrawal Mechanism in Smart Contracts
  • 💻Tutorial 48: Governance and Voting
    • Flash Loan Voting Exploit
    • Exploiting Self-Delegation
    • 💰Missing payable Keyword in Governance Execute Function
    • 👊Voting Multiple Times by Shifting Delegation
    • 🏑Missing Duplicate Veto Check
  • 📕Tutorial 49: Not Conforming To EIP standards
    • 💎Understanding EIP-2981: NFT Royalty Standard
    • 👍Improper Implementation of EIP-2612 Permit Standard
    • 🔁Vulnerabilities of Missing EIP-155 Replay Attack Protection
    • ➡️Vulnerabilities Due to Missing EIP-1967 in Proxy Contracts
    • 🔓Vulnerability of Design Preventing EIP-165 Extensibility
    • 🎟️The Dangers of Not Properly Implementing ERC-4626 in Yield Vaults
    • 🔁EIP-712 Implementation and Replay Attacks
  • ⏳Tutorial 50: Vesting
    • 🚔Vulnerability of Allowing Unauthorized Withdrawals in Vesting Contracts
    • 👊Vulnerability of Unbounded Timelock Loops in Vesting Contracts
    • ⬆️Vulnerability of Incorrect Linear Vesting Calculations
    • ⛳Missing hasStarted Modifier
    • 🔓Vulnerability in Bond Depositor's Vesting Period Reset
  • ⛽Tutorial 51: Ethereum's 63/64 Gas Rule
    • 🛢️Abusing Ethereum's 63/64 Gas Rule to Manipulate Contract Behavior
  • 📩Tutorial 52: NPM Dependency Confusion and Unclaimed Packages
    • 💎Exploiting Unclaimed NPM Packages and Scopes
  • 🎈Tutorial 53: Airdrops
    • 🛄Claiming on Behalf of Other Users
    • 🧲Repeated Airdrop Claims Vulnerability
    • 🍃Airdrop Vulnerability – Merkle Leaves and Parent Node Hash Collisions
  • 🎯Tutorial 54: Precision
    • 🎁Vulnerabilities Due to Insufficient Precision in Reward Calculations
    • Min-Shares: Fixed Minimum Share Values for Tokens with Low Decimal Precision
    • Vulnerability Due to Incorrect Rounding When the Numerator is Not a Multiple of the Denominator
    • Vulnerability from Small Deposits Being Rounded Down to Zero Shares in Smart Contracts
    • Precision Loss During Withdrawals from Vaults Can Block Token Transfers Due to Value < Amount
    • 18 Decimal Assumption Scaling: Loss of Precision in Asset Conversion Due to Incorrect Scaling
  • Tutorial 55: AssetIn == AssetOut, FromToken == ToToken
    • 🖼️Vulnerability: Missing fromToken != toToken Check
  • 🚿Tutorial 56: Vulnerabilities Related to LP Tokens Being the Same as Reward Tokens
    • 🖼️Vulnerabilities Caused by LP Tokens Being the Same as Reward Tokens
  • Tutorial 57: Unsanitized SWAP Paths and Arbitrary Contract Call Vulnerabilities
    • 📲Arbitrary Contract Calls from Unsanitized Paths
  • Tutorial 58: The Risk of Infinite Approvals and Arbitrary Contract Calls
    • 🪣Exploiting Infinite Approvals and Arbitrary Contract Calls
  • Tutorial 59: Low-Level Calls in Solidity Returning True for Non-Existent Contracts
    • Low-Level Calls Returning True for Non-Existent Contracts
  • 0️⃣Tutorial 60: The Impact of PUSH0 and the Shanghai Hardfork on Cross-Chain Deployments > 0.8.20
    • PUSH0 and Cross-Chain Compatibility Challenges
  • 🐍Tutorial 61: Vyper Vulnerable Versions
    • Vyper and the EVM
  • ⌨️Tutorial 62: Typos in Smart Contracts — The Silent Threat Leading to Interface Mismatch
    • Vyper and the EVM
  • ☁️Tutorial 63: Balance Check Using ==
    • The Vulnerability: == Balance Check
  • 💍Tutorial 64: Equal Royalties for Unequal NFTs
    • Understanding the Problem: Equal Royalties for Unequal NFTs
  • 🖼️Tutorial 65: ERC721 and NFTs
    • The Risk of Using transferFrom Instead of safeTransferFrom in ERC721 Projects
    • ❄️Why _safeMint Should Be Used Instead of _mint in ERC721 Projects
    • The Importance of Validating Token Types in Smart Contracts
    • 📬Implementing ERC721TokenReceiver to Handle ERC721 Safe Transfers
    • NFT Implementation Deviating from ERC721 Standard in Transfer Functions
    • NFT Approval Persistence after Transfer
    • 🎮Gameable NFT Launches through Pseudo-Randomness
    • 2️⃣Protecting Buyers from Losing Funds Due to Claimed NFT Rewards on Secondary Markets
    • ♻️Preventing Reentrancy When Using SafeERC721
    • 🖊️Preventing Re-use of EIP-712 Signatures in NFT Private Sales
  • 2️⃣Tutorial 66: Vulnerability Arising from NFTs Supporting Both ERC721 and ERC1155 Standards
  • 📷Tutorial 67: ERC1155 Vulnerabilities
    • ♻️Preventing Reentrancy in OpenZeppelin's SafeERC1155
    • 🛫Vulnerabilities in OpenZeppelin's ERC1155Supply Contract
    • Understanding Incorrect Token Owner Enumeration in ERC1155Enumerable
    • Avoiding Breaking ERC1155 Composability with Improper safeTransferFrom Implementation
    • 💍Ensuring Compatibility with EIP-2981 in ERC1155 Contracts
  • 🪟Informational Vulnerabilities
  • ⛽Gas Efficiency
  • 💻Automation Tools
  • 🔜Out Of Gas (Coming Soon)
  • 🔜DEX Aggregators (Coming Soon)
  • 🔜Bribes (Coming Soon)
  • 🔜Understanding Compiled Bytecode (coming soon)
  • 🔜Deployment Mistakes (coming soon)
  • 🔜Optimistic Roll-ups (coming soon)
  • 🔜Typos (coming soon)
  • 🔜Try-Catch (coming soon)
  • 🔜NFT Market-place (coming soon)
  • 🔜Upgrade-able Contracts (coming soon)
Powered by GitBook
On this page
  1. Tutorial 49: Not Conforming To EIP standards

EIP-712 Implementation and Replay Attacks

Ethereum Improvement Proposal (EIP) 712 is a standard for typed data signing that enhances security and transparency in smart contract interactions, specifically with off-chain signatures. It allows the signer to see the exact transaction data they are authorizing, preventing confusion or malicious alterations. However, improper implementation of EIP-712 can lead to serious vulnerabilities, such as replay attacks, where signed transactions can be reused maliciously without the sender’s consent.

Replay attacks exploit the reuse of signed transactions by manipulating the nonce or other factors within the transaction. Failing to implement proper protections, such as checking nonces or batch identifiers, leaves contracts open to the risk of these attacks, which can lead to financial loss and other critical issues.

In this tutorial, we will explore how failing to correctly implement EIP-712 can expose a protocol to replay attacks and how to mitigate this issue.


Tutorial: Understanding and Mitigating Replay Attacks in EIP-712 Transactions

1. What is EIP-712 and Why is it Important?

EIP-712 is a standard for typed data signing that improves the user experience when signing off-chain messages and transactions. It provides a structured format that allows users to clearly understand what they are signing. The structured format of EIP-712 prevents ambiguity and minimizes the risk of malicious actors tricking users into signing something they don’t intend to approve.

Key Benefits of EIP-712:

  • Clarity: Users can see exactly what they are signing.

  • Security: The structured data format prevents signed data from being misinterpreted.

  • Compatibility: It works across multiple wallets and tools, including MetaMask and other Web3 wallets.

2. The Risk of Replay Attacks in EIP-712

Replay attacks occur when a signed transaction is maliciously reused by an attacker. Without proper protections in place, such as unique nonces or batch identifiers, an attacker can simply replay the same transaction multiple times to drain funds or execute malicious actions.

Replay attacks are particularly dangerous in the context of EIP-712 transactions if the protocol does not implement safeguards such as batch ID checks or proper nonce management. Here’s an example of how replay attacks could occur:

  • A user signs a transaction with a specific nonce.

  • The attacker reuses the same signature and changes the transaction details (such as the recipient) without invalidating the original transaction.

  • The contract, failing to check for a unique batch ID or nonce, executes the replayed transaction, causing unintended consequences.

Why the Vulnerability Occurs

The vulnerability in the above scenario happens because the contract checks the nonce but not the batch ID itself. This allows the same nonce to be used in different batches, effectively bypassing the nonce check and allowing the attacker to replay the transaction.

Since there are 2^256 possible batch IDs, the attacker can replay the same transaction an arbitrary number of times by simply modifying the batch ID. This is a major issue in protocols that rely on EIP-712 for signing transactions but fail to properly account for batch ID or nonce uniqueness.

5. Mitigation Strategies

To prevent replay attacks and ensure EIP-712 compliance, it’s essential to include a batch ID or another form of unique identifier in the signed transaction’s hash. Here’s how to mitigate the vulnerability:

  • Include Batch ID in the Transaction Hash: Add the batch ID to the encodeTransactionData function so that each transaction is uniquely tied to a specific batch. This ensures that even if the nonce is reused, the transaction cannot be replayed.

    Example code for mitigating the issue:

    function encodeTransactionData(
        address to,
        uint256 value,
        bytes calldata data,
        uint8 operation,
        uint256 nonce,
        uint256 batchId
    ) public view returns (bytes32) {
        return keccak256(
            abi.encode(
                TRANSACTION_TYPEHASH,
                to,
                value,
                keccak256(data),
                operation,
                nonce,
                batchId
            )
        );
    }
  • Check for Batch ID Uniqueness: Ensure that each batch ID can only be used once with a specific nonce. This prevents attackers from reusing batch IDs to replay transactions.

  • Use Proper Nonce Management: Always increment nonces properly and ensure they are not reused across transactions.

6. Additional Best Practices

  • Time-limited Nonces: Consider using time-based nonces that expire after a certain period to prevent long-term replay attacks.

  • Signature Expiry: Implement expiration times for signatures to ensure they cannot be reused after a certain time has passed.

  • Comprehensive Testing: Test for potential replay vulnerabilities by simulating various transaction scenarios with different nonces and batch IDs.

7. Conclusion

Replay attacks are a serious concern when implementing EIP-712 in smart contracts. Proper nonce and batch ID management, along with ensuring that each signed transaction is uniquely tied to its execution context, can prevent these attacks. By following the mitigation steps outlined in this tutorial, you can ensure that your protocol is secure against replay attacks and compliant with EIP-712.

Key Takeaways:

  • Always include batch IDs or other unique identifiers in transaction hashes.

  • Ensure that nonces are properly managed and never reused.

  • Implement signature expiry and test thoroughly to prevent potential vulnerabilities.

Understanding the Importance of Correct TypeHash Implementation in EIP-712

EIP-712 is a standard for structured data signing on Ethereum, designed to improve security and usability when off-chain data is signed for on-chain execution. A crucial part of this standard involves hashing the data structures (like transactions and validation objects) correctly. This process, called TypeHash, encodes the structure of the data and is used to create a unique signature that ensures the integrity of the signed message.

However, incorrectly implementing TypeHash can lead to various issues, including:

  • Mismatched signatures that fail validation.

  • Replay attacks or unauthorized actions.

  • Incompatibility with integrators, wallets, or external systems that rely on the correct implementation of EIP-712.

In this tutorial, we will explore how incorrect TypeHash implementation can result in vulnerabilities, leading to transaction failures or even security risks, and we’ll walk through the steps required to ensure compliance with the EIP-712 standard.


Tutorial: Correcting TypeHash Implementation in EIP-712 Structures

1. What is EIP-712 and Why is TypeHash Important?

EIP-712 introduces a standard way to hash and sign typed data. This ensures that when users sign data off-chain, they are fully aware of the exact structure and content of the data being signed. This prevents attackers from manipulating or misinterpreting the signed data.

The TypeHash is the core part of this process: it represents the structure of the data that is being signed. Each structure in EIP-712 (such as a transaction or validation object) has a unique type definition, which is hashed using the keccak256 algorithm. The structure’s hash ensures that only data with the same format can be signed and validated.

2. The Vulnerability: Incorrect TypeHash Leads to Incompatibility and Security Risks

In the example provided, the protocol defines several structures like Transaction and Validation, but the TypeHash values used for these structures are incorrect because they were calculated based on outdated or removed structures. This mismatch can lead to incorrect validation of signed transactions and potentially allow for malicious actions.

Consider the following structures:

struct Transaction {
    uint8 operation;
    address to;
    address account;
    address executor;
    uint256 value;
    uint256 nonce;
    bytes data;
}

struct Validation {
    uint32 expiryEpoch;
    bytes32 transactionStructHash;
    bytes32 policyHash;
}

However, the calculated TypeHash for these structures is based on an old structure, ExecutionParams, rather than the actual Transaction and Validation structures. This discrepancy means that any signed data using these type hashes will not match the actual structure, causing validation failures or unexpected behavior.

Why This Vulnerability Occurs

The root of the problem is that the TypeHash calculation was not updated when the Transaction and Validation structures were changed. Instead of recalculating the TypeHash for the new structures, the protocol continues using the old hashes, leading to a mismatch between the signed data and its structure.

In EIP-712, the TypeHash must reflect the exact structure of the data being signed. For example:

typeHash = keccak256(encodeType(typeOf(structure)))

Where encodeType generates a string representation of the structure, such as:

"Transaction(uint8 operation,address to,address account,address executor,uint256 value,uint256 nonce,bytes data)"

If this encodeType does not match the structure, the generated hash will not be correct, and any signatures generated using this hash will be invalid.

5. Mitigating the Vulnerability: Correct TypeHash Calculation

To fix this issue, you need to ensure that the TypeHash is calculated based on the correct structure definitions. Here’s how you can update the code to correctly reflect the Transaction and Validation structures:

bytes32 public constant TRANSACTION_TYPEHASH = keccak256(
    "Transaction(uint8 operation,address to,address account,address executor,uint256 value,uint256 nonce,bytes data)"
);

bytes32 public constant VALIDATION_TYPEHASH = keccak256(
    "Validation(uint32 expiryEpoch,bytes32 transactionStructHash,bytes32 policyHash)"
);

This ensures that the TypeHash reflects the actual structure of the data, preventing validation failures and ensuring compatibility with external tools and integrators that rely on EIP-712.

Best Practices for Implementing EIP-712

To avoid issues like incorrect type hashes in the future, follow these best practices when implementing EIP-712:

  • Always update the TypeHash when structures change: If the structure of the data changes (e.g., adding or removing fields), make sure to recalculate the TypeHash accordingly.

  • Use automated tools for consistency: Tools like OpenZeppelin’s EIP-712 library help ensure that your type hashes are calculated consistently and correctly.

  • Write comprehensive tests: Ensure that your test suite includes validation of the TypeHash for each structure, preventing future changes from introducing discrepancies.

  • Use a deterministic type encoding: Follow the EIP-712 specification closely when encoding structures to ensure compatibility with external wallets and tools.

Ensuring EIP-712 Compliance for TypeHash and Dynamic Data Handling

Fixing Common EIP-712 Implementation Mistakes

1. Understanding TypeHash and Its Role in EIP-712

In EIP-712, every data structure that is part of a signed message must have a unique TypeHash. This hash is computed by encoding the structure's type and its members, which are then hashed using keccak256. The TypeHash ensures that the exact format of the data being signed is preserved, preventing manipulation.

For example, a simple structure might look like this:

solidityCopy codestruct Transaction {
    uint8 operation;
    address to;
    address account;
    address executor;
    uint256 value;
    uint256 nonce;
    bytes data;
}

The TypeHash for this structure is:

solidityCopy codebytes32 public constant TRANSACTION_TYPEHASH = keccak256(
    "Transaction(uint8 operation,address to,address account,address executor,uint256 value,uint256 nonce,bytes data)"
);

This ensures that when a Transaction is signed, it has a fixed format, preventing replay attacks or unauthorized actions.

2. Problem 1: Incorrect Handling of Dynamic Types (bytes)

One of the most common mistakes in EIP-712 implementations involves dynamic data types like bytes. According to the standard, dynamic types must first be hashed using keccak256 before being encoded with other fields. Failing to do so results in incorrect type hashes and transaction validation failures.

Consider the following incorrect implementation:

solidityCopy codefunction _deriveHookHash(Hook memory hook) internal view returns (bytes32) {
    return keccak256(
        abi.encode(_HOOK_TYPEHASH, hook.target, hook.itemIndex, hook.extraData)
    );
}

Here, hook.extraData is a dynamic bytes type, but it is encoded directly without hashing. This causes issues because dynamic types must be hashed before encoding.

Fix:

function _deriveHookHash(Hook memory hook) internal view returns (bytes32) {
    return keccak256(
        abi.encode(_HOOK_TYPEHASH, hook.target, hook.itemIndex, keccak256(hook.extraData))
    );
}

By hashing hook.extraData first, we ensure that the dynamic type is encoded as a fixed-length 32-byte word, as required by EIP-712.

3. Problem 2: Using abi.encode Instead of abi.encodePacked

Another common mistake is using abi.encode where abi.encodePacked should be used. This is particularly relevant when encoding strings or other types with varying lengths, where padding introduced by abi.encode can cause discrepancies.

For instance, consider the following code:

bytes32 rentalOrderTypeHash = keccak256(
    abi.encode(rentalOrderTypeString, hookTypeString, itemTypeString)
);

The issue here is that abi.encode pads the data to ensure each element is aligned to 32 bytes. This padding can lead to incorrect type hashes. Instead, abi.encodePacked should be used to ensure the exact bytes are hashed, without padding.

Fix:

bytes32 rentalOrderTypeHash = keccak256(
    abi.encodePacked(rentalOrderTypeString, hookTypeString, itemTypeString)
);

By using abi.encodePacked, the data is encoded without padding, ensuring the correct hash is produced.

4. Problem 3: Missing or Incorrect Fields in TypeHash

In some cases, the TypeHash may not correctly reflect the structure being signed. For example, if fields are missing or incorrect, the resulting hash will not match the structure, leading to transaction failures or incorrect validation.

Consider this incorrect implementation:

function _deriveOrderMetadataHash(OrderMetadata memory metadata) internal view returns (bytes32) {
    return keccak256(
        abi.encode(
            _ORDER_METADATA_TYPEHASH,
            metadata.rentDuration,
            keccak256(abi.encodePacked(metadata.hooks))
        )
    );
}

The issue here is that the ORDER_METADATA_TYPEHASH includes fields such as orderType and emittedExtraData, but these fields are not included in the encoding.

Best Practices for EIP-712 Compliance

To ensure your smart contracts remain compliant with EIP-712, follow these best practices:

  • Correctly encode dynamic types: Always hash dynamic types like bytes before encoding them with other fields.

  • Use abi.encodePacked when appropriate: Avoid padding issues by using abi.encodePacked instead of abi.encode for encoding strings and dynamic data.

  • Include all fields in the TypeHash: Ensure that all fields in the structure are properly reflected in the TypeHash, and update the TypeHash if the structure changes.

  • Write comprehensive tests: Include tests that check the correctness of the TypeHash and ensure that signed transactions are validated correctly.


Conclusion

Implementing EIP-712 correctly is essential for ensuring secure and reliable off-chain signing and on-chain verification of transactions. Incorrect handling of TypeHash, dynamic types, or improper encoding can lead to transaction validation failures, vulnerabilities, and integration issues. By following best practices and ensuring that your implementation adheres to the EIP-712 standard, you can avoid these pitfalls and maintain a secure, compliant protocol.

Key Takeaways:

  • Ensure dynamic types like bytes are hashed before encoding.

  • Use abi.encodePacked when working with strings and other dynamic data to avoid padding issues.

  • Always include all required fields in the TypeHash to avoid mismatches.

  • Test thoroughly to catch any issues in your EIP-712 implementation.

PreviousThe Dangers of Not Properly Implementing ERC-4626 in Yield VaultsNextTutorial 50: Vesting

Last updated 7 months ago

📕
🔁