pipelock-verify
Python verifier for Pipelock receipts. Supports ActionReceipt v1 chains and individual EvidenceReceipt v2 envelopes for contract-aware lifecycle events. Verifies Ed25519 signatures, v1 chain linkage, v2 payload schemas, key-purpose authority, and flight-recorder wrapping.
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 · EvidenceReceipt v2 · Well-known directory · What gets verified · Spec · Go reference
Install
pip install pipelock-verify
Only one runtime dependency: cryptography for the Ed25519 primitives.
Usage
Single receipt (auto-detects v1 vs v2)
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}")
The verify() function automatically routes to the correct verification
path based on the record_type field:
- No
record_typeor"action_receipt_v1"routes to ActionReceipt v1. "evidence_receipt_v2"routes to EvidenceReceipt v2.- Unknown
record_typeis rejected with a clear error.
Pin a signing key via the well-known directory
import pipelock_verify
# Fetch the signing keyset from the Pipelock instance.
directory = pipelock_verify.fetch_directory("pipelab.org")
key_hex = directory.public_key_hex()
result = pipelock_verify.verify(receipt_bytes, public_key_hex=key_hex)
ActionReceipt v1 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.
verify_chain() intentionally fails closed when the chain contains
EvidenceReceipt v2 entries. v0.2.0 verifies v2 receipts one at a time with
verify() or verify_evidence(); v2 chain verification is reserved for a
follow-up release once the cross-version chain-linking rules are specified.
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.
EvidenceReceipt v2
EvidenceReceipt v2 is the contract-aware receipt envelope introduced in Pipelock v2.4. It sits alongside ActionReceipt v1 (which remains unchanged for backward compatibility).
Direct v2 verification
For fine-grained control over v2-specific checks (key purpose enforcement, signer key ID pinning):
from pipelock_verify import verify_evidence
result = verify_evidence(
receipt_dict,
public_key_hex="...",
expected_signer_key_id="receipt-key-prod",
expected_key_purpose="receipt-signing",
)
if not result.valid:
raise SystemExit(f"v2 receipt failed: {result.error}")
print(f"Event: {result.event_id}, Kind: {result.payload_kind}")
13 payload kinds
| Payload kind | Signing purpose |
|---|---|
proxy_decision |
receipt-signing |
contract_ratified |
receipt-signing |
contract_promote_intent |
contract-activation-signing |
contract_promote_committed |
receipt-signing |
contract_rollback_authorized |
contract-activation-signing |
contract_rollback_committed |
receipt-signing |
contract_demoted |
receipt-signing |
contract_expired |
receipt-signing |
contract_drift |
receipt-signing |
shadow_delta |
receipt-signing |
opportunity_missing |
receipt-signing |
key_rotation |
contract-activation-signing |
contract_redaction_request |
contract-activation-signing |
The authority matrix is enforced automatically. A valid signature from the wrong key purpose is rejected.
v2 canonicalization
EvidenceReceipt v2 uses RFC 8785 JSON Canonicalization Scheme (JCS) for
signable preimages, not Go's encoding/json byte order (which is what
ActionReceipt v1 uses). JCS rules:
- Object keys sorted lexicographically by Unicode codepoint.
- Strings NFC-normalized.
- Floats rejected (use decimal strings).
- No whitespace between tokens.
The signature field is zeroed before computing the preimage.
Well-known directory
Pipelock instances serve their signing keys at
/.well-known/http-message-signatures-directory (RFC 9421). Use the
built-in fetch helper to retrieve and parse the keyset:
from pipelock_verify import fetch_directory, parse_directory
# Fetch from a live instance.
directory = fetch_directory("pipelab.org")
# Or parse from a pre-fetched JSON blob.
directory = parse_directory(json_bytes)
# Look up a specific key.
key = directory.get_key("pipelock-mediation-prod")
if key:
print(f"Key: {key.public_key}, Use: {key.use}")
What gets verified
On a single ActionReceipt v1:
- 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 single EvidenceReceipt v2:
- Envelope
record_typeandreceipt_version. - Strict unknown-field rejection (envelope, signature proof, and payload).
- Required envelope fields (
event_id,timestamp,payload_kind). - Payload schema validation for all 13 payload kinds.
- Key purpose authority matrix enforcement.
- Signature proof structure (
signer_key_id,key_purpose,algorithm). - Ed25519 PureEdDSA signature over
JCS(receipt_without_signature). - Optional trust anchors:
public_key_hex,expected_signer_key_id,expected_key_purpose.
On an ActionReceipt v1 chain:
- Every individual ActionReceipt v1 above.
- Signer consistency across the chain.
- Monotonic
chain_seqstarting at 0. chain_prev_hashlinkage via SHA-256 of canonical envelopes.- First receipt's
chain_prev_hashequals"genesis".
EvidenceReceipt v2 entries are rejected in chain mode with an explicit unsupported-v2-chain error. Verify them individually in v0.2.0.
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. ActionReceipt v1 bare receipts are verified as a chain. EvidenceReceipt v2 bare receipts are rejected in chain mode and should be verified individually.
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).
Relationship to the Go reference
- Go reference: https://github.com/luckyPipewrench/pipelock/tree/main/internal/receipt (v1), https://github.com/luckyPipewrench/pipelock/tree/main/internal/contract/receipt (v2)
- 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 single-receipt v2 golden files. For chain root hashes, v0.2.0 parity is limited to ActionReceipt v1 chains.
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.
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.3.0.tar.gz.
File metadata
- Download URL: pipelock_verify-0.3.0.tar.gz
- Upload date:
- Size: 59.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb25cfa9633d8a1396edb94be4770a102108eff61bc27a90d64c9a23a76b6c25
|
|
| MD5 |
5b6f95134c5670277fafd5c60dfd948b
|
|
| BLAKE2b-256 |
bfd9a809a325328b03d044080bf5ff58e65c9148eadb8600905f8680b7f26a26
|
Provenance
The following attestation bundles were made for pipelock_verify-0.3.0.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.3.0.tar.gz -
Subject digest:
eb25cfa9633d8a1396edb94be4770a102108eff61bc27a90d64c9a23a76b6c25 - Sigstore transparency entry: 2241598349
- Sigstore integration time:
-
Permalink:
luckyPipewrench/pipelock-verify-python@d2e0d4761e3f9186e60f79d2087f5c2972d5c220 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/luckyPipewrench
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d2e0d4761e3f9186e60f79d2087f5c2972d5c220 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pipelock_verify-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pipelock_verify-0.3.0-py3-none-any.whl
- Upload date:
- Size: 37.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7da048803f21849d4c91526d3da9d8dd1d999d67b0e8e23c9ddf40f881c7303f
|
|
| MD5 |
6d0154adf54e3bd6736178cca6520b5c
|
|
| BLAKE2b-256 |
dba51fec6adcbe15b9585057e1ae379ff65dfeb87dfa605b8e6be051920bfbd4
|
Provenance
The following attestation bundles were made for pipelock_verify-0.3.0-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.3.0-py3-none-any.whl -
Subject digest:
7da048803f21849d4c91526d3da9d8dd1d999d67b0e8e23c9ddf40f881c7303f - Sigstore transparency entry: 2241599301
- Sigstore integration time:
-
Permalink:
luckyPipewrench/pipelock-verify-python@d2e0d4761e3f9186e60f79d2087f5c2972d5c220 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/luckyPipewrench
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d2e0d4761e3f9186e60f79d2087f5c2972d5c220 -
Trigger Event:
push
-
Statement type: