Tengo una rutina de prueba en Truffle que procesa los datos, los firma y pasa los datos de la firma a un contrato para verificar. La primera prueba procesa una sola cadena y su firma se verifica con éxito mediante el contrato. El segundo cifra un número y una dirección. Esta segunda prueba falla ya ecrecover()
que no regresa msg.sender
.
El contrato que estoy probando es:
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;
}
}
Para mantener internas las funciones anteriores, he creado un contrato de envoltura de prueba:
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);
}
}
La rutina de pruebas para verificar los datos firmados es la siguiente:
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()
es una función auxiliar para realizar el corte de la firma sin formato devuelta por web3.eth.sign()
.
La primera assert.equal()
llamada pasa cuando la firma se verifica con éxito en el contrato. El segundo falla y actualmente no sé por qué.
Mi creencia es que hay un problema en el formato inputNumber
y inputAddress
cuando lo hago hash en el cliente. ¿Hay alguna manera de formatear estos datos antes de que se conviertan en hash?
Editar0:
Para verificar el hashing, he agregado la siguiente función a mi contrato de prueba:
function hashMultipleValues(uint256 inputNumber, address inputAddress) public pure returns (bytes32) {
return keccak256(abi.encodePacked(inputNumber, inputAddress));
}
Que simplemente devuelve la combinación hash de las dos entradas.
He agregado lo siguiente a una prueba para verificar los hashes uno al lado del otro:
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);
Con los valores de entrada anteriores, obtengo el siguiente resultado en la consola durante la prueba:
Solidity: 0xdce71017994de76c5339d7b083bcbe948e6e75786de1faeb971c2cb369913535
Web3: 0xdce71017994de76c5339d7b083bcbe948e6e75786de1faeb971c2cb369913535
Esto prueba que hay paridad entre los métodos de hashing que estoy usando.
Bien, parece que me he topado con la respuesta. El problema que tuve fue que estaba firmando el mensaje prefijado, en lugar de solo el hash sin el prefijo.
La prueba modificada, que recurre verifyMultipleInputs()
a mi contrato de prueba, aparece de la siguiente manera:
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()");
La función en el contrato ahora está volviendo verdadero y la prueba pasa. ¿Alguien puede explicar por qué no debería estar firmando datos prefijados antes de llamar al contrato?
Editar0:
geth
antepone el Ethereum Signed Message
internamente cuando se llama el signo.
https://github.com/ethereum/go-ethereum/commit/b59c8399fbe42390a3d41e945d03b1f21c1a9b8d
¿Puede probar esto para asegurarse de que msg.sender
se espera la función de contrato inteligente al llamar?
cambiar:
let validSig =
await contractAuth.methods.verifyMultipleInputs(price, contractAddr, sig.v, sig.r, sig.s).call();
a:
let validSig =
await contractAuth.methods.verifyMultipleInputs(price, contractAddr, sig.v, sig.r, sig.s).call({from: testAddr});
Y el problema puede venir de abi.encodePacked
; entonces estos dos a continuación son diferentes
keccak256(abi.encodePacked(inputNumber, inputAddress)
y
web3.utils.soliditySha3(price, contractAddr);
Puede dar una advertencia, pero puede intentar usarlo keccak256(inputNumber, inputAddress)
para confirmar el problema.
¡Espero que esto ayude!
keccak256
y sha3
no es el mismo algoritmo hashsoliditySha3()
era imitar el comportamiento de keccak256()
?abi.encodePacked
HaĐANG