1

Both at signature creation and at verification the processing shall be done over the same data. Based on the description the following bytes shall be put together in an RLP-encoded form to be processed and hashed with Keccak-256 (non-NIST SHA3-256):

RLP(nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0)

At verification we can retrieve all these data from the raw_transaction which contains in addition the signature value parameters (v, r, s) as well:

RLP(nonce, gasPrice, gasLimit, to, value, data, v, r, s)

I have tried to do this verification (to perform public key recovery) with php-ecrecover. I have made some cross-tests, so it seems that base functions of php-ecrecover work correctly. I have used the valid transaction 0x0c79e3ae36150eb36d6a631cc8d6250db4b9b832a82ac58ea356357f5987debe in block 0xda214d1b1d458e7ae0e626b69a52a59d19762c51a53ff64813c4d31256282fdf. I have parsed the raw_transaction (0xf8708308f7da8506fc23ac0083015f9094ef9f4c1d4103d42e05d54063337086d6884aee9a880e0c8912851e356c801ba04ed3077c674d49918f3dce6e25781070fce36b915e4c36554e704556f6a3e00ba03974fb9426ce34d0c520f1245e75f5b14a62795dd01c69063079a5353eb6cfb7) element of this transaction at MyCrypto.com.

{
  "nonce": 587738, //0x08f7da
  "gasPrice": {
    "type": "BigNumber",
    "hex": "0x06fc23ac00"
  },
  "gasLimit": {
    "type": "BigNumber",
    "hex": "0x015f90"
  },
  "to": "0xeF9F4c1d4103D42E05d54063337086D6884aEe9a",
  "value": {
    "type": "BigNumber",
    "hex": "0x0e0c8912851e356c"
  },
  "data": "0x",    //NULL??? 0x80???
  "chainId": 0,    //0x00
  "v": 27,         //0x1b
  "r": "0x4ed3077c674d49918f3dce6e25781070fce36b915e4c36554e704556f6a3e00b",
  "s": "0x3974fb9426ce34d0c520f1245e75f5b14a62795dd01c69063079a5353eb6cfb7",
  "from": "0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8",
  "hash": "0x0c79e3ae36150eb36d6a631cc8d6250db4b9b832a82ac58ea356357f5987debe",
  "type": null
}

I have put together the data-to-be-hashed in an RLP-encoded form (Recursive Length Prefix). I have verified it with an online RLP Debugger. From RLP-encoding syntax aspect it seems to be correct, but I am not sure whether NULL value of data is correctly set based on RLP standard, or even 0 value of chainId and r and s are properly encoded at the end of data-to-be-processed string...

data-to-be-processed (RLP(nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0)) f8308308f7da8506fc23ac0083015f9094ef9f4c1d4103d42e05d54063337086d6884aee9a880e0c8912851e356c80000000

signature (r + s + v with no length) 4ed3077c674d49918f3dce6e25781070fce36b915e4c36554e704556f6a3e00b3974fb9426ce34d0c520f1245e75f5b14a62795dd01c69063079a5353eb6cfb71b

But, unfortunately the calculated value (provided by php-ecrecover) and the expected value (provided by MyCrypto) differs:

calculated value: 0x50d2a6dab68933716b9226db43884233dcdca315

expected value: 0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8

What am I wrong? Are the RLP-encoded NULL and 0 values correct in my data-to-be-processed string (80000000)?

5
  • Check eips.ethereum.org/EIPS/eip-155 for an example of RLP before signing. If I'm not wrong chainId should 01, r and s empty. The result should something like f08308f7da8506fc23ac0083015f9094ef9f4c1d4103d42e05d54063337086d6884aee9a880e0c8912851e356c80018080.
    – Ismael
    Feb 20, 2022 at 4:46
  • Thanks, I have corrected the first bytes of the data-to-be-processed string. But I am still confused... I have tried RLP with another implementation and it told me, that NULL is converted to 0x80 and 0 shall be 0x00. I have checked the EIP-155 example, but there the value 0 (in "signing data") is converted to 0x80. So, I am not sure now which RLP conversion rule may be the right for value 0...
    – baronsz
    Feb 20, 2022 at 21:21
  • But I tried several different versions (also with covering 9 elements or just 6 elements, without v/chainId, r, s): the one string you suggested (with also 0 value as chainId) and with 0x00 bytes instead of 0x80 bytes (0xf08308f7da8506fc23ac0083015f9094ef9f4c1d4103d42e05d54063337086d6884aee9a880e0c8912851e356c80000000) but both cases failed to recover the expected key. :-(
    – baronsz
    Feb 20, 2022 at 21:22
  • 0x80 is the zero length array. The final source of truth is go-ethereum source code transaction_signing.go. It might be that before EIP155 chainId, r, s weren't hashed.
    – Ismael
    Feb 21, 2022 at 5:43
  • Thanks, now it is OK! I have analyzed this official source code and now I understand what I was wrong. The thing that I was missing is that the blockNumber defines the signer type, and signer type defines the exact structure of data-to-be-processed.
    – baronsz
    Feb 21, 2022 at 8:48

1 Answer 1

1

The thing that I was missing is that the blockNumber defines the signer type, and signer type defines the exact structure of data-to-be-processed. In case of a FrontierSigner the rlpHash() covers just 6 elements, while at EIP155Signer and LegacyTxType of eip2930Signer all the 9 elements are protected. There are also other signer types, such as londonSigner (with AccessList element) or AccessListTxType of eip2930Signer. At this transaction the signer type was the default FrontierSigner because no other requirement was met (e.g. blockNumber >= FORK_BLKNUM requirement of EIP-155 failed, because 2,345,678 < 2,675,000).

data-to-be-processed (RLP(nonce, gasPrice, gasLimit, to, value, data)) ed8308f7da8506fc23ac0083015f9094ef9f4c1d4103d42e05d54063337086d6884aee9a880e0c8912851e356c80

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.