Skip to content

Ed25519Signature2018 verification fails due to destructive JWS Header re-serialization in ssi-jws #701

Description

@shagamd

When verifying a Verifiable Credential using the Ed25519Signature2018 suite, the ssi library rejects valid signatures generated by standard compliant third-party issuers (such as Digital Bazaar and Danubetech) with a ProofValidationError::InvalidSignature.

The root cause is a serialization mismatch during the JWS Detached verification process. ssi-jws decodes the header into a Rust struct and then re-serializes it via encode_signing_bytes(). Because serde_json serializes the struct fields in declaration order (e.g., alg, crit, b64), the resulting base64url string differs from the original issuer's string if they ordered the JSON properties differently (e.g., alg, b64, crit as generated by Digital Bazaar). This alters the signing input bytes and completely breaks the mathematical validation of the Ed25519 signature.

To Reproduce
Attempt to verify the following standard AlumniCredential generated using the Digital Bazaar implementation.

{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    {
      "alumniOf": "https://schema.org/alumniOf",
      "AlumniCredential": "https://example.org/AlumniCredential"
    }
  ],
  "id": "urn:uuid:1234",
  "issuanceDate": "2026-05-20T22:40:24Z",
  "type": [
    "VerifiableCredential",
    "AlumniCredential"
  ],
  "issuer": "did:key:z6Mkh47kdEbVR8Q15zwDcFGNQ7Xd8W3r56MKNN6ouZLrKBoQ",
  "credentialSubject": {
    "id": "did:example:holder",
    "alumniOf": "Example University"
  },
  "proof": {
    "type": "Ed25519Signature2018",
    "created": "2026-05-20T22:42:45Z",
    "verificationMethod": "did:key:z6Mkh47kdEbVR8Q15zwDcFGNQ7Xd8W3r56MKNN6ouZLrKBoQ#z6Mkh47kdEbVR8Q15zwDcFGNQ7Xd8W3r56MKNN6ouZLrKBoQ",
    "proofPurpose": "assertionMethod",
    "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..FQ9H02q8qI3gP_ScK9bNV62FrCZOAqbRKxQXEee4eq0FbBSobGSUx58vw6rscy5e2UJIDLU6vofkBagqBTJ5BA"
  }
}
  • Result: ProofValidationError::InvalidSignature

  • Reason: The raw JWS header is eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19 ({"alg":"EdDSA","b64":false,"crit":["b64"]}). ssi re-serializes this to eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9 ({"alg":"EdDSA","crit":["b64"],"b64":false}), corrupting the signing input.

Proof of Concept / Workaround
To demonstrate the root cause, I applied a temporary workaround in ssi-data-integrity/core/src/signing/jws.rs (inside the verify function for DetachedJwsSigning). By bypassing the encode_signing_bytes() call and extracting the raw header directly from the JWS string, the signature validates successfully:

// Workaround in `verify`:
let raw_jws = proof.signature.jws.as_str(); 
let raw_header_b64 = raw_jws.split('.').next().unwrap_or(""); 

let mut signing_bytes = raw_header_b64.as_bytes().to_vec();
signing_bytes.push(b'.');
signing_bytes.extend_from_slice(prepared_claims.as_ref());

// Proceed to verify...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions