evidencevault-verify
Offline, dependency-light verification of EvidenceVault certificates — for third
parties, auditors, and courts. pip install evidencevault-verify gives you a CLI and a
Python library that verify a certificate's cryptographic and structural integrity using
only its public artifacts, with zero network access.
This is a faithful Python port of the EvidenceVault TypeScript verifier
(tools/cert-verify + packages/verify-core + packages/shared). It produces
byte-identical canonicalization and hashes and the same pass/fail verdicts as the
reference cert-verify tool — proven by a cross-validation test suite that diffs the two
verifiers against real deployed certificates.
What it verifies
For each certificate, exactly the checks the TypeScript verifier reports:
| Check | Meaning |
|---|---|
identityMatches |
The payload's cert_id / issuing_tenant_id match what you asked for |
canonicalHashMatches |
RFC 8785 JCS canonicalization of the payload → SHA-256 → unpadded base64url equals signatures.canonical_payload_sha256 |
ed25519Valid |
Ed25519 signature over the canonical payload bytes (key resolved through the tenant registry, incl. time-bounded agent subkey certificates) |
mlDsa65Valid |
ML-DSA-65 (FIPS 204) post-quantum co-signature |
slhDsaSha2128fValid |
SLH-DSA-SHA2-128f (FIPS 205) post-quantum co-signature |
merkleInclusionValid |
RFC 6962 inclusion proof (0x00 leaf / 0x01 node) binds the claimed leaf to source_evidence_merkle_root — null when no proof is present |
withinValidity |
issued_at <= now < expires_at |
dualSignatureValid |
Licensee co-signature for dual-signed classes: licensee credential present, platform-certified, verified and in-window — null when neither present nor required, fail-closed when required but missing |
predicatesValid |
Every declared predicate certificate exists, has status issued, and is within validity — fail-closed when predicates are declared but unresolved |
otsAnchorPresent / otsProofFound |
An OpenTimestamps .ots proof is present and non-empty (Bitcoin confirmation is delegated to the ots CLI via --ots-cli) |
A certificate is ok only when all mandatory checks pass and no optional check is false
— the identical conjunction the TypeScript verifier uses.
Both post-quantum families are required (a break in one family cannot forge a
certificate on its own), and both signatures were produced by
@noble/post-quantum (FIPS 204 / FIPS
205 final). See Post-quantum interoperability below.
Install
pip install evidencevault-verify
Requires Python 3.10+. Dependencies (all pure-Python or pip-wheel, no native OQS build):
cryptography (Ed25519), dilithium-py (ML-DSA-65), slh-dsa (SLH-DSA-SHA2-128f).
CLI
# Verify from a directory of public artifacts
evidencevault-verify --cert-id cert_01KY7HNX7T89K8HNNC17PBK3YD \
--tenant-id tnt_demo \
--dir ./bundle
# Verify from a self-contained bundle (base64-embedded files)
evidencevault-verify --cert-id <id> --tenant-id <tnt> --bundle ./bundle.json
# Machine-readable output
evidencevault-verify --cert-id <id> --tenant-id <tnt> --dir ./bundle --json
# Also run Bitcoin-tier OpenTimestamps confirmation (requires `ots` on PATH)
evidencevault-verify --cert-id <id> --tenant-id <tnt> --dir ./bundle --ots-cli
Output (byte-identical to the reference cert-verify):
✓ cert_01KY7HNX7T89K8HNNC17PBK3YD — {"tenantRegistryFound":true,...,"otsProofFound":true}
Exit codes: 0 = valid (cryptographically sound and within the reliance window),
2 = cryptographically sound but expired / not-yet-valid, 1 = cryptographically or
structurally invalid. (Argument/usage errors — including a malformed --now/EV_VERIFY_NOW
timestamp — also exit 2, per the argparse convention.) The verdict splits
cryptographicallyValid (clock-independent) from validity (valid | expired | not-yet-valid); pass --now <RFC3339> or set EV_VERIFY_NOW to override the clock.
Library API
from evidencevault_verify import verify_certificate, DirSource
result = verify_certificate(DirSource("./bundle"), "cert_01KY7HNX...", "tnt_demo")
print(result.ok) # overall verdict
print(result.checks.ed25519_valid) # individual checks (snake_case attributes)
print(result.checks.ml_dsa65_valid)
print(result.checks.slh_dsa_sha2_128f_valid)
print(result.checks.to_ts_dict()) # exact camelCase check map the CLI prints
for err in result.errors:
print(err)
Convenience wrappers: verify_certificate_from_dir(base_dir, cert_id, tenant_id) and
verify_certificate_from_bundle(bundle_path, cert_id, tenant_id). Pass now=<datetime>
to any of them to verify against a fixed time instead of the current clock.
verify_certificate accepts any object with a read(relative_path) -> bytes | None
method (the ArtifactSource protocol), so you can back it with a directory
(DirSource), an in-memory bundle (BundleSource), or your own storage.
Artifact layout
Identical to the reference cert-verify. In --dir mode the base directory contains:
public/tenants/<tenant_id>.json # tenant public key registry (trust root)
public/certificates/<cert_id>.json # existence records (for predicate resolution)
certificates/<cert_id>/payload.json # the signed certificate payload
certificates/<cert_id>/signatures.json # Ed25519 + hybrid PQC signature block
certificates/<cert_id>/merkle-proof.json # optional RFC 6962 inclusion proof
certificates/<cert_id>/cert.ots # optional OpenTimestamps proof
In --bundle mode a single JSON file carries every file above as base64-encoded bytes.
Both modes contact no network and read no EvidenceVault infrastructure.
Post-quantum interoperability
The load-bearing risk in a Python port is whether a Python PQC library can verify
signatures produced by @noble/post-quantum. It can — proven against the real
signatures on the shipped campaign certificates:
- ML-DSA-65 →
dilithium-py(pure-Python FIPS 204).ML_DSA_65.verify(pubkey, msg, sig)accepts @noble's signature and rejects a tampered message. - SLH-DSA-SHA2-128f →
slh-dsa(pure-Python FIPS 205). Verified with the pure variant and an empty context string (PublicKey.from_digest(pubkey, slhdsa.sha2_128f).verify_pure(msg, sig)), which is what@noble'sslh_dsa_sha2_128f.signproduces. Note: the pre-hashedverifyvariant does not accept these signatures — pure is required.
Both libraries are pure-Python (no WASM, no native liboqs build), which keeps the verifier
portable and easy to audit. The interop is asserted for every campaign certificate in
tests/test_pqc_interop.py.
Note on
quantcryptandliboqs:quantcryptwas evaluated first but on some platforms it has no pre-built wheel and its source build JIT-downloads PQClean from GitHub at runtime — unsuitable for an offline court tool.liboqs/oqsrequires a CMake/C build of liboqs. The pure-Pythondilithium-py+slh-dsapair verifies the same@noblesignatures with none of that friction, so it is the chosen dependency set.
Cross-validation (the correctness proof)
tests/test_cross_validation.py runs the Python verifier and the reference TypeScript
cert-verify against the same real certificates and asserts their check maps are
equal, field-for-field. Where a Node toolchain is present the TS verifier is run live
and diffed; the pure-Python assertions (all-green verdict, canonical-hash parity, PQC
interop, fail-closed tampering) run unconditionally.
pip install -e ".[test]"
pytest
For the operator: publishing to PyPI
The package is ready to publish. This repository does not publish it (that needs the operator's PyPI credentials). To publish:
cd tools/evidencevault-verify-py
python -m build # produces dist/*.tar.gz and dist/*.whl
twine upload dist/* # requires your PyPI API token
Run twine check dist/* first to validate the metadata.
License
MIT — see LICENSE.
Metadata
Release files for evidencevault-verify 2.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| evidencevault_verify-2.0.1.tar.gz | 42.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| evidencevault_verify-2.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 76.3 kB
Release files / evidencevault_verify-2.0.1.tar.gz
| Download URL | evidencevault_verify-2.0.1.tar.gz |
|---|---|
| Size | 42.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a98cdfca7b4b35e8d1602c5e416cbcf6360bb27d69ffd82e866c282cdcc9ef7f
|
|
BLAKE2b-256 checksum How to use checksums |
b3fb6714fc9605b68a343d04a5819df20ef6aa158af86c8ff500493588f99a09
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / evidencevault_verify-2.0.1-py3-none-any.whl
| Download URL | evidencevault_verify-2.0.1-py3-none-any.whl |
|---|---|
| Size | 34.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5753e51d7f0545f10eda7c1c0e4a2f86e8db6ae27d415cf2713fe8a68fa0a6ea
|
|
BLAKE2b-256 checksum How to use checksums |
5d276a544f10d97891f66cb3e90179a68284034abdd65aec8a4ee750862ad9dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|