35

Can I somehow get the public key of an ethereum account knowing just the corresponding ethereum address (e.g. 0x54dbb737eac5007103e729e9ab7ce64a6850a310)?

35
+50

You can if and only if a transaction has been sent from the account. When you send a tx, you sign the transaction and it includes these v r and s values. You parse these from the signed tx and then pass these v r and s values and the hash of the transaction back into a function and it'll spit out the public key. This is actually how you get the from address of a transaction.

You can do so yourself using a tool like ethereumjs-utils:

/**
 * ECDSA public key recovery from signature
 * @param {Buffer} msgHash
 * @param {Number} v
 * @param {Buffer} r
 * @param {Buffer} s
 * @return {Buffer} publicKey
 */
exports.ecrecover = function (msgHash, v, r, s) {
  var signature = Buffer.concat([exports.setLength(r, 32), exports.setLength(s, 32)], 64)
  var recovery = v - 27
  if (recovery !== 0 && recovery !== 1) {
    throw new Error('Invalid signature v value')
  }
  var senderPubKey = secp256k1.recover(msgHash, signature, recovery)
  return secp256k1.publicKeyConvert(senderPubKey, false).slice(1)
}

As another real-world scenario, ethereumjs-tx uses this function to verify the signature:

/**
* Determines if the signature is valid
* @return {Boolean}
*/
verifySignature () {
  const msgHash = this.hash(false)
  // All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid.
  if (this._homestead && new BN(this.s).cmp(N_DIV_2) === 1) {
    return false
  }

  try {
    let v = ethUtil.bufferToInt(this.v)
    if (this._chainId > 0) {
      v -= this._chainId * 2 + 8
    }
    this._senderPubKey = ethUtil.ecrecover(msgHash, v, this.r, this.s)
  } catch (e) {
    return false
  }

  return !!this._senderPubKey
}

For more information on v r and s:

v, r, and s are parameters that can be parsed from the signature. Here's a good example from the ethereumjs utils library:

 var sig = secp256k1.sign(msgHash, privateKey)
  var ret = {}
  ret.r = sig.signature.slice(0, 32)
  ret.s = sig.signature.slice(32, 64)
  ret.v = sig.recovery + 27

Note how you can parse each value from a given signature.

source

  • 1
    Very helpful. I had to look why an "extra" call secp256k1.publicKeyConvert is needed and the reason appears to be to get the decompressed public key, since secp256k1.recover returns a compressed public key. – eth Apr 4 '17 at 6:06
  • What do the values s, r, v stand for? – John Mar 22 '18 at 7:50
  • 2
    What is the msgHash? Is that the hash of the message sent as part of the transaction or the actual hash of the transaction itself? – NYC Tech Engineer Sep 19 '18 at 12:24
  • How are you going to parse v,r and if you don't have the private key of the end user? – Cristian Sep 10 at 20:44
7

I don't think this is possible, since you lose information when going from public key to address:

  1. Start with the public key (64 bytes)
  2. Take the Keccak-256 hash of the public key. You should now have a string that is 32 bytes. (note: SHA3-256 eventually became the standard, but Ethereum uses Keccak)
  3. Take the last 20 bytes of this public key (Keccak-256). Or, in other words, drop the first 12 bytes. These 20 bytes are the address, or 40 characters. When prefixed with 0x it becomes 42 characters long.

How are ethereum addresses generated?

  • 1
    I thought that all transactions from an ethereum address are signed using account's private key and validated on chain using account's public key. If that's the case, then public keys of all accounts should be available for all nodes in a network. Am I wrong? – Edward Ruchevits Mar 31 '17 at 11:50
  • 1
    @EdwardRuchevits The address in the question hasn't signed a transaction so it's not feasible to get the public key. When there's a signature, this may help crypto.stackexchange.com/questions/18105/… and stackoverflow.com/questions/19665491/… Maybe some people can also add great answers here. – eth Mar 31 '17 at 19:00
  • 4
    A nice property of addresses is that since the public key is not known until the account is used, the account is safe against quantum attacks until it is spent from – Tjaden Hess Apr 4 '17 at 3:15
0

Using @tyvano hint, you can do it as follows:

  1. Go to etherscan, and check if there are outgoing transactions: https://etherscan.io/address/0x54dbb737eac5007103e729e9ab7ce64a6850a310
  2. Get hash of one of them e.g. 0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260
  3. use ipython with web3
In [1]: import web3
w3 = web3.Web3(web3.HTTPProvider('https://geth.golem.network:55555'))
tx = w3.eth.getTransaction(0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260)
tx.hash
Out[1]: HexBytes('0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260')

In [2]: from eth_account.internal.signing import extract_chain_id, to_standard_v
s = w3.eth.account._keys.Signature(vrs=(
    to_standard_v(extract_chain_id(tx.v)[1]),
    w3.toInt(tx.r),
    w3.toInt(tx.s)
))

from eth_account.internal.transactions import ALLOWED_TRANSACTION_KEYS
tt = {k:tx[k] for k in ALLOWED_TRANSACTION_KEYS - {'chainId', 'data'}}
tt['data']=tx.input
tt['chainId']=extract_chain_id(tx.v)[0]

from eth_account.internal.transactions import serializable_unsigned_transaction_from_dict
ut = serializable_unsigned_transaction_from_dict(tt)
s.recover_public_key_from_msg_hash(ut.hash())
Out[2]: '0x9678ad0aa2fbd7f212239e21ed1472e84ca558fecf70a54bbf7901d89c306191c52e7f10012960085ecdbbeeb22e63a8e86b58f788990b4db53cdf4e0a55ac1e'

In [3]: s.recover_public_key_from_msg_hash(ut.hash()).to_checksum_address()
Out[3]: '0x54Dbb737EaC5007103E729E9aB7ce64a6850a310'

In [4]: t['from']
Out[4]: '0x54Dbb737EaC5007103E729E9aB7ce64a6850a310'

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.