2

can anyone spot what I am doing wrong? I am trying to recover the public address from data signed by web3.personal.sign in Metamask and with this code the public address comes out wrong. Hash and hex/de-hex functions work fine (tested).

 #include "secp256k1.h"
 #include "secp256k1_recovery.h"

std::string ecrecover(std::string sig, std::string msg)
{
  std::string _sig = hex_to_string(sig.substr(2)); // strip 0x

  size_t len = 32;
  size_t biglen = 65;

  int v = _sig[64];
  _sig = _sig.substr(0,64);

  if(v>3)
    v-=27;

  auto* ctx = secp256k1_context_create( SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY );

  secp256k1_ecdsa_recoverable_signature rawSig;
  if(!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rawSig, (unsigned char*)_sig.data(), v))
    {
      cout<<"Cannot parse compact"<<endl;
      return ("0");
    }

  uint8_t hash[len];
  keccak_256(hash, len, (unsigned char*)msg.data(), msg.length());                                       // hash message
  msg = string("\x19")+"Ethereum Signed Message:\n32"+string_to_hex(string(hash, hash+len));                                      // wrap message hash
  keccak_256(hash, len, (unsigned char*)msg.data(), msg.length());                                       // hash wrapped message hash

  secp256k1_pubkey rawPubkey;
  if(!secp256k1_ecdsa_recover(ctx, &rawPubkey, &rawSig, hash))                                           // 32 bit hash
    {
      cout<<"Cannot recover key"<<endl;
      return ("0");
    }

  uint8_t pubkey_serialized[biglen];
  secp256k1_ec_pubkey_serialize(ctx, pubkey_serialized, &biglen, &rawPubkey, SECP256K1_EC_UNCOMPRESSED);

  std::string out = std::string((char*)pubkey_serialized).substr(1);
  keccak_256(hash, len, (const unsigned char*)out.data(), out.length());

  return("0x"+bytes_to_hex_string(hash,len).substr(24));
}

Changed the byte arrays to std::array and still the same incorrect results:

std::string ecrecover(std::string sig, std::string msg)
{
  std::string _sig = hex_to_string(sig.substr(2)); // strip 0x


  int v = _sig[64];
  _sig = _sig.substr(0,64);

  if(v>3)
    v-=27;

  auto* ctx = secp256k1_context_create( SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY );

  secp256k1_ecdsa_recoverable_signature rawSig;
  if(!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rawSig, (unsigned char*)_sig.data(), v))
    {
      cout<<"Cannot parse compact"<<endl;
      return ("0x00000000000000000000000000000000");
    }

  std::array<uint8_t,32> hash;
  keccak_256(hash.data(), hash.size(), (unsigned char*)msg.data(), msg.length());                               // hash message
  msg = string("\x19")+"Ethereum Signed Message:\n320x"+string_to_hex(string(hash.begin(), hash.end()));        // wrap message hash
  keccak_256(hash.data(), hash.size(), (unsigned char*)msg.data(), msg.length());                               // hash wrapped message hash

  // hash produced here is same as produced by web3.sha3 javascript:
  // sha = web3.sha3(msg);
  // console.log('sha:\n'+sha);
  // console.log(web3.sha3("\x19Ethereum Signed Message:\n32"+sha));

  secp256k1_pubkey rawPubkey;
  if(!secp256k1_ecdsa_recover(ctx, &rawPubkey, &rawSig, hash.data()))                                           // 32 bit hash
    {
      cout<<"Cannot recover key"<<endl;
      return ("0x00000000000000000000000000000000");
    }

  std::array<uint8_t,65> pubkey_serialized;
  size_t biglen = pubkey_serialized.size();

  secp256k1_ec_pubkey_serialize(ctx, pubkey_serialized.data(), &biglen, &rawPubkey, SECP256K1_EC_UNCOMPRESSED);
  std::string out = std::string(pubkey_serialized.begin(),pubkey_serialized.end()).substr(1);

  keccak_256(hash.data(), hash.size(), (const unsigned char*)out.data(), out.length());
  return("0x"+string_to_hex(string(hash.begin(),hash.end())).substr(24));
}

The hash produced in the first half of the code that is used in secp256k1_ecdsa_recover is the same hash produced by (javascript) web3.sha3 so it appears to be correct (unless I am missing a step in the javascript hashing):

sha = web3.sha3(msg);
console.log('sha:\n'+sha);
console.log(web3.sha3("\x19Ethereum Signed Message:\n32"+sha));
  • What's the length of the second msg? You use hash as char* but you never make sure it is null terminated. – Ismael Sep 13 at 5:09
  • Thanks for pointing that out, I know that's wrong and I have updated it... it now gives the same hash result as web3.sha3 to use in secp256k1_ecdsa_recover... still getting wrong addresses, however. – Forrest Pump Sep 13 at 10:36
  • You have the same mistake again with the pubKey, also it is possible for both hash and pubKey that they have a valid zero byte, you have to use the [begin, end) in the string constructor. – Ismael Sep 13 at 14:39
  • The only problem I had before is I forgot to hexify it (which I have done in my actual code, but somehow I forgot to put that in my sample code for this post). Isn't string(hash,hash+len) the same as (char*) in c++? While it's true c++ does not terminate like C does, are you sure we want our strings terminated in crypto? I even tried converting all the uint8_t arrays to std::array and got exactly the same results. – Forrest Pump Sep 13 at 21:05
1

Ok I got this to work by hacking into geth and having it show me some debug values and changing my code until they matched.

Thing is my test data was wrong because metamask wants to sign a HASH of the message and not the actual MESSAGE like geth does. Wish I had known that before!

Anyway here is the working code:

std::string ecrecover(std::string sig, std::string msg)
{
  std::string _sig = hex_to_string(sig.substr(2)); // strip 0x

  int v = _sig[64];
  _sig = _sig.substr(0,64);

  if(v>3)
    v-=27;

  auto* ctx = secp256k1_context_create( SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY );

  secp256k1_ecdsa_recoverable_signature rawSig;
  if(!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rawSig, (unsigned char*)_sig.data(), v))
    {
      cout<<"Cannot parse compact"<<endl;
      return ("0x00000000000000000000000000000000");
    }

  std::array<uint8_t,32> hash;
  keccak_256(hash.data(), hash.size(), (unsigned char*)msg.data(), msg.length());                                       // hash message
  msg = string("\x19")+"Ethereum Signed Message:\n32"+string(hash.begin(),hash.end());                                  // wrap message hash
  keccak_256(hash.data(), hash.size(), (unsigned char*)msg.data(), msg.length());                                       // hash wrapped message hash

  secp256k1_pubkey rawPubkey;
  if(!secp256k1_ecdsa_recover(ctx, &rawPubkey, &rawSig, hash.data()))                                                   // 32 bit hash
    {
      cout<<"Cannot recover key"<<endl;
      return ("0x00000000000000000000000000000000");
    }

  std::array<uint8_t,65> pubkey;
  size_t biglen = 65;

  secp256k1_ec_pubkey_serialize(ctx, pubkey.data(), &biglen, &rawPubkey, SECP256K1_EC_UNCOMPRESSED);

  std::string out = std::string(pubkey.begin(),pubkey.end()).substr(1);

  keccak_256(hash.data(), hash.size(), (const unsigned char*)out.data(), out.length());

  return("0x"+bytes_to_hex_string(hash.data(),hash.size()).substr(24));
}

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.