Skip to main content

pipelock-verify

PyPI Python CI CodeQL OpenSSF Scorecard License: Apache-2.0

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_type or "action_receipt_v1" routes to ActionReceipt v1.
  • "evidence_receipt_v2" routes to EvidenceReceipt v2.
  • Unknown record_type is 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_hex argument).
  • Ed25519 signature over SHA-256(canonical action record).

On a single EvidenceReceipt v2:

  • Envelope record_type and receipt_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_seq starting at 0.
  • chain_prev_hash linkage via SHA-256 of canonical envelopes.
  • First receipt's chain_prev_hash equals "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:

  1. Flight-recorder entries -- the format Pipelock actually writes to disk. Each line is a recorder.Entry object with type == "action_receipt" and the receipt nested in detail. Non-receipt entries (checkpoints etc.) are skipped, not rejected.
  2. 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

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)

Source distribution for pipelock-verify 0.4.1
File Size Uploaded
pipelock_verify-0.4.1.tar.gz 72.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pipelock-verify 0.4.1
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page