ecrecover
returns public key, you need to convert it to address with pubToAddress
.
pub = ethJsUtil.ecrecover(msg, v, r, s);
addrBuf = ethJsUtil.pubToAddress(pub);
addr = ethJsUtil.bufferToHex(addrBuf);
Also, you can use fromRpcSig
to get v, r, s
sig = web3.eth.sign(myAccount,msg)
res = ethJsUtil.fromRpcSig(sig)
pub = ethJsUtil.ecrecover(msg, res.v, res.r, res.s);
Please note that web3.eth.sign
adds prefix to the message before signing it (see JSON-RPC spec).
Here is how to add it manually:
const util = require('ethereumjs-util')
const msg = new Buffer('hello');
const sig = web3.eth.sign(web3.eth.accounts[0], '0x' + msg.toString('hex'));
const res = util.fromRpcSig(sig);
const prefix = new Buffer("\x19Ethereum Signed Message:\n");
const prefixedMsg = util.sha3(
Buffer.concat([prefix, new Buffer(String(msg.length)), msg])
);
const pubKey = util.ecrecover(prefixedMsg, res.v, res.r, res.s);
const addrBuf = util.pubToAddress(pubKey);
const addr = util.bufferToHex(addrBuf);
console.log(web3.eth.accounts[0], addr);
On the other hand, testrpc (at least version 3.0.5) does not add such prefix.
Example node.js + testrpc session:
const util = require('ethereumjs-util')
const msg = web3.sha3('hello!');
const sig = web3.eth.sign(web3.eth.accounts[0], msg);
const {v, r, s} = util.fromRpcSig(sig);
const pubKey = util.ecrecover(util.toBuffer(msg), v, r, s);
const addrBuf = util.pubToAddress(pubKey);
const addr = util.bufferToHex(addrBuf);
console.log(web3.eth.accounts[0], addr);
I came across the answer myself, See working code below. I was following this guide which I would not recommend!.
I was also using parity in geth mode to sign the data, which I also would not recommend. Switching to plain old geth RPC gave me more sane results.
That being said, beware of v!. There is a bug between implementations of geth which will return either 00/01 or 1b/1c for v. I solved my problem by using ethereumjs-util's fromRpcSig method, which accounts for the bug. If the following doesn't work try importing web3 and using its sha3 method with ethUtils toBuffer.
Here's a complete snippet of code client side and server side which implement login:
CLIENT
function login() {
let data = web3.sha3('hello world');
web3.eth.sign(web3.eth.defaultAccount, data, (err, result) => {
if (!err) {
axios.post(url + '/login', {
addr: web3.eth.defaultAccount,
sig: result
}).then((res) => {
console.log(res);
});
}
});
}
SERVER
var ethUtils = require('ethereumjs-util');
let addr = req.body.addr;
let sig = req.body.sig;
let signature = ethUtils.fromRpcSig(sig);
let data = 'hello world';
let pubKey = ethUtils.ecrecover(
ethUtils.sha3(data),
signature.v,
signature.r,
signature.s);
let foundAddr = '0x' + ethUtils.pubToAddress(pubKey).toString('hex');
if (foundAddr === addr) {
// Hooray! The user's signature proves they are who they say they are!
} else {
// Not authenticated successfully
}
Best Answer
The following uses Web3.js version 1.0.0-beta which is now the default installed by npm. Note that no node is attached.
The second parameter in the below is the private key:
And the signature checks out, too (the recovered account is the same as the account generated from the private key directly):