"""CLI entry point for zkac-node.""" from __future__ import annotations import argparse import json import sys from . import client, store # ── identity ────────────────────────────────────────────────────────── def _cmd_identity_init(_args): if store.identity_exists(): print("identity already exists") ident = store.load_identity() print(f" issuance public key: {ident['issuance_pk'].hex()}") return path = store.create_identity() ident = store.load_identity() print("created identity") print(f" issuance public key: {ident['issuance_pk'].hex()}") print(f" (share this key out-of-band to receive credentials)") print(f" stored in {path}") def _cmd_identity_show(_args): ident = store.load_identity() print(f" issuance pk: {ident['issuance_pk'].hex()}") regs = client.list_own_registries() if regs: print(f" registries owned: {len(regs)}") for r in regs: print(f" {r['registry_id']} @ {r['server']} roles={r['roles']}") creds = store.list_credentials() if creds: print(f" credentials: {len(creds)}") for reg_hex, role in creds: print(f" {reg_hex[:16]}… / {role}") # ── server ──────────────────────────────────────────────────────────── def _cmd_serve(args): from .server import serve serve(args.data_dir, args.host, args.port) def _cmd_server_pin(args): pk_hex = args.key import base64 pk_bytes = bytes.fromhex(pk_hex) store.pin_server(args.server, base64.b64encode(pk_bytes).decode()) print(f"pinned {args.server} -> {pk_hex[:16]}…") # ── registry ────────────────────────────────────────────────────────── def _cmd_registry_create(args): roles = [r.strip() for r in args.roles.split(",")] rid = client.create_registry(args.server, roles) print(f"registry created: {rid}") print(f" roles: {', '.join(roles)}") def _cmd_registry_update(args): add = [r.strip() for r in args.add_roles.split(",")] client.update_registry(args.server, args.registry, add) print(f"registry updated: {args.registry[:16]}…") print(f" added roles: {', '.join(add)}") def _cmd_registry_get(args): info = client.get_registry(args.server, args.registry) print(f"registry: {args.registry}") print(f" state bytes: {len(info.get('state_bytes_b64', ''))} chars (b64)") def _cmd_registry_list(_args): regs = client.list_own_registries() if not regs: print("no registries") return for r in regs: print(f" {r['registry_id']} @ {r['server']} roles={r['roles']}") # ── grant (admin-initiated) ────────────────────────────────────────── def _cmd_grant(args): gid = client.grant(args.server, args.registry, args.role, args.to) print(f"granted {args.role!r} to {args.to[:16]}…") print(f" grant id: {gid}") print(f" recipient can collect with:") print(f" zkac-node collect {args.server}:{args.registry}:{args.role}") # ── credentials list / collect ──────────────────────────────────────── def _cmd_credentials_list(args): local = store.list_credentials() print("local credentials:") if not local: print(" (none)") for reg_hex, role in local: print(f" {reg_hex}:{role}") servers = list(args.server or []) for s in store.list_pinned_servers(): if s not in servers: servers.append(s) if not servers: print("\n(no servers to query; pass --server host:port to check for pending)") return print("\npending grants:") any_pending = False for srv in servers: try: grants = client.list_pending(srv) except Exception as exc: print(f" [{srv}] error: {exc}") continue for g in grants: any_pending = True rid = g.get("registry_id", "?") role = g.get("role_name", "?") if rid != "?" and role != "?" and store.has_credential(rid, role): note = " (already collected locally)" else: note = "" print(f" {srv}:{rid}:{role}{note}") if not any_pending: print(" (none)") def _cmd_collect(args): result = client.collect(args.spec) print("collected credential") print(f" registry: {result['registry_id']}") print(f" role: {result['role']}") print(f" server: {result['server']}") # ── auth ────────────────────────────────────────────────────────────── def _cmd_auth(args): resp = client.authenticate(args.registry, args.role, server=args.server) print(json.dumps(resp, indent=2)) # ── argparse wiring ─────────────────────────────────────────────────── def main(): p = argparse.ArgumentParser(prog="zkac-node") sub = p.add_subparsers(dest="group", required=True) # identity id_p = sub.add_parser("identity", help="manage local identity") id_sub = id_p.add_subparsers(dest="action", required=True) c = id_sub.add_parser("init", help="generate a new identity") c.set_defaults(func=_cmd_identity_init) c = id_sub.add_parser("show", help="show identity + registries + credentials") c.set_defaults(func=_cmd_identity_show) # serve c = sub.add_parser("serve", help="run as a ZKAC server node") c.add_argument("--data-dir", required=True, help="server data directory") c.add_argument("--host", default="127.0.0.1") c.add_argument("--port", type=int, default=9800) c.set_defaults(func=_cmd_serve) # server pin srv_p = sub.add_parser("server", help="manage server pins") srv_sub = srv_p.add_subparsers(dest="action", required=True) c = srv_sub.add_parser("pin", help="pin a server's public key") c.add_argument("server", help="host:port") c.add_argument("--key", required=True, help="server public key (hex)") c.set_defaults(func=_cmd_server_pin) # registry reg_p = sub.add_parser("registry", help="manage registries") reg_sub = reg_p.add_subparsers(dest="action", required=True) c = reg_sub.add_parser("create", help="create a new registry on a server") c.add_argument("server", help="host:port") c.add_argument("--roles", required=True, help="comma-separated role names") c.set_defaults(func=_cmd_registry_create) c = reg_sub.add_parser("update", help="add roles to a registry you own") c.add_argument("server", help="host:port") c.add_argument("--registry", required=True) c.add_argument("--add-roles", required=True, help="comma-separated new roles") c.set_defaults(func=_cmd_registry_update) c = reg_sub.add_parser("get", help="fetch registry info from server") c.add_argument("server", help="host:port") c.add_argument("--registry", required=True) c.set_defaults(func=_cmd_registry_get) c = reg_sub.add_parser("list", help="list local registries (owned)") c.set_defaults(func=_cmd_registry_list) # grant c = sub.add_parser("grant", help="issue a credential to a recipient (admin)") c.add_argument("--server", required=True, help="host:port") c.add_argument("--registry", required=True) c.add_argument("--role", required=True) c.add_argument("--to", required=True, help="recipient's issuance public key (hex)") c.set_defaults(func=_cmd_grant) # credentials list cred_p = sub.add_parser("credentials", help="credentials (local + pending)") cred_sub = cred_p.add_subparsers(dest="action", required=True) c = cred_sub.add_parser("list", help="show local + pending credentials") c.add_argument("--server", action="append", help="server to query (host:port); may be repeated") c.set_defaults(func=_cmd_credentials_list) # collect c = sub.add_parser("collect", help="fetch and finalize a pending credential") c.add_argument("spec", help="host:port:registry_id:role") c.set_defaults(func=_cmd_collect) # auth c = sub.add_parser("auth", help="authenticate to a server with a credential") c.add_argument("--registry", required=True) c.add_argument("--role", required=True) c.add_argument("--server", default=None, help="host:port (optional if known)") c.set_defaults(func=_cmd_auth) args = p.parse_args() if not hasattr(args, "func"): p.print_help() sys.exit(1) args.func(args) if __name__ == "__main__": main()