jwtk/jjwt · error · InvalidKeyException

Specified Edwards Curve PublicKey does not match the specifi

Error message

Specified Edwards Curve PublicKey does not match the specified PrivateKey's curve.

What it means

When building an Octet (Edwards curve) private JWK from a private key, if a public key is also supplied it must live on the same Edwards curve as the private key. A curve mismatch throws InvalidKeyException; if no public key is supplied, JJWT derives it from the private key.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/security/OctetPrivateJwkFactory.java:49

    public OctetPrivateJwkFactory() {
        super(PrivateKey.class, DefaultOctetPrivateJwk.PARAMS);
    }

    @Override
    protected boolean supportsKeyValues(JwkContext<?> ctx) {
        return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultOctetPrivateJwk.D.getId());
    }

    @Override
    protected OctetPrivateJwk<PrivateKey, PublicKey> createJwkFromKey(JwkContext<PrivateKey> ctx) {
        PrivateKey key = Assert.notNull(ctx.getKey(), "PrivateKey cannot be null.");
        EdwardsCurve crv = EdwardsCurve.forKey(key);

        PublicKey pub = ctx.getPublicKey();
        if (pub != null) {
            if (!crv.equals(EdwardsCurve.forKey(pub))) {
                String msg = "Specified Edwards Curve PublicKey does not match the specified PrivateKey's curve.";
                throw new InvalidKeyException(msg);
            }
        } else { // not supplied - try to generate it:
            pub = EdwardsCurve.derivePublic(key);
        }

        // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the
        // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1
        boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null;
        JwkContext<PublicKey> pubCtx = OctetPublicJwkFactory.INSTANCE.newContext(ctx, pub);
        OctetPublicJwk<PublicKey> pubJwk = OctetPublicJwkFactory.INSTANCE.createJwk(pubCtx);
        ctx.putAll(pubJwk);
        if (copyId) {
            ctx.setId(pubJwk.getId());
        }

        //now add the d value
        byte[] d = crv.getKeyMaterial(key);
        Assert.notEmpty(d, "Edwards PrivateKey 'd' value cannot be null or empty.");

View on GitHub (pinned to fb71496164)

Solutions

  1. Ensure the public and private keys come from the same key pair / curve; omit the public key and let JJWT derive it from the private key.
  2. Regenerate the pair with Jwts.SIG.Ed25519.keyPair().build() and use both members together.
  3. Validate curves before calling: EdwardsCurve.forKey(priv).equals(EdwardsCurve.forKey(pub)).

Example fix

// before
builder.publicKey(pubFromOtherPair).privateKey(priv);
// after
KeyPair kp = Jwts.SIG.Ed25519.keyPair().build();
builder.publicKey(kp.getPublic()).privateKey(kp.getPrivate());
Defensive patterns

Strategy: validation

Validate before calling

if (pub != null && !EdwardsCurve.forKey(priv).equals(EdwardsCurve.forKey(pub))) {
    throw new InvalidKeyException("Public/private curve mismatch");
}

Try / catch

try {
    OctetPrivateJwk jwk = Jwts.SIG.Ed25519.keyPair()...toOctetPrivateJwk();
} catch (InvalidKeyException e) {
    // mismatched key pair
}

Prevention

When it happens

Trigger: Calling Jwts.SIG.Ed25519.keyPair() ... .toOctetPrivateJwk() (or OctetPrivateJwkFactory) while supplying a mismatched pair, e.g. an Ed25519 private key with an Ed448 public key (or vice versa) in the key context.

Common situations: Mixing keys from different keyPair() generations; loading keys from separate files/keystore entries that do not correspond; wrong key selected from a keystore alias.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/6f9608e26220e0e6. Report an issue: GitHub.