ZAAR

Authentication and authorization

Normative. Designed so new ways to sign in can be added without changing the core or the journal.

Layers

request ──▶ Authenticator (gateway)  ──▶ Principal + key id ──▶ core: authorize(command)
            verifies a credential           (journaled actor)       scopes × ownership × status

Principals

Principal = Network            network operators (assets, markets, mint, orgs, fees)
          | Org(org)           an org's backend and staff
          | App(app)           a specific app of an org (attribution; narrower keys)
          | User(user)         an end user acting for themselves

API keys

ApiKey { id, public_key [u8;32] Ed25519, principal, scopes, accounts: Option<Vec<id>>,
         ip_allow: Option<Vec<cidr>>, label, created, expires: Option<ts>, status }

Scopes

scope allows
READ balances, orders, trades, transfers of reachable accounts
TRADE place, cancel, amend orders
TRANSFER send assets from reachable accounts
USERS_MANAGE create and disable users and accounts in the org
KEYS_MANAGE create and revoke keys at or below the holder
ORG_ADMIN org settings, apps
NETWORK_ADMIN assets, markets, fees, orgs, mint, burn (Network principal only)

Reachable accounts

principal reachable accounts
User(u) accounts owned by u
App(a), Org(o) accounts of every user of the org, plus the org's own accounts
Network all accounts (admin only; trading on behalf of users is not offered)

The Network principal reaches every account for READ and admin commands (mint, burn, account status), but never for TRADE or TRANSFER. For entities of an org (apps, users, accounts), Org and App principals reach their own org, User principals only themselves (a user key with USERS_MANAGE may open accounts for its own user). A target outside the key's reach is NotReachable.

accounts on a key narrows this further. Org and app keys choose the account per request with X-ZAAR-ACCOUNT (the Stripe/Alpaca broker pattern). User keys may omit it if the user has one account.

Request signing (v1)

X-ZAAR-APIKEY      base64url(public key)
X-ZAAR-TIMESTAMP   ms since epoch
X-ZAAR-RECV-WINDOW optional, ms, default 5000, max 60000 (decimals allowed: 0.5 = 500 µs)
X-ZAAR-ACCOUNT     optional account id
X-ZAAR-SIGNATURE   base64url(Ed25519 signature of the payload below)

payload = "ZAAR-v1\n" + timestamp + "\n" + METHOD + "\n" + path + "\n" + rawQuery + "\n"
          + account + "\n" + rawBody

Genesis and the first admin

The operator generates a key pair locally (zaar keys new). Its public key goes into the daemon config. On an empty journal the daemon writes Genesis { admin_public_key, … }, which creates the zaar org, the system accounts and a Network key with all scopes. The private key exists only on the operator's machine.

Reserved for later (no schema change needed)