Проверка подписанных данных

Я занят, пытаясь разобраться с восстановлением адреса с помощью ecrecover(). У меня возникли проблемы с восстановлением правильного адреса при предоставлении подписанных данных в контракт.

Контракт, который я тестирую, показан ниже:

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);
    }
}

При компиляции генерируется следующий объект 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"
    }
]

Я могу успешно вызвать testBuyOrder()функцию, используя небольшой фрагмент Javascript ниже:

// 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);
        }
    })
};

Вывод из javascript выше:

0.20.3
0x5f5e100
Success: false

Приведенный выше код успешно вызывает контракт, но я не понимаю, почему вызов testBuyOrder() всегда возвращает false.

Edit0: Кроме того, я прошу прощения за, казалось бы, дублированный вопрос, но я безрезультатно пытался применить мудрость ответов других.


Редактировать1:

Первым предложением Исмаэля было включить функции хеширования в сам контракт, которые гарантировали бы правильное объединение и форматирование данных перед хешированием. Функцию Ismael пришлось немного изменить, чтобы получить правильный хэш для подписи.

Рабочее решение этой функции приведено ниже:

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));
}

Редактировать2:

Изучив использование Web3 v1.0, я все еще пытаюсь восстановить адрес отправителя в контракте.

Мой обновленный клиентский код выглядит следующим образом:

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);
        }
    });
};

Я добавил signHashedMessage()и prefixHashedMessage()для полноты, чтобы показать мою работу.

Для проверки prefixHashedData()я добавил getMessageHash()описанное выше в свой контракт. Вызов getMessageHash()вернул тот же хэш, который был возвращен при использовании web.utils.soliditySha3(), как описал Исмаэль.

Новая проблема, с которой я столкнулся с приведенным выше кодом Web3 v1.0; при проверке подписи на контракте при вызове testBuyOrder()функция возвращает false.

Может ли кто-нибудь увидеть ошибку, которую я делаю?

Ответы (1)

Сообщение, которое вы отправляете в контракт, отличается от того, что вы кодируете в javascript orderTotal + tokenAddr, не то же самое, что и abi.encodePacked(orderTotal, tokenContract).

Либо используйте ту же функцию, которую вы используете в контракте.

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

И вызовите это из javascript verifyTest.getMessageHash(orderTotal, tokenAddr).

Другой вариант — вызвать soliditysha3 из web3 v1.0, который правильно отформатирует данные.

Большое спасибо за вашу помощь! Мне удалось получить хорошие результаты от вашего первого предложения. Ваш ответ почти готов — мне пришлось добавить префикс Ethereum Signed Message перед возвратом хэша, но эта подпись приводит к хорошему восстановлению адреса. Этот метод кажется быстрым в локальной тестовой сети, но правильно ли я думаю, что будет больше задержек при работе с сетями main/rinkeby/kovan при вычислении хэша?
Звонок по вашему контракту увеличивает задержку. Насколько долгой будет задержка, зависит от конфигурации вашего узла, если вы используете провайдер infura+vm, она будет минимальной (после кэширования байт-кода контракта выполнение будет локальным), но она все равно будет выше, чем при вызове soliditySha3().
Благодарю за разъяснение. Я изучу API web3 v1.0 и постараюсь заставить работать локальный конкат данных. Я обновлю свой вопрос, чтобы показать решения, к которым вы меня привели. В очередной раз благодарим за помощь.