71

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

1

5 Answers 5

71
+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

4
  • 2
    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
    Commented Apr 4, 2017 at 6:06
  • What do the values s, r, v stand for?
    – John
    Commented Mar 22, 2018 at 7:50
  • 5
    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? Commented Sep 19, 2018 at 12:24
  • How are you going to parse v,r and if you don't have the private key of the end user?
    – Iulian
    Commented Sep 10, 2019 at 20:44
18

It is now possible to recover public key from Ethereum transaction without any coding:

  1. Open the transaction in Etherscan.io
  2. Click on vertical ellipsis in the top-right corner
  3. Click on "Get Raw Tx Hex" in popup menu
  4. You will see raw transaction in hex, copy it
  5. Open “Recover address” tool from ABDK Toolkit
  6. Select "Transaction" radio button
  7. Paste raw transaction into "Message" field
  8. See the public key in the text bow below
4
  • This works extremely well. However, I couldn't find the source code for this anywhere, even though they say this is open sourced.
    – DUO Labs
    Commented Dec 30, 2020 at 21:52
  • The best I could do is make a WARC file for offline use here.
    – DUO Labs
    Commented Jan 1, 2021 at 20:49
  • How does this help just by knowing the address?
    – TylerH
    Commented Aug 18, 2022 at 21:45
  • @MikhailVladimirov doesn’t work anymore… computed addresses doesn’t match any of the transactions. Commented Nov 16, 2023 at 10:28
14

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?

4
  • 2
    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? Commented Mar 31, 2017 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
    Commented Mar 31, 2017 at 19:00
  • 6
    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 Commented Apr 4, 2017 at 3:15
  • @TjadenHess Are quantum attacks a thing in the present day?
    – atw
    Commented May 27, 2022 at 14:37
5

Using @tayvano 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._utils.signing import extract_chain_id, to_standard_v, serializable_unsigned_transaction_from_dict 

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._utils.legacy_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'
1
  • 2
    This doesn't work for contract deployment transactions.And for the latest version you have to adjust the code to from eth_account._utils.signing import extract_chain_id, to_standard_v, serializable_unsigned_transaction_from_dict from eth_account._utils.transactions import ALLOWED_TRANSACTION_KEYS
    – Chris
    Commented Jan 10, 2020 at 20:58
0

Solution in Java. Perhaps it can be done simpler, but it works.

Utility classes come from web3j.crypto.

    BigInteger v = new BigInteger("26", 16);
    BigInteger r = new BigInteger("5fd883bb01a10915ebc06621b925bd6d624cb6768976b73c0d468b31f657d15b", 16);
    BigInteger s = new BigInteger("121d855c539a23aadf6f06ac21165db1ad5efd261842e82a719c9863ca4ac04c", 16);
    BigInteger chainId = new BigInteger("1", 16);
    v = v.subtract(chainId.multiply(BigInteger.valueOf(2)).add(BigInteger.valueOf(8)));
    Sign.SignatureData signatureData = new Sign.SignatureData(v.toByteArray(), r.toByteArray(), s.toByteArray());
    byte[] raw = DatatypeConverter.parseHexBinary("f86b0b85250523760082520894eafaf9bb8f35235d0df61275e86fd65d9ef2c3f9870aaa0065c66b8b8026a05fd883bb01a10915ebc06621b925bd6d624cb6768976b73c0d468b31f657d15ba0121d855c539a23aadf6f06ac21165db1ad5efd261842e82a719c9863ca4ac04c");

    RawTransaction decoded = TransactionDecoder.decode(DatatypeConverter.printHexBinary(raw));
    byte[] encoded = TransactionEncoder.encode(decoded, chainId.longValue());
    byte[] rawTxHash = Hash.sha3(encoded);

    System.out.println("Raw tx hash:                    " + DatatypeConverter.printHexBinary(rawTxHash));
    System.out.println("Pub key from raw tx hash :      " + signedMessageHashToKey(rawTxHash, signatureData).toString(16));

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.