Skip to content

Instantly share code, notes, and snippets.

@axic
Last active May 13, 2024 12:07
Show Gist options
  • 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);
}
}
@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