Verify Pipelock action receipts (Ed25519-signed, chain-linked).
Project description
pipelock-verify
Python verifier for Pipelock action receipts. Verifies the Ed25519 signature, chain linkage, and flight-recorder wrapping of receipts emitted by the Pipelock mediator.
Mirrors the Go reference implementation byte-for-byte. The conformance golden files in tests/conformance/ are generated by Pipelock's Go code and verified identically by both sides.
Install · Usage · What gets verified · Canonicalization · Spec · Go reference
Install
pip install pipelock-verify
Only one runtime dependency: cryptography for the Ed25519 primitives.
Usage
Single receipt
import pipelock_verify
with open("receipt.json", "rb") as f:
result = pipelock_verify.verify(f.read())
if not result.valid:
raise SystemExit(f"bad receipt: {result.error}")
print(f"OK: {result.action_id} {result.verdict} {result.target}")
Pin a specific signing key to reject receipts from any other signer:
PROD_KEY = "70b991eb77816fc4ef0ae6a54d8a4119ddc5a16c9711c332c39e743079f6c63e"
result = pipelock_verify.verify(receipt_bytes, public_key_hex=PROD_KEY)
Receipt chain
Pass a flight-recorder JSONL path:
chain = pipelock_verify.verify_chain("evidence-proxy-0.jsonl")
if not chain.valid:
raise SystemExit(
f"chain broken at seq {chain.broken_at_seq}: {chain.error}"
)
print(f"CHAIN VALID: {chain.receipt_count} receipts, root {chain.root_hash}")
When no trust anchor is supplied, the first receipt's signer_key becomes
the expected key for the rest of the chain. This matches the signer-
consistency check in Go's receipt.VerifyChain.
CLI
python -m pipelock_verify receipt.json
python -m pipelock_verify evidence.jsonl
python -m pipelock_verify evidence.jsonl --key 70b991eb77816fc4...
Exit codes match pipelock verify-receipt: 0 on success, 1 on failure.
What gets verified
On a single receipt:
- Envelope version (rejects anything other than v1).
- Action record version (rejects anything other than v1).
- Required action record fields (
action_id,action_type,timestamp,target,verdict,transport). - Signature format (
ed25519:<hex>prefix, 64-byte length). - Signer key format (32-byte hex).
- Optional trust anchor match (
public_key_hexargument). - Ed25519 signature over
SHA-256(canonical action record).
On a chain:
- Every individual receipt above.
- Signer consistency (every receipt uses the same
signer_key, or the pinned trust anchor if one was supplied). - Monotonic
chain_seqstarting at 0. chain_prev_hashlinkage: each receipt'schain_prev_hashequalsSHA-256of the previous receipt's canonical envelope, in hex.- First receipt's
chain_prev_hashequals the literal string"genesis".
Failing receipts return the first break point (broken_at_seq) and a
descriptive error, the same shape the Go CLI prints.
Input formats
verify_chain() accepts JSONL in two shapes:
- Flight-recorder entries — the format Pipelock actually writes to
disk. Each line is a
recorder.Entryobject withtype == "action_receipt"and the receipt nested indetail. Non-receipt entries (checkpoints etc.) are skipped, not rejected. - Bare receipts — one receipt object per line, no wrapping. Used by the conformance suite and handy for ad-hoc testing.
verify() accepts:
- A JSON string or UTF-8 bytes.
- A pre-parsed
dict(for callers that already have the receipt loaded). - A flight-recorder entry dict (transparently unwrapped).
Canonicalization rules
The signing input is the SHA-256 of the Go json.Marshal output of the
ActionRecord struct. "Canonical" means matching that exactly:
- Fields emitted in Go struct declaration order (not alphabetical).
omitemptyfields dropped when the value is the Go zero value ("", empty slice,0,false,nil).- Compact JSON (no whitespace between tokens).
- HTML-safe escapes:
<,>,&, U+2028, U+2029 encoded as Unicode escapes, matching Go's defaultencoding/jsonbehavior. - Fields unknown to the v1 schema are dropped (matches Go
json.Unmarshalround-trip behavior).
Any deviation produces different bytes, a different hash, and a failed
signature. See pipelock_verify/_canonical.py for the full rule set.
Relationship to the Go reference
- Go reference: https://github.com/luckyPipewrench/pipelock/tree/main/internal/receipt
- Conformance suite: https://github.com/luckyPipewrench/pipelock/tree/main/sdk/conformance
- Spec page: https://pipelab.org/learn/action-receipt-spec/
Both implementations verify the same sdk/conformance/testdata/ golden
files and compute identical root hashes.
Development
git clone https://github.com/luckyPipewrench/pipelock-verify-python
cd pipelock-verify-python
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest
Maintainers: see RELEASING.md for the OIDC-based publish flow.
To refresh the conformance fixtures from a local Pipelock checkout:
cd /path/to/pipelock
go test ./sdk/conformance/ -run TestGenerateGoldenFiles -update
cp sdk/conformance/testdata/*.{json,jsonl} \
/path/to/pipelock-verify-python/tests/conformance/
pytest
License
Apache 2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
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 pipelock_verify-0.1.1.tar.gz.
File metadata
- Download URL: pipelock_verify-0.1.1.tar.gz
- Upload date:
- Size: 22.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1744a35c281ba065a9b8fb9132c0794226c99d22d224c97372da0896cc880ab1
|
|
| MD5 |
0e7a8ab32b60251c9c45541631af7b7b
|
|
| BLAKE2b-256 |
8d089e54c7bdfd0a67db29286f08bcdaea976d96ae3db03f9b8026a2b8be46a4
|
Provenance
The following attestation bundles were made for pipelock_verify-0.1.1.tar.gz:
Publisher:
release.yml on luckyPipewrench/pipelock-verify-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pipelock_verify-0.1.1.tar.gz -
Subject digest:
1744a35c281ba065a9b8fb9132c0794226c99d22d224c97372da0896cc880ab1 - Sigstore transparency entry: 1340757344
- Sigstore integration time:
-
Permalink:
luckyPipewrench/pipelock-verify-python@270faa3f8ee6b0bf1d434ae373fe25ec42c95443 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/luckyPipewrench
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@270faa3f8ee6b0bf1d434ae373fe25ec42c95443 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pipelock_verify-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pipelock_verify-0.1.1-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
317a44894615ff6ccbda356e0b0170c16f93dba826ed75021bd65c7252c04d9f
|
|
| MD5 |
67d17a6e0e51aa6747e072c8803c50cd
|
|
| BLAKE2b-256 |
b355cde7a087918b956c7aaf88cfe6752049da9cd86e2af9ce6a0d386dddbd80
|
Provenance
The following attestation bundles were made for pipelock_verify-0.1.1-py3-none-any.whl:
Publisher:
release.yml on luckyPipewrench/pipelock-verify-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pipelock_verify-0.1.1-py3-none-any.whl -
Subject digest:
317a44894615ff6ccbda356e0b0170c16f93dba826ed75021bd65c7252c04d9f - Sigstore transparency entry: 1340757345
- Sigstore integration time:
-
Permalink:
luckyPipewrench/pipelock-verify-python@270faa3f8ee6b0bf1d434ae373fe25ec42c95443 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/luckyPipewrench
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@270faa3f8ee6b0bf1d434ae373fe25ec42c95443 -
Trigger Event:
push
-
Statement type: