Skip to main content

Independent verifier for GetProofAnchor evidence bundles

Project description

gpa-verify

Independent verifier for GetProofAnchor evidence bundles.

Reads ONLY the bundled ZIP — no network, no GetProofAnchor servers required. Same input → same verdict on every machine.

CI PyPI License: MIT


Why does this exist?

A GetProofAnchor evidence bundle is a .zip containing a screenshot, HTML, HAR, video, TLS chain, eIDAS qualified timestamp, OpenTimestamps Bitcoin anchor, and an append-only hash chain. The bundle claims:

This URL existed in this exact form at this exact time.

For court use, this claim must be independently verifiable — a forensic expert appointed by a court should be able to confirm or deny it without trusting GetProofAnchor.

gpa-verify is the reference implementation of that verification protocol. It runs offline, has no GetProofAnchor dependencies at runtime, and produces a deterministic verdict.

Install

pip install gpa-verify

Requires Python 3.9+. Two pure-Python wheels are installed automatically: asn1crypto (RFC3161 / CMS / X.509 parsing) and cryptography (RSA / ECDSA signature verification).

Use

gpa-verify path/to/GetProofAnchor_Evidence_*.zip
GetProofAnchor Evidence Verifier
────────────────────────────────────────────────────────────────
  Proof ID:       aeb2b103-d7c5-441c-b540-c0df600a34cf
  Bundle format:  getproofanchor-evidence-2
  Generated at:   2026-05-07T08:30:25Z

  [PASS]  bundle_integrity
          31/31 files OK

  [PASS]  cross_references
          5/5 cross-checks OK

  [PASS]  chain_integrity
          1 entries, all linked

  [PASS]  eidas_signature
          valid RFC3161 token, signed by CN=SK TIMESTAMPING UNIT 2026R,
          gen_time=2026-05-07T08:30:24+00:00

  [PASS]  anchor_canonical_hash
          canonical SHA matches manifest (968f0c691384c87c...)

  [PASS]  ots_receipt
          valid OTS proof for anchor SHA 968f0c69..., bitcoin status=pending

  [PASS]  tls_evidence
          leaf cert SHA-256 matches tls.json (e7668f38708d0411...)

────────────────────────────────────────────────────────────────
  ✓ VERIFIED  (7 passed, 0 failed, 0 skipped)

JSON output for automation

gpa-verify --json bundle.zip > report.json

Exit codes

Code Meaning
0 All checks passed
1 At least one check failed
2 Invalid arguments / cannot read ZIP

What it actually checks

Verification runs in seven layers, each tightening the forensic claim. All layers must pass for a VERIFIED verdict.

Layer 1 — Bundle integrity

Every file declared in manifest.json exists and its SHA-256 matches. Catches any byte-level tampering with the bundle.

Layer 2 — Cross-references

proof.json's SHA-256 claims for screenshot.png, page.html, content.txt, and capture/capture_meta.json agree with actual file contents. Sidecar *.sha256 files agree. Catches selective tamper that fixes one file but forgets another.

Layer 3 — Chain integrity

chain/proof_chain.jsonl entries form a valid hash chain: entry_hash == SHA256(prev_hash | event_type | proof_id | data_hash), recursively verified. chain/chain_head.json matches the last entry. Catches tampering with the append-only event log.

Layer 4 — eIDAS signature

This is the cryptographic heart of the verification. The RFC3161 token in timestamp/eidas.tsr is verified for:

  1. Status — token grant status is granted or granted_with_mods.
  2. Imprint — the message digest the TSA signed equals SHA256(timestamp/eidas_payload.json). Binds the timestamp to this evidence bundle, not someone else's.
  3. CMS signature — the SignedData blob is verified against the signer certificate's public key, with proper signed-attributes re-encoding ([0]SET OF per RFC 5652).
  4. timeStamping EKU — the signer certificate has the id-kp-timeStamping Extended Key Usage. Defends against substitution from a non-timestamping certificate.

Together these prove the token can ONLY have been issued by the named TSA, and ONLY for our exact payload.

