KEZ is a portable, decentralized identity graph: a person signs claims
linking their many accounts, publishes those claims in places only the
claimed account can publish to, and anyone can verify the connections
without trusting a central server.
Layout
------
- SPEC.md Language-agnostic protocol spec (v0.2)
- rust/ Rust implementation: kez-core, kez-channels, kez-cli
- nodejs/ TypeScript port at full parity
- rust-sig-server/ Optional axum + SQLite storage server for sigchains
- crosstest.sh Cross-implementation interop harness
Capabilities (both implementations, byte-compatible)
----------------------------------------------------
- Two primary-key algorithms: nostr/secp256k1 Schnorr (BIP-340) and
Ed25519 (RFC 8032). Identifiers: nostr:npub1... and ed25519:<hex>.
- JCS (RFC 8785) canonicalization for everything signed.
- Four proof encodings: JSON envelope, compact (kez:z1:<base64url(zstd(json))>),
Markdown fence, DNS TXT.
- Five channel plugins (no API keys, no auth needed for any of them):
dns: system resolver, _kez.<domain> TXT records
github: public gist scan + <user>/<user> profile README fallback
nostr: kind-30078 events from default relays
bluesky: public AppView author feed
ap: WebFinger + actor JSON (alias mastodon:)
- Identical CLI surface:
kez identity new [--key-type nostr|ed25519]
kez claim create <subject> (--nsec | --ed25519-seed) [--format ...] [--out ...]
kez claim dns <domain> (--nsec | --ed25519-seed)
kez verify file <path>
kez verify id <identifier>
kez sigchain add|revoke|show|export|publish
- Sigchains: append-only signed log per primary, hash-chained per spec §6,
stored locally at ~/.kez/sigchains/, exportable as JSONL or kez:zc1: bundle.
- Sigchain publish destinations: chain server, web (file dump), DNS (zone
record print), nostr (kind-30078 wrapping event).
kez-sig-server
--------------
Optional storage tier. Axum + SQLite, single binary, no external deps.
- No auth — the cryptography is the access control. The server validates
every signature, every seq, every prev hash before storing.
- REST API: POST /v1/sigchains/{scheme}/{id}/events (append signed event,
201 with new head hash or 4xx); GET /{scheme}/{id} (full chain as JSONL);
GET /head; GET /healthz.
- Designed for one central instance for now; the design doesn't preclude
running more later (clients gain a configurable list, verifiers
reconcile per spec §6.2).
- Channel-based publishing remains the always-available fallback if the
server is unavailable.
Tests
-----
- rust/ 99 tests
- rust-sig-server/ 10 integration tests (real HTTP, real SQLite)
- nodejs/ 91 tests (vitest)
- crosstest.sh 19 cross-impl scenarios — proves JCS bytes,
Schnorr + Ed25519 sigs, all four claim encodings,
and the sigchain JSONL bundle are byte-compatible
between Rust and Node in both directions.
What's not done yet
-------------------
- verify id consulting the sigchain for revocations (data path exists,
just not wired into the verifier output).
- rotate and add_device sigchain ops (types reserved).
- expires_at enforcement during claim verification.
- Typed VerificationStatus.status reflecting the five failure modes.
- Auth-required publishers (GitHub gist, Bluesky, ActivityPub).
104 lines
3.7 KiB
Markdown
104 lines
3.7 KiB
Markdown
# KEZ
|
||
|
||
KEZ is a portable, decentralized identity graph. It lets a person say:
|
||
|
||
> "These accounts, keys, domains, and identities are all me."
|
||
|
||
…without depending on any central authority. Every connection is proven by a
|
||
cryptographic signature against a key the user already controls (a nostr key,
|
||
an Ed25519 key, etc.), and the proofs are published in places only the
|
||
claimed account itself can publish to (their gist, their DNS, their nostr
|
||
relay event). Anyone can verify the graph without trusting a server.
|
||
|
||
## Repository layout
|
||
|
||
```
|
||
.
|
||
├── SPEC.md ← The protocol. Language-agnostic, normative.
|
||
├── rust/ ← Rust implementation (kez-core, kez-channels, kez-cli)
|
||
├── nodejs/ ← TypeScript/Node implementation (same shape, same CLI)
|
||
├── rust-sig-server/ ← Optional HTTP store for sigchains (axum + SQLite)
|
||
├── crosstest.sh ← Interop test: artifacts move between implementations
|
||
└── README.md ← (this file)
|
||
```
|
||
|
||
Two parallel implementations. **Wire-compatible**: a claim signed in Rust
|
||
verifies in Node and vice versa. The cross-test harness proves it.
|
||
|
||
A separate [`rust-sig-server/`](rust-sig-server/) crate provides an optional
|
||
HTTP storage tier for sigchains — useful when a user doesn't want to set up
|
||
DNS/hosting/nostr, but **never required**; the protocol stays decentralized.
|
||
|
||
## Quick start
|
||
|
||
### Rust
|
||
```sh
|
||
cd rust
|
||
cargo build
|
||
cargo test # 81 tests
|
||
cargo run -p kez-cli -- verify id github:jason
|
||
```
|
||
Full guide: [`rust/README.md`](rust/README.md).
|
||
|
||
### Node.js
|
||
```sh
|
||
cd nodejs
|
||
npm install
|
||
npm test # 72 tests
|
||
npm run cli -- verify id github:jason
|
||
```
|
||
Full guide: [`nodejs/README.md`](nodejs/README.md).
|
||
|
||
## Cross-testing
|
||
|
||
```sh
|
||
./crosstest.sh
|
||
```
|
||
|
||
Runs 19 scenarios that swap implementations at the artifact boundary:
|
||
|
||
| # | Scenario |
|
||
|---|---|
|
||
| 1–2 | nostr-signed JSON claim, both directions |
|
||
| 3–4 | nostr-signed compact claim, both directions |
|
||
| 5–6 | nostr-signed markdown claim, both directions |
|
||
| 7–8 | nostr-signed DNS zone form, both directions |
|
||
| 9–10 | ed25519-signed JSON claim, both directions |
|
||
| 11–12 | ed25519-signed compact claim, both directions |
|
||
| 13–14 | ed25519-signed markdown claim, both directions |
|
||
| 15 | rust builds 3-event nostr sigchain → node parses + shows |
|
||
| 16 | rust-exported sigchain JSONL == node-exported JSONL (byte-identical) |
|
||
| 17 | node builds 3-event nostr sigchain → rust parses + shows |
|
||
| 18 | rust builds ed25519 sigchain → node parses + shows |
|
||
| 19 | node builds ed25519 sigchain → rust parses + shows |
|
||
|
||
If all 19 pass: JCS canonicalization, both signature suites (BIP-340 Schnorr
|
||
and Ed25519), the compact `kez:z1:` zstd+base64url encoding, the Markdown
|
||
fence, the DNS TXT shape, and the sigchain JSONL bundle format are all
|
||
byte-compatible across implementations.
|
||
|
||
Pass `-v` for verbose output (echoes intermediate commands and proofs).
|
||
|
||
## What ships in v0.2
|
||
|
||
- **Five channel plugins** in each implementation: `dns:`, `github:`,
|
||
`nostr:`, `bluesky:`, `ap:` (alias `mastodon:`).
|
||
- **Four wire encodings**: JSON, compact, Markdown fence, DNS TXT.
|
||
- **Two primary-key algorithms**: nostr/secp256k1 Schnorr (BIP-340) and
|
||
Ed25519 (RFC 8032).
|
||
- **JCS (RFC 8785)** canonicalization for everything signed.
|
||
- **No API keys required for any channel.**
|
||
|
||
## What's not done yet
|
||
|
||
Tracked in both [`rust/README.md`](rust/README.md#whats-not-done-yet) and the
|
||
spec:
|
||
|
||
- Sigchain walker (types exist; no append/walk/revoke flow yet).
|
||
- `expires_at` enforcement during verify.
|
||
- Typed `VerificationStatus.status` reflecting the five failure modes.
|
||
|
||
## License
|
||
|
||
Dual-licensed under MIT or Apache-2.0.
|