Cross-platform cryptographic hashing of EDN data structures for Clojure and ClojureScript.
hasch produces deterministic, content-based hashes for EDN values. Commutative data structures (maps, sets, records) hash identically regardless of iteration order, preserving Clojure value semantics. Hashes are consistent across JVM and JavaScript runtimes.
Two hashing modules are provided with different strengths:
hasch.core— string-based encoding with transparent structural hashing viaHashRef. Replace any subtree with itsHashRefand the parent hash stays identical — true merkle-style incremental hashing. Best for content-addressed storage, large nested structures, and cases where subtrees change independently.hasch.fast— binary-encoded streaming digest, ~6-14x faster per-value throughput. Best for hashing many independent values (UUID generation, deduplication, signatures).
Both modules support all EDN types and incognito record serialization. They produce different hash values and should not be mixed for the same data.
Add to deps.edn:
org.replikativ/hasch {:mvn/version "0.3.97"}(require '[hasch.core :as hc])
;; Generate UUID-5 from any EDN value
(hc/uuid {:name "Alice" :age 30})
;=> #uuid "..."
;; Transparent HashRef — same hash with or without:
(= (hc/uuid {:data [1 2 3]})
(hc/uuid {:data (hc/hash-ref [1 2 3])})) ;=> true
(hc/edn-hash [1 2 3])
;=> (120 75 53 ...) ;; seq of unsigned bytes
(hc/b64-hash "hello")
;=> "..."(require '[hasch.fast :as hf])
;; Same API, ~6-14x faster, different hash values
(hf/uuid {:name "Alice" :age 30})
;=> #uuid "2c6ea7f3-..."
;; Random UUID-4
(hf/uuid)
;=> #uuid "a27dfbb9-..."
;; Raw hash bytes
(hf/edn-hash [1 2 3])
;=> #object["[B" ...]
;; Hex string / Base64
(hf/hash->str (hf/edn-hash "hello"))
(hf/b64-hash "hello")
;; SHA-256 variant (faster, still cryptographically secure)
(hf/sha256-uuid {:data "value"})
;; Sequential UUIDs (time-prefixed, sortable)
(hf/squuid)| hasch.core | hasch.fast | |
|---|---|---|
| Encoding | String (pr-str of numbers) | Binary (direct byte layout) |
| Digest | Intermediate byte arrays per value | Streaming (update MessageDigest in-place) |
| Per-value speed | Baseline | ~6-14x faster |
| Structural hashing | Transparent HashRef |
— |
| Best for | Content-addressed stores, merkle trees, incremental rehashing | UUID generation, deduplication, high-throughput hashing |
For large nested structures where subtrees change independently, hasch.core with HashRef can be faster end-to-end than hasch.fast, because unchanged subtrees are never re-traversed.
hasch.core supports transparent merkle-style structural hashing via HashRef. A HashRef caches the coercion output of a value so that when it appears inside a larger structure, it produces the same hash as the original value — no re-traversal needed.
(require '[hasch.core :as hc])
;; These produce identical hashes:
(hc/uuid {:data [1 2 3]})
(hc/uuid {:data (hc/hash-ref [1 2 3])}) ;; same! transparent substitution
;; Even edn-hash of a HashRef equals the original:
(= (hc/edn-hash x) (hc/edn-hash (hc/hash-ref x))) ;; true for all xThis is useful for content-addressed storage where you want to:
- Hash large trees incrementally (only rehash the changed path)
- Avoid pulling entire subtrees into memory just to compute a parent hash
- Build git-like persistent data structures
;; Hash subtrees once, reuse in parent structures
(def ref-a (hc/hash-ref {:name "Alice" :scores [1 2 3]}))
(def ref-b (hc/hash-ref {:name "Bob" :scores [4 5]}))
(hc/uuid {:users [ref-a ref-b]})
;; When sub-a changes, only rehash the changed subtree:
(def ref-a' (hc/hash-ref {:name "Alice" :scores [1 2 3 4]}))
(hc/uuid {:users [ref-a' ref-b]}) ;; ref-b reused as-isNote: HashRef is only available in hasch.core. The streaming architecture of hasch.fast does not support transparent substitution.
Records are hashed via incognito, which normalizes JVM class names to a platform-neutral format (my.ns.MyRecord → my.ns/MyRecord). This ensures records hash identically across JVM and ClojureScript.
(require '[hasch.fast :as hf])
;; Records with default serialization work automatically
(defrecord Person [name age])
(hf/uuid (->Person "Alice" 30))
;; Custom write-handlers for non-default serialization
(hf/edn-hash (->Person "Alice" 30)
hf/sha512-message-digest
{'my.ns.Person (fn [r] [(:name r)])})Clojure's built-in hash is optimized for speed in data structures (murmur3, 32-bit) and intentionally trades collision resistance for performance. This is fine for hash maps but unacceptable for content-addressed systems where collisions could corrupt data. hasch uses SHA-512, providing cryptographic collision resistance suitable for distributed systems and untrusted environments.
Extend hasch.fast/PStreamHash (or hasch.benc/PHashCoercion for hasch.core) to add hashing for your own types. However, in most cases it is simpler to make your type serializable via incognito.
Your implementation must satisfy the equality relation:
(= a b)⟹(= (edn-hash a) (edn-hash b))(not= a b)⟹(not= (edn-hash a) (edn-hash b))
# Run JVM tests
clj -M:test
# Run CLJS tests (requires Node.js)
npx shadow-cljs compile node-test
# Build JAR
clj -T:build jar
# Install locally
clj -T:build install
# Deploy to Clojars
clj -T:build deploy- 0.3.97 Add
hasch.fastmodule with binary streaming digest (~6-14x speedup). AddHashReffor merkle-style structural hashing. Add incognito support tohasch.fast. - 0.3.5 Support BigInteger and BigDecimal hashing (same as for limited precision types).
- 0.3.4 Expose high-level base64 hashes with full precision.
- 0.3.2 Minimize dependencies, explicit profiles for different Clojure(Script) versions
- 0.3.1 fix bug in hashing sequences containing null
- 0.3.0 fix accidental hashing of records as maps
- 0.3.0-beta4 fix record serialization with incognito
- 0.3.0 Overhaul encoding for ~10x-20x times performance on the JVM. Use safe SHA-512. Add byte-array support for blobs.
- Max Penet
- James Conroy-Finn
- Konrad Kühne
- Christian Weilbach
Copyright © 2014-2026 Christian Weilbach and contributors
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.