diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..444308a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Rust tests + run: cargo test + + - name: WASM tests (Node) + run: wasm-pack test --node diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d9d870f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,36 @@ +name: Publish + +on: + push: + tags: ["v*"] + +env: + CARGO_TERM_COLOR: always + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Install wasm-pack + run: cargo install wasm-pack + + - name: Rust tests + run: cargo test + + - name: WASM tests (Node) + run: wasm-pack test --node + + - name: Build package + run: bash build.sh + + - name: Publish to npm + run: | + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > pkg/.npmrc + cd pkg + npm publish --access public + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 66fa33c..6cbdb65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target pkg/ -.idea/ \ No newline at end of file +.idea/ +*.ps1 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 277891a..2ff86ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,23 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "blake3" version = "1.8.4" @@ -29,11 +46,12 @@ dependencies = [ ] [[package]] -name = "blake3-wasm" -version = "0.0.1" +name = "blake3-wasm-rs" +version = "0.1.1" dependencies = [ "blake3", "wasm-bindgen", + "wasm-bindgen-test", ] [[package]] @@ -42,6 +60,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.60" @@ -79,18 +103,113 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -115,12 +234,70 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "syn" version = "2.0.117" @@ -138,6 +315,16 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasm-bindgen" version = "0.2.118" @@ -151,6 +338,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.118" @@ -182,3 +379,72 @@ checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb55e2540ad1c56eec35fd63e2aea15f83b11ce487fd2de9ad11578dfc047ea" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf0ca1bd612b988616bac1ab34c4e4290ef18f7148a1d8b7f31c150080e9295" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cda5ecc67248c48d3e705d3e03e00af905769b78b9d2a1678b663b8b9d4472" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 28935c8..4b3d231 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "blake3-wasm" -version = "0.0.1" +name = "blake3-wasm-rs" +version = "0.1.1" edition = "2024" license = "MIT" repository = "https://github.com/UneBaguette/blake3.wasm" @@ -14,3 +14,6 @@ crate-type = ["cdylib"] [dependencies] blake3 = { version = "1.8.4", features = ["wasm32_simd"] } wasm-bindgen = "0.2.118" + +[dev-dependencies] +wasm-bindgen-test = "0.3" \ No newline at end of file diff --git a/README.md b/README.md index a95d8c2..dedf07a 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,32 @@ 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 + +// MAC and key derivation +blake3.keyedHash(data, key); +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 './pkg/blake3_wasm.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) diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..064eb5c --- /dev/null +++ b/build.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="pkg" +rm -rf "$ROOT" + +echo "Building bundler target..." +wasm-pack build --target bundler -d "$ROOT/bundler" + +echo "Building nodejs target..." +wasm-pack build --target nodejs -d "$ROOT/node" + +echo "Building web target..." +wasm-pack build --target web -d "$ROOT/web" + +# Generate node-esm wrapper from the nodejs build exports +echo "Generating node-esm wrapper..." +mkdir -p "$ROOT/node-esm" + +# Extract export names from the nodejs .js file +EXPORTS=$(grep -oP '(?<=module\.exports\.)\w+' "$ROOT/node/blake3_wasm_rs.js" | sort -u || true) + +# Fallback: parse from .d.ts if module.exports pattern not found +if [ -z "$EXPORTS" ]; then + EXPORTS=$(grep -oP '(?<=export function )\w+' "$ROOT/node/blake3_wasm_rs.d.ts" | sort -u || true) +fi + +# Also check for exported classes +CLASSES=$(grep -oP '(?<=export class )\w+' "$ROOT/node/blake3_wasm_rs.d.ts" | sort -u || true) + +{ + echo "import { createRequire } from 'module';" + echo "const require = createRequire(import.meta.url);" + echo "const mod = require('../node/blake3_wasm_rs.js');" + echo "export default mod;" + + for name in $EXPORTS; do + echo "export const $name = mod.$name;" + done + + for name in $CLASSES; do + echo "export const $name = mod.$name;" + done +} > "$ROOT/node-esm/index.mjs" + +# Clean up wasm-pack generated package.json in each subfolder +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" + +# Generate root package.json +cat > "$ROOT/package.json" << 'EOF' +{ + "name": "blake3-wasm-rs", + "version": "0.2.0", + "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/blake3_wasm_rs.js", + "import": "./node-esm/index.mjs" + }, + "import": "./bundler/blake3_wasm_rs.js", + "default": "./web/blake3_wasm_rs.js" + } + }, + "types": "./bundler/blake3_wasm_rs.d.ts", + "files": [ + "node/", + "node-esm/", + "bundler/", + "web/" + ], + "keywords": [ + "blake3", + "wasm", + "hash", + "cryptography", + "wasm-bindgen" + ] +} +EOF + +echo "" +echo "Done! Package ready in $ROOT/" +echo " node (CJS): $ROOT/node/" +echo " node (ESM): $ROOT/node-esm/" +echo " bundler: $ROOT/bundler/" +echo " web: $ROOT/web/" +echo "" +echo "Verify with: cd $ROOT && npm pack --dry-run" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 23d468c..3522029 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,4 +62,85 @@ impl Hasher { pub fn reset(&mut self) { self.0.reset(); } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_deterministic() { + let a = hash(b"hello"); + let b = hash(b"hello"); + assert_eq!(a, b); + } + + #[test] + fn test_hash_length() { + assert_eq!(hash(b"hello").len(), 32); + } + + #[test] + fn test_hash_xof_length() { + assert_eq!(hash_xof(b"hello", 64).len(), 64); + assert_eq!(hash_xof(b"hello", 16).len(), 16); + } + + #[test] + fn test_keyed_hash_valid() { + let key = [0u8; 32]; + assert!(keyed_hash(b"hello", &key).is_ok()); + } + + #[test] + fn test_derive_key() { + let a = derive_key("ctx", b"material"); + let b = derive_key("ctx", b"material"); + assert_eq!(a, b); + assert_eq!(a.len(), 32); + + let c = derive_key("other", b"material"); + assert_ne!(a, c); + } + + #[test] + fn test_hasher_streaming() { + let oneshot = hash(b"helloworld"); + let mut h = Hasher::new(); + h.update(b"hello"); + h.update(b"world"); + assert_eq!(h.finalize(), oneshot); + } + + #[test] + fn test_hasher_reset() { + let mut h = Hasher::new(); + h.update(b"junk"); + h.reset(); + h.update(b"hello"); + assert_eq!(h.finalize(), hash(b"hello")); + } + + #[test] + fn test_hasher_keyed() { + let key = [1u8; 32]; + let h = Hasher::new_keyed(&key).unwrap(); + assert_ne!(h.finalize(), hash(b"").as_slice()); + } +} + +#[cfg(target_arch = "wasm32")] +mod wasm_tests { + use super::*; + use wasm_bindgen_test::*; + + #[wasm_bindgen_test] + fn test_keyed_hash_bad_key() { + assert!(keyed_hash(b"hello", &[0u8; 16]).is_err()); + } + + #[wasm_bindgen_test] + fn test_hasher_keyed_bad_key() { + assert!(Hasher::new_keyed(&[0u8; 10]).is_err()); + } } \ No newline at end of file