Skip to main content

legion-foundation

The verdict spine in Python — an independent implementation of a frozen, cross-language signing contract, with no dependency on the TypeScript reference binding. It exists to prove the underlying wire format is genuinely polyglot: this binding reproduces, byte-for-byte, the same pinned canonical bytes and verifies the same pinned signatures that nine other independent language bindings do, against one shared conformance corpus. The corpus is the contract; if a binding can't reproduce a vector, it fails — the tape is never edited to make a binding pass.

Provenance boundary. The normative, versioned contract for the wire formats this package implements is a document called SIGNING.md, which currently also lives in that private repository and is not distributed with this package. So: this README and the bundled conformance corpus are, today, the complete publicly available description of the wire format. Where a comment or a corpus spec/note field cites SIGNING.md §N or another private document, treat the corpus vectors themselves as the operative specification — they are the thing every binding is actually gated against, independent of whether the cited document is reachable.

Every signed message in this family follows the same construction:

signed_bytes = utf8("<DOMAIN>\n") || JCS(projection)

RFC 8785 JCS (JSON Canonicalization Scheme) over a null-stripped projection of the message, prefixed with an off-wire domain-separation tag that never appears in the message itself. This package implements several subsystems built on that one mechanism, each with its own distinct DOMAIN tag — so a signature for one can never validate as another:

  • verdict — DOMAIN legion.os/v/1: utf8("legion.os/v/1\n") || JCS(projection)
  • syscall — DOMAIN legion.os/syscall/v/1: utf8("legion.os/syscall/v/1\n") || JCS(projection)
  • trust — DOMAIN legion.os/trust/v/1: utf8("legion.os/trust/v/1\n") || JCS(projection)

The exported DOMAIN / DOMAIN_SYSCALL / DOMAIN_TRUST constants already carry the trailing newline — measured: DOMAIN == "legion.os/v/1\n", DOMAIN_SYSCALL == "legion.os/syscall/v/1\n", DOMAIN_TRUST == "legion.os/trust/v/1\n". The formula above is written with an explicit \n only to show where the newline lives conceptually. In code, construct the signed bytes as:

signed_input = DOMAIN.encode("utf-8") + jcs(projection)   # NOT DOMAIN.encode() + b"\n" + jcs(projection)

Appending a separate \n double-newlines the signed bytes and every signature fails to verify.

What it implements

