‹ back to the set

LavinHash

rust · dlah · npm: lavinhash

A high-performance fuzzy hashing library. LavinHash implements the Dual-Layer Adaptive Hashing (DLAH) algorithm for detecting file and text similarity — designed for high performance and cross-platform compatibility.

source ↗ npm ↗

Overview

LavinHash is a high-performance fuzzy hashing library that generates compact fingerprints for file similarity detection. Unlike cryptographic hashes that change completely with any modification, fuzzy hashes allow computing similarity scores between files.

A cryptographic hash (MD5, SHA-256) is deliberately brittle: flip one bit of the input and the digest changes beyond recognition. A fuzzy hash is deliberately elastic: nearby inputs produce nearby fingerprints, so two fingerprints can be compared — the result is a percentage, not a yes/no.

note Key insight: traditional hashes (MD5, SHA-256) answer "Are these files identical?" LavinHash answers "How similar are these files?" — making it ideal for malware detection, plagiarism analysis, and deduplication.

Key features

High performance

O(n) time complexity with SIMD optimizations for processing gigabytes per second.

Dual-layer analysis

Separates structural similarity from content similarity for accurate detection.

Delta detection

Identifies exact byte-level changes between similar files.

Cross-platform

Available for Rust, Node.js, Python, Java, Go, C++, and Swift.

DLAH: Dual-Layer Adaptive Hashing

The DLAH algorithm analyzes files in two orthogonal dimensions, combining them to produce a robust similarity metric resistant to both structural and content modifications.

Layer 1 — structural fingerprinting (30% weight)

Captures the file's topology using Shannon entropy analysis. This layer detects structural changes like data reorganization, compression changes, or block-level modifications.

  1. Divide input into fixed-size blocks (default: 256 bytes)
  2. For each block, calculate Shannon entropy: H(X) = −Σ p(x) log₂ p(x)
  3. Quantize entropy to 4-bit nibbles (0–15 range)
  4. Concatenate nibbles to form the structural vector
  5. Compare vectors using Levenshtein distance (edit distance)

Example: 256-byte block → entropy 4.83 → quantized to nibble 12 (0xC)

Layer 2 — content-based hashing (70% weight)

Extracts semantic features using a rolling hash over a sliding window. This layer detects content similarity even when data is moved, inserted, or partially modified.

  1. Initialize BuzHash with a 64-byte window
  2. Slide the window byte-by-byte, computing the rolling hash
  3. When hash ≡ 0 (mod M), extract a feature (adaptive trigger)
  4. Insert the feature into an 8192-bit Bloom filter using 3 hash functions
  5. Compare Bloom filters using Jaccard similarity: |A ∩ B| / |A ∪ B|

The modulus is selected adaptively, adjusting feature density to file size:

adaptive modulus selection
M = min(file_size / 256, 8192)

Combined similarity score

dlah similarity formula
Δ(A, B) = α · S_structural + (1 − α) · S_content

Where:

Reading the score

Every comparison decomposes into two sub-scores plus the weighted combination — similarity = 0.3 × structural + 0.7 × content:

ComponentComputed asWeight
StructuralLevenshtein distance on entropy vectors30%
ContentJaccard similarity on the Bloom filter70%
CombinedWeighted combination (DLAH), 0–100%
tip Thresholds used by the recipes in this manual: a score ≥ 70% is treated as a family match in the malware classifier, and the clustering recipe groups fingerprints at ≥ 80%. Polymorphic malware variants typically retain 70–90% similarity despite obfuscation.

System architecture

Core library (Rust)

source layout · rust
src/
├── lib.rs              # Public API and FFI exports
├── algo/
│   ├── entropy.rs      # Shannon entropy (SIMD optimized)
│   ├── buzhash.rs      # BuzHash rolling hash
│   └── bloom.rs        # Fixed 8192-bit Bloom filter
├── model/
│   └── fingerprint.rs  # Fingerprint struct (repr(C))
└── utils/
    └── mem.rs          # Zero-copy FFI helpers

Binary wire format

Fingerprints serialize to a compact, versioned binary layout:

OffsetFieldTypeSize
0x00Magicu81 byte
0x01Versionu81 byte
0x02–0x03Struct Lengthu16 (LE)2 bytes
0x04–0x403Content Bloomu8[1024]1024 bytes
0x404+Structural Datau8[n]Variable

Performance characteristics

O(n)

Time complexity · linear in file size

O(1)

Space complexity · constant memory

~1–2 KB

Fingerprint size · independent of file size

~500 MB/s

Throughput · single-threaded (Intel i7)

Optimization techniques

Getting started

Install

install · shell
npm install lavinhash

Cargo, Maven, and Go module coordinates for the other bindings are listed per language in guides.

Quick start

Hash a set of files, then fold over the fingerprints to find the closest match — here, detecting malware variants:

quick start · node.js / typescript
// Node.js / TypeScript - Functional Pipeline Approach
import { hash, compare, findDelta } from 'lavinhash';
import { readFile } from 'fs/promises';
import { pipeline } from 'stream/promises';

// Functional composition for file analysis
const analyzeFiles = async (paths: string[]) => {
  const fingerprints = await Promise.all(
    paths.map(async (path) => ({
      path,
      data: new Uint8Array(await readFile(path)),
      fingerprint: null as any
    }))
  );

  return fingerprints.map(({ path, data }) => ({
    path,
    fingerprint: hash(data),
    metadata: { size: data.length, timestamp: Date.now() }
  }));
};

// Compare with functional fold (reduce)
const findMostSimilar = (target: any, candidates: any[]) =>
  candidates.reduce((best, current) => {
    const sim = compare(target.fingerprint, current.fingerprint);
    return sim > best.similarity
      ? { candidate: current, similarity: sim }
      : best;
  }, { candidate: null, similarity: 0 });

// Usage: Detect malware variants
const [target, ...database] = await analyzeFiles([
  'unknown_sample.exe',
  'known_trojan_v1.exe',
  'known_trojan_v2.exe'
]);

const match = findMostSimilar(target, database);
console.log(`Match: ${match.candidate.path} (${match.similarity}%)`);

API reference

The whole surface is three functions and a configuration builder. Signatures below use the Node.js / TypeScript binding; the Rust core takes an extra optional config argument (hash(&data, Option<&Config>), compare(&a, &b, Option<&Config>)).

hash(data: Uint8Array, config?: Config) → Fingerprint

Computes a DLAH fingerprint for a byte buffer. The fingerprint is compact (~1–2 KB, independent of input size), serializable in the binary wire format, and deterministic across platforms.

compare(a: Fingerprint, b: Fingerprint) → number

Computes the combined DLAH similarity between two fingerprints. Returns a percentage in [0, 100]: 0.3 × structural + 0.7 × content.

findDelta(a: Fingerprint, b: Fingerprint, dataA: Uint8Array, dataB: Uint8Array) → Delta

Delta detection: given two fingerprints and their source buffers, identifies byte-level changes between similar files. The result exposes totalChanges, bytesAdded, and bytesDeleted.

new Config().withAlpha(α: number).withWindowSize(bytes: number) → Config

Builder for tuning the algorithm: withAlpha adjusts the structural weight (default 0.3), withWindowSize sets the rolling-hash window (default 64 bytes). The Rust core spells these Config::new().with_alpha(0.4).with_window_size(128).

Delta fields

FieldTypeMeaning
totalChangesnumberTotal number of detected changes between the two buffers
bytesAddednumberBytes present in B but not in A
bytesDeletednumberBytes present in A but not in B
note All language bindings follow the same API design for consistency. The core functionality (hash, compare, findDelta) works identically across all platforms.

Implementation guides

Language-specific guides for integrating LavinHash into your projects.

Node.js / TypeScript

install · shell
npm install lavinhash
basic usage · typescript
import { hash, compare, findDelta } from 'lavinhash';
import { readFile } from 'fs/promises';

// Functional approach with pipe composition
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);

