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(
msgBytes,
signature
);
const recoveredAddress = ethers.utils.recoverAddress(msgBytes, signature);
This still does not work unfortunately.
Best Answer
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 thev
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: