Skip to content

Instantly share code, notes, and snippets.

@axic
Last active April 13, 2024 09:01
Show Gist options
  • Star 58 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save axic/5b33912c6f61ae6fd96d6c4a47afde6d to your computer and use it in GitHub Desktop.
Save axic/5b33912c6f61ae6fd96d6c4a47afde6d to your computer and use it in GitHub Desktop.
Ethereum ECVerify
//
// The new assembly support in Solidity makes writing helpers easy.
// Many have complained how complex it is to use `ecrecover`, especially in conjunction
// with the `eth_sign` RPC call. Here is a helper, which makes that a matter of a single call.
//
// Sample input parameters:
// (with v=0)
// "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad",
// "0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf200",
// "0x0e5cb767cce09a7f3ca594df118aa519be5e2b5a"
//
// (with v=1)
// "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad",
// "0xdebaaa0cddb321b2dcaaf846d39605de7b97e77ba6106587855b9106cb10421561a22d94fa8b8a687ff9c911c844d1c016d1a685a9166858f9c7c1bc85128aca01",
// "0x8743523d96a1b2cbe0c6909653a56da18ed484af"
//
// (The hash is a hash of "hello world".)
//
// Written by Alex Beregszaszi (@axic), use it under the terms of the MIT license.
//
library ECVerify {
// Duplicate Solidity's ecrecover, but catching the CALL return value
function safer_ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal returns (bool, address) {
// We do our own memory management here. Solidity uses memory offset
// 0x40 to store the current end of memory. We write past it (as
// writes are memory extensions), but don't update the offset so
// Solidity will reuse it. The memory used here is only needed for
// this context.
// FIXME: inline assembly can't access return values
bool ret;
address addr;
assembly {
let size := mload(0x40)
mstore(size, hash)
mstore(add(size, 32), v)
mstore(add(size, 64), r)
mstore(add(size, 96), s)
// NOTE: we can reuse the request memory because we deal with
// the return code
ret := call(3000, 1, 0, size, 128, size, 32)
addr := mload(size)
}
return (ret, addr);
}
function ecrecovery(bytes32 hash, bytes sig) returns (bool, address) {
bytes32 r;
bytes32 s;
uint8 v;
if (sig.length != 65)
return (false, 0);
// The signature format is a compact form of:
// {bytes32 r}{bytes32 s}{uint8 v}
// Compact means, uint8 is not padded to 32 bytes.
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
// Here we are loading the last 32 bytes. We exploit the fact that
// 'mload' will pad with zeroes if we overread.
// There is no 'mload8' to do this, but that would be nicer.
v := byte(0, mload(add(sig, 96)))
// Alternative solution:
// 'byte' is not working due to the Solidity parser, so lets
// use the second best option, 'and'
// v := and(mload(add(sig, 65)), 255)
}
// albeit non-transactional signatures are not specified by the YP, one would expect it
// to match the YP range of [27, 28]
//
// geth uses [0, 1] and some clients have followed. This might change, see:
// https://github.com/ethereum/go-ethereum/issues/2053
if (v < 27)
v += 27;
if (v != 27 && v != 28)
return (false, 0);
return safer_ecrecover(hash, v, r, s);
}
function ecverify(bytes32 hash, bytes sig, address signer) returns (bool) {
bool ret;
address addr;
(ret, addr) = ecrecovery(hash, sig);
return ret == true && addr == signer;
}
}
contract ECVerifyTest {
function test_v0() returns (bool) {
bytes32 hash = 0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad;
bytes memory sig = "\xac\xa7\xda\x99\x7a\xd1\x77\xf0\x40\x24\x0c\xdc\xcf\x69\x05\xb7\x1a\xb1\x6b\x74\x43\x43\x88\xc3\xa7\x2f\x34\xfd\x25\xd6\x43\x93\x46\xb2\xba\xc2\x74\xff\x29\xb4\x8b\x3e\xa6\xe2\xd0\x4c\x13\x36\xea\xce\xaf\xda\x3c\x53\xab\x48\x3f\xc3\xff\x12\xfa\xc3\xeb\xf2\x00";
return ECVerify.ecverify(hash, sig, 0x0e5cb767cce09a7f3ca594df118aa519be5e2b5a);
}
function test_v1() returns (bool) {
bytes32 hash = 0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad;
bytes memory sig = "\xde\xba\xaa\x0c\xdd\xb3\x21\xb2\xdc\xaa\xf8\x46\xd3\x96\x05\xde\x7b\x97\xe7\x7b\xa6\x10\x65\x87\x85\x5b\x91\x06\xcb\x10\x42\x15\x61\xa2\x2d\x94\xfa\x8b\x8a\x68\x7f\xf9\xc9\x11\xc8\x44\xd1\xc0\x16\xd1\xa6\x85\xa9\x16\x68\x58\xf9\xc7\xc1\xbc\x85\x12\x8a\xca\x01";
return ECVerify.ecverify(hash, sig, 0x8743523d96a1b2cbe0c6909653a56da18ed484af);
}
}
@movee2016
Copy link

