schollz/croc · critical · Error
The stored file failed SHA-256 verification (${hex(actual)}
Error message
The stored file failed SHA-256 verification (${hex(actual)} != ${hex(expected)}) What it means
Thrown by verifySinkSHA256 when a stored-transfer file, after being written and finalized, hashes to a SHA-256 different from the value recorded in the encrypted manifest (field h). In the stored receive flow every downloaded file is checked this way before commit; a mismatch means the decrypted bytes do not match what the sender uploaded.
Source
Thrown at web/src/protocol/storage.ts:401
return new DownloadDestination();
}
export async function verifySink(sink: ReceiveSink, expected: Uint8Array) {
const actual = await sink.hash();
if (!bytesEqual(actual, expected)) {
throw new Error(
`The sender advertised xxhash ${hex(expected)}, but the received file hashes to ${hex(actual)}`,
);
}
}
export async function verifySinkSHA256(
sink: ReceiveSink,
expected: Uint8Array,
) {
const actual = await sink.hash("sha256");
if (!bytesEqual(actual, expected)) {
throw new Error(
`The stored file failed SHA-256 verification (${hex(actual)} != ${hex(expected)})`,
);
}
}
View on GitHub (pinned to e25f1bdc04)
Solutions
- Retry the receive; the claim/verified-download session bookkeeping makes re-download safe
- If reproducible, capture both digests and the file index — check sink.writeAt position math and chunk ordering (streaming sinks require strictly sequential chunks)
- Confirm the web client version matches the storage service chunk size (4 MiB); a size mismatch usually fails earlier but can manifest here
Defensive patterns
Strategy: retry
Try / catch
try { await verifySinkSHA256(sink, fromBase64URL(storedFile.h)); } catch (e) { await sink.abort().catch(() => {}); if (e instanceof Error && e.message.includes("SHA-256 verification")) { return restartStoredReceive(inspection); } throw e; } Prevention
- Verify hashes before sink.commit() so a failed check never touches the user's download
- On mismatch, restart from inspection rather than resuming — stored chunks are individually AEAD-protected, so a mismatch signals logic corruption, not a flaky network
When it happens
Trigger: downloadStoredFile() calls verifySinkSHA256(sink, fromBase64URL(storedFile.h)) after finalize(); the recomputed digest differs. Can be caused by corrupted chunk ciphertext that nonetheless passed AEAD (unlikely), a manifest/chunk index mix-up, or a buggy sink that drops or duplicates bytes.
Common situations: Wrong chunk written at wrong offset (position math bugs), concurrent downloads of the same share interleaving writes, or a service worker streaming bug dropping chunks. Because storeOpenChunk is AEAD-authenticated, most real instances are client write-path bugs rather than network corruption.
Related errors
- The sender advertised xxhash ${hex(expected)}, but the recei
- Received an invalid file chunk
- Received a file position that is too large
- Streaming downloads support SHA-256 verification only
- Invalid stored-transfer id
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/141bd989ae3eebb9.
Report an issue: GitHub.