Discovering a vulnerability in Relay Bridge's Solver Signature API

Summary

Relay Bridge is an instant, low-cost bridging and swapping solution. Users should submit their intents to a solver and pass the request ID generated off-chain through msg.data to the solver address while transferring the tokens.

It is simple, fast, and lightweight. Please read their docs for more information, as I won’t discuss specifics.

Discovery

I casually reviewed how it worked and used their API to generate a random request. While doing so, I came across this unique way to create a request ID and ask the solver to sign it using an API call. On-chain integrators later use these signatures to validate the existence of such intent order.

So first, we must call /quote with the user address, source token, destination chain, and destination token to generate the request ID.

Later, we can pass the request ID to the requests/<request-id>/signature/v2 endpoint to get a signature from the solver.

This signature can be used on-chain to verify if such an order exists on the relay.link platform. Surprisingly, when I debugged further, I found that the signature was generated using a wrong hash, leading to the inability of on-chain integrators to validate the existence of an intent order.

The Vulnerability

The vulnerability here is simple: the solvers sign a wrong message hash.

The hash is generated by encoding the request ID, sourceChainId, user address, sending token address, destination chain ID, receiver address on the destination, and the receiving token address.

However, instead of using the receiving token address, the hash is generated using the sending token address, leading to the generation of the wrong hash.

bytes32 hashToBeSigned = ECDSA.toEthSignedMessageHash(
  keccak256(
   abi.encodePacked(
        requestId,
        sourceChainId,
        bytes32(uint256(uint160(user))),
        bytes32(uint256(uint160(sendingTokenAddress))),
        destinationChainId,
        bytes32(uint256(uint160(receiver))),
        receivingTokenAddress
    )
   )
)


For example, I generated a request using the /quote endpoint for bringing USDC from Ethereum to USDC.e on Polygon.

Later, I used the same request ID to generate a signature using the requests/0xb3ef6ac9af24bf6b972ce2c16250f4dcfbacfd17c3f99b9faa743045ed442170/signature/v2 endpoint and received the following response.

{
  "requestData": {
    "originChainId": 1,
    "originUser": "0x37305B1cD40574E4C5Ce33f8e8306Be057fD7341",
    "originCurrency": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "destinationChainId": 137,
    "destinationUser": "0xa5F565650890fBA1824Ee0F21EbBbF660a179934",
    "destinationCurrency": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
  },
  "signature": "0x8784412f85648f2905b7ee970b9c01b7f3ff9e4bdb43bed2fac712f77de810cd359f1c9713b3d28d2e7f7fe609f69e7ab05482a242896a907f23ac8c2e463ef11b"
}      


If you notice, the destination currency is the same as the origin currency. So, it generated the message hash using USDC twice instead of USDC.e

Impact

Unexpected protocol behavior: The solver signs an invalid message hash, which can prevent verifying the intent on the chain.

Remediation

The fix is quite simple. The API logic should be updated to use the right destination currency while generating the hash for the solver to sign.

The issue was escalated to the team via e-mail and Telegram on November 19th around 5 PM IST, and a fix was released promptly after a couple of hours.

Subscribe to Sujith Somraaj
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.