Safe Tx Hash Verifier for Mitigating UI Spoofing Risk

tl;dr

Signing Safe (EIP-712) transactions carries risks due to UI spoofing—a vulnerability where malicious interfaces can deceive users into signing harmful transactions (as seen in the $1.46B Bybit hack). The fully offline web app was introduced to calculate tx hashes without using Safe API.

1. Problem Statement: UI Spoofing Risk in Safe Transaction Signing

The EIP-712 standard used by Safe multisig wallets requires users to sign a structured data hash to approve transactions. A significant vulnerability occurs when a dApp or web wallet’s user interface is compromised or maliciously crafted to display transaction details that don’t match the actual parameters used to generate messageHash, domainHash and safeTxHash presented to the user’s wallet (e.g., MetaMask, Ledger) for signing.

This UI spoofing and transaction data mismatch attack vector was used in the Bybit security incident on February 21, 2025, when signers were reportedly tricked by a manipulated UI into signing hashes for malicious transactions disguised as routine operations, leading to an estimated $1.46 billion loss. This highlighted the critical need for signers to independently verify the hash derived from the true, intended transaction parameters before signing.

2. Solution Overview: Safe Transaction Hash Verifier Tool

There’s a CLI solution made by pcaversaccio, which was later forked and upgraded by Cyfrin to work in offline mode.

The Lido DAO Tech Infra team has developed a standalone, fully offline web app containing JavaScript code that functions as a client-side calculator for Safe EIP-712 transaction hashes. This solution is based on the offline script, but tailored for signers within Lido DAO to simplify the mundane process of signing multisig transactions without needing to interact with the CLI.

  • Purpose: To allow Safe users to independently generate the expected hashes from manually entered transaction parameters.
  • Core Function: It replicates the EIP-712 hash generation process specific to Safe contracts, allowing users to verify the hash presented by their signing wallet against a hash calculated from known, intended inputs.
  • Environment: The tool runs entirely within the user’s web browser using JavaScript. It uses the ethers.js library, which is fully embedded within the HTML file’s script section, for cryptographic operations and ABI encoding. Beyond initial file loading, it requires no backend or external network calls. The tool is distributed via IPFS and the initial repo cloning.

3. Technical Implementation Details

The tool implements the EIP-712 typed data signing standard as required by various versions of the Safe smart contracts.

  • Core Standard: EIP-712 (eth_signTypedData_v4 structure).
  • Hashing Algorithm: keccak256, accessed via the embedded ethers.js library (ethers.keccak256() and ethers.solidityPackedKeccak256()). The embedded library originates from a specific official ethers.js v6 permalink.
  • Key Calculations & Logic:
    • Version Handling: The tool includes logic (compareVersions function) to adapt calculations based on the provided Safe contract version string (e.g., “1.3.0”).
    • Domain Separator (domainHash):
      • For Safe versions < 1.3.0: Calculated using keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH_OLD, verifyingContract)) where verifyingContract is the Safe address.
      • For Safe versions >= 1.3.0: Calculated using keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, chainId, verifyingContract)), incorporating the chainId. The correct DOMAIN_SEPARATOR_TYPEHASH constant is used based on this version check. Encoding is performed via ethers.AbiCoder.defaultAbiCoder().encode().
    • Message Payload (messageHash):
      • The SafeTx struct parameters are ABI-encoded. The struct definition (and thus the SAFE_TX_TYPEHASH) differs slightly for versions < 1.0.0 (using dataGas instead of baseGas). The tool selects the appropriate SAFE_TX_TYPEHASH constant (SAFE_TX_TYPEHASH or SAFE_TX_TYPEHASH_OLD).
      • Parameters encoded: (bytes32 typeHash, address to, uint256 value, bytes32 dataHash, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 nonce). Note: data itself is first hashed (ethers.keccak256(data)) before being included in the struct encoding.
      • The ABI-encoded struct string is then hashed: messageHash = ethers.keccak256(encoded_struct_data).
    • Final Signed Hash (safeTxHash): The final EIP-712 hash digest is constructed according to the standard: safeTxHash = ethers.solidityPackedKeccak256(["bytes1", "bytes1", "bytes32", "bytes32"], ["\x19", "\x01", domainHash, messageHash]).
  • Input Parameters: Required user inputs:
    • chainId, safeAddress, toAddress, value (wei), data (hex), operation (0 or 1), nonce.
    • It also takes Safe-specific parameters, pre-filled with defaults but editable: version (“1.3.0”), safeTxGas (0), baseGas (0), gasPrice (0), gasToken (address(0)), refundReceiver (address(0)).
    • Optional fields allow inputting hashes from external sources for comparison: domainHashProvided, messageHashProvided, safeTxHashProvided.
  • Output Hashes: The tool displays the calculated: safeTxHash, domainHash, messageHash.
  • Runtime Requirements: Requires a modern web browser with JavaScript enabled. The necessary ethers.js library is self-contained within the HTML file. This ensures the tool functions fully offline after loading and does not depend on external script hosting services (CDNs).
  • Code Structure: Key JavaScript functions include calculateHashes, calculateDomainHash, compareVersions, validateAndGetVersion, along with DOM manipulation for input/output and event handling, all within the index.html file.

