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 callableVerifyResult(...) 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 259/259 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 mechanismjcs (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.

Download files

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

Source Distribution

legion_foundation-0.1.1.tar.gz (160.5 kB view details)

Uploaded Source

Built Distribution

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

legion_foundation-0.1.1-py3-none-any.whl (157.4 kB view details)

Uploaded Python 3

File details

Details for the file legion_foundation-0.1.1.tar.gz.

File metadata

  • Download URL: legion_foundation-0.1.1.tar.gz
  • Upload date:
  • Size: 160.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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}

File hashes

Hashes for legion_foundation-0.1.1.tar.gz
Algorithm Hash digest
SHA256 cfb7d544acf06304a4eebe2295940a4d6abf1b8a52517ed4206ef03d50dc0033
MD5 e97a94fb47bf7fcb94c549d713d71ee2
BLAKE2b-256 b1b9ecb30f2aa71d5d88bfd0585969c47119dc8cf17381bf0bac87b3914aa0e1

See more details on using hashes here.

File details

Details for the file legion_foundation-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: legion_foundation-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 157.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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}

File hashes

Hashes for legion_foundation-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fe3a8dcfa560a3f43df7cdee51142821ba28c3a57e814a984f006467402dd53f
MD5 91fc9caf8204d4a01b7459015bb0e312
BLAKE2b-256 ef43e0935c68faa913a78f94c038f7815a8270ae37afa3ee6a30fcc2670339a7

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.1 This release

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