0

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.

3
  • Amount should be part of the signed message. If they change the signature won't be valid anymore. Also you have to ensure a signature couldn't be used multiple times, perhaps with a nonce, or a blacklist in the contract.
    – Ismael
    Oct 19, 2022 at 22:22
  • Hey Ismael thanks for your comment, I have thought about this approach. However, I couldn't retrieve amount from the signature. Any ideas how can I do that?
    – Tajammul
    Oct 20, 2022 at 8:08
  • A signature is fixed size, so you can't add "more data" to it. You include the data in the message, the derive msgH from the function parameters.
    – Ismael
    Oct 21, 2022 at 0:59

1 Answer 1

1

You have at least two options:

A) Make the function callable only by your backend wallet - probably by checking the sender address. OpenZeppelin has Ownable library for that.

B) Use signatures. ecrecover gives you only an address back, that's true, but you have to check that the address is the correct one. How signatures usually work:

  1. Someone creates a message which has all the needed information. In your case at least the amount

  2. An authorized party (probably your backend) signs the message with their private key.

  3. You pass the signature and the input for the original message to the function

  4. The function knows what address is the authorized party and uses ecrecover to make sure that the signer of the message is the authorized party. If any of the input (in your case, amount) is not correct the ecrecover will either return 0 or an arbitrary address.

Read for example here for more details about ecrecover.

2
  • Hey Lauri thanks for the comment. I can't go with approach A. I tried it earlier, the issue was whenever there was a transaction in pending. I was not able to make a new one. For approach B, how to I retrieve amount back from the signature in smart contract? User can always not change the signature and only change the incoming amount variable and steal funds right? Please let me know if I missed something.
    – Tajammul
    Oct 20, 2022 at 8:13
  • User supplies the plain-text inputs as well as the signature. Then you basically compare that they match. If you get wrong address back from ecrecover it means the user's plain-text input doesn't match the signature data Oct 20, 2022 at 9:14

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.