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