OpenNHP/opennhp · error

invalid ztdo file

Error message

invalid ztdo file

What it means

DecryptZtdoFile walks a ztdo file chunk by chunk after decrypting. When the header declares a signature, the file must contain exactly SIGNATURELenSize bytes remaining (the signature) once the ciphertext is consumed. If the header says a signature exists but the file ends with 0 bytes remaining, there is no signature block at all, so the file is structurally invalid and this error is thrown.

Solutions

  1. Re-acquire the ztdo file from its source — the file is truncated and unrecoverable in place.
  2. Verify the file size against the expected size (header + ciphertext chunks + SIGNATURELenSize) before decrypting.
  3. If you produce these files, ensure the signature block is written after encryption and before closing the file.
  4. If the file was intentionally unsigned, regenerate it with the signature flag cleared so the header matches the actual content.

Example fix

// before: blind decrypt
err := ztdo.DecryptZtdoFile(file, passphrase)
// after: sanity-check size first
fi, _ := file.Stat()
if fi.Size() < headerSize+SIGNATURELenSize {
    return fmt.Errorf("ztdo file truncated: %d bytes", fi.Size())
}
err := ztdo.DecryptZtdoFile(file, passphrase)
Defensive patterns

Strategy: validation

Validate before calling

fi, err := f.Stat()
if err != nil { return err }
if fi.Size() < int64(headerSize+SIGNATURELenSize) {
    return fmt.Errorf("ztdo file too small (%d bytes): signature block missing", fi.Size())
}

Try / catch

if err := ztdo.DecryptZtdoFile(f, pass); err != nil {
    if strings.Contains(err.Error(), "invalid ztdo file") {
        // recover: re-fetch or regenerate the file
        return errReacquireFile
    }
    return err
}

Prevention

When it happens

Trigger: Calling DecryptZtdoFile on a ztdo file whose header flag says HasSignature()==true but whose ciphertext section ends exactly at EOF with no signature bytes following — i.e. a truncated file where the signature block was cut off.

Common situations: Incomplete download or interrupted copy of a .ztdo file; a writer that set the signature flag but crashed/was killed before appending the signature; manual truncation or corruption during transfer; mixing a signed header with an unsigned file body from different versions.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/88328ec3a30e169e. Report an issue: GitHub.

Appendix: source

Thrown at nhp/core/ztdo/ztdo.go:458

		payloadBuf := toBuffer(payload)
		recalcSig.mixHash(payloadBuf)
		remainingCiphertextFileSize -= int64(payloadBuf.Len())

		plaintext, err := payload.GetPlainText(ztdo.header.GetCipherMode(), gcmKey, ad)
		if err != nil {
			return err
		}
		if _, err := plaintextFile.Write(plaintext); err != nil {
			return err
		}

		if ztdo.header.HasSignature() {
			if remainingCiphertextFileSize == SIGNATURELenSize {
				break
			}
			if remainingCiphertextFileSize == 0 {
				return fmt.Errorf("invalid ztdo file")
			}
		} else {
			if remainingCiphertextFileSize == 0 {
				break
			}
		}
	}

	// update recalculated signature
	recalcSig.sign(gcmKey)

	if ztdo.header.HasSignature() {
		if err := toStructure(ciphertextFile, &ztdo.signature); err != nil {
			return err
		}

		if !ztdo.signature.verify(recalcSig) {
			return fmt.Errorf("signature verification failed")

View on GitHub (pinned to 6e04ca5ff0)