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
- Re-acquire the ztdo file from its source — the file is truncated and unrecoverable in place.
- Verify the file size against the expected size (header + ciphertext chunks + SIGNATURELenSize) before decrypting.
- If you produce these files, ensure the signature block is written after encryption and before closing the file.
- 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
- Check file size against header+signature expectations before decrypting
- Verify integrity (checksum) of downloads before processing
- Ensure writers append the signature block before closing the file
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
- signature verification failed
- access url is empty, please check with data provider
- failed to download ztdo
- failed to parse ztdo header
- ztdo id mismatch, please check with data provider
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)