Register a device
Mint an operator token from a device key pair — no invite code, no admin. The caller proves possession of a NATS user nkey by signing a domain-separated message; the orchestrator returns a fresh operator token seeded with the default promotional credits.
Use this for the anonymous activation funnel. To join an existing grant space
instead, use the collaborator-invite path (POST /api/invites/accept).
1. Mint (or load) a device identity
DeviceIdentity (in quorum-crypto-core, default device feature) wraps a
NATS user nkey:
use quorum_crypto_core::DeviceIdentity;
let id = DeviceIdentity::generate(); // fresh U… user key
let seed = id.seed()?; // persist this (mode 0600); never transmit
let pubkey = id.public_key(); // U… — sent to the server
Rehydrate a persisted device on the next run with DeviceIdentity::from_seed(&seed)?
(it rejects a non-user key).
2a. Create path (first registration)
Sign the static, domain-separated message and post it. No nonce.
let msg = format!("nsed-operator-register:{pubkey}");
let signature = id.sign_hex(msg.as_bytes())?; // lowercase hex
POST /register
{ "pubkey": "<U…>", "signature": "<hex>", "display_name": "optional" }
A replayed create (same pubkey) collides with 409 Conflict — one device
maps to exactly one operator (op_<sha256(pubkey)[..16]>), so a replay can't
mint a second free operator.
2b. Login path (idempotent, multi-device)
To re-authenticate a known device — or deterministically auto-sync a second device to the same operator — use a one-time nonce instead of the static message:
GET /auth/challenge?pubkey=<U…> → { "nonce": "<one-time>" }
Sign "{nonce}:{pubkey}", then post the nonce alongside the signature:
POST /register
{ "pubkey": "<U…>", "signature": "<hex over nonce:pubkey>", "nonce": "<one-time>" }
With a valid nonce a re-seen pubkey returns its existing token (no 409); the nonce is consumed (replay-safe).
3. Store the token
{ "name": "op_1a2b3c4d5e6f7081", "token": "…", "budget": 0.0 }
token is shown once — store it securely; it authenticates subsequent API
calls as this operator. budget is the seeded promotional credit, set by the
deploy's self-serve grant policy (0 by default).
Constraints
pubkeymust be a NATS user key (U…); account/server keys are rejected.grantsandpublicare rejected if set — a self-serve operator may not self-assign tenancy grants (that would bypass the paid-ensemble gate). Join a grant space via the collaborator-invite path instead.- Signatures are lowercase hex over the exact message bytes; a wrong message, encoding, or key type fails verification.
See also
personayaml shapesDeviceIdentityAPI —quorum_crypto_core::DeviceIdentityrustdoc.