I'm currently trying to get the public key of the user that deploys a contract. Unfortunately I can't make it work.

I am trying to achieve this solely by using ethers.js as I don't want to bloat my React build with other packages. I can easily get the public key from a given signature using the following code taken from this issue.

    let msg = "This is a normal string.";
    let sig = await signer.signMessage(msg);
    const msgHash = ethers.utils.hashMessage(msg);
    const msgHashBytes = ethers.utils.arrayify(msgHash);
    const recoveredPubKey = ethers.utils.recoverPublicKey(msgHashBytes, sig);
    const recoveredAddress = ethers.utils.recoverAddress(msgHashBytes, sig);

When deploying a contract I should be able to do the same thing by simply stitching together the r, s and v values taken from the deployTransaction. The example in the documentation is similar. Here's my code:

    const deployTx = contract.deployTransaction;
    const msgHash = ethers.utils.hashMessage(deployTx.raw);
    const dataBytes = ethers.utils.arrayify(msgHash);
    const expanded = {
      r: deployTx.r,
      s: deployTx.s,
      recoveryParam: 0,
      v: deployTx.v
    const signature = ethers.utils.joinSignature(expanded);

    // now the signature should be correctly formatted
    const recoveredPubKey = ethers.utils.recoverPublicKey(dataBytes, signature);
    const recoveredAddress = ethers.utils.recoverAddress(dataBytes, signature);

This approach does not work. As far as I know the data that was signed during the deployment is in deployTransaction.raw. So this should work. But I tested it with deployTransaction.data as well.

To me it looks like the signature might be wrong. The joinSignature automatically converts the v value to either 27 or 28. According to EIP155 this doesn't make any sense?

Edit: To clarify, I think all I need is the true signing hash. How can I generate it? It's apparently not the hash of the raw deployment transaction.

Edit 2: After some research in the ethereum book I found this:

In Ethereum’s implementation of ECDSA, the "message" being signed is the transaction, or more accurately, the Keccak-256 hash of the RLP-encoded data from the transaction. The signing key is the EOA’s private key.

So I changed my code to the following:

    const deployTx = contract.deployTransaction;
    const msg = ethers.utils.RLP.encode(deployTx.data);
    const msgHash = ethers.utils.keccak256(msg);
    const msgBytes = ethers.utils.arrayify(msgHash);
    const expanded = {
      r: deployTx.r,
      s: deployTx.s,
      recoveryParam: 0,
      v: deployTx.v
    const signature = ethers.utils.joinSignature(expanded);
    const recoveredPubKey = ethers.utils.recoverPublicKey(
    const recoveredAddress = ethers.utils.recoverAddress(msgBytes, signature);

This still does not work unfortunately.


This is solved now. There was a tiny bug in the code and in the ethers library that would not correctly return the chainId and calculate the v value. It's fixed now, see here. Many thanks to ricmoo for helping out.

I wrote a gist that correctly recovers the public key given the transaction.

In short:

const tx = await provider.getTransaction(...)
const expandedSig = {
  r: tx.r,
  s: tx.s,
  v: tx.v
const signature = ethers.utils.joinSignature(expandedSig)
const txData = {
  gasPrice: tx.gasPrice,
  gasLimit: tx.gasLimit,
  value: tx.value,
  nonce: tx.nonce,
  data: tx.data,
  chainId: tx.chainId,
  to: tx.to // you might need to include this if it's a regular tx and not simply a contract deployment
const rsTx = await ethers.utils.resolveProperties(txData)
const raw = ethers.utils.serializeTransaction(rsTx) // returns RLP encoded tx
const msgHash = ethers.utils.keccak256(raw) // as specified by ECDSA
const msgBytes = ethers.utils.arrayify(msgHash) // create binary hash
const recoveredPubKey = ethers.utils.recoverPublicKey(msgBytes, signature)
const recoveredAddress = ethers.utils.recoverAddress(msgBytes, signature)

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.