Проблемы с ecrecover()

У меня есть тестовая процедура в Truffle, которая хэширует данные, подписывает их и передает данные подписи в контракт для проверки. Первый тест хэширует одну строку, и ее подпись успешно проверяется контрактом. Второй хеширует номер и адрес. Этот второй тест завершается неудачно, так как ecrecover()не возвращается msg.sender.

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

pragma solidity ^0.4.24;

contract ContractAuth {
    function getPrefixedHash(bytes32 messageHash) internal pure returns (bytes32) {
        bytes memory hashPrefix = "\x19Ethereum Signed Message:\n32";
        return keccak256(abi.encodePacked(hashPrefix, messageHash));
    } 

    // https://ethereum.stackexchange.com/a/15911
    function verifyMessageHash(bytes32 messageHash, uint8 v, bytes32 r, bytes32 s) internal view returns (bool) {
        bytes32 prefixedHash = getPrefixedHash(messageHash);
        return ecrecover(prefixedHash, v, r, s) == msg.sender;
    }
}

Чтобы сохранить вышеуказанные функции внутренними, я создал контракт тестовой оболочки:

pragma solidity ^0.4.24;

import "./ContractAuth .sol";

// TestContractAuth acts as a wrapper contract, allowing internal and
// private functions to be accessed without modifying the scope of the actual functions.
contract TestContractAuth is ContractAuth {

    // Test access to ContractAuth::getPrefixedHash()
    function getPrefixedHashTest(bytes32 messageHash) public pure returns (bytes32) {
        return getPrefixedHash(messageHash);
    }

    // Test access to ContractAuth::verifyMessageHash()
    function verifyMessageHashTest(bytes32 messageHash, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
        return verifyMessageHash(messageHash, v, r, s);
    }

    function verifyMultipleInputs(uint256 inputNumber, address inputAddress, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
        bytes32 messageHash = keccak256(abi.encodePacked(inputNumber, inputAddress));
        return verifyMessageHash(messageHash, v, r, s);
    }
}

Процедура тестов для проверки подписанных данных выглядит следующим образом:

it("verify signed data", async () => {
    // getInstance() deploys the test contract above and returns
    // an instance to it for testing
    const contractAuth = getInstance("TestContractAuth");
    const testAddr = await web3.eth.getCoinbase();
    const msgPrefix = "\x19Ethereum Signed Message:\n32";

    {
        let hashedMessage = web3.utils.soliditySha3("Hello, World!");
        const prefixedHash = web3.utils.soliditySha3(msgPrefix, hashedMessage);

        let rawSig = await web3.eth.sign(prefixedHash, testAddr);
        const sig = parseSignature(rawSig);

        let validSig =
            await contractAuth.methods.verifyMessageHashTest(prefixedHash, sig.v, sig.r, sig.s).call();

        assert.equal(validSig, true, "Expected valid signature returned by verifyMessageHashTest()");
    }
    {   // Test currently fails...
        // TODO: ask question on StackExchange

        let price = 100000000;
        let contractAddr = "0x856F6BD97c2e74F1089a9e7827586a8E3447400b";

        let hashedMessage = web3.utils.soliditySha3(price, contractAddr);
        const prefixedHash = web3.utils.soliditySha3(msgPrefix, hashedMessage);

        const rawSig = await web3.eth.sign(prefixedHash, testAddr);
        const sig = parseSignature(rawSig);

        let validSig =
            await contractAuth.methods.verifyMultipleInputs(price, contractAddr, sig.v, sig.r, sig.s).call();

        assert.equal(validSig, true, "expected valid sig from verifyMultipleInputs()");
    }
});

parseSignature()— это вспомогательная функция, выполняющая нарезку необработанной подписи, возвращаемой web3.eth.sign().

Первый assert.equal()звонок проходит, так как подпись на контракте успешно проверена. Второй терпит неудачу и в настоящее время я не знаю почему.

Я считаю, что есть проблема в формате inputNumberи inputAddressкогда я хеширую его в клиенте. Есть ли способ отформатировать эти данные перед хэшированием?


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

Чтобы проверить хеширование, я добавил в свой тестовый контракт следующую функцию:

function hashMultipleValues(uint256 inputNumber, address inputAddress) public pure returns (bytes32) {
    return keccak256(abi.encodePacked(inputNumber, inputAddress));
}

Который просто возвращает хешированную комбинацию двух входов.

Я добавил в тест следующее, чтобы проверить хеши рядом:

let hashedMessage = web3.utils.soliditySha3(price, contractAddr);
let solHashedMessage =
    await contractAuth.methods.hashMultipleValues(price, contractAddr).call();
console.log("Solidity:\t" + solHashedMessage);
console.log("Web3:\t\t" + hashedMessage);

С указанными выше входными значениями я получаю следующий вывод в консоли во время теста:

Solidity:       0xdce71017994de76c5339d7b083bcbe948e6e75786de1faeb971c2cb369913535
Web3:           0xdce71017994de76c5339d7b083bcbe948e6e75786de1faeb971c2cb369913535

Это доказывает, что между методами хэширования, которые я использую, существует паритет.

Ответы (2)

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

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

const testAddr = await web3.eth.getCoinbase();
let price = 100000000;
let contractAddr = "0x856F6BD97c2e74F1089a9e7827586a8E3447400b";

let hashedMessage = web3.utils.soliditySha3(price, contractAddr);

// Sign the hashedMessage, not a prefixedHash
const rawSig = await web3.eth.sign(hashedMessage, testAddr);
const sig = parseSignature(rawSig);

let validSig =
    await CentraDEX.methods.verifyMultipleInputs(price, contractAddr, sig.v, sig.r, sig.s).call({from: testAddr});

assert.equal(validSig, true, "expected valid sig from verifyMultipleInputs()");

Функция контракта теперь возвращает true, и тест проходит успешно. Может кто-нибудь объяснить, почему я не должен подписывать префиксные данные перед вызовом контракта?

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

gethдобавляет Ethereum Signed Messageвнутренне, когда вызывается знак.

https://github.com/ethereum/go-ethereum/commit/b59c8399fbe42390a3d41e945d03b1f21c1a9b8d

кстати, вы должны отредактировать свой вопрос, кроме как поместить содержание в ответ

Можете ли вы попробовать это, чтобы убедиться, что msg.senderпри вызове функции смарт-контракта ожидается

изменять:

let validSig =
    await contractAuth.methods.verifyMultipleInputs(price, contractAddr, sig.v, sig.r, sig.s).call();

к:

let validSig =
    await contractAuth.methods.verifyMultipleInputs(price, contractAddr, sig.v, sig.r, sig.s).call({from: testAddr});

И проблема может исходить от abi.encodePacked; так что эти два ниже - разница

keccak256(abi.encodePacked(inputNumber, inputAddress)

и

web3.utils.soliditySha3(price, contractAddr);

Это может дать предупреждение, но вы можете попробовать использовать keccak256(inputNumber, inputAddress)для подтверждения проблемы.

Надеюсь, это поможет!

Спасибо за быстрый ответ, но это дополнение не решило проблему для меня. Это дополнение, которое я оставлю в тестах, чтобы убедиться, что я отправляю с этого тестового адреса .
хорошо, обратите внимание, что keccak256и sha3это не тот же алгоритм хеширования
Я думал, что смысл в soliditySha3()том, чтобы имитировать поведение keccak256()?
ох, ладно! Виноват. Так что проблема может исходить отabi.encodePacked