The following sample is returning a different address from the signed message
const { ethers } = require('ethers')
;(async function() {
// yarn ganache-cli -p 8545 -d
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545')
const signer = provider.getSigner()
// 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
const ethAddress = await signer.getAddress()
const hash = await ethers.utils.keccak256(ethAddress)
const sig = await signer.signMessage(ethers.utils.arrayify(hash))
// 0x3dfB86Ee0e620A3E5B88195792e1B1f8B0A08cff
const recoveredAddress = ethers.utils.recoverAddress(hash, sig)
// Throwing here
if (recoveredAddress != ethAddress) {
throw Error(
`Address recovered do not match, original ${ethAddress} versus computed ${recoveredAddress}`
)
}
})()
Expecting to return 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
Node: Using ganache-cli to test yarn ganache-cli -p 8545 -d
The recoverAddress works on digests, and is for much lower-level functionality. You probable want verifyMessage, which works on messages:
// Make sure you arrayify the message if you want the bytes to be used as the message
const recoveredAddress = ethers.utils.verifyMessage(ethers.utils.arrayify(hash), sig)
Let me know if that still doesn't work.
Hey @ricmoo thanks for the answer, actually I'm looking for return the public key which, when returning the public key, the generated address isn't the one I expect.
const { ethers } = require('ethers')
;(async function() {
// yarn ganache-cli -p 8545 -d
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545')
const signer = provider.getSigner()
// 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
const ethAddress = await signer.getAddress()
const hash = await ethers.utils.keccak256(ethAddress)
const sig = await signer.signMessage(ethers.utils.arrayify(hash))
const pk = ethers.utils.recoverPublicKey(hash, sig)
// 0x3dfB86Ee0e620A3E5B88195792e1B1f8B0A08cff
const recoveredAddress = ethers.utils.computeAddress(ethers.utils.arrayify(pk))
// Throwing here
if (recoveredAddress != ethAddress) {
throw Error(
`Address recovered do not match, original ${ethAddress} versus computed ${recoveredAddress}`
)
}
})()
Also verifyMessage is working and returning the address correctly, but I really need the right publicKey
And the pk generated doesn't generate the publickKey that I expected, because the address computed isn't the right one
In that case you can use the lower level, but you must use hashMessage first:
// First the message must be binary
let hashBytes = arrayify(hash);
// Then you must compute the prefixed-message hash
let messgeHash = ethers.util.hashMessage(hashBytes);
// Then you must make this binary
let messageHashBytes = ethers.utils.arrayify(messageHash);
// Now you have the digest,
let publicKey = ethers.utils.recoverPublicKey(messageHashBytes);
You can of course make this a one liner:
let pubKey = recoverPublicKey(arrayify(hashMessage(arrayify(hash))), signature);
let address = computeAddress(pubKey)
That should get you where you want. :)
Sweet ! now is working as expected thank you very much :)
const pk = ethers.utils.recoverPublicKey(
ethers.utils.arrayify(ethers.utils.hashMessage(ethers.utils.arrayify(hash))),
sig
)
In case someone finds it useful, here is our example:
// Issue a signature
const wallet = Wallet.fromMnemonic(MNEMONIC, PATH)
const message = "Hello dapp"
const signature = await wallet.signMessage(message)
const expectedAddress = await wallet.getAddress()
const expectedPublicKey = wallet.signingKey.publicKey
console.log("ISSUING SIGNATURE")
console.log("ADDR: ", expectedAddress)
console.log("PUB K: ", expectedPublicKey)
console.log("SIG ", signature)
console.log()
// Approach 1
const actualAddress = utils.verifyMessage(message, signature)
console.log("APPROACH 1")
console.log("EXPECTED ADDR: ", expectedAddress)
console.log("ACTUAL ADDR: ", actualAddress)
console.log()
// Approach 2
const msgHash = utils.hashMessage(message);
const msgHashBytes = utils.arrayify(msgHash);
// Now you have the digest,
const recoveredPubKey = utils.recoverPublicKey(msgHashBytes, signature);
const recoveredAddress = utils.recoverAddress(msgHashBytes, signature);
const matches = expectedPublicKey === recoveredPubKey
console.log("APPROACH 2")
console.log("EXPECTED ADDR: ", expectedAddress)
console.log("RECOVERED ADDR: ", recoveredAddress)
console.log("EXPECTED PUB K: ", expectedPublicKey)
console.log("RECOVERED PUB K: ", recoveredPubKey)
console.log("SIGNATURE VALID: ", matches)
console.log()
I think that the documentation is missing an example about this use-case.
@ledfuse Thanks, this helped me a lot.
Found a bug. When signing UTF-string, recoverPublicKey and recoverAddress derive invalid pkey and addr.
Should I create another issue for this?
const provider = new ethers.providers.Web3Provider(window.web3.currentProvider);
const signer = provider.getSigner();
const message = '邪斜胁谐写械褢';
const signature = await signer.signMessage(message);
const digest = arrayify(hashMessage(message));
const publicKey = await recoverPublicKey(digest, signature);
const address = await recoverAddress(digest, signature);
const message2 = 'hijklmnop';
const signature2 = await signer.signMessage(message2);
const digest2 = arrayify(hashMessage(message2));
const publicKey2 = await recoverPublicKey(digest2, signature2);
const address2 = await recoverAddress(digest2, signature2);
console.log(`Addresses: 1 - ${address}; 2 - ${address2}`);
console.log(`Pubkeys: 1 - ${publicKey}; 2 - ${publicKey2}`);
@seniorjoinu Sorry, I didn't notice this message.
I just tried in both v4 and v5, and in both versions the code you provided works fine (they match). Can you give an example with an ethers.Wallet instance and private key and include the output you get so I can reproduce it deterministically?
(also, feel free to open a new issue ;))
In case someone finds it useful, here is our example:
// Issue a signature const wallet = Wallet.fromMnemonic(MNEMONIC, PATH) const message = "Hello dapp" const signature = await wallet.signMessage(message) const expectedAddress = await wallet.getAddress() const expectedPublicKey = wallet.signingKey.publicKey console.log("ISSUING SIGNATURE") console.log("ADDR: ", expectedAddress) console.log("PUB K: ", expectedPublicKey) console.log("SIG ", signature) console.log() // Approach 1 const actualAddress = utils.verifyMessage(message, signature) console.log("APPROACH 1") console.log("EXPECTED ADDR: ", expectedAddress) console.log("ACTUAL ADDR: ", actualAddress) console.log() // Approach 2 const msgHash = utils.hashMessage(message); const msgHashBytes = utils.arrayify(msgHash); // Now you have the digest, const recoveredPubKey = utils.recoverPublicKey(msgHashBytes, signature); const recoveredAddress = utils.recoverAddress(msgHashBytes, signature); const matches = expectedPublicKey === recoveredPubKey console.log("APPROACH 2") console.log("EXPECTED ADDR: ", expectedAddress) console.log("RECOVERED ADDR: ", recoveredAddress) console.log("EXPECTED PUB K: ", expectedPublicKey) console.log("RECOVERED PUB K: ", recoveredPubKey) console.log("SIGNATURE VALID: ", matches) console.log()I think that the documentation is missing an example about this use-case.
Please add this to documentation.
Thank you @MoMannn. You ended a very miserable couple of hours for me
Most helpful comment
In case someone finds it useful, here is our example:
I think that the documentation is missing an example about this use-case.