BLAKE3 hashing
for TypeScript/JS, compiled to WASM from the
blake3 Rust crate,
@BLAKE3-team/BLAKE3.
Two builds with the same API:
npm i @fuzdev/blake3_wasm # SIMD — faster, 45 KB
npm i @fuzdev/blake3_wasm_small # no SIMD — smaller, 32 KBUse @fuzdev/blake3_wasm
unless optimizing bundle size (or using Bun with its current SIMD issue).
It enables blake3's WASM SIMD optimizations
and is 2-3x faster at 64KB+ inputs on Deno and Node.
@fuzdev/blake3_wasm_small
is ~30% smaller but loses the 2-3x SIMD gains
(but it's up to ~2.5x faster on Bun, which has a WASM SIMD problem).
See benchmarks for details
(includes a comparison to the SIMD-lacking@connor4312/blake3,
blake3-wasm on npm).
Also available via npm: specifier in Deno (jsr coming soon, user requests will speed it up):
import{hash}from'npm:@fuzdev/blake3_wasm';Only import one — importing both loads two separate WASM modules. To swap builds without changing imports, use a bundler alias:
// vite.config.ts — use the small build everywhereexportdefault{resolve: {alias: {'@fuzdev/blake3_wasm': '@fuzdev/blake3_wasm_small'},},};import{Blake3Hasher,derive_key,hash,keyed_hash}from'@fuzdev/blake3_wasm';// one-shot hash — returns 32-byte Uint8Arrayconstdigest=hash(newTextEncoder().encode('hello'));console.log(digest.length);// 32// keyed hash (MAC) — key must be 32 bytesconstkey=newUint8Array(32);constmac=keyed_hash(key,newTextEncoder().encode('hello'));// key derivationconstderived=derive_key('my-app',newTextEncoder().encode('secret'));// streaming hasher — same result as one-shot aboveconsthasher=newBlake3Hasher();hasher.update(newTextEncoder().encode('hel'));hasher.update(newTextEncoder().encode('lo'));constresult=hasher.finalize();// same bytes as `digest`hasher.free();// release WASM memory, or use `using` — see API belowIn browsers, call init() once before using any hash functions.
It's a no-op if already initialized, so calling it unconditionally is safe.
import{hash,init}from'@fuzdev/blake3_wasm';awaitinit();constdigest=hash(newTextEncoder().encode('hello'));Node and Deno initialize automatically — init() is only needed in browsers.
The browser entry uses new URL('./blake3_wasm_bg.wasm', import.meta.url) internally,
which Vite and webpack handle automatically. Other bundlers may need a plugin for WASM assets.
Vite: Exclude from dependency pre-bundling so the .wasm file resolves correctly:
// vite.config.tsexportdefault{optimizeDeps: {exclude: ['@fuzdev/blake3_wasm'],},};Without this, Vite pre-bundles the JS into .vite/deps/ but doesn't copy the WASM binary alongside it,
causing a 404 at runtime. This is a common Vite issue
with WASM packages.
For synchronous initialization in Web Workers, use init_sync:
import{init_sync}from'@fuzdev/blake3_wasm';constwasm=awaitfetch('/blake3_wasm_bg.wasm').then((r)=>r.arrayBuffer());init_sync({module: wasm});import{derive_key_stream,hash_stream,keyed_hash_stream}from'@fuzdev/blake3_wasm';// file: File from <input>, drop event, or fetch Responseconstdigest=awaithash_stream(file.stream());constmac=awaitkeyed_hash_stream(key,file.stream());constderived=awaitderive_key_stream('my-app',file.stream());All functions return 32-byte (256-bit) digests. BLAKE3 supports variable-length output (XOF) but this API does not currently expose it — 32 bytes covers most use cases and XOF may be added in the future (open a discussion if you'd like it).
hash(data: Uint8Array): Uint8ArrayReturns 32-byte digest.
keyed_hash(key: Uint8Array,data: Uint8Array): Uint8ArrayKeyed hash (MAC). Throws if key is not exactly 32 bytes.
derive_key(context: string,key_material: Uint8Array): Uint8ArrayKey derivation. Returns 32 bytes.
consthasher=newBlake3Hasher();Create a hasher for plain hashing.
Blake3Hasher.new_keyed(key: Uint8Array): Blake3HasherKeyed hasher (MAC). Throws if key is not exactly 32 bytes.
Blake3Hasher.new_derive_key(context: string): Blake3HasherHasher in key derivation mode.
hasher.update(data: Uint8Array): voidFeed data incrementally.
hasher.finalize(): Uint8ArrayReturns 32-byte digest. Non-destructive — can call repeatedly.
hasher.finalize_and_reset(): Uint8ArrayFinalize and reset in one call.
hasher.reset(): voidReset to initial state. Preserves keyed/derive mode.
hasher.free(): voidRelease WASM memory. Supports using
for automatic cleanup:
using hasher=newBlake3Hasher();hasher.update(data);returnhasher.finalize();// hasher.free() called automaticallyhash_stream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array>Hash a stream. Returns 32-byte digest.
keyed_hash_stream(key: Uint8Array,stream: ReadableStream<Uint8Array>): Promise<Uint8Array>Keyed hash a stream. Key must be exactly 32 bytes. Returns 32-byte digest.
derive_key_stream(context: string,stream: ReadableStream<Uint8Array>): Promise<Uint8Array>Derive-key hash a stream. Returns 32-byte digest.
Stream functions handle hasher cleanup automatically, even if the stream throws mid-read.
See docs/architecture.md for crate structure, build pipeline, and tooling details.
# prerequisites
cargo install wasm-pack
cargo install cargo-component
rustup target add wasm32-unknown-unknown wasm32-wasip1
# build all WASM targets
deno task build:wasm
# or build individual targets
deno task build:wasm:deno # build for Deno (+ deno compile patch)
deno task build:wasm:web # build for web/Node (+ npm package)cargo-component and wasm32-wasip1
are only needed for WASI component builds and Wasmtime benchmarks (deno task bench).
The Deno build patches wasm-bindgen's fetch() to Deno.readFileSync() for
deno compile compatibility.
Include the pkg/deno/ directory via --include so the WASM binary is embedded:
deno compile --allow-read --include=path/to/pkg/deno my_script.tsnpm i # install dev dependencies (benchmark comparison)
cargo check --workspace # fast typecheck
deno task check # full check (typecheck + test:rust + clippy + fmt)
deno task compare # build + correctness: all builds vs test vectors
deno task test# all correctness tests (requires built artifacts)
deno task validate # everything: check + build + test + validate:*
deno task bench # performance: cross-runtime benchmarksOne snapshot of cross-runtime results (Deno, Node, Bun, Wasmtime) is in benches/results/report.md.
deno task bench # full pipeline: build + all runtimes + report
deno task bench:deno # Deno only
deno task bench:report # regenerate report from existing resultsUses changesets for version management.
Prerequisite: npm i -g @changesets/cli
changeset # add a changeset
deno task publish # dry-run
deno task publish --wetrun # version + check + build + validate + publish