EDIT 1
Got the idea here, I am now computing the message hash inside the smart contract and only passing in signature and plain text arguments from backend.
Backend code:
messageHashLocal = w3.solidityKeccak(['uint256', 'string', 'uint256'], [amount, message, nonce])
encoded_message = encode_defunct(messageHashLocal)
signed_message = w3.eth.account.sign_message(encoded_message, private_key=PRIVATE_KEY)
return {
'amount': amount,
'message': message,
'nonce': nonce,
'signature': to_32byte_hex(signed_message.signature)
}
Solidity code:
function getMessageHash(
uint256 _amount,
string memory _message,
uint256 _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_amount, _message, _nonce));
}
function getEthSignedMessageHash(bytes32 _messageHash)
public
pure
returns (bytes32)
{
return
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message: \n32",
_messageHash
)
);
}
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 verify(
address _signer,
uint256 _amount,
string memory _message,
uint256 _nonce,
bytes memory _signature
) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_amount, _message, _nonce);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, _signature) == _signer;
}
function splitSignature(bytes memory _sig)
public
pure
returns (
bytes32 r,
bytes32 s,
uint8 v
)
{
require(_sig.length == 65, "Invalid signature length");
assembly {
r := mload(add(_sig, 32))
s := mload(add(_sig, 64))
v := byte(0, mload(add(_sig, 96)))
}
}
for some reason ecrecover in Solidity contract returns and arbitrary address. I have double checked message hash I get from w3.solidityKeccak function in python is exactly the same that I get getMessageHash function that are
b'3|\x8a\xcc\x87l@\x85f\x19\x8d\x90>\xc3\x11k\x9b=\xe9if4\xc6\xd4\x90)ng:/\xceI'
b'3|\x8a\xcc\x87l@\x85f\x19\x8d\x90>\xc3\x11k\x9b=\xe9if4\xc6\xd4\x90)ng:/\xceI'
Any ideas what is causing the ecrecover to return an arbitrary address and not the actual signer?
ORIGINAL QUESION
I am trying to implement a system which lets users claim some tokens as rewards which they earn by performing some action items on our app. App supports both web3 and conventional users (wallet not connected). We save amount of claimable reward tokens in our database.
I have this function in smart contract that is responsible to transfer claimable tokens to users.
function processTransfer(
uint256 amount,
bytes32 msgh,
uint8 v,
bytes32 r,
bytes32 s
) external {
address signerPublicKey = ecrecover(msgh, v, r, s);
require(signerPublicKey == owner, "Signature verification failed!");
require(
token.allowance(owner, address(this)) >= amount,
"Insufficient token allowance allowance!"
);
currentDistributionAllowance -= amount;
token.transferFrom(owner, msg.sender, amount);
emit TransferProcessed(msg.sender, amount);
}
Issue The issue is with the amount parameter, I fetch it from database and send it to the smart contract along with a signed message which verifies that the request was initiated from our app. However, amount can be changed by simply intercepting the http request and changing this variable.
I am not really sure what is the best possible way to make this secure. I tried signing this amount as message from backend but I was unable to get this msg back on the smart contract, ecrecover only gets me back the public address.