| English | 中文版 |
Spec Number: NPS-3 Status: Proposed Version: 0.9 Date: 2026-05-11 Port: 17433 (default, shared) / 17435 (optional dedicated) Authors: Ori Lynn / INNO LOTUS PTY LTD Depends-On: NPS-1 (NCP v0.7)
The key words “MUST”, “MUST NOT”, “SHOULD”, and “MAY” in this document are to be interpreted as described in RFC 2119.
NIP issues a verifiable Neural Identity (NID) to every AI agent, NWP node, and human operator, carrying capability declarations and scope-bound permissions. It supports trust-chain propagation and real-time revocation. NIP is the heart of the NPS security model.
| Role | Description |
|---|---|
| Root CA | Root certificate authority; stored offline |
| Org CA | Organization intermediate CA; one per organization |
| Agent | AI agent holding an NID certificate |
| Node | NWP node holding an NID certificate |
| Operator | Human administrator holding an Operator certificate |
| Verifier | The party verifying an NID (usually a Node) |
Root CA (offline, rarely used)
│
└── Org Intermediate CA (one per org, 1-year validity)
├── Agent Certificate (30-day validity, auto-renewal supported)
├── Node Certificate (90-day validity)
└── Operator Certificate (1-year validity, bound to MFA)
nid = "urn:nps:" entity-type ":" issuer-domain ":" identifier
entity-type = "agent" / "node" / "org"
issuer-domain = <RFC 1034 domain>
identifier = 1*(ALPHA / DIGIT / "-" / "_" / ".")
Examples
urn:nps:agent:ca.innolotus.com:550e8400-e29b-41d4 ← AI agent
urn:nps:node:api.myapp.com:products ← NWP node
urn:nps:org:mycompany.com ← Organization CA
Two identifier prefixes are reserved on NIDs of entity-type = agent and signal a structural role within the orchestrator / session lineage model. Both follow the existing identifier ABNF (1*(ALPHA / DIGIT / "-" / "_" / ".")).
| Prefix | Role | Example |
|---|---|---|
group- |
Orchestrator group NID — the trust anchor for a fleet of session NIDs issued by the CA on the group’s behalf. Longer-lived (default 365 days) and revocable. | urn:nps:agent:ca.example.com:group-7f3c9e1a-b2d8-4c6f-9a01 |
session- |
Short-lived session NID issued under a group. The portion after session- MUST be of the form {unix-timestamp}-{random} where {random} is at least 8 hex characters. Default validity 1 hour, max 24 hours. |
urn:nps:agent:ca.example.com:session-1714672800-f3a92c0b |
Identifiers without these prefixes continue to behave as ordinary agent NIDs. Receivers MUST NOT reject a NID solely on its prefix; the prefix is informational and the authoritative role is carried in the signed IdentFrame.lineage.role field (§5.1.3). The protocol-level nop:orchestrate capability (NPS-5) continues to express the role of being an orchestrator and is independent of this prefix convention.
| Algorithm | Use | Key length |
|---|---|---|
| Ed25519 (primary) | High-frequency agent verification | 32-byte private key / 32-byte public key |
| ECDSA P-256 (fallback) | Compatibility scenarios | 32-byte private key / 64-byte public key |
Public-key encoding: {algorithm}:{base64url(DER)}, e.g. ed25519:MCowBQYDK2VwAyEA....
Agent identity declaration carrying the certificate. Sent as the handshake frame on every new connection.
Field definitions
| Field | Type | Required | Description |
|---|---|---|---|
frame |
uint8 | required | Fixed value 0x20 |
nid |
string | required | Agent NID |
pub_key |
string | required | Public key; format {alg}:{base64url} |
capabilities |
array | required | Capabilities held by the agent |
scope |
object | required | Access-scope declaration |
issued_by |
string | required | Issuer NID (Org CA) |
issued_at |
string | required | Issue time (ISO 8601 UTC) |
expires_at |
string | required | Expiration time (ISO 8601 UTC) |
serial |
string | required | Certificate serial (globally unique per Org CA, hex) |
signature |
string | required | CA’s signature over this frame; format {alg}:{base64url} |
cert_format |
string (enum) | required | Certificate encoding format. One of "x509-der" / "raw-pubkey". Excluded from the signed canonical JSON (see §5.1.3 canonicalisation note). |
cert_chain |
array of string | required when cert_format = "x509-der"; MUST be omitted when cert_format = "raw-pubkey" |
DER-encoded certificate chain, base64url-encoded, leaf first. Excluded from the signed canonical JSON (see §5.1.3 canonicalisation note). |
metadata |
object | optional | Agent metadata — see below |
assurance_level |
string | optional | One of "anonymous" / "attested" / "verified" (see §5.1.1). REQUIRED when the NID’s certificate carries the id-nid-assurance-level extension. Receivers MUST treat an absent field as "anonymous" (backward compatibility with v1.0-alpha.2 publishers). When both field and cert extension are present, they MUST carry the same value — phase gate: Phase 1–2 (current) enforcement is opt-in (SHOULD check, MAY enforce); starting Phase 3 flag day (see NPS-RFC-0003 §8.1) enforcement is MUST, violation returns NIP-ASSURANCE-MISMATCH. (NPS-RFC-0003) |
lineage |
object | optional | Signed lineage metadata. Present when the NID is an orchestrator group (role = "group") or a short-lived session (role = "session"). See §5.1.3. (NPS-CR-0003) |
metadata fields (optional)
| Field | Type | Description |
|---|---|---|
model_family |
string | Model-family identifier used by the agent, e.g. "openai/gpt-4o", "anthropic/claude-4" |
tokenizer |
string | Tokenizer identifier used by the agent, e.g. "cl100k_base" |
runtime |
string | Agent runtime identifier, e.g. "langchain/0.2", "autogen/0.4" |
The metadata field is not included in the signature computation; an agent MAY set it at runtime. Nodes use the tokenizer in metadata to perform CGN auto-matching (see token-budget.md).
Trust boundary for unsigned metadata (normative — closes issue #39)
Because metadata is outside the signature, every value inside it is agent-supplied and unverified. Nodes MUST NOT use any field of IdentFrame.metadata (including but not limited to model_family, tokenizer, and runtime) as input to:
For these uses a Node MUST instead consume a verified signal — an attribute carried in the signed IdentFrame body, the X.509 NID certificate (NPS-RFC-0002 extensions), or a CA-attested side channel — or a Node-internal observed measurement. Treating unsigned metadata as authoritative is a conformance violation.
Three-tier tokenizer trust model
To make the boundary above operational for token accounting, NPS defines three distinct tokenizer signals. Implementations MUST keep them separable in code paths and logs.
| Tier | Source | Trust | Permitted uses |
|---|---|---|---|
declared_tokenizer |
Agent-supplied via IdentFrame.metadata.tokenizer or the X-NWP-Tokenizer request header. Unsigned. |
Agent self-claim only. | Estimation hints only: shaping pre-flight CGN estimates, cache-key disambiguation, telemetry. MUST NOT drive billing, quota elevation, reputation, or security. |
verified_tokenizer |
CA-attested or platform-attested binding of a tokenizer identifier to the NID — e.g. an X.509 NID-cert extension, an AaaS-operator attestation under NPS-AaaS-Profile, or another signed channel agreed by the deployment. | Cryptographically verifiable. | Authoritative for policy, billing, quota tiers, and assurance-gated access. Settlement-grade flows MUST gate on this tier. |
observed_tokenizer_profile |
Node-side statistical or behavioral inference (e.g. byte-length distributions, response-shape fingerprinting, sampled re-tokenization). | Node-internal measurement. | Abuse detection, drift alerts, anomaly scoring, and inputs to a Node’s own reputation engine. MUST NOT be exported to peers as a verified claim. |
When more than one tier is available the Node MUST prefer the highest-trust tier for any decision the boundary above restricts; lower tiers MAY be retained alongside for diagnostic comparison.
Standard capabilities values
| Capability | Description |
|---|---|
nwp:query |
May query Memory Nodes |
nwp:action |
May invoke Action Nodes |
nwp:stream |
May receive StreamFrame responses |
ncp:stream |
May initiate NCP streaming |
nop:delegate |
May delegate subtasks to other agents |
nop:orchestrate |
May act as an orchestrator and emit TaskFrames |
topology:read |
May read Anchor Node topology data via reserved query types topology.snapshot / topology.stream (NPS-2 §12); Anchor Nodes MUST require this capability at Phase 1–2 per NPS-2 §12.4. Self-declared and signed at Phase 1–2; CA-attested role binding deferred to Phase 3 (RFC-0002 amendment). |
scope field
{
"nodes": ["nwp://api.myapp.com/*"],
"actions": ["orders:read", "orders:create"],
"max_token_budget": 50000
}
Signature computation
The signature is computed over the canonical JSON of the IdentFrame with the signature field removed (keys sorted alphabetically, no whitespace).
Full example
{
"frame": "0x20",
"nid": "urn:nps:agent:ca.innolotus.com:550e8400-e29b-41d4",
"pub_key": "ed25519:MCowBQYDK2VwAyEA...",
"capabilities": ["nwp:query", "nwp:action", "ncp:stream"],
"scope": {
"nodes": ["nwp://api.myapp.com/*"],
"actions": ["orders:read", "orders:create"],
"max_token_budget": 50000
},
"issued_by": "urn:nps:org:mycompany.com",
"issued_at": "2026-04-10T00:00:00Z",
"expires_at": "2026-05-10T00:00:00Z",
"serial": "0x0A3F9C",
"signature": "ed25519:3045022100..."
}
NPS defines three assurance levels an Agent identity may carry, modelled on NIST SP 800-63 IAL and CA/B Forum DV/OV/EV. The level travels in IdentFrame.assurance_level and is the source of truth for Node policy decisions (NWM.min_assurance_level); when X.509 NID certificates are in use (NPS-RFC-0002), the cert MUST also carry the id-nid-assurance-level extension and the two MUST agree — phase gate: enforcement is opt-in in Phase 1–2 (current); mandatory starting Phase 3 flag day (see NPS-RFC-0003 §8.1).
| Level | Enum value | Minimum CA criteria | Typical use |
|---|---|---|---|
| L0 | "anonymous" |
Self-signed, OR CA-signed without out-of-band identity binding | Hobbyist Agents, dev / test, free read-only endpoints |
| L1 | "attested" |
NID signed by an RFC-0002-compliant CA; CA attests possession of the NID private key (e.g. ACME agent-01 challenge); contact email or domain verified. The id-nid-assurance-level X.509 extension uses IANA-assigned OID 1.3.6.1.4.1.65715.2.1; the companion id-nps-node-roles extension uses 1.3.6.1.4.1.65715.2.2 (PEN 65715 assigned to LabAcacia 2026-05-08, NPS-CR-0004; see NPS-RFC-0002 §10 OQ-2). |
Most production Agents; default rate-limit tier |
| L2 | "verified" |
L1 criteria plus CA binds the operator’s legal identity (corporate registration for org-NIDs, signed AaaS-operator attestation for hosted Agents) | Regulated integrations, paid premium tiers, contract-grade orchestration |
Default is "anonymous" — pre-RFC-0003 NIDs and any NID lacking the field are treated as L0. A Node demanding stricter levels declares min_assurance_level in its NWM (NPS-2 §4.1, §4.3).
The values are an ordered enum: anonymous < attested < verified. A request whose level is below the Node’s required level MUST be rejected with NWP-AUTH-ASSURANCE-TOO-LOW (NPS-AUTH-FORBIDDEN).
Forward compatibility: An implementation receiving an assurance_level value not present in this enum MUST treat it as a protocol error and return NIP-ASSURANCE-UNKNOWN (NPS-CLIENT-BAD-FRAME). Implementations MUST NOT silently demote an unknown value to anonymous — silent demotion would create a security loophole for future higher-assurance levels introduced by a later spec revision.
See NPS-RFC-0003 for full design rationale, including the X.509 critical-extension flip planned in coordination with NPS-RFC-0002.
Companion to assurance levels (§5.1.1). Where
assurance_levelanswers “who is this Agent?”, reputation log entries answer “how has this NID behaved?” — published as a Certificate-Transparency-style append-only log so that any party (Node, auditor, downstream operator) can reach the same evidence about the same NID without a pre-existing relationship with the publisher.
A reputation entry is a signed JSON object that an issuer (typically an AaaS Anchor Node, an auditor, or a CA) publishes against a subject_nid to record one observed behavioral incident. Entries are aggregated by log operators that maintain an append-only history; the wire format below is the entry shape itself, independent of any specific log operator’s HTTP surface (see NPS-RFC-0004 §4.3 for the operator interface).
Field definitions
| Field | Type | Required | Description |
|---|---|---|---|
v |
uint8 | required | Schema version. This document defines 1. |
log_id |
string (NID) | required | NID of the log operator that appended (or will append) this entry. MAY equal issuer_nid for self-published entries. |
seq |
uint64 | required | Monotonically increasing per-log_id sequence number. Set by the log operator on append. |
timestamp |
string (RFC 3339 UTC) | required | Log operator’s commit time. |
subject_nid |
string (NID) | required | The NID this entry is about. |
incident |
string (enum) | required | One of the values in §5.1.2.1, or a forward-compatible unknown value (receivers MUST preserve unknown values). |
severity |
string (enum) | required | One of info / minor / moderate / major / critical. |
window |
object | optional | { start: ISO 8601, end: ISO 8601 } describing the observation window. |
observation |
object | optional | Free-form, machine-readable, per-incident-type detail (e.g. {requests: 45000, threshold: 300} for a rate-limit violation). |
evidence_ref |
string (URL) | optional | URL where richer evidence (logs, transcripts) lives. |
evidence_sha256 |
string (hex) | optional | SHA-256 of the evidence blob; lets queriers detect tampering with the off-log evidence. |
issuer_nid |
string (NID) | required | NID of the party making the assertion. MAY equal log_id. |
signature |
string | required | {alg}:{base64url} Ed25519 signature by issuer_nid’s private key over the entry minus signature, canonicalised per RFC 8785 (JCS). Log operators verify this signature, then re-sign the full entry with their log_id key for the ordering commitment (dual-signature model). |
| Value | Meaning |
|---|---|
cert-revoked |
CA revoked the subject_nid’s cert. |
rate-limit-violation |
Sustained violation of published rate limits. |
tos-violation |
Violated an AaaS Anchor Node’s published terms. |
scraping-pattern |
Behavior matched scraper heuristics. |
payment-default |
CGN or fiat default on a committed transaction. |
contract-dispute |
Unresolved contractual breach on an async NOP task. |
impersonation-claim |
Third-party claim that subject_nid is impersonating them. |
positive-attestation |
Explicit positive signal (e.g. independent audit passed). |
Receivers MUST treat unknown incident values as opaque pass-through (forward compatibility); operator-side filters MAY ignore them. See NPS-RFC-0004 §4.2 for the long-form rationale.
Errors
| Code | When |
|---|---|
NIP-REPUTATION-ENTRY-INVALID (NPS-CLIENT-BAD-FRAME) |
Entry signature fails verification or canonical form is malformed. |
NIP-REPUTATION-LOG-UNREACHABLE (NPS-DOWNSTREAM-UNAVAILABLE) |
A log operator referenced by a Node’s reputation_policy cannot be reached during admission evaluation. |
NWP-AUTH-REPUTATION-BLOCKED (NPS-AUTH-FORBIDDEN) |
Reputation policy matched a reject_on rule on the requesting subject_nid. (Defined in NPS-2 §6 — Anchor / Memory / Action / Complex / Bridge Nodes that opt in.) |
Phase 1 of NPS-RFC-0004 standardises the entry wire format and the signature rule above; the Merkle-tree / Signed Tree Head / inclusion-proof surface (RFC 9162-style) and the operator HTTP API land in Phase 2 alongside the NDP discovery hint. See the RFC for the full phasing.
IdentFrame.lineage carries the signed parent-chain metadata that links a short-lived session NID back to its orchestrator group NID, and that records who owns / authorised the group. The trust chain is:
human owner → Operator key (§2.1) → orchestrator group NID → short-lived session NID
lineage is part of the signed canonical JSON of the IdentFrame (unlike metadata, which is excluded). Modifying any sub-field invalidates the CA signature — this is what makes the chain verifiable end-to-end: a Node admitting a session NID can prove that lineage.parent_nid was authoritatively asserted by the CA at issuance time, not retro-fitted on the wire.
Field definitions
| Field | Type | Required | Description |
|---|---|---|---|
role |
enum ("group" / "session") |
required when lineage is present |
Lineage role of this NID. |
parent_nid |
string (NID) | required when role = "session" |
Immediate parent NID. For a 1-level chain (group → session), equals group_nid. |
group_nid |
string (NID) | required when role = "session" |
Group NID at the root of the chain. Equals parent_nid for a 1-level chain; reserved for future deeper chains. |
session_id |
string | required when role = "session" |
Stable id of this execution; matches the session-... portion of the NID identifier (§3.1). Echoes back so consumers can index without parsing the URN. |
purpose |
string (≤256 UTF-8 bytes) | optional | Free-form, human-readable label for what this execution is for, e.g. "order-classification-job". |
owner_user_id |
string | optional | Stable identifier of the human owner this group is acting on behalf of (e.g. internal user UUID). SHOULD be set when role = "group" and the deployment knows the owner. |
owner_key_id |
string | optional | kid hint identifying the owner-key (Operator key, OIDC sub, hardware-token id) that authorised creation of the group. |
Group-NID example
"lineage": {
"role": "group",
"owner_user_id": "user-7f3c9e1a",
"owner_key_id": "op-kid-2026-04"
}
Session-NID example
"lineage": {
"role": "session",
"parent_nid": "urn:nps:agent:ca.example.com:group-7f3c9e1a-b2d8-4c6f-9a01",
"group_nid": "urn:nps:agent:ca.example.com:group-7f3c9e1a-b2d8-4c6f-9a01",
"session_id": "session-1714672800-f3a92c0b",
"purpose": "data-extraction-job-42",
"owner_user_id": "user-7f3c9e1a",
"owner_key_id": "op-kid-2026-04"
}
Backward compatibility: pre-CR-0003 publishers omit lineage; pre-CR-0003 verifiers that strict-canonicalise (sort + omit signature / metadata / cert_format / cert_chain) will drop unknown fields if their DTO does not carry them, in which case signature verification of a CR-0003 frame fails. New senders aware of CR-0003 produce CR-0003 frames; old verifiers MUST be upgraded to verify them. Ordinary agent NIDs without lineage continue to verify exactly bit-compatibly.
Errors
| Code | When |
|---|---|
NIP-CA-GROUP-REVOKED (NPS-AUTH-FORBIDDEN) |
Cannot issue a session under a group that has been revoked. |
NIP-CA-PARENT-NOT-FOUND (NPS-CLIENT-NOT-FOUND) |
The parent_nid / group NID referenced by a session-issue request does not exist. |
NIP-CA-PARENT-NOT-GROUP (NPS-CLIENT-BAD-PARAM) |
The referenced parent NID exists but is not registered as lineage.role = "group". |
NIP-CA-SESSION-VALIDITY-INVALID (NPS-CLIENT-BAD-PARAM) |
Requested session validity below 60 seconds or above the CA’s configured maximum. |
NIP-CA-JWS-INVALID (NPS-AUTH-UNAUTHENTICATED) |
Group-JWS authorisation on a session-issue request fails signature, header, or shape validation. |
NIP-CA-JWS-EXPIRED (NPS-AUTH-UNAUTHENTICATED) |
Group-JWS iat outside the CA’s clock-skew window (default ±5 minutes). |
NIP-CERT-PARENT-REVOKED (NPS-AUTH-UNAUTHENTICATED) |
A session NID’s parent / group NID is revoked or expired (chain check, §7 step 3a). |
See NPS-CR-0003 for the full motivation, JWS shape, and migration impact.
Cross-CA trust-chain propagation and capability grant (commercial feature, NPS Cloud). A TrustFrame lets one CA (the grantor) authorise another CA (the grantee) to issue IdentFrames whose capabilities are accepted by Nodes that already trust the grantor — without the grantee being added to each Node’s trusted_issuers list. The grant is scoped to a capability subset and a set of nwp:// URL patterns; it is verifiable end-to-end via the grantor’s signature.
Field definitions
| Field | Type | Required | Description |
|---|---|---|---|
frame |
uint8 | required | Fixed 0x21. |
grantor_nid |
string (NID) | required | NID of the granting CA. MUST be in the verifying Node’s trusted_issuers for the TrustFrame to take effect. |
grantee_ca |
string (NID) | required | NID of the receiving CA. IdentFrames whose issued_by equals grantee_ca are admitted under the grant. |
trust_scope |
array of string | required | Capability strings the trust covers. MUST be a subset of the standard capabilities enum defined in §5.1 (nwp:query / nwp:action / nwp:stream / ncp:stream / nop:delegate / nop:orchestrate / topology:read). A grantee MUST NOT issue downstream IdentFrames carrying capabilities outside this set. |
nodes |
array of string | required | nwp:// URL patterns this trust applies to. * matches a single path segment; ** matches multiple segments. Empty array means the grant covers no nodes (effectively inert). |
issued_at |
string (ISO 8601 UTC) | required | Issuance time. |
expires_at |
string (ISO 8601 UTC) | required | Expiry time. After this instant the frame MUST be rejected with NIP-TRUST-FRAME-EXPIRED. |
serial |
string (hex) | required | 16-character zero-padded hex serial number used for revocation tracking via RevokeFrame (§5.3). |
signer_nid |
string (NID) | required | NID whose private key signs this frame. MUST be either grantor_nid itself or an operator NID under grantor_nid. |
signature |
string | required | {alg}:{base64url} — ed25519: (Ed25519) or ecdsa-p256: (ECDSA P-256). |
Signature computation
The signature is computed over the canonical JSON of the TrustFrame with the signature field removed (keys sorted alphabetically, no whitespace) — same canonicalisation rule as IdentFrame in §5.1. Supported algorithms: Ed25519 (ed25519:) and ECDSA P-256 (ecdsa-p256:).
Errors
| Code | When |
|---|---|
NIP-TRUST-FRAME-INVALID (NPS-CLIENT-BAD-FRAME) |
TrustFrame is malformed (missing required field, signature verification fails, or canonical form is invalid). |
NIP-TRUST-FRAME-EXPIRED (NPS-AUTH-UNAUTHENTICATED) |
Current time is past expires_at. |
NIP-TRUST-FRAME-GRANTOR-REVOKED (NPS-AUTH-UNAUTHENTICATED) |
The grantor_nid’s own CA certificate is revoked or expired at verification time. |
NIP-TRUST-FRAME-SCOPE-EXCEEDS-GRANTOR (NPS-AUTH-FORBIDDEN) |
trust_scope contains a capability that the grantor itself does not hold (no-scope-expansion principle, §10.3). |
NIP-TRUST-FRAME-NODES-PATTERN-INVALID (NPS-CLIENT-BAD-FRAME) |
An entry in nodes is not a syntactically valid nwp:// URL pattern, or uses wildcards in a position where they are not permitted. |
Full example
{
"frame": "0x21",
"grantor_nid": "urn:nps:org:org-a.com",
"grantee_ca": "urn:nps:org:org-b.com",
"trust_scope": ["nwp:query", "nwp:action"],
"nodes": ["nwp://api.org-a.com/public/**"],
"issued_at": "2026-05-11T00:00:00Z",
"expires_at": "2026-12-31T00:00:00Z",
"serial": "00000000000A3F9C",
"signer_nid": "urn:nps:org:org-a.com",
"signature": "ed25519:3045022100..."
}
Revokes an NID, all certificates issued under an NID, or a specific certificate identified by serial. RevokeFrames are emitted by an issuing CA (or by an authorised operator under that CA) and pushed to subscribed Nodes via the NIP push channel; receivers update their local cert / OCSP cache and reject subsequent requests authenticated under the revoked target. A RevokeFrame is fire-and-forget at the protocol layer (no response frame on success); receivers MUST emit an ErrorFrame only when the frame itself is malformed or unauthorised.
Field definitions
| Field | Type | Required | Description |
|---|---|---|---|
frame |
uint8 | required | Fixed value 0x22. |
target_nid |
string (NID) | required | NID being revoked. MAY be an agent NID, node NID, group NID, or org NID. |
serial |
string (hex) | optional | Specific certificate serial under target_nid to revoke. If present, MUST match the serial of a currently-issued cert for target_nid. If omitted, ALL currently-issued certs for target_nid are revoked (effectively revoking the NID itself). |
reason |
string (enum) | required | One of the values defined in the reason table below. |
revoked_at |
string (ISO 8601 UTC) | required | Wall-clock instant at which revocation takes effect. Receivers SHOULD reject any request whose IdentFrame issued_at is at or before revoked_at. |
parent_nid |
string (NID) | conditional | REQUIRED when reason = "parent_revoked"; identifies the group NID whose cascade triggered this revocation (NPS-CR-0003). MUST be omitted for any other reason. |
signer_nid |
string (NID) | required | NID of the CA or operator signing this revocation. MUST be either the issuing CA of target_nid, an operator under that CA holding the nip:revoke capability, or — for cascade revocations — the CA that issued parent_nid. |
signature |
string | required | {alg}:{base64url} signature by signer_nid’s private key. Supported algorithms: ed25519: (Ed25519) or ecdsa-p256: (ECDSA P-256). |
reason enum
| Value | Description |
|---|---|
key_compromise |
Agent / node private key is known or suspected to be compromised. Highest urgency. |
ca_compromise |
The issuing CA’s signing key is compromised; ALL certificates under that CA are implicitly revoked, and a per-NID RevokeFrame MAY be emitted for each affected child. |
affiliation_changed |
Agent left the issuing organisation; ALL of its capabilities are revoked even if individual certs have not yet expired. |
superseded |
Replaced by a re-issued certificate carrying the same NID (e.g. routine rotation that needs to invalidate the prior serial). The serial field SHOULD be set to identify the superseded cert. |
cessation_of_operation |
Agent / node has been decommissioned and will never present credentials again. |
parent_revoked |
(NPS-CR-0003) Cascade emitted by the CA on a session-NID whose group NID was revoked. The parent_nid field MUST be set to the group NID. Distinguishes ancestry-driven invalidation from a session revoked on its own merits. |
Forward compatibility: receivers encountering a reason value not present in the table above MUST treat it as key_compromise (the most restrictive interpretation). Receivers MAY additionally log the unknown value for diagnostic purposes and MAY emit NIP-REVOKE-FRAME-REASON-UNKNOWN to the publishing CA over a side channel, but MUST NOT silently demote an unknown reason to a less restrictive one.
Signature computation
The signature is computed over the canonical JSON of the RevokeFrame with the signature field removed (keys sorted alphabetically, no whitespace) — same canonicalisation rule as IdentFrame in §5.1 (RFC 8785 JCS).
Errors
| Code | When |
|---|---|
NIP-REVOKE-FRAME-INVALID (NPS-CLIENT-BAD-FRAME) |
RevokeFrame is malformed (missing required field, signature verification fails, or canonical form is invalid). |
NIP-REVOKE-FRAME-UNAUTHORIZED-ISSUER (NPS-AUTH-FORBIDDEN) |
signer_nid is not authorised to revoke target_nid (not the issuing CA of target_nid, not an operator under that CA holding nip:revoke, and — for parent_revoked — not the CA that issued parent_nid). |
NIP-REVOKE-FRAME-SERIAL-MISMATCH (NPS-CLIENT-BAD-PARAM) |
serial is present but does not match any currently-issued cert for target_nid. |
NIP-REVOKE-FRAME-REASON-UNKNOWN (NPS-CLIENT-BAD-FRAME) |
reason carries a value outside the defined enum. Receivers treat the revocation as key_compromise for safety (see Forward compatibility above); this code is reported back to the publishing CA so the publish can be corrected. |
Full example
{
"frame": "0x22",
"target_nid": "urn:nps:agent:ca.innolotus.com:550e8400-e29b-41d4",
"serial": "0x0A3F9C",
"reason": "key_compromise",
"revoked_at": "2026-04-10T12:00:00Z",
"signer_nid": "urn:nps:org:ca.innolotus.com",
"signature": "ed25519:3045022100..."
}
Cascade example (parent_revoked)
{
"frame": "0x22",
"target_nid": "urn:nps:agent:ca.example.com:session-1714672800-f3a92c0b",
"reason": "parent_revoked",
"revoked_at": "2026-04-10T12:00:00Z",
"parent_nid": "urn:nps:agent:ca.example.com:group-7f3c9e1a-b2d8-4c6f-9a01",
"signer_nid": "urn:nps:org:ca.example.com",
"signature": "ed25519:..."
}
parent_revoked (NPS-CR-0003) is set by the CA on a session-NID RevokeFrame it emits as part of cascade revocation when the session’s group NID was revoked. It distinguishes a session that was forcibly invalidated by ancestry from one revoked on its own merits.
Registration Auto-renewal Revocation CA rotation
│ │ │ │
↓ ↓ ↓ ↓
CA issues 7 days pre-expiry RevokeFrame Old + new CA
IdentFrame Agent triggers takes effect in parallel
30-day validity CA issues new cert immediately for 30 days
Both valid 1 hour Nodes reject Smooth rollover
all requests
for that NID
Node receives IdentFrame
│
├─ 1. Check expires_at > now (expired → NIP-CERT-EXPIRED)
├─ 2. Check issued_by is in NWM trusted_issuers (not trusted → NIP-CERT-UNTRUSTED-ISSUER)
├─ 3. Verify signature with issued_by CA public key (fail → NIP-CERT-SIGNATURE-INVALID)
├─ 3a. (NPS-CR-0003) If lineage.parent_nid is present, OCSP-lookup the parent
│ (revoked or expired → NIP-CERT-PARENT-REVOKED)
├─ 4. OCSP lookup (when NWM configures ocsp_url) or local CRL check (revoked → NIP-CERT-REVOKED)
├─ 5. Check capabilities contains what the node requires (missing → NIP-CERT-CAPABILITY-MISSING)
└─ 6. Check scope.nodes covers the target node path (not covered → NWP-AUTH-NID-SCOPE-VIOLATION)
All checks pass → authorize the request
Step 3a (chain check) is mandatory whenever lineage.parent_nid is present, regardless of whether the session NID itself is still inside its own validity window. Combined with the CA-side cascade documented in §5.3 (parent_revoked reason), it provides defense-in-depth: a misconfigured CA that fails to record the cascade still gets caught at the verifier; a CA that did record it still publishes the entries to the CRL for simple consumers.
Note on TrustFrame chains (§5.2): when the IdentFrame’s issued_by is not in the Node’s trusted_issuers directly, step 3 MAY still succeed via a TrustFrame chain. After step 3 (CA signature check), if the IdentFrame was issued by a grantee_ca, the Node SHOULD verify that a valid, unexpired TrustFrame exists from a matching grantor_nid (which IS in trusted_issuers) covering the requested capability and target node path. The TrustFrame itself MUST pass its own checks (§5.2 error table) — failure short-circuits admission with the corresponding NIP-TRUST-FRAME-* code instead of falling through to NIP-CERT-UNTRUSTED-ISSUER.
Inbound RevokeFrame handling (§5.3): RevokeFrames arrive asynchronously over the NIP push channel and are not part of the per-IdentFrame admission flow above. When a Node receives a RevokeFrame it MUST:
Node receives RevokeFrame (push channel)
│
├─ R1. Parse the frame and verify signer_nid signature over canonical JSON
│ (fail → emit ErrorFrame NIP-REVOKE-FRAME-INVALID; do not apply)
├─ R2. Verify signer_nid is authorised to revoke target_nid — see §5.3 signer_nid rules
│ (fail → emit ErrorFrame NIP-REVOKE-FRAME-UNAUTHORIZED-ISSUER; do not apply)
├─ R3. If `serial` is present, verify it matches a known issued cert for target_nid
│ (mismatch → emit ErrorFrame NIP-REVOKE-FRAME-SERIAL-MISMATCH; do not apply)
├─ R4. If `reason` is outside the defined enum, treat as `key_compromise` (most restrictive)
│ and emit ErrorFrame NIP-REVOKE-FRAME-REASON-UNKNOWN to the publisher
├─ R5. Poison the local cert / OCSP cache for target_nid
│ (if `serial` present, scope to that cert; otherwise scope to all certs for target_nid)
└─ R6. No success response is sent — RevokeFrame is fire-and-forget at the protocol layer.
After R5 takes effect, any subsequent IdentFrame for target_nid (or for the specific serial if scoped) will fail step 4 of the IdentFrame verification flow above with NIP-CERT-REVOKED.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/agents/register |
Operator Cert | Register an agent; returns NID + IdentFrame |
| POST | /v1/agents/{nid}/renew |
Agent Cert | Renew a certificate (up to 7 days before expiry) |
| POST | /v1/agents/{nid}/revoke |
Operator Cert | Revoke an NID |
| GET | /v1/agents/{nid}/verify |
None | Verify NID validity (OCSP lookup) |
| POST | /v1/nodes/register |
Operator Cert | Register an NWP node; issues a Node Certificate |
| POST | /v1/orchestrators/groups/register |
Operator Cert | (NPS-CR-0003) Register an orchestrator group; returns IdentFrame with lineage.role = "group" |
| POST | /v1/orchestrators/groups/{group_nid}/sessions/issue |
Group JWS or Operator Cert | (NPS-CR-0003) Issue a short-lived session NID under the group |
| POST | /v1/orchestrators/groups/{group_nid}/revoke |
Operator Cert | (NPS-CR-0003) Revoke the group AND cascade-revoke every live session under it |
| GET | /v1/orchestrators/groups/{group_nid}/sessions |
Operator Cert | (NPS-CR-0003) List sessions issued under this group (audit) |
| GET | /v1/ca/cert |
None | CA public-key certificate |
| GET | /v1/crl |
None | Certificate revocation list |
| GET | /.well-known/nps-ca |
None | CA discovery endpoint |
/.well-known/nps-ca response
{
"nps_ca": "0.1",
"issuer": "urn:nps:org:ca.mycompany.com",
"display_name": "MyCompany NPS CA",
"public_key": "ed25519:MCowBQYDK2VwAyEA...",
"algorithms": ["ed25519", "ecdsa-p256"],
"endpoints": {
"register": "https://ca.mycompany.com/v1/agents/register",
"verify": "https://ca.mycompany.com/v1/agents/{nid}/verify",
"ocsp": "https://ca.mycompany.com/ocsp",
"crl": "https://ca.mycompany.com/v1/crl"
},
"capabilities": ["agent", "node", "operator", "orchestrator-group"],
"max_cert_validity_days": 30
}
NPS-CR-0005 introduces a three-tier Registration Authority (RA) model that admits agents into the CA along policy-driven paths in addition to the Operator-credential POST /v1/agents/register flow above. The default tier remains operator_only — the new tiers are opt-in via NipCaOptions.EnrollmentTier and add the following endpoints:
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/enrollment/tokens |
Operator Cert | (NPS-CR-0005, Tier 2) Mint a single-use, NID-scoped bootstrap token. |
| GET | /v1/enrollment/pending |
Operator Cert | (NPS-CR-0005, Tier 3) List registration requests awaiting admin decision. |
| POST | /v1/enrollment/pending/{id}/approve |
Operator Cert | (NPS-CR-0005, Tier 3) Approve a pending request and issue the IdentFrame. |
| POST | /v1/enrollment/pending/{id}/reject |
Operator Cert | (NPS-CR-0005, Tier 3) Reject a pending request with an operator-supplied reason. |
The three tiers (allowlist / bootstrap token / pending queue), the new error codes (NIP-RA-TOKEN-INVALID, NIP-RA-TOKEN-EXPIRED, NIP-RA-NID-NOT-ALLOWED, NIP-RA-PENDING-REJECTED), and the NipCaOptions.EnrollmentTier selector are specified in full by NPS-CR-0005. The body, request/response shapes, and verification flow for these endpoints will land in this section once CR-0005 reaches Implemented.
| Error Code | NPS Status | Description |
|---|---|---|
NIP-CERT-EXPIRED |
NPS-AUTH-UNAUTHENTICATED |
Certificate has expired |
NIP-CERT-REVOKED |
NPS-AUTH-UNAUTHENTICATED |
Certificate has been revoked |
NIP-CERT-SIGNATURE-INVALID |
NPS-AUTH-UNAUTHENTICATED |
Certificate signature verification failed |
NIP-CERT-UNTRUSTED-ISSUER |
NPS-AUTH-UNAUTHENTICATED |
Issuer not in the trust list |
NIP-CERT-CAPABILITY-MISSING |
NPS-AUTH-FORBIDDEN |
Certificate missing a required capability |
NIP-CERT-SCOPE-VIOLATION |
NPS-AUTH-FORBIDDEN |
Certificate scope does not cover the target path |
NIP-CA-NID-NOT-FOUND |
NPS-CLIENT-NOT-FOUND |
NID does not exist |
NIP-CA-NID-ALREADY-EXISTS |
NPS-CLIENT-CONFLICT |
NID already exists (duplicate registration) |
NIP-CA-SERIAL-DUPLICATE |
NPS-CLIENT-CONFLICT |
Certificate serial already in use |
NIP-CA-RENEWAL-TOO-EARLY |
NPS-CLIENT-BAD-PARAM |
Renewal window not yet open |
NIP-CA-SCOPE-EXPANSION-DENIED |
NPS-AUTH-FORBIDDEN |
Requested scope exceeds the parent scope |
NIP-OCSP-UNAVAILABLE |
NPS-SERVER-UNAVAILABLE |
OCSP service temporarily unavailable |
NIP-TRUST-FRAME-INVALID |
NPS-CLIENT-BAD-FRAME |
TrustFrame signature or format is invalid — see §5.2 |
NIP-TRUST-FRAME-EXPIRED |
NPS-AUTH-UNAUTHENTICATED |
TrustFrame expires_at is in the past — see §5.2 |
NIP-TRUST-FRAME-GRANTOR-REVOKED |
NPS-AUTH-UNAUTHENTICATED |
TrustFrame grantor_nid’s own CA certificate is revoked or expired — see §5.2 |
NIP-TRUST-FRAME-SCOPE-EXCEEDS-GRANTOR |
NPS-AUTH-FORBIDDEN |
TrustFrame trust_scope contains a capability the grantor itself does not hold (no-scope-expansion principle, §10.3) — see §5.2 |
NIP-TRUST-FRAME-NODES-PATTERN-INVALID |
NPS-CLIENT-BAD-FRAME |
TrustFrame nodes entry is not a valid nwp:// URL pattern — see §5.2 |
NIP-REVOKE-FRAME-INVALID |
NPS-CLIENT-BAD-FRAME |
RevokeFrame is malformed (missing required field, signature verification fails, or canonical form is invalid) — see §5.3 |
NIP-REVOKE-FRAME-UNAUTHORIZED-ISSUER |
NPS-AUTH-FORBIDDEN |
RevokeFrame signer_nid is not authorised to revoke target_nid — see §5.3 |
NIP-REVOKE-FRAME-SERIAL-MISMATCH |
NPS-CLIENT-BAD-PARAM |
RevokeFrame serial is present but does not match any currently-issued cert for target_nid — see §5.3 |
NIP-REVOKE-FRAME-REASON-UNKNOWN |
NPS-CLIENT-BAD-FRAME |
RevokeFrame reason carries a value outside the defined enum; receivers treat it as key_compromise — see §5.3 |
NIP-ASSURANCE-MISMATCH |
NPS-CLIENT-BAD-FRAME |
IdentFrame.assurance_level does not match the cert extension id-nid-assurance-level (downgrade-attack defense) — see §5.1.1 |
NIP-ASSURANCE-UNKNOWN |
NPS-CLIENT-BAD-FRAME |
assurance_level carries a value outside the defined enum (anonymous / attested / verified) — see §5.1.1 |
NIP-REPUTATION-ENTRY-INVALID |
NPS-CLIENT-BAD-FRAME |
Reputation log entry signature fails verification or canonical form is malformed — see §5.1.2 (NPS-RFC-0004) |
NIP-REPUTATION-LOG-UNREACHABLE |
NPS-DOWNSTREAM-UNAVAILABLE |
A log operator referenced by a Node’s reputation_policy cannot be reached during admission evaluation — see §5.1.2 (NPS-RFC-0004) |
NIP-CA-GROUP-REVOKED |
NPS-AUTH-FORBIDDEN |
Cannot issue a session under a group NID that has been revoked — see §5.1.3 (NPS-CR-0003) |
NIP-CA-PARENT-NOT-FOUND |
NPS-CLIENT-NOT-FOUND |
The parent_nid / group NID referenced by a session-issue request does not exist — see §5.1.3 (NPS-CR-0003) |
NIP-CA-PARENT-NOT-GROUP |
NPS-CLIENT-BAD-PARAM |
The referenced parent NID exists but is not registered as lineage.role = "group" — see §5.1.3 (NPS-CR-0003) |
NIP-CA-SESSION-VALIDITY-INVALID |
NPS-CLIENT-BAD-PARAM |
Requested session validity below 60 seconds or above the CA’s configured maximum — see §5.1.3 (NPS-CR-0003) |
NIP-CA-JWS-INVALID |
NPS-AUTH-UNAUTHENTICATED |
Group-JWS authorisation on a session-issue request fails signature, header, or shape validation — see §5.1.3 (NPS-CR-0003) |
NIP-CA-JWS-EXPIRED |
NPS-AUTH-UNAUTHENTICATED |
Group-JWS iat outside the CA’s clock-skew window (default ±5 minutes) — see §5.1.3 (NPS-CR-0003) |
NIP-CERT-PARENT-REVOKED |
NPS-AUTH-UNAUTHENTICATED |
A session NID’s parent / group NID is revoked or expired (chain check, §7 step 3a) — see §5.1.3 (NPS-CR-0003) |
HTTP-mode status mapping: see status-codes.md.
CA private keys MUST be stored in an HSM or in an encrypted key file (AES-256-GCM). The OSS reference implementation uses encrypted files and reserves an HSM interface.
OCSP response times SHOULD be normalized (fixed 200 ms delay) to prevent certificate-status inference via response-time side channels.
At every link in the delegation chain, scope MUST NOT exceed that of its parent. CAs MUST enforce this at issuance time.
| Version | Date | Changes |
|---|---|---|
| 0.8 | 2026-05-11 | Expanded §5.2 TrustFrame: added full field definition table (10 fields, including new required issued_at, serial, and signer_nid for revocation/audit traceability), explicit signature-canonicalisation rule (matching §5.1 IdentFrame), error sub-table, and full example. Added TrustFrame verification note to §7 (admit IdentFrames whose issued_by is a grantee_ca when a valid TrustFrame from a trusted grantor_nid covers the request). Four new error codes (§9): NIP-TRUST-FRAME-EXPIRED, NIP-TRUST-FRAME-GRANTOR-REVOKED, NIP-TRUST-FRAME-SCOPE-EXCEEDS-GRANTOR, NIP-TRUST-FRAME-NODES-PATTERN-INVALID. |
| 0.7 | 2026-05-07 | NPS-CR-0003: Orchestrator group NIDs and short-lived session NIDs. New §3.1 reserves group- / session- identifier prefixes on entity-type = agent. New signed IdentFrame.lineage object with role / parent_nid / group_nid / session_id / purpose / owner_user_id / owner_key_id (§5.1.3). New revocation reason parent_revoked (§5.3). New verification step 3a for chain-check (§7). Four new CA endpoints under /v1/orchestrators/groups/... (§8). Seven new error codes (§9): NIP-CA-GROUP-REVOKED, NIP-CA-PARENT-NOT-FOUND, NIP-CA-PARENT-NOT-GROUP, NIP-CA-SESSION-VALIDITY-INVALID, NIP-CA-JWS-INVALID, NIP-CA-JWS-EXPIRED, NIP-CERT-PARENT-REVOKED. Backward-compatible for ordinary single-NID flows; opt-in for orchestrators. |
| 0.6 | 2026-05-01 | Added topology:read to the standard capabilities registry (§5.1 capabilities table). This capability is required by Anchor Nodes at Phase 1–2 to gate access to topology read operations (topology.snapshot / topology.stream) as defined by the NPS-2 §12.4 minimum authorization binding (M6). Self-declared and key-signed at Phase 1–2; CA-attested role binding (id-nps-node-roles cert extension) deferred to Phase 3 pending RFC-0002 stabilization. |
| 0.5 | 2026-04-26 | Added §5.1.2 Reputation Log Entry — wire format for Certificate-Transparency-style append-only behavioral records about a subject_nid. Phase 1 ships the entry shape (12 fields including dual-signature requirements over JCS-canonicalised form), the initial 8-value incident enum (cert-revoked / rate-limit-violation / tos-violation / scraping-pattern / payment-default / contract-dispute / impersonation-claim / positive-attestation), and the 5-step severity enum. New error codes NIP-REPUTATION-ENTRY-INVALID and NIP-REPUTATION-LOG-UNREACHABLE. Merkle / STH / inclusion-proof surface deferred to Phase 2 per NPS-RFC-0004 §8.1. |
| 0.4 | 2026-04-25 | Added §5.1.1 Assurance Levels (anonymous / attested / verified); IdentFrame gains optional assurance_level field with backward-compatible default anonymous; new error codes NIP-ASSURANCE-MISMATCH and NIP-ASSURANCE-UNKNOWN. See NPS-RFC-0003. Depends-On NCP version corrected to v0.6 per NPS-RFC-0001. |
| 0.2 | 2026-04-12 | Unified port 17433; added metadata field to IdentFrame (tokenizer auto-match); error codes switched to NPS-status-code mapping; error-code list completed |
| 0.1 | 2026-04-10 | Initial spec: NID format, IdentFrame/TrustFrame/RevokeFrame, CA Server API, verification flow |
Copyright: LabAcacia / INNO LOTUS PTY LTD · Apache 2.0