14

I'm getting to grips with web3.js and have noticed an unusual issue with signing data.

When I sign a string with the web3.eth.sign() method, I am getting a different signature than if I use web3.eth.account.sign(). Note this latter method takes the private key as an argument.

I'm using the the latest web3.js beta and ganache. The test public address & private key I'm using in the examples below are:

address: 0x627306090abaB3A6e1400e9345bC60c78a8BEf57
pkey: 0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
//note 0x prefix

Code:

const signTest = async function(){

    // Using eth.sign()

    let accounts = await web3.eth.getAccounts();
    let msg = "Some data";

    let prefix = "\x19Ethereum Signed Message:\n" + msg.length;
    let msgHash1 = web3.utils.sha3(prefix+msg);

    let sig1 = await web3.eth.sign(msgHash1, accounts[0]);
    

    // Using eth.accounts.sign() - returns an object

    let privateKey = "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3";

    let sigObj = await web3.eth.accounts.sign(msg, privateKey);
    let msgHash2 = sigObj.messageHash;
    
    let sig2 = sigObj.signature;

}

sig 1 and sig 2 do not match - the last 4 chars are different.

sig1: 0xb3bd48482d2c13a1d1f3bba070fb9de9375ffc61347504fb4e3aed903fbc073e65588de1207ed716e0e439bfa527d44ea13a28ba08f79ac025a0e42b683967ec00
sig2: 0xb3bd48482d2c13a1d1f3bba070fb9de9375ffc61347504fb4e3aed903fbc073e65588de1207ed716e0e439bfa527d44ea13a28ba08f79ac025a0e42b683967ec1b

However, note both signatures recover the correct account:

    let whoSigned1 = await web3.eth.accounts.recover(msgHash1, sig1);
    let whoSigned2 = await web3.eth.accounts.recover(sigObj);

Is this behaviour normal or could it lead to problems?

Thanks

6 Answers 6

10

Likely web3.eth.accounts sign method is encoding the chainId as the parameter V while web3.eth.sign (an older implementation) is not. This was an update wrt to EIP 155. More info: (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)

6

As of today(2019/10, web3 1.2.1), the sample code should be updated as below.

Note

  1. private key should be prefixed with '0x'

  2. web3.eth.accounts.recover API is changed

Code

    const signTest = async function(){

    // Using eth.sign()

    let accounts = await web3.eth.getAccounts();
    let msg = "Some data"

    let prefix = "\x19Ethereum Signed Message:\n" + msg.length
    let msgHash1 = web3.utils.sha3(prefix+msg)

    let sig1 = await web3.eth.sign(msg, accounts[0]);


    // Using eth.accounts.sign() - returns an object

    let privateKey = "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3"

    let sigObj = await web3.eth.accounts.sign(msg, privateKey)
    let msgHash2 = sigObj.messageHash;

    let sig2 = sigObj.signature;


    let whoSigned1 = await web3.eth.accounts.recover(msg, sig1)
    let whoSigned2 = await web3.eth.accounts.recover(sigObj)

}
4

I discovered that web3.eth.accounts.sign(msg, privateKey) does automatic prefixing of the msg. The actual signed message is: "\x19Ethereum Signed Message:\n" + message.length + message. So if in your solidity contract (when recovering and validating), you are expecting a prefix like that, you should use web3.eth.accounts.sign. Checkout the documentation for more info the solidity documentation

2
  • This comment saved me . I was going to have worst nightmare otherwise.
    – NinjaMAN
    Commented Jul 19, 2021 at 11:26
  • ummm how do I get users' "privateKey" when they just login through metamask...
    – Jim
    Commented Oct 29, 2021 at 2:19
1

According to this page: https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethsign the signature of web3.eth.sign is:

web3.eth.sign(address, dataToSign, [, callback])

But on your example you use, web3.eth.sign(data, address), that's likely to be one of the reasons ( :

1

you need to add prefix "0x" with your private key

2
  • even after adding "0x" before the private key, signatures are different. Commented Apr 13, 2019 at 13:43
  • how do I get "privateKey"? It's so private
    – Jim
    Commented Oct 29, 2021 at 2:20
1

web3.eth.sign and web3.eth.accounts.sign generate the same signature in web3.js v1.3.4 .

Correct me if I am wrong, It seems web3.eth.sign calls web3.eth.accounts.sign under the hood, here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.