Recovery Data
(error correction - format specifications, version 1)

Recovery data lets a damaged file be repaired without any other copy: a fixed percentage of error-correction blocks is stored next to the original bytes. It is optional and comes in two variants that share one container format:

• Embedded - the container is appended inside an encrypted file (.enc, format version 4). Such files open in every version of the application; versions that know the container repair damaged blocks automatically when decrypting.
• External - the container is written as a separate file named <file>.recovery next to any file (e.g. xxx.doc + xxx.doc.recovery).

Recovery Data Format

*A - Encrypted Data: as in the File Encryptor format (random data block + ZIP archive, chosen cipher in CTR mode). Every reader stops at the end of the ZIP archive, so the bytes that follow are ignored by versions without recovery support;
*B - Recovery Data container: described below; stored in plain (not encrypted);
*C - Protected region: the bytes the recovery data can repair - embedded: everything before the container (header, salt and encrypted data); external: the whole file;
*D - MAC: the encrypted file's BLAKE3 MAC covers the container as well, so a repaired file must pass the normal authentication; damaged recovery blocks are regenerated during a repair for the same reason;

Error-Correction Code

• Wirehair V2 fountain code, wire profile WIREHAIR_V2_PROFILE_CERTIFIED_2026_07 (profile id 0x4B295BBB47F4F9C9); all implementations produce identical recovery blocks.
• The protected region is split into independent segments; each segment is one Wirehair message of K source blocks and gets R recovery blocks:
R = ceil(level% × K) + 2
• Source block i is Wirehair packet i (the last block of a segment may be shorter); recovery block j is packet K + j and always has the full block size.
• A segment stays repairable as long as its damaged source blocks are not more than its intact recovery blocks; up to R - 2 damaged blocks are guaranteed (the two extra blocks cover the fountain code's decode overhead).
• Damage is located with the per-block CRC32 index, i.e. corrupted blocks are turned into erasures before decoding. A file shorter than its protected region (truncated) cannot be repaired.

Segments and Block Size

• Block size = the smallest multiple of 4096 bytes that keeps a segment within 60,000 blocks (a 256 MiB segment gives 8 KiB blocks). The segment size cap is implementation-defined (PC: 256 MiB) and bounds the memory needed for a repair (about twice the segment size).
• A known region length (external files) is split into segments of equal nominal size; a streamed region (embedded) uses the cap as the nominal size, so only the last segment is shorter.
• A protected region shorter than 8192 bytes is exactly two blocks of ceil(length / 2) bytes.
• If the last segment would hold fewer than two blocks, it uses a block size of ceil(length / 2); a 1-byte tail is extended one byte backwards, i.e. it overlaps the previous segment by one byte (that byte is simply protected twice).

Container Layout

All integers are little-endian. The container is parsed from its end: the last 88 bytes locate everything else, which is what makes it usable both as a standalone file and appended in front of the MAC of an encrypted file.

HEAD (64 bytes)
offset  size  field
0       8     magic "PFTEREC1"
8       1     container version = 1
9       1     flags: 1 = embedded, 2 = external
10      1     level in percent (1-50)
11      1     reserved (0)
12      4     nominal block size in bytes
16      8     nominal segment size in bytes
24      8     Wirehair profile id = 0x4B295BBB47F4F9C9
32      16    name hash: first 16 bytes of SHA-256(UTF-8 file name)
              (external files; all zero when embedded)
48      12    reserved (0)
60      4     CRC32 of bytes 0-59
RECOVERY BLOCKS - immediately after HEAD: the R0 recovery blocks of segment 0, then those of segment 1, and so on; each block is block size bytes of its segment.

SEGMENT TABLE - one 48-byte entry per segment, in segment order:
offset  size  field
0       8     offset of the segment's first byte within the protected region
8       4     R - number of recovery blocks
12      32    WHV2 descriptor (see below; carries the segment length and block size)
44      4     CRC32 of bytes 0-43
INDEX - for each segment in order: K CRC32 values of the source blocks (0 ... K-1), then R CRC32 values of the recovery blocks; 4 bytes each. A source block's CRC covers its actual length (the last block may be short).

TRAIL (88 bytes) - the last 88 bytes of the container:
offset  size  field
0       8     offset of SEGMENT TABLE (from the container start)
8       8     offset of INDEX
16      8     container length (HEAD ... TRAIL, inclusive)
24      8     protected region length
32      4     segment count
36      4     flags (same as HEAD)
40      32    SHA-256 over HEAD + SEGMENT TABLE + INDEX
72      4     CRC32 of bytes 0-71
76      4     reserved (0)
80      8     magic "PFTEREC1"
WHV2 descriptor (32 bytes) - the canonical Wirehair V2 message descriptor; a decoder is created from it alone:
offset  size  field
0       4     magic "WHV2"
4       2     encoding version = 1
6       2     descriptor size = 32
8       8     profile id = 0x4B295BBB47F4F9C9
16      8     message size = segment length in bytes
24      4     block size in bytes
28      1     seed attempt (0-255, chosen by the encoder)
29      3     reserved (0)

Notes:
• Embedded detection: an encrypted file carries recovery data when the 8 bytes right before its MAC (bytes length-40 ... length-33) are the magic "PFTEREC1"; the container then ends at the MAC and its length is read from TRAIL.
• Repair procedure: verify every block against INDEX; feed the intact source blocks and the intact recovery blocks of a damaged segment to the decoder; rebuilt blocks are checked against INDEX before they replace anything; damaged recovery blocks are regenerated from the solved message. The repaired file is verified (embedded: including the MAC) before it replaces the original, which is kept as <file>.damaged.
• External pairing is by file name (xxx.doc ↔ xxx.doc.recovery); the name hash in HEAD detects a renamed or mismatched pair.
• The container itself is protected by its CRC32 fields and the SHA-256 in TRAIL; damage to the recovery blocks is tolerated (they are simply not used, and regenerated by a repair), damage to HEAD, TABLE, INDEX or TRAIL makes the container unusable.

Implementations