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's signature, chain-linking and canonical-form checks byte-for-byte. The conformance golden files in tests/conformance/ are generated by Pipelock's Go code and verified identically by both sides. It does not implement the end-of-chain proof or the whole-group verification that newer Pipelock releases add; see the section "What a valid result does and does not prove" below.
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.
For an ActionReceipt v1 chain that rotated signing keys, pin the original root and supply one old-key-signed endorsement for each rotation boundary:
import pipelock_verify
endorsement = pipelock_verify.load_rotation_endorsement(
"receipt-rotation-2026-07-30.json"
)
chain = pipelock_verify.verify_chain(
"evidence-proxy.jsonl",
public_key_hex="4655a7e605c12ebb00a46037881c33c5bca5eb74b45a02e8e7261a7ff5a21678",
session_id="proxy",
rotation_endorsements=[endorsement],
)
The endorsement is verified under the retiring key and bound to the exact prior sequence, tail hash, recorder session, and successor key. Missing, altered, duplicate, replayed, cross-session, and unused endorsements fail closed.
session_id must match recorder_session in every signed session_open, and
the root segment must contain one. A chain without that signed session binding
cannot be verified with endorsements.
CLI
python -m pipelock_verify receipt.json
python -m pipelock_verify evidence.jsonl
python -m pipelock_verify evidence.jsonl --key 70b991eb77816fc4...
python -m pipelock_verify evidence.jsonl \
--key 4655a7e605c12ebb00a46037881c33c5bca5eb74b45a02e8e7261a7ff5a21678 \
--session-id proxy \
--rotation-endorsement receipt-rotation-2026-07-30.json
Exit codes: 0 on success, 1 on failure. In Pipelock releases that add end-of-chain proofs, pipelock verify-receipt also exits 3 for a valid chain whose end is not proven. This package has no such state.
What a valid result does and does not prove
CHAIN VALID means every ActionReceipt v1 extracted from the file has a valid signature, the signer matches, and each receipt links to the one before it. Other entries in a flight-recorder file are not checked here: altering the signature on a wrapped EvidenceReceipt v2 entry still returns CHAIN VALID, while Pipelock's own pipelock verify-receipt rejects the recorder. It also does not prove the chain ends where it should. A chain cut off after a receipt still verifies, because this package has no end-of-chain proof. The exception is a chain verified with a rotation endorsement: cutting it before the rotation boundary leaves the endorsement unused, and that is rejected. In Pipelock releases that add end-of-chain proofs, pipelock verify-receipt reports a chain with no proven end as VALID PREFIX, END UNPROVEN. Use it when a missing tail matters.
Parallel receipt chains (flight_recorder.receipt_chains greater than 1) write one chain per shard. Each shard verifies on its own with this package, but a valid shard does not show that the group is complete. Shard membership, missing shards, the signed group open and close, and the link to a previous group still need Pipelock's own group verifier.
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.
Metadata
Release files for pipelock-verify 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pipelock_verify-0.4.1.tar.gz | 72.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pipelock_verify-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 116.1 kB
Release files / pipelock_verify-0.4.1.tar.gz
| Download URL | pipelock_verify-0.4.1.tar.gz |
|---|---|
| Size | 72.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
56cc764b3addafac734e22844cc5132c63be4b73ff801d90f0a88b12bc9c9be6
|
|
BLAKE2b-256 checksum How to use checksums |
d1f5b0cf3a2c96f5d9dd1e4580bc5efd5fab4a00b581dc2b8fec19f5a2744d25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / pipelock_verify-0.4.1-py3-none-any.whl
| Download URL | pipelock_verify-0.4.1-py3-none-any.whl |
|---|---|
| Size | 43.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e00bc0b74a0eb9f4edcd10846e1d56ebb9c95d7ff6218f3012afc2465dfd0707
|
|
BLAKE2b-256 checksum How to use checksums |
f2ee5d7cdac2771cdcca6b7e64cdbe3a4ab606128cbc8a25ddc8a75cba544714
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency log