actaseal-verify
Verify an ActaSeal dispute evidence packet yourself, in about 60 seconds,
without installing ActaSeal, without a network call, and without trusting
us. This repo is one script (verify.py) plus a spec.
Who this is for: an underwriter, dispute analyst, or auditor who has been
handed a packet (a .zip) and wants to check, independently, that:
- the evidence chain hasn't been tampered with (every event's hash recomputes from its own recorded content, and the chain is unbroken);
- the receipt's signature is genuinely valid over that chain;
- the chain-of-custody and acquisition documents (the FRE 901/902 basis for admissibility) are present and internally consistent;
- if the packet claims a payment-rail settlement anchor, that anchor's hash actually recomputes from its declared content.
Verify a packet in 60 seconds
pip install cryptography # the only third-party dependency
unzip your-packet.zip -d packet
python verify.py packet
(Or pip install . from this repo, then python -m verify packet.)
Exit code 0 and VERIFIED: ... means every check above passed. Exit
code 1 prints exactly which check failed and why -- nothing is silently
waved through. Exit code 2 means cryptography isn't installed (no
partial/soft-pass is possible).
Try it right now against the demo packets checked into this repo:
python verify.py demo/demo-packet-unanchored
python verify.py demo/demo-packet-anchored # includes a rail settlement anchor
Or try it entirely in your browser, no install: tamper_demo/
loads a real signed packet, lets you edit any field, and re-verifies live as you type --
break the hash chain or the signature and watch it turn red immediately. Nothing is sent
anywhere; it's a JS port of the same checks (tamper_demo/verify_min.js), using the
browser's native WebCrypto Ed25519 support.
Use it in CI
action.yml in this repository is a reusable GitHub Action:
- uses: actaseal/actaseal-verify@main
with:
packet-dir: ./extracted-packet
# anchors: ./anchors.jsonl # optional
Fails the step (non-zero exit) if the packet doesn't verify.
Use it from Node / a browser
@actaseal/verify (tamper_demo/, npm install @actaseal/verify) is a
minimal, zero-dependency WebCrypto port covering chain integrity and
Ed25519 receipt signatures only -- see tamper_demo/README.npm.md for
its exact (reduced) scope before relying on it for anything beyond a
quick client-side check. The real verify.py above is the canonical,
full-scope verifier.
Telemetry: opt-in, count-only, off by default, NOT in verify.py itself
verify.py makes zero network calls, period -- that's a real,
tested guarantee (tests/test_verify_demo_packet.py:: test_verify_has_no_external_imports_beyond_cryptography), not a
default that can be toggled. Telemetry lives entirely in a separate,
optional wrapper instead: verify_with_telemetry.py calls verify.py's
own main() completely unchanged, and only after that call returns
does it (optionally) send a ping -- verify.py itself never imports
urllib or knows telemetry exists.
python verify_with_telemetry.py --telemetry <packet_dir>
Even with --telemetry passed, the wrapper ships with
TELEMETRY_ENDPOINT set to an empty string, so it's currently an inert
no-op regardless -- nothing is transmitted until a maintainer
deliberately sets a real endpoint in a future release. When/if that
happens, a ping sends exactly three fields: the wrapper's own version
string, the verification outcome (PASS/FAIL), and a failure count
(derived by counting verify.py's own printed failure lines, never by
inspecting packet content) -- never a filename, never anything about
the specific receipt or ledger being verified. Best-effort only: any
network failure is silently swallowed and never affects the exit code
or printed result, which are always exactly what plain python verify.py
would have produced.
Why "zero-import" is the whole point
verify.py has no dependency on ActaSeal's code, config, database, or
network access -- it is standalone by construction (see the assertion in
tests/test_verify_demo_packet.py::test_verify_has_no_external_imports_beyond_cryptography).
The only third-party package it needs is cryptography, for Ed25519/ECDSA
signature checks. That means:
- you can read the whole verifier in one sitting (~360 lines, no framework, no magic) and know exactly what it checks;
- you never run vendor code against your own systems to check evidence someone handed you -- it only reads files from the packet directory;
- the packet embeds its own trust root (
manifest.json'sreceipt_public_key_hex) -- verification needs no external key file, registry, or live service lookup. You separately compare that key against the operator's published key out of band; the packet cannot forge that comparison, only be internally consistent or not.
This is the file that ships inside every real ActaSeal dispute packet as
verify.py -- what you're running here is not a simplified demo version,
it's the literal artifact.
What "VERIFIED" actually means
Passing does not mean "ActaSeal says this is fine." It means every one of these independently re-derives from data already inside the packet:
- Ledger chain integrity -- each event's
payload_hash,event_id, andevent_hashrecompute from that event's own fields; the slice is an unbroken hash chain from the manifest's declared starting point. - Receipt signature -- the receipt's Ed25519/ECDSA signature verifies
against
manifest.json's embedded public key, over the canonical JSON of the receipt (minus the signature field itself). - Receipt-to-chain binding -- the receipt's
ledger_entry_hashnames a real event in the slice, for the same action, and that event's recorded action hash matches the receipt's. - Chain-of-custody / acquisition basis (FRE 901(b)(9), 902(13)-(14)) -- the acquisition report names its tool, clock source, and custodian; custody events are time-ordered and each bound to a real ledger event hash; the authentication statement's key id, pubkey hash, and mandate hash match the receipt.
- Scope conformance -- if the receipt claims an in-scope or breach
verdict, that verdict traces back to a
ScopeEvaluatedledger event whose recorded scope/action hashes recompute correctly. - Settlement anchor -- if the packet claims a payment-rail
settlement anchor, its
anchor_hashrecomputes from the anchor content; an unanchored packet says so explicitly (never silent).
See SPEC.md for the full packet format.
What this does not verify
- That the underlying transaction/refund/decision was correct -- only that the recorded evidence is internally consistent and unaltered.
- That
receipt_public_key_hexbelongs to the operator you think it does -- that binding is on you, out of band (e.g. compare against a key the operator has published elsewhere). - Anything about a deployment you haven't been handed a packet from.
Repo layout
verify.py the verifier -- the only file that matters at verify time
generate_demo_packet.py builds the two demo packets below, from scratch, standalone
demo/demo-packet-unanchored/ a valid packet with no settlement anchor
demo/demo-packet-anchored/ a valid packet with a payment-rail settlement anchor
tamper_demo/ in-browser, editable, live-verifying demo (see above)
SPEC.md packet format reference
tests/ pytest suite: valid packets pass, tampered ones fail loudly
License
Apache-2.0. See LICENSE.
Security
See SECURITY.md for how to report a vulnerability.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file actaseal_verify-1.0.0.tar.gz.
File metadata
- Download URL: actaseal_verify-1.0.0.tar.gz
- Upload date:
- Size: 23.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72c2e7bc4dd7d7b1180d183f42260ae16b8c954b1682a469a1f8eec537f7028b
|
|
| MD5 |
f60fd1ebe43d0556b5c88ef5576016c0
|
|
| BLAKE2b-256 |
799150292b8ccc4349ac1d1feadc8ad019c889ccab2b7a248c611adb627c85be
|
File details
Details for the file actaseal_verify-1.0.0-py3-none-any.whl.
File metadata
- Download URL: actaseal_verify-1.0.0-py3-none-any.whl
- Upload date:
- Size: 17.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6010fa343ab8bf4bab53cce02037e480361e61ed3414345c5fc7a4fb77fbc2e7
|
|
| MD5 |
9f3ae93f8dc7cfac9d72f6ad22445428
|
|
| BLAKE2b-256 |
7ed8474d19f8c8b3bc35fb18ec9ecbfdd3b993f5d8e4ac72ca6d90af2782d007
|