Web Java
Code sample for verifying wallet signature and wallet address.
<dependency>
<groupId>net.i2p.crypto</groupId>
<artifactId>eddsa</artifactId>
<version>0.3.0</version>
</dependency>
package com.broearn.wallet.crypto;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.Arrays;
public class CryptoEd25519Verify {
// Copy for displaying on wallet signature page, allowing customization
private static String sprintf_str = "Welcome to %s!\n\nClick to sign in and accept the %s Terms of Service: %s\n\nThis request will not trigger a blockchain transaction or cost any gas fees.\n\nNonce:%s";
private String companyName = "Your Project Name"; // The project name used to display on the wallet signature page
private String serviceDomain = "https://www.youdomain.com"; // Current website address for services provided
private static final boolean CHECK_TIME = false;
// Example
public static void main(String[] args) {
// Wallet signature string, encoded with base58
String signature = "21SNZbbXyhUkN9RXyfKBthzCq8fKA3asV5xcZ6RuQpDcsGRzr6aRreMfrV63jjE5Wc9yx1TzmSuf8h1nvtzSe9Rz";
// Base58 encoding of signed data
String message = "3Qm87Hq6eP6pgoyMa5SraCsWJFB1HcwoTYWbmvhzhFVBTr7Sbm2NgX7JvZ5DEcEXXirwewpsPzDmLoV8Bz8HmQSv4ZJ1kJzbg2NKhk7HcshtczUssJSrfJosev87EcwpXb5GAjJGJunv96P63n36v9WHjaqkqSiRwRetx8GhGPZ3jVCFphEo96Ciz8oQaggqG9TnxP9P1JiyGKuewJkcWnEXyc581yjEF3iwqEVQcmL8BDoLS9kpa17oLPmiPLNXxUXhUXd8bGUrncQmfhG8yNjNQ44wpacbxMZXrtw4ACrBY6qUBRGPbU469RmmWEenF4R8R4rH9Wb22YmRWUmvBPCDVJCCauLvpjwA6Gapv9Lb5HSMtix3241pxd2JhoqhkWK";
// wallet address ==> publicKey encoded with base58
String publicKey = "7x9XDk1JZTukhN2KQSGqSL1SaoEbGwzdMR3tYLpiJeex";
CryptoEd25519Verify cryptoEd25519 = new CryptoEd25519Verify();
System.out.println("getSignMsg: " + cryptoEd25519.getSignMsg());
try {
String res = cryptoEd25519.checkSign(signature, message, publicKey);
if (!res.isEmpty()) {
System.out.println("Verification passed, wallet address is: " + res);
//TODO This implements login or registration logic. If there is a return here, it indicates that the wallet address is valid and the real owner
} else {
System.out.println("Verification failed, signature error");
}
} catch (Exception e) {
System.out.println("Error Message: " + e.getMessage());
}
}
/**
* getSignMsg
* @return string sign message str
*/
public String getSignMsg()
{
String msg = "";
try {
msg = urlEncode(String.format(sprintf_str, companyName, companyName, serviceDomain, time()));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return msg;
}
public String checkSign(String signature, String message, String publicKey) throws Exception {
if (signature.isEmpty() || message.isEmpty() || publicKey.isEmpty()) {
throw new Exception("Parameter error");
}
//Verify expiration time
if (CHECK_TIME) {
String messageText = new String(Base58.decode(message));
int len = messageText.length() - 18;
String nonce = messageText.substring(len);
if (nonce.length() == 18 && messageText.contains("Nonce%3A")) {
Long clientTime = Long.valueOf(nonce.substring(8));
if (!checkTime(clientTime)) {
throw new Exception("Signature Expiration");
}
} else {
throw new Exception("Format error, please keep ending with the string \"Nonce%3A\" and a timestamp of length 10, like this \"Nonce%3A1689054559\"");
}
}
message = new String(Base58.decode(message));
byte[] publicKeyByte = Base58.decode(publicKey);
if(ed25519VerifySign(publicKeyByte, message, signature)) {
return publicKey;
}else {
return "";
}
}
/**
* ed25519 VerifySign
* @param data
* @param signData
* @return
* @throws SignatureException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
*/
public static Boolean ed25519VerifySign(byte[] publicKey, String data, String signData) {
EdDSAEngine edEng = new EdDSAEngine();
EdDSANamedCurveSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
PublicKey pk = null;
Boolean isSuccess = false;
try {
pk = new EdDSAPublicKey(new EdDSAPublicKeySpec(publicKey, spec));
} catch (Exception e) {
return isSuccess;
}
try {
edEng.initVerify(pk);
} catch (InvalidKeyException e) {
return isSuccess;
}
try {
edEng.setParameter(EdDSAEngine.ONE_SHOT_MODE);
} catch (InvalidAlgorithmParameterException e) {
return isSuccess;
}
try {
edEng.update(data.getBytes());
isSuccess = edEng.verify(Base58.decode(signData));
} catch (SignatureException e) {
}
return isSuccess;
}
/**
* time
* @return Long
*/
public static Long time() {
return System.currentTimeMillis() / 1000L;
}
/**
* CheckTime Check if the client time is within a certain range
* @param int $clientTime
* @return bool
*/
private boolean checkTime(Long clientTime) {
Long sysTime = time();
Long time = sysTime + Long.valueOf("120");
Long time2 = sysTime - Long.valueOf("120");
if(clientTime < time && clientTime > time2) {
//Client time is less than system time -60 and client time is greater than system time -60
return true;
}else {
return false;
}
}
private String urlEncode(String msg) throws UnsupportedEncodingException {
return URLEncoder.encode(msg, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
}
}
class Base58 {
public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private static final char ENCODED_ZERO = ALPHABET[0];
private static final int[] INDEXES = new int[128];
static {
Arrays.fill(INDEXES, -1);
for (int i = 0; i < ALPHABET.length; i++) {
INDEXES[ALPHABET[i]] = i;
}
}
/**
* Base58 encode
* @param input
* @return String
*/
public static String encode(byte[] input) {
if (input.length == 0) {
return "";
}
int zeros = 0;
while (zeros < input.length && input[zeros] == 0) {
++zeros;
}
input = Arrays.copyOf(input, input.length);
char[] encoded = new char[input.length * 2];
int outputStart = encoded.length;
for (int inputStart = zeros; inputStart < input.length;) {
encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
if (input[inputStart] == 0) {
++inputStart;
}
}
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
++outputStart;
}
while (--zeros >= 0) {
encoded[--outputStart] = ENCODED_ZERO;
}
return new String(encoded, outputStart, encoded.length - outputStart);
}
/**
* Base58 decode
* @param input
* @return byte[]
*/
public static byte[] decode(String input) {
if (input.length() == 0) {
return new byte[0];
}
byte[] input58 = new byte[input.length()];
for (int i = 0; i < input.length(); ++i) {
char c = input.charAt(i);
int digit = c < 128 ? INDEXES[c] : -1;
if (digit < 0) {
String msg = i +"Invalid characters,c=" + c;
throw new RuntimeException(msg);
}
input58[i] = (byte) digit;
}
int zeros = 0;
while (zeros < input58.length && input58[zeros] == 0) {
++zeros;
}
byte[] decoded = new byte[input.length()];
int outputStart = decoded.length;
for (int inputStart = zeros; inputStart < input58.length;) {
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
if (input58[inputStart] == 0) {
++inputStart;
}
}
while (outputStart < decoded.length && decoded[outputStart] == 0) {
++outputStart;
}
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
}
/**
* divmod
* @param number
* @param firstDigit
* @param base
* @param divisor
* @return byte
*/
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
int remainder = 0;
for (int i = firstDigit; i < number.length; i++) {
int digit = (int) number[i] & 0xFF;
int temp = remainder * base + digit;
number[i] = (byte) (temp / divisor);
remainder = temp % divisor;
}
return (byte) remainder;
}
}
Last updated