1

I am creating a contract which will allow an atomic transfer of a token with only the buyer needing to pay gas (instead of both parties). I am doing this by creating an agreement with price, quantity and sellerAddress as elements in a bytes32 array which is then hashed and checked against a valid authority signature. (the hash should be the same as the message hash has the same params are hashed together in both cases).

The problem I am running into is that ecrecover is not verifying as true despite the fact that the signature is valid. I understand that this might be because the javascript code which generated the signature was hashing string types for the message and solidity is using bytes32 which will have slightly different data, hence I have casted the bytes32 vars into strings (see code below).

Here is my solidity code:

//buyer pays all the fees, seller doesn't even need to have ether to do trade
function adminAuthorizedSwap(bytes32[] agreement, uint8 adminV, bytes32 adminR,
    bytes32 adminS) public payable returns(bool)
{
    // 0 = price, 1 = quantity, 2 = sellerAddress
    **//hash is 64 + 0x, thus 66.** 
    bytes memory prefix = '\x19Ethereum Signed Message:\n66';
    bytes32 agreementHash = keccak256(prefix, bytes32ToString(agreement[0]),
        "|", bytes32ToString(agreement[1]), "|", bytes32ToString(agreement[2]));
    //if the agreement hash matches then the trade can take place
    bool agreementIsValid = ecrecover(agreementHash, adminV, adminR, adminS) == admin;
    if(agreementIsValid && msg.value == uint(agreement[0]) *
        uint(agreement[1]))
    {
        //send over ether and tokens
        balances[msg.sender] += uint(agreement[1]);
        balances[address(agreementHash[2])] -= uint(agreement[1]);
        uint commission = (msg.value / 100) * transferFee;
        uint sellerAmt = msg.value - commission;
        address(agreementHash[2]).transfer(sellerAmt);
        admin.transfer(commission);
        numOfTransfers++;
        return true;
    }
    else revert();
}

function bytes32ToString(bytes32 x) public pure returns (string) {
    bytes memory bytesString = new bytes(32);
    uint charCount = 0;
    for (uint j = 0; j < 32; j++) {
        byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
        if (char != 0) {
            bytesString[charCount] = char;
            charCount++;
        }
    }
    bytes memory bytesStringTrimmed = new bytes(charCount);
    for (j = 0; j < charCount; j++) {
        bytesStringTrimmed[j] = bytesString[j];
    }
    return string(bytesStringTrimmed);
}

The signature is creating in Javascript using the following:

createSaleDeed : (price, quantity, address, wallet) =>
{
    try
    {
        //wallet is from: ethers-wallet
        let sig = wallet.signMessage(price, "|", quantity, "|", address).slice(2);
        let signatureObject = {};
        signatureObject.r = "0x" + sig.slice(0, 64);
        signatureObject.s = "0x" + sig.slice(64, 128);
        signatureObject.v = parseInt(sig.slice(128, 130), 16);
        signatureObject.signature = "0x" + sig;
        signatureObject.originalMessage = price + "|" + quantity + "|" + address;
        return signatureObject;
    }
    catch(e)
    {
        console.log(e);
        return e;
    }
}

Where the output of this code is:

{"r":"0x40952c3bbf6fc314d6469c8eb401d8a371980cc03443e6b0dd2c52403ff9db00","s":"0x07a63c8578da73818e4d7dba9ff5e3b48f608d637cfc0dfe33a2dccc663b375c","v":28,"signature":"0x40952c3bbf6fc314d6469c8eb401d8a371980cc03443e6b0dd2c52403ff9db0007a63c8578da73818e4d7dba9ff5e3b48f608d637cfc0dfe33a2dccc663b375c1c","originalMessage":"100000000|1|0xFE6d4bC2De2D0b0E6FE47f08A28Ed52F9d052A02"}

My solidity code is hitting the revert on the if/else statement...

Thank you!

  • Sorry, but I can't find where the variable admin is defined (line 10). – Roman Frolov Dec 21 '17 at 9:40
0

[EDIT: I had missed the output of the JavaScript code. Indeed, the message appears to be 54 bytes long, but the Solidity code has hardcoded a length of 32 (in the prefix). So I believe my answer below is a/the real issue.]


Without knowing what price, quantity, and address are or what values you're passing in agreement, it's hard to know what the issue is here. My best guess is that the length of the message is not 32 bytes. If that's not it, you'll need to share a sample that constructs an actual message (guaranteeing 32-byte length) and sends an actual agreement parameter.

Just for the sake of comparison, this simplified code worked correctly for me:

JavaScript

const ethers = require('ethers');

const wallet = ethers.Wallet.createRandom();
console.log(wallet.address);
const msg = "This message's length: 32 bytes."

const sig = wallet.signMessage(msg).slice(2);
console.log({
  r: '0x' + sig.slice(0, 64),
  s: '0x' + sig.slice(64, 128),
  v: parseInt(sig.slice(128), 16),
});

Solidity

pragma solidity ^0.4.19;

contract Recover {
    function recover(uint8 v, bytes32 r, bytes32 s) public pure returns (address) {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 hash = keccak256(prefix, "This message's length: 32 bytes.");
        return ecrecover(hash, v, r, s);
    }
}

I ran the JavaScript code and then called the Solidity function manually in Remix with the v, r, and s values emitted. The address returned from ecrecover matched the address logged from JavaScript.

  • Hi Smarx, thanks for writing your answer. My understanding was that the message was hashed first then signed and is thus always 32 bytes. I will try your method out. Thanks – James Dec 22 '17 at 6:29
  • This only works if your message is made up entirely of a fixed string, it doesn't work when the datatypes are uint or bytes32. I am guessing my signatures are not validating due to the data type nuances – James Dec 22 '17 at 10:25
  • For me to help more, I'd have to see how you're converting the components of the message into a bytes32 array to pass into the contract. – user19510 Dec 22 '17 at 13:55
  • Please see the updated question which addresses this point – James Dec 23 '17 at 10:22
  • Maybe I missed it, but I still don't see where the values are converted to byte32 and passed to the contract. You're still using a hardcoded length of 32, but the string you're signing isn't 32 bytes long. – user19510 Dec 23 '17 at 13:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.