The GentID Protocol

Version: 0.1 (draft) Status: Draft for implementation feedback License: This specification is released under CC-BY-4.0. Reference implementations are MIT.

GentID is a federated identity and authorization protocol for AI agents. It lets any organization issue cryptographic identities for its agents under its own domain, and lets any relying party verify those identities — and the financial authorizations they carry — with pure cryptography, offline after caching, with no central registry.

The trust model is DKIM's, applied to agents: anyone can run a GentID node, but only the controller of delta.com's DNS can issue identities under delta.com.

Requirement language. The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.


1. Overview and Terminology

TermMeaning
OrgA legal or operational entity anchored to a DNS domain. Controls a root key.
NodeSoftware an org runs (or has hosted) that issues certificates and serves protocol endpoints for its domain.
DelegationA signed certificate by which a key grants a narrower set of powers to a child key (gentid.delegation.v1, §4.1).
Agent certificateThe leaf certificate binding an agent's key to its identifier (gentid.agent.v1, §4.2).
BundleThe set of certificates (and optional attestations / revocation lists) a relying party needs to verify a chain offline (§5.1).
EnvelopeA signed, timestamped, nonced wrapper an agent puts around a message or request (gentid.msg.v1, §5.1).
MandateA signed, verifiable spending authorization (gentid.mandate.v1, §7.1).
EnforcerA settlement institution that holds funds and enforces mandates. GentID verifies mandates; enforcers enforce them (§7.3).
Relying party (RP)Any service that receives an envelope and verifies it.
AnchorThe set of root public keys for a domain, established via DNS TXT (§3.1) and/or /.well-known/gentid.json (§3.2).

A conforming verifier implements §3.3, §5.2, and §6.4. A conforming issuer additionally implements §3.1–§3.2, §4, and §6.1–§6.3. Mandate support (§7) and the HTTP binding (§8) are separately conformable modules.


2. Identifiers

2.1 Syntax

gentid-id     = "gentic:agent:" domain *( ":" path-seg ) ":" name
domain        = <DNS domain, lowercase, punycode for IDNs>
path-seg      = 1*32( ALPHA / DIGIT / "-" / "_" )   ; delegation path segment
name          = 1*64( ALPHA / DIGIT / "-" / "_" )   ; leaf agent name

Examples:

gentic:agent:delta.com:support-bot
gentic:agent:delta.com:ops:booking:rebooker-7

The path segments mirror the delegation chain (§4.1): each path-seg is the name of one delegation link between the org root and the agent. The final component is the agent's own name. Identifiers are case-sensitive after the domain; the domain is always lowercase.

Legacy identifiers. IDs of the form gentic:agent:<hex> where <hex> is 6–64 lowercase hex characters and contains no further : separators are legacy registry-era IDs. Parsers MUST accept them and flag them legacy: true. They carry no domain anchor and are verifiable only through the legacy compatibility node (§10).

Disambiguation rule: an ID whose first component after gentic:agent: matches ^[0-9a-f]{6,64}$ and has no following components is legacy. A domain always contains at least one ., so the grammars do not collide.

2.2 did:web projection

Every non-legacy GentID identifier projects to a did:web DID and back:

gentic:agent:delta.com:ops:rebooker-7
  ⇄  did:web:delta.com:gentid:ops:rebooker-7

Rule: did:web:<domain>:gentid[:path-seg…]:<name>. The gentid segment is fixed and reserved. A DID Document generator MUST produce a document whose verificationMethod contains the agent's current Ed25519 public key (as publicKeyJwk) with id = <did>#<kid>, and whose service array includes the org's GentID endpoint (type: "GentIDNode", serviceEndpoint from §3.2).


3. Trust Anchoring and Cryptography

3.1 DNS TXT anchor

An org anchors its root key(s) by publishing a TXT record at _gentid.<domain>:

