Hello,
I try to verify a message signer. Everything seems to be working well (hashing values, sign message, vrs values … are correct) But when I call the ecrecover function it gives back a "wrong" address...
Clean code:
contract SignTest {
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
function test(uint256 amount, uint256 nonce, bytes memory sig, uint tV, bytes32 tR, bytes32 tS, bytes32 tMsg) public view returns(address) {
bytes32 message = prefixed(keccak256(abi.encodePacked(amount, nonce)));
bytes32 messageWithoutPrefix = keccak256(abi.encodePacked(amount, nonce));
address signer = recoverSigner(messageWithoutPrefix, sig, tV, tR,tS);
return signer;
}
// Signature methods
function splitSignature(bytes memory sig)
internal
view
returns (uint8, bytes32, bytes32)
{
require(sig.length == 65, "B");
bytes32 r;
bytes32 s;
uint8 v;
assembly {
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig, uint tV, bytes32 tR, bytes32 tS)
internal
view
returns (address)
{
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(sig);
require(v==tV, "V is not correct");
require(r==tR, "R is not correct");
require(s==tS, "S is not correct");
return ecrecover(message, v, r, s);
}
// Builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 inputHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inputHash));
}
Code with logs:
contract SignTest {
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
function test(uint256 amount, uint256 nonce, bytes memory sig, uint tV, bytes32 tR, bytes32 tS, bytes32 tMsg) public view returns(address) {
console.log("\ncontract_sig: ");
console.logBytes(sig);
console.log("contract_amount: ", amount);
console.log("contract_nonce: ", nonce);
bytes32 message = prefixed(keccak256(abi.encodePacked(amount, nonce)));
bytes32 messageWithoutPrefix = keccak256(abi.encodePacked(amount, nonce));
console.log("\ncontract_message: ");
console.logBytes32(message);
console.log("contract_messageWithoutPrefix: ");
console.logBytes32(messageWithoutPrefix);
require(tMsg == messageWithoutPrefix, "Wrong message");
address signer = recoverSigner(messageWithoutPrefix, sig, tV, tR,tS);
console.log("\ncontract_signer: ", signer);
return signer;
}
// Signature methods
function splitSignature(bytes memory sig)
internal
view
returns (uint8, bytes32, bytes32)
{
require(sig.length == 65, "B");
bytes32 r;
bytes32 s;
uint8 v;
assembly {
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
console.log("contract_V: ", v);
console.log("contract_R: ");
console.logBytes32(r);
console.log("contract_S: ");
console.logBytes32(s);
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig, uint tV, bytes32 tR, bytes32 tS)
internal
view
returns (address)
{
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(sig);
require(v==tV, "V is not correct");
require(r==tR, "R is not correct");
require(s==tS, "S is not correct");
return ecrecover(message, v, r, s);
}
// Builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 inputHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inputHash));
}
The Test:
it("Test Sign", async function () {
accounts = await hre.ethers.getSigners();
deployer = accounts[1]
const SignTest = await ethers.getContractFactory("SignTest");
const iSignTest = await SignTest.connect(deployer).deploy();
await iSignTest.deployed();
//**Start */
const nonce = await deployer.getTransactionCount()
console.log("test_nonce: ", nonce)
const amount = ethers.BigNumber.from("1")
console.log("test_amount: ", amount)
const message = utils.keccak256(
utils.defaultAbiCoder.encode([ "uint256", "uint256" ], [amount, nonce])
)
console.log("test_message: ", message)
var sig = await deployer.signMessage(message);
console.log("test_sig: ", sig)
const breakdown = utils.splitSignature(sig)
console.log("Sig breakdown: ", utils.splitSignature(sig))
var toProve = await iSignTest.test(amount, nonce, sig, breakdown.v, breakdown.r, breakdown.s, message)
console.log("Result-Contract_Signer: ", toProve)
console.log("Signer: ", deployer.address)
});
The output:
SignTest
test_nonce: 237
test_amount: BigNumber { value: "1" }
test_message: 0x3047222747ab93d34559a4c10f40bf67fa1172f7f6a5b026d5516ba8dbc55df6
test_sig: 0x9f741d8eba8bd7b3ff1238f7e68b9dc3657d19d827888565810aa2e823bfc1ab2887d6b5c1e720c1b0af2b06a0ba787b8a60348bd493a37b7337c7127f58d4b11c
Sig breakdown: {
r: '0x9f741d8eba8bd7b3ff1238f7e68b9dc3657d19d827888565810aa2e823bfc1ab',
s: '0x2887d6b5c1e720c1b0af2b06a0ba787b8a60348bd493a37b7337c7127f58d4b1',
_vs: '0xa887d6b5c1e720c1b0af2b06a0ba787b8a60348bd493a37b7337c7127f58d4b1',
recoveryParam: 1,
v: 28,
yParityAndS: '0xa887d6b5c1e720c1b0af2b06a0ba787b8a60348bd493a37b7337c7127f58d4b1',
compact: '0x9f741d8eba8bd7b3ff1238f7e68b9dc3657d19d827888565810aa2e823bfc1aba887d6b5c1e720c1b0af2b06a0ba787 b8a60348bd493a37b7337c7127f58d4b1'
}
contract_sig:
0x9f741d8eba8bd7b3ff1238f7e68b9dc3657d19d827888565810aa2e823bfc1ab2887d6b5c1e720c1b0af2b06a0ba787b8a60348bd493a37b7337c7127f58d4b11c
contract_amount: 1
contract_nonce: 237
contract_message:
0x2d1a26d11c988a125ec07cbffcafd8fc0c7c4ab3ed9e1479dd26c48e4e5af9f4
contract_messageWithoutPrefix:
0x3047222747ab93d34559a4c10f40bf67fa1172f7f6a5b026d5516ba8dbc55df6
contract_V: 28
contract_R:
0x9f741d8eba8bd7b3ff1238f7e68b9dc3657d19d827888565810aa2e823bfc1ab
contract_S:
0x2887d6b5c1e720c1b0af2b06a0ba787b8a60348bd493a37b7337c7127f58d4b1
contract_signer: 0xfac47f5b7eb4db4bf57e46e341fff26150d7766c
Result-Contract_Signer: 0xFAc47f5b7eB4db4Bf57e46e341fff26150D7766c
Signer: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
- What could be the reason?
- Also I am wondering that, everywhere is said that you need to prefix the message (like here: https://docs.ethers.io/v4/cookbook-signing.html) But as you can see I give out the prefixed and not prefixed message and it looks like the message without the prefix is the same as ethers creates... that means ethers do not prefixing? (why does the doc say something else or what do I over see?)
(I tried both prefixed message and not prefixed)