4

I have ECDSA signatures created using Golang without using web3 or anything related to Ethereum, and I want to verify these in Solidity.

Step 1: Signing the message in Golang

My Go code to create signatures:

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "fmt"

    "github.com/ethereum/go-ethereum/common/math"
    "github.com/ethereum/go-ethereum/crypto/secp256k1"
    "crypto/rand"
    "github.com/miguelmota/go-solidity-sha3"
    "encoding/hex"
    "strconv"
)

func KeyGen() (pubkey []byte, privkey []byte) {
    key, err := ecdsa.GenerateKey(S256(), rand.Reader)
    if err != nil {
        panic(err)
    }

    pubkey = elliptic.Marshal(S256(), key.X, key.Y)
    return pubkey, math.PaddedBigBytes(key.D, 32)
}

func Sign(message string, sk []byte) ([]byte) {
    hash := solsha3.SoliditySHA3(solsha3.String("\x19Ethereum Signed Message:\n"+strconv.FormatInt(int64(len(message)),10)+message))
    signature, _ := secp256k1.Sign(hash, sk)
    return signature
}

Basically, in the Sign method, I take the hash of

"\x19Ethereum Signed Message:\n" + message.length + message

and sign the hash.

My main method is:

//Create keypair and signature
pk, sk := KeyGen()
message := "TEST"
sig := Sign(message, sk)

//Print public key and sig in hex notation
fmt.Println("PK", hex.EncodeToString(pk))
fmt.Println("Sig", hex.EncodeToString(sig))

//Print hash
hash := solsha3.SoliditySHA3(solsha3.String("\x19Ethereum Signed Message:\n"+strconv.FormatInt(int64(len(message)),10)+message))
fmt.Println("hash", hex.EncodeToString(hash[:]))

Step 2: Verification in Solidity

In Solidity, I use a library by openzeppelin found at https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ECRecovery.sol to recover the public key. I put the three values printed in Go (hash, publickey, signature) as attributes of the contract and attempt to verify the signature:

contract ECDSA {

    bytes32 hash = hex"fb96181ff706848b10a93f4028537caf17026e28ce5c0cce90af46b4d3ad04c6";
    bytes sig = hex"cd044278098c2e5e36cc716423638509bbc818b0daadd7b7bba6322a99de67373255201bac6634b6adeb29d4c3bd9b3a6a14363c4e5b5eb3087c7882c7ebca6800";
    bytes publickey = hex"0485d169380f762e82da692c4b50c8873b1ef9820f15ae6cd96d0d4741056a1b9c828f4f027d0374f22311cba8ceb5845232549e6e4df7ae490b6903a5ee1447be";

    function verifysignature() returns (bool)
    {
        return recover(hash, sig) == address(keccak256(publickey));
    }
}

Question

The above does not return 'true' when given a valid signature. What am I doing wrong?

A few things I think could be wrong:

  • Is it even possible to use ecrecover to validate signatures that were not created by web3.eth.sign?
  • Do I use the right prefix for the message before hashing it?
  • Is address(keccak256(publickey)) the right way to convert public key to the corresponding address?

UPDATE

I created a gist of the full working solution based on the answer by smarx. The gist is found here.

4

The main issue is that you're signing "TEST" but then trying to recover the signer as though you signed a hash of "\x19Ethereum Signed Message:\n4TEST".

I'm also not confident you're emitting the public key properly, but you presumably only want the address anyway. Here's some working code:

Go:

package main

import (
    "crypto/ecdsa"
    "fmt"

    "crypto/rand"
    "encoding/hex"
    "github.com/ethereum/go-ethereum/common/math"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/crypto/secp256k1"
    "github.com/miguelmota/go-solidity-sha3"
)

func main() {
    key, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
    if err != nil {
        panic(err)
    }

    message := "TEST"
    // Turn the message into a 32-byte hash
    hash := solsha3.SoliditySHA3(solsha3.String(message))
    // Prefix and then hash to mimic behavior of eth_sign
    prefixed := solsha3.SoliditySHA3(solsha3.String("\x19Ethereum Signed Message:\n32" + hex.EncodeToString(hash)))
    sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32))
    if err != nil {
        panic(err)
    }

    fmt.Println("private key:", hex.EncodeToString(math.PaddedBigBytes(key.D, 32)))
    fmt.Println("address:", hex.EncodeToString(crypto.PubkeyToAddress(key.PublicKey).Bytes()))
    fmt.Println("message:", hex.EncodeToString(prefixed))
    fmt.Println("signature:", hex.EncodeToString(sig))
}

Solidity (values come from the Go program's output):

pragma solidity ^0.4.24;

contract Test {
    function recover(bytes32 hash, bytes sig)
        internal
        pure
        returns (address)
    {
        bytes32 r;
        bytes32 s;
        uint8 v;

        // Check the signature length
        if (sig.length != 65) {
            return (address(0));
        }

        // Divide the signature in r, s and v variables
        // ecrecover takes the signature parameters, and the only way to get them
        // currently is to use assembly.
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }

        // Version of signature should be 27 or 28, but 0 and 1 are also possible versions
        if (v < 27) {
            v += 27;
        }

        // If the version is correct return the signer address
        if (v != 27 && v != 28) {
            return (address(0));
        } else {
            // solium-disable-next-line arg-overflow
            return ecrecover(hash, v, r, s);
        }
    }

    function verifySignature() public pure returns (bool) {
        bytes32 message = 0x2b350a58f723b94ef3992ad0d3046f2398aef2fe117dc3a36737fb29df4a706a;
        bytes memory sig = hex"e6ca6508de09cbb639216743721076bc8beb7bb45e796e0e3422872f9f0fcd362e693be7ca40e2123dd1efaf71ebb94d38052458281ad3b69ec8977c8294928400";
        address addr = 0x8e6a1f13a9c6b9443fea4393291308ac4c965b69;

        return recover(message, sig) == addr;
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.