How do you recover the EC public key from a VRS style signature in python?

I am trying to setup some encryption between two parties via ECIES, but I first wanted to obtain the public key via recovery from a transaction. The following is an example transaction from my geth console I've been working with.

  blockHash: "0xfdc5a1c9e1a0dcdfb2b9884919b79157a04eb15b0b58c91131e957f2803f8195",
  blockNumber: 100449,
  from: "0xc6f4f527587ea4a03aa85e0322783592367c1b9a",
  gas: 100000,
  gasPrice: 18000000000,
  hash: "0x912b33e09311a72b988af66918abcd53f43831688c7acb6b44870c491f073dab",
  input: "0x",
  nonce: 21,
  r: "0xab90122dc4e4bbdbb14ef22ad3ae21aecc19a1c90a9c8989c68b26cc782ff303",
  s: "0x36e5f275147049d3afd5d33b735cc9313d2c1aad3ab401aefdce678128e2f1d0",
  to: "0xa593094cebb06bf34df7311845c2a34996b52324",
  transactionIndex: 0,
  v: "0x1c",
  value: 1000000000000

From the transaction above, I have the raw material for the signature (e.g., V, R, S) and the sender (i.e., "from" field) which I can use to verify the address.

I'm trying to do this in python.

sender = "0xc6f4f527587ea4a03aa85e0322783592367c1b9a"
hash = "0x912b33e09311a72b988af66918abcd53f43831688c7acb6b44870c491f073dab"
r = "0xab90122dc4e4bbdbb14ef22ad3ae21aecc19a1c90a9c8989c68b26cc782ff303"
s ="0x36e5f275147049d3afd5d33b735cc9313d2c1aad3ab401aefdce678128e2f1d0"
v = "0x1c"
import bitcoin
import ethereum.keys #for a good sha3 implementation
from rlp.utils import decode_hex, encode_hex
rawhash = decode_hex(hash[2:]) # omit the 0x
vrs = (int(v[2:],16),int(r[2:],16),int(s[2:],16)) # omit the 0x and convert to integers based on base16 (hex)
rec_pub = bitcoin.ecdsa_raw_recover(rawhash,vrs) ## Trouble line!
enc_rec_pub = bitcoin.encode_pubkey(rec_pub,"bin")
rec_address = encode_hex(ethereum.keys.sha3(enc_rec_pub[1:]))[24:]
if rec_address == sender[2:]:
    print "[+++] The address was recovered from the public key! :-)"
    print "[-] This address is not the same :-("

The ecdsa_raw_recover never results in the correct value. I've opened up the keyfile to verify the public/private key and the following line never results in the correct value.

rec_pub = bitcoin.ecdsa_raw_recover(rawhash,vrs)

Is there some aspect to this that I am missing or a better recommendation on how to achieve this? Thanks in advance!


The rawhash you used is not right!

Read this: How to generate the rawhash in python.

Since you have the package ethereum in you code, the simplest way to get public key is:

#!/usr/bin/env python

import rlp
import ethereum
import ethereum.transactions

class MyTransaction(ethereum.transactions.Transaction):

    _publickey = None

    def publickey(self):
        secpk1n = ethereum.transactions.secpk1n
        if not self._publickey:
            if self.v in (27, 28):
                vee = self.v
                rlpdata = rlp.encode(
                    self, ethereum.transactions.UnsignedTransaction)
            elif self.v >= 37:
                vee = self.v - self.network_id * 2 - 8
                assert vee in (27, 28)
                rlpdata = rlp.encode(
                    rlp.infer_sedes(self).serialize(self)[:-3] \
                    + [self.network_id, '', ''])
                return None  # Invalid V value
            if self.r >= secpk1n or self.s >= secpk1n or self.r == 0 \
                    or self.s == 0:
                return None  # Invalid signature values!
            sighash = ethereum.utils.sha3(rlpdata)
            pub = ethereum.utils.ecrecover_to_pub(sighash, vee, self.r, self.s)
            if pub == b'\x00' * 64:
                return None  # Invalid signature (zero privkey cannot sign)
            self._publickey = pub
        return self._publickey

    def publickey(self, value):
        self._publickey = value

def main():
    nonce = 21
    gasprice = 18000000000
    startgas = 100000
    to = '0xa593094cebb06bf34df7311845c2a34996b52324'
    value = 1000000000000
    data = ''
    sender = "0xc6f4f527587ea4a03aa85e0322783592367c1b9a"
    r = "0xab90122dc4e4bbdbb14ef22ad3ae21aecc19a1c90a9c8989c68b26cc782ff303"
    s ="0x36e5f275147049d3afd5d33b735cc9313d2c1aad3ab401aefdce678128e2f1d0"
    v = "0x1c"
    r = int(r, 16)
    s = int(s, 16)
    v = int(v, 16)
    tx = MyTransaction(nonce, gasprice, startgas, to, value, data, v, r, s)
    assert sender[2:] == ethereum.utils.sha3(tx.publickey)[-20:].hex()

if __name__ == '__main__':
| improve this answer | |
  • What if you only have the signature and v, r, s values? – NYC Tech Engineer Dec 11 '18 at 8:46

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.