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

Robb Hoff
  • 103
  • 3
nervous-energy
  • 243
  • 1
  • 2
  • 5

6 Answers6

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)

user2163865
  • 558
  • 1
  • 5
  • 8
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)

}
afu802
  • 376
  • 4
  • 8
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

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

Burt
  • 141
  • 4
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 ( :

kroe
  • 226
  • 3
  • 5
1

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

Talha Ch
  • 11
  • 2