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

  1. Sign the exact byte array returned by presentation.serialize(), without re-encoding
  2. Use the private key pair corresponding to the credential presentation's public key
  3. Send the signature with byte-preserving encodings (avoid transformations that alter bytes)
  4. 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

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


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)