У меня есть тестовая процедура в 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
Это доказывает, что между методами хэширования, которые я использую, существует паритет.
Итак, я, кажется, наткнулся на ответ. Проблема, с которой я столкнулся, заключалась в том, что я подписывал сообщение с префиксом, а не просто хэш без префикса.
Модифицированный тест, который вызывает 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
Ха ДЖАНГ