Ethers.js – Troubleshooting Ethers.js Wrong Signature

ethers.jssecp256k1signature

From the same private key in ethers.js and secp256k1 I get the same eth address. But if I sign a message I get a different (wrong) signature in ethers.js. What is wrong with my code?

import { createKeyPair, sign } from '@erebos/secp256k1'
import { pubKeyToAddress } from '@erebos/keccak256'
import { ethers, Wallet } from 'ethers';

async function testsig(message){

// ethers:

var ethersprivateKey = '0x0123456789012345678901234567890123456789012345678901234567890123';

var etherswallet = new ethers.Wallet(ethersprivateKey);
console.log(etherswallet.address)
//0x14791697260E4c9A71f18484C9f997B308e59325

var s_ethers= await etherswallet.signMessage(message)
console.log( ethers.utils.arrayify(s_ethers) ) 
//int8Array(65) [166, 122, 63, 76, 122, 170, 175, 139, 106, 144, 156, 80, 245, 237, 145, 126, 24, 132, 20, 93, 157, 42, 208, 8, 158, 115, 23, 196, 253, 183, 157, 249, 8, 20, 82, 203, 147, 90, 139, 78, 10, 22, 10, 133, 199, 161, 228, 217, 234, 204, 197, 207, 9, 37, 149, 16, 151, 230, 159, 30, 25, 171, 24, 176, 27]




// secp256k1:

var secp256k1privateKey = '0123456789012345678901234567890123456789012345678901234567890123';

let secp256k1keyPair = createKeyPair(secp256k1privateKey);
const secp256k1user = pubKeyToAddress(secp256k1keyPair.getPublic('array'))
console.log(secp256k1user)
//0x14791697260e4c9a71f18484c9f997b308e59325


var s_secp256k1=sign(message, secp256k1keyPair)
console.log( s_secp256k1)
//(65) [93, 182, 197, 217, 33, 175, 143, 115, 43, 243, 207, 2, 160, 182, 26, 117, 218, 181, 239, 132, 22, 133, 182, 155, 86, 25, 151, 186, 76, 167, 48, 228, 23, 78, 253, 193, 86, 243, 164, 96, 149, 68, 209, 43, 122, 6, 186, 68, 147, 74, 110, 71, 175, 197, 222, 132, 208, 223, 168, 84, 46, 81, 141, 73, 1]
}
testsig("abc")

Best Answer

Ethers.js prefixes the signature with \x19Ethereum Signed Message:\n<message length> before signing it, and signs a hash of the message. This is equivalent to the personal_sign JSON-RPC method. To match the behaviour of Ethers.js with the secp256k1 library, you have to:

  1. Get a hash of the message (e) using Keccak256(message).
  2. Hash e, using Keccak256("\x19Ethereum Signed Message:\n32" + e). We can hardcode a length of 32 here, because Keccak-256 hashes are always 32 bytes (= 256 bits) long.
  3. Sign the resulting hash with a private key.

The signature {r, s, v} should be a valid Ethereum signature, where r and s are the first 32 bytes and second 32 bytes respectively, and v is the last byte (also called recovery ID). Note that you may have to add 27 to the v parameter, to get a v of 27 or 28 (as used by Ethereum).

Related Topic