70

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

eth
  • 85,679
  • 53
  • 285
  • 406
Edward Ruchevits
  • 1,285
  • 2
  • 15
  • 24
  • This is the latest(Apr 2023) method that worked for me https://ethereum.stackexchange.com/questions/112641/i-want-to-get-public-key-from-transaction/148932#148932. – Saikat Karmakar Apr 15 '23 at 10:59

5 Answers5

70

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

tworec
  • 173
  • 6
tayvano
  • 15,961
  • 4
  • 45
  • 74
17

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
daisy
  • 284
  • 1
  • 8
Mikhail Vladimirov
  • 7,313
  • 1
  • 23
  • 38
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?

Olmo Ziarko
  • 320
  • 1
  • 6
  • 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? – 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 http://crypto.stackexchange.com/questions/18105/how-does-recovering-the-public-key-from-an-ecdsa-signature-work and http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k Maybe some people can also add great answers here. – eth Mar 31 '17 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 – Tjaden Hess Apr 04 '17 at 03:15
  • @TjadenHess Are quantum attacks a thing in the present day? – atw May 27 '22 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'

tworec
  • 173
  • 6
  • 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 Jan 10 '20 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));

K SS
  • 1