signalapp/Signal-Server · error · BackupFailedZkAuthenticationException
backup auth credential presentation signature verification…
Error message
backup auth credential presentation signature verification failed
What it means
BackupFailedZkAuthenticationException thrown after the ZK proof verifies but the signature over the presentation, checked via publicKey.verifySignature(presentation.serialize(), signature), fails. The client must prove possession of the private key corresponding to the presentation's public key; a bad signature means the requester did not, so authentication is aborted. A metric tagged signature_validation is incremented.
Solutions
- Sign the exact byte array returned by presentation.serialize(), without re-encoding
- Use the private key pair corresponding to the credential presentation's public key
- Send the signature with byte-preserving encodings (avoid transformations that alter bytes)
- Regenerate the keypair and credential if account keys were reset
Example fix
// before signature = sign(canonicalJson(presentation)); // wrong bytes // after signature = sign(presentation.serialize()); // exact serialized presentation bytes
Defensive patterns
Strategy: validation
Validate before calling
byte[] serialized = presentation.serialize();
if (!publicKey.verifySignature(serialized, signature)) {
throw new IllegalStateException("local signature check failed before sending");
} Try / catch
try { authenticateBackup(presentation, signature); }
catch (BackupFailedZkAuthenticationException e) {
reSignAndRetryOnce(presentation); // recompute signature over exact serialize() bytes
} Prevention
- Sign presentation.serialize() bytes exactly, once, with the matching private key
- Avoid encodings that transform signature bytes in transit
- Verify the signature locally with the public key before sending
- Re-sign after any change to key material or serialization
When it happens
Trigger: Signing something other than the exact presentation.serialize() bytes, signing with the wrong private key, or the signature being mangled/truncated by transport encoding — the server re-verifies the signature over the canonical serialized presentation with the presentation's public key.
Common situations: Client sign helpers that canonicalize or re-serialize the payload (different byte encoding than the server's); key material mismatch after account re-registration; base64/URL-encoding differences that corrupt the signature; replay proxies altering the body.
Related errors
- backup auth credential presentation verification failed
- Only primary device can set backup-id
- Must set at least one of message/media credential requests
- receipt credential presentation verification failed
- Invalid sourceObject size
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/a1d059ad87815f67.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/backup/BackupManager.java:760
*/
private PresentationSignatureVerifier verifyPresentation(final BackupAuthCredentialPresentation presentation)
throws BackupFailedZkAuthenticationException {
try {
presentation.verify(clock.instant(), serverSecretParams);
} catch (VerificationFailedException e) {
Metrics.counter(ZK_AUTHN_COUNTER_NAME,
SUCCESS_TAG_NAME, String.valueOf(false),
FAILURE_REASON_TAG_NAME, "presentation_verification")
.increment();
throw new BackupFailedZkAuthenticationException("backup auth credential presentation verification failed");
}
return (signature, publicKey) -> {
if (!publicKey.verifySignature(presentation.serialize(), signature)) {
Metrics.counter(ZK_AUTHN_COUNTER_NAME,
SUCCESS_TAG_NAME, String.valueOf(false),
FAILURE_REASON_TAG_NAME, "signature_validation")
.increment();
throw new BackupFailedZkAuthenticationException("backup auth credential presentation signature verification failed");
}
return new Pair<>(presentation.getType(), presentation.getBackupLevel());
};
}
/**
* Check that the authenticated backup user is authorized to use the provided backupLevel
*
* @param backupUser The backup user to check
* @param backupLevel The authorization level to verify the backupUser has access to
* @throws BackupPermissionException if the backupUser is not authorized to access {@code backupLevel}
*/
@VisibleForTesting
static void checkBackupLevel(final AuthenticatedBackupUser backupUser, final BackupLevel backupLevel)
throws BackupPermissionException {
if (backupUser.backupLevel().compareTo(backupLevel) < 0) {
Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME, Tags.of(
UserAgentTagUtil.getPlatformTag(backupUser.userAgent()),View on GitHub (pinned to 100ab61c82)