4. Suggested Verification Process

The process includes verifying the integrity of the tool itself before using it to verify transaction hashes:

  1. Tool Acquisition: Obtain the index.html tool file exclusively from trusted sources, such as one’s internal repository clone or the official, verified IPFS link (see Section 6).
  2. Tool Integrity Verification (index.html - SHA-384): Before execution, verify the integrity of the obtained index.html file.
    • Calculate the SHA-384 hash of the downloaded index.html file using standard methods (e.g., openssl dgst -sha384 -binary index.html | openssl base64 -A, or verified online tools).
    • This calculated hash must match the known-good hash provided in one’s official documentation (see Section 6: d2VLr...). A mismatch indicates the file may have been tampered with; in this case, do not proceed.
  3. Embedded Library Verification (ethers.js snippet - SHA-384): Specifically verify the integrity of the embedded ethers.js code within the HTML file.
    • The JavaScript code block within the <script> tag (originating from the specified ethers.js v6 permalink) should be extracted into a temporary file.
    • Calculate the SHA-384 hash of this extracted code snippet (e.g., using openssl dgst -sha384 -binary ethers_extracted.js | openssl base64 -A).
    • This hash must match the known-good hash for the embedded library (see Section 6: NRAZj...). A mismatch prevents usage and indicates potential tampering.
  4. Tool Execution Environment: After verifying both the index.html file and the embedded library code through hash comparison, open the index.html file in a clean browser instance with all extensions disabled to minimize potential external interference.
  5. Parameter Gathering & Entry: Gather the precise transaction parameters from the verified source and meticulously input them into the now-verified tool’s form, double-checking for accuracy.
  6. Hash Generation: The “Generate Hashes” button is used to calculate the safeTxHash, domainHash, and messageHash based on the entered parameters.
  7. Wallet Interaction & Comparison: The signing process is initiated via standard procedures. When the hardware or software wallet presents the final EIP-712 hash for signing, perform an exact, character-by-character comparison between this wallet-presented hash and the safeTxHash generated by the verified tool.
  8. Signing Decision Protocol:
    • On Match: If the hashes match exactly, it provides strong cryptographic assurance that the signature request corresponds to the intended parameters. Proceed with approving the signature.
    • On Mismatch: If there is any discrepancy, immediately abort the signing process. A mismatch requires an internal review to determine the cause before taking any further action on this transaction.

5. Security Benefit

This two-stage verification process—verifying the tool and then the transaction hash—provides a defense against UI spoofing attacks. It confirms that the tool in use is untampered and then uses this tool to confirm the transaction hash matches the intended parameters. This directly addresses the interaction-layer risks exposed by the Bybit incident.

6. Availability and Verification Hashes

7. Disclaimer

While this tool and process aid in verification, its effectiveness relies on users carefully performing integrity checks and accurately inputting all transaction parameters. Any incorrect checks or inputs will compromise the verification process. Users must fully understand the transaction they intend to sign.

8 Likes

Hi everyone, MixBytes CTO is here!

I’m pleased to share that the Safe Tx Hash Verifier has undergone a full review by the team of 4 auditors. This tool plays a crucial role in protecting Safe multisig signers from UI spoofing risks by allowing users to verify transaction hashes offline, directly from raw parameters.

During our review, we proposed several improvements to enhance both security assurance and user clarity. We’re happy to confirm that all suggestions were thoughtfully implemented. These include:

  • Visual highlighting of suspicious transaction types like DELEGATECALL, helping signers recognize high-risk actions.

  • Fields for entering precomputed hashes (safeTxHash, domainHash, messageHash) to cross-verify results.

  • Guidance in the README advising users to open the tool in a clean browser environment, reducing exposure to extension-based attacks.

  • Optional ABI input support, making it easier to decode and interpret the transaction data.

We believe this tool provides a reliable and practical layer of defense for interacting with Safe contracts, especially in high-stakes DAO or treasury operations.

3 Likes