I am trying to generate a hash with four elements connecting to a contract in solidity. I have not been able to get it to work offline.
The contract is this:
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
/* Signature Verification
How to Sign and Verify
# Signing
1. Create message to sign
2. Hash the message
3. Sign the hash (off chain, keep your private key secret)
# Verify
1. Recreate hash from the original message
2. Recover signer from signature and hash
3. Compare recovered signer to claimed signer
*/
contract Signature {
/* Get message hash to sign
getMessageHash(
beneficiary address,
amount,
nonce,
payment contract
) take the hash
*/
function getMessageHash(
address _to, uint _amount, uint _nonce, address _contract
)
public pure returns (bytes32)
{
return keccak256(abi.encodePacked(_to, _amount, _nonce, _contract));
}
/* 2. Sign message hash
# using browser console
account = "copy paste account of signer here"
hash = the hash created
ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)
# using web3
web3.personal.sign(hash, web3.eth.defaultAccount, console.log)
*/
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash));
}
/* 4. Verify signature
signer = sender address
to = beneficiary address
amount = weis
nonce
payment address
signature = the sign hash created before
*/
function verify(
address _signer,
address _to, uint _amount, uint _nonce, address _contract,
bytes memory signature
)
public pure returns (bool)
{
bytes32 messageHash = getMessageHash(_to, _amount, _nonce, _contract);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == _signer;
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public pure returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig)
public pure returns (bytes32 r, bytes32 s, uint8 v)
{
require(sig.length == 65, "invalid signature length");
assembly {
/*
First 32 bytes stores the length of the signature
add(sig, 32) = pointer of sig + 32
effectively, skips first 32 bytes of signature
mload(p) loads next 32 bytes starting at the memory address p into memory
*/
// 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)))
}
// implicitly return (r, s, v)
}
}
Well, I have created a mini DAPP with hardhat to connect with the contract deployed on the testnet. is the following code:
async function signPayment() {
if (typeof window.ethereum !== 'undefined') {
const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
console.log('cuenta conectada: ', account);
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(signatureAddress, Signature.abi, signer);
try {
const hash = await contract.getMessageHash(recipient, ethers.utils.parseEther(amount), nonce, contractAddress);
//const hash = await transation.wait();
console.log('el primer hash', hash);
Swal.fire({
title: "Datos hasheados antes de ser firmados",
text: `Cantidad: ${amount}, Serie del cheque: ${nonce}, Hash: ${hash}`,
icon: 'success',
confirmButtonText: "Cerrar",
confirmButtonClass: "",
buttonsStyling: true,
showCloseButton: true,
}).then((result) => {
if (result.isConfirmed) {
console.log('hash',hash);
console.log('cantidad ',amount);
console.log('serie del cheque',nonce);
}
});
await window.ethereum.request({method:"personal_sign", params: [account, hash]}).then(console.log)
} catch (err) {
let mensajeError = err.message;
if (err.data) {
if (err.data.message === '') {
mensajeError = 'para otra cosa';
} else {
console.log('error: ',mensajeError);
}
}
Swal.fire({
title: 'Ooops!',
text: `${mensajeError}`,
icon: 'error',
confirmButtonText: 'Cerrar'
})
console.log("Error: ", err)
}
}
}
As I am in tests, I enter the data by default. The only thing that changes is the Nonce for capturing the timestamp now.
const [recipient, setRecipient] = useState('0x1D918aD261752d71FFD63EF9Fb217001C5875005');
const [nonce, setNonce] = useState(Math.floor(new Date().getTime()/1000.0));
const [amount, setAmount] = useState('100000000000000000');
// const [account, setAccount] = useState('');
const [contractAddress, setContractAddress] = useState("0xc972f1e1a446966AA51B1a13A4Acf703C05A9d4f"); // contrato de cobro.
I connect with the same contract in BSC testnet.
It is verified so you can review it.:
Now, the problem is that with the same data and obviously the same order I get different hashes as a result
For example, with the following data:
Contract: 0xc972f1e1a446966AA51B1a13A4Acf703C05A9d4f
Recipient: 0x1D918aD261752d71FFD63EF9Fb217001C5875005
Amount: 100000000000000000
Nonce: 1630169254
Connecting by remix I get the hash:
0x94674213c7a2efc0ff486f699ea61064bd68d75857afa2883cc312e0da5cb556
And connecting by my Dapp i get other hash:
0x0edd5b8bf737a53fb39f823708dab5f427077ab2e6ab84beb691c6b3e28667da
Why that???? Is the same function...