walletconnect-monorepo: Can't get valid signatures for signMessage for web3-provider

I can’t get a verifiable signature when I use web3 provider like ethers, direct usage of connector works fine.

This works (with direct connector usage): https://codesandbox.io/s/unruffled-benz-9ubn6?file=/src/App.vue

This doesn’t work: https://codesandbox.io/s/gallant-banach-z7svg?file=/src/App.vue the same code works for direct MetaMask usage as a browser extension.

I was also testing with wrappers like:

const rawMessageLength = new Blob([rawMessage]).size
let message = ethers.utils.toUtf8Bytes("\x19Ethereum Signed Message:\n" + rawMessageLength + rawMessage)
message = ethers.utils.keccak256(message)

They don’t help as well.

This topic was also discussed here: https://stackoverflow.com/questions/63793873/sign-and-verifiy-message-on-ethereum-using-wallet-connect-not-working/63817084#63817084

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 19 (8 by maintainers)

Most upvoted comments

The method below you can try, and you can see the full raw message (not hex) on the client side

import { Signer, utils, providers } from 'ethers'

const signMessageAsync = async (signer: Signer, address: string, message: string): Promise<string> => {
  const messageBytes = utils.toUtf8Bytes(message)
  if (signer instanceof providers.JsonRpcSigner) {
    try {
      const signature = await signer.provider.send('personal_sign', [
        utils.hexlify(messageBytes),
        address.toLowerCase()
      ])
      return signature
    } catch (e) {
      if (e.message.includes('personal_sign')) {
        return await signer.signMessage(messageBytes)
      }
      throw e
    }
  } else {
    return await signer.signMessage(messageBytes)
  }
}

reference from zkSync

  1. change message string to Utf8Bytes
    1. zkSync utils getSignedBytesFromMessage call
    2. zkSync utils getSignedBytesFromMessage
  2. use provider to send personal_sign call
    1. zkSync utils signMessagePersonalAPI call
    2. zkSync utils signMessagePersonalAPI

Here’s my workaround that works for window.ethereum and wallet connect, with ethers:

async function signMessage(provider: ethers.providers.Web3Provider, rawMessage: string) {
  if(provider.provider instanceof WalletConnectProvider) {
    const rawMessageLength = new Blob([rawMessage]).size
    const message = ethers.utils.toUtf8Bytes("\x19Ethereum Signed Message:\n" + rawMessageLength + rawMessage)
    const signer = provider.getSigner();
    const address = await signer.getAddress();
    const keccakMessage = ethers.utils.keccak256(message);

    const wc = provider.provider as WalletConnectProvider;
    const signature = await wc.connector.signMessage([
      address.toLowerCase(),
      keccakMessage,
    ]);
    return signature;
  } else {
    const signer = provider.getSigner();
    return await signer.signMessage(rawMessage);
  }
}
            // For signing the message you use to have to restore proper behavior if using MetaMask
            // ethers will change eth_sign to personal_sign if it detects metamask (this is no longer the case)
            // https://github.com/ethers-io/ethers.js/blob/2a7dbf05718e29e550f7a208d35a095547b9ccc2/packages/providers/src.ts/web3-provider.ts#L33
            if (isMetamask) library.provider.isMetaMask = true

            const signer = await library.getSigner().getAddress()
            const signedPayload = await library.getSigner().signMessage(id(body))

            // Restore back behavior
            if (isMetamask) library.provider.isMetaMask = false

now its just using _legacySignMessage see https://github.com/ethers-io/ethers.js/commit/8947fd405e3aea07f6db958d89a3ad39abe3a25a

This is true now for any provider, not just Metamask, so this should affect walletconnect as well

All in all, it seems as though each wallet has it’s own vagaries that have to be explored and mapped out.

I spent a fair amount of time trying to understand what’s going on with signatures and wallet, and the response is that it’s just a jungle.

What I found:

  • One should not use ethers.js(v5) for signing messages, it doesn’t work in lot of situations.
  • Metamask receives parameters in reverse order as others wallets
  • Signing messages with Contract wallets (as Argent) has to be handled differently and spec is still draft

Check this: https://github.com/mooni/mooni/blob/master/app/src/lib/eth.ts#L130 I don’t support contract wallets

@miohtama currently I use the following hack:

var rawMessage = "Hello World";
var rawMessageLength = new Blob([rawMessage]).size
var message = ethers.utils.toUtf8Bytes("\x19Ethereum Signed Message:\n" + rawMessageLength + rawMessage)
message = ethers.utils.keccak256(message)
var params = [
    await this.signer.getAddress(),
    message
]
this.signature = await this.wc.connector.signMessage(params);
this.verified = ethers.utils.verifyMessage(rawMessage, this.signature);

Do you wish me to make a PR with a fix for that?