Can I somehow get the public key of an ethereum account knowing just the corresponding ethereum address (e.g. 0x54dbb737eac5007103e729e9ab7ce64a6850a310
)?
-
This is the latest(Apr 2023) method that worked for me ethereum.stackexchange.com/questions/112641/….– Saikat KarmakarCommented Apr 15, 2023 at 10:59
5 Answers
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.
-
2Very 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, sincesecp256k1.recover
returns a compressed public key.– eth ♦Commented Apr 4, 2017 at 6:06 -
-
5What 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?– IulianCommented Sep 10, 2019 at 20:44
It is now possible to recover public key from Ethereum transaction without any coding:
- Open the transaction in Etherscan.io
- Click on vertical ellipsis in the top-right corner
- Click on "Get Raw Tx Hex" in popup menu
- You will see raw transaction in hex, copy it
- Open “Recover address” tool from ABDK Toolkit
- Select "Transaction" radio button
- Paste raw transaction into "Message" field
- See the public key in the text bow below
-
This works extremely well. However, I couldn't find the source code for this anywhere, even though they say this is open sourced.– DUO LabsCommented Dec 30, 2020 at 21:52
-
-
-
@MikhailVladimirov doesn’t work anymore… computed addresses doesn’t match any of the transactions. Commented Nov 16, 2023 at 10:28
I don't think this is possible, since you lose information when going from public key to address:
- Start with the public key (64 bytes)
- 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)
- 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.
-
2I 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
-
6A 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
-
Using @tayvano hint, you can do it as follows:
- Go to etherscan, and check if there are outgoing transactions: https://etherscan.io/address/0x54dbb737eac5007103e729e9ab7ce64a6850a310
- Get hash of one of them e.g.
0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260
- 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'
-
2This 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
– ChrisCommented Jan 10, 2020 at 20:58
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));