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 requests, no GetProofAnchor servers contacted at any point during verification. 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, the cryptographic part of that claim must be independently verifiable — a forensic expert appointed by a court should be able to confirm or deny it without relying on the GetProofAnchor service.

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

Scope of verification

This tool verifies the cryptographic seals on an evidence bundle. A successful verdict establishes that the bundle is internally consistent, cryptographically intact since sealing, and bound to a qualified electronic timestamp from an EU-accredited Trust Service Provider.

It does not, on its own, establish that the screenshot or HTML faithfully represent what an ordinary visitor would have seen at the captured URL — that question depends on the trustworthiness of the capture process and is supported (but not proved) by the bundled video recording, HAR network log, and TLS evidence. A complete forensic opinion combines this cryptographic verification with the visual and network record, and any contextual evidence available to the examiner.

See the Threat model section below for a precise statement of what each layer catches and what is out of scope.

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

Note on Python command name: On Linux and macOS the command is python3 and pip3; on Windows it is python and pip.

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.

Supported signature algorithms: RSA-PKCS1v15, ECDSA, RSA-PSS (across SHA-256, SHA-384, SHA-512 digests).

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 receipts 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 therefore 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 the following attacks:

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. This requires an online Bitcoin node — use the ots client.
  • Whether the TSA's certificate is currently listed on the EU Trusted List. The bundle includes a snapshot of the EU Trusted List as supporting context, but real-time validation against the live list is out of scope for an offline tool.
  • Whether the captured page actually showed what the screenshot shows. This is a content question, not a cryptographic one. The bundled capture.webm video, HAR network log, and TLS evidence exist to support that forensic judgement, but interpreting them is the job of a qualified examiner.

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

  • eIDAS Regulation (EU) 910/2014, Article 41 — qualified electronic timestamps and their legal effect across EU Member States
  • ETSI EN 319 422 — Time-stamping protocol and electronic time-stamp profiles
  • ETSI EN 319 421 — Policy and security requirements for trust service providers issuing time-stamps
  • ETSI EN 319 102-1 — Procedures for creation and validation of AdES digital signatures
  • ISO/IEC 27037:2012 — Guidelines for identification, collection, acquisition and preservation of digital evidence
  • RFC 3161 — Time-Stamp Protocol (TSP)
  • RFC 5652 — Cryptographic Message Syntax (CMS)
  • RFC 5280 — X.509 certificates
  • OpenTimestamps — Bitcoin-anchored timestamps
  • EU Trusted List — qualified TSPs

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.2.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.2.0-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: gpa_verify-1.2.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.2.0.tar.gz
Algorithm Hash digest
SHA256 d5eff0ebac7dd32bb66a24f6dc36f53d1714725b293e266a7902078fd52d4dea
MD5 d03f6003e6cb03d1cc7ffd0f7915c6e7
BLAKE2b-256 c9843035c4361fd856328620d121dfc11cda0c06f92516e46b081a690e3343db

See more details on using hashes here.

Provenance

The following attestation bundles were made for gpa_verify-1.2.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: gpa_verify-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 18.9 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3830d7bf5c4360b957f45ee295d717a34d8993330b6bb9f383426774c066edfb
MD5 eb9697343f53d1030ddd1cfc158d6928
BLAKE2b-256 b25f7da3d5c769c71f4e64406ddeb06163ec149cb1ddda631ea722eeda9ee878

See more details on using hashes here.

Provenance

The following attestation bundles were made for gpa_verify-1.2.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