0

I've got an Arduino microcontroller (ATmega328P) where I can use the following code (borrowed from here) snippet to sign an arbitrary message . First, I take the keccak256 hash of the message, and the code produces a hash that is successfully verified using Etherscan's "Verify Message" tool. The message below is 1234

static const uint8_t nprivateKey[] = {
0x02,0x86,0xeb,0xe8,0x02,0xee,0x88,0x2c,
0x16,0x3b,0xee,0x41,0xbd,0x6b,0xbd,0x93,
0x1b,0xf1,0x7a,0x8a,0x2d,0xcf,0xa7,0x28,
0xf5,0xb5,0x2d,0x47,0xd0,0x80,0x6d,0x0a};

  // keccak256 of "1234": 
  0x387a8233c96e1fc0ad5e284353276177af2186e7afa85296f106336e376669f7
  uint8_t hash[32] = {
  0x38, 0x7a, 0x82, 0x33, 0xc9, 0x6e, 0x1f, 0xc0,
  0xad, 0x5e, 0x28, 0x43, 0x53, 0x27, 0x61, 0x77,
  0xaf, 0x21, 0x86, 0xe7, 0xaf, 0xa8, 0x52, 0x96,
  0xf1, 0x06, 0x33, 0x6e, 0x37, 0x66, 0x69, 0xf7
};
  
uECC_sign(nprivateKey, hash, 32, signature, uECC_secp256k1());
signed_tx = String(convertBytesKeyToHexKey(signature, 65));

Sometimes we need to sign transaction hashes, as when interacting with a Gnosis safe. The code above produces a verifiable signature for 1234, but when I take the keccak256 hash of a transaction hash, the signature produced is invalid. I've also tried using the 65-byte transaction hash directly, skipping the keccak256 step, but the resulting signature is also invalid.

When I use python to sign the same transaction hash, however, the signature is valid:

from eth_account import Account
from hexbytes import  HexBytes
from web3 import Web3

web3 = Web3()
contract_transaction_hash = HexBytes('0x610f9dee9a3d29d00a8af5096ce777f413b5975de0f6deff60b0e15863d7e0fd0f010604e2608b4fb355e551bcf545352eb50dfd1493126bc1e30a9c3094195b64')
account = Account.from_key('0x0286ebe802ee882c163bee41bd6bbd931bf17a8a2dcfa728f5b52d47d0806d0a')
signature = account.signHash(contract_transaction_hash)
print(signature.signature.hex()) #this signature is valid

So, for the microcontroller, in the case of a message like 1234, taking the keccak256 hash and signing produces a valid signature, but in the case of signing a transaction hash, the signature is invalid. If I use python to sign the transaction hash, however, the signature is valid.

This means that there is some difference between what the python code is doing to sign and what the C code of the microcontroller is doing to sign.

Is there a fundamental difference between signing an arbitrary message and signing a transaction hash? Does signHash first take the keccak256 hash of the message before signing?

How does the eth_account signHash function work under the hood, and how is it different from what the ethers uECC_sign function is doing?

2
  • I don't know any of the libraries you are using but have you tried to create a hex buffer from the 65-byte transaction hash rather than using keccak256. Jan 3 at 18:20
  • @ruby_newbie yes I think I tried using hash directly with uint8_t hash[65] but no dice. I will double check though
    – Ryan
    Jan 3 at 21:57

1 Answer 1

0

For signing Gnosis safe transaction hashes, I had to add 1c to the end of uECC_sign output to produce a valid signature

For signing a message (to verify a signature), I had to add 1b to the end of uECC_sign output to produce a valid signature

I tried appending those because of a question here. To my surprise, it worked.

Also I had to change the size parameter when calling convertBytesKeyToHexKey to 64.

Now it works

So at least in this case, the answer to the question seems to be: Add "1c" when signing a tx hash and "1b" when signing a message hash. When signing a tx hash, no need to take keccak256 of the hash before signing.

//same nprivateKey and hash values as above

uint8_t signature[64];
uECC_sign(nprivateKey, hash, 32, signature, uECC_secp256k1());

//for message signature (verifying signature) add 1b to the end of the 
signature

signed_tx = "0x" + String(convertBytesKeyToHexKey(signature, 64)) + "1b";

/*
//for signing Gnosis tx hash, add 1c to the end of the signature
gnosis_signed_tx = "0x" + String(convertBytesKeyToHexKey(signature, 64)) + "1c";
*/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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