matrym-hashchain
A tamper-evident, append-only, hash-chained ledger. Dependency-free (stdlib only).
Each entry carries a SHA-256 over its own payload and the previous entry's hash, so any later edit, reorder, or deletion of a past record is detected the next time the log is read. It's a file-simple, zero-dependency primitive for audit trails, transaction logs, and chain-of-custody records - the kind of thing a compliance, finance, or records system needs and shouldn't hand-roll.
Install
pip install matrym-hashchain
Released to PyPI via trusted publishing (GitHub OIDC, no stored token).
Use
from pathlib import Path
from hashchain import append, read, verify, HashChainError
ledger = Path("audit.jsonl")
append(ledger, {"event": "created", "who": "alice"})
append(ledger, {"event": "approved", "who": "bob"})
for entry in read(ledger): # read() verifies the whole chain as it goes
print(entry.seq, entry.payload)
verify(ledger) # True -- the history reads clean end to end
# If anyone edits, reorders, or deletes a past record...
ledger.write_text(ledger.read_text().replace("alice", "mallory"))
verify(ledger) # False
read(ledger) # raises HashChainError: record 0 was tampered ...
The store is plain JSON Lines (.jsonl) - one record per line, human-readable, greppable, and
portable. No database, no daemon, no dependencies.
Command line
The same three verbs, from one terminal line (installed as matrym-hashchain, or run
python -m hashchain):
matrym-hashchain append audit.jsonl '{"event": "created", "who": "alice"}'
matrym-hashchain append audit.jsonl '{"event": "approved", "who": "bob"}'
matrym-hashchain read audit.jsonl # 0<TAB>{"event": "created", ...}
matrym-hashchain verify audit.jsonl # "clean: 2 record(s), head e7c1f28a3595"; exit 0
verify exits 0 on a clean chain and 1 on a broken one, naming the tampered record on stderr,
so it drops straight into a shell gate or a CI step. append also reads the payload object from
stdin when you omit it (... | matrym-hashchain append audit.jsonl).
Signing and truncation work from the CLI too. The HMAC key is read from an environment variable (never the command line, so it can't leak into the process table or shell history):
export LEDGER_KEY='a secret only writers hold'
matrym-hashchain append audit.jsonl '{"event": "created"}' --key-env LEDGER_KEY
matrym-hashchain verify audit.jsonl --key-env LEDGER_KEY # exit 0; without it -> exit 1
anchor=$(matrym-hashchain head audit.jsonl --key-env LEDGER_KEY) # stash off-ledger
matrym-hashchain verify audit.jsonl --key-env LEDGER_KEY --expected-head "$anchor" # catches a dropped tail
What it guarantees (and what it doesn't)
This proves integrity by default, and closes the two classic gaps with two optional features:
| Attack | Detected? |
|---|---|
| A payload was edited | ✅ content-hash mismatch |
| Records were reordered or one was inserted | ✅ sequence / prior-hash mismatch |
| A middle record was deleted | ✅ the next record's prior-hash no longer links |
| The last record(s) were dropped | ✅ with an anchor - stash head_hash(path) elsewhere (a receipt, a second store, a witness) and check verify(path, expected_head=...); the chain alone can't see truncation |
| Who wrote a record (authenticity) | ✅ with a signing key - pass key=... to sign records with HMAC-SHA256; only a key-holder can produce or validate them |
Any break fails loud with HashChainError rather than returning a dishonest history.
Signing and truncation, honestly
KEY = b"a secret only writers hold"
append(ledger, {"event": "created"}, key=KEY) # HMAC-signed
verify(ledger, key=KEY) # True; verify(ledger) alone -> False (no key)
anchor = head_hash(ledger, key=KEY) # stash this off-ledger
# ... later, after someone drops the last record ...
verify(ledger, key=KEY, expected_head=anchor) # False -- the tail is gone
Honest label: HMAC is a symmetric shared-secret MAC. It proves a record was written by a holder
of the key; it is not public-key non-repudiation (anyone with the key can both sign and verify).
The same key must be passed to every append/read/verify/head_hash over a signed ledger.
One more honest bound: this is a single-writer primitive. append is not guarded against two
processes writing the same ledger at once (no file lock), so concurrent appends can interleave.
Serialize writers (one process, or your own lock) if that's a risk; read/verify are read-only
and safe alongside a single writer.
Performance
append is O(1) in ledger size: it reads only the ledger's tail (a backward seek), not the
whole chain, so appends stay fast as the log grows without bound. That claim ships with evidence,
not just an assertion, measured head to head against the pre-0.1.1 O(n) behavior:
| ledger size | O(n) baseline | O(1) current | speedup |
|---|---|---|---|
| 500 | 7.15 ms | 0.08 ms | 93x |
| 64,000 | 930.52 ms | 0.08 ms | 11,941x |
Across a 128x ledger growth the old append slowed 130x (linear, ~1 s per write at 64k records);
the current one stayed flat at ~0.08 ms. Full method, table, and reproduction:
benchmarks/RESULTS.md (make bench).
API
All functions take an optional keyword-only key: bytes (sign/verify with HMAC; omit for plain
integrity).
append(path, payload, *, key=None) -> Entry- validate, hash-chain, and append one record. Reads only the tail (O(1)), validating that record before extending, so you can't quietly append onto a tampered tail.read(path, *, key=None) -> list[Entry]- read every record, verifying as it goes. Empty/missing store ->[].verify(path, *, key=None, expected_head=None) -> bool-Trueif the ledger reads clean end to end; passexpected_headto also catch truncation.head_hash(path, *, key=None) -> str- the last record's content hash (orGENESISif empty); the value you anchor off-ledger to detect truncation.content_hash(mapping, *, key=None) -> str- the canonical digest the chain uses: SHA-256 unkeyed, HMAC-SHA256 when akeyis given (sorted keys, no whitespace).Entry(seq, payload, prior_hash, content_hash)- one link;HashChainError- the loud failure.
Test
pip install -e ".[dev]" # pytest, ruff, mypy, coverage, bandit, pip-audit
make check # ruff + mypy --strict + pytest at 100% coverage (the full gate)
make security # bandit SAST + pip-audit the dependency closure (proves "zero deps")
make bench # the O(1)-append benchmark
The suite pins both acceptance and refusal: a clean chain reads end to end and verify
returns True, while every tampered case (an edited payload, a reorder, a deleted middle
record, a mislinked record with a valid own hash, an append onto an already-broken log) fails
loud with HashChainError rather than returning a dishonest history. CI runs it on every push
across Python 3.10-3.13, at 100% line + branch coverage, and audits the dependency closure.
Evaluation
Scored by forge-audit - the fleet's proof-tool, which
runs the gates on a target repo behind a mockable GitHub-API seam and emits a pass|watchlist|fail
scorecard. This part passes at the advanced stage (a snapshot; reproduce with
forge-audit --path . --stage advanced --online):
| Dimension | Verdict | Evidence |
|---|---|---|
| lint | ✅ pass | clean |
| typecheck | ✅ pass | clean |
| tests | ✅ pass | green suite, coverage 100% ≥ 85% |
| security | ✅ pass | bandit + pip-audit clean |
| dependencies | ✅ pass | clean |
| ci | ✅ pass | 3 CI workflows (check, release, CodeQL) |
| collaboration | ✅ pass | 9 merged PRs |
| performance | ✅ pass | benchmark artifact present |
| readme | ✅ pass | covers purpose, install, run, test |
Overall: pass (advanced). Role signals: testing, security, backend, devops, collaboration,
performance, documentation.
Provenance
This is a MatrymLabs Hardware Store part: a pattern first proven in the CodeForge MUD engine (the "Chronicle" - the game's tamper-evident memory), then reused across the fleet (a federal guidance-check ledger; an AI-triage decision audit trail). Published standalone so any project can reuse the same tested primitive - one well-made part, many jobs.
License
MIT
Metadata
Release files for matrym-hashchain 0.3.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 | Provenance | Actions |
|---|---|---|---|---|
| matrym_hashchain-0.3.0.tar.gz | 19.8 kB | With provenance | Details for matrym_hashchain-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| matrym_hashchain-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for matrym_hashchain-0.3.0-py3-none-any.whl |
Total release size: 33.3 kB
Release files / matrym_hashchain-0.3.0.tar.gz
| Download URL | matrym_hashchain-0.3.0.tar.gz |
|---|---|
| Size | 19.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f81935fbd9fd26c40e1939cb896761aa6f26de9ebb25de394281927f6e72b200
|
|
BLAKE2b-256 checksum How to use checksums |
6aa14dec193f823d595222ba5e24edf5d1ad639f67a6e20092ca712fb80e47ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jul 17, 2026.
Transparency logRelease files / matrym_hashchain-0.3.0-py3-none-any.whl
| Download URL | matrym_hashchain-0.3.0-py3-none-any.whl |
|---|---|
| Size | 13.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9624f24bccd5619abc8a087e1f1c8b1f0f7c3e8813774f98c8cea0340e70d506
|
|
BLAKE2b-256 checksum How to use checksums |
2be4bd5c5f6d29e66a31a9238518e976f1665c6a898a7d6ad5eff4078f803689
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jul 17, 2026.
Transparency logSecurity for matrym-hashchain 0.3.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for matrym_hashchain-0.3.0.tar.gz
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 Jul 17, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.