2

I'm using ethers v6 and foundry to test message signing. Though having the same hashed message and the signer private key, I can't find out why the signature is different. The following is my script.

  • forge 0.2.0
  • ethers 6.9.0

solidity

function run() public {
        vm.startBroadcast();

        console.log("sender", vm.addr(senderPk));

        bytes32 message = keccak256(abi.encodePacked(uint256(42), false));

        console.log("message:");
        console.logBytes32(message);

        (uint8 v, bytes32 r, bytes32 s) = vm.sign(senderPk, message);
        bytes memory signature = abi.encodePacked(r, s, v);

        console.log("signature:");
        console.logBytes(signature);

        console.log("v", v);
        console.logBytes32(r);
        console.logBytes32(s);

        vm.stopBroadcast();
    }
== Logs ==
  sender 0xd58B38a32881Dc2a89aB2359b55Ad5Cd8aB621E4
  message:
  0x8c3e5593f2ccfe5fc2e6b9d1350aed0ff196ac3b543cd85b1d721562a7e46696
  signature:
  0xa375c84e232e704537ecad5d2f7b60472a8f7bcb7b8288e58c8467b0cec16f6049bbda12c780bd26fa2929902465fe804beadf9df3e7410658d3891cb1a49a011c
  v 28
  0xa375c84e232e704537ecad5d2f7b60472a8f7bcb7b8288e58c8467b0cec16f60
  0x49bbda12c780bd26fa2929902465fe804beadf9df3e7410658d3891cb1a49a01
  chainId 421614
  =================
  recovered signer 0xd58B38a32881Dc2a89aB2359b55Ad5Cd8aB621E4

typescript

const sender = new Wallet(process.env.DEV_PRIVATE_KEY).connect(provider)

console.log('sender:', sender.address)

const message = ethers.solidityPackedKeccak256(['uint256', 'bool'], [42n, false])

console.log('message: ', message)

const signature = await sender.signMessage(message)
console.log('signature: ', signature)
const sig = Signature.from(signature)

console.log('v:', sig.v)
console.log('r:', sig.r)
console.log('s:', sig.s)
chainId: 421614
sender: 0xd58B38a32881Dc2a89aB2359b55Ad5Cd8aB621E4
message:  0x8c3e5593f2ccfe5fc2e6b9d1350aed0ff196ac3b543cd85b1d721562a7e46696
signature:  0xb97111f21d2900395f74a9b6d3cce613e89c34cf5c37ec627a3f423d8d9af0c20159e62d82bdc9647fed4369e1eb96f881e77c35fb5f60bf81521b7922fba1721c
v: 28
r: 0xb97111f21d2900395f74a9b6d3cce613e89c34cf5c37ec627a3f423d8d9af0c2
s: 0x0159e62d82bdc9647fed4369e1eb96f881e77c35fb5f60bf81521b7922fba172
signer: 0xd58B38a32881Dc2a89aB2359b55Ad5Cd8aB621E4
1
  • Try this while calculating the message hash in foundry bytes32 message = keccak256("\x19\x01",abi.encodePacked(uint256(42), false)); Commented Jan 13 at 13:25

1 Answer 1

3

I discovered that the signMessage function in ethers v6 follows EIP-191 , utilizing the string "\x19Ethereum Signed Message: + length of the message + message". For instance, if the message is "hello world", it gets signed as keccak256("\x19Ethereum Signed Message:\n11hello world")

In foundry, vm.sign(senderPk, message) is employed for signing a raw message. However, in ethers, there is no way to sign raw message due to security considerations. Refer to https://github.com/ethers-io/ethers.js/issues/555#issuecomment-508847309 for more details.

To achieve the same signature in Solidity, the approach is as follows:

bytes memory m = bytes("hello world");

bytes memory message = abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(m.length), m);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(senderPk, keccak256(message));

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.