I am not getting the correct results if I call the functions from web3 on a parity client. Any clues?

@maraoz
Copy link

maraoz commented Nov 23, 2017

@sullof
Copy link

sullof commented Jan 5, 2018

@axic, @maraoz, thanks for your great libraries. Unfortunately, I have a blocking problem.
A smart contract receives signatures from outside. If the signature is passed as a parameter generated by a dApp, it's easy to pass bytes. But, if the smart contract recovers the signature, for example, from an oracle, the recovered signature is an hex string. It would be useful to have an helper that does the necessary conversion.
I searched everywhere, but I was unable to find a way to convert a string like 0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf200
to a bytes. Any idea?

@sullof
Copy link

sullof commented Jan 8, 2018

So, I solved this way:

pragma solidity ^0.4.18;


contract ECTools {

  // @dev Recovers the address which has signed a message
  // @thanks https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d
  function recoverSigner(bytes32 _hashedMsg, string _sig) public constant returns (address){
    require(_hashedMsg != 0x00);

    if (bytes(_sig).length != 132) {
      return 0x0;
    }
    bytes32 r;
    bytes32 s;
    uint8 v;
    bytes memory sig = hexstrToBytes(substring(_sig, 2, 132));
    assembly {
      r := mload(add(sig, 32))
      s := mload(add(sig, 64))
      v := byte(0, mload(add(sig, 96)))
    }
    if (v < 27) {
      v += 27;
    }
    if (v < 27 || v > 28) {
      return 0x0;
    }
    return ecrecover(_hashedMsg, v, r, s);
  }

  // @dev Verifies if the message is signed by an address
  function isSignedBy(bytes32 _hashedMsg, string _sig, address _addr) public constant returns (bool){
    require(_addr != 0x0);

    return _addr == recoverSigner(_hashedMsg, _sig);
  }

  // @dev Converts an hexstring to bytes
  function hexstrToBytes(string _hexstr) public constant returns (bytes) {
    uint len = bytes(_hexstr).length;
    require(len % 2 == 0);

    bytes memory bstr = bytes(new string(len / 2));
    uint k = 0;
    string memory s;
    string memory r;
    for (uint i = 0; i < len; i += 2) {
      s = substring(_hexstr, i, i + 1);
      r = substring(_hexstr, i + 1, i + 2);
      uint p = parseInt16Char(s) * 16 + parseInt16Char(r);
      bstr[k++] = uintToBytes32(p)[31];
    }
    return bstr;
  }

  // @dev Parses a hexchar, like 'a', and returns its hex value, in this case 10
  function parseInt16Char(string _char) public constant returns (uint) {
    bytes memory bresult = bytes(_char);
    bool decimals = false;
    if ((bresult[0] >= 48) && (bresult[0] <= 57)) {
      return uint(bresult[0]) - 48;
    } else if ((bresult[0] >= 65) && (bresult[0] <= 70)) {
      return uint(bresult[0]) - 55;
    } else if ((bresult[0] >= 97) && (bresult[0] <= 102)) {
      return uint(bresult[0]) - 87;
    } else {
      revert();
    }
  }

  // @dev Converts a uint to a bytes32
  // @thanks https://ethereum.stackexchange.com/questions/4170/how-to-convert-a-uint-to-bytes-in-solidity
  function uintToBytes32(uint _uint) public constant returns (bytes b) {
    b = new bytes(32);
    assembly {mstore(add(b, 32), _uint)}
  }

  // @dev Hashes the signed message
  function toEthereumSignedMessage(string _msg) public constant returns (bytes32) {
    uint len = bytes(_msg).length;
    require(len > 0);
    bytes memory prefix = "\x19Ethereum Signed Message:\n";
    return keccak256(prefix, uintToString(len), _msg);
  }

  // @dev Converts a uint in a string
  function uintToString(uint _uint) public constant returns (string str) {
    uint len = 0;
    uint m = _uint + 0;
    while (m != 0) {
      len++;
      m /= 10;
    }
    bytes memory b = new bytes(len);
    uint i = len - 1;
    while (_uint != 0) {
      uint remainder = _uint % 10;
      _uint = _uint / 10;
      b[i--] = byte(48 + remainder);
    }
    str = string(b);
  }


  // @dev extract a substring
  // @thanks https://ethereum.stackexchange.com/questions/31457/substring-in-solidity
  function substring(string _str, uint _startIndex, uint _endIndex) public constant returns (string) {
    bytes memory strBytes = bytes(_str);
    require(_startIndex <= _endIndex);
    require(_startIndex >= 0);
    require(_endIndex <= strBytes.length);

    bytes memory result = new bytes(_endIndex - _startIndex);
    for (uint i = _startIndex; i < _endIndex; i++) {
      result[i - _startIndex] = strBytes[i];
    }
    return string(result);
  }

}

