Jason Tudisco b1240c13e5 Add Python implementation and cross-test interop
Add a Python port of the KEZ CLI under python/, mirroring the Rust and
Node implementations command-for-command and byte-for-byte:

- Pure-Python JCS (RFC 8785), BIP-340 Schnorr, and Bech32; cryptography
  for Ed25519 and zstandard for the compact zstd framing.
- Full CLI: identity new, claim create/dns, verify file, and
  sigchain add/revoke/show/export.

Wire Python into crosstest.sh with 35 new scenarios covering Python
against both Rust and Node, in every direction, across all wire formats,
both key types, DNS proofs, and sigchains (incl. JSONL byte parity).
All 55 scenarios pass.

Update root README and .gitignore for the new implementation.
2026-06-01 13:29:45 -06:00
..

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

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.