This Custodian-Assisted DID Onboarding Protocol (CADOP) specifies an off-chain protocol enabling Web2 users to easily create AI Agent DIDs (as defined in NIP-1). Users typically start by generating a did:key via a WebAuthn Passkey client-side. They then authenticate with a CadopIdPService (Identity Provider), which issues a token (e.g., ID Token or Verifiable Credential) attesting to the user’s did:key, public key, and a Sybil resistance level. A CadopCustodianService (Custodian) consumes this token, verifies it, and then assists the user in registering their DID (e.g., by facilitating on-chain transactions if needed), ensuring the user is the controller of their Agent DID from inception with full capabilityDelegation rights. The Custodian’s operational keys are only granted capabilityInvocation for its specific services within the user’s DID document. Optional Web2ProofServiceCADOP instances can provide further Web2 claim verifications.
The primary goal of CADOP is to significantly lower the barrier for Web2 users to enter Web3 and AI Agent ecosystems. It achieves this by:
did:key).CadopIdPService (Identity Providers) to bridge Web2 authentication methods (like Passkeys, OAuth) with verifiable attestations about the user’s client-generated DID and Sybil resistance level.CadopCustodianService (Custodians) to consume these attestations and assist with the complexities of DID registration (e.g., gas payment), while ensuring user sovereignty from day one.| Pain point | Effect | Solution offered by CADOP (with IdP and Custodian roles) |
|---|---|---|
| Web2 users often lack crypto wallets/knowledge | High friction for DID creation/management; cannot sign initial transactions. | User generates did:key via Passkey. CadopIdPService attests to this did:key + sybil_level after user auth. CadopCustodianService consumes attestation and assists with DID registration, ensuring user’s initial key has capabilityDelegation. |
| Need for user-controlled identity from start | Concerns about vendor lock-in or custodian/IdP overreach. | User is controller of their DID from inception. IdP provides attestations. Custodian assists based on attestations. Both IdP and Custodian service keys (if in user’s DID doc) only get capabilityInvocation. User can revoke access or migrate DID at any time. |
| Need for verifiable Sybil resistance for new DIDs | Risk of anonymous DID spam or abuse if onboarding is too easy. | CadopIdPService authenticates user and issues a verifiable sybil_level claim. CadopCustodianService uses this claim to apply policies (e.g., for gas sponsorship or access to certain features), mitigating Sybil attacks. |
| Desire for progressive decentralization | Users might start with did:key but want to upgrade or use other services. |
User’s full control (capabilityDelegation) allows them to update their DID (e.g., to an on-chain DID), manage keys, and grant/revoke capabilities to any service (including IdPs or Custodians) according to NIP-1. |
| Need for verifiable Web2 links (optional) | DIDs alone don’t prove linkage to existing Web2 identities or attributes. | Web2ProofServiceCADOP (which could be part of an IdP or a separate entity) allows verification of specific Web2 information, issuing VCs that can be associated with the user’s DID for enhanced trust. |
| Discoverability of onboarding & related services | How to find trusted IdPs, Custodians, or Proof services? | CadopIdPService, CadopCustodianService, and Web2ProofServiceCADOP are discoverable via their NIP-1 compliant DID documents, enabling a competitive and decentralized ecosystem of providers. |
Custodians declare their services as part of their DID document (NIP-1) using the service property. This allows clients to discover and interact with Custodian services in a decentralized manner.
Each Custodian service endpoint in the DID document’s service array for a CadopCustodianService (formerly CustodianServiceNIP3) MUST include:
id: A URI that conforms to the DID Core specification, typically a fragment identifier relative to the Custodian’s DID (e.g., did:example:custodian123#cadop-service).type: A string identifying the type of service. For CADOP Custodian services, this MUST be CadopCustodianService.serviceEndpoint: A URI specifying the HTTPS base URL for the Custodian’s API related to DID onboarding.metadata: An optional JSON object containing additional information about the service, such as:
name: (String) A human-readable name for the Custodian service.auth_methods: (array of u16) An array of numeric codes representing the Web2 login methods supported by the Custodian for DID onboarding assistance (see auth_methods enumeration below).sybilLevel: (Integer, 0-3) An optional integer indicating the general level of Sybil resistance or proof-of-uniqueness associated with DIDs minted via this custodian. Level 0 indicates no specific measures, while higher levels (1-3) indicate increasing stringency (e.g., Level 1: email uniqueness; Level 2: phone uniqueness; Level 3: biometric or strong government ID based proof). Specifics of these levels are informative and may be further defined by community standards or the custodian’s policy.maxDailyMints: (Integer) An optional integer indicating a suggested or enforced maximum number of DIDs this custodian service instance might assist in minting per day, as a general operational parameter or rate-limiting indicator.Example DID Document Snippet for a Custodian (Illustrating CadopCustodianService):
{
"@context": "https://www.w3.org/ns/did/v1",
"id": "did:example:custodian123",
"service": [
{
"id": "did:example:custodian123#cadop-service",
"type": "CadopCustodianService",
"serviceEndpoint": "https://custodian.example.com/api/cadop",
"metadata": {
"name": "Example Custodian Inc.",
"auth_methods": [1, 7],
"sybilLevel": 1,
"maxDailyMints": 1000
}
},
]
}
Clients discover Custodians by resolving their DIDs and looking for service entries with type: "CadopCustodianService". The serviceEndpoint URI is then used to interact with the Custodian’s API as defined in the “Custodian-Assisted DID Onboarding Protocol”.
Web2ProofServiceCADOP)A Custodian or other entity MAY also offer a Web2ProofServiceCADOP. This service is distinct from, but complementary to, the CadopIdPService. While the CadopIdPService is primarily responsible for issuing the core ID Token or VC that attests to the user’s client-generated did:key and sybil_level to drive the CADOP onboarding flow, a Web2ProofServiceCADOP is responsible for verifying more specific Web2 claims and issuing Verifiable Credentials or signed attestations for these particular attributes (e.g., linking a DID to a specific Twitter handle, verifying an email address not used in the primary IdP auth, or attesting to an age group).
These VCs/attestations from a Web2ProofServiceCADOP can be:
CadopCustodianService alongside the primary ID Token from a CadopIdPService, if the Custodian’s policies require or can use such additional proofs for higher assurance levels or specific features.This service is declared in the DID document as follows:
id: A URI for the service (e.g., did:example:custodian123#web2proof).type: MUST be Web2ProofServiceCADOP.serviceEndpoint: A URI specifying the HTTPS base URL for the Web2 Proof Service API.metadata: An optional JSON object containing:
name: (String) A human-readable name for the proof service.accepts: (Array of Strings) An array of identifiers for the types of Web2 proofs or authentication methods it accepts as input (e.g., "GoogleOAuthProof", "TwitterOAuthProof", "EmailOTPProof", "PasskeyAssertion").supportedClaims: (Array of Strings) An array of claim types or Verifiable Credential types this service can issue or attest to (e.g., "EmailVerifiedClaim", "TwitterHandleClaim", "AgeOver18Claim").Example DID Document Snippet for a Web2ProofServiceCADOP:
{
"id": "did:example:custodian123#web2proof",
"type": "Web2ProofServiceCADOP",
"serviceEndpoint": "https://custodian.example.com/api/web2proof",
"metadata": {
"name": "Example Web2 Proof Oracle",
"accepts": ["GoogleOAuthProof", "PasskeyAssertion"],
"supportedClaims": ["EmailVerifiedCredential", "PasskeyOwnershipCredential"]
}
}
CadopIdPService)For CADOP to function with an externalized identity assertion mechanism, an Identity Provider (IdP) service is introduced. This IdP is responsible for authenticating the user (potentially via various methods including Passkeys or traditional Web2 OAuth) and issuing a secure token (typically an ID Token or a Verifiable Credential) that attests to key information about the user and their client-generated DID (e.g., a did:key).
An entity offering this IdP role MUST declare a CadopIdPService in its own DID document (NIP-1). This service endpoint is used by clients to initiate the authentication flow and by Custodians to obtain public keys for token verification.
CadopIdPService Entry Details:
id: A URI for the service (e.g., did:example:idp123#cadop-idp).type: MUST be CadopIdPService.serviceEndpoint: The base URI of the IdP’s OIDC compliant server (e.g., https://idp.example.com).metadata: An optional JSON object containing:
name: (String) A human-readable name for the IdP service.jwks_uri: (String) REQUIRED. The URI where the IdP’s JSON Web Key Set (JWKS) can be found, for verifying the signature of ID Tokens issued by this IdP.issuer_did: (String) OPTIONAL but RECOMMENDED. The DID of the IdP itself. This helps Custodians verify that the token issuer matches the expected IdP, especially when validating VCs.authorization_endpoint, token_endpoint) MAY be included here, or clients can rely on fetching them from the /.well-known/openid-configuration endpoint.Example DID Document Snippet for a CadopIdPService:
{
"id": "did:example:idp123#cadop-idp",
"type": "CadopIdPService",
"serviceEndpoint": "https://id.example.com",
"metadata": {
"name": "Example Identity Provider",
"jwks_uri": "https://id.example.com/.well-known/jwks.json",
"issuer_did": "did:example:idp123"
}
}
Required OIDC Endpoints for CadopIdPService:
The IdP offering CadopIdPService MUST expose standard OIDC discovery and operational endpoints.
| HTTP Method | Endpoint | Description |
|---|---|---|
GET |
/.well-known/openid-configuration |
Standard OIDC discovery document. MUST list at least authorization_endpoint, token_endpoint, jwks_uri, and supported response_types. |
GET |
(Value of jwks_uri) |
The IdP’s JSON Web Key Set (JWKS). |
GET/POST |
(Authorization Endpoint) | As defined in OIDC, e.g., /authorize. |
POST |
(Token Endpoint) | As defined in OIDC, e.g., /token. |
POST |
/credential |
OPTIONAL. If the IdP supports OIDC4VCI for issuing Verifiable Credentials instead of or in addition to ID Tokens. |
Parameters for OIDC /authorize Request:
When a client application redirects the user to the CadopIdPService’s authorization endpoint, it MUST include the following parameters:
| Parameter | Description |
|---|---|
scope |
MUST include openid did. The did scope signals that the client is requesting claims related to the user’s DID for CADOP. Additional scopes MAY be included. |
state |
REQUIRED. An opaque value used to maintain state between the request and the callback. It MUST be a Base64URL-encoded JSON string containing at least the custodianDid (the DID of the target CadopCustodianService which will assist in DID minting) and a nonce (a random string for replay protection and linking the request to the callback). The client SHOULD store the nonce locally to verify it upon callback. Other application-specific state parameters MAY be included in the JSON object before Base64URL encoding. |
response_type |
MUST be code (Authorization Code Flow). |
client_id |
The OIDC client identifier registered with the IdP. |
redirect_uri |
The callback URI where the IdP will redirect the user agent after authorization. |
code_challenge |
REQUIRED (PKCE). The code challenge generated by the client. |
code_challenge_method |
REQUIRED (PKCE). Typically S256. |
Required Claims in ID Token or Verifiable Credential:
The ID Token (or a Verifiable Credential obtained via OIDC4VCI from the /credential endpoint) issued by the CadopIdPService MUST contain the following claims. These claims are essential for the CadopCustodianService to verify the user’s control over the asserted DID and their Sybil resistance level.
| Claim | Type | Description |
|---|---|---|
iss |
String | REQUIRED. Issuer Identifier. This MUST match the issuer value expected by the Custodian (e.g., the serviceEndpoint or metadata.issuer_did of the trusted CadopIdPService). |
sub |
String | REQUIRED. Subject Identifier. This MUST be the user’s DID, typically a did:key derived from their Passkey’s public key, which the user generated on their client before initiating the IdP flow. |
aud |
String | REQUIRED. Audience. This MUST be the DID of the target CadopCustodianService (as passed in the state parameter during the /authorize request) or match a pattern agreed upon by trusted Custodians (e.g., cadop.* if the IdP serves multiple Custodians and policies allow). |
exp |
NumericDate | REQUIRED. Expiration time. The lifetime of this token SHOULD be short (e.g., recommended TTL ≤ 5 minutes) to limit its usability window. |
iat |
NumericDate | REQUIRED. Issued at time. |
jti |
String | REQUIRED. JWT ID. A unique identifier for the token, which can be used by the Custodian to prevent reuse (replay attacks). UUIDs are recommended. |
nonce |
String | REQUIRED. The nonce value that was passed in the state parameter of the initial /authorize request. This helps the client (and potentially the custodian if state is relayed) to correlate the token with the initial request and prevent injection. |
pub_jwk |
Object | REQUIRED. The public key, in JWK (JSON Web Key) format, corresponding to the private key of the sub (user’s DID). This allows the Custodian to verify that did:key(pub_jwk) matches the sub claim, ensuring the IdP is attesting to the correct user-controlled key. |
sybil_level |
Integer | REQUIRED (0-3). An integer representing the level of Sybil resistance verification performed by the IdP for this user and their sub DID. (0=No specific verification; 1=Email or basic Web2 OAuth; 2=Phone number; 3=Government ID or strong biometric, etc.). |
If a Verifiable Credential (VC) format (e.g., SD-JWT VC, JSON-LD based VC, or W3C VC Data Model with JWT profile) is used instead of an ID Token, the above claims MUST be mapped to their semantically equivalent properties or claims within the VC structure. The
issuerof the VC would be the IdP’s DID. ThecredentialSubjectwould containid(user’s DID),pub_jwk, andsybil_level.
When a CadopCustodianService receives a request (e.g., at a /cadop/mint endpoint) from a client, including an ID Token or VC obtained from a CadopIdPService, it MUST perform the following validation steps before assisting with DID registration or other actions:
jwks_uri specified in the trusted IdP’s CadopIdPService metadata (or a cached version).issuer DID which should correspond to the IdP’s DID).jwks_uri endpoints.exp: Ensure the token/VC has not expired.iat: Ensure the token/VC is not used too far in the past (optional, based on policy).jti: Check against a list of recently seen JTIs to prevent replay attacks. This list should be maintained for at least the maximum expected token lifetime.aud) Verification:
aud claim in the token/VC matches the Custodian’s own DID or a pattern it accepts (e.g., cadop.*) according to its policy and its trust relationship with the IdP.sub) and Public Key (pub_jwk) Consistency:
sub claim (user’s DID, e.g., did:key:z...) correctly corresponds to the provided pub_jwk. This typically involves constructing a did:key from the pub_jwk and ensuring it matches the sub value.sybil_level) Check:
sybil_level claim from the token/VC against the Custodian’s own configured minimum acceptable sybilLevel (as might be advertised in its own CadopCustodianService metadata).token.sybil_level < custodian.min_sybilLevel.Nonce Verification (Client-Side): While the nonce in the token helps the client verify the callback, the Custodian typically doesn’t see the initial client-generated nonce unless the state parameter or parts of it are relayed by the client to the Custodian along with the token. If they are, the Custodian MAY also verify it.
maxDailyMints for the IdP or globally) and any other internal risk management policies.Error Handling (Recommended HTTP Status Codes and JSON Error Responses):
| HTTP Status | JSON error Value |
Description |
|---|---|---|
400 |
invalid_request |
Malformed request, missing parameters, or client-provided data inconsistent. |
401 |
invalid_token |
Token/VC signature validation failed, iss is untrusted, exp is invalid, or jti reuse. |
403 |
permission_denied |
General permission issue not covered by more specific errors. |
403 |
untrusted_issuer |
The token/VC issuer (iss) is not in the Custodian’s list of trusted IdPs. |
403 |
audience_mismatch |
The aud claim does not match the Custodian. |
403 |
subject_key_mismatch |
The sub DID does not match the pub_jwk. |
403 |
insufficient_sybil_level |
The sybil_level from the token/VC is below the Custodian’s minimum requirement. |
429 |
quota_exceeded |
Minting quota (e.g., daily free mints) exceeded for the user, IdP, or globally. |
Compatibility with Multiple Identity Providers:
CADOP inherently supports the use of multiple CadopIdPService providers.
sybil_level, or other criteria.jwks_uri). Only tokens/VCs from these trusted IdPs will be accepted.auth_methods enumeration| Code | Login method | Protocol reference |
|---|---|---|
1 |
Google OAuth | OIDC |
2 |
Twitter OAuth | OAuth 2 |
3 |
Apple Sign-In | JWT |
4 |
GitHub OAuth | OAuth 2 |
5 |
Email OTP | RFC 6120 |
6 |
SMS OTP | — |
7 |
WeChat QR | OAuth 2 |
8 |
Discord OAuth | OAuth 2 |
10+ |
Reserved | Added in future versions |
To address potential Sybil attacks (where a single entity creates numerous DIDs illegitimately) and to provide varying levels of identity assurance, CADOP incorporates the concept of proof levels and operational quotas, often indicated through the Custodian’s CadopCustodianService metadata.
sybilLevel): The sybilLevel (e.g., 0-3) in a Custodian’s service metadata provides an indication of the strength of Web2 identity verification or uniqueness proofing associated with DIDs onboarded through that custodian.
Web2ProofServiceCADOP).Web2ProofServiceCADOP.maxDailyMints): The maxDailyMints metadata allows a Custodian to signal suggested or enforced limits on the number of DIDs it will assist in onboarding daily. This serves as a general operational parameter and a basic rate-limiting indicator, contributing to overall system stability and abuse prevention.Web2ProofServiceCADOP for Enhanced Proofs: For more sophisticated Sybil resistance or to link specific Web2 claims to a DID, the Web2ProofServiceCADOP plays a crucial role. It can consume various Web2 authentication outputs (e.g., OAuth tokens, Passkey assertions for specific relying parties) and issue Verifiable Credentials or signed attestations. These credentials can then be associated with the user’s DID, providing stronger and more specific assurances about the DID holder’s identity or attributes.While CADOP itself does not mandate specific on-chain contract internals for enforcing these, Custodians are expected to implement appropriate backend logic and policies to support their advertised sybilLevel and manage their operational quotas. Client applications can use this metadata to inform users or to select Custodians that meet their required level of trust or assurance.
This section outlines the off-chain message flow for a client application to assist a user in creating an Agent DID with the help of a CadopIdPService and a CadopCustodianService.
CadopIdPService (IdP) and CadopCustodianService (Custodian) providers. This can be through a curated list, recommendations, prior configuration, or by resolving their DIDs and inspecting their service arrays for the respective service types.serviceEndpoint URIs and other relevant metadata (e.g., IdP’s jwks_uri, Custodian’s supported sybilLevel).sequenceDiagram
participant UClient as User Client (Browser/SDK with Passkey)
participant IdP as CadopIdPService
participant Custodian as CadopCustodianService
participant Chain as Verifiable Data Registry (e.g., Blockchain)
UClient->>UClient: "User action: Gen initialKey (e.g. from Passkey) yielding userDID (a did:key)"
Note over UClient: "userDID (did:key) is now the subject."
UClient->>IdP: "Initiate OIDC AuthN Request to /authorize (scope: openid did, state: {custodianDid, nonce}, client_id, redirect_uri, PKCE params)"
IdP->>UClient: "Authenticate User (e.g., IdP prompts user for Passkey, OAuth, etc.)"
UClient-->>IdP: "User completes authentication with IdP"
IdP->>UClient: "Redirect to client redirect_uri with OIDC AuthZ Code"
UClient->>IdP: "Exchange AuthZ Code for ID Token at /token (grant_type: authorization_code, code, redirect_uri, client_id, PKCE code_verifier)"
IdP-->>UClient: "ID Token (contains claims: iss, sub=userDID, aud=custodianDid, exp, iat, jti, nonce, pub_jwk=initialAgentKey's public JWK, sybil_level)"
Note over UClient: "Client verifies ID Token (nonce, etc.)"
UClient->>Custodian: "Request DID Onboarding Assistance (e.g., POST /cadop/mint)"
Note right of UClient: "Payload includes: userDID (did:key), initialAgentKey_pub (JWK or other format), ID Token from IdP"
Custodian->>Custodian: "1. Verify ID Token signature (using IdP's jwks_uri from trusted IdP list)"
Custodian->>Custodian: "2. Validate claims (iss, aud, exp, jti, sub == did:key(pub_jwk from token), token.sybil_level >= custodian.min_sybilLevel)"
alt Verification Fails
Custodian-->>UClient: "Error (e.g., 401 invalid_token, 403 insufficient_sybil_level)"
end
Note over Custodian: "If verification successful, proceed to assist."
Custodian->>Chain: "Facilitate Agent DID registration (e.g., if creating on-chain DID or associating did:key with on-chain account)"
Note over Chain: "New DID has userDID as controller."
Note over Chain: "initialAgentKey_pub is in authentication & capabilityDelegation."
Note over Chain: "Custodian's service key (if any, for its services) only in capabilityInvocation."
Chain-->>Custodian: "tx_receipt / new_agent_did_details (if applicable)"
Custodian-->>UClient: "Success (new_agent_did, confirmation)"
UClient->>UClient: "User now has a self-controlled Agent DID."
Flow Description and Key Elements:
User Key Generation (Client-Side): The user, via their client application (browser/SDK), generates an initial cryptographic key pair. If using a WebAuthn Passkey, this key pair is managed by the Passkey provider. The public key is used to derive an initial DID for the user, typically a did:key (referred to as userDID). This userDID will be the subject (sub) in the ID Token and the controller of the final Agent DID.
CadopIdPService:
CadopIdPService./authorize request includes scope=openid did, and a state parameter containing the target custodianDid and a nonce./token endpoint.sub (the userDID), pub_jwk (the public key of userDID), sybil_level, and aud (the target custodianDid).CadopCustodianService:
CadopCustodianService (e.g., to a /cadop/mint endpoint). This request includes:
userDID (e.g., the did:key).initialAgentKey_pub) corresponding to userDID (e.g., in JWK format or another suitable representation if not already in the token’s pub_jwk).CadopIdPService.aud, exp, jti, sub vs pub_jwk consistency, sybil_level, etc.).userDID is a did:key and is intended to be the final Agent DID: The Custodian might simply acknowledge and record the successful onboarding if no on-chain action is needed from its side.userDID or initialAgentKey_pub as its controller and primary authenticator/delegator).userDID (or the user themselves via initialAgentKey_pub) as its controller.initialAgentKey_pub MUST be registered in the Agent DID’s verificationMethod and included in both authentication and capabilityDelegation verification relationships.CadopCustodianService or Web2ProofServiceCADOP) that are added to the user’s Agent DID document MUST only be in capabilityInvocation.initialAgentKey_pub: As described in the diagram notes, this is the user’s client-generated public key.web2_proof_attestation (Optional, from Web2ProofServiceCADOP): If a higher sybil_level or specific claims are needed beyond what the IdP’s ID Token provides, additional VCs from a Web2ProofServiceCADOP could be submitted to the Custodian. The Custodian would then also validate these VCs. This flow can be layered on top of the primary IdP flow.This protocol establishes a user-centric permission model from the inception of the DID, diverging from scenarios where a custodian might initially hold full control.
Key Principles for CADOP-minted DIDs:
User as Initial Controller: For DIDs created via CADOP, the controller field of the new Agent DID (which may initially be a did:key representation) MUST point to the Agent’s own DID. The user, through their initial key (e.g., derived from a Passkey), effectively controls their own DID from the start.
did:key, or the first device key registered during the CADOP flow) MUST be added to the verificationMethod array of the Agent’s DID document and MUST be included in both the authentication AND capabilityDelegation verification relationships.
controller (e.g., when upgrading from a did:key to a different DID method or rotating their primary controlling key).verificationMethod entries associated with the Custodian (i.e., keys controlled by the Custodian service itself, used for interacting with its declared services like CadopCustodianService or Web2ProofServiceCADOP) MUST only be included in the capabilityInvocation verification relationship in the user’s Agent DID document.
serviceEndpoint). The Custodian CANNOT manage the user’s other keys, change other verification relationships, or modify the controller of the user’s DID.expires property for its verificationMethod entries in the user’s DID document if its operational keys are intended to be short-lived or rotated.controller:
capabilityDelegation rights (as per point 2), any change to the controller field of the Agent’s DID document (e.g., if migrating from a did:key to a different on-chain DID method that the user will control) is authorized by the user’s own key, following the standard NIP-1 rules for such operations.authentication keys to change the controller in this model, as the controlling authentication key is already designated with capabilityDelegation.capabilityDelegation rights, remove the Custodian’s verificationMethod entry (and its reference in capabilityInvocation) from their DID document. This effectively revokes the Custodian’s ability to interact with the user’s DID via those specific service interfaces.This model ensures that the user retains self-sovereignty over their DID at all times, while the Custodian acts as an assistant for onboarding and a provider of callable services, without holding ultimate control over the user’s identity.
Given the permission model above, where the user’s primary key (e.g., derived from a Passkey) holds capabilityDelegation rights from the outset, the process for a user to change the controller of their Agent DID (for instance, when migrating from an initial did:key to a different DID method like did:rooch or another on-chain DID) follows the standard NIP-1 procedure for controller updates. This operation would be authorized by a signature from the user’s key that possesses capabilityDelegation.
capabilityDelegation rights, decides to change the controller of their Agent DID to a new DID or entity they control.controller field.capabilityDelegation (and also typically authentication).capabilityDelegation, the update is authorized.controller field of the Agent’s DID document is updated.If the user is also disassociating from a Custodian whose service keys were previously in capabilityInvocation, the user would, in a separate or combined update (authorized by their capabilityDelegation key), remove those service-specific verificationMethod entries and their references from capabilityInvocation.
The primary motivation for this NIP (now CADOP) is to lower the barrier to entry for Web2 users into the AI Agent ecosystem by abstracting away the immediate need for a crypto wallet, while ensuring user sovereignty from the outset.
DID-based Service Discovery: Custodian and Web2 Proof services are declared within their own DID documents (NIP-1). This approach aligns with the decentralized ethos of DIDs and offers:
User-Centric Onboarding:
CADOP enables users to obtain a fully self-controlled Agent DID (potentially a did:key initially) with assistance from a Custodian.
authentication and capabilityDelegation rights, making the user the DID’s controller.capabilityInvocation for its specific services within the user’s DID document.capabilityDelegation rights.This protocol focuses on custodian-assisted DID onboarding, service discovery via DID, and Web2 proof service declaration. Gas-relay, specific fee structures, and detailed SLA mechanics can be defined in separate, layered NIPs or by service providers.
This NIP defines a new protocol and service discovery mechanism. Systems not aware of CadopCustodianService in DID documents will not be able to discover these Custodians.
Test cases are highly recommended for all NIPs.
CadopCustodianService):
CadopCustodianService entry in the service array.serviceEndpoint, auth_methods, sybilLevel, and maxDailyMints.Web2ProofServiceCADOP):
Web2ProofServiceCADOP provider.Web2ProofServiceCADOP entry in the service array.serviceEndpoint, accepts, and supportedClaims.CadopIdPService):
CadopIdPService entry in the service array.serviceEndpoint, jwks_uri, and issuer_did./.well-known/openid-configuration.did:key (userDID).CadopIdPService using correct parameters (scope, state with custodianDid and nonce, PKCE).iss, sub=userDID, aud=custodianDid, exp, iat, jti, nonce, pub_jwk, sybil_level).nonce in the ID Token.userDID, pub_jwk, and ID Token to CadopCustodianService.aud, exp, jti, sub vs pub_jwk, sybil_level against its policy).controller field correctly points to the userDID.initialAgentKey_pub (from pub_jwk) is listed in verificationMethod and included in both authentication AND capabilityDelegation relationships in the new Agent’s DID document.capabilityInvocation.aud mismatch, insufficient sybil_level, JTI replay).did:key / Passkey):
deviceKey is paramount. If compromised, an attacker could impersonate the user’s Agent or attempt to change the controller. Implementations should ensure robust local key management.CadopIdPService Trust and Security:
sybil_level.redirect_uri, PKCE enforcement, secure token issuance).jwks_uri upon key rotation.exp): Short-lived tokens (as recommended for ID Tokens used in this flow) limit the window for misuse if intercepted.aud): Proper aud validation by Custodians is crucial to ensure tokens are used only for their intended purpose/recipient.sub and pub_jwk Binding: The Custodian’s verification that did:key(token.pub_jwk) == token.sub is critical to ensure the token attests to the user-controlled key.state and nonce Handling: Clients MUST use state for CSRF protection during the OIDC flow and verify the nonce in the ID Token to prevent token injection.did:key (if not hardware-bound like a Passkey) is securely managed.CadopCustodianService Trust and Security:
sybil_level and operational quotas.controller keys to prevent malicious modification of their service endpoints.Web2ProofServiceCADOP is crucial for verifying Web2 claims and issuing Verifiable Credentials. Custodians must ensure that their service endpoints are secure and that they properly validate incoming requests.sub (user’s did:key) is itself considered sensitive.Copyright and related rights waived via CC0.