Go Ethereum – How to Recover an ethers.js Signed Message

cryptographyecdsaethers.jsgo-ethereumsignature

I generated a signed message using ethers.js. Here's how I did that:

const provider = new ethers.providers.Web3Provider(web3Provider.provider);
const signer = provider.getSigner();
await signer.signMessage("hexcowboy")

Now I have a message signed by 0x37121F74d25262011AdD8cc8D13E6923AEb699d8 with the message hexcowboy and the signed message being 0x27876c5cc1be67e8313633ab0f72ace790886c88a664df1ca0b858c1834782002a0ae1e44fdb3fe7586eec431c115205a36a05bbd31c6c2bce04605825e91cfe1b.

I know my message signature is valid because ether.js can recover my public key from it:

import { utils } from 'ethers';
const output = utils.verifyMessage("hexcowboy", "0x27876c5cc1be67e8313633ab0f72ace790886c88a664df1ca0b858c1834782002a0ae1e44fdb3fe7586eec431c115205a36a05bbd31c6c2bce04605825e91cfe1b");
console.log(output);

When I try to recover the public key from this message using Go Ethereum, I'm not getting the correct public key using SigToPub.

func VerifyMessage(ctx context.Context, message string, signedMessage string) (string, error) {
    // Hash the unsigned message
    hash := crypto.Keccak256Hash([]byte(message))

    // Get the bytes of the signed message
    decodedMessage := hexutil.MustDecode(signedMessage)
    decodedMessage[64] = 0

    test, err := crypto.SigToPub(hash.Bytes(), decodedMessage)
    if err != nil {
        return "", err
    }

    fmt.Println(crypto.PubkeyToAddress(*test)) // 0xDd874aAD8bAf218e4DfAd2b59E2798E775e48eB5

    return "", nil
}

What do I need to do to recover an address from an ethers.js signed message in Go Ethereum?

Best Answer

Solved my answer. I wasn't aware I needed to add the prefix for EIP-191 signed messages. Here is my working code:

func VerifyMessage(ctx context.Context, message string, signedMessage string) (string, error) {
    // Hash the unsigned message using EIP-191
    hashedMessage := []byte("\x19Ethereum Signed Message:\n" + strconv.Itoa(len(message)) + message)
    hash := crypto.Keccak256Hash(hashedMessage)

    // Get the bytes of the signed message
    decodedMessage := hexutil.MustDecode(signedMessage)

    // Handles cases where EIP-115 is not implemented (most wallets don't implement it)
    if decodedMessage[64] == 27 || decodedMessage[64] == 28 {
        decodedMessage[64] -= 27
    }

    // Recover a public key from the signed message
    sigPublicKeyECDSA, err := crypto.SigToPub(hash.Bytes(), decodedMessage)
    if sigPublicKeyECDSA == nil {
        err = errors.New("Could not get a public get from the message signature")
    }
    if err != nil {
        return "", err
    }

    return crypto.PubkeyToAddress(*sigPublicKeyECDSA).String(), nil
}
Related Topic