Skip to content

Repository files navigation

blake3

BLAKE3 hashing for TypeScript/JS, compiled to WASM from the blake3 Rust crate, @BLAKE3-team/BLAKE3.

Install

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 KB

Use @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'},},};

Usage

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 below

Browser

In 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});

Hashing a ReadableStream

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());

API

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).

One-shot functions

hash(data: Uint8Array): Uint8Array

Returns 32-byte digest.

keyed_hash(key: Uint8Array,data: Uint8Array): Uint8Array

Keyed hash (MAC). Throws if key is not exactly 32 bytes.

derive_key(context: string,key_material: Uint8Array): Uint8Array

Key derivation. Returns 32 bytes.

Streaming hasher

consthasher=newBlake3Hasher();

Create a hasher for plain hashing.

Blake3Hasher.new_keyed(key: Uint8Array): Blake3Hasher

Keyed hasher (MAC). Throws if key is not exactly 32 bytes.

Blake3Hasher.new_derive_key(context: string): Blake3Hasher

Hasher in key derivation mode.

hasher.update(data: Uint8Array): void

Feed data incrementally.

hasher.finalize(): Uint8Array

Returns 32-byte digest. Non-destructive — can call repeatedly.

hasher.finalize_and_reset(): Uint8Array

Finalize and reset in one call.

hasher.reset(): void

Reset to initial state. Preserves keyed/derive mode.

hasher.free(): void

Release WASM memory. Supports using for automatic cleanup:

using hasher=newBlake3Hasher();hasher.update(data);returnhasher.finalize();// hasher.free() called automatically

Stream functions

hash_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.

Architecture

See docs/architecture.md for crate structure, build pipeline, and tooling details.

Building from source

Requires Rust and Deno.

# 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.ts

Development

npm 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 benchmarks

Benchmarks

One 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 results

Publish

Uses 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

Contributing

fuz.dev/contributing

License

MIT

About

BLAKE3 hashing compiled to WASM

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Sponsor this project

Used by

Contributors

Languages