Completes the parallel tutorial set across all three implementations.
Python now has the same friendly step-by-step walkthrough that the
Rust and Node sides have had since the original tutorial commits.
Python tutorial content mirrors the others 1:1, adapted for the
Python invocation style (.venv/bin/python kez_cli.py …), plus:
• Programmatic section uses Python imports (NostrSecret.from_nsec,
sign_claim, default_registry, etc.) instead of the TS imports
from the Node tutorial.
• Same "Recovery phrases" mini-chapter as rust/nodejs — both 12-word
AND 24-word are explained, with the entropy table, picking guide,
hardware-wallet-incompatibility callout, concrete backup advice
("pencil + paper, numbered words, fireproof, don't split,
don't permute"), and "Working with phrases later" examples
(`identity mnemonic`, `identity from-mnemonic`).
• Notes that `sigchain publish` isn't in the Python CLI yet (only
add/revoke/show/export) — match the actual current surface; the
JSONL the Python CLI produces is byte-compatible with Rust/Node,
so users can build the chain in Python and publish via either
of the other CLIs in the meantime.
• Troubleshooting includes ModuleNotFoundError: kez (a Python-
specific footgun when running outside the venv).
• Links to ../rust/TUTORIAL.md and ../nodejs/TUTORIAL.md as parallel
references throughout.
python/README.md now opens with the same "New to KEZ? Read TUTORIAL.md"
callout as the rust and nodejs READMEs do.
Root README's quick-start blocks for each implementation now reference
BOTH the impl README (reference) AND the impl TUTORIAL (step-by-step,
on-ramp) instead of just the README.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KEZ — Python Implementation
KEZ is a portable, decentralized identity graph. It lets one person say:
"These accounts, keys, domains, and identities are all me."
…without depending on any central authority. Every connection is proven by a
signature against a key the user already controls. The protocol is specified in
../SPEC.md; this directory is the Python implementation of that
spec.
It is wire-compatible with the Rust and Node
implementations: a claim signed here verifies there and vice versa, in every
direction. The repo-root crosstest.sh proves it.
What's in this directory
python/
├── pyproject.toml Package metadata + entry point (`kez`)
├── requirements.txt Runtime deps (cryptography, zstandard)
├── kez_cli.py Standalone launcher (used by ../crosstest.sh)
└── kez/
├── jcs.py RFC 8785 JSON canonicalization
├── bech32.py Bech32 (nsec/npub) encode/decode
├── schnorr.py Pure-Python BIP-340 Schnorr over secp256k1
├── identity.py `system:identifier` parsing + normalization
├── keys.py NostrSecret / Ed25519Secret signers + verification
├── envelope.py Envelope, claim & sigchain-event payloads, sign/verify
├── encodings.py JSON / compact (kez:z1:) / markdown / DNS / JSONL bundle
├── sigchain.py Append-only signed sigchain + on-disk storage
├── channels.py parse_proof across all four wire encodings
└── cli.py The `kez` command-line interface
Setup
New to KEZ? Read
TUTORIAL.md— a friendly step-by-step walkthrough that takes you from "I have a nostrnsec" to "I have a verified, published sigchain," including the BIP-39 recovery-phrase backup (12 or 24 words). It assumes nothing.This README is the reference; the tutorial is the on-ramp.
cd python
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
Then run the CLI either through the launcher or the installed entry point:
.venv/bin/python kez_cli.py identity new
# or, after `.venv/bin/pip install -e .`:
.venv/bin/kez identity new
Crypto stack
| Concern | Choice | Why |
|---|---|---|
| JCS (RFC 8785) | hand-rolled (jcs.py) |
KEZ payloads are strings/ints/objects only; a tiny dependency-free canonicalizer guarantees byte-identical output |
| secp256k1 Schnorr (BIP-340) | pure-Python reference (schnorr.py) |
the native coincurve/secp256k1 bindings fail to build on recent CPython; signing fixed-size digests is fast enough for a CLI. Signs with zero aux-rand to match Rust/Node exactly |
| Ed25519 (RFC 8032) | cryptography |
well-maintained, ships wheels |
| zstd | zstandard |
level 3, matching the other impls; decompressobj handles frames without a content-size header |
| Bech32 | hand-rolled (bech32.py) |
the BIP-173 reference is small and avoids a dependency |
All signing is deterministic, so the same claim signs identically every time.
CLI reference
kez identity new [--key-type nostr|ed25519]
kez claim create <subject> (--nsec <nsec> | --ed25519-seed <hex>)
[--format json|compact|markdown] [--out <path>]
kez claim dns <domain> (--nsec <nsec> | --ed25519-seed <hex>)
kez verify file <path>
kez sigchain add <subject> (--nsec | --ed25519-seed) [--proof-url <url>]
kez sigchain revoke <subject> (--nsec | --ed25519-seed)
kez sigchain show [--primary <id> | --nsec | --ed25519-seed]
kez sigchain export [--primary <id> | --nsec | --ed25519-seed]
[--format jsonl|compact] [--out <path>]
Sigchain state lives in ~/.kez/sigchains/<primary-with-colons-as-underscores>.jsonl
— the same paths the Rust and Node CLIs use, so chains built by one are
readable by the others.
What's not done yet
Matching the gap list in ../rust/README.md, the Python
CLI implements claim, verify file, and sigchain add/revoke/show/export.
Not yet ported: verify id channel resolution (network fetch), sigchain publish, and the rotate/add_device ops.
License
Dual-licensed under MIT or Apache-2.0.