Require a SHA-256 hash in every manifest entry (closes #224)
check / check (push) Waiting to run

Loading accepted an entry whose hashes held no SHA-256 multihash, and
check and fetch, which compute only SHA-256, then reported the intact
file as a hash mismatch. Loading now refuses, naming the path, an entry
that does not list exactly one SHA-256 multihash; other hashes beside
it are allowed. AddFileWithHash accepts only a SHA-256 multihash, and
freshen carries an unchanged entry's SHA-256 hash over instead of its
first hash, found with SHA256Multihash, new in package mfer.
docs/FORMAT.md states the rule for writers and readers. Two loading
tests whose entries held an empty hash now give each entry a SHA-256
hash; the decoding-size test's path lengths change with it.

Model: opus-5-5
This commit is contained in:
2026-10-15 18:50:24 +00:00
parent 6a3f532c8d
commit a7875cfe3f
7 changed files with 255 additions and 112 deletions
+24 -15
View File
@@ -80,15 +80,15 @@ After decompressing `innerMessage`, the result is a serialized `MFFile`
Each file entry contains:
| Field | Number | Type | Description |
| ---------- | ------ | ------------------------- | ------------------------------------------------------------- |
| `path` | 1 | string | Relative file path (see Path Rules) |
| `size` | 2 | int64 | File size in bytes |
| `hashes` | 3 | repeated `MFFileChecksum` | At least one hash required; readers reject an entry with none |
| `mimeType` | 301 | string (optional) | MIME type |
| `mtime` | 302 | Timestamp (optional) | Modification time |
| `ctime` | 303 | Timestamp (optional) | Change time (inode metadata change) |
| `mode` | 304 | uint32 | Permission bits (see File Mode) |
| Field | Number | Type | Description |
| ---------- | ------ | ------------------------- | ------------------------------------------------------------ |
| `path` | 1 | string | Relative file path (see Path Rules) |
| `size` | 2 | int64 | File size in bytes |
| `hashes` | 3 | repeated `MFFileChecksum` | Must include exactly one SHA-256 multihash (see Hash Format) |
| `mimeType` | 301 | string (optional) | MIME type |
| `mtime` | 302 | Timestamp (optional) | Modification time |
| `ctime` | 303 | Timestamp (optional) | Change time (inode metadata change) |
| `mode` | 304 | uint32 | Permission bits (see File Mode) |
## File Mode
@@ -133,8 +133,16 @@ Each checksum is a single `bytes multiHash` field containing a
self-describing: the encoded bytes include a varint algorithm identifier
followed by a varint digest length followed by the digest itself.
The 1.0 implementation writes SHA-256 multihashes (`0x12` algorithm code).
Implementations must be able to verify SHA-256 multihashes at minimum.
A writer must give every file entry exactly one SHA-256 multihash of the file's
content: the algorithm code `0x12`, the digest length `0x20` and the 32 bytes of
the digest. It may list multihashes of other algorithms beside it. A reader must
refuse a manifest with an entry that has no SHA-256 multihash or more than one,
whatever other hashes the entry lists. A verifier then needs only SHA-256,
checking each file against its entry's SHA-256 multihash; it may ignore the
other hashes.
The reference implementation writes one hash for each entry, the SHA-256
multihash, and its `check` and `fetch` compute only SHA-256.
## Signature Scheme
@@ -212,10 +220,11 @@ compresses the same inner message to the same bytes:
- Each entry holds only `path`, `size`, `hashes` with exactly one hash, `mtime`
and `mode`. The hash is the SHA-256 multihash of the file's content, except
that `freshen` reads only the files that are new or whose size, `mtime` or
`mode` changed, and for every other file carries the first hash of its
existing entry over unchanged. `mtime` is the file's modification time as the
file system gives it, to the nanosecond, and `mode` is `0000` unless `gen` or
`freshen` is given `--include-permissions`.
`mode` changed, and for every other file carries the SHA-256 multihash of its
existing entry over unchanged, leaving out any other hash the entry lists.
`mtime` is the file's modification time as the file system gives it, to the
nanosecond, and `mode` is `0000` unless `gen` or `freshen` is given
`--include-permissions`.
- `createdAt` is left out unless `gen` or `freshen` is given
`--include-timestamps`. Then it holds the time the run started building the
manifest, to the nanosecond, so it differs on every run.