The "spki" format in your posted code is not a 'raw' or 'bare' publickey, and whatever you referenced in a comment but didn't show is apparently wrong also.
Here is a correct way (using Bouncy LWAPI) plus 4 alternatives using JCA (with either the Bouncy provider, or the standard Oracle/OpenJDK provider in Java 15 up). I don't have/use typescript but your 'typescript' is really just nodejs. I output all the data from it in base64: classic base64 for data signature, and spki-format key, base64url for raw key because that's what JWK natively uses and Java can handle it just as easily. You could use hex or any other bit-preserving encoding as long as you are consistent.
# nodejs input
const crypto = require('crypto');
const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
const data = Buffer.from(JSON.stringify({example:'test data'}));
console.log( data.toString('base64') );
console.log( crypto.sign(null,data,privateKey).toString('base64') );
console.log( publicKey.export({format:'der',type:'spki'}).toString('base64') );
console.log( publicKey.export({format:'jwk'}).x ); // base64urlsafe
# output stored in file and input to java below
eyJleGFtcGxlIjoidGVzdCBkYXRhIn0=
g2L2cSrMskh+p62HJN48AGefLzaKf8TyN/6IzaaYyWUeGoBm3OvibHFjtAtXlD0pm/ldaQJq/LOhUtJcbhWYCQ==
MCowBQYDK2VwAyEA+XYOwM61UpixNFD89bo4OViD6HCm0G6DQnmSYbky5Hs=
-XYOwM61UpixNFD89bo4OViD6HCm0G6DQnmSYbky5Hs
// nopackage
import java.io.*;
import java.math.BigInteger;
import java.security.spec.*;
import java.security.*;
import java.util.Base64;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.jcajce.spec.RawEncodedKeySpec;
public class SO76753558 {
public static void main (String[] args) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
BufferedReader br = new BufferedReader (new InputStreamReader (System.in));
byte[] data = Base64.getDecoder().decode(br.readLine()),
sig = Base64.getDecoder().decode(br.readLine()),
spki = Base64.getDecoder().decode(br.readLine()),
bare = Base64.getUrlDecoder().decode(br.readLine());
// Bouncy LWAPI
{
Signer v = new Ed25519Signer();
v.init(false, new Ed25519PublicKeyParameters(bare));
v.update(data, 0, data.length);
System.out.println ("LWAPI:" + v.verifySignature(sig));
}
// standard algorithm-specific; requires Java 15 up and not very convenient
{
byte[] rev = new byte[bare.length];
for( int i = 0; i<bare.length; i++ ){ rev[i] = bare[bare.length-1-i]; }
boolean hibit = (rev[0]&0x80)>0; rev[0] &= ~0x80;
EdECPublicKeySpec spec = new EdECPublicKeySpec(NamedParameterSpec.ED25519,
new EdECPoint (hibit, new BigInteger(1,rev)) );
KeyFactory f = KeyFactory.getInstance("Ed25519","SunEC");
Signature v = Signature.getInstance("Ed25519","SunEC");
v.initVerify(f.generatePublic(spec));
v.update(data);
System.out.println ("SunEC bare:"+ v.verify(sig));
}
// Bouncy algorithm-specific
{
KeyFactory f = KeyFactory.getInstance("Ed25519","BC");
Signature v = Signature.getInstance("Ed25519","BC");
v.initVerify(f.generatePublic(new RawEncodedKeySpec(bare)));
v.update(data);
System.out.println ("BC bare:"+ v.verify(sig));
}
// JCA generic; requires Java 15 up for SunEC
for( String provider : new String[]{ "SunEC", "BC" } ){
KeyFactory f = KeyFactory.getInstance("Ed25519",provider);
Signature v = Signature.getInstance("Ed25519",provider);
v.initVerify(f.generatePublic(new X509EncodedKeySpec(spki)));
v.update(data);
System.out.println (provider+" spki:"+ v.verify(sig));
}
}
}
Note that using new Ed25519PublicKeyParameters(spki)
throws an exception telling you it is the wrong length, which if you know what you are doing is enough to identify the problem. Using the (spki,0)
ctor instead suppresses the exception but the result is still completely and totally wrong; this is like putting masking tape over the light on your dashboard that indicates your brakes have failed -- it doesn't make anything better, it just prevents you from recognizing you are in danger.