_gentid.delta.com.  IN TXT  "v=gentid1; k=ed25519; p=<base64url raw 32-byte public key>"
  • Multiple _gentid TXT records MAY exist (key rotation, offline roots). Each valid record contributes one anchor key.
  • v=gentid1 is mandatory and MUST be first. Unknown tag=value pairs MUST be ignored.
  • Verifiers SHOULD note whether the DNS response was DNSSEC-authenticated and MAY expose this as a signal; absence of DNSSEC does not fail verification.

3.2 /.well-known/gentid.json

An org (or its node) MUST serve https://<domain>/.well-known/gentid.json:

{
  "type": "gentid.node.v1",
  "domain": "delta.com",
  "protocolVersion": "0.1",
  "rootKeys": [
    { "kid": "…", "kty": "OKP", "crv": "Ed25519", "x": "…" }
  ],
  "endpoints": {
    "agents": "https://gentid.delta.com/v1/agents/{id}",
    "delegations": "https://gentid.delta.com/v1/delegations/{kid}",
    "revocations": "https://gentid.delta.com/v1/revocations"
  },
  "revocation": {
    "maxAge": 300,
    "refreshInterval": 3600
  },
  "policy": {
    "mandateEnforcers": []
  },
  "sig": { "alg": "EdDSA", "kid": "…", "sig": "…" }
}
  • The document MUST be signed (§3.3) by a root key. Verifiers MUST check the signature against a key that appears in rootKeys and, when DNS is resolvable, require at least one rootKeys entry to match a _gentid TXT anchor (§3.1). When DNS is unavailable, a cached, previously DNS-corroborated document MAY be used within its cache TTL.
  • endpoints values are URI templates (RFC 6570 level 1). The three endpoints above are mandatory for issuers. Responses: agents/{id} returns the agent's current bundle (§5.1 bundle object); delegations/{kid} returns a single delegation certificate; revocations returns the current signed revocation list (§6.1).
  • revocation.maxAge (seconds) is the org's declared bound for financial-class freshness (§6.4). Default 300. revocation.refreshInterval is the re-publish cadence (§6.3).
  • policy.mandateEnforcers lists the domains of settlement institutions the org designates for its mandates (§7.4). Default empty.

3.3 Canonicalization, key identifiers, signatures

  • Algorithm: Ed25519 (EdDSA) only, in v0.1. alg MUST be "EdDSA".
  • Canonicalization: RFC 8785 (JCS). To sign an object: remove its sig member, canonicalize with JCS, sign the UTF-8 bytes. To verify: remove sig, canonicalize, verify.
  • Key identifier (`kid`): the RFC 7638 JWK thumbprint (SHA-256, base64url, no padding) of the key's OKP JWK {"crv":"Ed25519","kty":"OKP","x":"…"}.
  • Signature object: every signed protocol object carries "sig": { "alg": "EdDSA", "kid": "<signer kid>", "sig": "<base64url signature>" }.
  • All base64 in this spec is base64url without padding (RFC 4648 §5).
  • All timestamps are integer Unix seconds (UTC).

Private keys MUST be generated locally by the party that will control them. No conforming API transmits a private key across a network boundary.


4. Certificates

4.1 gentid.delegation.v1

A delegation certificate grants a child key a subset of the signer's powers:

{
  "type": "gentid.delegation.v1",
  "domain": "delta.com",
  "name": "ops",
  "path": [],
  "subject": { "kid": "<child kid>", "kty": "OKP", "crv": "Ed25519", "x": "…" },
  "grants": {
    "scopes": ["booking:*", "support:read"],
    "mandateCeiling": { "currency": "USD", "perTransaction": 500, "perDay": 2000, "total": 10000 },
    "maxDepth": 2
  },
  "nbf": 1750000000,
  "exp": 1781536000,
  "sig": { "alg": "EdDSA", "kid": "<parent kid>", "sig": "…" }
}
  • path is the list of ancestor delegation names between the root and this link (empty when signed by the root). name is this link's own segment. The concatenation path + [name] becomes the path of any child.
  • subject embeds the child's full public key, so bundles verify without key lookups.
  • Monotonic narrowing (issuers MUST enforce at issuance; verifiers MUST enforce at verification): - scopes ⊆ parent's effective scopes. Scope matching is exact-segment with * as a trailing wildcard segment (booking:* covers booking:create, not bookingx). The root's implicit scope set is ["*"]. - Every field of mandateCeiling ≤ the parent's corresponding field, in the same currency. A field absent on the parent means unlimited for the parent; a field absent on the child inherits the parent's value. The root's implicit ceiling is unlimited. If the parent has no mandateCeiling at all and the child declares none, the effective ceiling is unlimited — issuers SHOULD always set explicit ceilings. - exp ≤ parent exp; nbf ≥ parent nbf. - Chain length below this link ≤ maxDepth (root implicit maxDepth = 8; a child's maxDepth MUST be ≤ parent's maxDepth − 1).
  • A delegation signed directly by a root key is a root delegation; its sig.kid MUST match an anchor key (§3.1/§3.2).

