Solidity ecrecover и web3j Sign.signMessage() несовместимы, не так ли?

Использование Web3j для подписи и проверки сообщения — это нормально, но когда я использую подпись, сгенерированную Web3j, для проверки с помощью ecrecover in solidity, похоже, она работает неправильно. Адрес подписывающей стороны отличается при выполнении кода солидности.

При поиске решения я обнаружил, что более новая версия geth добавляет перед подписью строку "\x19Ethereum Signed Message:\n32".

Ссылка здесь: полностью сбит с толку ecrecover

Я пытался использовать решение в справочном вопросе, но, похоже, оно не работает. Мой вопрос в том, что отличается от Web3j? Может ли кто-нибудь, знакомый с Web3j, предоставить мне рабочий пример для подписи и проверки сообщений?

Вот упрощенная версия контракта, над которым я работал:

pragma solidity ^0.4.0;

contract Auth {   

    string public name;

    function Auth(){
        name = "Auth 1.0";
    }

    function verify(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) constant returns (address) {
        address signer = ecrecover(_message, _v, _r, _s);
        return signer;
    }

    function verifyWithPrefix(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) constant returns (address) {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedHash = sha3(prefix, _message);
        address signer = ecrecover(prefixedHash, _v, _r, _s);
        return signer;
    }
}

Вот код Java, использующий Web3j.

    // Message to sign
    String plainMessage = "hello world";
    byte[] hexMessage = Hash.sha3(plainMessage.getBytes());

    // Use java to sign and verify the signature
    Sign.SignatureData signMessage = Sign.signMessage(hexMessage, ALICE.getEcKeyPair());
    String pubKey = Sign.signedMessageToKey(hexMessage, signMessage).toString(16);
    String signerAddress = Keys.getAddress(pubKey);
    System.out.println("Signer address    : 0x"+ signerAddress);

    // Now use java signature to verify from the blockchain
    Bytes32 message = new Bytes32(hexMessage);
    Uint8 v = new Uint8(signMessage.getV());
    Bytes32 r = new Bytes32(signMessage.getR());
    Bytes32 s = new Bytes32(signMessage.getS());

    String address = contract.verify(message, v, r, s).get().getValue().toString(16);
    String address2 = contract.verifyWithPrefix(message, v, r, s).get().getValue().toString(16);
    System.out.println("Recovered address1 : 0x"+address);
    System.out.println("Recovered address2 : 0x"+address2);

Ответы (1)

На самом деле проблема заключалась в двойном хешировании.

Глядя на метод signMessage() Web3j

Sign.SignatureData sig =Sign.signMessage(messageBytes, ecKeyPair);

и метод signedMessageToKey()

String pubKey = Sign.signedMessageToKey(messageBytes, sig).toString(16);

Эти методы внутренне хешируют (sha3) входные байты сообщения перед подписанием и при проверке. Это прекрасно работает, когда обе подписи создаются и проверяются с помощью самого Web3j. Но это отличает сигнатуру Web3j от того, что получается методом geth sign().

Вот общедоступный список с измененным файлом Web3j Sign.java, который можно использовать, чтобы избежать проблемы двойного хеширования: https://gist.github.com/xoriole/4c2a9630dba5a20ee28fb58edf193375 .