In the next days I will optimize it, making it a library to reduce gas consumption. Any suggestion would be very appreciated.

I also tested it. When ready I will post it in a repo.

@chris-shyft
Copy link

@sullof thanks for that :) post here when you get a chance to library-ify it :)

@ToJen
Copy link

ToJen commented Feb 16, 2018

@sullof that looks very useful! Have you been able to make a library yet?

@yezooz
Copy link

yezooz commented Feb 22, 2018

pragma solidity ^0.4.18;


library ECTools {

  // @dev Recovers the address which has signed a message
  // @thanks https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d
  function recoverSigner(bytes32 _hashedMsg, string _sig) public pure returns (address) {
    require(_hashedMsg != 0x00);

    if (bytes(_sig).length != 132) {
      return 0x0;
    }
    bytes32 r;
    bytes32 s;
    uint8 v;
    bytes memory sig = hexstrToBytes(substring(_sig, 2, 132));
    assembly {
      r := mload(add(sig, 32))
      s := mload(add(sig, 64))
      v := byte(0, mload(add(sig, 96)))
    }
    if (v < 27) {
      v += 27;
    }
    if (v < 27 || v > 28) {
      return 0x0;
    }
    return ecrecover(_hashedMsg, v, r, s);
  }

  // @dev Verifies if the message is signed by an address
  function isSignedBy(bytes32 _hashedMsg, string _sig, address _addr) public pure returns (bool) {
    require(_addr != 0x0);

    return _addr == recoverSigner(_hashedMsg, _sig);
  }

  // @dev Converts an hexstring to bytes
  function hexstrToBytes(string _hexstr) public pure returns (bytes) {
    uint len = bytes(_hexstr).length;
    require(len % 2 == 0);

    bytes memory bstr = bytes(new string(len / 2));
    uint k = 0;
    string memory s;
    string memory r;
    for (uint i = 0; i < len; i += 2) {
      s = substring(_hexstr, i, i + 1);
      r = substring(_hexstr, i + 1, i + 2);
      uint p = parseInt16Char(s) * 16 + parseInt16Char(r);
      bstr[k++] = uintToBytes32(p)[31];
    }
    return bstr;
  }

  // @dev Parses a hexchar, like 'a', and returns its hex value, in this case 10
  function parseInt16Char(string _char) public pure returns (uint) {
    bytes memory bresult = bytes(_char);
    // bool decimals = false;
    if ((bresult[0] >= 48) && (bresult[0] <= 57)) {
      return uint(bresult[0]) - 48;
    } else if ((bresult[0] >= 65) && (bresult[0] <= 70)) {
      return uint(bresult[0]) - 55;
    } else if ((bresult[0] >= 97) && (bresult[0] <= 102)) {
      return uint(bresult[0]) - 87;
    } else {
      revert();
    }
  }

  // @dev Converts a uint to a bytes32
  // @thanks https://ethereum.stackexchange.com/questions/4170/how-to-convert-a-uint-to-bytes-in-solidity
  function uintToBytes32(uint _uint) public pure returns (bytes b) {
    b = new bytes(32);
    assembly {mstore(add(b, 32), _uint)}
  }

  // @dev Hashes the signed message
  // @ref https://github.com/ethereum/go-ethereum/issues/3731#issuecomment-293866868
  function toEthereumSignedMessage(string _msg) public pure returns (bytes32) {
    uint len = bytes(_msg).length;
    require(len > 0);
    bytes memory prefix = "\x19Ethereum Signed Message:\n";
    return keccak256(prefix, uintToString(len), _msg);
  }

  // @dev Converts a uint in a string
  function uintToString(uint _uint) public pure returns (string str) {
    uint len = 0;
    uint m = _uint + 0;
    while (m != 0) {
      len++;
      m /= 10;
    }
    bytes memory b = new bytes(len);
    uint i = len - 1;
    while (_uint != 0) {
      uint remainder = _uint % 10;
      _uint = _uint / 10;
      b[i--] = byte(48 + remainder);
    }
    str = string(b);
  }


  // @dev extract a substring
  // @thanks https://ethereum.stackexchange.com/questions/31457/substring-in-solidity
  function substring(string _str, uint _startIndex, uint _endIndex) public pure returns (string) {
    bytes memory strBytes = bytes(_str);
    require(_startIndex <= _endIndex);
    require(_startIndex >= 0);
    require(_endIndex <= strBytes.length);

    bytes memory result = new bytes(_endIndex - _startIndex);
    for (uint i = _startIndex; i < _endIndex; i++) {
      result[i - _startIndex] = strBytes[i];
    }
    return string(result);
  }
}

@Tony3210
Copy link

How do we calculate the gas to employ and call ECVerifyTest contract?

@ice09
Copy link

ice09 commented Sep 5, 2018

@yezooz @sullof thanks a lot, that's very helpful. FWIW I created a Java signer/ecrecover which is "compatible" to the ECTools https://gist.github.com/ice09/736a483675287659c91f71e70a049300

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment