fuzzbite
Fast fuzzy hashing and one-to-many fingerprint comparisons for Python, powered by ffuzzy and Rust.
Use ssdeep/CTPH fingerprints to find similar content. Hash a document once, then
reuse a Matcher to search batches of stored fingerprints. No Python runtime
dependencies.
Installation
Install from GitHub with uv:
uv add git+https://github.com/taranis-ai/fuzzbite.git
Source builds require Rust and a C linker. Supported platforms are Linux x86_64, Linux aarch64, and macOS arm64, with standard CPython 3.10–3.14.
Quick start
import fuzzbite
content = b"The river monitoring station reports water levels every morning. " * 20
fingerprint = fuzzbite.hash(content)
# Compare two fingerprints: scores range from 0 to 100.
score = fuzzbite.compare(fingerprint, fingerprint) # 100
# Find the first candidate scoring at least 90.
matcher = fuzzbite.Matcher(fingerprint)
match = matcher.find_match([fingerprint], threshold=90) # (0, 100)
Reuse the matcher across batches. Each result contains the candidate's index
within that batch and its score, or None if nothing meets the threshold.
Matching stops at the first qualifying candidate; it does not find the best match.
API
| Function | Returns |
|---|---|
hash(data: bytes) |
An ssdeep fingerprint string |
compare(left: str, right: str) |
An integer similarity score from 0 to 100 |
Matcher(fingerprint: str) |
A reusable comparison target |
matcher.find_match(candidates, *, threshold=90) |
(index, score) or None |
- Encode text to
bytesbefore hashing. - Pass candidates as a sequence of strings, such as a list or tuple. Generators are not accepted.
- Thresholds range from 0 to 100, inclusive. An empty batch returns
None. - Fingerprints must use
blocksize:first:second, with valid ssdeep block sizes and digest lengths of at most 64 and 32 characters. Invalid fingerprints raiseValueError; filename suffixes and long-form second digests are not supported. - Candidate types are checked for the entire batch. Fingerprint syntax is checked only up to the first match, so malformed strings after a match are not parsed.
Hashing and scoring release the GIL. Candidate batches are copied before scanning, so use bounded batches for large collections. Content normalization, minimum content length, and storage are handled by the caller. A score of 100 does not prove that the original content is identical.
Compatibility
fuzzbite uses ffuzzy's ssdeep scoring. Scores can differ from ppdeep, so test your similarity threshold before switching implementations.
The compatibility suite compares 46 inputs and 2,116 ordered pairs against ppdeep 20260221. Hashes agree for those inputs; scores differ for 96 pairs. For example, one edited article scores 90 with ppdeep and 83 with fuzzbite. ffuzzy also validates fingerprints more strictly than ppdeep.
Benchmarks
On the included synthetic comparison corpus, full scans with Matcher took
roughly 1.7–1.8× less time than a Python loop over fuzzbite.compare. Immediate
matches can be faster with a single comparison because batching copies candidates.
See benchmark results and methodology for timings, ppdeep comparisons, and workload details. To run the benchmarks:
uv run --locked python -m benchmarks.run --output benchmark-results.json
Development
git clone https://github.com/taranis-ai/fuzzbite.git
cd fuzzbite
uv sync --locked --python 3.14
After changing Rust code, rebuild the extension:
uv run --locked maturin develop --release --locked
Run checks:
cargo fmt --check
PYO3_PYTHON="$(uv python find)" cargo clippy --locked --all-targets -- -D warnings
PYO3_PYTHON="$(uv python find)" cargo test --locked
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked python -m pytest -q
Build and verify distributions:
uv build
uv run --locked python scripts/validate_artifacts.py
On Linux, set MATURIN_PEP517_ARGS='--compatibility linux' for both commands.
These local Linux wheels are platform-specific and cannot be uploaded to PyPI.
CI checks all supported Python versions plus the latest stable Python, and builds
on each supported platform. Workflows follow upstream action branches, latest uv,
and stable Rust. Ubuntu x86_64 and macOS use -latest runners; Ubuntu ARM uses
ubuntu-26.04-arm because GitHub does not provide a ubuntu-latest-arm label.
Publishing
The release workflow publishes a source archive and a macOS arm64 wheel when a GitHub release is published. Linux users build from source until portable Linux wheels are available.
Configure PyPI Trusted Publishing with these values:
| Field | Value |
|---|---|
| PyPI project name | fuzzbite |
| Owner | taranis-ai |
| Repository name | fuzzbite |
| Workflow name | release.yml |
| Environment name | pypi |
Create the pypi environment in the repository's GitHub settings and configure
required reviewers to approve uploads. No PyPI API token is needed. The workflow
must be committed before creating a release. Keep versions in pyproject.toml
and Cargo.toml aligned, and use a matching tag such as v0.1.0.
License
GPL-2.0-or-later. See third-party notices for dependency licenses.
Metadata
Release files for fuzzbite 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fuzzbite-0.1.0.tar.gz | 50.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fuzzbite-0.1.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 312.2 kB
Release files / fuzzbite-0.1.0.tar.gz
| Download URL | fuzzbite-0.1.0.tar.gz |
|---|---|
| Size | 50.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6a3541bacef95c0435c643da1c13b5595f767867eb90d26a6a6dbc823ef20f18
|
|
BLAKE2b-256 checksum How to use checksums |
dd62544c4cb0a4ea002617fb150e964e66e311ad528ccff019decc08c299d51b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / fuzzbite-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | fuzzbite-0.1.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 261.7 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
f03424b6b2db145d64ea64cd0b9bca9df8578fd0a88aabdedf3a2a58fc8dc5c5
|
|
BLAKE2b-256 checksum How to use checksums |
260ad6bbea7f4547116984ea51818939786ac577166b090f9c0fe7802147f850
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log