Использование 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);
На самом деле проблема заключалась в двойном хешировании.
Глядя на метод 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 .