const toUint8Array = (buffer: Buffer) => new Uint8Array(buffer);
const computeHash = (data: Uint8Array) => ({ data, fingerprint: hash(data) });

// Process single file
const processFile = pipe(toUint8Array, computeHash);

// Compare files functionally
const compareFiles = async (path1: string, path2: string) => {
  const [file1, file2] = await Promise.all([
    readFile(path1).then(processFile),
    readFile(path2).then(processFile)
  ]);

  const similarity = compare(file1.fingerprint, file2.fingerprint);
  const delta = findDelta(
    file1.fingerprint, file2.fingerprint,
    file1.data, file2.data
  );

  return {
    similarity,
    changes: { total: delta.totalChanges, added: delta.bytesAdded, deleted: delta.bytesDeleted }
  };
};

// Usage
const result = await compareFiles('v1.bin', 'v2.bin');
console.log(`Similarity: ${result.similarity}%`);
console.log(`Delta: +${result.changes.added} -${result.changes.deleted}`);

Advanced: streaming and clustering

advanced usage · typescript
// Advanced: Streaming pipeline with backpressure
import { hash, compare, Config } from 'lavinhash';
import { createReadStream } from 'fs';
import { pipeline } from 'stream/promises';
import { Transform } from 'stream';

// Custom configuration with builder pattern
const createConfig = () =>
  new Config()
    .withAlpha(0.4)
    .withWindowSize(128);

// Stream-based processing for large files
class HashTransform extends Transform {
  constructor(private config: Config) {
    super({ objectMode: true });
  }

  _transform(chunk: { path: string; data: Uint8Array }, _, callback) {
    const fingerprint = hash(chunk.data, this.config);
    callback(null, { ...chunk, fingerprint });
  }
}

// Functional similarity matrix computation
const computeSimilarityMatrix = (fingerprints: any[]) =>
  fingerprints.map((fp1, i) =>
    fingerprints.slice(i + 1).map((fp2) => ({
      pair: [i, i + fingerprints.indexOf(fp2, i + 1)],
      similarity: compare(fp1, fp2)
    }))
  ).flat();

// Find clusters using transitive closure
const findClusters = (matrix: any[], threshold = 80) =>
  matrix
    .filter(({ similarity }) => similarity >= threshold)
    .reduce((clusters, { pair: [a, b] }) => {
      const cluster = clusters.find(c => c.includes(a) || c.includes(b));
      return cluster ? (cluster.push(a, b), clusters) : [...clusters, [a, b]];
    }, [] as number[][]);

// Usage
const files = ['file1.bin', 'file2.bin', 'file3.bin'];
const results = await processFiles(files);
const matrix = computeSimilarityMatrix(results.map(r => r.fingerprint));
const clusters = findClusters(matrix);

Rust

install · cargo.toml
[dependencies]
lavinhash-core = "0.1.0"
basic usage · rust
use lavinhash_core::{hash, compare};

fn main() {
    // Hash a file
    let data = std::fs::read("file.txt")?;
    let fingerprint = hash(&data, None)?;

    // Compare two files
    let data2 = std::fs::read("file2.txt")?;
    let fp2 = hash(&data2, None)?;

    let similarity = compare(&fingerprint, &fp2, None);
    println!("Similarity: {}%", similarity);
}

Java

install · pom.xml
<dependency>
    <groupId>com.lavinhash</groupId>
    <artifactId>lavinhash-jni</artifactId>
    <version>0.1.0</version>
</dependency>
basic usage · java
import com.lavinhash.*;

public class Example {
    public static void main(String[] args) {
        // Hash a file
        byte[] data = Files.readAllBytes(Paths.get("file.txt"));
        Fingerprint fp = LavinHash.hash(data);

        // Compare two files
        byte[] data2 = Files.readAllBytes(Paths.get("file2.txt"));
        Fingerprint fp2 = LavinHash.hash(data2);

        int similarity = LavinHash.compare(fp, fp2);
        System.out.println("Similarity: " + similarity + "%");
    }
}

Go

install · shell
go get github.com/lavinhash/lavinhash-go
basic usage · go
package main

