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 therfc8785package (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 wirealgis never used to select the algorithm; the keystore's bound alg is, and a mismatch is rejected), integrity overutf8("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) viacryptography: 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_deeprecursively NFC-normalizes every string — object keys and values — via the stdlibunicodedata.normalize("NFC", …)(native, Python's own UCD — not ICU), then the existingjcssorts the already-normalized keys. Additive — the sign/verify path is untouched, so a composed/decomposed pair shares acontent_keybut 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:
- 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_catchervector,must_verify=false); and - converts the 64-byte
r‖sto DER viaencode_dss_signaturebefore handing it tocryptography.
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 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 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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cfb7d544acf06304a4eebe2295940a4d6abf1b8a52517ed4206ef03d50dc0033
|
|
| MD5 |
e97a94fb47bf7fcb94c549d713d71ee2
|
|
| BLAKE2b-256 |
b1b9ecb30f2aa71d5d88bfd0585969c47119dc8cf17381bf0bac87b3914aa0e1
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe3a8dcfa560a3f43df7cdee51142821ba28c3a57e814a984f006467402dd53f
|
|
| MD5 |
91fc9caf8204d4a01b7459015bb0e312
|
|
| BLAKE2b-256 |
ef43e0935c68faa913a78f94c038f7815a8270ae37afa3ee6a30fcc2670339a7
|