Vouchsafe
Vouchsafe is a JWT that proves who sent it, without key distribution, registries, or a callback to an identity provider.
A normal JWT proves the claims weren't tampered with, but you still need a separate, pre-shared way to know whose key signed it — an API key, a shared secret, an OIDC handshake, something. A Vouchsafe token carries that proof inside itself: the issuer's identity, their public key, and the signature are all bound together in one package. If a Vouchsafe token validates, you already know exactly who issued it, and that nothing has changed, with no lookups and no infrastructure.
trusted_issuers = {
"urn:vouchsafe:alice.tp5y...vhsq": ["webhook:order_placed"],
}
Trust is configured locally and explicitly: you declare who you trust and for what, instead of provisioning API keys or registering callback endpoints.
That alone covers most "is this JWT really from who it claims to be from" use cases. But identity-proof is really just the foundation. Vouchsafe tokens can also vouch for each other, forming chains of delegated trust: Alice can vouch for Bob's claim, someone who trusts Alice can transitively trust Bob through her, and any statement in the chain can later be revoked. That turns Vouchsafe from "a JWT that verifies itself" into a small, portable trust and authorization system: offline credentials, delegated permissions, multi-party attestations, expressed entirely as data, with no contact with a server or service required to evaluate it.
This package implements Vouchsafe for Python.
Installation
pip install vouchsafe
The package is imported as vouchsafe and uses normal Python snake_case
names:
from vouchsafe import (
Identity,
VOUCHSAFE_SPEC_VERSION,
create_vouchsafe_identity,
create_vouchsafe_identity_from_keypair,
verify_urn_matches_key,
create_jwt,
verify_jwt,
decode_jwt,
get_app_claims,
create_attestation,
create_vouch_token,
revoke_vouch_token,
create_revoke_token,
create_burn_token,
validate_vouch_token,
verify_vouch_token,
hash_jwt,
is_burn_token,
is_revocation_token,
decode_token,
validate_trust_chain,
verify_trust_chain,
)
Quickstart (Identity Interface)
Most applications should start with Identity. It covers the common path:
create an identity, issue a token, and validate a trust chain.
Example: Sending and Verifying a Webhook
The sender creates an attestation containing the webhook data:
import json
from pathlib import Path
from vouchsafe import Identity
# Generate once with: create_vouchsafe_id.py --label alice -o alice.json
# Store identity JSON securely.
alice = Identity.from_data(json.loads(Path("alice.json").read_text()))
webhook_token = alice.attest({
"purpose": "webhook:order_placed",
"order_id": "12345",
"amount": 4999,
})
# Send webhook_token in the body or header required by your application.
The receiver validates the token and evaluates local trust policy:
from vouchsafe import get_app_claims, validate_trust_chain
trusted_issuers = {
"urn:vouchsafe:alice.tp5y...vhsq": ["webhook:order_placed"],
}
subject_token = received_webhook_token
result = validate_trust_chain(
[subject_token],
subject_token,
trusted_issuers,
["webhook:order_placed"],
)
if not result["valid"]:
raise PermissionError("Untrusted webhook source")
subject = result["subjectToken"]
app_claims = get_app_claims(subject["decoded"])
print("Trusted webhook from:", subject["decoded"]["iss"])
print("Webhook data:", app_claims)
There is no required callback to the issuer.
Token Delivery and Trust Material
Vouchsafe makes trust decisions using only the tokens provided and your local configuration. No external service or state is required beyond the token set being evaluated. This is what makes local, deterministic verification possible. Vouchsafe validates cryptographic statements, not transport mechanisms: it does not require any particular API, header format, database, message bus, or identity provider to deliver tokens. You decide how to obtain tokens and which trust material to keep available, based on what your application needs.
For trust-chain evaluation, provide the subject token, the related tokens you have available, and your local trusted-issuer policy. That token set can be assembled from more than one source:
- tokens presented by the actor in a request, webhook, or message;
- tokens bundled alongside the subject token;
- tokens your application has cached or synchronized previously;
- revocation and burn tokens from an application-managed revocation feed; and
- tokens supplied through an offline import, QR code, file, or any other application-specific channel.
For example, an application can merge caller-provided tokens with a local revocation corpus before validation:
from vouchsafe import validate_trust_chain
presented_tokens = request.tokens
revocation_tokens = load_local_revocation_tokens()
tokens = [*presented_tokens, *revocation_tokens]
result = validate_trust_chain(
tokens,
request.subject_token,
trusted_issuers,
required_purposes,
)
The delivery model does not dilute Vouchsafe's security properties. Every token is still validated for its signature, issuer-to-key binding, token structure, and role in the trust graph; local policy still decides which issuers and purposes are trusted. Because Vouchsafe has no infrastructure dependency, the application is free, in a way most authorization systems do not allow, to choose the delivery model that fits its own constraints. A high-stakes financial system might use high-availability infrastructure or a blockchain ledger, while a simple chat app might rely on opportunistic sync over BLE (Bluetooth Low Energy) or even printed QR codes. The choice is yours.
What Vouchsafe Tokens Can Represent
Vouchsafe tokens are simple types that combine into richer trust relationships:
- Attestations: "I assert this fact." Example: Alice attests that an order was created or that she controls an email address.
- Vouches: "I vouch for someone else's token." Example: if you trust Alice for email confirmation, you can transitively trust Bob's email attestation through Alice's vouch.
- Revocations: "I withdraw a previous attestation or vouch I issued."
- Burn tokens: "I am permanently destroying this identity." A burn causes future trust evaluation to reject tokens from that issuer when the burn token is included in the evaluation set.
Each token is self-contained and cryptographically bound to its issuer. By combining tokens, you can represent signed webhooks, delegated permissions, revocable credentials, and offline-verifiable statements. You decide who to trust through issuer URNs and for what through purposes; the evaluator handles the graph traversal and purpose attenuation.
Examples
Example 1: Attestation
An attestation is the simplest Vouchsafe token: a signed statement whose issuer and public key can be verified from the token itself.
from vouchsafe import Identity, get_app_claims, validate_vouch_token
alice = Identity.create("alice")
email_attestation = alice.attest({
"purpose": "email-confirmation",
"email": "alice@example.com",
})
decoded = validate_vouch_token(email_attestation)
print(get_app_claims(decoded))
# {"email": "alice@example.com"}
Example 2: Attestation, Vouch, and Trust Check
Bob attests to an email address, Alice vouches for that attestation, and a
verifier trusts Alice for email-confirmation.
from vouchsafe import Identity, get_app_claims, validate_trust_chain, validate_vouch_token
bob = Identity.create("bob")
email_attestation = bob.attest({
"purpose": "email-confirmation",
"email": "bob@example.com",
})
alice = Identity.create("alice")
vouch = alice.vouch(email_attestation, {"purpose": "email-confirmation"})
trusted_issuers = {alice.urn: ["email-confirmation"]}
print(get_app_claims(validate_vouch_token(email_attestation)))
result = validate_trust_chain(
[email_attestation, vouch],
email_attestation,
trusted_issuers,
["email-confirmation"],
)
assert result["valid"]
print("Email is trusted via:", result["trustRoot"])
Example 3: Revoking a Vouch
When Alice revokes her vouch, including that revocation in the token set causes the trust chain to fail.
from vouchsafe import Identity, validate_trust_chain
bob = Identity.create("bob")
alice = Identity.create("alice")
email_attestation = bob.attest({"purpose": "email-confirmation"})
vouch = alice.vouch(email_attestation, {"purpose": "email-confirmation"})
trusted_issuers = {alice.urn: ["email-confirmation"]}
assert validate_trust_chain(
[email_attestation, vouch], email_attestation, trusted_issuers,
["email-confirmation"],
)["valid"]
revoke = alice.revoke(vouch)
assert not validate_trust_chain(
[email_attestation, vouch, revoke], email_attestation, trusted_issuers,
["email-confirmation"],
)["valid"]
Example 4: Delegation with Constraints
Vouch tokens may carry application claims that constrain how a delegated permission is used. Vouchsafe verifies the identity, delegation, purpose, and revocation status; your application enforces its own domain constraints.
import time
from vouchsafe import Identity, get_app_claims, validate_trust_chain
alice = Identity.create("alice")
bob = Identity.create("bob")
now = int(time.time())
upload_request = bob.attest({
"purpose": "file:write",
"filename": "report.pdf",
"size": 3 * 1024 * 1024,
"exp": now + 600,
})
constrained_vouch = alice.vouch(upload_request, {
"purpose": "file:write",
"max_uses": 1,
"max_size": 5 * 1024 * 1024,
"exp": now + 300,
})
result = validate_trust_chain(
[upload_request, constrained_vouch],
upload_request,
{alice.urn: ["file:write"]},
["file:write"],
)
if not result["valid"]:
raise PermissionError("Upload not authorized")
claims = get_app_claims(result["subjectToken"]["decoded"])
if claims["size"] > 5 * 1024 * 1024:
raise ValueError("Upload exceeds the application limit")
Validation API
Vouchsafe has two validation layers:
- Token-level validation asks whether a token is a properly formed, correctly signed Vouchsafe token.
- Trust-chain validation asks whether that subject token is trusted for required purposes by one of your configured trust roots.
Token-level validation
Use validate_vouch_token when you need a verified Vouchsafe token:
from vouchsafe import get_app_claims, validate_vouch_token
decoded = validate_vouch_token(compact_jwt)
# Success means the token has valid structure, signature, URN/key binding,
# timestamps, and token-kind-specific fields.
app_claims = get_app_claims(decoded)
Use verify_vouch_token(vouch_jwt, subject_jwt) to confirm a vouch references
the intended subject. decode_token(raw_token) returns a trust-evaluation
object containing the raw token, decoded claims, and compact-JWT hash.
Trust-chain validation
result = validate_trust_chain(
tokens,
subject_token,
trusted_issuers,
required_purposes=None,
options=None,
)
tokens: all available tokens, including the subject. Combine actor-provided tokens with locally managed revocation or burn material here.subject_token: the token being evaluated for trust.trusted_issuers: a mapping of issuer URNs to permitted purposes.required_purposes: purposes required by this operation. Omit it or pass an empty list when any surviving purpose is acceptable.options["max_depth"]: optional maximum number of vouch hops.options["return_all_valid_chains"]: return every valid chain rather than stopping at the first match.options["strict"]: raiseTrustChainValidationErrorfor invalid ancillary token material. By default, invalid ancillary tokens are ignored and reported invalidation_errors.
On success, the result includes valid, subjectToken, trustRoot, chains,
effectivePurposes, and validation_errors. Each item in chains contains the ordered chain,
the surviving purposes, and the trustRoot URN. On failure, valid is false
and the chain list is empty.
validate_trust_chain raises TrustChainValidationError when the subject token
itself cannot be evaluated safely, or when the token bundle contains conflicting
tokens with the same issuer and JTI.
The evaluator cleans the graph before traversal: it decodes and validates
tokens, deduplicates them, processes revocations and burns, and only then
evaluates delegation and purpose attenuation. verify_trust_chain is the
legacy compatibility wrapper; use validate_trust_chain for new code.
CLI Tools
The Python package installs shell tools for identities and tokens. Their .py
suffix is intentional: it prevents collisions with identically purposed
commands installed by the Node package.
create_vouchsafe_id.py: generate a Vouchsafe identity and keypair.create_vouchsafe_token.py: mint attestations, vouches, and revocations.verify_vouchsafe_token.py: validate tokens and evaluate trust chains.
create_vouchsafe_id.py
create_vouchsafe_id.py --label alice -o alice.json
The resulting JSON contains the identity URN, base64-encoded Ed25519 keypair,
public-key hash, and specification version. Use --separate to write a .urn,
.public.pem, and .private.pem file instead.
--existing reuses the keypair from an existing identity JSON file and derives a
new identity document for the label you provide. The command refuses to
overwrite the original identity file in place.
Usage: create_vouchsafe_id.py [-h] -l LABEL [-s] [-q] [-e EXISTING] [-o OUTPUT]
[--public PUBLIC_FILE] [--private PRIVATE_FILE]
-l, --label LABEL Identity label (required)
-s, --separate Output URN and PEM files separately
-q, --quiet Suppress status output
-e, --existing FILE Load an existing identity JSON file
-o, --output FILE Output filename or prefix
--public FILE Existing public key PEM file
--private FILE Existing private key PEM file
create_vouchsafe_token.py
# Attestation with a purpose
create_vouchsafe_token.py -i alice.json -p msg-signing > token.jwt
# Vouch for an existing token
create_vouchsafe_token.py -i alice.json --vouch -t subject.jwt \
-p email-confirmation -o vouch.jwt
# Revoke a previous attestation or vouch
create_vouchsafe_token.py -i alice.json --revoke -t vouch.jwt -o revoke.jwt
Usage: create_vouchsafe_token.py [-h] -i IDENTITY [-f CLAIMS] [-c KEY=VALUE]
[-p PURPOSE] [-e EXPIRES] [-o OUTPUT]
[-t TOKEN_FILE] [-T TOKEN]
[--attest | --vouch | --revoke]
-i, --identity FILE Identity JSON file (required)
-f, --claims FILE Claims JSON object
-c, --claim KEY=VALUE Additional claim (repeatable; JSON values accepted)
-p, --purpose PURPOSE Purpose (repeatable; attest/vouch)
-e, --expires SECONDS Expiration (default 86400; 0 disables expiration)
-q, --quiet Suppress warnings and status output
-v, --verbose Emit additional status output
-t, --token-file FILE Subject token for vouch/revoke
-T, --token TOKEN Subject token string for vouch/revoke
The CLI intentionally creates attestations, vouches, and revocations. Use
create_burn_token from the functional API to issue a burn token.
verify_vouchsafe_token.py
# Validate token structure, signature, and URN binding
verify_vouchsafe_token.py -t token.jwt
# Evaluate a chain with a local trust policy and emit shell variables
verify_vouchsafe_token.py -E -p email-confirmation \
--trusted trusted.json -t chain.txt -O unix
Usage: verify_vouchsafe_token.py [-h] [-t TOKEN_FILE] [-T TOKEN]
[-O {json,unix}] [-f FIELD] [-E]
[-P PREFIX] [--trusted FILE]
[--trusted-issuer URN=purpose[,purpose...]]
[-p PURPOSE]
-t, --token-file FILE File with one or more tokens (first is subject)
-T, --token TOKEN Token string (first is subject)
-O, --output FORMAT json or unix
-f, --field DOTPATH Output only this field (repeatable)
-E, --extended Require trust for the supplied purpose(s)
-q, --quiet Suppress warnings and status output
-v, --verbose Emit additional status output
-p, --purpose PURPOSE Required purpose (repeatable)
--trusted FILE Trusted issuers JSON or text file
--trusted-issuer VALUE Inline URN=purpose[,purpose...] entry
Trusted issuers JSON:
{
"urn:vouchsafe:alice...": ["email-confirmation", "webhook:order_placed"],
"urn:vouchsafe:bob...": ["email-confirmation"]
}
The text form has one URN purpose1 purpose2 entry per line. Token files may
contain one token per line, or a single whitespace-delimited line. In either
case, the first token is the subject and the rest are additional trust material.
Identity Class API (High-level)
Identity is the recommended entry point unless you need low-level control.
Identity.create(label): generate a new identity with URN and keypair.Identity.from_data({"urn": urn, "keypair": keypair}, verify=True): rehydrate stored identity material and verify the URN/key binding by default.Identity.from_keypair(label, keypair): build an identity from an existing keypair.identity.urn: the self-verifying issuer URN; safe to share.identity.attest(claims=None): issue an attestation.identity.vouch(subject_token, options=None): issue a vouch.identity.revoke(token, options=None): revoke an attestation or vouch.identity.verify(token): validate one Vouchsafe token.identity.to_dict(): export JSON-ready identity data for storage, includingurn,keypair,publicKeyHash, andversion.
Functional API (Low-level)
Use these building blocks when the Identity facade is not the right fit.
Identity helpers
create_vouchsafe_identity(label): generate{ "urn", "keypair", ... }.create_vouchsafe_identity_from_keypair(label, keypair): derive an identity from an existing base64-encoded Ed25519 keypair.verify_urn_matches_key(urn, public_key): return whether the URN matches the public key.
JWT helpers
create_jwt(issuer, issuer_key, private_key, claims=None, options=None, *, exclude_iss_key=False): create an EdDSA JWT. Setexclude_iss_keyonly for non-Vouchsafe compatibility cases.verify_jwt(token, *, public_key_override=None, verify_issuer_key=True): verify a JWT signature and, by default, the issuer-key relationship.decode_jwt(token, *, full=False): decode without signature verification. Withfull=True, return header and payload.get_app_claims(decoded_token): remove JWT and Vouchsafe housekeeping claims.
Token creation
create_attestation(issuer, issuer_keypair, claims=None)create_vouch_token(subject_jwt, issuer, issuer_keypair, claims=None)revoke_vouch_token(token, issuer_keypair, claims=None)create_revoke_token(claims, issuer, issuer_keypair)create_burn_token(issuer, issuer_keypair, claims=None)hash_jwt(token): calculate the compact JWT hash used for linkage and revocation.
Token validation and inspection
validate_vouch_token(token): full Vouchsafe structure, URN-binding, and signature validation.verify_vouch_token(vouch_jwt, subject_jwt): verify a vouch's linkage to its subject token.is_revocation_token(decoded_token)/is_burn_token(decoded_token): classify a decoded token and raise if the matching token is malformed.decode_token(raw_token): create the decoded trust-evaluation token object.
Trust-chain evaluation
validate_trust_chain(tokens, start_token, trusted_issuers, purposes=None, options=None): validate delegation, revocations, burns, and purpose attenuation.verify_trust_chain(subject_token, trusted_issuers, options=None): legacy compatibility wrapper. Prefervalidate_trust_chainin new code.
Interoperability Test Corpus
The repository includes helper scripts under tests/helpers/ for cross-language
interoperability testing. They are used to prove two directions:
- Python can generate Vouchsafe identities and tokens that another implementation, such as JavaScript, validates correctly.
- Python can validate a corpus generated by another implementation, as long as that corpus follows the same manifest format.
The corpus is file-based so it can move cleanly between repositories and CI jobs. A generated corpus contains:
manifest.json: the test plan and expected outcomes;identities/: exported identity JSON files;tokens/: individual attestation, vouch, revocation, burn, and negative-test JWTs;bundles/: newline-delimited token sets for trust-chain tests; andtrusted/: trusted-issuer policy JSON files.
Generate a Python-produced corpus
python3 tests/helpers/generate_interop_assets.py
The script prints the output directory path. Pass a directory argument to choose where the corpus is written:
python3 tests/helpers/generate_interop_assets.py /tmp/vouchsafe-interop-py
The current Python corpus includes cases for:
- identity round-tripping;
- valid attestation and vouch verification;
- valid multi-hop trust chains;
- rejection after revocation;
- rejection after an identity burn;
- rejection of a tampered
vch_sum; - purpose attenuation across delegated vouches;
- rejection of purpose expansion;
- rejection of malformed revocation tokens; and
- rejection of non-Vouchsafe external JWT subjects.
The generated manifest.json includes a producer field. Python writes
"producer": "py". Another implementation can write its own producer label and
reuse the same asset layout.
Validate a corpus with Python
python3 tests/helpers/validate_interop_assets.py /tmp/vouchsafe-interop-py
The validator reads manifest.json, resolves the referenced assets, and runs
the appropriate Python library checks for each case type. By default it prints a
JSON summary and exits non-zero if any case fails.
Optional flags:
--tap: continue through all cases and emit TAP output instead of stopping at the first failure.--skip-unknown-types: skip manifest case types that this Python validator does not implement yet.
Example:
python3 tests/helpers/validate_interop_assets.py /tmp/vouchsafe-interop-js \
--skip-unknown-types --tap
This is the intended cross-language workflow:
- Generate a corpus in Python and validate it in JavaScript.
- Generate a corpus in JavaScript and validate it with
tests/helpers/validate_interop_assets.py. - Keep both implementations aligned by adding new manifest case types only when the other language can either validate them or explicitly skip them.
The automated Python test tests/test_interop.py covers the local half of this
workflow by generating a Python corpus and validating it with the Python
validator.
Learn More
- getvouchsafe.org: conceptual overview and use cases.
- Vouchsafe Specification: token format, URN rules, and trust-chain semantics.
- "Vouchsafe: A Zero-Infrastructure Capability Graph Model for Offline Identity and Trust" - The formal model and security analysis behind Vouchsafe
Vouchsafe is designed to be self-contained, zero-infrastructure, and human-scale: identity you can prove, trust you can carry.
License
BSD 3-Clause License
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 vouchsafe-1.0.0.tar.gz.
File metadata
- Download URL: vouchsafe-1.0.0.tar.gz
- Upload date:
- Size: 39.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c201766a268459c667cbb926c7cded076cd64eeec79366a2d18b3505d963f6c
|
|
| MD5 |
e307db00c29c019e846feaa32285cd47
|
|
| BLAKE2b-256 |
df335738a600821f03d77147a5f98d4c485b466787669d0f9c981a1aad2fdfc9
|
File details
Details for the file vouchsafe-1.0.0-py3-none-any.whl.
File metadata
- Download URL: vouchsafe-1.0.0-py3-none-any.whl
- Upload date:
- Size: 32.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd256a4e55a28e1ebde56d8a75bd2dc6d1cf3565f36eab821a2183a24e656bd0
|
|
| MD5 |
04e54c6f232b94c0fa39a521564ddd9a
|
|
| BLAKE2b-256 |
d877674c4ff7f10bd7a09edb4844a1d7649f6e940126ac9a7b0e60a573c5273e
|