signalapp/Signal-Server · error · JsonParseException
Could not interpret bytes as a ZK credential public key
Error message
Could not interpret bytes as a ZK credential public key
What it means
Thrown by ZkCredentialPublicKeyAdapter.deserialize when the base64 decoding succeeded but the resulting bytes cannot construct a ZkCredentialPublicKey (libsignal InvalidInputException). The adapter notes this should essentially never happen since ZkCredentialPublicKey is a plain ByteArray wrapper, so hitting it means the payload is genuinely not the expected key type or the byte length/format is wrong.
Solutions
- Verify the bytes are actually a ZK credential public key serialized by the same signal-protocol version the server uses.
- Align client and server org.whispersystems signal-protocol library versions.
- Re-serialize the key from its source object (ZkCredentialPublicKey.serialize()) rather than hand-assembling bytes.
- Check the decoded byte length matches the expected key size for your protocol version.
- Since this is defensive against an 'impossible' case, catch JsonParseException and reject the payload as invalid rather than retrying.
Example fix
// before (arbitrary bytes) byte[] bad = "some random bytes".getBytes(); // after (serialize from the real key object) byte[] ok = zkCredentialPublicKey.serialize();
Defensive patterns
Strategy: try-catch
Validate before calling
// before deserializing
byte[] bytes = Base64.getDecoder().decode(keyString);
if (bytes.length == 0) {
throw new IllegalArgumentException("zk credential public key is empty");
}
// optionally attempt construction early to fail with a clearer error
new ZkCredentialPublicKey(bytes); Try / catch
try {
ZkCredentialPublicKeys keys = objectMapper.readValue(json, ZkCredentialPublicKeys.class);
} catch (JsonParseException e) {
if (e.getCause() instanceof InvalidInputException) {
throw new BadRequestException("zk credential public key bytes are invalid for this protocol version");
}
throw e;
} Prevention
- Serialize keys only via ZkCredentialPublicKey.serialize(), never hand-built byte arrays
- Pin matching org.whispersystems signal-protocol versions on client and server
- Confirm the field actually holds a ZK credential public key, not another key type
- Reject empty and zero-length key strings before they reach the adapter
When it happens
Trigger: Deserializing a JSON field whose value base64-decodes to bytes that ZkCredentialPublicKey's constructor rejects under InvalidInputException — e.g. zero-length after a path change, or bytes produced by a different signal protocol version with a different key encoding.
Common situations: Version mismatch between client and server signal-protocol libraries; a field holding the wrong key (e.g. a different credential or an identity key) pasted into the ZK credential public key slot; corrupted binary payloads re-encoded to base64.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Could not interpret identity key bytes as an EC public key
- Could not parse key as a base64-encoded value
- return Response.status(422).build();
- exceeded maximum uploadLength
- Invalid length
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/19bf6df991904e06.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/util/ZkCredentialPublicKeyAdapter.java:52
@Override
public ZkCredentialPublicKey deserialize(final JsonParser parser, final DeserializationContext context) throws IOException {
final byte[] zkCredentialPublicKeyBytes;
try {
zkCredentialPublicKeyBytes = Base64.getDecoder().decode(parser.getValueAsString());
} catch (final IllegalArgumentException e) {
throw new JsonParseException(parser, "Could not parse key as a base64-encoded value", e);
}
if (zkCredentialPublicKeyBytes.length == 0) {
return null;
}
try {
return new ZkCredentialPublicKey(zkCredentialPublicKeyBytes);
} catch (final InvalidInputException e) {
// this should really never happen, as ZkCredentialPublicKey simply extends ByteArray
throw new JsonParseException(parser, "Could not interpret bytes as a ZK credential public key", e);
}
}
}
}
View on GitHub (pinned to 100ab61c82)