NOOSChain Authentication And Identities
NOOSChain uses cryptographic identities for transaction authorization, replay protection, encrypted-record recipient identity, node identity, and future smart contract caller identity. This is not a token custody model; the core concepts are identities, signers, keychains, credentials, and node identities.
Transactions are signed with Ed25519. The signed canonical envelope contains nonce, createdAt for new production transactions, type, signerPublicKey, and payload. The signature is computed over deterministic canonical JSON. The transaction hash is computed from { envelope, signature }. Canonical serialization is consensus-critical.
Replay protection is tracked in account_nonces by logical identity: owner_type, owner_id, and current_nonce. The nonce is not keyed by raw public key, so key rotation cannot reopen replay windows. During block execution, NOOSChain verifies the signature, resolves the signer identity, consumes exactly current_nonce + 1, and then executes the protocol-versioned domain handler.
ROTATE_USER_KEY is signed by the user’s current active key. A successful rotation updates users.public_key and preserves the same logical user nonce stream. The old key becomes invalid immediately.
HTTP APIs prefer bearer sessions. POST /auth/challenge creates a random, short-lived, single-use challenge. POST /auth/login verifies the signed challenge and returns a short-lived bearer token. x-noos-public-key is allowed only outside NODE_ENV=production as a development convenience.
Injectable clock and expiration testing
Auth expiration uses the Clock abstraction in src/time/clock.ts. Production servers default to SystemClock, which delegates to normal system time. Tests and dev verification scripts can inject ManualClock into buildServer() so challenge and bearer-token expiry can be advanced deterministically.
NOOSChain deliberately does not monkeypatch Date or mutate process-wide time. Each Fastify instance owns its auth session store and clock, so one integration test can expire a bearer token without affecting consensus, sync, gossip, or another server in the same process.
Challenge validation uses the injected clock for expiresAt, enforces single-use semantics, and returns explicit errors such as AUTH_CHALLENGE_EXPIRED, AUTH_CHALLENGE_ALREADY_USED, and AUTH_CHALLENGE_INVALID_SIGNATURE. Bearer validation also uses the injected clock and returns AUTH_TOKEN_EXPIRED, AUTH_TOKEN_INVALID, or AUTH_TOKEN_MISSING.
In production mode, protected routes require bearer auth. The development x-noos-public-key identity header is accepted only when allowDevPublicKeyHeader=true and productionMode=false.
Node-to-node transaction gossip uses separate node authentication headers. A registered node signs a deterministic request message, and the receiver checks both the node signature and trusted-peer configuration before admitting the transaction to mempool.
Future deployments can replace local PEM handling with HSMs, MPC/threshold signing, delegated signing, DID credentials, or organization key policies without changing the deterministic execution rule: committed transactions must resolve to a logical identity and consume exactly one nonce.
Operator CLI Transactions
The operator CLI can inspect organization and user identity state with noos organizations ... and noos users ..., but it does not create or mutate organizations/users directly. Registration is transaction-only:
npm run noos -- tx build --type REGISTER_ORGANIZATION --payload-file org.json --signer-public-key="<public-key>" --signer-private-key-path admin.key --output tx.json
npm run noos -- tx submit --file tx.json --yesnoos tx build validates the payload against the protocol schema, resolves the signer and next nonce using local DATABASE_URL, and signs the canonical transaction. noos tx submit posts the signed transaction to /transactions; it never inserts identity rows directly.
For guided initialization, operators can use recipes that wrap the same transaction path:
npm run noos -- recipe init:template organization --output org.json
npm run noos -- recipe init:create-organization --payload-file org.json --signer-public-key="<public-key>" --signer-private-key-path admin.key --dry-run
npm run noos -- recipe init:create-user --payload-file user.json --signer-public-key="<public-key>" --signer-private-key-path admin.key --yesRecipes validate and sign transactions; they do not directly mutate identity tables.
API Authentication.
Main flow
NOOSChain’s HTTP API auth is centered on signed challenge login plus short-lived bearer sessions.
The main flow is:
Client calls POST /auth/challenge with a publicKey. Server creates a random, single-use challenge with a 5 minute TTL. Client signs { challengeId, challenge, purpose: "NOOSCHAIN_API_LOGIN" }. Client calls POST /auth/login with publicKey, challengeId, and challengeSignature. Server verifies the signature against the public key and returns a random bearer token. Protected API calls use: Authorization: Bearer <token>
Important details:
Tokens are random in-memory session tokens, not JWTs. Challenge TTL is 5 minutes. Session TTL is 1 hour. Challenges are single-use. Bearer parsing is strict: exactly Bearer <token>. Invalid, missing, or expired tokens become 40
Actor resolution happens after token validation. The token maps back to a public key, then NOOSChain resolves that key to a user or node by looking in users first and nodes second
Development
There is also a development shortcut: outside production, APIs may accept x-noos-public-key or x-noos-public-key-base64 instead of bearer auth
Operator / Observability routes
Main auth
Observability APIs authenticate via $env:NOOS_OPERATOR_TOKEN . This must be provided as Authorization: Bearer <NOOS_OPERATOR_TOKEN>
Development
Outside production, if NOOS_OPERATOR_TOKEN is not set, the code accepts this built-in development token: "dev-operator-token" Authorization: Bearer dev-operator-token
That fallback is only for local/dev use. In production, if NOOS_OPERATOR_TOKEN is missing, observability routes reject all requests with OBSERVABILITY_AUTH_NOT_CONFIGURED. Normal /auth/login bearer tokens do not grant observability access. This is intentionally a separate operator guard for node diagnostics.
HTTP Gossip auth
The receiving endpoint is:
POST /transactions/gossip The sender includes three auth headers:
x-noos-node-id: <sender-node-id> x-noos-node-timestamp: <ISO timestamp> x-noos-node-signature: <signature> How it works:
The sending node builds the gossip request body. It canonicalizes and hashes the body. It creates a message from: POST:/transactions/gossip:<timestamp>:<bodyHash> It signs that message with its node private key. The receiver looks up x-noos-node-id in the nodes table. The receiver checks that the node is active, is an active validator in governance state, and is also configured as an active trusted peer. It rejects stale timestamps, currently older/newer than 5 minutes. It verifies the signature using the registered node public key. Only then does it try to admit the transaction into the mempool. So x-noos-node-id identifies the sender, x-noos-node-timestamp prevents replay of old requests, and x-noos-node-signature proves the sender controls the private key for that registered node.