Probleme mit ecrecover()

Ich habe eine Testroutine in Truffle, die Daten hasht, signiert und die Signaturdaten zur Überprüfung an einen Vertrag übergibt. Der erste Test hasht eine einzelne Zeichenfolge und ihre Signatur wird erfolgreich durch den Vertrag verifiziert. Der zweite hasht eine Nummer und eine Adresse. Dieser zweite Test schlägt fehl, da ecrecover()nicht zurückgegeben wird msg.sender.

Der Vertrag, den ich teste, lautet:

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

Um die oben genannten Funktionen intern zu halten, habe ich einen Test-Wrapper-Vertrag erstellt:

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

Die Testroutine zur Überprüfung signierter Daten sieht wie folgt aus:

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()ist eine Hilfsfunktion, um das Slicing der von zurückgegebenen Rohsignatur durchzuführen web3.eth.sign().

Der erste assert.equal()Anruf wird bestanden, da die Unterschrift auf dem Vertrag erfolgreich verifiziert wurde. Der zweite schlägt fehl und derzeit weiß ich nicht warum.

Ich glaube, dass es ein Problem im Format gibt inputNumberund inputAddresswann ich es im Client hash. Gibt es eine Möglichkeit, diese Daten zu formatieren, bevor sie gehasht werden?


Bearbeiten0:

Um das Hashing zu verifizieren, habe ich meinem Testvertrag folgende Funktion hinzugefügt:

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

Was einfach die gehashte Kombination der beiden Eingaben zurückgibt.

Ich habe einem Test Folgendes hinzugefügt, um Hashes nebeneinander zu überprüfen:

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

Mit den oben angegebenen Eingabewerten erhalte ich während des Tests folgende Ausgabe in der Konsole:

Solidity:       0xdce71017994de76c5339d7b083bcbe948e6e75786de1faeb971c2cb369913535
Web3:           0xdce71017994de76c5339d7b083bcbe948e6e75786de1faeb971c2cb369913535

Dies beweist, dass es Parität zwischen den von mir verwendeten Hashing-Methoden gibt.

Antworten (2)

OK, also scheine ich über die Antwort gestolpert zu sein. Das Problem, das ich hatte, war, dass ich die Nachricht mit Präfix signierte und nicht nur den Hash ohne Präfix.

Der modifizierte Test, der verifyMultipleInputs()meinen Testvertrag aufruft, sieht wie folgt aus:

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()");

Die Funktion für den Vertrag gibt jetzt wahr zurück und der Test wird bestanden. Kann jemand erklären, warum ich keine vorangestellten Daten unterschreiben sollte, bevor ich den Vertrag anrufe?

Bearbeiten0:

gethstellt das Ethereum Signed Messageintern voran, wenn sign aufgerufen wird.

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

Übrigens sollten Sie Ihre Frage bearbeiten, anstatt den Inhalt in die Antwort aufzunehmen

Können Sie dies versuchen, um sicherzustellen, dass die msg.senderSmart-Contract-Funktion beim Aufrufen erwartet wird

ändern:

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

Zu:

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

Und das Problem kann kommen von abi.encodePacked; also sind diese beiden unten unterschied

keccak256(abi.encodePacked(inputNumber, inputAddress)

Und

web3.utils.soliditySha3(price, contractAddr);

Es kann eine Warnung geben, aber Sie können versuchen, keccak256(inputNumber, inputAddress)das Problem mit zu bestätigen.

Hoffe, das wird helfen!

Vielen Dank für die schnelle Antwort, aber diese Ergänzung hat das Problem für mich nicht behoben. Es ist eine Ergänzung, die ich in den Tests behalte, um sicherzustellen, dass ich von dieser Testadresse sende.
Okay, beachten Sie, dass keccak256und sha3nicht derselbe Hash-Algorithmus ist
Ich dachte, es ginge darum, soliditySha3()das Verhalten von nachzuahmen keccak256()?
Oh, in Ordnung! mein Fehler. Das Problem kann also von der kommenabi.encodePacked