FRX-native hash primitives — algebraic permutations and byte hashes, each written to lower to a single fused kernel.
hash-frx sits between FRX — Fractalyze's fork of
JAX — and everything that hashes: the proving
blocks in zorch, the signature schemes in
sig-frx, and any other FRX consumer. FRX provides tracing and codegen, lowered
through Fractalyze XLA, its fork of stock XLA
that adds native field and elliptic-curve types.
- Two seams, no concrete hash in the consumer.
Permutationis a fixed-width permutation over a single dtype — a field one for the algebraic hashes (Poseidon, Poseidon2), a machine word for the bit-oriented ones (Keccak-f[1600]);ByteHashmaps a batch of equal-length byte messages to digests (SHA-256, BLAKE3, SHA-3). A consumer readswidth/dtypeordigest_sizeand callspermute/digest— it never names the hash it runs on. - Primitive / extension / adapter. A primitive is one of those two seams.
An extension is a schedule that turns a primitive into a hash — Merkle–Damgård,
a sponge, a tree — written once per construction rather than once per family. An
adapter (HMAC, HKDF, MGF1, PBKDF2) reads
digestand nothing below it. So a new family costs a round function and its constants, not a vertical. - Fusion by construction. A permutation call, a digest call, and each
sponge
absorb/squeezelower to one fused kernel by construction — alax.compositemarker an XLA emitter recognizes — never by a per-primitive compiler pattern-match. - Application-agnostic. No proving scheme, signature scheme, or blockchain leaks in. Domain separation, parameter choice, and padding conventions belong to the consumer.
- Byte-exact with the standard. A byte hash reproduces its specification exactly (SHA-256 = FIPS 180-4, BLAKE3 = the BLAKE3 spec, SHA-3/SHAKE = FIPS 202), verified against the published test vectors.
Releases are on PyPI, so nothing else has to be configured:
pip install hash-frxDev builds also publish to the Fractalyze package index on every green build of
main. They are timestamped X.Y.Z.devYYYYMMDDHHMMSS and exist so a consuming
workspace can pin the exact build it tested against, which means naming both the
index and the version:
pip install hash-frx==0.1.0.dev20260730045722 \
--extra-index-url https://fractalyze.github.io/pypi/simple/Both carry the runtime tree only — the testing/ packages, including the
reference fixtures, are not package API.
A Bazel consumer takes the module directly and needs no wheel:
bazel_dep(name = "hash_frx", version = "0.0.0")
git_override(
module_name = "hash_frx",
commit = "<sha>",
remote = "https://github.com/fractalyze/hash-frx.git",
)Bootstrapping. The symmetric layer is being extracted from zorch/hash and
extended with BLAKE3 and the Keccak family; zorch then consumes this repo.
Work is tracked on the issues.
Python 3.11. frxlib publishes cp311 wheels only, and both the hermetic
Bazel toolchain and .python-version pin that version.
Bazel is the build, and the whole suite is one command — the same one CI's CPU leg runs:
bazel test //...Tests are backend-agnostic and default to CPU (.bazelrc sets
FRX_PLATFORMS=cpu), so a plain run is deterministic on any machine. Run them
on the device to exercise the fusion markers — that leg is the only one that
reports a lost marker, because an unrecognized marker still produces the right
bytes:
bazel test --test_env=FRX_PLATFORMS=cuda \
--test_env=XLA_PYTHON_CLIENT_PREALLOCATE=false //...cuda is strict — there is no CPU fallback — so a green run really did execute
on the device. Preallocation is off because Bazel runs the test actions
concurrently against the one device, and each process would otherwise claim most
of its memory.
For interactive work outside Bazel, the same pinned toolchain in a virtualenv:
python3.11 -m venv .venv && . .venv/bin/activate
pip install -r requirements.in \
--extra-index-url https://fractalyze.github.io/pypi/simple/The extra index carries the frx builds and the CUDA plugin wheels, which are
too large for PyPI's per-file limit. requirements.in holds the pins;
regenerate the lock with bazel run //:requirements.update instead of editing
it by hand.
Install the git hooks with both stages named. Plain pre-commit install wires
only the pre-commit stage, which leaves the commit-message linter inactive —
formatting hooks fire while a malformed commit message sails through to CI:
pre-commit install --install-hooks --hook-type pre-commit --hook-type commit-msgCommit messages follow Conventional Commits:
a valid type, a lowercase summary with no trailing period, a
header of at most 80 characters, and a body on everything but docs. Scope is
free-form. The same linter runs in CI over the pull request title and every
commit in it.
The rest of the dev loop — backend selection, the CUDA version the GPU path
requires, running against an unreleased Fractalyze XLA, and the compile-cache
rule — is in
docs/reference/development.md.
- Task-indexed hub:
docs/README.md— indexes the seams, constructions, and implementations by what you are trying to do, and states the fusion contract they all share. - Contributing with Claude Code:
CLAUDE.md— the same map, plus the two rules every change must respect.
Licensed under the Apache License, Version 2.0 (see LICENSE).