Verificación de datos firmados

Estoy ocupado tratando de entender la recuperación de una dirección usando ecrecover(). Tengo problemas para recuperar la dirección correcta al proporcionar datos firmados en el contrato.

El contrato que estoy probando se muestra a continuación:

pragma solidity ^0.4.24;

contract VerifyTest {
    // https://ethereum.stackexchange.com/a/15911
    function verifyMessage(bytes32 messageHash, uint8 v, bytes32 r, bytes32 s) private view returns (bool) {
        bytes memory hashPrefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedHash = keccak256(abi.encodePacked(hashPrefix, messageHash));
        return ecrecover(prefixedHash, v, r, s) == msg.sender;
    }

    function testBuyOrder(uint256 orderTotal, address tokenContract, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
        bytes32 messageHash = keccak256(abi.encodePacked(orderTotal, tokenContract));
        return verifyMessage(messageHash, v, r, s);
    }
}

Cuando se compila, genera el siguiente objeto ABI:

[
    {
        "constant": true,
        "inputs": [
            {
                "name": "orderTotal",
                "type": "uint256"
            },
            {
                "name": "tokenContract",
                "type": "address"
            },
            {
                "name": "v",
                "type": "uint8"
            },
            {
                "name": "r",
                "type": "bytes32"
            },
            {
                "name": "s",
                "type": "bytes32"
            }
        ],
        "name": "testBuyOrder",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    }
]

Puedo llamar con éxito a la testBuyOrder()función usando el pequeño fragmento de Javascript a continuación:

// Trying to keep it concise; assume we have a functioning web3 instance...

const signTestAddr = "[ADDRESS_OF_DEPLOYED_CONTRACT]";
const signTestABI = [...]; // ABI quoted above...

testSignMessage = () => {
    console.log(web3.version.api);

    let tokenAddr = "0x128Df2a07Dc41E034bD9a3CEaddDc0341250a6C8";
    let verifyTest = web3.eth.contract(signTestABI).at(signTestAddr);
    let orderTotal = web3.fromDecimal(100000000);
    console.log(orderTotal);

    let testHash = web3.sha3(orderTotal + tokenAddr);

    signHashedMessage(testHash, (error, signature) => {
        if(error === null) {
            signature = signature.substring(2);
            let r = '0x' + signature.substring(0, 64);
            let s = '0x' + signature.substring(64, 128);
            let v = '0x' + signature.slice(128, 130);
            let vDec = parseInt(v, 16);

            // Call the function on our contract here...
            verifyTest.testBuyOrder(100000000, tokenAddr, vDec, r, s, (testErr, result) => {
                    if(testErr === null) {
                        console.log("Success: " + result);
                    } else {
                        console.log("Error: " + testErr);
                    }
            });

        } else {
            console.log("error: " + error);
        }
    })
};

La salida del javascript anterior es:

0.20.3
0x5f5e100
Success: false

El código anterior llama con éxito al contrato, pero no puedo ver por qué la llamada testBuyOrder() siempre devuelve falso.

Edit0: Además, me disculpo por la pregunta aparentemente duplicada, pero he intentado aplicar la sabiduría de las respuestas de otros sin éxito.


Edit1:

La primera sugerencia de Ismael fue incluir funciones de hash en el propio contrato, lo que garantizaría que los datos se concatenen y formateen correctamente antes de ser hash. La función de Ismael tuvo que modificarse ligeramente para poder firmar el hash correcto.

La solución de trabajo para esa función es la siguiente:

function getMessageHash(uint256 orderTotal, address tokenContract) public pure returns (bytes32) {
    bytes memory hashPrefix = "\x19Ethereum Signed Message:\n32";
    bytes32 messageHash = keccak256(abi.encodePacked(orderTotal, tokenContract));
    return keccak256(abi.encodePacked(hashPrefix, messageHash));
}

Edit2:

Habiendo explorado el uso de Web3 v1.0, todavía estoy luchando por recuperar la dirección del remitente en el contrato.

Mi código de cliente actualizado tiene el siguiente aspecto:

signHashedMessage = (messageHash, callback) => {
    web3.eth.getCoinbase().then((coinbase) => {
        web3.eth.sign(prefixHashedData(messageHash), coinbase, callback);
    });
};

prefixHashedData = (messageHash) => {
    let msgPrefix = "\x19Ethereum Signed Message:\n32";
    return web3.utils.soliditySha3(msgPrefix, messageHash);
};

testSignMessage = () => {

    let verifyTest = new web3.eth.Contract(signTestABI, signTestAddr);

    let tokenAddr = "0x128Df2a07Dc41E034bD9a3CEaddDc0341250a6C8";
    let orderTotal = 100000000;

    let testHash = web3.utils.soliditySha3(orderTotal, tokenAddr);

    signHashedMessage(testHash, (error, signature) => {
        if(error === null) {
            signature = signature.substring(2);
            let r = '0x' + signature.substring(0, 64);
            let s = '0x' + signature.substring(64, 128);
            let v = '0x' + signature.slice(128, 130);
            let vDec = parseInt(v, 16);

            // Call the function on our contract here...
            let testBuyOrderRes = verifyTest.methods.testBuyOrder(orderTotal, tokenAddr, vDec, r, s);
            testBuyOrderRes.call((callError, callResult) => {
                if (callError === null) {
                    console.log("Verified: " + callResult);
                } else {
                    console.log("Error: " + callError);
                }
            });
        } else {
            console.log("error: " + error);
        }
    });
};

He agregado signHashedMessage()y prefixHashedMessage()para completar para mostrar mi trabajo.

Para verificar prefixHashedData(), agregué getMessageHash(), descrito anteriormente, a mi contrato. La llamada getMessageHash()devolvió el mismo hash que se devolvió al usar web.utils.soliditySha3()como Ismael describió que lo haría.

El nuevo problema que tengo con el código Web3 v1.0 anterior; al verificar la firma en el contrato, al llamar testBuyOrder(), la función está devolviendo false.

¿Alguien puede ver el error que estoy cometiendo?

Respuestas (1)

El mensaje que envía al contrato no es el mismo que está codificando en javascript orderTotal + tokenAddrno es el mismo que abi.encodePacked(orderTotal, tokenContract).

Utilice la misma función que utiliza en el contrato.

function getMessageHash(uint256 orderTotal, address tokenContract) public pure returns (bytes32) {
    bytes32 messageHash = keccak256(abi.encodePacked(orderTotal, tokenContract));
    returnmessageHash;
}

Y llama eso desde javascript verifyTest.getMessageHash(orderTotal, tokenAddr).

Otra opción es llamar a soliditysha3 desde web3 v1.0 que formateará correctamente los datos.

¡Muchas gracias por su ayuda! He logrado obtener buenos resultados de su primera sugerencia. Su respuesta está casi allí: tuve que anteponer el prefijo del mensaje firmado de Ethereum antes de devolver el hash, pero esa firma da como resultado una buena recuperación de la dirección. Este método parece rápido en una red de prueba local, pero ¿estaría en lo cierto al pensar que habría más latencia cuando se trata de redes principales/rinkeby/kovan, al calcular el hash?
Hacer una llamada a su contrato agrega latencia. La duración del retraso depende de la configuración de su nodo, si usa el proveedor infura+vm será mínimo (una vez que el código de bytes del contrato se almacena en caché, la ejecución será local), pero aún será mayor que llamar soliditySha3().
Gracias por la aclaración. Estudiaré la API web3 v1.0 y buscaré que funcione un concatenador de datos local. Actualizaré mi pregunta para mostrar las soluciones a las que me ha guiado. De nuevo, gracias por tu ayuda.