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

  1. Retry the receive; the claim/verified-download session bookkeeping makes re-download safe
  2. If reproducible, capture both digests and the file index — check sink.writeAt position math and chunk ordering (streaming sinks require strictly sequential chunks)
  3. 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

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


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/141bd989ae3eebb9. Report an issue: GitHub.