Layer 5 — Anchor canonical hash

manifest.anchor.payload_sha256 equals SHA-256 of the canonical JSON of anchor/anchor_payload.json (sorted keys, no whitespace, UTF-8). Catches anchor receipt pointing to a different chain than ours.

Layer 6 — OTS receipt structure

anchor/anchor_receipt.ots is a valid OpenTimestamps proof and its file-hash field equals the anchor canonical SHA. Catches substituted Bitcoin anchors. (Full Bitcoin block confirmation requires a Bitcoin node and is out of scope; use the ots client for that.)

Layer 7 — TLS evidence

tls/leaf_cert.pem SHA-256 fingerprint matches the network/tls.json claim. Confirms the TLS leaf certificate stored in the bundle is the same one observed at capture time.

Threat model

gpa-verify defends against:

Attack Caught by
Modify any file (image, HTML, HAR, etc.) Layer 1
Modify file + manifest SHA Layer 2 (proof.json)
Modify chain entry Layer 3
Forge eIDAS payload + update all SHAs Layer 4 (imprint mismatch)
Substitute eIDAS token from non-TSA cert Layer 4 (EKU check)
Substitute anchor for a different chain Layer 5
Substitute OTS receipt Layer 6
Substitute leaf TLS cert Layer 7

It does NOT verify:

  • Whether the OpenTimestamps receipt has been confirmed in a Bitcoin block (use the ots client).
  • Whether the TSA's certificate chain validates against the EU Trusted List in real time (the bundle includes a snapshot of the EU Trusted List as supporting context, but online validation is out of scope).
  • Whether the captured page actually showed what the screenshot shows (this is a content question, not a cryptographic one — open the bundled capture.webm video for visual confirmation).

API

from gpa_verify import verify_evidence_zip

with open("evidence.zip", "rb") as f:
    report = verify_evidence_zip(f.read())

if report.all_passed:
    print(f"VERIFIED  proof {report.proof_id}")
else:
    for c in report.checks:
        if not c.passed and not c.skipped:
            print(f"FAIL  {c.name}: {c.detail}")

Building from source

git clone https://github.com/getproofanchor/gpa-verify.git
cd gpa-verify
pip install -e .[dev]
pytest

License

MIT — see LICENSE.

This tool is published as open source so any forensic expert, court-appointed examiner, or independent journalist can audit the verification logic line-by-line. The cryptographic algorithms used (RFC 3161, CMS / RFC 5652, X.509, SHA-256, RSA, ECDSA, OpenTimestamps) are open standards.

Standards & references

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gpa_verify-1.0.0.tar.gz (30.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gpa_verify-1.0.0-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file gpa_verify-1.0.0.tar.gz.

File metadata

  • Download URL: gpa_verify-1.0.0.tar.gz
  • Upload date:
  • Size: 30.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gpa_verify-1.0.0.tar.gz
Algorithm Hash digest
SHA256 4511ab41feb6cdaa665fa62be6fc1fd20365650595cd1329fc30dba8f888e6c7
MD5 ab78ca953c56f95cd7c271743db2f34e
BLAKE2b-256 e9cd7306bf73b7c23bf799dece59281ba0c3770f7504a88bcc526e6071c4b758

See more details on using hashes here.

Provenance

The following attestation bundles were made for gpa_verify-1.0.0.tar.gz:

Publisher: publish.yml on Getproofanchor/gpa-verify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gpa_verify-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: gpa_verify-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 17.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gpa_verify-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 04e1e7e31520f0d67a648e0cc4423b859e250a56da62ccd6318048a4dde969e5
MD5 6562570c6847dfab3945aa47f0e8a90f
BLAKE2b-256 310a0af71712aca5855979de735b99f86028be3c046b9834a0845aeb2224a575

See more details on using hashes here.

Provenance

The following attestation bundles were made for gpa_verify-1.0.0-py3-none-any.whl:

Publisher: publish.yml on Getproofanchor/gpa-verify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page