简体   繁体   中英

How to connect Metamask to Web3J (java)

I am trying to connect my Metamask wallet to my Java Spring-Boot backend. I was trying to follow the example here . I am able to autogenerate the nonce and receive the wallet ID without a problem. I am trying to verify the signed nonce from the Wallet on the server to make sure that the sender is indeed who they say they are. However, I am unable to find any documentation on Web3J to do this.

Is web3j not the right package to use for this? The example shows how to do the verification on NodeJS based on javascript but I don't find any example on how to do this on Java.

My understanding is that the public key is the wallet ID itself and that the message is the nonce signed by the private key of the wallet which is not shared for obvious reasons. According to this, I would need to "decrypt" the message using the public key and see if the decrypted message is same as the nonce that the backend sent to Metamask to sign. Is this correct?

Here is my code to create and send the nonce to UI:

public User findUserByPublicAddress(String publicWalletId) {
    User u = userRepository.findByPublicWalletId(publicWalletId);
    if(u == null) {
        u = new User("", "", "", null, publicWalletId, "");
        String nonce = StringUtil.generateRandomAlphaNumericString();
        u.setNonce(nonce);
        userRepository.saveAndFlush(u);
    }
    return u;
}

Here, I see if the user is already in my system and if they are not, then I just create a temporary user with a random nonce generated and saved in the DB. This nonce is sent to the UI for Metamask to sign. However, I am not sure how to do the verification part of it.

I was able to figure this out finally. My initial understanding was incorrect. I was not supposed to attempt to decrypt the message to retrieve the nonce. Rather I needed to use the nonce to see if I can retrieve the public key of the private key used to sign the message and see if that public key retrieved matches the wallet ID.

The algorithm:

  1. Receive the signed message and the wallet ID from the client
  2. Retrieve the nonce sent to the client with the same wallet ID
  3. Generate the hash of the nonce
  4. Generate the signature data from the message. This basically retrieves the V, R and S and. R and S are the outputs of the ECDSA Signature and V is the Recovery ID.
  5. Using the ECDSA Signature and Hash of the Nonce, generate the possible public Key that was used to sign the message. At max, one will be able to generate 4 possible public keys for this message.
  6. Check if any of the generated keys match public wallet ID that the client sent. If it matches, then we have a positive match. Generate the JWT and respond to the client. If not, we know that the nonce was not signed by the Metamask wallet we expected.

The Code:

Here is a sample code for UI (JavaScript and HTML):

web3.eth.sign(
    web3.utils.sha3(nonce),
    window.userWalletAddress)
.then((message) => {
    console.log(message)
    data['message'] = message // BODY
    var xmlReq = new XMLHttpRequest();
    xmlReq.onreadystatechange = function() {
        if(this.readyState == 4 && this.status == 200) {
            response = this.responseText
            console.log(response)
        }
    };
    xmlReq.open("POST", "/api/users/login", true)
    xmlReq.setRequestHeader('Content-Type', 'application/json')
    xmlReq.send(JSON.stringify(data))
})

The web3.eth.sign() takes the message to be signed and takes the wallet ID that is signing it. This is then sent to the backend. In the backend:

public User signin(UserLoginDTO loginDetails, HttpServletResponse response) {
    try {
        // Get the wallet ID and signed message from the body stored in the DTO
        String publicWalletId = loginDetails.getPublicWalletId();
        String message = loginDetails.getMessage();

        // Find the nonce from the DB that was used to sign this message
        User user = userRepository.findByPublicWalletId(publicWalletId);
        String nonce = user.getNonce();

        // Generate the HASH of the Nonce
        byte[] nonceHash = Hash.sha3(nonce.getBytes()) // org.web3j.crypto.Hash

        // Generate the Signature Data
        byte[] signatureBytes = Numeric.hexStringToByteArray(message); // org.web3j.utils.Numeric
        
        byte v = (byte) ((signatureBytes[64] < 27) ? (signatureBytes[64] + 27) : signatureBytes[64]);
        byte[] r = Arrays.copyOfRange(signatureBytes, 0, 32);
        byte[] s = Arrays.copyOfRange(signatureBytes, 32, 64);
        
        SignatureData signatureData = new SignatureData(v, r, s); // org.web3j.crypto.Sign.SignatureData

        // Generate the 4 possible Public Keys
        List<String> recoveredKeys = new ArrayList<>();
        for(int i = 0; i < 4; i++) {
            BigInteger r = new BigInteger(1, signatureData.getR());
            BigInteger s = new BigInteger(1, signatureData.getS());
            ECDSASignature ecdsaSignature = new ECDSASignature(r, s);
            BigInteger recoveredKey = Sign.recoverFromSignature((byte)i, ecdsaSignature, nonceHash);
            if(recoveredKey != null) {
                recoveredKeys.add("0x" + Keys.getAddressFromKey(recoveredKey)); // org.web3j.crypto.Keys
            }
        }

        // Check if one of the generated Keys match the public wallet ID.
        for(String recoveredKey : recoveredKeys) {
            if(recoveredKey.equalsIgnoreCase(publicWalletId)) { 
                // Add Code here to create the JWT and add that to your HttpServletResponse. Not shown here.
                return user;
            }
        }
        throw new CustomException("Message Sign Invalid", HttpStatus.UNAUTHORIZED);
    }
    catch (Exception ex) {
         // Custom Error Handling.
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM