Incorrect address returned from `signedPrefixedMessageToKey` when invalid data passed in
Opened this issue · 0 comments
kibagateaux commented
Incorrect address returned from signedPrefixedMessageToKey
when invalid data passed in
Steps To Reproduce
There are two ways to get incorrect address returned from signedPrefixedMessageToKey
- invalid signature hash
- invalid message
public static String getAddressUsedToSignHashedMessage(String signedMessageInHex, String originalMessage)
throws SignatureException {
if (signedMessageInHex.startsWith("0x")) {
signedMessageInHex = signedMessageInHex.substring(2);
}
// No need to prepend these strings with 0x because
// Numeric.hexStringToByteArray() accepts both formats
String r = signedMessageInHex.substring(0, 64);
String s = signedMessageInHex.substring(64, 128);
String v = signedMessageInHex.substring(128, 130);
// Using Sign.signedPrefixedMessageToKey for EIP-712 compliant signatures.
String pubkey = Sign.signedPrefixedMessageToKey(originalMessage.getBytes(),
new Sign.SignatureData(
Numeric.hexStringToByteArray(v)[0],
Numeric.hexStringToByteArray(r),
Numeric.hexStringToByteArray(s)))
.toString(16);
return Keys.getAddress(pubkey);
}
Sign.SignatureData signatureData = Sign.signPrefixedMessage(myMsg.getBytes());
byte[] signedMessageBytes = new byte[32 + 32 + 1]; // 32 bytes for r, 32 bytes for s, 1 byte for v
System.arraycopy(signatureData.getR(), 0, signedMessageBytes, 0, 32);
System.arraycopy(signatureData.getS(), 0, signedMessageBytes, 32, 32);
System.arraycopy(signatureData.getV(), 0, signedMessageBytes, 64, 1);
String mySignedMessageHex = Numeric.toHexString(signedMessageBytes);
// Example #1 - Invalid Sig
String invalidAddress1 = getAddressUsedToSignHashedMessage(yourRandomSignedMessageHex, myMsg.getBytes())
// Example #2 - Invalid message
String aDifferentInvalidAddress = getAddressUsedToSignHashedMessage(mySignedMessageHex, yourRandomMsg.getBytes())
// Working Example
String myAddress = getAddressUsedToSignHashedMessage(mySignedMessageHex, myMsg.getBytes())
Expected behavior
signedPrefixedMessageToKey
returns null if:
- Signature data is compromised in some way
- message passed does not match original signature
- its not a valid ETH signature at all
Actual behavior
When invalid data passed to signedPrefixedMessageToKey
I get random addresses returned that did not sign the message and are not on the wallet that signed the original message.
Environment
- Web3j version:
4.10.3
- Java version :
openjdk 18.0.2
+ using Clojure version1.11.1
- Operating System 1:
macOS 13.0.1
with M1 chip
Additional context
- This isn't how
ecrecover
works in Solidity so it was unexpected behaviour to me but maybe it is intentional for some reason? - Would be great to know how I can programmatically check if the signature is valid or not if
signedPrefixedMessageToKey
is supposed to be returning random addresses using invalid data - Very critical bug since
signedPrefixedMessageToKey
assumes EIP-712 signatures so a valid ETH signature from the right address could be passed in but with the wrong format and web3j would return the wrong signer. So someone might think this function does work on non EIP-712 signatures and use incorrectly. - I know the function docs say to comapre returned address from expected signer but that feels incorrect to me because then the logic isnt entirely based on cryptography. Someone could somehow figure out the false address that will be returned from an invalid signature to bypass any checks or worse figure out how to spoof a specific address.
- Could be issue with me coercing
v
value to 27/28 except that i checked my signed message and it uses 27/28 already. Also this should still work according to EIP-712 spec I'm pretty sure. - I dont believe that this has to do with Java interop from Clojure since all code is working fine when correct parameters provided.
- I've been using this Java gist as reference for my Clojure code.
- Clojure code implementing
ecrecover
that is returning false addresses
(defn ecrecover
"original-msg is human readable string that signer was shown
signed-msg-hash is hexstring bytecode output of rpc/wallet signing function
returns checksummed ethereum address that signed msg-hash
"
[signed-msg-hash original-msg]
(let [raw-hex-str (if (clojure.string/starts-with? signed-msg-hash "0x")
(subs signed-msg-hash 2)
signed-msg-hash)
r (hex->bytes (subs raw-hex-str 0 64))
s (hex->bytes (subs raw-hex-str 64 128))
_v (subs raw-hex-str 128 130) ;; could be 0/1 or 27/28
v (hex->bytes (if (< (hex->int _v) 27) (int->hex (+ (hex->int _v) 27)) _v)) ;; so coerce to ETH native 27/28
signature-data (new org.web3j.crypto.Sign$SignatureData (first v) r s)
hashed-msg (.getBytes original-msg)
;; Using Sign.signedPrefixedMessageToKey for EIP-712 compliant signatures
pubkey (Sign/signedPrefixedMessageToKey hashed-msg signature-data)
address (if (nil? pubkey) nil (Keys/toChecksumAddress (Keys/getAddress (bigint->hex pubkey))))]
address))