The verdict spine is checked by two ordered gates, referred to below as GATE 1 and GATE 2 — GATE 1 (verify) establishes authenticity and well-formedness; GATE 2 (certify_close) then checks standing (expiry and revocation) on an already-verified envelope. Both are async def functions — see the calling convention under VerifyResult / CertifyResult are unions, not classes below.

  • RFC 8785 JCS (jcs.py) via the rfc8785 package (maintained by Trail of Bits, itself adapted from the RFC author's own reference implementation). Cross-checked in the conformance test against the official cyberphone vectors (the independent oracle) and the pinned canonical hex in the committed corpus.
  • GATE 1 — verify(envelope, resolve_key) (verdict.py): kid-binds-alg (the wire alg is never used to select the algorithm; the keystore's bound alg is, and a mismatch is rejected), integrity over utf8("legion.os/v/1\n") || JCS(null-stripped projection), body_type↔body disjointness, and a canonical-number guard. Returns the canonical projection, not the raw input.
  • GATE 2 — certify_close(verified, ...) (verdict.py): expiry + status_ref → active/revoked/suspended/unknown (fail-closed), require_status, expiry-checked-before-status.
  • Crypto (crypto.py) via cryptography: Ed25519 (default), HMAC-SHA-256 (constant-time compare, single trust domain), and ECDSA P-256/SHA-256.
  • content_key(input) (contentkey.py): the Legion-defined DERIVED id, "sha256:" + base64url-UNPADDED(SHA-256(JCS(NFC-deep(input)))). nfc_deep recursively NFC-normalizes every string — object keys and values — via the stdlib unicodedata.normalize("NFC", …) (native, Python's own UCD — not ICU), then the existing jcs sorts the already-normalized keys. Additive — the sign/verify path is untouched, so a composed/decomposed pair shares a content_key but keeps different signed bytes (audit fidelity). An NFC key collision raises (never merges); scope is encoding only ("100" != 100, dates stay distinct). Apps get normalized identity for free and must never hand-normalize. Gated against the project's committed conformance vectors.

The ES256 cross-language detail (the whole point)

SIGNING.md §4.1 fixes the on-wire ES256 signature as raw r‖s, 64 bytes (IEEE P1363) — not ASN.1 DER. Python's cryptography ECDSA verify expects DER, so this binding:

  1. rejects any ES256 signature that is not exactly 64 bytes — a DER-encoded signature presented on the wire (~70 bytes) is rejected (the es256_der_catcher vector, must_verify=false); and
  2. converts the 64-byte r‖s to DER via encode_dss_signature before handing it to cryptography.

A binding that accepts DER on the wire is non-conformant.

Import paths

legion_foundation's top-level package re-exports the verdict-spine surface directly:

from legion_foundation import (
    jcs, canonicalize, CanonicalizationLimitError,
    MAX_CANON_DEPTH, MAX_CANON_NODES, MAX_CANON_SCALAR,
    content_key, nfc, nfc_deep,
    verify_bytes, sign_bytes, MintRefusedError,
    ED25519_SMALLORDER_BLOCKLIST, ed25519_strict_profile_fails, es256_is_high_s,
    b64u_decode, b64u_encode,
    verify, certify_close,
    VerifyResult, CertifyResult, VerifyingKey,
    NonCanonicalNumber, assert_canonical_numbers,
    well_formed_error, signed_view, is_alg, is_tier, is_credential, is_verdict,
    ALLOWED_ALGS, BODY_TYPE_CREDENTIAL, BODY_TYPE_VERDICT, DOMAIN,
)

The syscall, trust, receipt, and didkey modules are not re-exported at the top level — import them by their submodule path:

from legion_foundation.syscall import authorize, mint_delegation, attenuate

VerifyResult / CertifyResult are unions, not classes

Both are Union type aliases over a Literal-tagged success/failure pair. That tagging is what makes the canonical idiom narrow correctly — after if r.ok:, a type checker knows r.envelope is a dict rather than dict | None.

verify and certify_close are both async def coroutines — calling either returns a coroutine object, not a VerifyResult/CertifyResult, until it is awaited. await is only legal inside an async def function, so the canonical idiom is:

import asyncio

async def check(envelope, resolve_key):
    r = await verify(envelope, resolve_key)
    if r.ok:
        issuer = r.envelope["issuer"]   # narrows; no assert or cast needed
    else:
        log(r.reason)

asyncio.run(check(envelope, resolve_key))

Calling r = verify(envelope, resolve_key) without await binds r to an un-awaited coroutine: r.ok raises AttributeError (coroutines have no .ok attribute), and Python additionally emits a RuntimeWarning: coroutine … was never awaited because the verification never actually ran.

The practical consequence: isinstance(r, VerifyResult) works, but the alias is not callable — VerifyResult(...) raises TypeError. Construct or match the variants instead, which are exported for exactly that purpose:

from legion_foundation import VerifyOk, VerifyFail, CertifyOk, CertifyFail

This package ships a PEP 561 py.typed marker, so these shapes are authoritative to mypy and pyright.

Run the conformance gate

Requires Python >= 3.11. Runtime dependencies are rfc8785 (RFC 8785 JCS) and cryptography (Ed25519 / ECDSA P-256 / HMAC-SHA-256) — both installed automatically by pip.

pip install legion-foundation
python -m legion_foundation.conformance   # prints N/N; exits non-zero on any failure

That command is the one that actually works for a PyPI consumer: the distribution bundles a pinned 23-file slice of the conformance corpus (jcs-vectors/, verdict/, syscall/, trust/, receipt/) as package data, so the gate can run against an installed copy with no repo checkout in sight. It mirrors the TS binding's three angles against the bundled tape: (1) JCS == official vectors == pinned hex, (2) signed_input reconstruction + crypto-primitive verify/reject per must_verify, (3) the real verify() gate agrees with must_verify (incl. the DER-catcher and the null-omission lenient input), plus certify_close asserted by name.

Also run python -m legion_foundation.conformance --self-test to check the gate's own failure mode rather than its happy path: it copies a real corpus tree into a temp directory, mutates it three ways (deletes a whitelisted file, truncates a vector array, points at an empty directory), and asserts that each mutation makes the gate fail non-zero. A green 283/283 from a gate that would also pass against a silently truncated tape is not evidence of anything; --self-test is the check that it would not.

Contributors working from a monorepo checkout do NOT run the same command against the same tape. python -m legion_foundation.conformance alone reads whichever corpus resolved into the installed package at build time (mode: bundled) — measured, in a clean venv from the repo root: mode: bundled. The command that actually prefers the live, freshly regenerated corpus/ tree is the in-repo shim:

python3 packages/foundation-py/tests/conformance.py

The shim prepends the repo's own src/ tree to PYTHONPATH and relaunches the gate in a fresh subprocess. That is the whole mechanism: the module the subprocess imports is the repo's copy, so its own __file__-anchored resolution — marker-anchored on legion.manifest.json, walking up from __file__, never from cwd — naturally lands on mode: repo against the live tree. Repo source against the repo tape, with no flag and no hand-off.

There is deliberately no command-line option to point the gate at an arbitrary corpus. An earlier revision had one; it was removed rather than hardened, because a flag that redirects the tape is indistinguishable from the attack it enables — it let an installed copy verify an unpinned tree while skipping the bundled digest check. Resolution is now a property of where the imported module lives, which a caller cannot forge by passing an argument.

A bare python -m legion_foundation.conformance therefore resolves mode: repo whenever the module it imports lives under a checkout (via PYTHONPATH, or an editable install), and mode: bundled otherwise — which is the case for an ordinary pip install legion-foundation.

Every run — through the shim or bare — prints a line naming which tape it actually tested, so you never have to guess:

conformance gate resolution -> mode: bundled, root: <path>

or mode: repo when the module resolved a live checkout. Check that line before trusting a green result to mean "the tape in this checkout," not "whatever was staged into the wheel."

One caveat worth knowing, since this package hardens against it internally: a bare python -m <module> puts the current working directory on sys.path, so running the gate from a directory you do not control could import something other than the installed package. That is ordinary CPython -m behaviour rather than anything specific to this package, and the two subprocess relaunches this package performs internally both set PYTHONSAFEPATH=1 against it. If you are running the gate somewhere untrusted, do the same — PYTHONSAFEPATH=1 python -m legion_foundation.conformance.

What a green run proves, and what it does not. A passing run proves that this artifact — this interpreter, this build of cryptography, this build of OpenSSL — reproduces the pinned bytes and pinned signatures on your machine. That is genuine environment-drift detection: it did not exist before this package shipped its own corpus slice, and no other check available to a PyPI consumer offers it. It does not prove cross-binding conformance — that the Go, Rust, Zig, Swift, Java, Haskell, Elixir, C, and TypeScript bindings agree with this one is established by a ten-job CI pipeline a consumer of this package cannot see or rerun; running this gate is not a substitute for that pipeline, and no claim to the contrary should be inferred from a green result here.

A green run also does not prove the artifact you installed is authentic. The gate ships inside the same distribution it tests: anyone who can replace the contents of this package on your machine (a compromised mirror, a tampered wheel, a supply-chain substitution) can replace the gate along with it, so a self-test can never catch tampering to the artifact carrying it. That is a property of every self-test, not something specific to this design. Verifying that the bits you installed are the bits actually published requires publish-side attestation — Sigstore / PEP 740 provenance — which this release does not carry.

The bundled corpus is deliberately NOT all non-forgeable, and only the ES256 vectors are. The JCS vectors are the official third-party cyberphone RFC 8785 test suite — an oracle external to this project, not authored by it, and untouched. The ES256 signing vectors publish a public key with no matching private key bundled anywhere in this distribution, so verifying those pinned signatures cannot be faked by regenerating both sides of the check from a private key you also control. The EdDSA and HS256 portions of the tape are different: the corpus deliberately publishes the raw Ed25519 seed_hex and the HS256 secret_hex used to mint those vectors — both are byte-counting sequences (000102...1f), oracle material with no purpose beyond reproducing this exact tape, securing nothing, and identical across every language binding. If you run a secret scanner over your installed site-packages, it will flag these as apparent private keys/secrets — they are intentional, public, non-sensitive test fixtures, not a leaked credential.

syscall — capabilities

legion_foundation.syscall is the Python binding for the second subsystem (import it explicitly — see Import paths above). It reuses this package's mechanism — jcs (RFC 8785), crypto.verify_bytes (Ed25519 via cryptography), and crypto.b64u_decode/b64u_encode — and adds only the syscall-specific parts: a distinct DOMAIN tag legion.os/syscall/v/1 (so a verdict signature can never verify as a syscall one — pinned by a cross-domain test), and did:key Ed25519 principals (base58btc decoded by a dependency-free ~12-line decoder, then strip the 0xed01 multicodec).

It also carries the UCAN pol evaluator — ==/!=/</<=/>/>=/like/ not/and/or/all/any over dotted+index selectors; empty policy = permit; an unresolvable selector evaluates to false rather than raising; the evaluator is finite by construction.

The authorize gate is the syscall analogue of certify_close, and is fail-closed in a fixed order: gate 0 size/depth → gate 1 nonce-freshness first (replay exits before any crypto) → verify → walk the proof chain → root anchor. authorize's exact refusal reasons are gated against the project's committed conformance vectors.

One caller-visible failure mode is not a return value. If the opt-in require_root_pol argument is present and is not a Mapping of selector → JSON scalar, authorize raises TypeError — before gate 0, so ahead of every refusal path. That is deliberate: it is caller-side API misuse, not folded into a token refusal, because the AuthorizeResult.reason strings are corpus-pinned and reporting a misconfiguration as a request refusal would send whoever debugs it to inspect an innocent request instead. Pass None for "no pin." The argument is snapshotted at entry, so mutating the mapping later in the call — from an injected resolve_prf, say — cannot void the pin.

trust — the recursion

legion_foundation.trust is the third subsystem's binding (import it explicitly by submodule path). It answers a different question than verdict or syscall: not "is this envelope authentic" but "how much should a verifier be trusted, given its measured track record." DOMAIN legion.os/trust/v/1, distinct from the other two. weight_from_record turns a track record (true positives, false positives, false negatives, and the ground-truth base rate) into a calibrated integer-PPM weight — deliberately float-free, ≈0 at cold start (no graded verdicts yet), and ≈0 for a "fabricator" — a verifier whose measured precision is at or below the base rate, i.e. no better than guessing the prior and contributing no skill above chance. mint_weight_verdict signs the resulting weight as a spine CREDENTIAL over utf8(DOMAIN_TRUST) || JCS(projection) — reusing the same verdict-spine shape and signing mechanism, with only the DOMAIN tag distinct — and structurally refuses to mint a weight for a SEMANTIC verifier (one with no answer key to measure against), since there is no track record to calibrate against in that case.

receipt — the closing leg

legion_foundation.receipt (import it explicitly by submodule path) wires up the third leg of the UCAN triad — delegation → invocation → receipt — but mints no DOMAIN tag and no new wire primitive of its own. It is pure orchestration over the existing spine: an execution record is an ordinary verdict credential (the executor's claim: "I ran invocation X, status S, output digest D"), and a receipt is an ordinary verdict verdict envelope — an independent party's signed appraisal of that execution record. The one invariant receipt adds is that the receipt's signer must not be the execution record's signer (verifier != executor); a receipt signed by the same key that signed the execution record would merely notarize the executor's own claim rather than independently confirm it, so legion_foundation.receipt refuses to build one (ProposerIsVerifierError) and re-checks the same condition at verify time.

Security

This package implements a cryptographic signing and authorization system. The source repository is private and has no public issue tracker, so please do not open a public issue for a suspected vulnerability. Instead, report it directly to:

security@copiawealthstudios.com

License

Apache-2.0. This distribution includes the LICENSE and NOTICE files.

Metadata

Release files for legion-foundation 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for legion-foundation 0.2.0
File Size Uploaded Provenance
legion_foundation-0.2.0.tar.gz 200.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for legion-foundation 0.2.0
File Interpreter ABI Platform
legion_foundation-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 371.6 kB

Release files / legion_foundation-0.2.0.tar.gz

Download URL legion_foundation-0.2.0.tar.gz
Size 200.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9cecf028cc7eaef923daca44e02150a3040a59863f5e7c288c0f331ceff34c82
BLAKE2b-256 checksum
How to use checksums
0905b5f764da205430234f585fdb0d7c0472bfaf5f7a47980487bbe69888bf06
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / legion_foundation-0.2.0-py3-none-any.whl

Download URL legion_foundation-0.2.0-py3-none-any.whl
Size 171.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5e1c1813c82cffba1ad83d18884b09a4cbe33419690563c917f3fa675d28f40f
BLAKE2b-256 checksum
How to use checksums
037f62f2c324fc1dc15eb80fab911c41383a0f407219045d6aef92cfebf38e1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for legion-foundation 0.2.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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