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 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_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 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_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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pipelock_verify-0.4.0.tar.gz (66.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pipelock_verify-0.4.0-py3-none-any.whl (42.0 kB view details)

Uploaded Python 3

File details

Details for the file pipelock_verify-0.4.0.tar.gz.

File metadata

  • Download URL: pipelock_verify-0.4.0.tar.gz
  • Upload date:
  • Size: 66.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pipelock_verify-0.4.0.tar.gz
Algorithm Hash digest
SHA256 681d30a5cd3e519d886a2a077a1774bfa4e37b17c64e3211278aaecb73497a24
MD5 6bf10ac2ce2f6755fdf79d0f497cc18d
BLAKE2b-256 b04ebc3a81fbedf09a8732611a5dd5acb7c84ac0f5ce8fd03ed926ba3911255b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pipelock_verify-0.4.0.tar.gz:

Publisher: release.yml on luckyPipewrench/pipelock-verify-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pipelock_verify-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: pipelock_verify-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 42.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pipelock_verify-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b380cb3f51fadbbb34ddd264b588893c9ac1c676058225915a2679d0e34a47c6
MD5 2b6519662f86cf6287e207ebadb106be
BLAKE2b-256 8432181f81ea3e8818ccff83e671ca9d76bd87dea52e628251bb437301ab9ca5

See more details on using hashes here.

Provenance

The following attestation bundles were made for pipelock_verify-0.4.0-py3-none-any.whl:

Publisher: release.yml on luckyPipewrench/pipelock-verify-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.0

2 files

0.1.1

2 files

0.1.0

2 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