CryptoValid Open Core
Evidence files that a third party verifies offline, years later, without trusting whoever wrote them: hash-chained JSONL ledgers, Ed25519 (optionally + ML-DSA-65) signatures, a signed chain tip, RFC 3161 timestamps, and verifiers in six languages that agree on the same conformance vectors.
The problem
Regulated records have to outlive the system that wrote them, and the reader is often someone who has no reason to trust that system:
- Manufacturers under the EU Cyber Resilience Act (Regulation (EU) 2024/2847) must keep the technical documentation at the disposal of market surveillance authorities for at least 10 years after the product is placed on the market, or for the support period if longer (Art. 13(13)); the Regulation applies from 11 December 2027, its reporting obligations (Art. 14) already from 11 September 2026 (Art. 71(2)). cra-evidence writes that evidence in this repository's ledger profile (see What cra-evidence relies on).
- Providers of high-risk AI systems must keep the logs their systems generate automatically (EU AI Act, Regulation (EU) 2024/1689, Art. 12(1)), to the extent the logs are under their control, for at least six months unless Union or national law provides otherwise (Art. 19); for Annex III systems those obligations apply from 2 December 2027 (Art. 113 as amended by Regulation (EU) 2026/1744).
- Auditors and supervisors receive a log, a database export or a vendor dashboard and have to decide whether it was altered, truncated or re-written. A hash chain alone cannot tell a shorter prefix from the whole file; a vendor's database asks to be trusted.
This repository is the format and the tools to check it: one stdlib-only command replays a ledger and either reaches the same hashes or names the entry where it breaks; a signed chain tip makes truncation and a re-written tail visible; an evidence pack bundles ledgers, digests, signatures and an optional RFC 3161 token for an auditor.
Demo (one minute, from a clone)
git clone https://github.com/robertolocatelli81-dev/cryptovalid-opencore && cd cryptovalid-opencore
python3 verifier.py --quiet examples/sample_ledger.jsonl # PASS, exit 0
python3 verifier.py --quiet examples/sample_ledger_tampered.jsonl # FAIL, exit 1: entry 1 was edited
pip install 'cryptography>=48' # only signatures need it
python3 signer.py keygen log.key # prints public_key_hex
cp examples/sample_ledger.jsonl ledger.jsonl
python3 cryptovalid_tip.py sign ledger.jsonl log.key # writes ledger.jsonl.tip.json
python3 verifier.py --quiet --trusted-pubkey <public_key_hex> --require-tip ledger.jsonl # PASS
head -n 2 ledger.jsonl > cut && mv cut ledger.jsonl # drop the last entry
python3 verifier.py --quiet --trusted-pubkey <public_key_hex> --require-tip ledger.jsonl # FAIL, exit 1
Without --quiet the verifier prints a JSON receipt: the last run names tail_truncated: file has 2 entries, the signed tip commits to 3, and every receipt carries a scope field that says what a bare chain cannot see.
Exit codes: 0 PASS, 1 FAIL, 2 usage, 74 the receipt could not be written whole (stdout or --out full, closed, or a
reader gone), whatever the verdict.
Proofs
- CI on every push and weekly (verify.yml): every
test_*.pysuite on Python 3.9 and 3.11, the conformance vectors, a real PKCS#11 token (SoftHSM2) signing a ledger end to end, a tampered ledger that must be refused, the declared dependency floors, and the verifiers in the other languages. - Verifiers in six languages, by the same author: Python (
verifier.py, the reference), JavaScript (verifiers/js), Go (verifiers/go, which also writes ledgers), Java (verifiers/java/CvVerify.java, JDK standard library only), Rust (verifiers/rust, no external crates for the hash chain) and Swift (verifiers/swift, reduced scope: no chain tip, no ML-DSA). The differential oracle (verifiers/differential_oracle.py) runs Python, JS, Go, Rust and Java on the same hostile inputs in CI and fails on any disagreement. - Conformance vectors:
spec/vectors/holds 11 normative ledger vectors with their expected receipts;python3 conformance.pyexits 0 only if the reference reproduces all 11 ("conformant": true, "vectors": 11). Signature vectors are inspec/vectors/signatures/. Implementing the format elsewhere means matching these. - Used downstream, measured in their CI: cra-evidence runs a job (
interop-newest-cryptovalid) that writes a ledger with a signed tip and requires the newest release of this package to verify it, and to fail it once truncated; ap2-evidence-pack runs a job (downstream-cryptovalid) that clones this repository and runs its AP2 front-end suites against each new build. Both were green on their last runs (2026-11-04 and 2026-11-05). - Archived on Zenodo, concept DOI 10.5281/zenodo.22539578.
Core and experimental modules
The maintained core is the evidence format and what cra-evidence relies on. Every other module at the top of the
repository stays in the repository and in the wheel, is still run by the same CI (every test_*.py;
fundcert_crosssource.py has no test of its own), and is marked experimental in its module docstring: no new
scope is added to it.
| Core | What it is |
|---|---|
SPEC_EVIDENCE_FORMAT.md §1-7, §10 |
ledger container, closed entry schema, canonical JSON and hashing, evidence payload conventions, verification algorithm, versioning, conformance, evidence-pack receipt |
verifier.py |
stdlib-only reference verifier (SHA-256 / SHA3-256 chains, strict JSON profile, signed tip check) |
cryptovalid_tip.py, ed25519_order.py |
signed chain tip (cryptovalid_tip/1); refusal of small- and mixed-order Ed25519 keys and R |
signer.py, cryptovalid_kms.py |
per-entry Ed25519 (+ optional ML-DSA-65) signatures; keys in a PKCS#11 HSM, AWS KMS, Vault Transit or a Nitrokey NetHSM |
cryptovalid_tsa.py |
RFC 3161 timestamp client and token check |
evidence_pack.py, verify_evidence.py (auto, pack, ledger) |
auditor bundle and the cryptovalid-verify front-end |
conformance.py, spec/vectors/, verifiers/ |
conformance vectors and the JS, Go, Java, Rust and Swift verifiers |
| Experimental | Modules |
|---|---|
| AP2 / SD-JWT dispute evidence (front-end to ap2-evidence-pack) | verify_evidence.py ap2, MCP tool verify_ap2_evidence |
| MCP server | cryptovalid_mcp.py |
| Receipts, monitor, checkpoints, witnesses, SCITT, COSE receipts, JWS | cryptovalid_receipt.py, cryptovalid_monitor.py, cryptovalid_checkpoint.py, cryptovalid_witness.py, cryptovalid_scitt.py, cryptovalid_cose_receipts.py, cryptovalid_jws.py |
| Merkle proofs (RFC 6962, SPEC §9) and high-frequency ingestion | cryptovalid_merkle.py, cryptovalid_ingest.py, verify_evidence.py archive |
| eIDAS: EU trusted list (LOTL), qualified-ledger self-assessment, QEAA | cryptovalid_lotl.py, eidas_ledger_check.py, cryptovalid_qeas.py |
| Anchoring and retention: Solana, OpenTimestamps, heterogeneous anchors, re-anchor, WORM, pruning probe | cryptovalid_solana.py, committed_attestation.py, cryptovalid_heterogeneous.py, cryptovalid_reanchor.py, cryptovalid_worm.py, cryptovalid_pruning_probe.py |
| Confidential attestations (CLDMA, Pedersen commitments) | cldma_confidential.py, pedersen_commit.py, verify_evidence.py cldma |
| Agent decision receipts of other formats (ACTA, APS, Cedulon, Vaara) | cryptovalid_acta.py, cryptovalid_aps.py, cryptovalid_cedulon.py, cryptovalid_vaara_receipt.py |
| Domain records: DORA incidents, IRS 1099-DA transactions, fund metrics, microfinance | dora_incident.py, tx_evidence.py, fundcert_canonical.py, fundcert_crosssource.py, microfinance.py |
| Auditor report (HTML/PDF) | cryptovalid_report.py |
| Regulatory profiles (SPEC §8) | refresh_regulatory.py, spec/regulatory_profiles.json |
| Development tooling | mutation_runner.py |
Each experimental module documents its use in its docstring; 21 of the 33 also print their usage with --help. The
release history is in CHANGELOG.md.
What cra-evidence relies on
Measured on cra-evidence f18097e (master, 2026-11-12): it declares no runtime dependency on this package
(dependencies = []) and writes the ledger and the tip itself. What it relies on is the profile:
- the ledger profile of SPEC §1-3 and §5 (canonical JSON,
prev_hash/self_hash, strict JSON acceptance, the 64 MiB line bound of the Go verifier); - the signed chain tip
cryptovalid_tip/1, byte-identical payload; verifier.verify_ledger(path)andverifier.verify_ledger(path, trusted_pubkey_hex=…, require_tip=True), which its interop test calls (PASS on a ledger it wrote, FAIL once truncated);- the parser and canonical encoder of
verifiers/go,verifiers/rustandverifiers/js, which its own verifiers reuse (its THIRD_PARTY_NOTICES.md names them).
It does not use regulatory_ref, the regulatory profiles, Merkle proofs, ingestion or any experimental module.
Signed evidence
python3 signer.py keygen signer.key # Ed25519 seed, mode 0600
python3 signer.py sign ledger.jsonl ledger.signed.jsonl signer.key
python3 verifier.py ledger.signed.jsonl # hash chain still PASS (stdlib only)
python3 signer.py verify ledger.signed.jsonl --pubkey <hex> # signatures PASS against the pinned key
The signature covers each entry's self_hash; signature / signer (and signature_pq / signer_pq) are excluded
from the content hash, so a signed ledger still passes the hash verifier unchanged. signer verify re-derives
self_hash from the content, so the chain checked is content → self_hash → signature. Without --pubkey the
receipt says classical_status: unpinned: the signatures were checked against the key the ledger carries.
Hybrid post-quantum (opt-in): keygen --pq also writes an ML-DSA-65 key (FIPS 204, PKCS#8); sign --pq-key
(or --pq-kms <AWS KMS key>) signs every self_hash with both keys; verify --pubkey <hex> --pq-pubkey <b64>
requires both layers. The FIPS 204 context is empty, so the JDK, OpenSSL 3.5 and Go verify the same bytes. The
ML-DSA-65 layer of entries and tip is checked by Python, Go (≥ 1.27) and Java (JDK 24+); JS, Rust and Swift report it
as unchecked.
Keys in an HSM or KMS (cryptovalid_kms.py): the signature is delegated to a backend where the key is not
exportable; the format and the verifier do not change.
export CRYPTOVALID_PIN=**** # PKCS#11 (SoftHSM2, YubiHSM 2, smartcard): PIN from the environment, never on argv
python3 cryptovalid_kms.py keygen-hsm --backend \
"pkcs11:module=/usr/lib/softhsm/libsofthsm2.so;token=cryptovalid;key=evidence;pin=env:CRYPTOVALID_PIN"
python3 signer.py sign ledger.jsonl ledger.signed.jsonl --backend "pkcs11:module=...;token=...;key=evidence"
python3 signer.py sign ledger.jsonl out.jsonl --backend "awskms:key_id=alias/cryptovalid;region=eu-south-1"
python3 signer.py sign ledger.jsonl out.jsonl --backend "vault:url=https://vault:8200;key=cryptovalid"
In CI, PKCS#11 is tested end to end against a real SoftHSM2 token, AWS KMS against its API contract (MessageType RAW,
ED25519_SHA_512) with an injected client, and Vault Transit against a local stub. Outside CI, on 2026-11-08,
signatures were measured against SoftHSM2 2.6.1, real AWS KMS (eu-central-1) and a Vault 1.20 dev server (logs not in
this repository); an ML-DSA-65 key in AWS KMS was verified live in 0.13.0. The Nitrokey NetHSM backend (nethsm:) is
exercised by test_kms.py against Nitrokey's public demo instance when it is reachable (an integration bench, never
key custody).
Evidence pack
python3 evidence_pack.py build pack_dir/ ledger.signed.jsonl --subject "audit CUST-001" --sign-key signer.key
python3 evidence_pack.py verify pack_dir/ # or: cryptovalid-verify pack pack_dir/
The pack holds the ledgers, a MANIFEST with per-file SHA-256, per-ledger hash and signature verdicts, signer keys,
entry count and head hash, an optional RFC 3161 token, and a human SUMMARY.md. verify returns valid: true only
if every digest matches, the manifest is self-consistent, every ledger passes its chain and its signatures, and a
claimed RFC 3161 token is not refuted; with --sign-key the manifest is authenticated (manifest_authenticated) and
a truncated ledger fails. The receipt states per subject what was checked and how it came out (admission,
states: SPEC §10), so a refused manifest is told apart from tampering.
Guarantees and assumptions
What a PASS means, and what it does not:
- Bare chain: internally consistent, unmodified entries in order. It cannot see truncation or a suffix
re-chained by someone with write access; the receipt's
scopefield says so. - Signed tip, with the trusted log key: truncation, a rewritten tail and unsealed appends become named FAILs
(
tail_truncated,tail_rewritten,unsealed_tail). A holder of the log key can still truncate and re-sign (key custody belongs in an HSM/KMS); a rollback to an older genuine tip passes unless--tip-not-beforeis given or the tip is compared with a copy kept elsewhere; when one key signs several ledgers,--expect-ledger-idbinds the tip to its chain. Without a trusted key the tip is not checked (tip_untrusted), never a PASS. - Signatures: prove which key sealed each entry; they count only against a key the relying party pins. A non-exportable key does not stop a compromised host from asking the HSM to sign; KMS policy, rotation and revocation are part of the perimeter.
- Time: entry timestamps come from the writer's clock. An RFC 3161 token proves the data existed at the TSA's
time; the evidence pack checks the token's status and its binding to the manifest, not the TSA's signature
(
time_anchor: tsa_not_checked). Between an event and its anchor, a root-level attacker on the writer can re-write and re-seal. - Completeness: an append-only log proves what it contains, never that everything that should have been recorded was.
- Post-quantum: Ed25519 and the RSA/ECDSA signatures of TSA tokens do not survive a quantum adversary; the ML-DSA-65 layer is opt-in and counts only when its key is pinned; the evidence-pack manifest signature is Ed25519.
- What it is not: not proof that the recorded facts are true, not an HSM, not a qualified trust service, not a conformity assessment or certification under any of the Regulations named above, and not legal advice.
The detailed threat analysis of each release is in CHANGELOG.md; key handling is in docs/KEY_LIFECYCLE.md.
Install (pip)
pip install --extra-index-url https://robertolocatelli81-dev.github.io/pypi/ cryptovalid-opencore
The hash-chain verifier needs only the standard library. Signing and signature verification (Ed25519, ECDSA,
ML-DSA-65, COSE, JWS, AP2) need cryptography (≥ 48: the floor where ML-DSA-65 works; measured with 48.0.0 and
50.0.x), installed with the sign extra:
pip install --extra-index-url https://robertolocatelli81-dev.github.io/pypi/ 'cryptovalid-opencore[sign]'
AP2 dispute evidence (experimental) needs the ap2 extra ('cryptovalid-opencore[ap2]'), which installs
ap2-evidence-pack >= 1.6.0, < 2 from the same index; without it cryptovalid-verify ap2 is a refusal that names the
extra (exit 1). Release artifacts are attached to GitHub Releases; the index links carry #sha256= fragments that
pip verifies. All documented python3 <file>.py commands also work from a clone.
Licence, contact, citation
- Licence: AGPL-3.0-or-later, see LICENSE. A commercial licence of the same code is available from the author.
- Contact: issues and Discussions of this repository (interoperability reports and divergences found by your own verifier are welcome); roberto.locatelli.81@gmail.com. Security reports: SECURITY.md.
- Citation: DOI 10.5281/zenodo.22539578 (concept DOI: always the latest version).
- Digital Public Good candidate for SDG 16 (accountable institutions) and SDG 9 (open infrastructure): see DPG_NOMINATION.md.
- Author: Roberto Locatelli, 2026. Public interventions by his AI agent (Noûs) are signed as such.
- Changes: CHANGELOG.md.
Metadata
Release files for cryptovalid-opencore 0.23.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 |
|---|---|---|---|---|
| cryptovalid_opencore-0.23.0.tar.gz | 298.6 kB | With provenance | Details for cryptovalid_opencore-0.23.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cryptovalid_opencore-0.23.0-py3-none-any.whl | Python 3 | none | any | Details for cryptovalid_opencore-0.23.0-py3-none-any.whl |
Total release size: 629.3 kB
Release files / cryptovalid_opencore-0.23.0.tar.gz
| Download URL | cryptovalid_opencore-0.23.0.tar.gz |
|---|---|
| Size | 298.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b20fd25df196363aa79f1932e07407f15413d726cd19ea8e0ad902e31bd2aed2
|
|
BLAKE2b-256 checksum How to use checksums |
25faa3041c9abbc8f222162a2c434af2429c8dd66cd108f11e2c06080b9f1f43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 13, 2026.
Transparency logRelease files / cryptovalid_opencore-0.23.0-py3-none-any.whl
| Download URL | cryptovalid_opencore-0.23.0-py3-none-any.whl |
|---|---|
| Size | 330.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
71f4b9128f880c201da88bc2b1c55fa780eedc5299e67976b8977108fdc0ee42
|
|
BLAKE2b-256 checksum How to use checksums |
b2d200613fb0334281604c5278cc2ced4181568394fa8a596d045d2d4a6d9b37
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 13, 2026.
Transparency logSecurity for cryptovalid-opencore 0.23.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
Both files in this release have been uploaded with provenance metadata.
Attestation for cryptovalid_opencore-0.23.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 Nov 13, 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.