Skip to main content

CryptoValid Open Core

DOI verify-evidence

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_*.py suite 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.py exits 0 only if the reference reproduces all 11 ("conformant": true, "vectors": 11). Signature vectors are in spec/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) and verifier.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/rust and verifiers/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 scope field 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-before is given or the tip is compared with a copy kept elsewhere; when one key signs several ledgers, --expect-ledger-id binds 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)

Source distribution for cryptovalid-opencore 0.23.0
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)

Table of built distributions (wheels) for cryptovalid-opencore 0.23.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.23.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page