4.2 gentid.agent.v1

The leaf certificate binding an agent key to its identifier:

{
  "type": "gentid.agent.v1",
  "id": "gentic:agent:delta.com:ops:rebooker-7",
  "domain": "delta.com",
  "path": ["ops"],
  "name": "rebooker-7",
  "subject": { "kid": "<agent kid>", "kty": "OKP", "crv": "Ed25519", "x": "…" },
  "grants": {
    "scopes": ["booking:rebook"],
    "mandateCeiling": { "currency": "USD", "perTransaction": 200 }
  },
  "nbf": 1750000000,
  "exp": 1755184000,
  "sig": { "alg": "EdDSA", "kid": "<issuing delegation's subject kid>", "sig": "…" }
}
  • id MUST equal gentic:agent: + domain + : + each path segment + : + name.
  • path MUST equal the issuing delegation's path + [name-of-issuing-delegation], or [] if issued directly by the root.
  • grants narrow monotonically from the issuing link exactly as in §4.1.
  • Agent certificates SHOULD be short-lived (RECOMMENDED ≤ 60 days).

5. Messages and Verification

5.1 gentid.msg.v1 envelope and the bundle

An agent proves authorship of a request by attaching an envelope:

{
  "type": "gentid.msg.v1",
  "id": "b64url-128-bit-random-nonce",
  "ts": 1752570000,
  "agent": "gentic:agent:delta.com:ops:rebooker-7",
  "kid": "<agent kid>",
  "payloadHash": "<base64url SHA-256 of the request payload>",
  "mandate": null,
  "sig": { "alg": "EdDSA", "kid": "<agent kid>", "sig": "…" }
}
  • id is a nonce: ≥ 128 bits of randomness, base64url. RPs MUST reject a repeated (agent, id) pair within the acceptance window.
  • ts MUST be within ±300 seconds of the RP's clock.
  • payloadHash binds the envelope to the request body (for HTTP: the raw request body bytes; empty body hashes the empty string). Binding of method/URL is profile-specific (§8.1).
  • mandate, when present, is a gentid.mandate.v1 object (§7.1) or its id if the mandate was delivered in the bundle.

The bundle delivers the verification material:

{
  "type": "gentid.bundle.v1",
  "agentCert": { …gentid.agent.v1… },
  "delegations": [ …root-first list of gentid.delegation.v1… ],
  "attestations": [ …optional, §9… ],
  "revocations": { …optional gentid.revocations.v1 snapshot, §6.1… }
}

5.2 Chain verification procedure

