schollz/croc · error · Error

Streaming downloads support SHA-256 verification only

Error message

Streaming downloads support SHA-256 verification only

What it means

Thrown by StreamingDownloadSink.hash() when called with "xxhash". The streaming download sink (service-worker based receive path used when File System Access API is unavailable) hashes data incrementally with SHA-256 as chunks are written, so it physically has no xxhash digest to return. Any verification flow that requests the xxhash algorithm on this sink fails immediately.

Source

Thrown at web/src/protocol/storage.ts:268

    if (position !== this.offset) {
      throw new Error("Streaming browser downloads require sequential chunks");
    }
    await wasm().sha256Update(this.hashHandle, bytes);
    const copy = Uint8Array.from(bytes);
    await this.send({ type: "chunk", bytes: copy.buffer }, [copy.buffer]);
    this.offset += bytes.byteLength;
  }

  async finalize() {
    if (this.closed) return;
    this.closed = true;
    this.digest = await wasm().sha256Final(this.hashHandle);
    await this.send({ type: "end" });
  }

  async hash(algorithm: "xxhash" | "sha256" = "sha256") {
    if (algorithm !== "sha256") {
      throw new Error("Streaming downloads support SHA-256 verification only");
    }
    if (!this.digest) throw new Error("Destination must be finalized before hashing");
    return this.digest;
  }

  async commit() {}

  async abort() {
    if (this.closed) return;
    this.closed = true;
    await this.send({ type: "abort" }).catch(() => undefined);
  }
}

export class StreamingDownloadDestination implements ReceiveDestination {
  async createEmptyFolder() {}

  async openFile(file: OfferedFile) {

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Use verifySinkSHA256(sink, expected) / sink.hash("sha256") for streaming destinations
  2. Branch on destination type before verification: xxhash for Directory/Download sinks, SHA-256 for StreamingDownloadSink
  3. Have the sender/manifest also advertise SHA-256 (stored transfers already store per-file SHA-256 in manifest field h) so SHA-256 verification is always possible

Example fix

// before
await verifySink(sink, offered.hash); // xxhash -> throws on StreamingDownloadSink

// after
await verifySinkSHA256(sink, expectedSHA256); // matches StreamingDownloadSink.hash("sha256")
Defensive patterns

Strategy: type-guard

Validate before calling

import { StreamingDownloadDestination } from "../protocol/storage";
// destination comes from chooseStoredReceiveDestination()

Type guard

const isStreaming = (d: ReceiveDestination): boolean => d instanceof StreamingDownloadDestination;
// then: isStreaming(d) ? await verifySinkSHA256(sink, sha) : await verifySink(sink, xxhash)

Try / catch

try { await sink.hash("sha256"); } catch (e) { if (e instanceof Error && e.message.includes("SHA-256 verification only")) { /* branch to sha256 */ } throw e; }

Prevention

When it happens

Trigger: Calling sink.hash("xxhash") or verifySink(sink, expected) (which defaults to xxhash) on a StreamingDownloadDestination-produced sink. This happens when receive code paths written for DirectorySink/DownloadSink are reused verbatim with StreamingDownloadDestination.

Common situations: Receiving on Firefox/Safari (no showDirectoryPicker) where chooseStoredReceiveDestination returns a StreamingDownloadDestination, then running the legacy xxhash verification step; adding a new destination type without updating the verification branch to use verifySinkSHA256.

Related errors


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