Skip to content

SipHash NAPI

SipHash NAPI is a native Node.js addon that brings SipHash-2-4 — a fast, secure hash function designed to resist hash-flooding DoS attacks — to JavaScript and TypeScript. The hashing is implemented in Rust and exposed to Node.js/Bun through napi-rs bindings, delivering native-speed 64-bit hashing with a clean three-function API.

SipHash is widely used as the default hasher in Rust’s HashMap and Python’s dict. This package makes the same algorithm available to Node.js developers who need a fast, non-cryptographic hash for content deduplication, cache keying, or hash table construction.

  • Native Rust Performance — The core SipHash-2-4 algorithm runs as compiled Rust via the siphasher crate, with zero JavaScript overhead in the hot path
  • Streaming File HashingsiphashFile() reads files in 8 KiB chunks, hashing arbitrarily large files without loading them into memory
  • Keyed HashingsiphashWithKey() accepts a 128-bit key as two bigint halves, enabling per-instance keying to defend against hash-flooding attacks
  • 8-Target Cross-Compilation — Pre-built binaries for macOS (x64/arm64), Linux glibc (x64/arm64), Linux musl (x64/arm64), and Windows (x64/arm64) via a CI matrix
  • TypeScript-First — Ships with auto-generated .d.ts declarations; all functions return bigint for full 64-bit precision
  • Multi-Runtime — Works across CommonJS, ESM, and Bun with automatic platform/architecture detection at load time

The entire public surface is three functions exported from lib.rs:

src/lib.rs
#[napi]
pub fn siphash(data: Buffer) -> BigInt {
let mut hasher = SipHasher::new();
hasher.write(data.as_ref());
BigInt::from(hasher.finish())
}
#[napi]
pub fn siphash_with_key(data: Buffer, key0: BigInt, key1: BigInt) -> Result<BigInt> {
let (_, k0, _) = key0.get_u128();
let (_, k1, _) = key1.get_u128();
let mut hasher = SipHasher::new_with_keys(k0 as u64, k1 as u64);
hasher.write(data.as_ref());
Ok(BigInt::from(hasher.finish()))
}
#[napi]
pub fn siphash_file(path: String) -> Result<BigInt> {
let mut file = std::fs::File::open(&path)
.map_err(|e| Error::from_reason(format!("failed to open {path}: {e}")))?;
let mut hasher = SipHasher::new();
let mut buf = [0u8; 8192];
loop {
let n = file.read(&mut buf)
.map_err(|e| Error::from_reason(format!("failed to read {path}: {e}")))?;
if n == 0 { break; }
hasher.write(&buf[..n]);
}
Ok(BigInt::from(hasher.finish()))
}

On the JavaScript side, usage is straightforward:

import { siphash, siphashWithKey, siphashFile } from "@jadujoel/siphash-napi";
// Hash a buffer with the default (0, 0) key
const hash = siphash(Buffer.from("hello")); // bigint
// Hash with a custom 128-bit key
const keyed = siphashWithKey(Buffer.from("hello"), 1n, 2n);
// Stream-hash a file without loading it into memory
const fileHash = siphashFile("/path/to/large-file.bin");

The release binary is optimized with LTO and symbol stripping (Cargo.toml profile):

[profile.release]
lto = true
strip = "symbols"

A GitHub Actions CI matrix builds all 8 targets in parallel, then runs smoke tests on Ubuntu, macOS, and Windows. The publish workflow triggers on version tags and uses napi pre-publish to push per-platform npm packages under the @jadujoel scope.

The project includes a smoke test suite in smoke/index.ts that verifies:

  • Deterministic output for identical inputs
  • Different inputs produce different hashes
  • Empty buffer handling
  • Keyed hashing produces distinct results from default-key hashing
  • File hashing matches buffer hashing for the same content
  • Proper error throwing for nonexistent files

The CI pipeline also runs a quick inline smoke test (node -e "...") on each platform after artifact download.

LanguageRust, TypeScript
Binding Layernapi-rs (N-API v6)
Hash Implementationsiphasher crate v1
PlatformNode.js, Bun
CI/CDGitHub Actions (build matrix × 8 targets)
Package@jadujoel/siphash-napi on npm

The source code is available on the project’s GitHub repository.