如何将 Metamask 连接到 Web3J (java)
How to connect Metamask to Web3J (java)
我正在尝试将我的 Metamask 钱包连接到我的 Java Spring-Boot 后端。我试图按照示例 here 进行操作。我能够自动生成随机数并毫无问题地接收钱包 ID。我正在尝试验证来自服务器上钱包的已签名随机数,以确保发件人确实是他们所说的人。但是,我无法在 Web3J 上找到任何文档来执行此操作。
web3j 不是用于此的正确包吗?该示例显示了如何基于 javascript 在 NodeJS 上进行验证,但我没有找到任何关于如何在 Java.
上执行此操作的示例
我的理解是 public 密钥是钱包 ID 本身,消息是由钱包私钥签名的随机数,由于显而易见的原因不共享。据此,我需要使用 public 密钥“解密”消息,并查看解密后的消息是否与后端发送到 Metamask 进行签名的随机数相同。这是正确的吗?
这是我创建随机数并将其发送到 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;
}
在这里,我查看用户是否已经在我的系统中,如果不在,那么我只是创建一个临时用户,其中生成并保存在数据库中的随机随机数。这个随机数被发送到 UI 供 Metamask 签名。但是,我不确定如何进行验证部分。
我终于弄明白了。我最初的理解是错误的。我不应该尝试解密消息来检索随机数。相反,我需要使用随机数来查看是否可以检索用于签署消息的私钥的 public 密钥,并查看检索到的 public 密钥是否与钱包 ID 匹配。
算法:
- 收到客户端的签名消息和钱包ID
- 取回发送给同钱包ID客户端的nonce
- 生成随机数的哈希值
- 从消息中生成签名数据。这基本上检索了 V、R 和 S 和。 R 和 S 是 ECDSA 签名的输出,V 是恢复 ID。
- 使用 Nonce 的 ECDSA 签名和哈希,生成可能的 public 密钥,用于对消息进行签名。最多可以为此消息生成 4 个可能的 public 密钥。
- 检查是否有任何生成的密钥与客户端发送的 public 钱包 ID 相匹配。如果匹配,则我们有一个正匹配。生成 JWT 并响应客户端。如果不是,我们知道随机数没有被我们预期的 Metamask 钱包签名。
代码:
这是 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))
})
web3.eth.sign() 获取要签名的消息并获取对其进行签名的钱包 ID。然后将其发送到后端。在后端:
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.
}
}
我正在尝试将我的 Metamask 钱包连接到我的 Java Spring-Boot 后端。我试图按照示例 here 进行操作。我能够自动生成随机数并毫无问题地接收钱包 ID。我正在尝试验证来自服务器上钱包的已签名随机数,以确保发件人确实是他们所说的人。但是,我无法在 Web3J 上找到任何文档来执行此操作。
web3j 不是用于此的正确包吗?该示例显示了如何基于 javascript 在 NodeJS 上进行验证,但我没有找到任何关于如何在 Java.
上执行此操作的示例我的理解是 public 密钥是钱包 ID 本身,消息是由钱包私钥签名的随机数,由于显而易见的原因不共享。据此,我需要使用 public 密钥“解密”消息,并查看解密后的消息是否与后端发送到 Metamask 进行签名的随机数相同。这是正确的吗?
这是我创建随机数并将其发送到 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;
}
在这里,我查看用户是否已经在我的系统中,如果不在,那么我只是创建一个临时用户,其中生成并保存在数据库中的随机随机数。这个随机数被发送到 UI 供 Metamask 签名。但是,我不确定如何进行验证部分。
我终于弄明白了。我最初的理解是错误的。我不应该尝试解密消息来检索随机数。相反,我需要使用随机数来查看是否可以检索用于签署消息的私钥的 public 密钥,并查看检索到的 public 密钥是否与钱包 ID 匹配。
算法:
- 收到客户端的签名消息和钱包ID
- 取回发送给同钱包ID客户端的nonce
- 生成随机数的哈希值
- 从消息中生成签名数据。这基本上检索了 V、R 和 S 和。 R 和 S 是 ECDSA 签名的输出,V 是恢复 ID。
- 使用 Nonce 的 ECDSA 签名和哈希,生成可能的 public 密钥,用于对消息进行签名。最多可以为此消息生成 4 个可能的 public 密钥。
- 检查是否有任何生成的密钥与客户端发送的 public 钱包 ID 相匹配。如果匹配,则我们有一个正匹配。生成 JWT 并响应客户端。如果不是,我们知道随机数没有被我们预期的 Metamask 钱包签名。
代码:
这是 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))
})
web3.eth.sign() 获取要签名的消息并获取对其进行签名的钱包 ID。然后将其发送到后端。在后端:
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.
}
}