verifyChain(bundle, anchors, opts)anchors is the set of root keys for the claimed domain (resolved per §3.1/§3.2 by the caller or an injected resolver); opts includes now, operationClass (§6.4), and the envelope under test. Steps:

  1. Parse the envelope and bundle; reject unknown type values or malformed objects.
  2. Freshness of envelope: check ts window (±300 s) and nonce non-reuse.
  3. Anchor the chain root: the first delegation in delegations (or the agent cert itself if delegations is empty) MUST be signed by a kid present in anchors. If not ⇒ reject (E_ANCHOR).
  4. Walk the chain root→leaf: for each link, verify (a) sig against the previous link's subject key (or anchor key for the first), (b) nbf ≤ now ≤ exp, (c) path/name consistency (§4.1–§4.2), (d) monotonic narrowing of grants (scopes, ceilings, depth, validity windows). Any failure ⇒ reject (E_CHAIN, with the failing link index).
  5. Bind the leaf: the agent cert's subject.kid MUST equal the envelope's kid, its id MUST equal the envelope's agent, and the envelope sig MUST verify against the agent cert's subject key. Failure ⇒ reject (E_LEAF).
  6. Revocation: evaluate §6.4 for opts.operationClass against the freshest available revocation list for the domain. Any revoked kid or id anywhere in the chain ⇒ reject (E_REVOKED); stale/unknown revocation state is handled per class (§6.4).
  7. Output: { id, domain, path, name, grants (effective, fully narrowed), chain, assurance } (assurance per §9).

Steps 3–6 are the normative core; a verifier MUST NOT reorder them in a way that skips any check. verifyChain is a pure function over its inputs: all network resolution happens outside it.


6. Revocation

6.1 gentid.revocations.v1

{
  "type": "gentid.revocations.v1",
  "domain": "delta.com",
  "serial": 42,
  "issuedAt": 1752570000,
  "nextUpdateBy": 1752573600,
  "revoked": [
    { "kid": "…", "at": 1752569000, "reason": "key-compromise" },
    { "id": "gentic:agent:delta.com:ops:rebooker-7", "at": 1752569500 }
  ],
  "sig": { "alg": "EdDSA", "kid": "<root kid>", "sig": "…" }
}
  • serial MUST increase monotonically with every publication (including empty re-publications).
  • Anti-rollback: a verifier that has seen serial n for a domain MUST reject any list with serial < n (E_ROLLBACK) and MUST NOT let it overwrite the cached list.
  • Revoking a kid invalidates every certificate signed by that kid and, transitively, the entire subtree below it. Revoking an id invalidates that agent only.
  • The list MUST be signed by a root key.

6.2 Propagation and webhooks

A node MUST update its served list within 5 seconds of a revocation command. Nodes SHOULD support registered webhooks: on publication, POST the new list to each registered URL with an X-GentID-Serial header. Webhook delivery is best-effort; the pull path (§6.3) is authoritative.

6.3 Serving

The revocations endpoint (§3.2) serves the current list with standard HTTP caching (ETag = serial). Even with no revocations, a node MUST re-publish (new serial, fresh issuedAt/nextUpdateBy) at least every revocation.refreshInterval seconds, so that nextUpdateBy in the wild never lapses while the node is healthy.

6.4 Freshness tiers

Callers declare an operationClass; the verifier enforces:

ClassRequirement on the revocation list used
readBest effort. A cached list of any age MAY be used; missing list ⇒ proceed, flag revocationChecked: false.
commitList MUST satisfy now ≤ nextUpdateBy (grace: + 1 × refreshInterval). Otherwise ⇒ reject (E_STALE).
financialList MUST satisfy now − issuedAt ≤ revocation.maxAge (§3.2). Stale, missing, or unverifiable list ⇒ reject (E_STALE). There is no override parameter.

financial + unknown = deny is a normative invariant. Implementations MUST NOT expose a configuration flag that relaxes it.


7. Mandates

7.1 gentid.mandate.v1

A mandate is a signed spending authorization issued down the same delegation chain:

