简体   繁体   English

如何将 Metamask 连接到 Web3J (java)

[英]How to connect Metamask to Web3J (java)

I am trying to connect my Metamask wallet to my Java Spring-Boot backend.我正在尝试将我的 Metamask 钱包连接到我的 Java Spring-Boot 后端。 I was trying to follow the example here .我试图按照这里的例子。 I am able to autogenerate the nonce and receive the wallet ID without a problem.我能够自动生成随机数并毫无问题地接收钱包 ID。 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.但是,我无法在 Web3J 上找到任何文档来执行此操作。

Is web3j not the right package to use for this? web3j 不是用于此目的的正确软件包吗? 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.该示例显示了如何基于 javascript 在 NodeJS 上进行验证,但我没有找到任何关于如何在 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.我的理解是,公钥是钱包 ID 本身,消息是由钱包私钥签名的随机数,出于明显的原因不共享。 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.据此,我需要使用公钥“解密”消息,并查看解密的消息是否与后端发送给 Metamask 签名的随机数相同。 Is this correct?这个对吗?

Here is my code to create and send the nonce to UI:这是我创建随机数并将其发送到 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.这个 nonce 被发送到 UI 以供 Metamask 签名。 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.相反,我需要使用 nonce 来查看是否可以检索用于签署消息的私钥的公钥,并查看检索到的公钥是否与钱包 ID 匹配。

The algorithm:算法:

  1. Receive the signed message and the wallet ID from the client从客户端接收签名消息和钱包ID
  2. Retrieve the nonce sent to the client with the same wallet ID检索发送给具有相同钱包 ID 的客户端的 nonce
  3. Generate the hash of the nonce生成随机数的哈希
  4. Generate the signature data from the message.从消息中生成签名数据。 This basically retrieves the V, R and S and.这基本上检索了 V、R 和 S 和。 R and S are the outputs of the ECDSA Signature and V is the Recovery ID. R 和 S 是 ECDSA 签名的输出,V 是恢复 ID。
  5. Using the ECDSA Signature and Hash of the Nonce, generate the possible public Key that was used to sign the message.使用 ECDSA 签名和 Nonce 的散列,生成可能用于对消息进行签名的公钥。 At max, one will be able to generate 4 possible public keys for this message.最多可以为该消息生成 4 个可能的公钥。
  6. Check if any of the generated keys match public wallet ID that the client sent.检查任何生成的密钥是否与客户端发送的公共钱包 ID 匹配。 If it matches, then we have a positive match.如果它匹配,那么我们有一个肯定的匹配。 Generate the JWT and respond to the client.生成 JWT 并响应客户端。 If not, we know that the nonce was not signed by the Metamask wallet we expected.如果不是,我们就知道这个 nonce 没有被我们预期的 Metamask 钱包签名。

The Code:编码:

Here is a sample code for UI (JavaScript and HTML):这是 UI(JavaScript 和 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. web3.eth.sign() 获取要签名的消息并获取正在签名的钱包 ID。 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.
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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