Search code examples
gogo-ethereumgeth

Geth ecrecover invalid signature recovery id


I'm trying to verify signature passed in from client using the geth golang library. I'm getting the sample data (signature / address) from one of my cryptokitties accounts (I can see it in the request). If I paste the blow credentials into https://etherscan.io/verifySig, it gets verified, so I know the parameters are correct.

My code:

import (
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/crypto"
)

sig := 0x80f5bac5b6300ed64835d5e2f167a368c892ccc2d0e252bc84befbcb093f5a2d36294b95d86683cec778c8c796049933c04c71576c56c1d6e9a9fa10342beca31c

data := "Cryptokitties"
decoded = hexutil.MustDecode(sig) // j8aUTtPid0ZnNa/s4Ef5gisYYh1bCeLSmFrtJtDjNRRqxShUr+1A3BVgoAPwiZ+lKN0POB1JOdVhVHI9tcHmABs=
hash := crypto.Keccak256([]byte(data)) // "ljQQTm25oqIbD+LMl70aRUcTzXCeeDGfkRj9YJYsgKY="
pubKey, err := crypto.Ecrecover(hash, sig) // error: "invalid signature recovery id"

I'm sure I'm missing something simple, but not really sure where to look.

**UPDATE

What I tried after looking around some answers:

  • changing the message like this: fmt.Sprintf("\u0019Ethereum Signed Message:\n%d%s", len(data), data) //"\u0019Ethereum Signed Message:\n13Cryptokitties"

  • hex encoding the message before hashing it: data=hexutil.Encode(data)

  • combining the above two, so first prepending the 'Ethereum Signed Message', then hexencoding it.

Any points would be appreciated, I'm sure this is a noob question.

**UPDATE

Looking through the source code, I discovered its expecting recovery id to be greater than 4: sig[64] >= 4

In my case it turns out to be 27: sig[64] --> 27


Solution

  • See: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459

    // EcRecover returns the address for the account that was used to create the signature.
    // Note, this function is compatible with eth_sign and personal_sign. As such it recovers
    // the address of:
    // hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
    // addr = ecrecover(hash, signature)
    //
    // Note, the signature must conform to the secp256k1 curve R, S and V values, where
    // the V value must be be 27 or 28 for legacy reasons.
    //
    // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
    func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
        if len(sig) != 65 {
            return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
        }
        if sig[64] != 27 && sig[64] != 28 {
            return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
        }
        sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
    
        rpk, err := crypto.Ecrecover(signHash(data), sig)
        if err != nil {
            return common.Address{}, err
        }
        pubKey := crypto.ToECDSAPub(rpk)
        recoveredAddr := crypto.PubkeyToAddress(*pubKey)
        return recoveredAddr, nil
    }
    

    Here is my gist: https://gist.github.com/dcb9/385631846097e1f59e3cba3b1d42f3ed#file-eth_sign_verify-go