import (
    "fmt"
    "os"
    lavin "github.com/lavinhash/lavinhash-go"
)

func main() {
    // Hash a file
    data, _ := os.ReadFile("file.txt")
    fp, _ := lavin.Hash(data)

    // Compare two files
    data2, _ := os.ReadFile("file2.txt")
    fp2, _ := lavin.Hash(data2)

    similarity := lavin.Compare(fp, fp2)
    fmt.Printf("Similarity: %d%%\n", similarity)
}

Production use cases

Malware detection & classification

Identify variants of known malware families by comparing samples. Polymorphic malware often retains 70–90% similarity despite obfuscation.

~85% detection rate for malware variants, <0.1% false positives.

File deduplication

Find near-duplicate files in large datasets. Unlike exact-match dedup, catches slightly modified versions (renamed variables, reformatted code).

Reduces storage by 40–60% in typical codebases.

Plagiarism detection

Detect copied code or documents with cosmetic changes. Resistant to identifier renaming, whitespace changes, and minor refactoring.

Detects 95%+ of paraphrased content.

Version control & change tracking

Determine if files are related versions. The delta detection feature shows exact changes between similar files.

Processes 1000s of files per second.

Worked example — malware variant detection

A complete classifier: hash the unknown sample, compare it against a fingerprint database, and treat scores at or above 70% as a family match.

malware classification · typescript
// Functional malware classification with monadic error handling
import { readFile } from 'fs/promises';
import { hash, compare } from 'lavinhash';

type MalwareFamily = { name: string; fingerprint: any; severity: string };
type ClassificationResult =
  | { type: 'match'; family: string; similarity: number; severity: string }
  | { type: 'unknown'; candidates: Array<{ family: string; similarity: number }> };

// Pure function composition
const classifyMalware = (database: MalwareFamily[]) =>
  (unknownSample: Uint8Array): ClassificationResult => {
    const unknownFP = hash(unknownSample);

    const matches = database
      .map(({ name, fingerprint, severity }) => ({
        family: name,
        similarity: compare(unknownFP, fingerprint),
        severity
      }))
      .sort((a, b) => b.similarity - a.similarity);

    const [best, ...rest] = matches;

    return best.similarity >= 70
      ? { type: 'match', ...best }
      : { type: 'unknown', candidates: matches.slice(0, 3) };
  };

// Database as immutable data structure
const malwareDB: MalwareFamily[] = [
  { name: 'Trojan.Emotet', fingerprint: fp1, severity: 'critical' },
  { name: 'Ransomware.WannaCry', fingerprint: fp2, severity: 'critical' },
  { name: 'Backdoor.Cobalt', fingerprint: fp3, severity: 'high' }
];

// Pipeline with async/await
const analyzeSample = async (path: string) => {
  const data = new Uint8Array(await readFile(path));
  const classifier = classifyMalware(malwareDB);
  return classifier(data);
};

// Usage with pattern matching
const result = await analyzeSample('suspicious.exe');
const message = result.type === 'match'
  ? `⚠️  ${result.family} detected (${result.similarity}%, ${result.severity})`
  : `Unknown sample (top: ${result.candidates[0].family} at ${result.candidates[0].similarity}%)`;
console.log(message);

Implementation details

Rust core library

core usage · rust
use lavinhash_core::{hash, compare, Config};
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Read files
    let data1 = fs::read("file1.bin")?;
    let data2 = fs::read("file2.bin")?;

    // Generate fingerprints
    let fp1 = hash(&data1, None)?;
    let fp2 = hash(&data2, None)?;

    // Compare
    let similarity = compare(&fp1, &fp2, None);
    println!("Similarity: {}%", similarity);

    // Custom configuration
    let config = Config::new()
        .with_alpha(0.4)  // Adjust structural weight
        .with_window_size(128);
    let fp3 = hash(&data1, Some(&config))?;

    Ok(())
}

Memory safety guarantees

Cross-platform determinism

Identical files produce identical fingerprints across all platforms:

note Determinism is achieved through explicit endianness handling and deterministic hash seeding.