5 Commits
10 changed files with 456 additions and 103 deletions
+11 -3
View File
@@ -2,9 +2,9 @@ name: CI
on:
push:
branches: [master]
branches: [ master ]
pull_request:
branches: [master]
branches: [ master ]
env:
CARGO_TERM_COLOR: always
@@ -16,7 +16,15 @@ jobs:
- uses: actions/checkout@v6
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
run: |
curl -L https://github.com/rustwasm/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo mv wasm-pack-*/wasm-pack /usr/local/bin/
- name: Install wasm-bindgen-cli
run: |
curl -L https://github.com/wasm-bindgen/wasm-bindgen/releases/download/0.2.118/wasm-bindgen-0.2.118-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo mv wasm-bindgen-*/wasm-bindgen /usr/local/bin/
sudo mv wasm-bindgen-*/wasm-bindgen-test-runner /usr/local/bin/
- name: Rust tests
run: cargo test
+15 -2
View File
@@ -2,7 +2,7 @@ name: Publish
on:
push:
tags: ["v*"]
tags: [ "v*" ]
env:
CARGO_TERM_COLOR: always
@@ -16,7 +16,20 @@ jobs:
- uses: actions/checkout@v6
- name: Install wasm-pack
run: cargo install wasm-pack
run: |
curl -L https://github.com/wasm-bindgen/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo mv wasm-pack-*/wasm-pack /usr/local/bin/
- name: Install wasm-bindgen-cli
run: |
curl -L https://github.com/wasm-bindgen/wasm-bindgen/releases/download/0.2.118/wasm-bindgen-0.2.118-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo mv wasm-bindgen-*/wasm-bindgen /usr/local/bin/
sudo mv wasm-bindgen-*/wasm-bindgen-test-runner /usr/local/bin/
- name: Install wasm-opt
run: |
curl -L https://github.com/WebAssembly/binaryen/releases/latest/download/binaryen-version_129-x86_64-linux.tar.gz | tar xz
sudo mv binaryen-*/bin/wasm-opt /usr/local/bin/
- name: Rust tests
run: cargo test
Generated
+33 -1
View File
@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
[[package]]
name = "arrayref"
version = "0.3.9"
@@ -47,10 +53,11 @@ dependencies = [
[[package]]
name = "blake3-wasm-rs"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"blake3",
"hex",
"talc",
"wasm-bindgen",
"wasm-bindgen-test",
]
@@ -164,6 +171,15 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "memchr"
version = "2.8.0"
@@ -250,6 +266,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.228"
@@ -316,6 +338,16 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "talc"
version = "5.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3081e3f3ad542dc4cd0ae720f752598f8880fd364dc2d031132e3509f673aa23"
dependencies = [
"allocator-api2",
"lock_api",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
+9 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "blake3-wasm-rs"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
license = "MIT"
authors = ["UneBaguette <[email protected]>"]
@@ -13,10 +13,16 @@ categories = ["cryptography", "wasm"]
[lib]
crate-type = ["cdylib", "rlib"]
[features]
talc = ["dep:talc"]
[dependencies]
blake3 = { version = "1.8", features = ["wasm32_simd"] }
wasm-bindgen = "0.2"
wasm-bindgen = "=0.2.118"
[dev-dependencies]
wasm-bindgen-test = "0.3"
hex = "0.4"
hex = "0.4"
[target.'cfg(target_family = "wasm")'.dependencies]
talc = { version = "5", optional = true }
+63 -58
View File
@@ -1,78 +1,83 @@
ROOT := pkg
CRATE := blake3_wasm_rs
VERSION := 0.3.0
VERSION := 0.4.0
.PHONY: all clean build bundler node web node-esm package verify
CARGO := cargo
WASM_BINDGEN := wasm-bindgen
WASM_OPT := wasm-opt
WASM_OPT_FLAGS := --enable-bulk-memory --enable-nontrapping-float-to-int -O
WASM_TARGET := wasm32-unknown-unknown
TARGET_DIR := target/$(WASM_TARGET)/release
.PHONY: all clean build build-all node-esm package verify
all: clean build package
clean:
rm -rf $(ROOT)
build: bundler node web node-esm
build: build-all node-esm
bundler:
@echo "Building bundler target..."
wasm-pack build --target bundler -d $(ROOT)/bundler
build-all:
@echo "Building all target..."
RUSTFLAGS='-C opt-level=s' $(CARGO) build --target $(WASM_TARGET) --release --features talc
@mkdir -p $(ROOT)/bundler $(ROOT)/web $(ROOT)/node
$(WASM_BINDGEN) --target bundler --out-dir $(ROOT)/bundler $(TARGET_DIR)/$(CRATE).wasm
$(WASM_BINDGEN) --target web --out-dir $(ROOT)/web $(TARGET_DIR)/$(CRATE).wasm
$(WASM_BINDGEN) --target nodejs --out-dir $(ROOT)/node $(TARGET_DIR)/$(CRATE).wasm
@mv $(ROOT)/bundler/$(CRATE)_bg.wasm $(ROOT)/$(CRATE)_bg.wasm
@rm -f $(ROOT)/web/$(CRATE)_bg.wasm $(ROOT)/node/$(CRATE)_bg.wasm
$(WASM_OPT) $(WASM_OPT_FLAGS) $(ROOT)/$(CRATE)_bg.wasm -o $(ROOT)/$(CRATE)_bg.wasm
@node -e "['bundler','web','node'].forEach(d=>{const f='$(ROOT)/'+d+'/$(CRATE).js';require('fs').writeFileSync(f,require('fs').readFileSync(f,'utf8').replace(/$(CRATE)_bg\.wasm/g,'../$(CRATE)_bg.wasm'))})"
@rm -f $(ROOT)/bundler/package.json $(ROOT)/web/package.json
@rm -f $(ROOT)/bundler/.gitignore $(ROOT)/web/.gitignore $(ROOT)/node/.gitignore
@echo '{"type":"commonjs"}' > $(ROOT)/node/package.json
@cp scripts/tpl/index.js.template $(ROOT)/index.js
@cp scripts/tpl/index.d.ts $(ROOT)/index.d.ts
@cp scripts/tpl/README.md $(ROOT)/README.md
@cp LICENSE $(ROOT)/LICENSE
@sed -i 's|// @ts-nocheck|// Types|' $(ROOT)/index.d.ts
@node -e "\
const pkg = {\
name: 'blake3-wasm-rs',\
type: 'module',\
version: '$(VERSION)',\
description: 'BLAKE3 hashing via Rust/WASM - works in Node.js (CJS + ESM), browsers, and bundlers',\
license: 'MIT',\
repository: { type: 'git', url: 'https://github.com/UneBaguette/blake3.wasm' },\
main: 'index.js',\
types: 'index.d.ts',\
exports: { '.': {\
node: { require: './node/$(CRATE).js', import: './node-esm/index.mjs' },\
import: './bundler/$(CRATE).js',\
default: './web/$(CRATE).js'\
}},\
files: ['bundler/', 'web/', 'node/', 'node-esm/', '$(CRATE)_bg.wasm', 'README.md', 'LICENSE'],\
keywords: ['blake3', 'wasm', 'hash', 'cryptography', 'wasm-bindgen']\
};\
require('fs').writeFileSync('./$(ROOT)/package.json', JSON.stringify(pkg, null, 2) + '\n');"
node:
@echo "Building nodejs target..."
wasm-pack build --target nodejs -d $(ROOT)/node
web:
@echo "Building web target..."
wasm-pack build --target web -d $(ROOT)/web
node-esm: node
node-esm:
@echo "Generating node-esm wrapper..."
@mkdir -p $(ROOT)/node-esm
@node -e "\
const fs = require('fs'); \
const dts = fs.readFileSync('$(ROOT)/node/$(CRATE).d.ts', 'utf8'); \
const fns = [...dts.matchAll(/export function (\w+)/g)].map(m => m[1]); \
const cls = [...dts.matchAll(/export class (\w+)/g)].map(m => m[1]); \
const lines = [ \
\"import { createRequire } from 'module';\", \
\"const require = createRequire(import.meta.url);\", \
\"const mod = require('../node/$(CRATE).js');\", \
\"export default mod;\", \
...fns.map(n => 'export const ' + n + ' = mod.' + n + ';'), \
...cls.map(n => 'export const ' + n + ' = mod.' + n + ';'), \
]; \
fs.writeFileSync('$(ROOT)/node-esm/index.mjs', lines.join('\n') + '\n'); \
const fs = require('fs'); \
const dts = fs.readFileSync('$(ROOT)/node/$(CRATE).d.ts', 'utf8'); \
const fns = [...dts.matchAll(/export function (\w+)/g)].map(m => m[1]); \
const cls = [...dts.matchAll(/export class (\w+)/g)].map(m => m[1]); \
const lines = [ \
\"import { createRequire } from 'module';\", \
\"const require = createRequire(import.meta.url);\", \
\"const mod = require('../node/$(CRATE).js');\", \
\"export default mod;\", \
...fns.map(n => 'export const ' + n + ' = mod.' + n + ';'), \
...cls.map(n => 'export const ' + n + ' = mod.' + n + ';'), \
]; \
fs.writeFileSync('$(ROOT)/node-esm/index.mjs', lines.join('\n') + '\n'); \
"
package: build
@cp README.md $(ROOT)/README.md
@rm -f $(ROOT)/bundler/package.json $(ROOT)/node/package.json $(ROOT)/web/package.json
@rm -f $(ROOT)/bundler/.gitignore $(ROOT)/node/.gitignore $(ROOT)/web/.gitignore
@node -e "\
const pkg = { \
name: 'blake3-wasm-rs', \
version: '$(VERSION)', \
description: 'BLAKE3 hashing via Rust/WASM - works in Node.js (CJS + ESM), browsers, and bundlers', \
license: 'MIT', \
repository: { \
type: 'git', \
url: 'https://github.com/UneBaguette/blake3.wasm', \
}, \
exports: { \
'.': { \
node: { \
require: './node/$(CRATE).js', \
import: './node-esm/index.mjs', \
}, \
import: './bundler/$(CRATE).js', \
default: './web/$(CRATE).js', \
}, \
}, \
types: './bundler/$(CRATE).d.ts', \
files: ['node/', 'node-esm/', 'bundler/', 'web/', 'README.md'], \
keywords: ['blake3', 'wasm', 'hash', 'cryptography', 'wasm-bindgen'], \
}; \
require('fs').writeFileSync('$(ROOT)/package.json', JSON.stringify(pkg, null, 2) + '\n'); \
"
package:
@echo ""
@echo "Done! Package ready in $(ROOT)/"
@echo " node (CJS): $(ROOT)/node/"
+115 -33
View File
@@ -1,11 +1,11 @@
# blake3 to wasm
# blake3-wasm-rs
[![npm version](https://img.shields.io/npm/v/blake3-wasm-rs)](https://www.npmjs.com/package/blake3-wasm-rs)
[![CI](https://github.com/UneBaguette/blake3.wasm/actions/workflows/ci.yml/badge.svg)](https://github.com/UneBaguette/blake3.wasm/actions/workflows/ci.yml)
[![license](https://img.shields.io/npm/l/blake3-wasm-rs)](https://github.com/UneBaguette/blake3.wasm/blob/master/LICENSE)
[![npm downloads](https://img.shields.io/npm/dm/blake3-wasm-rs)](https://www.npmjs.com/package/blake3-wasm-rs)
**blake3.wasm** is a WebAssembly port of the BLAKE3 cryptographic hash function written in **Rust**. It enables fast and secure hashing right inside **browsers** and **Node.js**.
**blake3-wasm-rs** is a WebAssembly port of the BLAKE3 cryptographic hash function written in **Rust**. It enables fast and secure hashing right inside **browsers** and **Node.js**.
## build
@@ -36,43 +36,125 @@ const key = new Uint8Array(32).fill(1);
// One-shot hashing
blake3.hash(data);
blake3.hashXof(data, 64); // variable output length
// MAC and key derivation
blake3.keyedHash(data, key);
blake3.hashXof(data, 64); // variable output length
blake3.keyedHash(data, key); // key must be exactly 32 bytes
blake3.deriveKey('my context', key);
// Conctruct for streaming
const hasher = new blake3.Hasher();
hasher.update(data.slice(0, 5));
hasher.update(data.slice(5));
hasher.finalize();
```
#### OR
```js
import { hash, hashXof, keyedHash, deriveKey, Hasher } from 'blake3-wasm-rs'
const data = new TextEncoder().encode('hello world')
const key = new Uint8Array(32).fill(1)
// One-shot hashing
hash(data)
hashXof(data, 64) // variable output length
// MAC and key derivation
keyedHash(data, key)
deriveKey('my context', key)
// Conctruct for Streaming
{
using h = new blake3.Hasher();
h.update(data.slice(0, 5));
h.update(data.slice(5));
h.finalize();
h.finalizeXof(64);
h.reset();
}
// Streaming
const h = new Hasher()
h.update(data.slice(0, 5))
h.update(data.slice(5))
h.finalize()
// Keyed (MAC mode)
{
using mac = blake3.Hasher.newKeyed(key);
mac.update(data);
mac.finalize();
}
// Streaming
// Key derivation mode
{
using kdf = blake3.Hasher.newDeriveKey('my app v1 :: subkey');
kdf.update(key);
kdf.finalize();
}
// Batch hashing without re-allocating
{
using h = new blake3.Hasher();
h.update(chunk1);
const first = h.finalizeAndReset();
h.update(chunk2);
const second = h.finalizeAndReset();
}
```
#### Named imports
```ts
import { hash, hashXof, keyedHash, deriveKey, Hasher } from 'blake3-wasm-rs';
const data = new TextEncoder().encode('hello world');
const key = new Uint8Array(32).fill(1);
// One-shot hashing
hash(data);
hashXof(data, 64); // variable output length
keyedHash(data, key); // key must be exactly 32 bytes
deriveKey('my context', key);
// Streaming
{
using h = new Hasher();
h.update(data.slice(0, 5));
h.update(data.slice(5));
h.finalize();
h.finalizeXof(64);
h.reset();
}
// Keyed (MAC mode)
{
using mac = Hasher.newKeyed(key);
mac.update(data);
mac.finalize();
}
// Key derivation mode
{
using kdf = Hasher.newDeriveKey('my app v1 :: subkey');
kdf.update(key);
kdf.finalize();
}
// Batch hashing without re-allocating
{
using h = new Hasher();
h.update(chunk1);
const first = h.finalizeAndReset();
h.update(chunk2);
const second = h.finalizeAndReset();
}
```
## API
### Functions
| Function | Returns | Description |
|-----------------------------------|--------------|-------------------------------------------------|
| `hash(data)` | `Uint8Array` | One-shot 32-byte BLAKE3 digest |
| `hashXof(data, outLen)` | `Uint8Array` | Variable-length digest (XOF mode) |
| `keyedHash(data, key)` | `Uint8Array` | Keyed hash / MAC (key must be exactly 32 bytes) |
| `deriveKey(context, keyMaterial)` | `Uint8Array` | Derive a 32-byte subkey |
### Hasher class
| Method | Returns | Description |
|--------------------------------|--------------|-----------------------------------------------------------|
| `new Hasher()` | `Hasher` | Streaming hasher, unkeyed |
| `Hasher.newKeyed(key)` | `Hasher` | Streaming hasher, MAC mode (key must be exactly 32 bytes) |
| `Hasher.newDeriveKey(context)` | `Hasher` | Streaming hasher, KDF mode |
| `.update(data)` | `void` | Feed data, can be called multiple times |
| `.finalize()` | `Uint8Array` | 32-byte digest, non-destructive |
| `.finalizeXof(outLen)` | `Uint8Array` | Variable-length digest, non-destructive |
| `.finalizeAndReset()` | `Uint8Array` | Finalize then reset (useful for batch hashing) |
| `.reset()` | `void` | Reset to initial state, preserves mode |
| `.free()` | `void` | Release WASM memory manually (prefer `using` instead) |
> **Memory management:** In all modern browsers (and wasm-bindgen ≥ 0.2.91), WASM memory is freed automatically via the TC39 weak references proposal when the JS object goes out of scope.
>
> In practice, you often don't need to think about this. For deterministic cleanup or environments without weak reference support (older browsers, some Node.js setups), use `using` (TypeScript 5.2+ / ES2026) or call `.free()` manually.
>
> Never call `.free()` on a `using`-managed instance otherwise it will double-free.
## Benchmarks
Tested on **Apple M4**, Node.js v24.
+188
View File
@@ -0,0 +1,188 @@
# blake3-wasm-rs
[![npm version](https://img.shields.io/npm/v/blake3-wasm-rs)](https://www.npmjs.com/package/blake3-wasm-rs)
[![CI](https://github.com/UneBaguette/blake3.wasm/actions/workflows/ci.yml/badge.svg)](https://github.com/UneBaguette/blake3.wasm/actions/workflows/ci.yml)
[![license](https://img.shields.io/npm/l/blake3-wasm-rs)](https://github.com/UneBaguette/blake3.wasm/blob/master/LICENSE)
[![npm downloads](https://img.shields.io/npm/dm/blake3-wasm-rs)](https://www.npmjs.com/package/blake3-wasm-rs)
**blake3-wasm-rs** is a WebAssembly port of the BLAKE3 cryptographic hash function written in **Rust**. It enables fast and secure hashing right inside **browsers** and **Node.js**.
## build
```sh
# lets build it!
# For NodeJS
wasm-pack build --target nodejs --release
# For the web!
wasm-pack build --target web --release
```
SIMD is enabled via `wasm32_simd` feature in `Cargo.toml` and requires:
```toml
# .cargo/config.toml
[target.wasm32-unknown-unknown]
rustflags = ["-C", "target-feature=+simd128"]
```
## Usage
```ts
import * as blake3 from 'blake3-wasm-rs';
const data = new TextEncoder().encode('hello world');
const key = new Uint8Array(32).fill(1);
// One-shot hashing
blake3.hash(data);
blake3.hashXof(data, 64); // variable output length
blake3.keyedHash(data, key); // key must be exactly 32 bytes
blake3.deriveKey('my context', key);
// Conctruct for Streaming
{
using h = new blake3.Hasher();
h.update(data.slice(0, 5));
h.update(data.slice(5));
h.finalize();
h.finalizeXof(64);
h.reset();
}
// Streaming
// Keyed (MAC mode)
{
using mac = blake3.Hasher.newKeyed(key);
mac.update(data);
mac.finalize();
}
// Streaming
// Key derivation mode
{
using kdf = blake3.Hasher.newDeriveKey('my app v1 :: subkey');
kdf.update(key);
kdf.finalize();
}
// Batch hashing without re-allocating
{
using h = new blake3.Hasher();
h.update(chunk1);
const first = h.finalizeAndReset();
h.update(chunk2);
const second = h.finalizeAndReset();
}
```
#### Named imports
```ts
import { hash, hashXof, keyedHash, deriveKey, Hasher } from 'blake3-wasm-rs';
const data = new TextEncoder().encode('hello world');
const key = new Uint8Array(32).fill(1);
// One-shot hashing
hash(data);
hashXof(data, 64); // variable output length
keyedHash(data, key); // key must be exactly 32 bytes
deriveKey('my context', key);
// Streaming
{
using h = new Hasher();
h.update(data.slice(0, 5));
h.update(data.slice(5));
h.finalize();
h.finalizeXof(64);
h.reset();
}
// Keyed (MAC mode)
{
using mac = Hasher.newKeyed(key);
mac.update(data);
mac.finalize();
}
// Key derivation mode
{
using kdf = Hasher.newDeriveKey('my app v1 :: subkey');
kdf.update(key);
kdf.finalize();
}
// Batch hashing without re-allocating
{
using h = new Hasher();
h.update(chunk1);
const first = h.finalizeAndReset();
h.update(chunk2);
const second = h.finalizeAndReset();
}
```
## API
### Functions
| Function | Returns | Description |
|-----------------------------------|--------------|-------------------------------------------------|
| `hash(data)` | `Uint8Array` | One-shot 32-byte BLAKE3 digest |
| `hashXof(data, outLen)` | `Uint8Array` | Variable-length digest (XOF mode) |
| `keyedHash(data, key)` | `Uint8Array` | Keyed hash / MAC (key must be exactly 32 bytes) |
| `deriveKey(context, keyMaterial)` | `Uint8Array` | Derive a 32-byte subkey |
### Hasher class
| Method | Returns | Description |
|--------------------------------|--------------|-----------------------------------------------------------|
| `new Hasher()` | `Hasher` | Streaming hasher, unkeyed |
| `Hasher.newKeyed(key)` | `Hasher` | Streaming hasher, MAC mode (key must be exactly 32 bytes) |
| `Hasher.newDeriveKey(context)` | `Hasher` | Streaming hasher, KDF mode |
| `.update(data)` | `void` | Feed data, can be called multiple times |
| `.finalize()` | `Uint8Array` | 32-byte digest, non-destructive |
| `.finalizeXof(outLen)` | `Uint8Array` | Variable-length digest, non-destructive |
| `.finalizeAndReset()` | `Uint8Array` | Finalize then reset (useful for batch hashing) |
| `.reset()` | `void` | Reset to initial state, preserves mode |
| `.free()` | `void` | Release WASM memory manually (prefer `using` instead) |
> **Memory management:** In all modern browsers (and wasm-bindgen ≥ 0.2.91), WASM memory is freed automatically via the TC39 weak references proposal when the JS object goes out of scope.
>
> In practice, you often don't need to think about this. For deterministic cleanup or environments without weak reference support (older browsers, some Node.js setups), use `using` (TypeScript 5.2+ / ES2026) or call `.free()` manually.
>
> Never call `.free()` on a `using`-managed instance otherwise it will double-free.
## Benchmarks
Tested on **Apple M4**, Node.js v24.
| Size | @noble/hashes | awasm-noble | awasm-noble (threads) | blake3-wasm |
|-------|---------------|-------------|-----------------------|-------------|
| 32 B | 28 MB/s | 105 MB/s | 94 MB/s | 129 MB/s |
| 1 KB | 105 MB/s | 843 MB/s | 819 MB/s | 568 MB/s |
| 64 KB | 102 MB/s | 1,898 MB/s | 1,855 MB/s | 2,004 MB/s |
| 1 MB | 101 MB/s | 1,943 MB/s | 4,711 MB/s | 1,893 MB/s |
| 10 MB | 101 MB/s | 1,911 MB/s | 6,456 MB/s | 2,185 MB/s |
Tested on **Ryzen 7 5800X**, Node.js v24.
| Size | @noble/hashes | awasm-noble | awasm-noble (threads) | blake3-wasm |
|-------|---------------|-------------|-----------------------|-------------|
| 32 B | 11 MB/s | 34 MB/s | 45 MB/s | 70 MB/s |
| 1 KB | 56 MB/s | 499 MB/s | 526 MB/s | 885 MB/s |
| 64 KB | 52 MB/s | 1,729 MB/s | 1,684 MB/s | 2,037 MB/s |
| 1 MB | 51 MB/s | 1,550 MB/s | 4,036 MB/s | 1,829 MB/s |
| 10 MB | 50 MB/s | 1,497 MB/s | 4,946 MB/s | 1,877 MB/s |
## Security
The underlying `blake3` Rust crate targets algorithmic constant time. However, the JavaScript boundary (via napi-rs or WASM) introduces non-determinism from the V8 runtime that is outside our control. For absolute security, use the `blake3` Rust crate directly in a Rust program.
## See also
- [@noble/hashes](https://github.com/paulmillr/noble-hashes) | pure JS implementation
- [awasm-noble](https://github.com/paulmillr/awasm-noble) | auditable WASM implementation
- [blake3-napi](https://github.com/UneBaguette/blake3-napi) | native Node.js addon, faster for large inputs
+3
View File
@@ -0,0 +1,3 @@
// @ts-nocheck
export { Hasher, deriveKey, hash, hashXof, keyedHash } from './bundler/blake3_wasm_rs';
+4
View File
@@ -0,0 +1,4 @@
// blake3-wasm-rs. Auto-generated entry point
// Do not edit directly!
export { Hasher, deriveKey, hash, hashXof, keyedHash } from './bundler/blake3_wasm_rs.js';
+15 -3
View File
@@ -1,5 +1,13 @@
use wasm_bindgen::prelude::*;
#[cfg(all(
not(target_feature = "atomics"),
target_family = "wasm",
feature = "talc"
))]
#[global_allocator]
static TALC: talc::wasm::WasmDynamicTalc = talc::wasm::new_wasm_dynamic_allocator();
/// Hash data and return a 32-byte BLAKE3 digest.
#[wasm_bindgen]
pub fn hash(value: &[u8]) -> Vec<u8> {
@@ -8,7 +16,7 @@ pub fn hash(value: &[u8]) -> Vec<u8> {
/// Hash data with variable-length output (XOF mode).
/// Returns `out_len` bytes of BLAKE3 extended output.
#[wasm_bindgen]
#[wasm_bindgen(js_name = "hashXof")]
pub fn hash_xof(data: &[u8], out_len: usize) -> Vec<u8> {
let mut out = vec![0u8; out_len];
let mut reader = blake3::Hasher::new().update(data).finalize_xof();
@@ -20,7 +28,7 @@ pub fn hash_xof(data: &[u8], out_len: usize) -> Vec<u8> {
/// Compute a keyed BLAKE3 hash (MAC). Key must be exactly 32 bytes.
/// Throws if the key length is wrong.
#[wasm_bindgen]
#[wasm_bindgen(js_name = "keyedHash")]
pub fn keyed_hash(data: &[u8], key: &[u8]) -> Result<Vec<u8>, JsError> {
let key: &[u8; 32] = key
.try_into()
@@ -31,7 +39,7 @@ pub fn keyed_hash(data: &[u8], key: &[u8]) -> Result<Vec<u8>, JsError> {
/// Derive a 32-byte key from a context string and key material.
/// Context should be a hardcoded, globally unique, application-specific string.
#[wasm_bindgen]
#[wasm_bindgen(js_name = "deriveKey")]
pub fn derive_key(context: &str, key_material: &[u8]) -> Vec<u8> {
blake3::derive_key(context, key_material).to_vec()
}
@@ -57,6 +65,7 @@ impl Hasher {
/// Create a keyed hasher (MAC mode). Key must be exactly 32 bytes.
/// Throws if the key length is wrong.
#[wasm_bindgen(js_name = "newKeyed")]
pub fn new_keyed(key: &[u8]) -> Result<Hasher, JsError> {
let key: &[u8; 32] = key
.try_into()
@@ -68,6 +77,7 @@ impl Hasher {
/// Create a hasher in derive-key mode.
/// Context should be a hardcoded, globally unique, application-specific string.
/// Feed key material via `update()`, then call `finalize()`.
#[wasm_bindgen(js_name = "newDeriveKey")]
pub fn new_derive_key(context: &str) -> Hasher {
Hasher(blake3::Hasher::new_derive_key(context))
}
@@ -83,6 +93,7 @@ impl Hasher {
}
/// Return `out_len` bytes of extended output (XOF mode). Non-destructive.
#[wasm_bindgen(js_name = "finalizeXof")]
pub fn finalize_xof(&self, out_len: usize) -> Vec<u8> {
let mut out = vec![0u8; out_len];
self.0.finalize_xof().fill(&mut out);
@@ -92,6 +103,7 @@ impl Hasher {
/// Finalize the hash and reset the hasher in one call.
/// Useful for hashing multiple inputs sequentially without creating new instances.
#[wasm_bindgen(js_name = "finalizeAndReset")]
pub fn finalize_and_reset(&mut self) -> Vec<u8> {
let out = self.0.finalize().as_bytes().to_vec();
self.0.reset();