{
  "type": "gentid.mandate.v1",
  "id": "b64url-128-bit-random",
  "domain": "delta.com",
  "agent": "gentic:agent:delta.com:ops:rebooker-7",
  "limits": { "currency": "USD", "perTransaction": 200, "perDay": 1000, "total": 5000 },
  "scope": { "payees": ["gentic:agent:united.com:*", "acme-travel.com"], "categories": ["travel"] },
  "approval": { "aboveAmount": 150, "method": "human" },
  "escrow": { "required": true, "releaseOn": "receipt" },
  "enforcers": ["atheries.com"],
  "nbf": 1752500000,
  "exp": 1755184000,
  "sig": { "alg": "EdDSA", "kid": "<a kid in the agent's chain>", "sig": "…" }
}
  • sig.kid MUST be a key in the agent's verified chain (root, a delegation subject, or — discouraged — the agent itself). The mandate's authority derives from that key's position.
  • Every limits field MUST be ≤ the effective `mandateCeiling` at the signing key's chain position (§4.1), same currency. Exceeding any ceiling ⇒ invalid.
  • enforcers MUST be a subset of the domain's policy.mandateEnforcers (§3.2). An empty policy list means the org has designated no enforcers and no valid mandate can name one.
  • scope.payees entries are GentID id patterns (trailing * wildcard on the name/path) or bare domains. approval.aboveAmount marks the threshold above which the enforcer MUST obtain out-of-band human approval — enforcement of this is the enforcer's duty, not the verifier's.

7.2 Mandate verification

verifyMandate(mandate, bundle, anchors, opts):

  1. Run verifyChain(bundle, anchors, opts) — with opts.operationClass = "financial" whenever the mandate will be acted on. Failure fails the mandate.
  2. mandate.agent MUST equal the verified chain's id; mandate.domain MUST equal its domain.
  3. mandate.sig.kid MUST appear in the verified chain; verify sig against that key.
  4. nbf ≤ now ≤ exp.
  5. limits MUST fit within the effective mandateCeiling at the signing key's position.
  6. enforcers ⊆ the domain's published policy.mandateEnforcers (from the anchored .well-known document supplied in opts).
  7. Neither the mandate's id nor any chain key may be revoked (financial freshness, §6.4).

Output: { valid, mandate, effectiveCeiling, enforcers }.

7.3 Verified ≠ enforceable

Verification proves a mandate is authentic: really issued, within ceilings, not expired, not revoked. It does not prove funds exist, budgets have room, or approvals happened — that is the enforcer's job, against live state GentID does not hold. Conforming implementations MUST preserve this distinction in API naming and documentation. GentID software MUST NOT maintain balances, escrow state, or budget accounting.

7.4 Enforcers

An enforcer is identified by its domain and MUST itself be a GentID org (anchored per §3). Orgs designate acceptable enforcers in policy.mandateEnforcers. The enforcer client interface (MandateEnforcerClient: quote, escrow, release, status) is normatively named but its wire protocol is out of scope for v0.1; the HTTP 402 binding (§8.2) defines the RP-visible flow.

7.7 gentid.receipt.v1

Enforcers emit signed settlement receipts; GentID implementations parse and verify them (they never emit them):

{
  "type": "gentid.receipt.v1",
  "id": "…",
  "enforcer": "atheries.com",
  "mandate": "<mandate id>",
  "agent": "gentic:agent:delta.com:ops:rebooker-7",
  "payee": "acme-travel.com",
  "amount": 142.50,
  "currency": "USD",
  "outcome": "settled",
  "ts": 1752571000,
  "sig": { "alg": "EdDSA", "kid": "<enforcer key>", "sig": "…" }
}

outcomesettled | refunded | disputed | failed. Verification anchors sig.kid in the enforcer's domain (§3). Receipts are the evidence base for assurance tier 3 (§9).


8. HTTP Binding

8.1 Request headers

GentID-Envelope: <base64url(JSON gentid.msg.v1)>
GentID-Bundle:   <base64url(JSON gentid.bundle.v1)>
  • payloadHash covers the raw request body. For GET/HEAD (no body), the hash of the empty string. The envelope's canonical payloadHash input for HTTP additionally prepends the line <METHOD> <path+query>\n to the body bytes, binding the route.
  • RPs SHOULD cache bundles per agent (keyed by agent id + cert exp) and accept envelopes without a GentID-Bundle header when a cached, still-valid bundle exists; the response GentID-Bundle-Required: true header requests a re-send.

8.2 HTTP 402 payment binding

A service that requires payment responds:

