Using Bouncy Castle (BC) as crypto provider library is always a good choice, and they have a "build in" support for deriving the public key from an existing private key. Please keep in mind that BC does not work with a Private or Public key but instead with Ed25519PrivateKeyParameters and Ed25519PublicKeyParameters but as you can see it's very easy to get them from the encoded keys.
The full program is a little bit longer to prove that the rebuild public key is been able to verify a signature generated by its corresponding private key. Therefore, the main part of the program is to generate and verify a ED25519 signature.
Those two lines are doing what you are asking:
Ed25519PrivateKeyParameters privateKeyRebuild = new Ed25519PrivateKeyParameters(privateKeyEncoded, 0);
Ed25519PublicKeyParameters publicKeyRebuild = privateKeyRebuild.generatePublicKey();
The following lines are verifying the signature with the rebuild public key successfully.
output:
ED25519 signature with BC and deriving public key from private key
signature Length :64 Data:218c6dd5053ee22e94325981cdeb81d623b80715b21495d22ef9d8dbf0c4a097699747bafedbd2fd2bcdfdededb2664ea5b732e2242b7cb92ddd6e51acbed30e
signature correct :true
Rebuild the keys and verify the signature with rebuild public key
signature correct :true
Security warning: the code does not have any exception handling and is for educational purpose only.
code:
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator;
import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
public class Ed25519SignatureWithPublicKeyDeriving {
public static void main(String[] args) throws CryptoException {
System.out.println("ED25519 signature with BC and deriving public key from private key");
Security.addProvider(new BouncyCastleProvider());
// generate ed25519 keys
SecureRandom RANDOM = new SecureRandom();
Ed25519KeyPairGenerator keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.init(new Ed25519KeyGenerationParameters(RANDOM));
AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
Ed25519PrivateKeyParameters privateKey = (Ed25519PrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
Ed25519PublicKeyParameters publicKey = (Ed25519PublicKeyParameters) asymmetricCipherKeyPair.getPublic();
// the message
byte[] message = "Message to sign".getBytes(StandardCharsets.UTF_8);
// create the signature
Signer signer = new Ed25519Signer();
signer.init(true, privateKey);
signer.update(message, 0, message.length);
byte[] signature = signer.generateSignature();
// verify the signature
Signer verifier = new Ed25519Signer();
verifier.init(false, publicKey);
verifier.update(message, 0, message.length);
boolean shouldVerify = verifier.verifySignature(signature);
// output
System.out.println("signature Length :" + signature.length + " Data:" + bytesToHex(signature));
System.out.println("signature correct :" + shouldVerify);
// derive pub key from private key, here in encoded
byte[] privateKeyEncoded = privateKey.getEncoded();
// rebuild the keys
System.out.println("Rebuild the keys and verify the signature with rebuild public key");
Ed25519PrivateKeyParameters privateKeyRebuild = new Ed25519PrivateKeyParameters(privateKeyEncoded, 0);
Ed25519PublicKeyParameters publicKeyRebuild = privateKeyRebuild.generatePublicKey();
// verify the signature
Signer verifierDerived = new Ed25519Signer();
verifierDerived.init(false, publicKeyRebuild);
verifierDerived.update(message, 0, message.length);
boolean shouldVerifyDerived = verifierDerived.verifySignature(signature);
System.out.println("signature correct :" + shouldVerifyDerived);
}
private static String bytesToHex(byte[] bytes) {
StringBuffer result = new StringBuffer();
for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
}