> For the complete documentation index, see [llms.txt](https://wallet-docs.broearn.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wallet-docs.broearn.com/advanced/auth/web-java.md).

# Web Java

```
<dependency>
    <groupId>net.i2p.crypto</groupId>
    <artifactId>eddsa</artifactId>
    <version>0.3.0</version>
</dependency>
```

<pre class="language-java" data-overflow="wrap"><code class="lang-java">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());
    	}
<strong>    } 
</strong> 
    /**
     * 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 &#x26;&#x26; 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 &#x3C; time &#x26;&#x26; 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 &#x3C; 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 &#x3C; input.length &#x26;&#x26; 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 &#x3C; input.length;) {
            encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
            if (input[inputStart] == 0) {
                ++inputStart;
            }
        }
        while (outputStart &#x3C; encoded.length &#x26;&#x26; 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 &#x3C; input.length(); ++i) {
            char c = input.charAt(i);
            int digit = c &#x3C; 128 ? INDEXES[c] : -1;
            if (digit &#x3C; 0) {
                String msg = i +"Invalid characters,c=" + c;
                throw new RuntimeException(msg);
            }
            input58[i] = (byte) digit;
        }
        int zeros = 0;
        while (zeros &#x3C; input58.length &#x26;&#x26; input58[zeros] == 0) {
            ++zeros;
        }
        byte[] decoded = new byte[input.length()];
        int outputStart = decoded.length;
        for (int inputStart = zeros; inputStart &#x3C; input58.length;) {
            decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
            if (input58[inputStart] == 0) {
                ++inputStart;
            }
        }
        while (outputStart &#x3C; decoded.length &#x26;&#x26; 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 &#x3C; number.length; i++) {
            int digit = (int) number[i] &#x26; 0xFF;
            int temp = remainder * base + digit;
            number[i] = (byte) (temp / divisor);
            remainder = temp % divisor;
        }
        return (byte) remainder;
    }
}
</code></pre>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://wallet-docs.broearn.com/advanced/auth/web-java.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
