[Ethereum] Recover public key from transaction signature

addressespublic-key

I have this transaction:

{
  "blockHash": "0x163ad564b32bf17db9262391c2c9a5f191e4fd0b098ae5ce9d2e3e7dcde4bd70",
  "blockNumber": "0x2",
  "from": "0x55d40a83b8445c004df5964ce4a0e261b599c01a",
  "gas": "0x76c0",
  "gasPrice": "0x9184e72a000",
  "hash": "0x5e97c5377da7864e5b82784c9e5335a2e075adfec3b075f5f2e5705733aacf8b",
  "input": "0x",
  "nonce": "0x8",
  "to": "0x589724c5abf2bce219726477cf0a60fa09321380",
  "transactionIndex": "0x0",
  "value": "0x1",
  "v": "0x1c",
  "r": "0xfe084488847df1cf427f72784c4b1ba86526ed5f8ef0feb6bc51caf450d66e8b",
  "s": "0x6226e5f4dd0d948b7014199faf018c27862c56faad0b49197057816dee9a7fa8"
}

So I made two files from the data of the transaction above: msg (transaction hash) & sig (r + s + v-27).

[root .eth]# hexdump -C msg
00000000  5e 97 c5 37 7d a7 86 4e  5b 82 78 4c 9e 53 35 a2  |^..7}..N[.xL.S5.|
00000010  e0 75 ad fe c3 b0 75 f5  f2 e5 70 57 33 aa cf 8b  |.u....u...pW3...|
00000020
[root .eth]# hexdump -C sig
00000000  fe 08 44 88 84 7d f1 cf  42 7f 72 78 4c 4b 1b a8  |..D..}..B.rxLK..|
00000010  65 26 ed 5f 8e f0 fe b6  bc 51 ca f4 50 d6 6e 8b  |e&._.....Q..P.n.|
00000020  62 26 e5 f4 dd 0d 94 8b  70 14 19 9f af 01 8c 27  |b&......p......'|
00000030  86 2c 56 fa ad 0b 49 19  70 57 81 6d ee 9a 7f a8  |.,V...I.pW.m....|
00000040  01                                                |.|
00000041

Then I wrote a program in go to recover the public key:

[root@v48807 .eth]# cat recover-pk.go
package main

import (
  "io/ioutil"
  "fmt"
  "os"

        secp256k1 "github.com/ethereum/go-ethereum/crypto/secp256k1"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
        msg, err := ioutil.ReadFile("msg")
  check(err)

        sig, err := ioutil.ReadFile("sig")
  check(err)

  pkbin, err := secp256k1.RecoverPubkey(msg, sig)
  check(err)

        f, err := os.Create("pkbin")
  check(err)
  defer f.Close()
        n, err := f.Write(pkbin)
  check(err)
  fmt.Printf("wrote %d bytes\n", n)
}

Here is a dump of the resulting public key:

[root .eth]# hexdump -C pkbin
00000000  04 bc 63 e4 84 c2 ed 68  56 8d 20 74 b0 ff 2e a1  |..c....hV. t....|
00000010  b0 51 02 3d d9 c8 f2 4c  c0 c2 5d 93 f4 14 2b bc  |.Q.=...L..]...+.|
00000020  40 56 d9 4b 32 dc 1e 65  74 6b c7 0a 3e fe 2a ba  |@V.K2..etk..>.*.|
00000030  db d2 e3 74 d8 ad 9c fd  d1 0c f1 1b 12 74 ef df  |...t.........t..|
00000040  53                                                |S|
00000041

The problem is that this public key is a different from that that I generated in the first place:

[root .eth]# cat addr1/address
55d40a83b8445c004df5964ce4a0e261b599c01a
[root .eth]# hexdump -C addr1/pubo-bin
00000000  04 0c 2d 16 2c 3d 76 cd  47 de e5 84 c9 9e 08 80  |..-.,=v.G.......|
00000010  b4 f2 2a 38 3b 7e bc bb  f6 cc bb 25 4a fe 01 b6  |..*8;~.....%J...|
00000020  dd 37 de ee ee b1 06 9a  af 39 f0 e8 c4 6a f7 ca  |.7.......9...j..|
00000030  53 01 5f 8f 73 7e 57 cc  2b 7a 61 32 35 54 e7 9c  |S._.s~W.+za25T..|
00000040  26                                                |&|
00000041

As you can see, the ethereum address (file address) matches the one from was used to send the ether in the transaction in the beginning of the post.

I generated the keypair like this:

[root .eth]# cat genkey.sh
#!/usr/bin/env bash
openssl ecparam -name secp256k1 -genkey -noout | openssl ec -text -noout > keypair
cat keypair | grep pub -A 5 | tail -n +2 | tr -d '\n[:space:]:' | sed 's/^04//' > pub
cat keypair | grep priv -A 3 | tail -n +2 | tr -d '\n[:space:]:' | sed 's/^00//' > priv
cat pub | keccak-256sum -x -l | tr -d ' -' | tail -c 41 > address
cat priv | xxd -r -p > priv-bin
cat pub | xxd -r -p > pub-bin
#geth account import priv

So what might be the problem? Why the recovered public key doesn't match the original public key?

Best Answer

You are using wrong hash when recovering public key. You hashed the whole transaction including the signature, but this does not make much sense: hash that ought be signed cannot depend on the signature. You need to hash only those parts of the transaction that were known before the signing: "to", "value", "data", "nonce", "gas", and "gas price" (EIP-155 added "chain ID" to the list). Here is how these six values look in RLP encoding:

// ["0x08","0x09184e72a000","0x76c0","0x589724c5abf2bce219726477cf0a60fa09321380","0x01","0x"]
0xe2088609184e72a0008276c094589724c5abf2bce219726477cf0a60fa093213800180

Here is the hash of the above to be singed:

0x7eb00f90181255e010b43c944da6f9c7575e595467a710c0925263921ead9107

Here is your raw transaction with signature:

0xf865088609184e72a0008276c094589724c5abf2bce219726477cf0a60fa0932
  138001801ca0fe084488847df1cf427f72784c4b1ba86526ed5f8ef0feb6bc51
  caf450d66e8ba06226e5f4dd0d948b7014199faf018c27862c56faad0b491970
  57816dee9a7fa8

Here is the signature alone:

0xfe084488847df1cf427f72784c4b1ba86526ed5f8ef0feb6bc51caf450d66e8b
  6226e5f4dd0d948b7014199faf018c27862c56faad0b49197057816dee9a7fa8
  1c

And here is public key obtained from the signature and proper hash:

0x040c2d162c3d76cd47dee584c99e0880b4f22a383b7ebcbbf6ccbb254afe01b6
  dd37deeeeeb1069aaf39f0e8c46af7ca53015f8f737e57cc2b7a61323554e79c
  26

BTW, I used ABDK Toolkit to make all these calculations.