HTTP/1.1 402 Payment Required
GentID-Price: 12.50 USD
GentID-Payee: acme-travel.com
GentID-Enforcers-Accepted: atheries.com, other-settler.example

Client flow: (1) hold a verified mandate whose enforcers intersects GentID-Enforcers-Accepted and whose limits/scope cover price and payee; (2) request escrow from the enforcer via its MandateEnforcerClient; (3) retry the original request adding GentID-Escrow: <base64url enforcer-signed escrow proof>; (4) the service verifies the escrow proof against the enforcer's anchors, fulfils, and the enforcer releases per the mandate's escrow.releaseOn rule, emitting a gentid.receipt.v1.


9. Assurance Tiers

TierNameEstablished by
0self-declaredChain verifies but no DNS/.well-known anchor corroborated (e.g. offline, unanchored dev keys).
1domain-verifiedChain anchors to the domain per §3. Proves control of the domain, nothing else.
2org-verifiedA gentid.attestation.v1 of kind org-verified, signed by a recognized attestation authority, attached to the org root and carried in the bundle.
3transaction-provenTier 2 plus verifiable gentid.receipt.v1 history from recognized enforcers.
{
  "type": "gentid.attestation.v1",
  "kind": "org-verified",
  "subjectDomain": "delta.com",
  "subjectKid": "<org root kid>",
  "authority": "gentid.com",
  "claims": { "legalName": "Delta Air Lines, Inc.", "jurisdiction": "US-GA" },
  "issuedAt": 1752000000,
  "exp": 1783536000,
  "sig": { "alg": "EdDSA", "kid": "<authority key>", "sig": "…" }
}

Verifiers compute tiers 0–1 locally; tiers 2–3 require the verifier to recognize the attestation authority / enforcer (a local trust decision, like a browser root store).

Honesty caveat (normative for documentation): domain-verified proves an agent belongs to a domain — not that the domain is honest. de1ta.com can be domain-verified. Lookalike-domain risk is mitigated by tiers 2–3, not tier 1, and conforming documentation MUST say so.


10. Legacy Compatibility and Aliases

  • Legacy hash IDs (§2.1) verify only against the designated legacy node (legacy.gentid.com), which acts as the anchor domain for all registry-era identities during the deprecation window.
  • Alias record — emitted at migration so history remains verifiable:
{
  "type": "gentid.alias.v1",
  "legacyId": "gentic:agent:a3f9d2e8b1c4",
  "id": "gentic:agent:delta.com:ops:rebooker-7",
  "issuedAt": 1752570000,
  "sig": { "alg": "EdDSA", "kid": "<legacy node root kid>", "sig": "…" }
}

Verifiers presented with a legacy signature MAY resolve the alias and report the new identity.


11. Security Considerations

  • Clock skew: the ±300 s envelope window is a hard bound; RPs with looser clocks MUST fix their clocks, not widen the window.
  • Nonce storage: RPs MUST retain seen (agent, id) pairs for at least the acceptance window (600 s) to prevent replay.
  • Key rotation: publish the new root TXT record alongside the old, re-sign .well-known, re-issue root delegations, then remove the old record. Verifiers accept any current anchor.
  • Offline roots: orgs SHOULD keep root keys offline and operate day-to-day from a root delegation; revoking that delegation's kid is then the org-level kill switch.
  • Downgrade: verifiers MUST ignore any object whose algEdDSA in v0.1.
  • Rollback: §6.1's serial monotonicity MUST be enforced with persisted state per domain.

12. Test Vectors

Conformance vectors live in spec/test-vectors/ as JSON files, each { "name", "description", "input": …, "expect": "valid" | "<error code>" }. The suite MUST cover: a valid 3-link chain; grant-widening chains (E_CHAIN); expired links; revoked kids; rollback lists (E_ROLLBACK); tampered envelopes (E_LEAF); stale financial-class checks (E_STALE); mandates exceeding ceilings; mandates naming undesignated enforcers. A verifier passing all vectors and implementing §5.2/§6.4/§7.2 is conformant.