From 8bcec603784bfc3ac6f13ef2c118dd764c65bed2 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 18:21:31 +0200 Subject: [PATCH 01/14] infra: add sync:ii-spec script and port 16 upstream spec commits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds scripts/sync-ii-spec.mjs — a deterministic 1:1 sync script that transforms docs/ii-spec.mdx from dfinity/internet-identity into docs/references/internet-identity-spec.md. Applies the same pattern as sync-motoko: strip MDX imports, remove H1, inline the Candid .did file, rewrite 8 absolute link patterns to relative paths, inject frontmatter. Bumps .sources/internetidentity to af980848..74ad617 (16 commits) and regenerates internet-identity-spec.md from the new pin. Updates AGENTS.md: internetidentity row now says to run npm run sync:ii-spec instead of manually applying patches; link-adaptation note updated to match. --- .sources/internetidentity | 2 +- AGENTS.md | 11 +- docs/references/internet-identity-spec.md | 818 +++++++++++++++++++--- package.json | 3 +- scripts/sync-ii-spec.mjs | 133 ++++ 5 files changed, 842 insertions(+), 125 deletions(-) create mode 100755 scripts/sync-ii-spec.mjs diff --git a/.sources/internetidentity b/.sources/internetidentity index af980848..74ad6172 160000 --- a/.sources/internetidentity +++ b/.sources/internetidentity @@ -1 +1 @@ -Subproject commit af9808483aa7182776700fda95efce5ef60c27f9 +Subproject commit 74ad617253d2c9555e473600b920a786ab09c448 diff --git a/AGENTS.md b/AGENTS.md index 29954278..8a771935 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -292,7 +292,7 @@ EOF | `candid` | Check for spec changes affecting the Candid reference or type-mapping examples | | `response-verification` | Check for API changes affecting certified variables patterns | | `dotskills` | Check if the `technical-documentation` skill changed in ways that affect review criteria | -| `internetidentity` | Check for spec changes in `docs/ii-spec.mdx`; re-sync `internet-identity-spec.md` (see link adaptation note below). Re-copy Candid interface from `src/internet_identity/internet_identity.did` if changed | +| `internetidentity` | Run `npm run sync:ii-spec` — the script handles all link rewrites, Candid inlining, and frontmatter. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` in `scripts/sync-ii-spec.mjs` | | `chain-fusion-signer` | Check for changed canister IDs, API methods, or key derivation patterns | | `papi` | Check for changed payment interface or cycle cost model | | `ic-pub-key` | Check for changed CLI flags or commands | @@ -317,14 +317,7 @@ EOF ``` 4. Run `npm run build` to confirm no broken links. -**Link adaptation for `internet-identity-spec.md`:** The upstream source (`docs/ii-spec.mdx`) uses absolute `internetcomputer.org` URLs pointing to the IC interface spec. Our file uses relative paths into the split `ic-interface-spec/` directory. After every re-sync, run: -```bash -grep -n "ic-interface-spec" docs/references/internet-identity-spec.md -``` -Any link of the form `internetcomputer.org/.../ic-interface-spec#` or `./ic-interface-spec.md#` must be converted. Use the anchor-to-file mapping at the bottom of `docs/references/internet-identity-spec.md` as the authoritative guide. If a new anchor appears that is not in the comment, find its file with: -```bash -grep -r "{#}" docs/references/ic-interface-spec/ -``` +**Link adaptation for `internet-identity-spec.md`:** Handled automatically by `npm run sync:ii-spec`. The script rewrites all absolute `internetcomputer.org` links to relative paths using `linkMap` in `scripts/sync-ii-spec.mjs`. If a new link pattern appears in the upstream source, the script exits with a warning listing the unhandled URL — add it to `linkMap` with the correct relative path (use `grep -r "{#}" docs/references/ic-interface-spec/` to find which file owns the anchor), then re-run. ### Synced files from submodules diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index 1624b91e..c8ae5dc0 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -1,29 +1,31 @@ --- title: "Internet Identity specification" -description: "The Internet Identity specification: identity design, client authentication protocol, delegation chain, and backend Candid interface" +description: "Technical specification of the Internet Identity service: authentication protocol, backend interface, and implementation notes." sidebar: - order: 14 + order: 10 --- +# The Internet Identity Specification + ## Introduction This document describes and specifies Internet Identity from various angles and at various levels of abstraction, namely: -- High level goals, requirements and use cases. +- High level goals, requirements and use cases. -- Overview of the security and identity machinery, including the interplay of identities, keys, and delegations. +- Overview of the security and identity machinery, including the interplay of identities, keys, and delegations. -- Interface as used by client applications frontends, i.e., our [client authentication protocol](#client-authentication-protocol). +- Interface as used by client applications frontends, i.e., our [client authentication protocol](#client-authentication-protocol). -- The interface of the Internet Identity Service *backend*, i.e., describing its contract at the Candid layer, as used by its frontend. +- The interface of the Internet Identity Service _backend_, i.e., describing its contract at the Candid layer, as used by its frontend. -- Important implementation notes about the Internet Identity Service backend. +- Important implementation notes about the Internet Identity Service backend. The Internet Identity service consists of: -- Its backend, a canister on ICP. More precisely, a canister on a dedicated subnet with a *well-known* canister id, and +- Its backend, a canister on ICP. More precisely, a canister on a dedicated subnet with a _well-known_ canister id, and -- its frontend, a web application served by the backend canister. +- its frontend, a web application served by the backend canister. Similarly, the client applications consist of a frontend (served by a canister) and (typically) one or more backend canisters. Only the frontend interacts with the Internet Identity Service directly (via the [client authentication protocol](#client-authentication-protocol) described below). @@ -31,73 +33,74 @@ Similarly, the client applications consist of a frontend (served by a canister) The Internet Identity service allows users to -- maintain identities on the Internet Computer +- maintain identities on the Internet Computer -- log in with these identities using one out of a set of security devices +- log in with these identities using one out of a set of security devices -- manage their set of security devices +- manage their set of security devices Some functional requirements are -- users have separate identities (or "pseudonyms") per client application (more precisely, per client application frontend "hostname", though see [Alternative Frontend Origins](#alternative-frontend-origins) for caveat about `.raw` domains) +- users have separate identities (or "pseudonyms") per client application (more precisely, per client application frontend "hostname", though see [Alternative Frontend Origins](#alternative-frontend-origins) for caveat about `.raw` domains) -- these identities are stable, i.e., do not depend on a user's security devices +- these identities are stable, i.e., do not depend on a user's security devices -- the client frontends interact with any canister on the Internet Computer under the user's identity with that frontend +- the client frontends interact with any canister on the Internet Computer under the user's identity with that frontend -- users do not need ever to remember secret information (but possibly per-user non-secret information) +- users do not need ever to remember secret information (but possibly per-user non-secret information) -- a security device does not need to be manually touched upon every interaction with a client application; a login is valid for a certain amount of time per identity +- a security device does not need to be manually touched upon every interaction with a client application; a login is valid for a certain amount of time per identity Some security requirements are -- The separate identities of a single user cannot be related merely based on their public key or principal ids, to impede user tracking. +- The separate identities of a single user cannot be related merely based on their public key or principal ids, to impede user tracking. -- The security of the identities does not depend on the privacy of data stored on canisters, or transmitted to and from canisters. In particular, the delegations handed out by the backend canister must not be sensitive information. +- The security of the identities does not depend on the privacy of data stored on canisters, or transmitted to and from canisters. In particular, the delegations handed out by the backend canister must not be sensitive information. -- (many more, of course; apply common sense) +- (many more, of course; apply common sense) Some noteworthy security assumptions are: -- The delivery of frontend applications is secure. In particular, a user accessing the Internet Identity Service Frontend through a TLS-secured HTTP connection cannot be tricked into running another web application. +- The delivery of frontend applications is secure. In particular, a user accessing the Internet Identity Service Frontend through a TLS-secured HTTP connection cannot be tricked into running another web application. :::note Just for background: At launch this meant we relied on the trustworthiness of the boundary nodes as well as the replica the boundary nodes happens to fetch the assets from. After launch, certification of our HTTP Gateway protocol and trustworthy client-side code (browser extensions, proxies, etc.) have improved this situation. ::: -- The security devices only allow the use of their keys from the same web application that created the key (in our case, the Internet Identity Service Frontend). +- The security devices only allow the use of their keys from the same web application that created the key (in our case, the Internet Identity Service Frontend). -- The user's browser is trustworthy, `postMessage` communication between different origins is authentic. +- The user's browser is trustworthy, `postMessage` communication between different origins is authentic. -- For user privacy, we also assume the Internet Identity Service backend can keep a secret (but since data is replicated, we do not rely on this assumption for other security properties). +- For user privacy, we also assume the Internet Identity Service backend can keep a secret (but since data is replicated, we do not rely on this assumption for other security properties). ## Identity design and data model - -The Internet Computer serves this frontend under hostnames `https://identity.internetcomputer.org` (official) and `https://identity.ic0.app` (legacy). +The Internet Computer serves this frontend under the hostname `https://id.ai` (official), with `https://identity.internetcomputer.org` and `https://identity.ic0.app` as legacy aliases. The canister maintains a salt (in the following the `salt`), a 32 byte long blob that is obtained via the Internet Computer's source of secure randomness. - :::note Due to replication of data in canisters, the salt should not be considered secret against a determined attacker. However, the canister will not reveal the salt directly and to the extent it is unknown to an attacker it helps maintain privacy of user identities. ::: -A user account is identified by a unique *Identity Anchor*, a natural number chosen by the canister. +A user Identity is identified by a unique _Identity Anchor_, a natural number chosen by the canister. A client application frontend is identified by its origin (e.g., `https://abcde-efg.ic0.app`, `https://nice-name.com`). Frontend applications can be served by canisters or by websites that are not hosted on the Internet Computer. -A user has a separate *user identity* for each client application frontend (i.e., per origin). This identity is a [*self-authenticating id*](./ic-interface-spec/index.md#id-classes) of the [DER encoded canister signature public key](./ic-interface-spec/index.md#canister-signatures) which has the form +A user has a separate _user identity_ for each client application frontend (i.e., per origin). This identity is a [_self-authenticating id_](./ic-interface-spec/index.md#id-classes) of the [DER encoded canister signature public key](./ic-interface-spec/index.md#canister-signatures) which has the form + ``` user_id = SHA-224(DER encoded public key) · 0x02` (29 bytes) ``` and the `BIT STRING` field of the DER encoded public key has the form + ``` bit_string = |ii_canister_id| · ii_canister_id · seed ``` where the `seed` is derived as follows + ``` seed = H(|salt| · salt · |user_number| · user_number · |frontend_origin| · frontend_origin) ``` @@ -108,11 +111,11 @@ where `H` is SHA-256, `·` is concatenation, `|…|` is a single byte representi A `frontend_origin` of the form `https://.icp0.io` will be rewritten to `https://.ic0.app` before being used in the seed. This ensures transparent pseudonym transfer between apps hosted on `ic0.app` and `icp0.io` domains. ::: -When a client application frontend wants to authenticate as a user, it uses a *session key* (e.g., Ed25519 or ECDSA), and by way of the authentication flow (details below) obtains a [*delegation chain*](./ic-interface-spec/https-interface.md#authentication) that allows the session key to sign for the user's main identity. +When a client application frontend wants to authenticate as a user, it uses a _session key_ (e.g., Ed25519 or ECDSA), and by way of the authentication flow (details below) obtains a [_delegation chain_](./ic-interface-spec/https-interface.md#authentication) that allows the session key to sign for the user's main identity. -The delegation chain consists of one delegation, called the *client delegation*. It delegates from the user identity (for the given client application frontend) to the session key. This delegation is created by the Internet Identity Service Canister, and signed using a [canister signature](./ic-interface-spec/index.md#canister-signatures). This delegation is unscoped (valid for all canisters) and has a maximum lifetime of 30 days, with a default of 30 minutes. +The delegation chain consists of one delegation, called the _client delegation_. It delegates from the user identity (for the given client application frontend) to the session key. This delegation is created by the Internet Identity Service Canister, and signed using a [canister signature](./ic-interface-spec/index.md#canister-signatures). This delegation is unscoped (valid for all canisters) and has a maximum lifetime of 30 days, with a default of 30 minutes. -The Internet Identity service frontend also manages an *identity frontend delegation*, delegating from the security device's public key to a session key managed by this frontend, so that it can interact with the backend without having to invoke the security device for each signature. +The Internet Identity service frontend also manages an _identity frontend delegation_, delegating from the security device's public key to a session key managed by this frontend, so that it can interact with the backend without having to invoke the security device for each signature. ## Client authentication protocol @@ -138,26 +141,30 @@ sequenceDiagram 2. It installs a `message` event handler on its own `window`. -3. It loads the url `https://identity.internetcomputer.org/#authorize` in a separate tab. Let `identityWindow` be the `Window` object returned from this. +3. It loads the url `https://id.ai/#authorize` in a separate tab. Let `identityWindow` be the `Window` object returned from this. 4. In the `identityWindow`, the user logs in, and the `identityWindow` invokes + ```ts - window.opener.postMessage(msg, "*") + window.opener.postMessage(msg, "*"); ``` where `msg` is + ```ts interface InternetIdentityReady { - kind: "authorize-ready" + kind: "authorize-ready"; } ``` 5. The client application, after receiving the `InternetIdentityReady`, invokes + ```ts - identityWindow.postMessage(msg, "https://identity.internetcomputer.org") + identityWindow.postMessage(msg, "https://id.ai"); ``` where `msg` is a value of type + ```ts interface InternetIdentityAuthRequest { kind: "authorize-client"; @@ -165,47 +172,50 @@ sequenceDiagram maxTimeToLive?: bigint; allowPinAuthentication?: boolean; derivationOrigin?: string; - autoSelectionPrincipal?: string + autoSelectionPrincipal?: string; } ``` where - - the `sessionPublicKey` contains the public key of the session key pair. + - the `sessionPublicKey` contains the public key of the session key pair. - - the `maxTimeToLive`, if present, indicates the desired time span (in nanoseconds) until the requested delegation should expire. The Identity Provider frontend is free to set an earlier expiry time, but should not create a one larger. + - the `maxTimeToLive`, if present, indicates the desired time span (in nanoseconds) until the requested delegation should expire. The Identity Provider frontend is free to set an earlier expiry time, but should not create a one larger. - - the `allowPinAuthentication` (EXPERIMENTAL), if present, indicates whether or not the Identity Provider should allow the user to authenticate and/or register using a temporary key/PIN identity. Authenticating dapps may want to prevent users from using Temporary keys/PIN identities because Temporary keys/PIN identities are less secure than Passkeys (webauthn credentials) and because Temporary keys/PIN identities generally only live in a browser database (which may get cleared by the browser/OS). + - the `allowPinAuthentication` (EXPERIMENTAL), if present, indicates whether or not the Identity Provider should allow the user to authenticate and/or register using a temporary key/PIN identity. Authenticating dapps may want to prevent users from using Temporary keys/PIN identities because Temporary keys/PIN identities are less secure than Passkeys (webauthn credentials) and because Temporary keys/PIN identities generally only live in a browser database (which may get cleared by the browser/OS). - - the `derivationOrigin`, if present, indicates an origin that should be used for principal derivation instead of the client origin. Internet Identity will validate the `derivationOrigin` by checking that it lists the client application origin in the `/.well-known/ii-alternative-origins` file (see [Alternative Frontend Origins](#alternative-frontend-origins)). - - - the `autoSelectionPrincipal`, if present, indicates the textual representation of this dapp's principal for which the delegation is requested. If it is known to Internet Identity and the corresponding identity has been the most recently used for the client application origin, it will skip the identity selection and immediately prompt for authentication. This feature can be used to streamline re-authentication after a session expiry. + - the `derivationOrigin`, if present, indicates an origin that should be used for principal derivation instead of the client origin. Internet Identity will validate the `derivationOrigin` by checking that it lists the client application origin in the `/.well-known/ii-alternative-origins` file (see [Alternative Frontend Origins](#alternative-frontend-origins)). + - the `autoSelectionPrincipal`, if present, indicates the textual representation of this dapp's principal for which the delegation is requested. If it is known to Internet Identity and the corresponding identity has been the most recently used for the client application origin, it will skip the identity selection and immediately prompt for authentication. This feature can be used to streamline re-authentication after a session expiry. 6. Now the client application window expects a message back, with data `event`. -7. If `event.origin` is not either `"https://identity.ic0.app"` or `"https://identity.internetcomputer.org"` (depending on which endpoint you are using), ignore this message. +7. If `event.origin` is not one of `"https://id.ai"`, `"https://identity.internetcomputer.org"`, or `"https://identity.ic0.app"`, ignore this message. 8. The `event.data` value is a JS object with the following type: + ```ts interface InternetIdentityAuthResponse { kind: "authorize-client-success"; - delegations: [{ - delegation: { - pubkey: Uint8Array; - expiration: bigint; - targets?: Principal[]; - }; - signature: Uint8Array; - }]; + delegations: [ + { + delegation: { + pubkey: Uint8Array; + expiration: bigint; + targets?: Principal[]; + }; + signature: Uint8Array; + }, + ]; userPublicKey: Uint8Array; authnMethod: "passkey"; } ``` - where the `userPublicKey` is the user's Identity on the given frontend and `delegations` corresponds to the CBOR-encoded delegation chain as used for [*authentication on the IC*](./ic-interface-spec/https-interface.md#authentication) and `authnMethod` is the method used by the user to authenticate (`passkey` for webauthn, `pin` for temporary key/PIN identity, and `recovery` for recovery phrase or recovery device). + where the `userPublicKey` is the user's Identity on the given frontend and `delegations` corresponds to the CBOR-encoded delegation chain as used for [_authentication on the IC_](./ic-interface-spec/https-interface.md#authentication) and `authnMethod` is the method used by the user to authenticate (`passkey` for webauthn, `pin` for temporary key/PIN identity, and `recovery` for recovery phrase or recovery device). 9. It could also receive a failure message of the following type + ```ts interface InternetIdentityAuthResponse { kind: "authorize-client-failure"; @@ -215,24 +225,30 @@ sequenceDiagram The client application frontend needs to be able to detect when any of the delegations in the chain has expired, and re-authorize the user in that case. -The [`@dfinity/auth-client`](https://www.npmjs.com/package/@dfinity/auth-client) NPM package provides helpful functionality here. +The [`@icp-sdk/auth`](https://www.npmjs.com/package/@icp-sdk/auth) NPM package provides helpful functionality here. The client application frontend should support delegation chains of length more than one, and delegations with `targets`, even if the present version of this spec does not use them, to be compatible with possible future versions. :::note The Internet Identity frontend will use `event.origin` as the "Frontend URL" to base the user identity on. This includes protocol, full hostname and port. This means -- Changing protocol, hostname (including subdomains) or port will invalidate all user identities. - - However, multiple different frontend URLs can be mapped back to the canonical frontend URL, see [Alternative Frontend Origins](#alternative-frontend-origins). - - Frontend URLs on `icp0.io` are mapped to `ic0.app` automatically, see [Identity design and data model](#identity-design-and-data-model). -- The frontend application must never allow any untrusted JavaScript code to be executed, on any page on that origin. Be careful when implementing a JavaScript playground on the Internet Computer. -::: +- Changing protocol, hostname (including subdomains) or port will invalidate all user identities. + - However, multiple different frontend URLs can be mapped back to the canonical frontend URL, see [Alternative Frontend Origins](#alternative-frontend-origins). + - Frontend URLs on `icp0.io` are mapped to `ic0.app` automatically, see [Identity design and data model](#identity-design-and-data-model). +- The frontend application must never allow any untrusted JavaScript code to be executed, on any page on that origin. Be careful when implementing a JavaScript playground on the Internet Computer. + ::: ## Alternative frontend origins To allow flexibility regarding the canister frontend URL, the client may choose to provide another URL as the `derivationOrigin` (see [Client authentication protocol](#client-authentication-protocol)). This means that Internet Identity will issue the same principals to the frontend (which uses a different origin) as it would if it were using the `derivationOrigin` directly. + This feature works for `canisterId` based URLs and [ICP custom domains](../guides/frontends/custom-domains.md) (backed by canisters) as `derivationOrigin`. +Glossary: + +- Derivation origin: The origin used to create the principal, e.g., `example.com`. +- Alternative origin: An origin that is allowed to use the same principal as the derivation origin. + :::caution This feature is intended to allow more flexibility with respect to the origins of a _single_ service. Do _not_ use this feature to allow _third party_ services to use the same principals. Only add origins you fully control to `/.well-known/ii-alternative-origins` and never set origins you do not control as `derivationOrigin`! ::: @@ -243,11 +259,42 @@ This feature is intended to allow more flexibility with respect to the origins o In order for Internet Identity to accept the `derivationOrigin` the origin of the client application must be listed in the JSON object served on the URL `https://.icp0.io/.well-known/ii-alternative-origins` (i.e. the file must be hosted by an ICP canister that must implement the `http_request` query call as specified [here](./http-gateway-protocol-spec.md)). +Internet Identity will fetch the `/.well-known/ii-alternative-origins` file from the derivation origin and check if the alternative origin is listed in the file. -### JSON Schema {#alternative-frontend-origins-schema} +```mermaid +sequenceDiagram + participant U as User + participant A as App Alternative Origin + participant IF as II Frontend + participant D as App Derivation Origin + D->>D: Adds /.well-known/ii-alternative-origins endpoint + D->>D: Adds "Alternative Origin" in /.well-known/ii-alternative-origins response + U->>A: User accesses Alternative Origin + U->>A: Clicks to log in with II + A->>IF: Opens II in new Tab + A->>IF: Sends Derivation Origin + IF->>D: Fetches /.well-known/ii-alternative-origins + IF->>IF: Checks presence of "Alternative Origin" in response + alt is present + IF->>U: Request user authentication + U->>IF: User authenticates + IF->>A: Authentication successful + else is NOT present + IF->>U: Invalid origin request + end +``` + +Requirements: +- No more than 10 alternative origins can be listed in the `/.well-known/ii-alternative-origins` file. +- Derivation Origin must expose the `/.well-known/ii-alternative-origins` file. +- The `/.well-known/ii-alternative-origins` file must be hosted by an ICP canister that must implement the `http_request` query call. +- The alternative origins must be present in the file `/.well-known/ii-alternative-origins` of the derivation origin requested. +- The alternative origin's application must set the desired Derivation Origin when [logging in the user with AuthClient](https://github.com/dfinity/icp-js-core/blob/a6aa6e2eb58cf2cbae92156b957293e40f458323/packages/auth-client/src/index.ts#L132). -``` json +### JSON Schema {#alternative-frontend-origins-schema} + +```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "II Alternative Origins Principal Derivation Origins", @@ -265,14 +312,13 @@ In order for Internet Identity to accept the `derivationOrigin` the origin of th "uniqueItems": true } }, - "required": [ "alternativeOrigins" ] + "required": ["alternativeOrigins"] } ``` - - ### Example -``` json + +```json { "alternativeOrigins": [ "https://alternative-1.com", @@ -290,7 +336,7 @@ To prevent misuse of this feature, the number of alternative origins _must not_ ::: :::note -In order to allow Internet Identity to read the path `/.well-known/ii-alternative-origins`, the CORS response header [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) must be set and allow the Internet Identity origin `https://identity.internetcomputer.org`. +In order to allow Internet Identity to read the path `/.well-known/ii-alternative-origins`, the CORS response header [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) must be set and allow the Internet Identity origin being used, for example `https://id.ai`, `https://identity.internetcomputer.org`, or `https://identity.ic0.app`. ::: ## The Internet Identity Service Backend interface @@ -298,17 +344,18 @@ In order to allow Internet Identity to read the path `/.well-known/ii-alternativ This section describes the interface that the backend canister provides. Note that the interface is split into 4 categories: -* Identity management API, i.e. APIs for end-users to manage their identity. - * Legacy API. - * API v2 (experimental, incomplete). - * The aim of the API v2 is to introduce changes that cannot be made without breaking existing clients of the legacy API. While the API v2 has feature parity with the legacy API, the desired changes are not fully implemented yet: - * Methods should return proper results with meaningful errors. - * Adding explicit WebAuthn signatures to security critical operations. -* HTTP gateway protocol, required to serve `https://identity.internetcomputer.org`. -* Auth protocol, for creating signed delegations. -* Verifiable credentials protocol, for creating id alias credentials. -* Internal methods, not intended to be called by external clients. - * These are methods related to initialization of II itself and integration with its archive. + +- Identity management API, i.e. APIs for end-users to manage their identity. + - Legacy API. + - API v2 (experimental, incomplete). + - The aim of the API v2 is to introduce changes that cannot be made without breaking existing clients of the legacy API. While the API v2 has feature parity with the legacy API, the desired changes are not fully implemented yet: + - Methods should return proper results with meaningful errors. + - Adding explicit WebAuthn signatures to security critical operations. +- HTTP gateway protocol, required to serve `https://id.ai`. +- Auth protocol, for creating signed delegations. +- Verifiable credentials protocol, for creating id alias credentials. +- Internal methods, not intended to be called by external clients. + - These are methods related to initialization of II itself and integration with its archive. The summary is given by the Candid interface: @@ -317,6 +364,7 @@ type UserNumber = nat64; type AccountNumber = nat64; type PublicKey = blob; type CredentialId = blob; +type Aaguid = blob; type DeviceKey = PublicKey; type UserKey = PublicKey; type SessionKey = PublicKey; @@ -341,7 +389,6 @@ type HttpResponse = record { headers : vec HeaderField; body : blob; upgrade : opt bool; - streaming_strategy : opt StreamingStrategy; }; type StreamingCallbackHttpResponse = record { @@ -388,6 +435,7 @@ type DeviceData = record { pubkey : DeviceKey; alias : text; credential_id : opt CredentialId; + aaguid : opt Aaguid; purpose : Purpose; key_type : KeyType; protection : DeviceProtection; @@ -407,6 +455,7 @@ type DeviceWithUsage = record { pubkey : DeviceKey; alias : text; credential_id : opt CredentialId; + aaguid : opt Aaguid; purpose : Purpose; key_type : KeyType; protection : DeviceProtection; @@ -449,6 +498,8 @@ type AddTentativeDeviceResponse = variant { device_registration_mode_off; // There is another device already added tentatively another_device_tentatively_added; + // Passkey with this public key is already used + passkey_with_this_public_key_is_already_used; }; type VerifyTentativeDeviceResponse = variant { @@ -562,7 +613,7 @@ type CaptchaConfig = record { // Each field is wrapped is `opt` to indicate whether the field should // keep the previous value or update to a new value (e.g. `null` keeps the previous value). // -// Some fields, like `openid_google`, have an additional nested `opt`, this indicates +// Some fields, like `analytics_config`, have an additional nested `opt`, this indicates // enable/disable status (e.g. `opt null` disables a feature while `null` leaves it untouched). type InternetIdentityInit = record { // Set lowest and highest anchor @@ -589,17 +640,56 @@ type InternetIdentityInit = record { // Configuration for New Origin Flows. // If present, list of origins using the new authentication flow. new_flow_origins : opt vec text; - // Configuration for OpenID Google client - openid_google : opt opt OpenIdConfig; + // Configurations for OpenID clients + openid_configs : opt vec OpenIdConfig; + // Allowlist of domains that may be registered as discoverable SSO + // providers via `add_discoverable_oidc_config`. When set, fully replaces + // the built-in defaults. When unset, falls back to `dfinity.org` + // (production) or `beta.dfinity.org` (everything else), keyed off + // `is_production`. + sso_discoverable_domains : opt vec text; // Configuration for Web Analytics analytics_config : opt opt AnalyticsConfig; - // Configuration to fetch root key or not from frontend assets - fetch_root_key : opt bool; // Configuration to show dapps explorer or not enable_dapps_explorer : opt bool; // Configuration to set the canister as production mode. // For now, this is used only to show or hide the banner. is_production : opt bool; + // Configuration for dummy authentication used in e2e tests. + dummy_auth : opt opt DummyAuthConfig; + // Backend canister ID, needed for backward compatibility. + backend_canister_id : opt principal; + // Backend origin, needed to sync configuration with frontend. + backend_origin : opt text; + // DNSSEC verification configuration. Trust anchors used by any feature + // that verifies DNS records against the IANA-rooted DNSSEC chain + // (currently the email-recovery DKIM/DMARC flow). See + // `docs/ongoing/email-recovery.md` §7.5. + // + // Wrapped in `opt opt` to match the same set/clear pattern as + // `analytics_config` / `dummy_auth`: outer null keeps the previously + // stored value across an upgrade, `opt null` clears it, `opt opt c` + // sets it to `c`. + dnssec_config : opt opt DnssecConfig; +}; + +// DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS +// records consumes the same anchors; not specific to email recovery. +type DnssecConfig = record { + // IANA root KSK trust anchors. Multiple are accepted simultaneously so + // KSK rollover is a single config change in the next upgrade arg. + root_anchors : vec DnssecRootAnchor; +}; + +// One IANA root KSK trust anchor, in the same shape IANA publishes at +// `data.iana.org/root-anchors/root-anchors.xml`. Only `digest_type = 2` +// (SHA-256) is accepted; the legacy SHA-1 form is rejected at the +// verifier boundary. +type DnssecRootAnchor = record { + key_tag : nat16; + algorithm : nat8; + digest_type : nat8; + digest : blob; }; type ChallengeKey = text; @@ -612,10 +702,12 @@ type CaptchaResult = ChallengeResult; // Extra information about registration status for new devices type DeviceRegistrationInfo = record { - // If present, the user has tentatively added a new device. This - // new device needs to be verified (see relevant endpoint) before - // 'expiration'. + // If present, the user has registered a new authentication method. This new authentication + // method needs to be confirmed before 'expiration' in order to be added to the identity. tentative_device : opt DeviceData; + // If present, the user has registered a new session. This new session needs to be confirmed before + // 'expiration' in order for it be authorized to register an authentication method to the identity. + tentative_session : opt principal; // The timestamp at which the anchor will turn off registration mode // (and the tentative device will be forgotten, if any, and if not verified) expiration : Timestamp; @@ -631,6 +723,8 @@ type IdentityAnchorInfo = record { openid_credentials : opt vec OpenIdCredential; // The name of the Internet Identity name : opt text; + // The timestamp at which the anchor was created + created_at : opt Timestamp; }; type AnchorCredentials = record { @@ -662,18 +756,48 @@ type BufferedArchiveEntry = record { }; // OpenID specific types - type Iss = text; type Sub = text; type Aud = text; type JWT = text; type Salt = blob; +type OpenIdEmailVerification = variant { + Unknown; + Google; + Microsoft; +}; + type OpenIdConfig = record { + name : text; + logo : text; + issuer : text; client_id : text; + jwks_uri : text; + auth_uri : text; + auth_scope : vec text; + fedcm_uri : opt text; + email_verification : opt OpenIdEmailVerification; +}; + +// SSO provider config that uses two-hop discovery. +// The backend fetches https://{discovery_domain}/.well-known/ii-openid-configuration +// for { client_id, openid_configuration } and then fetches the standard OIDC +// discovery at openid_configuration for { issuer, jwks_uri }. +type DiscoverableOidcConfig = record { + discovery_domain : text; }; -type OpenIdCredentialKey = record { Iss; Sub }; +// Resolved SSO provider state. +// All fields other than discovery_domain are None until discovery completes. +type OidcConfig = record { + discovery_domain : text; + client_id : opt text; + openid_configuration : opt text; + issuer : opt text; +}; + +type OpenIdCredentialKey = record { Iss; Sub; Aud }; type AnalyticsConfig = variant { Plausible : record { @@ -690,6 +814,16 @@ type OpenIdCredential = record { aud : Aud; last_usage_timestamp : opt Timestamp; metadata : MetadataMapV2; + // SSO discovery domain, looked up by `(iss, aud)` against the + // canister's registered discoverable OIDC configs. `None` for + // direct-provider credentials (Google / Apple / Microsoft) and for + // SSO credentials whose provider is no longer registered. + sso_domain : opt text; + // Human-readable SSO name from the domain's + // `/.well-known/ii-openid-configuration`. `None` when the domain + // doesn't publish one — callers should fall back to `sso_domain` + // for the label. + sso_name : opt text; }; type OpenIdCredentialAddError = variant { @@ -697,6 +831,7 @@ type OpenIdCredentialAddError = variant { JwtVerificationFailed; OpenIdCredentialAlreadyRegistered; InternalCanisterError : text; + JwtExpired; }; type OpenIdCredentialRemoveError = variant { @@ -709,6 +844,7 @@ type OpenIdDelegationError = variant { NoSuchAnchor; JwtVerificationFailed; NoSuchDelegation; + JwtExpired; }; type OpenIdPrepareDelegationResponse = record { @@ -719,7 +855,6 @@ type OpenIdPrepareDelegationResponse = record { // API V2 specific types // WARNING: These type are experimental and may change in the future. - type IdentityNumber = nat64; // Map with some variants for the value type. @@ -736,6 +871,9 @@ type MetadataMapV2 = vec record { type WebAuthn = record { credential_id : CredentialId; pubkey : PublicKey; + + // Authenticator Attestation Global Unique Identifier (AAGUID) + aaguid : opt Aaguid; }; // Authentication method using generic signatures @@ -786,14 +924,18 @@ type AuthnMethodData = record { type OpenIDRegFinishArg = record { jwt : JWT; salt : Salt; + name : text; }; // Extra information about registration status for new authentication methods type AuthnMethodRegistrationInfo = record { - // If present, the user has registered a new authentication method. This - // new authentication needs to be verified before 'expiration' in order to - // be added to the identity. + + // If present, the user has registered a new authentication method. This new authentication + // method needs to be confirmed before 'expiration' in order to be added to the identity. authn_method : opt AuthnMethodData; + // If present, the user has registered a new session. This new session needs to be confirmed before + // 'expiration' in order for it be authorized to register an authentication method to the identity. + session : opt principal; // The timestamp at which the identity will turn off registration mode // (and the authentication method will be forgotten, if any, and if not verified) expiration : Timestamp; @@ -804,6 +946,13 @@ type AuthnMethodConfirmationCode = record { expiration : Timestamp; }; +type AuthnMethodSessionInfo = record { + name : opt text; + created_at : opt Timestamp; +}; + +type RegistrationId = text; + type AuthnMethodRegisterError = variant { // Authentication method registration mode is off, either due to timeout or because it was never enabled. RegistrationModeOff; @@ -811,9 +960,15 @@ type AuthnMethodRegisterError = variant { RegistrationAlreadyInProgress; // The metadata of the provided authentication method contains invalid entries. InvalidMetadata : text; + // The caller's principal is not self-authenticating. + NotSelfAuthenticating : principal; + // Passkey with this public key is already used + PasskeyWithThisPublicKeyIsAlreadyUsed; }; type AuthnMethodConfirmationError = variant { + Unauthorized : principal; + InternalCanisterError : text; // Wrong confirmation code entered. Retry with correct code. WrongCode : record { retries_left : nat8; @@ -824,6 +979,25 @@ type AuthnMethodConfirmationError = variant { NoAuthnMethodToConfirm; }; +type AuthnMethodRegistrationModeEnterError = variant { + InvalidRegistrationId : text; + Unauthorized : principal; + AlreadyInProgress; + InternalCanisterError : text; +}; + +type AuthnMethodRegistrationModeExitError = variant { + Unauthorized : principal; + InternalCanisterError : text; + RegistrationModeOff; + InvalidMetadata : text; + PasskeyWithThisPublicKeyIsAlreadyUsed; +}; + +type LookupByRegistrationIdError = variant { + InvalidRegistrationId : text; +}; + type IdentityAuthnInfo = record { authn_methods : vec AuthnMethod; recovery_authn_methods : vec AuthnMethod; @@ -835,6 +1009,9 @@ type IdentityInfo = record { openid_credentials : opt vec OpenIdCredential; // Authentication method independent metadata metadata : MetadataMapV2; + name : opt text; + // The timestamp at which the anchor was created + created_at : opt Timestamp; }; type IdentityInfoError = variant { @@ -852,6 +1029,8 @@ type AuthnMethodReplaceError = variant { InvalidMetadata : text; // No authentication method found with the given public key. AuthnMethodNotFound; + // Passkey with this public key is already used + PasskeyWithThisPublicKeyIsAlreadyUsed; }; type AuthnMethodMetadataReplaceError = variant { @@ -881,12 +1060,14 @@ type CreateAccountError = variant { InternalCanisterError : text; AccountLimitReached; Unauthorized : principal; + NameTooLong; }; type UpdateAccountError = variant { InternalCanisterError : text; AccountLimitReached; Unauthorized : principal; + NameTooLong; }; type AccountDelegationError = variant { @@ -895,6 +1076,28 @@ type AccountDelegationError = variant { NoSuchDelegation; }; +type GetDefaultAccountError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchAnchor; + NoSuchOrigin : record { + anchor_number : UserNumber; + }; +}; + +type SetDefaultAccountError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchAnchor; + NoSuchOrigin : record { + anchor_number : UserNumber; + }; + NoSuchAccount : record { + anchor_number : UserNumber; + origin : FrontendHostname; + }; +}; + type PrepareAccountDelegation = record { user_key : UserKey; expiration : Timestamp; @@ -1014,8 +1217,6 @@ type IdRegFinishResult = record { }; type IdRegFinishError = variant { - // The configured maximum number of identities has been reached. - IdentityLimitReached; // This call is unexpected, see next_step. UnexpectedCall : record { next_step : RegistrationFlowNextStep; @@ -1041,11 +1242,251 @@ type AccountUpdate = record { name : opt text; }; +type DummyAuthConfig = record { + // Prompts user for a index value (0 - 255) when set to true, + // this is used in e2e to have multiple dummy auth identities. + prompt_for_index : bool; +}; + +type IdentityPropertiesReplace = record { + name : opt text; +}; + +type IdentityPropertiesReplaceError = variant { + Unauthorized : principal; + StorageSpaceExceeded : record { + space_available : nat64; + space_required : nat64; + }; + NameTooLong : record { + limit : nat64; + }; + InternalCanisterError : text; +}; + +type PrepareAttributeRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The attributes to be prepared. Each key has the form + // `:`, where `` is either + // `openid:` (e.g. `openid:https://accounts.google.com:email`) + // or `sso:` (e.g. `sso:dfinity.org:email`). + // + // Each linked credential is addressable via exactly one scope: + // credentials obtained through a `DiscoverableOidcConfig` (two-hop SSO + // discovery) are reachable only via `sso:`; credentials from + // hardcoded OIDC providers (Google, Microsoft, …) are reachable only via + // `openid:`. Under `sso:` only `email` and `name` are supported; + // under `openid:` `email`, `name`, and `verified_email` are supported. + attribute_keys : vec text; +}; + +type GetAccountError = variant { + NoSuchOrigin : record { + anchor_number : UserNumber; + }; + NoSuchAccount : record { + anchor_number : UserNumber; + origin : FrontendHostname; + }; +}; + +type PrepareAttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; +}; + +type PrepareAttributeResponse = record { + issued_at_timestamp_ns : Timestamp; + attributes : vec record { text; blob }; +}; + +type GetAttributesRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // Timestamp received from the prepare_attributes call. + issued_at_timestamp_ns : Timestamp; + + // The attribute to be retrieved, must be a subset of certified_attributes from + // the prepare_attributes response. + attributes : vec record { text; blob }; +}; + +type CertifiedAttribute = record { + key : text; + value : blob; + signature : blob; +}; + +type CertifiedAttributes = record { + expires_at_timestamp_ns : Timestamp; + certified_attributes : vec CertifiedAttribute; +}; + +type GetAttributesError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; +}; + +// ICRC-3 attribute sharing types +// ============================== + +type Icrc3Value = variant { + Nat : nat; + Int : int; + Blob : blob; + Text : text; + Array : vec Icrc3Value; + Map : vec record { text; Icrc3Value }; +}; + +// A specification of an attribute to be certified. +type AttributeSpec = record { + // `:` where `` is + // either `openid:` or `sso:`. + // + // Examples: + // - `openid:https://accounts.google.com:email` + // - `sso:dfinity.org:email` + key : text; + + // If `value` is set, this attribute should be certified only if it matches + // the current value. This enables the II frontend to request a certificate + // for a set of attributes that is guaranteed to be approved by the user. + value : opt blob; + + // Whether the attribute scope should be omitted before certification. + // Certifying unscoped attributes is useful, e.g., when the user wants + // to share their preferred email without revealing which OpenID + // provider they use with II. + // + // Examples (e.g., `key = "openid:https://accounts.google.com:email"`): + // 1. If `omit_scope = true`, then the certified attribute key is `email`. + // 2. Otherwise, the certified attribute key is literally the value of `key`. + omit_scope : bool; +}; + +type PrepareIcrc3AttributeRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. May have + // been remapped from `.icp0.io` to `.ic0.app` for principal + // stability — see `unmapped_origin`. + origin : FrontendHostname; + + // The relying party's actual origin, before the legacy `icp0.io → + // ic0.app` remap that `origin` may have gone through. When set, this + // value is what gets certified as `implicit:origin` instead of `origin`, + // so that an RP signed in via the icp0.io domain sees its real origin + // in the certified message. The canister verifies that mapping + // `unmapped_origin` through the same legacy remap yields `origin`, + // so an RP can't certify an arbitrary value here. + unmapped_origin : opt FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The attributes to be certified. + attributes : vec AttributeSpec; + + // The nonce is a 32-byte value generated by the relying party. + // The purpose of the nonce is to prevent replay attacks and + // enable the relying party to expire attributes issued for a given flow. + // The value of the nonce will be included into the signed ICRC-3 + // message as a separate attribute with the key `implicit:nonce`. + nonce : blob; +}; + +type PrepareIcrc3AttributeResponse = record { + // Candid-encoded ICRC-3 Value map. Pass this to get_icrc3_attributes. + message : blob; +}; + +type PrepareIcrc3AttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; + AttributeMismatch : record { + problems : vec text; + }; +}; + +type GetIcrc3AttributeRequest = record { + // Identity for which the attributes were prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The message blob from PrepareIcrc3AttributeResponse. + message : blob; +}; + +type GetIcrc3AttributeResponse = record { + signature : blob; +}; + +type GetIcrc3AttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; + NoSuchSignature; +}; + +// List available attributes +type ListAvailableAttributesRequest = record { + // Identity for which available attributes should be listed. + identity_number : IdentityNumber; + + // Optional list of attribute keys to filter by. + // Each key is either a fully-scoped key (e.g., + // `"openid:https://accounts.google.com:email"` or + // `"sso:dfinity.org:email"`) or an unscoped attribute name (e.g., + // `"email"`) which matches all scopes. + // If not provided, all available attributes are returned. + attributes : opt vec text; +}; + +type ListAvailableAttributesResponse = vec record { text; blob }; + +type ListAvailableAttributesError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; +}; service : (opt InternetIdentityInit) -> { // Legacy identity management API // ============================== - create_challenge : () -> (Challenge); register : (DeviceData, ChallengeResult, opt principal) -> (RegisterResponse); add : (UserNumber, DeviceData) -> (); @@ -1069,7 +1510,6 @@ service : (opt InternetIdentityInit) -> { // V2 Identity Management API // ========================== // WARNING: The following methods are experimental and may ch 0ange in the future. - // Starts the identity registration flow to create a new identity. identity_registration_start : () -> (variant { Ok : IdRegNextStepResult; Err : IdRegStartError }); @@ -1093,6 +1533,11 @@ service : (opt InternetIdentityInit) -> { // Requires authentication. identity_metadata_replace : (IdentityNumber, MetadataMapV2) -> (variant { Ok; Err : IdentityMetadataReplaceError }); + // Replaces the identity properties. + // The existing properties will be overwritten. + // Requires authentication. + identity_properties_replace : (IdentityNumber, IdentityPropertiesReplace) -> (variant { Ok; Err : IdentityPropertiesReplaceError }); + // Adds a new authentication method to the identity. // Requires authentication. authn_method_add : (IdentityNumber, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodAddError }); @@ -1121,35 +1566,55 @@ service : (opt InternetIdentityInit) -> { // confirmed before it can be used for authentication on this identity. // The registration mode is automatically exited after the returned expiration timestamp. // Requires authentication. - authn_method_registration_mode_enter : (IdentityNumber) -> (variant { Ok : record { expiration : Timestamp }; Err }); + authn_method_registration_mode_enter : (IdentityNumber, opt RegistrationId) -> (variant { Ok : record { expiration : Timestamp }; Err : AuthnMethodRegistrationModeEnterError }); // Exits the authentication method registration mode for the identity. // Requires authentication. - authn_method_registration_mode_exit : (IdentityNumber) -> (variant { Ok; Err }); + authn_method_registration_mode_exit : (IdentityNumber, opt AuthnMethodData) -> (variant { Ok; Err : AuthnMethodRegistrationModeExitError }); // Registers a new authentication method to the identity. // This authentication method needs to be confirmed before it can be used for authentication on this identity. authn_method_register : (IdentityNumber, AuthnMethodData) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); + // Registers a new session for the identity. + // This session needs to be confirmed before it can be used to register an authentication method on this identity. + authn_method_session_register : (IdentityNumber) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); + + // Returns session info when session is confirmed and caller matches session. + authn_method_session_info : (IdentityNumber) -> (opt AuthnMethodSessionInfo) query; + // Confirms a previously registered authentication method. // On successful confirmation, the authentication method is permanently added to the identity and can // subsequently be used for authentication for that identity. // Requires authentication. authn_method_confirm : (IdentityNumber, confirmation_code : text) -> (variant { Ok; Err : AuthnMethodConfirmationError }); + lookup_by_registration_mode_id : (RegistrationId) -> (opt IdentityNumber) query; + // Authentication protocol // ======================= prepare_delegation : (UserNumber, FrontendHostname, SessionKey, maxTimeToLive : opt nat64) -> (UserKey, Timestamp); get_delegation : (UserNumber, FrontendHostname, SessionKey, Timestamp) -> (GetDelegationResponse) query; - // Attribute Sharing MVP API - // ========================= + // Attribute sharing protocol + // ========================== + prepare_attributes : (PrepareAttributeRequest) -> (variant { Ok : PrepareAttributeResponse; Err : PrepareAttributeError }); + get_attributes : (GetAttributesRequest) -> (variant { Ok : CertifiedAttributes; Err : GetAttributesError }) query; + + // ICRC-3 Attribute sharing protocol + // ================================== + prepare_icrc3_attributes : (PrepareIcrc3AttributeRequest) -> (variant { Ok : PrepareIcrc3AttributeResponse; Err : PrepareIcrc3AttributeError }); + get_icrc3_attributes : (GetIcrc3AttributeRequest) -> (variant { Ok : GetIcrc3AttributeResponse; Err : GetIcrc3AttributeError }) query; + list_available_attributes : (ListAvailableAttributesRequest) -> (variant { Ok : ListAvailableAttributesResponse; Err : ListAvailableAttributesError }) query; + + // Old Verifiable Credentials API + // ============================== // The methods below are used to generate ID-alias credentials during attribute sharing flow. prepare_id_alias : (PrepareIdAliasRequest) -> (variant { Ok : PreparedIdAlias; Err : PrepareIdAliasError }); get_id_alias : (GetIdAliasRequest) -> (variant { Ok : IdAliasCredentials; Err : GetIdAliasError }) query; // OpenID credentials protocol - // ========================= + // =========================== openid_identity_registration_finish : (OpenIDRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); openid_credential_add : (IdentityNumber, JWT, Salt) -> (variant { Ok; Err : OpenIdCredentialAddError }); openid_credential_remove : (IdentityNumber, OpenIdCredentialKey) -> (variant { Ok; Err : OpenIdCredentialRemoveError }); @@ -1159,13 +1624,18 @@ service : (opt InternetIdentityInit) -> { // HTTP Gateway protocol // ===================== http_request : (request : HttpRequest) -> (HttpResponse) query; - http_request_update : (request : HttpRequest) -> (HttpResponse); + + // OIDC Discovery + // =============== + discovered_oidc_configs : () -> (vec OidcConfig) query; + add_discoverable_oidc_config : (DiscoverableOidcConfig) -> (); // Internal Methods // ================ init_salt : () -> (); stats : () -> (InternetIdentityStats) query; config : () -> (InternetIdentityInit) query; + whoami : () -> (principal) query; deploy_archive : (wasm : blob) -> (DeployArchiveResult); // Returns a batch of entries _sorted by sequence number_ to be archived. @@ -1211,17 +1681,32 @@ service : (opt InternetIdentityInit) -> { session_key : SessionKey, expiration : Timestamp ) -> (variant { Ok : SignedDelegation; Err : AccountDelegationError }) query; + + get_default_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + ) -> (variant { Ok : AccountInfo; Err: GetDefaultAccountError }) query; + + set_default_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber + ) -> (variant { Ok : AccountInfo; Err: SetDefaultAccountError }); + + // Looks up identity number when called with a recovery phrase + lookup_caller_identity_by_recovery_phrase : () -> (opt IdentityNumber); }; ``` ### Identity management (legacy API) -#### The `create_challenge` and `register` methods + +#### The `create_challenge` and `register` methods **Authorization**: This `register` request must be sent to the canister with `caller` that is the self-authenticating id derived from the given `DeviceKey`. -The `register` method is used to create a new user. The Internet Identity Service backend creates a *fresh* Identity Anchor, creates the account record, and adds the given device as the first device. +The `register` method is used to create a new user. The Internet Identity Service backend creates a _fresh_ Identity Anchor, creates the Identity record, and adds the given device as the first device. -In order to protect the Internet Computer from too many "free" update calls, and to protect the Internet Identity Service from too many user registrations, this call is protected using a CAPTCHA challenge. The `register` call can only succeed if the `ChallengeResult` contains a `key` for a challenge that was created with `create_challenge` (see below) in the last 5 minutes *and* if the `chars` match the characters that the Internet Identity Service has stored internally for that `key`. +In order to protect the Internet Computer from too many "free" update calls, and to protect the Internet Identity Service from too many user registrations, this call is protected using a CAPTCHA challenge. The `register` call can only succeed if the `ChallengeResult` contains a `key` for a challenge that was created with `create_challenge` (see below) in the last 5 minutes _and_ if the `chars` match the characters that the Internet Identity Service has stored internally for that `key`. #### The `add` method @@ -1235,7 +1720,7 @@ The `add` method appends a new device to the given user's record. The Internet Identity Service backend rejects the call if the user already has a device on record with the given public key. -This may also fail (with a *reject*) if the user is registering too many devices. +This may also fail (with a _reject_) if the user is registering too many devices. #### The `remove` method @@ -1340,6 +1825,7 @@ API V2: `identity_info` Fetches all data associated with an anchor including registration mode and tentatively registered devices. #### The `get_principal` query method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Fetches the principal for a given user and front end. @@ -1351,7 +1837,8 @@ Fetches the principal for a given user and front end. **Authorization**: Any non-anonymous identity can call this Initiates the registration of a new identity. Identity registration is a multistep process: -1. Start the registration (this call). + +1. Start the registration (this call). 2. Solve the captcha, if any. Whether this step is required is indicated by the result of the first (this) call. 3. Provide an authentication method to authenticate with in the future. @@ -1370,22 +1857,53 @@ This call is used to supply a solution to the captcha challenge returned from `i Supply an authentication method to complete the process of creating a new identity. If successful, the identity number of the newly created identity is returned. #### The `authn_method_metadata_replace` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Replaces the `metadata` map of the given authn_method. #### The `authn_method_security_settings_replace` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Replaces the `authn_method_security_settings_replace` map of the given authn_method. This method is split from `authn_method_metadata_replace` in order to be able to introduce different security requirements for security relevant changes to authn_methods while not impeding non-critical changes (such as e.g. changing the authn_method alias). #### The `identity_metadata_replace` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Replaces the `metadata` map associated with the given identity. +### Account Management + +Internet Identity supports Accounts. Accounts are subordinate entities to an Identity and allow a user to appear to any given dApp as a completely different user/principal while using the same Identity and authentication methods. Accounts are separated per origin - in practice this means that you will have one set of accounts for each dapp. + +Accounts can be renamed. They can currently not be deleted. There is a limit to how many accounts can be created per Identity. Unnamed ('Primary') accounts do not count towards the account limit, making sure a user can always log in to any dApp. + +The following endpoints relate to Account Management: + +#### The `create_account` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +The `create_account` method causes the Internet Identity Service backend to create a new account for the user for a specific origin. This is counted against the accounts limit. + +#### The `update_account` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Only Account numbers associated with the caller Identity may be used. + +The `update_account` method causes the Internet Identity Service backend to update an existing account for the user. Currently, only account names can be updated. Updating the name of a previously unnamed ('Primary') account means it is now counted against the accounts limit. Updating has no influence on the principal of the account. + +#### The `get_accounts` query method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +The `get_accounts` method causes the Internet Identity Service backend to return a list of all accounts for a given origin. + ### Authentication protocol + #### The `prepare_delegation` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. The `prepare_delegation` method causes the Internet Identity Service backend to prepare a delegation from the user identity associated with the given Identity Anchor and Client Application Frontend Hostname to the given session key. @@ -1399,38 +1917,109 @@ The method returns the expiration timestamp of the delegation. This is returned The actual delegation can be fetched using `get_delegation` immediately afterwards. #### The `get_delegation` query method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. For a certain amount of time after a call to `prepare_delegation`, a query call to `get_delegation` with the same arguments, plus the timestamp returned from `prepare_delegation`, actually fetches the delegation. Together with the `UserKey` returned by `prepare_delegation`, the result of this method is used by the Frontend to pass to the client application as per the [client authentication protocol](#client-authentication-protocol). +#### Account Delegations + +Account Delegations function like regular delegations, but the resultant principal is based on the account number and frontend hostname. + +#### The `prepare_account_delegation` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Only Account numbers associated with the caller Identity may be used. + +The `prepare_account_delegation` method causes the Internet Identity Service backend to prepare a delegation from the user identity associated with the given Identity Anchor, Account Number and Client Application Frontend Hostname to the given session key. The account number is an Option parameter. If a named account is to be used, the Option should be `Some(account_number)`. If an unnamed ('Primary') account is to be used, the parameter should be `None`. When passing `None`, this endpoint is functionally equivalent to `prepare_delegation`. + +This method returns the user's identity that's associated with the given Client Application Frontend Hostname. By returning this here, and not in the less secure `get_account_delegation` query, we prevent attacks that trick the user into using a wrong identity. It also returns the expiration timestamp of the delegation. This is returned purely so that the client can feed it back to the backend in `get_account_delegation`. + +This method needs to be called before the delegation can be fetched using `get_account_delegation`. + +#### The `get_account_delegation` query method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Only Account numbers associated with the caller Identity may be used. + +The `get_account_delegation` method causes the Internet Identity Service backend to return a delegation from the user identity associated with the given Identity Anchor, Account Number and Client Application Frontend Hostname to the given session key, if it has been prepared using `prepare_account_delegation`. The account number is an Option parameter. If a named account is to be used, the Option should be `Some(account_number)`. If an unnamed ('Primary') account is to be used, the parameter should be `None`. When passing `None`, this endpoint is functionally equivalent to `get_delegation` + +For a certain amount of time after a call to `prepare_account_delegation`, a query call to `get_account_delegation` with the same arguments, plus the timestamp returned from `prepare_account_delegation`, actually fetches the delegation. + +Together with the `UserKey` returned by `prepare_account_delegation`, the result of this method is used by the Frontend to pass to the client application as per the [client authentication protocol](#client-authentication-protocol). + +This method needs to be called after the delegation has been prepared using `prepare_account_delegation`. + +### OpenID Connect Protocol + +Internet Identity supports authentication using OpenID Connect credentials. This allows users to authenticate using credentials from supported OpenID Connect providers. + +#### The `openid_identity_registration_finish` method + +**Authorization**: Any non-anonymous identity can call this + +Completes the registration of a new identity using OpenID Connect credentials. This is part of the identity registration flow that starts with `identity_registration_start`. The salt needs to match the salt that was used to anonymize the principal when fetching the JWT from the frontend (in this case an anonymous principal). + +#### The `openid_credential_add` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Adds a new OpenID Connect credential to an existing identity. The credential is identified by a JWT token and a salt value. The salt needs to match the salt that was used to anonymize the principal when fetching the JWT from the frontend. + +#### The `openid_credential_remove` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Removes an OpenID Connect credential from an identity. + +#### The `openid_prepare_delegation` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Prepares a delegation for authentication using OpenID Connect credentials. Similar to the regular `prepare_delegation` method, but uses OpenID Connect credentials for authentication. + +This delegation is only used to authenticate to Internet Identity. When authenticating to a third-party dApp, the OpenID-based delegation is then used to prepare and get a regular delegation. + +Must be called before `openid_get_delegation`. + +#### The `openid_get_delegation` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Retrieves a prepared delegation for OpenID Connect authentication. This is used in conjunction with `openid_prepare_delegation` to complete the authentication flow. + +Must be called immediately after `openid_prepare_delegation`. + ### Verifiable Credentials Protocol See [here](../guides/authentication/verifiable-credentials.md) for the specification of the verifiable credentials protocol. ### HTTP gateway protocol -The methods `http_request` and `http_request_update` serve the front-end assets to browesers. See [here](./http-gateway-protocol-spec.md) for additional details. +The method `http_request` serve the front-end assets to browsers. See [here](./http-gateway-protocol-spec.md) for additional details. ### Internal APIs #### The `init_salt` method + **Authorization**: Can only be called by anyone. The `init_salt` method initialises the [salt](#salt). Traps if the salt is already initialised. #### The `stats` method + **Authorization**: Can only be called by anyone. Reports statistics and configuration values of Internet Identity. #### The `deploy_archive` method + **Authorization**: Can only be called by anyone. The `deploy_archive` method deploys the supplied wasm to the archive canister, given it matches the configured archive wasm hash. #### The `fetch_entries` and `acknowledge_entries` methods + **Authorization**: Can only be called by the archive canister. API for the archive canister to fetch archive entries from the Internet Identity canister and to trigger pruning of archived entries within Internet Identity. @@ -1445,13 +2034,13 @@ The `salt` used to blind the hashes that form the `seed` of the Canister Signatu Since this cannot be done during `canister_init` (no calls from canister init), the randomness is fetched by someone triggering the `init_salt()` method explicitly, or just any other update call. More concretely: -- Anyone can invoke `init_salt()` +- Anyone can invoke `init_salt()` -- `init_salt()` traps if `salt != EMPTY_SALT` +- `init_salt()` traps if `salt != EMPTY_SALT` -- Else, `init_salt()` calls `aaaaa-aa.raw_rand()`. When that comes back successfully, and *still* `salt == EMPTY_SALT`, it sets the salt. Else, it traps (so that even if it is run multiple times concurrently, only the first to write the salt has an effect). +- Else, `init_salt()` calls `aaaaa-aa.raw_rand()`. When that comes back successfully, and _still_ `salt == EMPTY_SALT`, it sets the salt. Else, it traps (so that even if it is run multiple times concurrently, only the first to write the salt has an effect). -- *all* other update methods, at the beginning, if `salt == EMPTY_SALT`, they await `self.init_salt()`, ignoring the result (even if it is an error). Then they check if we still have `salt == EMPTY_SALT` and trap if that is the case. +- _all_ other update methods, at the beginning, if `salt == EMPTY_SALT`, they await `self.init_salt()`, ignoring the result (even if it is an error). Then they check if we still have `salt == EMPTY_SALT` and trap if that is the case. ### Why we do not use `canister_inspect_message` @@ -1461,9 +2050,9 @@ It seems that this implies that we should use `canister_inspect_message` to reje But upon closer inspection (heh), this is not actually useful. -- One justification for this mechanism would be if we expect a high number of accidentally invalid calls. But we have no reason to expect them at the moment. +- One justification for this mechanism would be if we expect a high number of accidentally invalid calls. But we have no reason to expect them at the moment. -- Another is to protect against a malicious actor. But that is only useful if the malicious actor doesn't have an equally effective attack vector anyways, and in our case they do: If they want to flood the NNS with calls, they can use calls that do authenticate (e.g. keeping removing and adding devices, or preparing delegations); these calls would pass message inspection. +- Another is to protect against a malicious actor. But that is only useful if the malicious actor doesn't have an equally effective attack vector anyways, and in our case they do: If they want to flood the NNS with calls, they can use calls that do authenticate (e.g. keeping removing and adding devices, or preparing delegations); these calls would pass message inspection. On the flip side, implementing `canister_inspect_message` adds code, and thus a risk for bugs. In particular, it increases the risk that some engineer might wrongly assume that the authentication check in `canister_inspect_message` is sufficient and will not do it again in the actual method, which could lead to a serious bug. @@ -1473,6 +2062,7 @@ Therefore, the Internet Identity Canister intentionally does not implement `cani Link replacements from source (source used absolute/relative paths pointing outside this site): - internetcomputer.org [/docs]/current/references/ic-interface-spec#id-classes → ./ic-interface-spec/index.md#id-classes - internetcomputer.org [/docs]/current/references/ic-interface-spec/#canister-signatures → ./ic-interface-spec/index.md#canister-signatures (×2) + - internetcomputer.org [/docs]/current/references/ic-interface-spec/#signatures → ./ic-interface-spec/index.md#signatures - internetcomputer.org [/docs]/current/references/ic-interface-spec#authentication → ./ic-interface-spec/https-interface.md#authentication - internetcomputer.org [/docs]/current/references/ic-interface-spec/#system-api-inspect-message → ./ic-interface-spec/canister-interface.md#system-api-inspect-message - internetcomputer.org [/docs]/current/references/http-gateway-protocol-spec → ./http-gateway-protocol-spec.md diff --git a/package.json b/package.json index f93428f0..45dd0987 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "preview": "astro preview", "astro": "astro", "validate": "node scripts/validate.js --all", - "sync:motoko": "./scripts/sync-motoko.sh" + "sync:motoko": "./scripts/sync-motoko.sh", + "sync:ii-spec": "./scripts/sync-ii-spec.mjs" }, "dependencies": { "@astrojs/starlight": "^0.38.1", diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs new file mode 100755 index 00000000..b794167d --- /dev/null +++ b/scripts/sync-ii-spec.mjs @@ -0,0 +1,133 @@ +#!/usr/bin/env node +// Syncs the Internet Identity specification from .sources/internetidentity into +// docs/references/internet-identity-spec.md. +// +// Transformations applied: +// - Strip MDX import lines +// - Remove the H1 heading (Starlight renders the frontmatter title as H1) +// - Inline the Candid interface from internet_identity.did in place of the +// component +// - Rewrite absolute / relative links that point outside this site +// +// Usage: node scripts/sync-ii-spec.mjs +// or: npm run sync:ii-spec + +import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { execSync } from 'node:child_process'; + +const SOURCE_MDX = '.sources/internetidentity/docs/ii-spec.mdx'; +const SOURCE_DID = '.sources/internetidentity/src/internet_identity/internet_identity.did'; +const TARGET = 'docs/references/internet-identity-spec.md'; + +if (!existsSync(SOURCE_MDX)) { + console.error( + `ERROR: ${SOURCE_MDX} not found.\n` + + 'Run: git submodule update --init --depth 1 .sources/internetidentity' + ); + process.exit(1); +} + +const version = execSync('git -C .sources/internetidentity rev-parse --short HEAD') + .toString().trim(); +console.log(`Syncing II spec from dfinity/internet-identity@${version}...`); + +let content = readFileSync(SOURCE_MDX, 'utf8'); +const didContent = readFileSync(SOURCE_DID, 'utf8'); + +// 1. Strip MDX import lines +content = content.replace(/^import .*\n/gm, ''); + +// 2. Strip the H1 (Starlight renders it from frontmatter) +content = content.replace(/^# The Internet Identity Specification\n\n/, ''); + +// 3. Inline the Candid interface +content = content.replace( + '{IICandidInterface}', + '```candid\n' + didContent.trimEnd() + '\n```' +); + +// 4. Rewrite links — more specific patterns first to avoid partial matches +const linkMap = [ + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec#id-classes', + './ic-interface-spec/index.md#id-classes', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#canister-signatures', + './ic-interface-spec/index.md#canister-signatures', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#signatures', + './ic-interface-spec/index.md#signatures', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-inspect-message', + './ic-interface-spec/canister-interface.md#system-api-inspect-message', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication', + './ic-interface-spec/https-interface.md#authentication', + ], + [ + 'https://internetcomputer.org/docs/current/references/http-gateway-protocol-spec', + './http-gateway-protocol-spec.md', + ], + [ + 'https://internetcomputer.org/docs/current/developer-docs/web-apps/custom-domains/using-custom-domains', + '../guides/frontends/custom-domains.md', + ], + [ + '](vc-spec.md)', + '](../guides/authentication/verifiable-credentials.md)', + ], +]; + +for (const [old, replacement] of linkMap) { + content = content.replaceAll(old, replacement); +} + +// 5. Strip leading blank lines, then inject frontmatter +content = content.replace(/^\n+/, ''); +content = + `---\n` + + `title: "Internet Identity specification"\n` + + `description: "Technical specification of the Internet Identity service: authentication protocol, backend interface, and implementation notes."\n` + + `sidebar:\n` + + ` order: 10\n` + + `---\n\n` + + content; + +// 6. Append the link-adaptation log and Upstream comment +content = + content.trimEnd() + + '\n' + + `\n\n` + + `\n`; + +writeFileSync(TARGET, content); +console.log(`Written: ${TARGET}`); + +// Warn about any remaining absolute docs links that weren't rewritten +const remaining = [...content.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] + .map(m => m[0]); +const unique = [...new Set(remaining)]; +if (unique.length) { + console.warn('\nWARNING: Unhandled absolute links — add them to the linkMap:'); + unique.forEach(l => console.warn(` ${l}`)); + process.exit(1); +} else { + console.log('\nAll absolute links adapted. Run `npm run build` to verify.'); +} From e27fe3eb381f922f6b48bbd11efe011c9bf42901 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 18:26:10 +0200 Subject: [PATCH 02/14] fix(sync-ii-spec): fix H1 strip, sidebar order, and code-block link scope Three corrections to the sync script: - Add m flag to H1 regex so it matches after leading blank lines from import stripping - Move Candid inlining after link rewriting so .did code comments are not rewritten - Exclude code blocks from the remaining-links check (prose only) - Restore sidebar order: 14 to match previous value --- docs/references/internet-identity-spec.md | 6 ++--- scripts/sync-ii-spec.mjs | 28 ++++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index c8ae5dc0..7b276e69 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -2,11 +2,9 @@ title: "Internet Identity specification" description: "Technical specification of the Internet Identity service: authentication protocol, backend interface, and implementation notes." sidebar: - order: 10 + order: 14 --- -# The Internet Identity Specification - ## Introduction This document describes and specifies Internet Identity from various angles and at various levels of abstraction, namely: @@ -877,7 +875,7 @@ type WebAuthn = record { }; // Authentication method using generic signatures -// See ./ic-interface-spec/index.md#signatures for +// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#signatures for // supported signature schemes. type PublicKeyAuthn = record { pubkey : PublicKey; diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs index b794167d..c731e5ad 100755 --- a/scripts/sync-ii-spec.mjs +++ b/scripts/sync-ii-spec.mjs @@ -5,9 +5,9 @@ // Transformations applied: // - Strip MDX import lines // - Remove the H1 heading (Starlight renders the frontmatter title as H1) -// - Inline the Candid interface from internet_identity.did in place of the -// component // - Rewrite absolute / relative links that point outside this site +// - Inline the Candid interface from internet_identity.did in place of the +// component (done after link rewriting so .did comments are untouched) // // Usage: node scripts/sync-ii-spec.mjs // or: npm run sync:ii-spec @@ -38,15 +38,9 @@ const didContent = readFileSync(SOURCE_DID, 'utf8'); content = content.replace(/^import .*\n/gm, ''); // 2. Strip the H1 (Starlight renders it from frontmatter) -content = content.replace(/^# The Internet Identity Specification\n\n/, ''); - -// 3. Inline the Candid interface -content = content.replace( - '{IICandidInterface}', - '```candid\n' + didContent.trimEnd() + '\n```' -); +content = content.replace(/^# The Internet Identity Specification\n\n/m, ''); -// 4. Rewrite links — more specific patterns first to avoid partial matches +// 3. Rewrite links before Candid inlining (keeps .did comments untouched) const linkMap = [ [ 'https://internetcomputer.org/docs/current/references/ic-interface-spec#id-classes', @@ -86,6 +80,12 @@ for (const [old, replacement] of linkMap) { content = content.replaceAll(old, replacement); } +// 4. Inline the Candid interface (after link rewriting to avoid touching .did comments) +content = content.replace( + '{IICandidInterface}', + '```candid\n' + didContent.trimEnd() + '\n```' +); + // 5. Strip leading blank lines, then inject frontmatter content = content.replace(/^\n+/, ''); content = @@ -93,7 +93,7 @@ content = `title: "Internet Identity specification"\n` + `description: "Technical specification of the Internet Identity service: authentication protocol, backend interface, and implementation notes."\n` + `sidebar:\n` + - ` order: 10\n` + + ` order: 14\n` + `---\n\n` + content; @@ -120,8 +120,10 @@ content = writeFileSync(TARGET, content); console.log(`Written: ${TARGET}`); -// Warn about any remaining absolute docs links that weren't rewritten -const remaining = [...content.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] +// Warn about any remaining absolute docs links that weren't rewritten. +// Strip code blocks first — URLs inside .did comments are intentionally preserved. +const prose = content.replace(/```[\s\S]*?```/g, ''); +const remaining = [...prose.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] .map(m => m[0]); const unique = [...new Set(remaining)]; if (unique.length) { From 00e2099d30e701ba905e944bc823ece3e7286d88 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 18:28:57 +0200 Subject: [PATCH 03/14] infra: add sync-ii-spec workflow (manual trigger, relevant files only) --- .github/workflows/sync-ii-spec.yml | 148 +++++++++++++++++++++++++++++ AGENTS.md | 2 +- 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/sync-ii-spec.yml diff --git a/.github/workflows/sync-ii-spec.yml b/.github/workflows/sync-ii-spec.yml new file mode 100644 index 00000000..7696c49f --- /dev/null +++ b/.github/workflows/sync-ii-spec.yml @@ -0,0 +1,148 @@ +name: Sync II spec + +on: + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 + + - name: Create GitHub App Token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + id: app-token + with: + client-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_CLIENT_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} + + - name: Initialize internetidentity submodule at current pin + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --depth 1 .sources/internetidentity + + - name: Get current pinned hash + id: current + run: | + HASH=$(git -C .sources/internetidentity rev-parse HEAD) + echo "hash=$HASH" >> $GITHUB_OUTPUT + echo "short=$(git -C .sources/internetidentity rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Fetch latest main + run: git -C .sources/internetidentity fetch --depth 100 origin main + + - name: Get latest main hash + id: latest + run: | + HASH=$(git -C .sources/internetidentity rev-parse FETCH_HEAD) + echo "hash=$HASH" >> $GITHUB_OUTPUT + echo "short=$(git -C .sources/internetidentity rev-parse --short FETCH_HEAD)" >> $GITHUB_OUTPUT + + - name: Check if relevant files changed + id: check + run: | + CURRENT="${{ steps.current.outputs.hash }}" + LATEST="${{ steps.latest.outputs.hash }}" + BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" + + if [ "$CURRENT" = "$LATEST" ]; then + echo "Already at latest main. No update needed." + echo "needed=false" >> $GITHUB_OUTPUT + exit 0 + fi + + if git ls-remote --exit-code origin "refs/heads/${BRANCH}" > /dev/null 2>&1; then + echo "Branch $BRANCH already exists — PR likely open, skipping." + echo "needed=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Only proceed if the spec files themselves changed + CHANGED=$(git -C .sources/internetidentity diff --name-only "${CURRENT}..${LATEST}" -- \ + docs/ii-spec.mdx \ + src/internet_identity/internet_identity.did) + + if [ -z "$CHANGED" ]; then + echo "No changes to ii-spec.mdx or internet_identity.did. Skipping." + echo "needed=false" >> $GITHUB_OUTPUT + else + echo "Spec files changed:" + echo "$CHANGED" + echo "needed=true" >> $GITHUB_OUTPUT + echo "changed_files<> $GITHUB_OUTPUT + echo "$CHANGED" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Bump submodule to latest main + if: steps.check.outputs.needed == 'true' + run: git -C .sources/internetidentity checkout FETCH_HEAD + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + if: steps.check.outputs.needed == 'true' + with: + node-version: 22 + cache: npm + + - name: Install dependencies + if: steps.check.outputs.needed == 'true' + run: npm ci + + - name: Initialize examples submodule (required for build) + if: steps.check.outputs.needed == 'true' + run: git submodule update --init --depth 1 .sources/examples + + - name: Run II spec sync + if: steps.check.outputs.needed == 'true' + run: npm run sync:ii-spec + + - name: Build check + if: steps.check.outputs.needed == 'true' + run: npm run build + + - name: Create PR + if: steps.check.outputs.needed == 'true' + run: | + git config user.name "pr-automation-bot-public[bot]" + git config user.email "pr-automation-bot-public[bot]@users.noreply.github.com" + + BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" + git checkout -b "$BRANCH" + git add .sources/internetidentity docs/references/internet-identity-spec.md + git commit -m "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" + git push -u origin "$BRANCH" + + CHANGED="${{ steps.check.outputs.changed_files }}" + { + echo "## Summary" + echo "" + echo "Automated sync of the Internet Identity specification from \`dfinity/internet-identity\` main." + echo "" + echo "**Pin:** \`${{ steps.current.outputs.short }}\` → \`${{ steps.latest.outputs.short }}\`" + echo "" + echo "**Changed upstream files:**" + while IFS= read -r f; do + [ -n "$f" ] && echo "- \`$f\`" + done <<< "$CHANGED" + echo "" + echo "- Ran \`npm run sync:ii-spec\` — regenerated \`docs/references/internet-identity-spec.md\`" + echo "- Build passed ✓" + echo "" + echo "## Checklist" + echo "" + echo "- [ ] Review the diff to \`docs/references/internet-identity-spec.md\` for content changes" + echo "- [ ] Check for new absolute \`internetcomputer.org\` link patterns (script exits non-zero if any were missed)" + echo "- [ ] Verify any renamed or restructured sections are reflected correctly" + echo "" + echo "## Sync recommendation" + echo "" + echo "\`sync from dfinity/internet-identity — docs/ii-spec.mdx, src/internet_identity/internet_identity.did\`" + } > /tmp/pr-body.md + + gh pr create \ + --title "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" \ + --body-file /tmp/pr-body.md + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/AGENTS.md b/AGENTS.md index 8a771935..b06155eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -292,7 +292,7 @@ EOF | `candid` | Check for spec changes affecting the Candid reference or type-mapping examples | | `response-verification` | Check for API changes affecting certified variables patterns | | `dotskills` | Check if the `technical-documentation` skill changed in ways that affect review criteria | -| `internetidentity` | Run `npm run sync:ii-spec` — the script handles all link rewrites, Candid inlining, and frontmatter. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` in `scripts/sync-ii-spec.mjs` | +| `internetidentity` | Run `npm run sync:ii-spec` — the script handles all link rewrites, Candid inlining, and frontmatter. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` in `scripts/sync-ii-spec.mjs`. The **Sync II spec** workflow (`.github/workflows/sync-ii-spec.yml`) can be triggered manually from GitHub Actions; it only opens a PR when `docs/ii-spec.mdx` or `internet_identity.did` actually changed. | | `chain-fusion-signer` | Check for changed canister IDs, API methods, or key derivation patterns | | `papi` | Check for changed payment interface or cycle cost model | | `ic-pub-key` | Check for changed CLI flags or commands | From 653aa97ab0173dd5a60cf711ef347005ca4e8cda Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 18:35:26 +0200 Subject: [PATCH 04/14] fix(sync-ii-spec): use explicit hash instead of FETCH_HEAD in bump step FETCH_HEAD is a file that could theoretically be overwritten between steps if another fetch runs. Using the hash saved to step outputs is safer and more explicit. --- .github/workflows/sync-ii-spec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-ii-spec.yml b/.github/workflows/sync-ii-spec.yml index 7696c49f..ed6a5d3f 100644 --- a/.github/workflows/sync-ii-spec.yml +++ b/.github/workflows/sync-ii-spec.yml @@ -78,7 +78,7 @@ jobs: - name: Bump submodule to latest main if: steps.check.outputs.needed == 'true' - run: git -C .sources/internetidentity checkout FETCH_HEAD + run: git -C .sources/internetidentity checkout ${{ steps.latest.outputs.hash }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 if: steps.check.outputs.needed == 'true' From 266b6aaa3c71394f3a67c39fa1c32ab5cf30b353 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 18:38:42 +0200 Subject: [PATCH 05/14] fix(validate): skip code blocks in checkForbiddenPatterns checkEmdash already tracks inFence state to skip code blocks. checkForbiddenPatterns was missing the same logic, causing false positives on URLs inside Candid/code block comments. --- scripts/validate.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/validate.js b/scripts/validate.js index c0362f43..3a0c3fbd 100644 --- a/scripts/validate.js +++ b/scripts/validate.js @@ -72,8 +72,11 @@ function checkForbiddenPatterns(file, content) { if (isSynced(file)) return []; const errors = []; const lines = content.split('\n'); + let inFence = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; + if (/^```/.test(line.trimStart())) { inFence = !inFence; continue; } + if (inFence) continue; for (const { re, msg } of FORBIDDEN) { if (re.test(line)) errors.push(`line ${i + 1}: ${msg}`); } From 6d7f90b28cacfa0d093e0941fa2079c30e5fdedc Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 12 May 2026 18:47:44 +0200 Subject: [PATCH 06/14] refactor(sync-ii-spec): store .did as public asset, link instead of inline Consistent with ic.did / http-gateway.did: copy internet_identity.did to public/references/internet-identity.did and replace the inline with a prose download link. Reduces internet-identity-spec.md from 2075 to 736 lines. Workflow updated to stage the .did file. --- .github/workflows/sync-ii-spec.yml | 2 +- docs/references/internet-identity-spec.md | 1341 +-------------------- public/references/internet-identity.did | 1336 ++++++++++++++++++++ scripts/sync-ii-spec.mjs | 30 +- 4 files changed, 1356 insertions(+), 1353 deletions(-) create mode 100644 public/references/internet-identity.did diff --git a/.github/workflows/sync-ii-spec.yml b/.github/workflows/sync-ii-spec.yml index ed6a5d3f..76cc828b 100644 --- a/.github/workflows/sync-ii-spec.yml +++ b/.github/workflows/sync-ii-spec.yml @@ -110,7 +110,7 @@ jobs: BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" git checkout -b "$BRANCH" - git add .sources/internetidentity docs/references/internet-identity-spec.md + git add .sources/internetidentity docs/references/internet-identity-spec.md public/references/internet-identity.did git commit -m "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" git push -u origin "$BRANCH" diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index 7b276e69..929c24d8 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -357,1344 +357,7 @@ Note that the interface is split into 4 categories: The summary is given by the Candid interface: -```candid -type UserNumber = nat64; -type AccountNumber = nat64; -type PublicKey = blob; -type CredentialId = blob; -type Aaguid = blob; -type DeviceKey = PublicKey; -type UserKey = PublicKey; -type SessionKey = PublicKey; -type FrontendHostname = text; -type Timestamp = nat64; - -type HeaderField = record { - text; - text; -}; - -type HttpRequest = record { - method : text; - url : text; - headers : vec HeaderField; - body : blob; - certificate_version : opt nat16; -}; - -type HttpResponse = record { - status_code : nat16; - headers : vec HeaderField; - body : blob; - upgrade : opt bool; -}; - -type StreamingCallbackHttpResponse = record { - body : blob; - token : opt Token; -}; - -type Token = record {}; - -type StreamingStrategy = variant { - Callback : record { - callback : func(Token) -> (StreamingCallbackHttpResponse) query; - token : Token; - }; -}; - -type Purpose = variant { - recovery; - authentication; -}; - -type KeyType = variant { - unknown; - platform; - cross_platform; - seed_phrase; - browser_storage_key; -}; - -// This describes whether a device is "protected" or not. -// When protected, a device can only be updated or removed if the -// user is authenticated with that very device. -type DeviceProtection = variant { - protected; - unprotected; -}; - -type Challenge = record { - png_base64 : text; - challenge_key : ChallengeKey; -}; - -type DeviceData = record { - pubkey : DeviceKey; - alias : text; - credential_id : opt CredentialId; - aaguid : opt Aaguid; - purpose : Purpose; - key_type : KeyType; - protection : DeviceProtection; - origin : opt text; - // Metadata map for additional device information. - // - // Note: some fields above will be moved to the metadata map in the future. - // All field names of `DeviceData` (such as 'alias', 'origin, etc.) are - // reserved and cannot be written. - // In addition, the keys "usage" and "authenticator_attachment" are reserved as well. - metadata : opt MetadataMap; -}; - -// The same as `DeviceData` but with the `last_usage` field. -// This field cannot be written, hence the separate type. -type DeviceWithUsage = record { - pubkey : DeviceKey; - alias : text; - credential_id : opt CredentialId; - aaguid : opt Aaguid; - purpose : Purpose; - key_type : KeyType; - protection : DeviceProtection; - origin : opt text; - last_usage : opt Timestamp; - metadata : opt MetadataMap; -}; - -type DeviceKeyWithAnchor = record { - pubkey : DeviceKey; - anchor_number : UserNumber; -}; - -// Map with some variants for the value type. -// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. -type MetadataMap = vec record { - text; - variant { map : MetadataMap; string : text; bytes : vec nat8 }; -}; - -type RegisterResponse = variant { - // A new user was successfully registered. - registered : record { - user_number : UserNumber; - }; - // No more registrations are possible in this instance of the II service canister. - canister_full; - // The challenge was not successful. - bad_challenge; -}; - -type AddTentativeDeviceResponse = variant { - // The device was tentatively added. - added_tentatively : record { - verification_code : text; - // Expiration date, in nanos since the epoch - device_registration_timeout : Timestamp; - }; - // Device registration mode is off, either due to timeout or because it was never enabled. - device_registration_mode_off; - // There is another device already added tentatively - another_device_tentatively_added; - // Passkey with this public key is already used - passkey_with_this_public_key_is_already_used; -}; - -type VerifyTentativeDeviceResponse = variant { - // The device was successfully verified. - verified; - // Wrong verification code entered. Retry with correct code. - wrong_code : record { - retries_left : nat8; - }; - // Device registration mode is off, either due to timeout or because it was never enabled. - device_registration_mode_off; - // There is no tentative device to be verified. - no_device_to_verify; -}; - -type Delegation = record { - pubkey : PublicKey; - expiration : Timestamp; - targets : opt vec principal; -}; - -type SignedDelegation = record { - delegation : Delegation; - signature : blob; -}; - -type GetDelegationResponse = variant { - // The signed delegation was successfully retrieved. - signed_delegation : SignedDelegation; - - // The signature is not ready. Maybe retry by calling `prepare_delegation` - no_such_delegation; -}; - -type InternetIdentityStats = record { - users_registered : nat64; - storage_layout_version : nat8; - assigned_user_number_range : record { - nat64; - nat64; - }; - archive_info : ArchiveInfo; - canister_creation_cycles_cost : nat64; - // Map from event aggregation to a sorted list of top 100 sub-keys to their weights. - // Example: {"prepare_delegation_count 24h ic0.app": [{"https://dapp.com", 100}, {"https://dapp2.com", 50}]} - event_aggregations : vec record { text; vec record { text; nat64 } }; -}; - -// Configuration parameters related to the archive. -type ArchiveConfig = record { - // The allowed module hash of the archive canister. - // Changing this parameter does _not_ deploy the archive, but enable archive deployments with the - // corresponding wasm module. - module_hash : blob; - // Buffered archive entries limit. If reached, II will stop accepting new anchor operations - // until the buffered operations are acknowledged by the archive. - entries_buffer_limit : nat64; - // The maximum number of entries to be transferred to the archive per call. - entries_fetch_limit : nat16; - // Polling interval to fetch new entries from II (in nanoseconds). - // Changes to this parameter will only take effect after an archive deployment. - polling_interval_ns : nat64; -}; - -// Information about the archive. -type ArchiveInfo = record { - // Canister id of the archive or empty if no archive has been deployed yet. - archive_canister : opt principal; - // Configuration parameters related to the II archive. - archive_config : opt ArchiveConfig; -}; - -// Rate limit configuration. -// Currently only used for `register`. -type RateLimitConfig = record { - // Time it takes (in ns) for a rate limiting token to be replenished. - time_per_token_ns : nat64; - // How many tokens are at most generated (to accommodate peaks). - max_tokens : nat64; -}; - -// Captcha configuration -// Default: -// - max_unsolved_captchas: 500 -// - captcha_trigger: Static, CaptchaEnabled -type CaptchaConfig = record { - // Maximum number of unsolved captchas. - max_unsolved_captchas : nat64; - // Configuration for when captcha protection should kick in. - captcha_trigger : variant { - // Based on the rate of registrations compared to some reference time frame and allowing some leeway. - Dynamic : record { - // Percentage of increased registration rate observed in the current rate sampling interval (compared to - // reference rate) at which II will enable captcha for new registrations. - threshold_pct : nat16; - // Length of the interval in seconds used to sample the current rate of registrations. - current_rate_sampling_interval_s : nat64; - // Length of the interval in seconds used to sample the reference rate of registrations. - reference_rate_sampling_interval_s : nat64; - }; - // Statically enable / disable captcha - Static : variant { - CaptchaEnabled; - CaptchaDisabled; - }; - }; -}; - -// Init arguments of II which can be supplied on install and upgrade. -// -// Each field is wrapped is `opt` to indicate whether the field should -// keep the previous value or update to a new value (e.g. `null` keeps the previous value). -// -// Some fields, like `analytics_config`, have an additional nested `opt`, this indicates -// enable/disable status (e.g. `opt null` disables a feature while `null` leaves it untouched). -type InternetIdentityInit = record { - // Set lowest and highest anchor - assigned_user_number_range : opt record { - nat64; - nat64; - }; - // Configuration parameters related to the II archive. - // Note: some parameters changes (like the polling interval) will only take effect after an archive deployment. - // See ArchiveConfig for details. - archive_config : opt ArchiveConfig; - // Set the amounts of cycles sent with the create canister message. - // This is configurable because in the staging environment cycles are required. - // The canister creation cost on mainnet is currently 100'000'000'000 cycles. If this value is higher thant the - // canister creation cost, the newly created canister will keep extra cycles. - canister_creation_cycles_cost : opt nat64; - // Rate limit for the `register` call. - register_rate_limit : opt RateLimitConfig; - // Configuration of the captcha in the registration flow. - captcha_config : opt CaptchaConfig; - // Configuration for Related Origins Requests. - // If present, list of origins from where registration is allowed. - related_origins : opt vec text; - // Configuration for New Origin Flows. - // If present, list of origins using the new authentication flow. - new_flow_origins : opt vec text; - // Configurations for OpenID clients - openid_configs : opt vec OpenIdConfig; - // Allowlist of domains that may be registered as discoverable SSO - // providers via `add_discoverable_oidc_config`. When set, fully replaces - // the built-in defaults. When unset, falls back to `dfinity.org` - // (production) or `beta.dfinity.org` (everything else), keyed off - // `is_production`. - sso_discoverable_domains : opt vec text; - // Configuration for Web Analytics - analytics_config : opt opt AnalyticsConfig; - // Configuration to show dapps explorer or not - enable_dapps_explorer : opt bool; - // Configuration to set the canister as production mode. - // For now, this is used only to show or hide the banner. - is_production : opt bool; - // Configuration for dummy authentication used in e2e tests. - dummy_auth : opt opt DummyAuthConfig; - // Backend canister ID, needed for backward compatibility. - backend_canister_id : opt principal; - // Backend origin, needed to sync configuration with frontend. - backend_origin : opt text; - // DNSSEC verification configuration. Trust anchors used by any feature - // that verifies DNS records against the IANA-rooted DNSSEC chain - // (currently the email-recovery DKIM/DMARC flow). See - // `docs/ongoing/email-recovery.md` §7.5. - // - // Wrapped in `opt opt` to match the same set/clear pattern as - // `analytics_config` / `dummy_auth`: outer null keeps the previously - // stored value across an upgrade, `opt null` clears it, `opt opt c` - // sets it to `c`. - dnssec_config : opt opt DnssecConfig; -}; - -// DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS -// records consumes the same anchors; not specific to email recovery. -type DnssecConfig = record { - // IANA root KSK trust anchors. Multiple are accepted simultaneously so - // KSK rollover is a single config change in the next upgrade arg. - root_anchors : vec DnssecRootAnchor; -}; - -// One IANA root KSK trust anchor, in the same shape IANA publishes at -// `data.iana.org/root-anchors/root-anchors.xml`. Only `digest_type = 2` -// (SHA-256) is accepted; the legacy SHA-1 form is rejected at the -// verifier boundary. -type DnssecRootAnchor = record { - key_tag : nat16; - algorithm : nat8; - digest_type : nat8; - digest : blob; -}; - -type ChallengeKey = text; - -type ChallengeResult = record { - key : ChallengeKey; - chars : text; -}; -type CaptchaResult = ChallengeResult; - -// Extra information about registration status for new devices -type DeviceRegistrationInfo = record { - // If present, the user has registered a new authentication method. This new authentication - // method needs to be confirmed before 'expiration' in order to be added to the identity. - tentative_device : opt DeviceData; - // If present, the user has registered a new session. This new session needs to be confirmed before - // 'expiration' in order for it be authorized to register an authentication method to the identity. - tentative_session : opt principal; - // The timestamp at which the anchor will turn off registration mode - // (and the tentative device will be forgotten, if any, and if not verified) - expiration : Timestamp; -}; - -// Information about the anchor -type IdentityAnchorInfo = record { - // All devices that can authenticate to this anchor - devices : vec DeviceWithUsage; - // Device registration status used when adding devices, see DeviceRegistrationInfo - device_registration : opt DeviceRegistrationInfo; - // OpenID accounts linked to this anchor - openid_credentials : opt vec OpenIdCredential; - // The name of the Internet Identity - name : opt text; - // The timestamp at which the anchor was created - created_at : opt Timestamp; -}; - -type AnchorCredentials = record { - credentials : vec WebAuthnCredential; - recovery_credentials : vec WebAuthnCredential; - recovery_phrases : vec PublicKey; -}; - -type WebAuthnCredential = record { - credential_id : CredentialId; - pubkey : PublicKey; -}; - -type DeployArchiveResult = variant { - // The archive was deployed successfully and the supplied wasm module has been installed. The principal of the archive - // canister is returned. - success : principal; - // Initial archive creation is already in progress. - creation_in_progress; - // Archive deployment failed. An error description is returned. - failed : text; -}; - -type BufferedArchiveEntry = record { - anchor_number : UserNumber; - timestamp : Timestamp; - sequence_number : nat64; - entry : blob; -}; - -// OpenID specific types -type Iss = text; -type Sub = text; -type Aud = text; -type JWT = text; -type Salt = blob; - -type OpenIdEmailVerification = variant { - Unknown; - Google; - Microsoft; -}; - -type OpenIdConfig = record { - name : text; - logo : text; - issuer : text; - client_id : text; - jwks_uri : text; - auth_uri : text; - auth_scope : vec text; - fedcm_uri : opt text; - email_verification : opt OpenIdEmailVerification; -}; - -// SSO provider config that uses two-hop discovery. -// The backend fetches https://{discovery_domain}/.well-known/ii-openid-configuration -// for { client_id, openid_configuration } and then fetches the standard OIDC -// discovery at openid_configuration for { issuer, jwks_uri }. -type DiscoverableOidcConfig = record { - discovery_domain : text; -}; - -// Resolved SSO provider state. -// All fields other than discovery_domain are None until discovery completes. -type OidcConfig = record { - discovery_domain : text; - client_id : opt text; - openid_configuration : opt text; - issuer : opt text; -}; - -type OpenIdCredentialKey = record { Iss; Sub; Aud }; - -type AnalyticsConfig = variant { - Plausible : record { - domain : opt text; - hash_mode : opt bool; - track_localhost : opt bool; - api_host : opt text; - }; -}; - -type OpenIdCredential = record { - iss : Iss; - sub : Sub; - aud : Aud; - last_usage_timestamp : opt Timestamp; - metadata : MetadataMapV2; - // SSO discovery domain, looked up by `(iss, aud)` against the - // canister's registered discoverable OIDC configs. `None` for - // direct-provider credentials (Google / Apple / Microsoft) and for - // SSO credentials whose provider is no longer registered. - sso_domain : opt text; - // Human-readable SSO name from the domain's - // `/.well-known/ii-openid-configuration`. `None` when the domain - // doesn't publish one — callers should fall back to `sso_domain` - // for the label. - sso_name : opt text; -}; - -type OpenIdCredentialAddError = variant { - Unauthorized : principal; - JwtVerificationFailed; - OpenIdCredentialAlreadyRegistered; - InternalCanisterError : text; - JwtExpired; -}; - -type OpenIdCredentialRemoveError = variant { - Unauthorized : principal; - OpenIdCredentialNotFound; - InternalCanisterError : text; -}; - -type OpenIdDelegationError = variant { - NoSuchAnchor; - JwtVerificationFailed; - NoSuchDelegation; - JwtExpired; -}; - -type OpenIdPrepareDelegationResponse = record { - user_key : UserKey; - expiration : Timestamp; - anchor_number : UserNumber; -}; - -// API V2 specific types -// WARNING: These type are experimental and may change in the future. -type IdentityNumber = nat64; - -// Map with some variants for the value type. -// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. -type MetadataMapV2 = vec record { - text; - variant { Map : MetadataMapV2; String : text; Bytes : vec nat8 }; -}; - -// Authentication method using WebAuthn signatures -// See https://www.w3.org/TR/webauthn-2/ -// This is a separate type because WebAuthn requires to also store -// the credential id (in addition to the public key). -type WebAuthn = record { - credential_id : CredentialId; - pubkey : PublicKey; - - // Authenticator Attestation Global Unique Identifier (AAGUID) - aaguid : opt Aaguid; -}; - -// Authentication method using generic signatures -// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#signatures for -// supported signature schemes. -type PublicKeyAuthn = record { - pubkey : PublicKey; -}; - -// The authentication methods currently supported by II. -type AuthnMethod = variant { - WebAuthn : WebAuthn; - PubKey : PublicKeyAuthn; -}; - -// This describes whether an authentication method is "protected" or not. -// When protected, a authentication method can only be updated or removed if the -// user is authenticated with that very authentication method. -type AuthnMethodProtection = variant { - Protected; - Unprotected; -}; - -type AuthnMethodPurpose = variant { - Recovery; - Authentication; -}; - -type AuthnMethodSecuritySettings = record { - protection : AuthnMethodProtection; - purpose : AuthnMethodPurpose; -}; - -type AuthnMethodData = record { - authn_method : AuthnMethod; - security_settings : AuthnMethodSecuritySettings; - // contains the following fields of the DeviceWithUsage type: - // - alias - // - origin - // - authenticator_attachment: data taken from key_type and reduced to "platform", "cross_platform" or absent on migration - // - usage: data taken from key_type and reduced to "recovery_phrase", "browser_storage_key" or absent on migration - // Note: for compatibility reasons with the v1 API, the entries above (if present) - // must be of the `String` variant. This restriction may be lifted in the future. - metadata : MetadataMapV2; - last_authentication : opt Timestamp; -}; - -type OpenIDRegFinishArg = record { - jwt : JWT; - salt : Salt; - name : text; -}; - -// Extra information about registration status for new authentication methods -type AuthnMethodRegistrationInfo = record { - - // If present, the user has registered a new authentication method. This new authentication - // method needs to be confirmed before 'expiration' in order to be added to the identity. - authn_method : opt AuthnMethodData; - // If present, the user has registered a new session. This new session needs to be confirmed before - // 'expiration' in order for it be authorized to register an authentication method to the identity. - session : opt principal; - // The timestamp at which the identity will turn off registration mode - // (and the authentication method will be forgotten, if any, and if not verified) - expiration : Timestamp; -}; - -type AuthnMethodConfirmationCode = record { - confirmation_code : text; - expiration : Timestamp; -}; - -type AuthnMethodSessionInfo = record { - name : opt text; - created_at : opt Timestamp; -}; - -type RegistrationId = text; - -type AuthnMethodRegisterError = variant { - // Authentication method registration mode is off, either due to timeout or because it was never enabled. - RegistrationModeOff; - // There is another authentication method already registered that needs to be confirmed first. - RegistrationAlreadyInProgress; - // The metadata of the provided authentication method contains invalid entries. - InvalidMetadata : text; - // The caller's principal is not self-authenticating. - NotSelfAuthenticating : principal; - // Passkey with this public key is already used - PasskeyWithThisPublicKeyIsAlreadyUsed; -}; - -type AuthnMethodConfirmationError = variant { - Unauthorized : principal; - InternalCanisterError : text; - // Wrong confirmation code entered. Retry with correct code. - WrongCode : record { - retries_left : nat8; - }; - // Authentication method registration mode is off, either due to timeout or because it was never enabled. - RegistrationModeOff; - // There is no registered authentication method to be confirmed. - NoAuthnMethodToConfirm; -}; - -type AuthnMethodRegistrationModeEnterError = variant { - InvalidRegistrationId : text; - Unauthorized : principal; - AlreadyInProgress; - InternalCanisterError : text; -}; - -type AuthnMethodRegistrationModeExitError = variant { - Unauthorized : principal; - InternalCanisterError : text; - RegistrationModeOff; - InvalidMetadata : text; - PasskeyWithThisPublicKeyIsAlreadyUsed; -}; - -type LookupByRegistrationIdError = variant { - InvalidRegistrationId : text; -}; - -type IdentityAuthnInfo = record { - authn_methods : vec AuthnMethod; - recovery_authn_methods : vec AuthnMethod; -}; - -type IdentityInfo = record { - authn_methods : vec AuthnMethodData; - authn_method_registration : opt AuthnMethodRegistrationInfo; - openid_credentials : opt vec OpenIdCredential; - // Authentication method independent metadata - metadata : MetadataMapV2; - name : opt text; - // The timestamp at which the anchor was created - created_at : opt Timestamp; -}; - -type IdentityInfoError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -type AuthnMethodAddError = variant { - InvalidMetadata : text; -}; - -type AuthnMethodReplaceError = variant { - InvalidMetadata : text; - // No authentication method found with the given public key. - AuthnMethodNotFound; - // Passkey with this public key is already used - PasskeyWithThisPublicKeyIsAlreadyUsed; -}; - -type AuthnMethodMetadataReplaceError = variant { - InvalidMetadata : text; - // No authentication method found with the given public key. - AuthnMethodNotFound; -}; - -type AuthnMethodSecuritySettingsReplaceError = variant { - // No authentication method found with the given public key. - AuthnMethodNotFound; -}; - -type IdentityMetadataReplaceError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // The identity including the new metadata exceeds the maximum allowed size. - StorageSpaceExceeded : record { - space_available : nat64; - space_required : nat64; - }; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -type CreateAccountError = variant { - InternalCanisterError : text; - AccountLimitReached; - Unauthorized : principal; - NameTooLong; -}; - -type UpdateAccountError = variant { - InternalCanisterError : text; - AccountLimitReached; - Unauthorized : principal; - NameTooLong; -}; - -type AccountDelegationError = variant { - InternalCanisterError : text; - Unauthorized : principal; - NoSuchDelegation; -}; - -type GetDefaultAccountError = variant { - InternalCanisterError : text; - Unauthorized : principal; - NoSuchAnchor; - NoSuchOrigin : record { - anchor_number : UserNumber; - }; -}; - -type SetDefaultAccountError = variant { - InternalCanisterError : text; - Unauthorized : principal; - NoSuchAnchor; - NoSuchOrigin : record { - anchor_number : UserNumber; - }; - NoSuchAccount : record { - anchor_number : UserNumber; - origin : FrontendHostname; - }; -}; - -type PrepareAccountDelegation = record { - user_key : UserKey; - expiration : Timestamp; -}; - -type GetAccountsError = variant { - InternalCanisterError : text; - Unauthorized : principal; -}; - -type PrepareIdAliasRequest = record { - // Origin of the issuer in the attribute sharing flow. - issuer : FrontendHostname; - // Origin of the relying party in the attribute sharing flow. - relying_party : FrontendHostname; - // Identity for which the IdAlias should be generated. - identity_number : IdentityNumber; -}; - -type PrepareIdAliasError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -// The prepared id alias contains two (still unsigned) credentials in JWT format, -// certifying the id alias for the issuer resp. the relying party. -type PreparedIdAlias = record { - rp_id_alias_jwt : text; - issuer_id_alias_jwt : text; - canister_sig_pk_der : PublicKey; -}; - -// The request to retrieve the actual signed id alias credentials. -// The field values should be equal to the values of corresponding -// fields from the preceding `PrepareIdAliasRequest` and `PrepareIdAliasResponse`. -type GetIdAliasRequest = record { - rp_id_alias_jwt : text; - issuer : FrontendHostname; - issuer_id_alias_jwt : text; - relying_party : FrontendHostname; - identity_number : IdentityNumber; -}; - -type GetIdAliasError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // The credential(s) are not available: may be expired or not prepared yet (call prepare_id_alias to prepare). - NoSuchCredentials : text; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -// The signed id alias credentials for each involved party. -type IdAliasCredentials = record { - rp_id_alias_credential : SignedIdAlias; - issuer_id_alias_credential : SignedIdAlias; -}; - -type SignedIdAlias = record { - credential_jws : text; - id_alias : principal; - id_dapp : principal; -}; - -type IdRegNextStepResult = record { - // The next step in the registration flow - next_step : RegistrationFlowNextStep; -}; - -type IdRegStartError = variant { - // The method was called anonymously, which is not supported. - InvalidCaller; - // Too many registrations. Please try again later. - RateLimitExceeded; - // A registration flow is already in progress. - AlreadyInProgress; -}; - -// The next step in the registration flow: -// - CheckCaptcha: supply the solution to the captcha using `check_captcha` -// - Finish: finish the registration using `identity_registration_finish` -type RegistrationFlowNextStep = variant { - // Supply the captcha solution using check_captcha - CheckCaptcha : record { - captcha_png_base64 : text; - }; - // Finish the registration using identity_registration_finish - Finish; -}; - -type CheckCaptchaArg = record { - solution : text; -}; - -type CheckCaptchaError = variant { - // The supplied solution was wrong. Try again with the new captcha. - WrongSolution : record { - new_captcha_png_base64 : text; - }; - // This call is unexpected, see next_step. - UnexpectedCall : record { - next_step : RegistrationFlowNextStep; - }; - // No registration flow ongoing for the caller. - NoRegistrationFlow; -}; - -type IdRegFinishArg = record { - authn_method : AuthnMethodData; - name : opt text; -}; - -type IdRegFinishResult = record { - identity_number : nat64; -}; - -type IdRegFinishError = variant { - // This call is unexpected, see next_step. - UnexpectedCall : record { - next_step : RegistrationFlowNextStep; - }; - // No registration flow ongoing for the caller. - NoRegistrationFlow; - // The supplied authn_method is not valid. - InvalidAuthnMethod : text; - // Error while persisting the new identity. - StorageError : text; -}; - -type AccountInfo = record { - // Null is unreserved default account - account_number : opt AccountNumber; - origin : text; - last_used : opt Timestamp; - // Configurable properties - name : opt text; -}; - -type AccountUpdate = record { - name : opt text; -}; - -type DummyAuthConfig = record { - // Prompts user for a index value (0 - 255) when set to true, - // this is used in e2e to have multiple dummy auth identities. - prompt_for_index : bool; -}; - -type IdentityPropertiesReplace = record { - name : opt text; -}; - -type IdentityPropertiesReplaceError = variant { - Unauthorized : principal; - StorageSpaceExceeded : record { - space_available : nat64; - space_required : nat64; - }; - NameTooLong : record { - limit : nat64; - }; - InternalCanisterError : text; -}; - -type PrepareAttributeRequest = record { - // Identity for which the attributes should be prepared. - identity_number : IdentityNumber; - - // Origin of the relying party in the attribute sharing flow. - origin : FrontendHostname; - - // II account for the relying party. - account_number : opt AccountNumber; - - // The attributes to be prepared. Each key has the form - // `:`, where `` is either - // `openid:` (e.g. `openid:https://accounts.google.com:email`) - // or `sso:` (e.g. `sso:dfinity.org:email`). - // - // Each linked credential is addressable via exactly one scope: - // credentials obtained through a `DiscoverableOidcConfig` (two-hop SSO - // discovery) are reachable only via `sso:`; credentials from - // hardcoded OIDC providers (Google, Microsoft, …) are reachable only via - // `openid:`. Under `sso:` only `email` and `name` are supported; - // under `openid:` `email`, `name`, and `verified_email` are supported. - attribute_keys : vec text; -}; - -type GetAccountError = variant { - NoSuchOrigin : record { - anchor_number : UserNumber; - }; - NoSuchAccount : record { - anchor_number : UserNumber; - origin : FrontendHostname; - }; -}; - -type PrepareAttributeError = variant { - ValidationError : record { - problems : vec text; - }; - AuthorizationError : principal; - GetAccountError : GetAccountError; -}; - -type PrepareAttributeResponse = record { - issued_at_timestamp_ns : Timestamp; - attributes : vec record { text; blob }; -}; - -type GetAttributesRequest = record { - // Identity for which the attributes should be prepared. - identity_number : IdentityNumber; - - // Origin of the relying party in the attribute sharing flow. - origin : FrontendHostname; - - // II account for the relying party. - account_number : opt AccountNumber; - - // Timestamp received from the prepare_attributes call. - issued_at_timestamp_ns : Timestamp; - - // The attribute to be retrieved, must be a subset of certified_attributes from - // the prepare_attributes response. - attributes : vec record { text; blob }; -}; - -type CertifiedAttribute = record { - key : text; - value : blob; - signature : blob; -}; - -type CertifiedAttributes = record { - expires_at_timestamp_ns : Timestamp; - certified_attributes : vec CertifiedAttribute; -}; - -type GetAttributesError = variant { - ValidationError : record { - problems : vec text; - }; - AuthorizationError : principal; - GetAccountError : GetAccountError; -}; - -// ICRC-3 attribute sharing types -// ============================== - -type Icrc3Value = variant { - Nat : nat; - Int : int; - Blob : blob; - Text : text; - Array : vec Icrc3Value; - Map : vec record { text; Icrc3Value }; -}; - -// A specification of an attribute to be certified. -type AttributeSpec = record { - // `:` where `` is - // either `openid:` or `sso:`. - // - // Examples: - // - `openid:https://accounts.google.com:email` - // - `sso:dfinity.org:email` - key : text; - - // If `value` is set, this attribute should be certified only if it matches - // the current value. This enables the II frontend to request a certificate - // for a set of attributes that is guaranteed to be approved by the user. - value : opt blob; - - // Whether the attribute scope should be omitted before certification. - // Certifying unscoped attributes is useful, e.g., when the user wants - // to share their preferred email without revealing which OpenID - // provider they use with II. - // - // Examples (e.g., `key = "openid:https://accounts.google.com:email"`): - // 1. If `omit_scope = true`, then the certified attribute key is `email`. - // 2. Otherwise, the certified attribute key is literally the value of `key`. - omit_scope : bool; -}; - -type PrepareIcrc3AttributeRequest = record { - // Identity for which the attributes should be prepared. - identity_number : IdentityNumber; - - // Origin of the relying party in the attribute sharing flow. May have - // been remapped from `.icp0.io` to `.ic0.app` for principal - // stability — see `unmapped_origin`. - origin : FrontendHostname; - - // The relying party's actual origin, before the legacy `icp0.io → - // ic0.app` remap that `origin` may have gone through. When set, this - // value is what gets certified as `implicit:origin` instead of `origin`, - // so that an RP signed in via the icp0.io domain sees its real origin - // in the certified message. The canister verifies that mapping - // `unmapped_origin` through the same legacy remap yields `origin`, - // so an RP can't certify an arbitrary value here. - unmapped_origin : opt FrontendHostname; - - // II account for the relying party. - account_number : opt AccountNumber; - - // The attributes to be certified. - attributes : vec AttributeSpec; - - // The nonce is a 32-byte value generated by the relying party. - // The purpose of the nonce is to prevent replay attacks and - // enable the relying party to expire attributes issued for a given flow. - // The value of the nonce will be included into the signed ICRC-3 - // message as a separate attribute with the key `implicit:nonce`. - nonce : blob; -}; - -type PrepareIcrc3AttributeResponse = record { - // Candid-encoded ICRC-3 Value map. Pass this to get_icrc3_attributes. - message : blob; -}; - -type PrepareIcrc3AttributeError = variant { - ValidationError : record { - problems : vec text; - }; - AuthorizationError : principal; - GetAccountError : GetAccountError; - AttributeMismatch : record { - problems : vec text; - }; -}; - -type GetIcrc3AttributeRequest = record { - // Identity for which the attributes were prepared. - identity_number : IdentityNumber; - - // Origin of the relying party in the attribute sharing flow. - origin : FrontendHostname; - - // II account for the relying party. - account_number : opt AccountNumber; - - // The message blob from PrepareIcrc3AttributeResponse. - message : blob; -}; - -type GetIcrc3AttributeResponse = record { - signature : blob; -}; - -type GetIcrc3AttributeError = variant { - ValidationError : record { - problems : vec text; - }; - AuthorizationError : principal; - GetAccountError : GetAccountError; - NoSuchSignature; -}; - -// List available attributes -type ListAvailableAttributesRequest = record { - // Identity for which available attributes should be listed. - identity_number : IdentityNumber; - - // Optional list of attribute keys to filter by. - // Each key is either a fully-scoped key (e.g., - // `"openid:https://accounts.google.com:email"` or - // `"sso:dfinity.org:email"`) or an unscoped attribute name (e.g., - // `"email"`) which matches all scopes. - // If not provided, all available attributes are returned. - attributes : opt vec text; -}; - -type ListAvailableAttributesResponse = vec record { text; blob }; - -type ListAvailableAttributesError = variant { - ValidationError : record { - problems : vec text; - }; - AuthorizationError : principal; -}; - -service : (opt InternetIdentityInit) -> { - // Legacy identity management API - // ============================== - create_challenge : () -> (Challenge); - register : (DeviceData, ChallengeResult, opt principal) -> (RegisterResponse); - add : (UserNumber, DeviceData) -> (); - update : (UserNumber, DeviceKey, DeviceData) -> (); - // Atomically replace device matching the device key with the new device data - replace : (UserNumber, DeviceKey, DeviceData) -> (); - remove : (UserNumber, DeviceKey) -> (); - // Returns all devices of the user (authentication and recovery) but no information about device registrations. - // Note: Clears out the 'alias' fields on the devices. Use 'get_anchor_info' to obtain the full information. - // Deprecated: Use 'get_anchor_credentials' instead. - lookup : (UserNumber) -> (vec DeviceData) query; - get_anchor_credentials : (UserNumber) -> (AnchorCredentials) query; - get_anchor_info : (UserNumber) -> (IdentityAnchorInfo); - get_principal : (UserNumber, FrontendHostname) -> (principal) query; - - enter_device_registration_mode : (UserNumber) -> (Timestamp); - exit_device_registration_mode : (UserNumber) -> (); - add_tentative_device : (UserNumber, DeviceData) -> (AddTentativeDeviceResponse); - verify_tentative_device : (UserNumber, verification_code : text) -> (VerifyTentativeDeviceResponse); - - // V2 Identity Management API - // ========================== - // WARNING: The following methods are experimental and may ch 0ange in the future. - // Starts the identity registration flow to create a new identity. - identity_registration_start : () -> (variant { Ok : IdRegNextStepResult; Err : IdRegStartError }); - - // Check the captcha challenge - // If successful, the registration can be finished with `identity_registration_finish`. - check_captcha : (CheckCaptchaArg) -> (variant { Ok : IdRegNextStepResult; Err : CheckCaptchaError }); - - // Starts the identity registration flow to create a new identity. - identity_registration_finish : (IdRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); - - // Returns information about the authentication methods of the identity with the given number. - // Only returns the minimal information required for authentication without exposing any metadata such as aliases. - identity_authn_info : (IdentityNumber) -> (variant { Ok : IdentityAuthnInfo; Err }) query; - - // Returns information about the identity with the given number. - // Requires authentication. - identity_info : (IdentityNumber) -> (variant { Ok : IdentityInfo; Err : IdentityInfoError }); - - // Replaces the authentication method independent metadata map. - // The existing metadata map will be overwritten. - // Requires authentication. - identity_metadata_replace : (IdentityNumber, MetadataMapV2) -> (variant { Ok; Err : IdentityMetadataReplaceError }); - - // Replaces the identity properties. - // The existing properties will be overwritten. - // Requires authentication. - identity_properties_replace : (IdentityNumber, IdentityPropertiesReplace) -> (variant { Ok; Err : IdentityPropertiesReplaceError }); - - // Adds a new authentication method to the identity. - // Requires authentication. - authn_method_add : (IdentityNumber, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodAddError }); - - // Atomically replaces the authentication method matching the supplied public key with the new authentication method - // provided. - // Requires authentication. - authn_method_replace : (IdentityNumber, PublicKey, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodReplaceError }); - - // Replaces the authentication method metadata map. - // The existing metadata map will be overwritten. - // Requires authentication. - authn_method_metadata_replace : (IdentityNumber, PublicKey, MetadataMapV2) -> (variant { Ok; Err : AuthnMethodMetadataReplaceError }); - - // Replaces the authentication method security settings. - // The existing security settings will be overwritten. - // Requires authentication. - authn_method_security_settings_replace : (IdentityNumber, PublicKey, AuthnMethodSecuritySettings) -> (variant { Ok; Err : AuthnMethodSecuritySettingsReplaceError }); - - // Removes the authentication method associated with the public key from the identity. - // Requires authentication. - authn_method_remove : (IdentityNumber, PublicKey) -> (variant { Ok; Err }); - - // Enters the authentication method registration mode for the identity. - // In this mode, a new authentication method can be registered, which then needs to be - // confirmed before it can be used for authentication on this identity. - // The registration mode is automatically exited after the returned expiration timestamp. - // Requires authentication. - authn_method_registration_mode_enter : (IdentityNumber, opt RegistrationId) -> (variant { Ok : record { expiration : Timestamp }; Err : AuthnMethodRegistrationModeEnterError }); - - // Exits the authentication method registration mode for the identity. - // Requires authentication. - authn_method_registration_mode_exit : (IdentityNumber, opt AuthnMethodData) -> (variant { Ok; Err : AuthnMethodRegistrationModeExitError }); - - // Registers a new authentication method to the identity. - // This authentication method needs to be confirmed before it can be used for authentication on this identity. - authn_method_register : (IdentityNumber, AuthnMethodData) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); - - // Registers a new session for the identity. - // This session needs to be confirmed before it can be used to register an authentication method on this identity. - authn_method_session_register : (IdentityNumber) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); - - // Returns session info when session is confirmed and caller matches session. - authn_method_session_info : (IdentityNumber) -> (opt AuthnMethodSessionInfo) query; - - // Confirms a previously registered authentication method. - // On successful confirmation, the authentication method is permanently added to the identity and can - // subsequently be used for authentication for that identity. - // Requires authentication. - authn_method_confirm : (IdentityNumber, confirmation_code : text) -> (variant { Ok; Err : AuthnMethodConfirmationError }); - - lookup_by_registration_mode_id : (RegistrationId) -> (opt IdentityNumber) query; - - // Authentication protocol - // ======================= - prepare_delegation : (UserNumber, FrontendHostname, SessionKey, maxTimeToLive : opt nat64) -> (UserKey, Timestamp); - get_delegation : (UserNumber, FrontendHostname, SessionKey, Timestamp) -> (GetDelegationResponse) query; - - // Attribute sharing protocol - // ========================== - prepare_attributes : (PrepareAttributeRequest) -> (variant { Ok : PrepareAttributeResponse; Err : PrepareAttributeError }); - get_attributes : (GetAttributesRequest) -> (variant { Ok : CertifiedAttributes; Err : GetAttributesError }) query; - - // ICRC-3 Attribute sharing protocol - // ================================== - prepare_icrc3_attributes : (PrepareIcrc3AttributeRequest) -> (variant { Ok : PrepareIcrc3AttributeResponse; Err : PrepareIcrc3AttributeError }); - get_icrc3_attributes : (GetIcrc3AttributeRequest) -> (variant { Ok : GetIcrc3AttributeResponse; Err : GetIcrc3AttributeError }) query; - list_available_attributes : (ListAvailableAttributesRequest) -> (variant { Ok : ListAvailableAttributesResponse; Err : ListAvailableAttributesError }) query; - - // Old Verifiable Credentials API - // ============================== - // The methods below are used to generate ID-alias credentials during attribute sharing flow. - prepare_id_alias : (PrepareIdAliasRequest) -> (variant { Ok : PreparedIdAlias; Err : PrepareIdAliasError }); - get_id_alias : (GetIdAliasRequest) -> (variant { Ok : IdAliasCredentials; Err : GetIdAliasError }) query; - - // OpenID credentials protocol - // =========================== - openid_identity_registration_finish : (OpenIDRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); - openid_credential_add : (IdentityNumber, JWT, Salt) -> (variant { Ok; Err : OpenIdCredentialAddError }); - openid_credential_remove : (IdentityNumber, OpenIdCredentialKey) -> (variant { Ok; Err : OpenIdCredentialRemoveError }); - openid_prepare_delegation : (JWT, Salt, SessionKey) -> (variant { Ok : OpenIdPrepareDelegationResponse; Err : OpenIdDelegationError }); - openid_get_delegation : (JWT, Salt, SessionKey, Timestamp) -> (variant { Ok : SignedDelegation; Err : OpenIdDelegationError }) query; - - // HTTP Gateway protocol - // ===================== - http_request : (request : HttpRequest) -> (HttpResponse) query; - - // OIDC Discovery - // =============== - discovered_oidc_configs : () -> (vec OidcConfig) query; - add_discoverable_oidc_config : (DiscoverableOidcConfig) -> (); - - // Internal Methods - // ================ - init_salt : () -> (); - stats : () -> (InternetIdentityStats) query; - config : () -> (InternetIdentityInit) query; - whoami : () -> (principal) query; - - deploy_archive : (wasm : blob) -> (DeployArchiveResult); - // Returns a batch of entries _sorted by sequence number_ to be archived. - // This is an update call because the archive information _must_ be certified. - // Only callable by this IIs archive canister. - fetch_entries : () -> (vec BufferedArchiveEntry); - acknowledge_entries : (sequence_number : nat64) -> (); - - // Discoverable passkeys protocol - lookup_device_key : (credential_id : blob) -> (opt DeviceKeyWithAnchor) query; - - // Multiple accounts - get_accounts : ( - anchor_number : UserNumber, - origin : FrontendHostname, - ) -> (variant { Ok : vec AccountInfo; Err: GetAccountsError }) query; - - create_account : ( - anchor_number : UserNumber, - origin : FrontendHostname, - name : text - ) -> (variant { Ok : AccountInfo; Err: CreateAccountError }); - - update_account : ( - anchor_number : UserNumber, - origin : FrontendHostname, - account_number : opt AccountNumber, // Null is unreserved default account - update : AccountUpdate - ) -> (variant { Ok : AccountInfo; Err: UpdateAccountError }); - - prepare_account_delegation : ( - anchor_number : UserNumber, - origin : FrontendHostname, - account_number : opt AccountNumber, // Null is unreserved default account - session_key : SessionKey, - max_ttl : opt nat64 - ) -> (variant { Ok : PrepareAccountDelegation; Err : AccountDelegationError }); - - get_account_delegation : ( - anchor_number : UserNumber, - origin : FrontendHostname, - account_number : opt AccountNumber, // Null is unreserved default account - session_key : SessionKey, - expiration : Timestamp - ) -> (variant { Ok : SignedDelegation; Err : AccountDelegationError }) query; - - get_default_account : ( - anchor_number : UserNumber, - origin : FrontendHostname, - ) -> (variant { Ok : AccountInfo; Err: GetDefaultAccountError }) query; - - set_default_account : ( - anchor_number : UserNumber, - origin : FrontendHostname, - account_number : opt AccountNumber - ) -> (variant { Ok : AccountInfo; Err: SetDefaultAccountError }); - - // Looks up identity number when called with a recovery phrase - lookup_caller_identity_by_recovery_phrase : () -> (opt IdentityNumber); -}; -``` +The complete Candid interface definition is available at [`internet-identity.did`](/references/internet-identity.did). This file defines all types and method signatures in machine-readable Candid format and can be used for binding generation and type checking. ### Identity management (legacy API) @@ -2068,6 +731,6 @@ Link replacements from source (source used absolute/relative paths pointing outs - vc-spec.md (relative, same dir in source repo) → ../guides/authentication/verifiable-credentials.md Other changes from source: - `# The Internet Identity Specification` H1 removed (Starlight renders frontmatter title as H1) - - `{IICandidInterface}` replaced with inlined content from src/internet_identity/internet_identity.did + - `{IICandidInterface}` replaced with download link to /references/internet-identity.did --> diff --git a/public/references/internet-identity.did b/public/references/internet-identity.did new file mode 100644 index 00000000..955353bd --- /dev/null +++ b/public/references/internet-identity.did @@ -0,0 +1,1336 @@ +type UserNumber = nat64; +type AccountNumber = nat64; +type PublicKey = blob; +type CredentialId = blob; +type Aaguid = blob; +type DeviceKey = PublicKey; +type UserKey = PublicKey; +type SessionKey = PublicKey; +type FrontendHostname = text; +type Timestamp = nat64; + +type HeaderField = record { + text; + text; +}; + +type HttpRequest = record { + method : text; + url : text; + headers : vec HeaderField; + body : blob; + certificate_version : opt nat16; +}; + +type HttpResponse = record { + status_code : nat16; + headers : vec HeaderField; + body : blob; + upgrade : opt bool; +}; + +type StreamingCallbackHttpResponse = record { + body : blob; + token : opt Token; +}; + +type Token = record {}; + +type StreamingStrategy = variant { + Callback : record { + callback : func(Token) -> (StreamingCallbackHttpResponse) query; + token : Token; + }; +}; + +type Purpose = variant { + recovery; + authentication; +}; + +type KeyType = variant { + unknown; + platform; + cross_platform; + seed_phrase; + browser_storage_key; +}; + +// This describes whether a device is "protected" or not. +// When protected, a device can only be updated or removed if the +// user is authenticated with that very device. +type DeviceProtection = variant { + protected; + unprotected; +}; + +type Challenge = record { + png_base64 : text; + challenge_key : ChallengeKey; +}; + +type DeviceData = record { + pubkey : DeviceKey; + alias : text; + credential_id : opt CredentialId; + aaguid : opt Aaguid; + purpose : Purpose; + key_type : KeyType; + protection : DeviceProtection; + origin : opt text; + // Metadata map for additional device information. + // + // Note: some fields above will be moved to the metadata map in the future. + // All field names of `DeviceData` (such as 'alias', 'origin, etc.) are + // reserved and cannot be written. + // In addition, the keys "usage" and "authenticator_attachment" are reserved as well. + metadata : opt MetadataMap; +}; + +// The same as `DeviceData` but with the `last_usage` field. +// This field cannot be written, hence the separate type. +type DeviceWithUsage = record { + pubkey : DeviceKey; + alias : text; + credential_id : opt CredentialId; + aaguid : opt Aaguid; + purpose : Purpose; + key_type : KeyType; + protection : DeviceProtection; + origin : opt text; + last_usage : opt Timestamp; + metadata : opt MetadataMap; +}; + +type DeviceKeyWithAnchor = record { + pubkey : DeviceKey; + anchor_number : UserNumber; +}; + +// Map with some variants for the value type. +// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. +type MetadataMap = vec record { + text; + variant { map : MetadataMap; string : text; bytes : vec nat8 }; +}; + +type RegisterResponse = variant { + // A new user was successfully registered. + registered : record { + user_number : UserNumber; + }; + // No more registrations are possible in this instance of the II service canister. + canister_full; + // The challenge was not successful. + bad_challenge; +}; + +type AddTentativeDeviceResponse = variant { + // The device was tentatively added. + added_tentatively : record { + verification_code : text; + // Expiration date, in nanos since the epoch + device_registration_timeout : Timestamp; + }; + // Device registration mode is off, either due to timeout or because it was never enabled. + device_registration_mode_off; + // There is another device already added tentatively + another_device_tentatively_added; + // Passkey with this public key is already used + passkey_with_this_public_key_is_already_used; +}; + +type VerifyTentativeDeviceResponse = variant { + // The device was successfully verified. + verified; + // Wrong verification code entered. Retry with correct code. + wrong_code : record { + retries_left : nat8; + }; + // Device registration mode is off, either due to timeout or because it was never enabled. + device_registration_mode_off; + // There is no tentative device to be verified. + no_device_to_verify; +}; + +type Delegation = record { + pubkey : PublicKey; + expiration : Timestamp; + targets : opt vec principal; +}; + +type SignedDelegation = record { + delegation : Delegation; + signature : blob; +}; + +type GetDelegationResponse = variant { + // The signed delegation was successfully retrieved. + signed_delegation : SignedDelegation; + + // The signature is not ready. Maybe retry by calling `prepare_delegation` + no_such_delegation; +}; + +type InternetIdentityStats = record { + users_registered : nat64; + storage_layout_version : nat8; + assigned_user_number_range : record { + nat64; + nat64; + }; + archive_info : ArchiveInfo; + canister_creation_cycles_cost : nat64; + // Map from event aggregation to a sorted list of top 100 sub-keys to their weights. + // Example: {"prepare_delegation_count 24h ic0.app": [{"https://dapp.com", 100}, {"https://dapp2.com", 50}]} + event_aggregations : vec record { text; vec record { text; nat64 } }; +}; + +// Configuration parameters related to the archive. +type ArchiveConfig = record { + // The allowed module hash of the archive canister. + // Changing this parameter does _not_ deploy the archive, but enable archive deployments with the + // corresponding wasm module. + module_hash : blob; + // Buffered archive entries limit. If reached, II will stop accepting new anchor operations + // until the buffered operations are acknowledged by the archive. + entries_buffer_limit : nat64; + // The maximum number of entries to be transferred to the archive per call. + entries_fetch_limit : nat16; + // Polling interval to fetch new entries from II (in nanoseconds). + // Changes to this parameter will only take effect after an archive deployment. + polling_interval_ns : nat64; +}; + +// Information about the archive. +type ArchiveInfo = record { + // Canister id of the archive or empty if no archive has been deployed yet. + archive_canister : opt principal; + // Configuration parameters related to the II archive. + archive_config : opt ArchiveConfig; +}; + +// Rate limit configuration. +// Currently only used for `register`. +type RateLimitConfig = record { + // Time it takes (in ns) for a rate limiting token to be replenished. + time_per_token_ns : nat64; + // How many tokens are at most generated (to accommodate peaks). + max_tokens : nat64; +}; + +// Captcha configuration +// Default: +// - max_unsolved_captchas: 500 +// - captcha_trigger: Static, CaptchaEnabled +type CaptchaConfig = record { + // Maximum number of unsolved captchas. + max_unsolved_captchas : nat64; + // Configuration for when captcha protection should kick in. + captcha_trigger : variant { + // Based on the rate of registrations compared to some reference time frame and allowing some leeway. + Dynamic : record { + // Percentage of increased registration rate observed in the current rate sampling interval (compared to + // reference rate) at which II will enable captcha for new registrations. + threshold_pct : nat16; + // Length of the interval in seconds used to sample the current rate of registrations. + current_rate_sampling_interval_s : nat64; + // Length of the interval in seconds used to sample the reference rate of registrations. + reference_rate_sampling_interval_s : nat64; + }; + // Statically enable / disable captcha + Static : variant { + CaptchaEnabled; + CaptchaDisabled; + }; + }; +}; + +// Init arguments of II which can be supplied on install and upgrade. +// +// Each field is wrapped is `opt` to indicate whether the field should +// keep the previous value or update to a new value (e.g. `null` keeps the previous value). +// +// Some fields, like `analytics_config`, have an additional nested `opt`, this indicates +// enable/disable status (e.g. `opt null` disables a feature while `null` leaves it untouched). +type InternetIdentityInit = record { + // Set lowest and highest anchor + assigned_user_number_range : opt record { + nat64; + nat64; + }; + // Configuration parameters related to the II archive. + // Note: some parameters changes (like the polling interval) will only take effect after an archive deployment. + // See ArchiveConfig for details. + archive_config : opt ArchiveConfig; + // Set the amounts of cycles sent with the create canister message. + // This is configurable because in the staging environment cycles are required. + // The canister creation cost on mainnet is currently 100'000'000'000 cycles. If this value is higher thant the + // canister creation cost, the newly created canister will keep extra cycles. + canister_creation_cycles_cost : opt nat64; + // Rate limit for the `register` call. + register_rate_limit : opt RateLimitConfig; + // Configuration of the captcha in the registration flow. + captcha_config : opt CaptchaConfig; + // Configuration for Related Origins Requests. + // If present, list of origins from where registration is allowed. + related_origins : opt vec text; + // Configuration for New Origin Flows. + // If present, list of origins using the new authentication flow. + new_flow_origins : opt vec text; + // Configurations for OpenID clients + openid_configs : opt vec OpenIdConfig; + // Allowlist of domains that may be registered as discoverable SSO + // providers via `add_discoverable_oidc_config`. When set, fully replaces + // the built-in defaults. When unset, falls back to `dfinity.org` + // (production) or `beta.dfinity.org` (everything else), keyed off + // `is_production`. + sso_discoverable_domains : opt vec text; + // Configuration for Web Analytics + analytics_config : opt opt AnalyticsConfig; + // Configuration to show dapps explorer or not + enable_dapps_explorer : opt bool; + // Configuration to set the canister as production mode. + // For now, this is used only to show or hide the banner. + is_production : opt bool; + // Configuration for dummy authentication used in e2e tests. + dummy_auth : opt opt DummyAuthConfig; + // Backend canister ID, needed for backward compatibility. + backend_canister_id : opt principal; + // Backend origin, needed to sync configuration with frontend. + backend_origin : opt text; + // DNSSEC verification configuration. Trust anchors used by any feature + // that verifies DNS records against the IANA-rooted DNSSEC chain + // (currently the email-recovery DKIM/DMARC flow). See + // `docs/ongoing/email-recovery.md` §7.5. + // + // Wrapped in `opt opt` to match the same set/clear pattern as + // `analytics_config` / `dummy_auth`: outer null keeps the previously + // stored value across an upgrade, `opt null` clears it, `opt opt c` + // sets it to `c`. + dnssec_config : opt opt DnssecConfig; +}; + +// DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS +// records consumes the same anchors; not specific to email recovery. +type DnssecConfig = record { + // IANA root KSK trust anchors. Multiple are accepted simultaneously so + // KSK rollover is a single config change in the next upgrade arg. + root_anchors : vec DnssecRootAnchor; +}; + +// One IANA root KSK trust anchor, in the same shape IANA publishes at +// `data.iana.org/root-anchors/root-anchors.xml`. Only `digest_type = 2` +// (SHA-256) is accepted; the legacy SHA-1 form is rejected at the +// verifier boundary. +type DnssecRootAnchor = record { + key_tag : nat16; + algorithm : nat8; + digest_type : nat8; + digest : blob; +}; + +type ChallengeKey = text; + +type ChallengeResult = record { + key : ChallengeKey; + chars : text; +}; +type CaptchaResult = ChallengeResult; + +// Extra information about registration status for new devices +type DeviceRegistrationInfo = record { + // If present, the user has registered a new authentication method. This new authentication + // method needs to be confirmed before 'expiration' in order to be added to the identity. + tentative_device : opt DeviceData; + // If present, the user has registered a new session. This new session needs to be confirmed before + // 'expiration' in order for it be authorized to register an authentication method to the identity. + tentative_session : opt principal; + // The timestamp at which the anchor will turn off registration mode + // (and the tentative device will be forgotten, if any, and if not verified) + expiration : Timestamp; +}; + +// Information about the anchor +type IdentityAnchorInfo = record { + // All devices that can authenticate to this anchor + devices : vec DeviceWithUsage; + // Device registration status used when adding devices, see DeviceRegistrationInfo + device_registration : opt DeviceRegistrationInfo; + // OpenID accounts linked to this anchor + openid_credentials : opt vec OpenIdCredential; + // The name of the Internet Identity + name : opt text; + // The timestamp at which the anchor was created + created_at : opt Timestamp; +}; + +type AnchorCredentials = record { + credentials : vec WebAuthnCredential; + recovery_credentials : vec WebAuthnCredential; + recovery_phrases : vec PublicKey; +}; + +type WebAuthnCredential = record { + credential_id : CredentialId; + pubkey : PublicKey; +}; + +type DeployArchiveResult = variant { + // The archive was deployed successfully and the supplied wasm module has been installed. The principal of the archive + // canister is returned. + success : principal; + // Initial archive creation is already in progress. + creation_in_progress; + // Archive deployment failed. An error description is returned. + failed : text; +}; + +type BufferedArchiveEntry = record { + anchor_number : UserNumber; + timestamp : Timestamp; + sequence_number : nat64; + entry : blob; +}; + +// OpenID specific types +type Iss = text; +type Sub = text; +type Aud = text; +type JWT = text; +type Salt = blob; + +type OpenIdEmailVerification = variant { + Unknown; + Google; + Microsoft; +}; + +type OpenIdConfig = record { + name : text; + logo : text; + issuer : text; + client_id : text; + jwks_uri : text; + auth_uri : text; + auth_scope : vec text; + fedcm_uri : opt text; + email_verification : opt OpenIdEmailVerification; +}; + +// SSO provider config that uses two-hop discovery. +// The backend fetches https://{discovery_domain}/.well-known/ii-openid-configuration +// for { client_id, openid_configuration } and then fetches the standard OIDC +// discovery at openid_configuration for { issuer, jwks_uri }. +type DiscoverableOidcConfig = record { + discovery_domain : text; +}; + +// Resolved SSO provider state. +// All fields other than discovery_domain are None until discovery completes. +type OidcConfig = record { + discovery_domain : text; + client_id : opt text; + openid_configuration : opt text; + issuer : opt text; +}; + +type OpenIdCredentialKey = record { Iss; Sub; Aud }; + +type AnalyticsConfig = variant { + Plausible : record { + domain : opt text; + hash_mode : opt bool; + track_localhost : opt bool; + api_host : opt text; + }; +}; + +type OpenIdCredential = record { + iss : Iss; + sub : Sub; + aud : Aud; + last_usage_timestamp : opt Timestamp; + metadata : MetadataMapV2; + // SSO discovery domain, looked up by `(iss, aud)` against the + // canister's registered discoverable OIDC configs. `None` for + // direct-provider credentials (Google / Apple / Microsoft) and for + // SSO credentials whose provider is no longer registered. + sso_domain : opt text; + // Human-readable SSO name from the domain's + // `/.well-known/ii-openid-configuration`. `None` when the domain + // doesn't publish one — callers should fall back to `sso_domain` + // for the label. + sso_name : opt text; +}; + +type OpenIdCredentialAddError = variant { + Unauthorized : principal; + JwtVerificationFailed; + OpenIdCredentialAlreadyRegistered; + InternalCanisterError : text; + JwtExpired; +}; + +type OpenIdCredentialRemoveError = variant { + Unauthorized : principal; + OpenIdCredentialNotFound; + InternalCanisterError : text; +}; + +type OpenIdDelegationError = variant { + NoSuchAnchor; + JwtVerificationFailed; + NoSuchDelegation; + JwtExpired; +}; + +type OpenIdPrepareDelegationResponse = record { + user_key : UserKey; + expiration : Timestamp; + anchor_number : UserNumber; +}; + +// API V2 specific types +// WARNING: These type are experimental and may change in the future. +type IdentityNumber = nat64; + +// Map with some variants for the value type. +// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. +type MetadataMapV2 = vec record { + text; + variant { Map : MetadataMapV2; String : text; Bytes : vec nat8 }; +}; + +// Authentication method using WebAuthn signatures +// See https://www.w3.org/TR/webauthn-2/ +// This is a separate type because WebAuthn requires to also store +// the credential id (in addition to the public key). +type WebAuthn = record { + credential_id : CredentialId; + pubkey : PublicKey; + + // Authenticator Attestation Global Unique Identifier (AAGUID) + aaguid : opt Aaguid; +}; + +// Authentication method using generic signatures +// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#signatures for +// supported signature schemes. +type PublicKeyAuthn = record { + pubkey : PublicKey; +}; + +// The authentication methods currently supported by II. +type AuthnMethod = variant { + WebAuthn : WebAuthn; + PubKey : PublicKeyAuthn; +}; + +// This describes whether an authentication method is "protected" or not. +// When protected, a authentication method can only be updated or removed if the +// user is authenticated with that very authentication method. +type AuthnMethodProtection = variant { + Protected; + Unprotected; +}; + +type AuthnMethodPurpose = variant { + Recovery; + Authentication; +}; + +type AuthnMethodSecuritySettings = record { + protection : AuthnMethodProtection; + purpose : AuthnMethodPurpose; +}; + +type AuthnMethodData = record { + authn_method : AuthnMethod; + security_settings : AuthnMethodSecuritySettings; + // contains the following fields of the DeviceWithUsage type: + // - alias + // - origin + // - authenticator_attachment: data taken from key_type and reduced to "platform", "cross_platform" or absent on migration + // - usage: data taken from key_type and reduced to "recovery_phrase", "browser_storage_key" or absent on migration + // Note: for compatibility reasons with the v1 API, the entries above (if present) + // must be of the `String` variant. This restriction may be lifted in the future. + metadata : MetadataMapV2; + last_authentication : opt Timestamp; +}; + +type OpenIDRegFinishArg = record { + jwt : JWT; + salt : Salt; + name : text; +}; + +// Extra information about registration status for new authentication methods +type AuthnMethodRegistrationInfo = record { + + // If present, the user has registered a new authentication method. This new authentication + // method needs to be confirmed before 'expiration' in order to be added to the identity. + authn_method : opt AuthnMethodData; + // If present, the user has registered a new session. This new session needs to be confirmed before + // 'expiration' in order for it be authorized to register an authentication method to the identity. + session : opt principal; + // The timestamp at which the identity will turn off registration mode + // (and the authentication method will be forgotten, if any, and if not verified) + expiration : Timestamp; +}; + +type AuthnMethodConfirmationCode = record { + confirmation_code : text; + expiration : Timestamp; +}; + +type AuthnMethodSessionInfo = record { + name : opt text; + created_at : opt Timestamp; +}; + +type RegistrationId = text; + +type AuthnMethodRegisterError = variant { + // Authentication method registration mode is off, either due to timeout or because it was never enabled. + RegistrationModeOff; + // There is another authentication method already registered that needs to be confirmed first. + RegistrationAlreadyInProgress; + // The metadata of the provided authentication method contains invalid entries. + InvalidMetadata : text; + // The caller's principal is not self-authenticating. + NotSelfAuthenticating : principal; + // Passkey with this public key is already used + PasskeyWithThisPublicKeyIsAlreadyUsed; +}; + +type AuthnMethodConfirmationError = variant { + Unauthorized : principal; + InternalCanisterError : text; + // Wrong confirmation code entered. Retry with correct code. + WrongCode : record { + retries_left : nat8; + }; + // Authentication method registration mode is off, either due to timeout or because it was never enabled. + RegistrationModeOff; + // There is no registered authentication method to be confirmed. + NoAuthnMethodToConfirm; +}; + +type AuthnMethodRegistrationModeEnterError = variant { + InvalidRegistrationId : text; + Unauthorized : principal; + AlreadyInProgress; + InternalCanisterError : text; +}; + +type AuthnMethodRegistrationModeExitError = variant { + Unauthorized : principal; + InternalCanisterError : text; + RegistrationModeOff; + InvalidMetadata : text; + PasskeyWithThisPublicKeyIsAlreadyUsed; +}; + +type LookupByRegistrationIdError = variant { + InvalidRegistrationId : text; +}; + +type IdentityAuthnInfo = record { + authn_methods : vec AuthnMethod; + recovery_authn_methods : vec AuthnMethod; +}; + +type IdentityInfo = record { + authn_methods : vec AuthnMethodData; + authn_method_registration : opt AuthnMethodRegistrationInfo; + openid_credentials : opt vec OpenIdCredential; + // Authentication method independent metadata + metadata : MetadataMapV2; + name : opt text; + // The timestamp at which the anchor was created + created_at : opt Timestamp; +}; + +type IdentityInfoError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +type AuthnMethodAddError = variant { + InvalidMetadata : text; +}; + +type AuthnMethodReplaceError = variant { + InvalidMetadata : text; + // No authentication method found with the given public key. + AuthnMethodNotFound; + // Passkey with this public key is already used + PasskeyWithThisPublicKeyIsAlreadyUsed; +}; + +type AuthnMethodMetadataReplaceError = variant { + InvalidMetadata : text; + // No authentication method found with the given public key. + AuthnMethodNotFound; +}; + +type AuthnMethodSecuritySettingsReplaceError = variant { + // No authentication method found with the given public key. + AuthnMethodNotFound; +}; + +type IdentityMetadataReplaceError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // The identity including the new metadata exceeds the maximum allowed size. + StorageSpaceExceeded : record { + space_available : nat64; + space_required : nat64; + }; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +type CreateAccountError = variant { + InternalCanisterError : text; + AccountLimitReached; + Unauthorized : principal; + NameTooLong; +}; + +type UpdateAccountError = variant { + InternalCanisterError : text; + AccountLimitReached; + Unauthorized : principal; + NameTooLong; +}; + +type AccountDelegationError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchDelegation; +}; + +type GetDefaultAccountError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchAnchor; + NoSuchOrigin : record { + anchor_number : UserNumber; + }; +}; + +type SetDefaultAccountError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchAnchor; + NoSuchOrigin : record { + anchor_number : UserNumber; + }; + NoSuchAccount : record { + anchor_number : UserNumber; + origin : FrontendHostname; + }; +}; + +type PrepareAccountDelegation = record { + user_key : UserKey; + expiration : Timestamp; +}; + +type GetAccountsError = variant { + InternalCanisterError : text; + Unauthorized : principal; +}; + +type PrepareIdAliasRequest = record { + // Origin of the issuer in the attribute sharing flow. + issuer : FrontendHostname; + // Origin of the relying party in the attribute sharing flow. + relying_party : FrontendHostname; + // Identity for which the IdAlias should be generated. + identity_number : IdentityNumber; +}; + +type PrepareIdAliasError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +// The prepared id alias contains two (still unsigned) credentials in JWT format, +// certifying the id alias for the issuer resp. the relying party. +type PreparedIdAlias = record { + rp_id_alias_jwt : text; + issuer_id_alias_jwt : text; + canister_sig_pk_der : PublicKey; +}; + +// The request to retrieve the actual signed id alias credentials. +// The field values should be equal to the values of corresponding +// fields from the preceding `PrepareIdAliasRequest` and `PrepareIdAliasResponse`. +type GetIdAliasRequest = record { + rp_id_alias_jwt : text; + issuer : FrontendHostname; + issuer_id_alias_jwt : text; + relying_party : FrontendHostname; + identity_number : IdentityNumber; +}; + +type GetIdAliasError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // The credential(s) are not available: may be expired or not prepared yet (call prepare_id_alias to prepare). + NoSuchCredentials : text; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +// The signed id alias credentials for each involved party. +type IdAliasCredentials = record { + rp_id_alias_credential : SignedIdAlias; + issuer_id_alias_credential : SignedIdAlias; +}; + +type SignedIdAlias = record { + credential_jws : text; + id_alias : principal; + id_dapp : principal; +}; + +type IdRegNextStepResult = record { + // The next step in the registration flow + next_step : RegistrationFlowNextStep; +}; + +type IdRegStartError = variant { + // The method was called anonymously, which is not supported. + InvalidCaller; + // Too many registrations. Please try again later. + RateLimitExceeded; + // A registration flow is already in progress. + AlreadyInProgress; +}; + +// The next step in the registration flow: +// - CheckCaptcha: supply the solution to the captcha using `check_captcha` +// - Finish: finish the registration using `identity_registration_finish` +type RegistrationFlowNextStep = variant { + // Supply the captcha solution using check_captcha + CheckCaptcha : record { + captcha_png_base64 : text; + }; + // Finish the registration using identity_registration_finish + Finish; +}; + +type CheckCaptchaArg = record { + solution : text; +}; + +type CheckCaptchaError = variant { + // The supplied solution was wrong. Try again with the new captcha. + WrongSolution : record { + new_captcha_png_base64 : text; + }; + // This call is unexpected, see next_step. + UnexpectedCall : record { + next_step : RegistrationFlowNextStep; + }; + // No registration flow ongoing for the caller. + NoRegistrationFlow; +}; + +type IdRegFinishArg = record { + authn_method : AuthnMethodData; + name : opt text; +}; + +type IdRegFinishResult = record { + identity_number : nat64; +}; + +type IdRegFinishError = variant { + // This call is unexpected, see next_step. + UnexpectedCall : record { + next_step : RegistrationFlowNextStep; + }; + // No registration flow ongoing for the caller. + NoRegistrationFlow; + // The supplied authn_method is not valid. + InvalidAuthnMethod : text; + // Error while persisting the new identity. + StorageError : text; +}; + +type AccountInfo = record { + // Null is unreserved default account + account_number : opt AccountNumber; + origin : text; + last_used : opt Timestamp; + // Configurable properties + name : opt text; +}; + +type AccountUpdate = record { + name : opt text; +}; + +type DummyAuthConfig = record { + // Prompts user for a index value (0 - 255) when set to true, + // this is used in e2e to have multiple dummy auth identities. + prompt_for_index : bool; +}; + +type IdentityPropertiesReplace = record { + name : opt text; +}; + +type IdentityPropertiesReplaceError = variant { + Unauthorized : principal; + StorageSpaceExceeded : record { + space_available : nat64; + space_required : nat64; + }; + NameTooLong : record { + limit : nat64; + }; + InternalCanisterError : text; +}; + +type PrepareAttributeRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The attributes to be prepared. Each key has the form + // `:`, where `` is either + // `openid:` (e.g. `openid:https://accounts.google.com:email`) + // or `sso:` (e.g. `sso:dfinity.org:email`). + // + // Each linked credential is addressable via exactly one scope: + // credentials obtained through a `DiscoverableOidcConfig` (two-hop SSO + // discovery) are reachable only via `sso:`; credentials from + // hardcoded OIDC providers (Google, Microsoft, …) are reachable only via + // `openid:`. Under `sso:` only `email` and `name` are supported; + // under `openid:` `email`, `name`, and `verified_email` are supported. + attribute_keys : vec text; +}; + +type GetAccountError = variant { + NoSuchOrigin : record { + anchor_number : UserNumber; + }; + NoSuchAccount : record { + anchor_number : UserNumber; + origin : FrontendHostname; + }; +}; + +type PrepareAttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; +}; + +type PrepareAttributeResponse = record { + issued_at_timestamp_ns : Timestamp; + attributes : vec record { text; blob }; +}; + +type GetAttributesRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // Timestamp received from the prepare_attributes call. + issued_at_timestamp_ns : Timestamp; + + // The attribute to be retrieved, must be a subset of certified_attributes from + // the prepare_attributes response. + attributes : vec record { text; blob }; +}; + +type CertifiedAttribute = record { + key : text; + value : blob; + signature : blob; +}; + +type CertifiedAttributes = record { + expires_at_timestamp_ns : Timestamp; + certified_attributes : vec CertifiedAttribute; +}; + +type GetAttributesError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; +}; + +// ICRC-3 attribute sharing types +// ============================== + +type Icrc3Value = variant { + Nat : nat; + Int : int; + Blob : blob; + Text : text; + Array : vec Icrc3Value; + Map : vec record { text; Icrc3Value }; +}; + +// A specification of an attribute to be certified. +type AttributeSpec = record { + // `:` where `` is + // either `openid:` or `sso:`. + // + // Examples: + // - `openid:https://accounts.google.com:email` + // - `sso:dfinity.org:email` + key : text; + + // If `value` is set, this attribute should be certified only if it matches + // the current value. This enables the II frontend to request a certificate + // for a set of attributes that is guaranteed to be approved by the user. + value : opt blob; + + // Whether the attribute scope should be omitted before certification. + // Certifying unscoped attributes is useful, e.g., when the user wants + // to share their preferred email without revealing which OpenID + // provider they use with II. + // + // Examples (e.g., `key = "openid:https://accounts.google.com:email"`): + // 1. If `omit_scope = true`, then the certified attribute key is `email`. + // 2. Otherwise, the certified attribute key is literally the value of `key`. + omit_scope : bool; +}; + +type PrepareIcrc3AttributeRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. May have + // been remapped from `.icp0.io` to `.ic0.app` for principal + // stability — see `unmapped_origin`. + origin : FrontendHostname; + + // The relying party's actual origin, before the legacy `icp0.io → + // ic0.app` remap that `origin` may have gone through. When set, this + // value is what gets certified as `implicit:origin` instead of `origin`, + // so that an RP signed in via the icp0.io domain sees its real origin + // in the certified message. The canister verifies that mapping + // `unmapped_origin` through the same legacy remap yields `origin`, + // so an RP can't certify an arbitrary value here. + unmapped_origin : opt FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The attributes to be certified. + attributes : vec AttributeSpec; + + // The nonce is a 32-byte value generated by the relying party. + // The purpose of the nonce is to prevent replay attacks and + // enable the relying party to expire attributes issued for a given flow. + // The value of the nonce will be included into the signed ICRC-3 + // message as a separate attribute with the key `implicit:nonce`. + nonce : blob; +}; + +type PrepareIcrc3AttributeResponse = record { + // Candid-encoded ICRC-3 Value map. Pass this to get_icrc3_attributes. + message : blob; +}; + +type PrepareIcrc3AttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; + AttributeMismatch : record { + problems : vec text; + }; +}; + +type GetIcrc3AttributeRequest = record { + // Identity for which the attributes were prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The message blob from PrepareIcrc3AttributeResponse. + message : blob; +}; + +type GetIcrc3AttributeResponse = record { + signature : blob; +}; + +type GetIcrc3AttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; + NoSuchSignature; +}; + +// List available attributes +type ListAvailableAttributesRequest = record { + // Identity for which available attributes should be listed. + identity_number : IdentityNumber; + + // Optional list of attribute keys to filter by. + // Each key is either a fully-scoped key (e.g., + // `"openid:https://accounts.google.com:email"` or + // `"sso:dfinity.org:email"`) or an unscoped attribute name (e.g., + // `"email"`) which matches all scopes. + // If not provided, all available attributes are returned. + attributes : opt vec text; +}; + +type ListAvailableAttributesResponse = vec record { text; blob }; + +type ListAvailableAttributesError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; +}; + +service : (opt InternetIdentityInit) -> { + // Legacy identity management API + // ============================== + create_challenge : () -> (Challenge); + register : (DeviceData, ChallengeResult, opt principal) -> (RegisterResponse); + add : (UserNumber, DeviceData) -> (); + update : (UserNumber, DeviceKey, DeviceData) -> (); + // Atomically replace device matching the device key with the new device data + replace : (UserNumber, DeviceKey, DeviceData) -> (); + remove : (UserNumber, DeviceKey) -> (); + // Returns all devices of the user (authentication and recovery) but no information about device registrations. + // Note: Clears out the 'alias' fields on the devices. Use 'get_anchor_info' to obtain the full information. + // Deprecated: Use 'get_anchor_credentials' instead. + lookup : (UserNumber) -> (vec DeviceData) query; + get_anchor_credentials : (UserNumber) -> (AnchorCredentials) query; + get_anchor_info : (UserNumber) -> (IdentityAnchorInfo); + get_principal : (UserNumber, FrontendHostname) -> (principal) query; + + enter_device_registration_mode : (UserNumber) -> (Timestamp); + exit_device_registration_mode : (UserNumber) -> (); + add_tentative_device : (UserNumber, DeviceData) -> (AddTentativeDeviceResponse); + verify_tentative_device : (UserNumber, verification_code : text) -> (VerifyTentativeDeviceResponse); + + // V2 Identity Management API + // ========================== + // WARNING: The following methods are experimental and may ch 0ange in the future. + // Starts the identity registration flow to create a new identity. + identity_registration_start : () -> (variant { Ok : IdRegNextStepResult; Err : IdRegStartError }); + + // Check the captcha challenge + // If successful, the registration can be finished with `identity_registration_finish`. + check_captcha : (CheckCaptchaArg) -> (variant { Ok : IdRegNextStepResult; Err : CheckCaptchaError }); + + // Starts the identity registration flow to create a new identity. + identity_registration_finish : (IdRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); + + // Returns information about the authentication methods of the identity with the given number. + // Only returns the minimal information required for authentication without exposing any metadata such as aliases. + identity_authn_info : (IdentityNumber) -> (variant { Ok : IdentityAuthnInfo; Err }) query; + + // Returns information about the identity with the given number. + // Requires authentication. + identity_info : (IdentityNumber) -> (variant { Ok : IdentityInfo; Err : IdentityInfoError }); + + // Replaces the authentication method independent metadata map. + // The existing metadata map will be overwritten. + // Requires authentication. + identity_metadata_replace : (IdentityNumber, MetadataMapV2) -> (variant { Ok; Err : IdentityMetadataReplaceError }); + + // Replaces the identity properties. + // The existing properties will be overwritten. + // Requires authentication. + identity_properties_replace : (IdentityNumber, IdentityPropertiesReplace) -> (variant { Ok; Err : IdentityPropertiesReplaceError }); + + // Adds a new authentication method to the identity. + // Requires authentication. + authn_method_add : (IdentityNumber, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodAddError }); + + // Atomically replaces the authentication method matching the supplied public key with the new authentication method + // provided. + // Requires authentication. + authn_method_replace : (IdentityNumber, PublicKey, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodReplaceError }); + + // Replaces the authentication method metadata map. + // The existing metadata map will be overwritten. + // Requires authentication. + authn_method_metadata_replace : (IdentityNumber, PublicKey, MetadataMapV2) -> (variant { Ok; Err : AuthnMethodMetadataReplaceError }); + + // Replaces the authentication method security settings. + // The existing security settings will be overwritten. + // Requires authentication. + authn_method_security_settings_replace : (IdentityNumber, PublicKey, AuthnMethodSecuritySettings) -> (variant { Ok; Err : AuthnMethodSecuritySettingsReplaceError }); + + // Removes the authentication method associated with the public key from the identity. + // Requires authentication. + authn_method_remove : (IdentityNumber, PublicKey) -> (variant { Ok; Err }); + + // Enters the authentication method registration mode for the identity. + // In this mode, a new authentication method can be registered, which then needs to be + // confirmed before it can be used for authentication on this identity. + // The registration mode is automatically exited after the returned expiration timestamp. + // Requires authentication. + authn_method_registration_mode_enter : (IdentityNumber, opt RegistrationId) -> (variant { Ok : record { expiration : Timestamp }; Err : AuthnMethodRegistrationModeEnterError }); + + // Exits the authentication method registration mode for the identity. + // Requires authentication. + authn_method_registration_mode_exit : (IdentityNumber, opt AuthnMethodData) -> (variant { Ok; Err : AuthnMethodRegistrationModeExitError }); + + // Registers a new authentication method to the identity. + // This authentication method needs to be confirmed before it can be used for authentication on this identity. + authn_method_register : (IdentityNumber, AuthnMethodData) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); + + // Registers a new session for the identity. + // This session needs to be confirmed before it can be used to register an authentication method on this identity. + authn_method_session_register : (IdentityNumber) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); + + // Returns session info when session is confirmed and caller matches session. + authn_method_session_info : (IdentityNumber) -> (opt AuthnMethodSessionInfo) query; + + // Confirms a previously registered authentication method. + // On successful confirmation, the authentication method is permanently added to the identity and can + // subsequently be used for authentication for that identity. + // Requires authentication. + authn_method_confirm : (IdentityNumber, confirmation_code : text) -> (variant { Ok; Err : AuthnMethodConfirmationError }); + + lookup_by_registration_mode_id : (RegistrationId) -> (opt IdentityNumber) query; + + // Authentication protocol + // ======================= + prepare_delegation : (UserNumber, FrontendHostname, SessionKey, maxTimeToLive : opt nat64) -> (UserKey, Timestamp); + get_delegation : (UserNumber, FrontendHostname, SessionKey, Timestamp) -> (GetDelegationResponse) query; + + // Attribute sharing protocol + // ========================== + prepare_attributes : (PrepareAttributeRequest) -> (variant { Ok : PrepareAttributeResponse; Err : PrepareAttributeError }); + get_attributes : (GetAttributesRequest) -> (variant { Ok : CertifiedAttributes; Err : GetAttributesError }) query; + + // ICRC-3 Attribute sharing protocol + // ================================== + prepare_icrc3_attributes : (PrepareIcrc3AttributeRequest) -> (variant { Ok : PrepareIcrc3AttributeResponse; Err : PrepareIcrc3AttributeError }); + get_icrc3_attributes : (GetIcrc3AttributeRequest) -> (variant { Ok : GetIcrc3AttributeResponse; Err : GetIcrc3AttributeError }) query; + list_available_attributes : (ListAvailableAttributesRequest) -> (variant { Ok : ListAvailableAttributesResponse; Err : ListAvailableAttributesError }) query; + + // Old Verifiable Credentials API + // ============================== + // The methods below are used to generate ID-alias credentials during attribute sharing flow. + prepare_id_alias : (PrepareIdAliasRequest) -> (variant { Ok : PreparedIdAlias; Err : PrepareIdAliasError }); + get_id_alias : (GetIdAliasRequest) -> (variant { Ok : IdAliasCredentials; Err : GetIdAliasError }) query; + + // OpenID credentials protocol + // =========================== + openid_identity_registration_finish : (OpenIDRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); + openid_credential_add : (IdentityNumber, JWT, Salt) -> (variant { Ok; Err : OpenIdCredentialAddError }); + openid_credential_remove : (IdentityNumber, OpenIdCredentialKey) -> (variant { Ok; Err : OpenIdCredentialRemoveError }); + openid_prepare_delegation : (JWT, Salt, SessionKey) -> (variant { Ok : OpenIdPrepareDelegationResponse; Err : OpenIdDelegationError }); + openid_get_delegation : (JWT, Salt, SessionKey, Timestamp) -> (variant { Ok : SignedDelegation; Err : OpenIdDelegationError }) query; + + // HTTP Gateway protocol + // ===================== + http_request : (request : HttpRequest) -> (HttpResponse) query; + + // OIDC Discovery + // =============== + discovered_oidc_configs : () -> (vec OidcConfig) query; + add_discoverable_oidc_config : (DiscoverableOidcConfig) -> (); + + // Internal Methods + // ================ + init_salt : () -> (); + stats : () -> (InternetIdentityStats) query; + config : () -> (InternetIdentityInit) query; + whoami : () -> (principal) query; + + deploy_archive : (wasm : blob) -> (DeployArchiveResult); + // Returns a batch of entries _sorted by sequence number_ to be archived. + // This is an update call because the archive information _must_ be certified. + // Only callable by this IIs archive canister. + fetch_entries : () -> (vec BufferedArchiveEntry); + acknowledge_entries : (sequence_number : nat64) -> (); + + // Discoverable passkeys protocol + lookup_device_key : (credential_id : blob) -> (opt DeviceKeyWithAnchor) query; + + // Multiple accounts + get_accounts : ( + anchor_number : UserNumber, + origin : FrontendHostname, + ) -> (variant { Ok : vec AccountInfo; Err: GetAccountsError }) query; + + create_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + name : text + ) -> (variant { Ok : AccountInfo; Err: CreateAccountError }); + + update_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber, // Null is unreserved default account + update : AccountUpdate + ) -> (variant { Ok : AccountInfo; Err: UpdateAccountError }); + + prepare_account_delegation : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber, // Null is unreserved default account + session_key : SessionKey, + max_ttl : opt nat64 + ) -> (variant { Ok : PrepareAccountDelegation; Err : AccountDelegationError }); + + get_account_delegation : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber, // Null is unreserved default account + session_key : SessionKey, + expiration : Timestamp + ) -> (variant { Ok : SignedDelegation; Err : AccountDelegationError }) query; + + get_default_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + ) -> (variant { Ok : AccountInfo; Err: GetDefaultAccountError }) query; + + set_default_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber + ) -> (variant { Ok : AccountInfo; Err: SetDefaultAccountError }); + + // Looks up identity number when called with a recovery phrase + lookup_caller_identity_by_recovery_phrase : () -> (opt IdentityNumber); +}; diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs index c731e5ad..768ec2bf 100755 --- a/scripts/sync-ii-spec.mjs +++ b/scripts/sync-ii-spec.mjs @@ -1,23 +1,24 @@ #!/usr/bin/env node // Syncs the Internet Identity specification from .sources/internetidentity into -// docs/references/internet-identity-spec.md. +// docs/references/internet-identity-spec.md and public/references/internet-identity.did. // // Transformations applied: // - Strip MDX import lines // - Remove the H1 heading (Starlight renders the frontmatter title as H1) // - Rewrite absolute / relative links that point outside this site -// - Inline the Candid interface from internet_identity.did in place of the -// component (done after link rewriting so .did comments are untouched) +// - Replace component with a download link to internet-identity.did +// - Copy internet_identity.did to public/references/internet-identity.did // // Usage: node scripts/sync-ii-spec.mjs // or: npm run sync:ii-spec -import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs'; import { execSync } from 'node:child_process'; const SOURCE_MDX = '.sources/internetidentity/docs/ii-spec.mdx'; const SOURCE_DID = '.sources/internetidentity/src/internet_identity/internet_identity.did'; const TARGET = 'docs/references/internet-identity-spec.md'; +const TARGET_DID = 'public/references/internet-identity.did'; if (!existsSync(SOURCE_MDX)) { console.error( @@ -31,8 +32,7 @@ const version = execSync('git -C .sources/internetidentity rev-parse --short HEA .toString().trim(); console.log(`Syncing II spec from dfinity/internet-identity@${version}...`); -let content = readFileSync(SOURCE_MDX, 'utf8'); -const didContent = readFileSync(SOURCE_DID, 'utf8'); +let content = readFileSync(SOURCE_MDX, 'utf8'); // 1. Strip MDX import lines content = content.replace(/^import .*\n/gm, ''); @@ -40,7 +40,7 @@ content = content.replace(/^import .*\n/gm, ''); // 2. Strip the H1 (Starlight renders it from frontmatter) content = content.replace(/^# The Internet Identity Specification\n\n/m, ''); -// 3. Rewrite links before Candid inlining (keeps .did comments untouched) +// 3. Rewrite links const linkMap = [ [ 'https://internetcomputer.org/docs/current/references/ic-interface-spec#id-classes', @@ -80,10 +80,12 @@ for (const [old, replacement] of linkMap) { content = content.replaceAll(old, replacement); } -// 4. Inline the Candid interface (after link rewriting to avoid touching .did comments) +// 4. Replace the component with a download link to the .did file content = content.replace( '{IICandidInterface}', - '```candid\n' + didContent.trimEnd() + '\n```' + 'The complete Candid interface definition is available at [`internet-identity.did`](/references/internet-identity.did).' + + ' This file defines all types and method signatures in machine-readable Candid format' + + ' and can be used for binding generation and type checking.' ); // 5. Strip leading blank lines, then inject frontmatter @@ -113,17 +115,19 @@ content = ` - vc-spec.md (relative, same dir in source repo) → ../guides/authentication/verifiable-credentials.md\n` + `Other changes from source:\n` + ` - \`# The Internet Identity Specification\` H1 removed (Starlight renders frontmatter title as H1)\n` + - ` - \`{IICandidInterface}\` replaced with inlined content from src/internet_identity/internet_identity.did\n` + + ` - \`{IICandidInterface}\` replaced with download link to /references/internet-identity.did\n` + `-->\n` + `\n`; writeFileSync(TARGET, content); console.log(`Written: ${TARGET}`); +// Copy the .did file to public/references/ +copyFileSync(SOURCE_DID, TARGET_DID); +console.log(`Written: ${TARGET_DID}`); + // Warn about any remaining absolute docs links that weren't rewritten. -// Strip code blocks first — URLs inside .did comments are intentionally preserved. -const prose = content.replace(/```[\s\S]*?```/g, ''); -const remaining = [...prose.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] +const remaining = [...content.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] .map(m => m[0]); const unique = [...new Set(remaining)]; if (unique.length) { From f93cde7db8eabc443183e1a802acd12df4af9ab5 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 07:42:23 +0200 Subject: [PATCH 07/14] feat(sync-ii-spec): convert Mermaid sequence diagrams to PlantUML Site uses remarkPlantUML; Mermaid blocks render as raw text. The sync script now converts sequenceDiagram blocks to PlantUML automatically. Conversion rules: participant alias/name flipped and quoted,
becomes \n, sequenceDiagram keyword stripped, wrapped in @startuml/@enduml. Adds a validation check that exits non-zero if any mermaid blocks remain after conversion (catches unsupported diagram types added upstream). --- docs/references/internet-identity-spec.md | 27 ++++++----- scripts/sync-ii-spec.mjs | 56 +++++++++++++++++++++-- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index 929c24d8..b15d31a2 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -119,11 +119,11 @@ The Internet Identity service frontend also manages an _identity frontend delega This section describes the Internet Identity service from the point of view of a client application frontend. -```mermaid -sequenceDiagram - participant C as Client Application - participant II as Internet Identity - participant U as User +```plantuml +@startuml + participant "Client Application" as C + participant "Internet Identity" as II + participant "User" as U C ->> C: Generate session key pair C ->> II: Open Internet Identity window / tab @@ -132,7 +132,8 @@ sequenceDiagram II ->> U: Prompt authentication U ->> II: Authenticate II ->> II: Sign delegation - II ->> C: Signed delegation
`authorize-client-success` message + II ->> C: Signed delegation\n`authorize-client-success` message +@enduml ``` 1. The client application frontend creates a session key pair (e.g., Ed25519). @@ -259,12 +260,12 @@ In order for Internet Identity to accept the `derivationOrigin` the origin of th Internet Identity will fetch the `/.well-known/ii-alternative-origins` file from the derivation origin and check if the alternative origin is listed in the file. -```mermaid -sequenceDiagram - participant U as User - participant A as App Alternative Origin - participant IF as II Frontend - participant D as App Derivation Origin +```plantuml +@startuml + participant "User" as U + participant "App Alternative Origin" as A + participant "II Frontend" as IF + participant "App Derivation Origin" as D D->>D: Adds /.well-known/ii-alternative-origins endpoint D->>D: Adds "Alternative Origin" in /.well-known/ii-alternative-origins response U->>A: User accesses Alternative Origin @@ -280,6 +281,7 @@ sequenceDiagram else is NOT present IF->>U: Invalid origin request end +@enduml ``` Requirements: @@ -732,5 +734,6 @@ Link replacements from source (source used absolute/relative paths pointing outs Other changes from source: - `# The Internet Identity Specification` H1 removed (Starlight renders frontmatter title as H1) - `{IICandidInterface}` replaced with download link to /references/internet-identity.did + - Mermaid sequenceDiagram blocks converted to PlantUML (site uses remarkPlantUML, not Mermaid) --> diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs index 768ec2bf..1f2f4bd5 100755 --- a/scripts/sync-ii-spec.mjs +++ b/scripts/sync-ii-spec.mjs @@ -6,9 +6,14 @@ // - Strip MDX import lines // - Remove the H1 heading (Starlight renders the frontmatter title as H1) // - Rewrite absolute / relative links that point outside this site +// - Convert Mermaid sequenceDiagram blocks to PlantUML (site uses remarkPlantUML) // - Replace component with a download link to internet-identity.did // - Copy internet_identity.did to public/references/internet-identity.did // +// Validation (exits non-zero on failure): +// - Unhandled absolute internetcomputer.org/docs links +// - Unconverted Mermaid blocks (unsupported diagram type added upstream) +// // Usage: node scripts/sync-ii-spec.mjs // or: npm run sync:ii-spec @@ -80,7 +85,31 @@ for (const [old, replacement] of linkMap) { content = content.replaceAll(old, replacement); } -// 4. Replace the component with a download link to the .did file +// 4. Convert Mermaid sequenceDiagram blocks to PlantUML +function convertMermaidSequence(body) { + const lines = body.split('\n'); + const out = ['@startuml']; + for (const line of lines) { + if (line.trim() === 'sequenceDiagram') continue; + // participant X as Long Name → participant "Long Name" as X + const pm = line.match(/^(\s*)participant\s+(\S+)\s+as\s+(.+)$/); + if (pm) { + out.push(`${pm[1]}participant "${pm[3].trim()}" as ${pm[2]}`); + continue; + } + //
→ \n for multiline message labels + out.push(line.replace(//gi, '\\n')); + } + while (out.length > 1 && out[out.length - 1].trim() === '') out.pop(); + out.push('@enduml'); + return out.join('\n'); +} + +content = content.replace(/^```mermaid\n([\s\S]*?)^```/gm, (_, body) => { + return '```plantuml\n' + convertMermaidSequence(body) + '\n```'; +}); + +// 6. Replace the component with a download link to the .did file content = content.replace( '{IICandidInterface}', 'The complete Candid interface definition is available at [`internet-identity.did`](/references/internet-identity.did).' + @@ -88,7 +117,7 @@ content = content.replace( ' and can be used for binding generation and type checking.' ); -// 5. Strip leading blank lines, then inject frontmatter +// 7. Strip leading blank lines, then inject frontmatter content = content.replace(/^\n+/, ''); content = `---\n` + @@ -99,7 +128,7 @@ content = `---\n\n` + content; -// 6. Append the link-adaptation log and Upstream comment +// 8. Append the link-adaptation log and Upstream comment content = content.trimEnd() + '\n' + @@ -116,6 +145,7 @@ content = `Other changes from source:\n` + ` - \`# The Internet Identity Specification\` H1 removed (Starlight renders frontmatter title as H1)\n` + ` - \`{IICandidInterface}\` replaced with download link to /references/internet-identity.did\n` + + ` - Mermaid sequenceDiagram blocks converted to PlantUML (site uses remarkPlantUML, not Mermaid)\n` + `-->\n` + `\n`; @@ -126,6 +156,8 @@ console.log(`Written: ${TARGET}`); copyFileSync(SOURCE_DID, TARGET_DID); console.log(`Written: ${TARGET_DID}`); +let failed = false; + // Warn about any remaining absolute docs links that weren't rewritten. const remaining = [...content.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] .map(m => m[0]); @@ -133,7 +165,23 @@ const unique = [...new Set(remaining)]; if (unique.length) { console.warn('\nWARNING: Unhandled absolute links — add them to the linkMap:'); unique.forEach(l => console.warn(` ${l}`)); + failed = true; +} + +// Warn about unconverted Mermaid blocks (unsupported diagram type added upstream). +// Only sequenceDiagram is handled; anything else needs a new conversion case. +const mermaidBlocks = [...content.matchAll(/^```mermaid$/gm)]; +if (mermaidBlocks.length) { + console.warn('\nWARNING: Unconverted Mermaid blocks remain — add conversion support in convertMermaidSequence:'); + for (const m of mermaidBlocks) { + const line = content.slice(0, m.index).split('\n').length; + console.warn(` line ${line}`); + } + failed = true; +} + +if (failed) { process.exit(1); } else { - console.log('\nAll absolute links adapted. Run `npm run build` to verify.'); + console.log('\nAll checks passed. Run `npm run build` to verify.'); } From 11cc64d280f6a6abdcc90a1f77ca1ad5bb6765fe Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 07:47:38 +0200 Subject: [PATCH 08/14] fix(sync-ii-spec): omit @startuml/@enduml so remarkPlantUML applies brand skin The plugin only injects the SKIN when content does not already start with @startuml. Other diagrams in the docs omit the wrapper and let the plugin add it. Converted diagrams now follow the same pattern. --- docs/references/internet-identity-spec.md | 4 ---- scripts/sync-ii-spec.mjs | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index b15d31a2..384d4c2c 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -120,7 +120,6 @@ The Internet Identity service frontend also manages an _identity frontend delega This section describes the Internet Identity service from the point of view of a client application frontend. ```plantuml -@startuml participant "Client Application" as C participant "Internet Identity" as II participant "User" as U @@ -133,7 +132,6 @@ This section describes the Internet Identity service from the point of view of a U ->> II: Authenticate II ->> II: Sign delegation II ->> C: Signed delegation\n`authorize-client-success` message -@enduml ``` 1. The client application frontend creates a session key pair (e.g., Ed25519). @@ -261,7 +259,6 @@ In order for Internet Identity to accept the `derivationOrigin` the origin of th Internet Identity will fetch the `/.well-known/ii-alternative-origins` file from the derivation origin and check if the alternative origin is listed in the file. ```plantuml -@startuml participant "User" as U participant "App Alternative Origin" as A participant "II Frontend" as IF @@ -281,7 +278,6 @@ Internet Identity will fetch the `/.well-known/ii-alternative-origins` file from else is NOT present IF->>U: Invalid origin request end -@enduml ``` Requirements: diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs index 1f2f4bd5..9e2061f9 100755 --- a/scripts/sync-ii-spec.mjs +++ b/scripts/sync-ii-spec.mjs @@ -88,7 +88,7 @@ for (const [old, replacement] of linkMap) { // 4. Convert Mermaid sequenceDiagram blocks to PlantUML function convertMermaidSequence(body) { const lines = body.split('\n'); - const out = ['@startuml']; + const out = []; for (const line of lines) { if (line.trim() === 'sequenceDiagram') continue; // participant X as Long Name → participant "Long Name" as X @@ -100,8 +100,7 @@ function convertMermaidSequence(body) { //
→ \n for multiline message labels out.push(line.replace(//gi, '\\n')); } - while (out.length > 1 && out[out.length - 1].trim() === '') out.pop(); - out.push('@enduml'); + while (out.length > 0 && out[out.length - 1].trim() === '') out.pop(); return out.join('\n'); } From 9ed848e844d4e2034a10a91e27c8a107ec2d5667 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 12:47:51 +0200 Subject: [PATCH 09/14] feat(sync-ii-spec): sync vc-spec.md as docs/references/verifiable-credentials-spec.md Extends the sync script to also produce a normative reference page from .sources/internetidentity/docs/vc-spec.md. Rewrites 5 stale portal links at sync time; upstream fix tracked in dfinity/internet-identity#3889. The ii-spec link to vc-spec.md now resolves to the reference page instead of the implementation guide. The guide's "Next steps" section updated to link to the new reference page rather than the raw GitHub file. --- .../authentication/verifiable-credentials.md | 2 +- docs/references/internet-identity-spec.md | 4 +- .../references/verifiable-credentials-spec.md | 416 ++++++++++++++++++ scripts/sync-ii-spec.mjs | 101 ++++- 4 files changed, 509 insertions(+), 14 deletions(-) create mode 100644 docs/references/verifiable-credentials-spec.md diff --git a/docs/guides/authentication/verifiable-credentials.md b/docs/guides/authentication/verifiable-credentials.md index 6030a78f..c769c600 100644 --- a/docs/guides/authentication/verifiable-credentials.md +++ b/docs/guides/authentication/verifiable-credentials.md @@ -395,7 +395,7 @@ The VC protocol provides the following privacy guarantees: ## Next steps -- Read the [VC specification](https://github.com/dfinity/internet-identity/blob/main/docs/vc-spec.md) for the full protocol details. +- Read the [VC specification](../../references/verifiable-credentials-spec.md) for the full protocol details. - Explore the [verifiable credentials playground](https://github.com/dfinity/vc-playground) for issuer and relying party reference implementations. - Review [Internet Identity integration](internet-identity.md) for authentication setup. - See the [Internet Identity specification](../../references/internet-identity-spec.md) for alternative derivation origins and canister signature details. diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index 384d4c2c..5ecd16b0 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -651,7 +651,7 @@ Must be called immediately after `openid_prepare_delegation`. ### Verifiable Credentials Protocol -See [here](../guides/authentication/verifiable-credentials.md) for the specification of the verifiable credentials protocol. +See [here](./verifiable-credentials-spec.md) for the specification of the verifiable credentials protocol. ### HTTP gateway protocol @@ -726,7 +726,7 @@ Link replacements from source (source used absolute/relative paths pointing outs - internetcomputer.org [/docs]/current/references/ic-interface-spec/#system-api-inspect-message → ./ic-interface-spec/canister-interface.md#system-api-inspect-message - internetcomputer.org [/docs]/current/references/http-gateway-protocol-spec → ./http-gateway-protocol-spec.md - internetcomputer.org [/docs]/current/developer-docs/web-apps/custom-domains/using-custom-domains → ../guides/frontends/custom-domains.md - - vc-spec.md (relative, same dir in source repo) → ../guides/authentication/verifiable-credentials.md + - vc-spec.md (relative, same dir in source repo) → ./verifiable-credentials-spec.md Other changes from source: - `# The Internet Identity Specification` H1 removed (Starlight renders frontmatter title as H1) - `{IICandidInterface}` replaced with download link to /references/internet-identity.did diff --git a/docs/references/verifiable-credentials-spec.md b/docs/references/verifiable-credentials-spec.md new file mode 100644 index 00000000..0d7c26ae --- /dev/null +++ b/docs/references/verifiable-credentials-spec.md @@ -0,0 +1,416 @@ +--- +title: "Verifiable Credentials specification" +description: "Normative specification of the ICP Verifiable Credentials protocol: Issuer Candid API and Identity Provider window.postMessage interface." +sidebar: + order: 15 +--- + +## Issuer API + +This section describes the (Candid) interface to be implemented by an issuer of verifiable credentials on the IC. +This interface is used by the II-canister during attribute sharing flow (cf. [flow description](https://github.com/dfinity/wg-identity-authentication/blob/d2664795afe9cea40386804bdb1259a47e34540d/topics/attribute-sharing.md)) +An example implementation of the interface is given in [demos/vc_issuer](https://github.com/dfinity/internet-identity/tree/main/demos/vc_issuer). + +The Candid interface is as follows, and the subsequent sections describe the +services and the corresponding messages in more detail. + +```candid +// Specification of a requested credential. +type CredentialSpec = record { + credential_type : text; + /// arguments are optional, and specific to the credential_type + arguments : opt vec record { text; ArgumentValue }; +}; +type ArgumentValue = variant { "Int" : int32; String : text }; + +/// Types for ICRC-21 consent message, cf. +/// https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_21_consent_msg.md +type Icrc21ConsentInfo = record { consent_message : text; language : text }; +type Icrc21ConsentPreferences = record { language : text }; +type Icrc21Error = variant { + GenericError : record { description : text; error_code : nat }; + UnsupportedCanisterCall : Icrc21ErrorInfo; + ConsentMessageUnavailable : Icrc21ErrorInfo; +}; +type Icrc21ErrorInfo = record { description : text }; +type Icrc21VcConsentMessageRequest = record { + preferences : Icrc21ConsentPreferences; + credential_spec : CredentialSpec; +}; + +/// Types for `prepare_credential`. +type PrepareCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; +}; +type SignedIdAlias = record { + credential_jws : text; +}; +type PreparedCredentialData = record { prepared_context : opt vec nat8 }; + +/// Types for `get_credential`. +type GetCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; + prepared_context : opt blob; +}; +type IssuedCredentialData = record { vc_jws : text }; + +type IssueCredentialError = variant { + /// The caller is not known to the issuer. Caller should register first with the issuer before retrying. + UnknownSubject : text; + /// The caller is not authorized to obtain the requested credential. Caller requested a credential + /// for a different principal, or the issuer does not have sufficient knowledge about the caller + /// to issue the requested credential. + UnauthorizedSubject : text; + /// The id_alias credential provided by the identity provider is invalid. + InvalidIdAlias : text; + /// The issuer does not issue credentials described in the credential spec. + UnsupportedCredentialSpec : text; + /// Internal errors, indicate malfunctioning of the issuer. + SignatureNotFound : text; + Internal : text; +}; + +/// Types for `derivation_origin`. +type DerivationOriginRequest = record { + frontend_hostname : text; +}; +type DerivationOriginData = record { origin : text }; +type DerivationOriginError = variant { + Internal : text; + UnsupportedOrigin : text; +}; + +service: { + derivation_origin : (DerivationOriginRequest) -> + (variant {Ok: DerivationOriginData; Err: DerivationOriginError}); + vc_consent_message : (Icrc21VcConsentMessageRequest) -> + (variant { Ok : Icrc21ConsentInfo; Err : Icrc21Error;}); + prepare_credential : (PrepareCredentialRequest) -> + (variant { Ok : PreparedCredentialData; Err : IssueCredentialError;}); + get_credential : (GetCredentialRequest) -> + (variant { Ok : IssuedCredentialData; Err : IssueCredentialError;}) query; +} +``` + +### 1: Consent Message + +In the attribute sharing flow a user must approve the issuance of a verifiable +credential by an issuer, and this happens by approving a human-readable consent message from the issuer. + +Identity provider uses a VC-extension of [ICRC-21](https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_21_consent_msg.md), and requests the consent message via `Icrc21VcConsentMessageRequest`, +Upon successful response identity provider displays the consent message from `Icrc21ConsentInfo` to the user. + +### 2: Derivation Origin + +The issuer must implement also `derivation_origin`-API, which allows for taking the advantage +of the [Alternative Derivation Origins](./internet-identity-spec.md#alternative-frontend-origins)-feature. +`derivation_origin` is called by the identity provider to obtain an URL to be used as the derivation origin +for user's principal. If an issuer doesn't use the _Alternative Derivation Origins_-feature, +the function should return just the default value, namely the canister's URL: `https://.icp0.io`. + +```candid +service: { + derivation_origin : (DerivationOriginRequest) -> + (variant {Ok: DerivationOriginData; Err: DerivationOriginError}); +} + +type DerivationOriginRequest = record { + frontend_hostname : text; +}; +type DerivationOriginData = record { origin : text }; +type DerivationOriginError = variant { + Internal : text; + UnsupportedOrigin : text; +}; +``` + +Please note that the returned derivation origin is subject to verification via +`.well-known/ii-alternative-origins`, as described in the [feature-description](./internet-identity-spec.md#alternative-frontend-origins). + +### 3: Prepare Credential + +Preparation of a credential involves checking the validity of the request, +and upon success, preparation of the actual credential requested by the user. + +```candid +service : { + prepare_credential : (PrepareCredentialRequest) -> + (variant { Ok : PreparedCredentialData; Err : IssueCredentialError;}); +}; + +type PrepareCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; +}; + +type SignedIdAlias = record { + credential_jws : text; +}; +type PreparedCredentialData = record { prepared_context : opt vec nat8 }; + +type CredentialSpec = record { + credential_type : text; + /// arguments are optional, and specific to the credential_type + arguments : opt vec record { text; ArgumentValue }; +}; +type ArgumentValue = variant { "Int" : int32; String : text }; +``` + +Specifically, the issuer checks via `signed_id_alias.credential_jws` that user identified by its `sub` claim on the +issuer side has a valid `has_id_alias` principal for the purpose of attribute sharing, and that the credential +described by `credential_spec` does apply to the `caller`. When these checks are successful, the issuer +prepares and returns a context in `PreparedCredentialData.prepared_context` (if any). The returned prepared context is then +passed back to the issuer in a subsequent `get_credential`-call (see below). +This call must be authenticated, i.e. the sender must match the principal for which the credential is requested. + +**NOTE:** +The value of `prepared_context` is basically used to transfer information between `prepare_credential` +and `get_credential` steps, and it is totally up to the issuer to decide on the content of +that field. That is, the issuer creates `prepared_context`, and is the only entity that +consumes it. For example, when using [canister signatures](./ic-interface-spec/index.md#canister-signatures) +the context contains a time-stamped yet unsigned VC, for which the canister signature will be +available only at `get_credential`-call. + +**NOTE:** +The convention is to sign the credential with the domain separator `"iccs_verifiable_credential"` as an array of bytes. This is achieved by adding a prefix that consists of a 1-byte lenth of the domain separator, followed by the actual separator. See [vc_signing_input_hash](https://docs.rs/ic-verifiable-credentials/1.0.1/ic_verifiable_credentials/fn.vc_signing_input_hash.html). + +### 4: Get Credential + +`get_credential`-service issues the actual credential requested by the user. + +```candid +service : { + get_credential : (GetCredentialRequest) -> + (variant { Ok : IssuedCredentialData; Err : IssueCredentialError;}) query; +}; + +type GetCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; + prepared_context : opt blob; +}; +type IssuedCredentialData = record { vc_jws : text }; +``` + +`GetCredentialRequest` should contain the same parameters as `PrepareCredentialRequest`, plus the +`prepared_context`-value returned by `prepare_credential`, if any. +The issuer performs the same checks as during the `prepare_credential`-call, +plus verify that `prepared_context` is consistent with the other parameters. +This call must be authenticated, i.e. the sender must match the principal for which the credential is requested. + +Upon successful checks, issuer returns the signed credential in JWS-format. + +### Recommended convention connecting credential specification with the returned credentials. + +Service discovery and syntax of the claims in the returned credentials are out of scope +of this spec (of the MVP service). However, an issuer may follow the convention below, +for an easier verification of the returned credentials. + +Given a credential spec like + +```json + "credentialSpec": { + "credentialType": "SomeVerifiedProperty", + "arguments": { + "argument_1": "value_1", + "another_argument": 42, + } +``` + +the returned JWT should contain in `credentialSubject` a property +named by the value of `credentialType` from the spec, with key-value +entries listing the arguments from the spec, namely + +```json + "SomeVerifiedProperty": { + "argument_1": "value_1", + "another_argument": 42, + } +``` + +For example, for `VerifiedAdult`-credential we'd use the following credential spec + +```json + "credentialSpec": { + "credentialType": "VerifiedAdult", + "arguments": { + "minAge": 18, + } +``` + +and a compliant issuer would issue a VC that contains `credentialSubject` with the property + +```json + "VerifiedAdult": { + "minAge": 18, + } +``` + +## Identity Provider API + +This section describes the [_`window.postMessage()`_](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)-interface implemented by the identity provider (Internet Identity). +This interface is used by a Relying Party during attribute sharing flow (cf. [flow description](https://github.com/dfinity/wg-identity-authentication/blob/main/topics/attribute-sharing.md)) + +### 1: Load II in a new window + +The II window needs to be opened with the URL path `/vc-flow`. + +After opening the II window, II will load and notify `window.opener` with the following JSON-RPC notification: + +```json +{ + "jsonrpc": "2.0", + "method": "vc-flow-ready" +} +``` + +### 2: Request a VC + +After receiving the notification that II is ready, the relying party can request a VC by sending the following JSON-RPC request: + +- Method: `request_credential` +- Params: + - `issuer`: An issuer that the relying party trusts. It has the following properties: + - `origin`: The front-end origin of the issuer. If this value is different from the value returned from the `derivation_origin` canister call, then the `origin` must be a valid alternative origin as per the [Alternative Frontend Origins](./internet-identity-spec.md#alternative-frontend-origins)-feature. + - `canisterId`: The canister id of the issuer canister (i.e. the one, that implements the candid issuer API as defined above). + - `credentialSpec`: The spec of the credential that the relying party wants to request from the issuer. + - `credentialType`: The type of the requested credential. + - `arguments`: (optional) A map with arguments specific to the requested credentials. It maps string keys to values that must be either strings or integers. + - `credentialSubject`: The subject of the credential as known to the relying party. Internet Identity will use this principal to ensure that the flow is completed using the matching identity. + - `derivationOrigin`: (optional) The origin that should be used for principal derivation (instead of the client origin) during the verification of `credentialSubject` (applicable if the relying party + uses the [Alternative Frontend Origins](./internet-identity-spec.md#alternative-frontend-origins)-feature). + +#### Examples + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "request_credential", + "params": { + "issuer": { + "origin": "https://employment-info.com", + "canisterId": "rwlgt-iiaaa-aaaaa-aaaaa-cai" + }, + "credentialSpec": { + "credentialType": "VerifiedEmployee", + "arguments": { + "employerName": "XYZ Ltd." + } + }, + "credentialSubject": "2mdal-aedsb-hlpnv-qu3zl-ae6on-72bt5-fwha5-xzs74-5dkaz-dfywi-aqe" + } +} +``` + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "request_credential", + "params": { + "issuer": { + "origin": "https://kyc-star.com", + "canisterId": "rdmx6-jaaaa-aaaaa-aaadq-cai" + }, + "credentialSpec": { + "credentialType": "VerifiedAdult", + "arguments": { + "minAge": 21 + } + }, + "credentialSubject": "s33qc-ctnp5-ubyz4-kubqo-p2tem-he4ls-6j23j-hwwba-37zbl-t2lv3-pae", + "derivationOrigin": "https://vt36r-2qaaa-aaaad-aad5a-cai.icp0.io" + } +} +``` + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "request_credential", + "params": { + "issuer": { + "origin": "https://kyc-resident-info.org", + "canisterId": "rwlgt-iiaaa-aaaaa-aaaaa-cai" + }, + "credentialSpec": { + "credentialType": "VerifiedResident", + "arguments": { + "countryName": "Panama", + "countryAlpha2": "PA" + } + }, + "credentialSubject": "cpehq-54hef-odjjt-bockl-3ldtg-jqle4-ysi5r-6bfah-v6lsa-xprdv-pqe" + } +} +``` + +### 3: Get a Response + +#### Receive a Verifiable Presentation + +After the user has successfully completed the flow, Internet Identity will respond with the following JSON-RPC response: + +- `verifiablePresentation`: The JWT based verifiable presentation containing two credentials: + 1. A verifiable credential issued by Internet Identity that relates the requested `credentialSubject` to another alias principal. + 2. A verifiable credential issued by the requested issuer to the alias principal. + +**NOTE:** the order of the credentials in the presentation should match the list above, +i.e. the credential that relates the subject to alias principal should come first. + +##### Example + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "verifiablePresentation": "eyJQ..." + } +} +``` + +An example of such a verifiable presentation can be found [here](https://www.w3.org/TR/vc-data-model/#example-jwt-payload-of-a-jwt-based-verifiable-presentation-non-normative). + +#### Receive an Error Message + +If the flow failed for any reason, Internet Identity returns an error. +For privacy protection, the error does not give any details on the root cause of the failure. +(This may change in the future, as some failures can be reported to the relying +party without impacting user's privacy.) + +##### Example + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "error": { + "version": "1", + "code": "UNKNOWN" + } +} +``` + +#### Interaction Model + +Given the interactive nature of the flow, the relying party should not expect to receive a response immediately. Instead, the relying party should wait for one of the following events: + +- II sends a JSON-RPC response as described above. +- II sends a JSON-RPC error response. +- The user closes the II window. This should be treated as an error. + +The relying party may also close the II window after some timeout. The user should then be notified by the relying party that the flow failed. + + + diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs index 9e2061f9..5b91557c 100755 --- a/scripts/sync-ii-spec.mjs +++ b/scripts/sync-ii-spec.mjs @@ -1,8 +1,10 @@ #!/usr/bin/env node -// Syncs the Internet Identity specification from .sources/internetidentity into -// docs/references/internet-identity-spec.md and public/references/internet-identity.did. +// Syncs specs from .sources/internetidentity: +// - docs/ii-spec.mdx → docs/references/internet-identity-spec.md +// - docs/vc-spec.md → docs/references/verifiable-credentials-spec.md +// - src/internet_identity/internet_identity.did → public/references/internet-identity.did // -// Transformations applied: +// Transformations applied to ii-spec: // - Strip MDX import lines // - Remove the H1 heading (Starlight renders the frontmatter title as H1) // - Rewrite absolute / relative links that point outside this site @@ -10,9 +12,13 @@ // - Replace component with a download link to internet-identity.did // - Copy internet_identity.did to public/references/internet-identity.did // +// Transformations applied to vc-spec: +// - Remove the H1 heading (Starlight renders the frontmatter title as H1) +// - Rewrite absolute links that point to the retired portal +// // Validation (exits non-zero on failure): -// - Unhandled absolute internetcomputer.org/docs links -// - Unconverted Mermaid blocks (unsupported diagram type added upstream) +// - Unhandled absolute internetcomputer.org/docs links (both specs) +// - Unconverted Mermaid blocks (ii-spec only) // // Usage: node scripts/sync-ii-spec.mjs // or: npm run sync:ii-spec @@ -20,10 +26,12 @@ import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs'; import { execSync } from 'node:child_process'; -const SOURCE_MDX = '.sources/internetidentity/docs/ii-spec.mdx'; -const SOURCE_DID = '.sources/internetidentity/src/internet_identity/internet_identity.did'; -const TARGET = 'docs/references/internet-identity-spec.md'; -const TARGET_DID = 'public/references/internet-identity.did'; +const SOURCE_MDX = '.sources/internetidentity/docs/ii-spec.mdx'; +const SOURCE_VC = '.sources/internetidentity/docs/vc-spec.md'; +const SOURCE_DID = '.sources/internetidentity/src/internet_identity/internet_identity.did'; +const TARGET = 'docs/references/internet-identity-spec.md'; +const TARGET_VC = 'docs/references/verifiable-credentials-spec.md'; +const TARGET_DID = 'public/references/internet-identity.did'; if (!existsSync(SOURCE_MDX)) { console.error( @@ -33,6 +41,14 @@ if (!existsSync(SOURCE_MDX)) { process.exit(1); } +if (!existsSync(SOURCE_VC)) { + console.error( + `ERROR: ${SOURCE_VC} not found.\n` + + 'Run: git submodule update --init --depth 1 .sources/internetidentity' + ); + process.exit(1); +} + const version = execSync('git -C .sources/internetidentity rev-parse --short HEAD') .toString().trim(); console.log(`Syncing II spec from dfinity/internet-identity@${version}...`); @@ -77,7 +93,7 @@ const linkMap = [ ], [ '](vc-spec.md)', - '](../guides/authentication/verifiable-credentials.md)', + '](./verifiable-credentials-spec.md)', ], ]; @@ -140,7 +156,7 @@ content = ` - internetcomputer.org [/docs]/current/references/ic-interface-spec/#system-api-inspect-message → ./ic-interface-spec/canister-interface.md#system-api-inspect-message\n` + ` - internetcomputer.org [/docs]/current/references/http-gateway-protocol-spec → ./http-gateway-protocol-spec.md\n` + ` - internetcomputer.org [/docs]/current/developer-docs/web-apps/custom-domains/using-custom-domains → ../guides/frontends/custom-domains.md\n` + - ` - vc-spec.md (relative, same dir in source repo) → ../guides/authentication/verifiable-credentials.md\n` + + ` - vc-spec.md (relative, same dir in source repo) → ./verifiable-credentials-spec.md\n` + `Other changes from source:\n` + ` - \`# The Internet Identity Specification\` H1 removed (Starlight renders frontmatter title as H1)\n` + ` - \`{IICandidInterface}\` replaced with download link to /references/internet-identity.did\n` + @@ -179,6 +195,69 @@ if (mermaidBlocks.length) { failed = true; } +// --- vc-spec sync --- +console.log(`\nSyncing VC spec from dfinity/internet-identity@${version}...`); + +let vcContent = readFileSync(SOURCE_VC, 'utf8'); + +// 1. Strip the H1 (Starlight renders it from frontmatter) +vcContent = vcContent.replace(/^# II Verifiable Credential Spec \(MVP\)\n\n/m, ''); + +// 2. Rewrite retired portal links to internal paths +// Note: upstream still uses internetcomputer.org/docs/current/ — tracked in +// https://github.com/dfinity/internet-identity/issues/3889 +const vcLinkMap = [ + [ + 'https://internetcomputer.org/docs/current/references/ii-spec#alternative-frontend-origins', + './internet-identity-spec.md#alternative-frontend-origins', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures', + './ic-interface-spec/index.md#canister-signatures', + ], +]; + +for (const [old, replacement] of vcLinkMap) { + vcContent = vcContent.replaceAll(old, replacement); +} + +// 3. Strip leading blank lines, then inject frontmatter +vcContent = vcContent.replace(/^\n+/, ''); +vcContent = + `---\n` + + `title: "Verifiable Credentials specification"\n` + + `description: "Normative specification of the ICP Verifiable Credentials protocol: Issuer Candid API and Identity Provider window.postMessage interface."\n` + + `sidebar:\n` + + ` order: 15\n` + + `---\n\n` + + vcContent; + +// 4. Append the link-adaptation log and Upstream comment +vcContent = + vcContent.trimEnd() + + '\n' + + `\n\n` + + `\n`; + +writeFileSync(TARGET_VC, vcContent); +console.log(`Written: ${TARGET_VC}`); + +// Validate vc-spec for unhandled portal links +const vcRemaining = [...vcContent.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] + .map(m => m[0]); +const vcUnique = [...new Set(vcRemaining)]; +if (vcUnique.length) { + console.warn('\nWARNING: Unhandled absolute links in vc-spec — add them to vcLinkMap:'); + vcUnique.forEach(l => console.warn(` ${l}`)); + failed = true; +} + if (failed) { process.exit(1); } else { From a1b1b4f52bf4814a5e7a6a2edc467c04afbd8a97 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 12:50:43 +0200 Subject: [PATCH 10/14] docs(claude): update ii sync instructions and workflow for vc-spec - Submodule table: add docs/vc-spec.md as a synced source - Extra checks row: document vcLinkMap, clarify the "only bump if synced docs changed" policy, and note the workflow skips unrelated code bumps - Link adaptation section: cover verifiable-credentials-spec.md and note that the vc-spec rewrites become harmless once dfinity/internet-identity#3889 is resolved - Workflow: add docs/vc-spec.md to the changed-files filter, stage verifiable-credentials-spec.md in the commit step, and update PR body --- .github/workflows/sync-ii-spec.yml | 10 ++++++---- AGENTS.md | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sync-ii-spec.yml b/.github/workflows/sync-ii-spec.yml index 76cc828b..6b275f79 100644 --- a/.github/workflows/sync-ii-spec.yml +++ b/.github/workflows/sync-ii-spec.yml @@ -62,10 +62,11 @@ jobs: # Only proceed if the spec files themselves changed CHANGED=$(git -C .sources/internetidentity diff --name-only "${CURRENT}..${LATEST}" -- \ docs/ii-spec.mdx \ + docs/vc-spec.md \ src/internet_identity/internet_identity.did) if [ -z "$CHANGED" ]; then - echo "No changes to ii-spec.mdx or internet_identity.did. Skipping." + echo "No changes to ii-spec.mdx, vc-spec.md, or internet_identity.did. Skipping." echo "needed=false" >> $GITHUB_OUTPUT else echo "Spec files changed:" @@ -110,7 +111,7 @@ jobs: BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" git checkout -b "$BRANCH" - git add .sources/internetidentity docs/references/internet-identity-spec.md public/references/internet-identity.did + git add .sources/internetidentity docs/references/internet-identity-spec.md docs/references/verifiable-credentials-spec.md public/references/internet-identity.did git commit -m "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" git push -u origin "$BRANCH" @@ -127,18 +128,19 @@ jobs: [ -n "$f" ] && echo "- \`$f\`" done <<< "$CHANGED" echo "" - echo "- Ran \`npm run sync:ii-spec\` — regenerated \`docs/references/internet-identity-spec.md\`" + echo "- Ran \`npm run sync:ii-spec\` — regenerated \`docs/references/internet-identity-spec.md\` and \`docs/references/verifiable-credentials-spec.md\`" echo "- Build passed ✓" echo "" echo "## Checklist" echo "" echo "- [ ] Review the diff to \`docs/references/internet-identity-spec.md\` for content changes" + echo "- [ ] Review the diff to \`docs/references/verifiable-credentials-spec.md\` for content changes" echo "- [ ] Check for new absolute \`internetcomputer.org\` link patterns (script exits non-zero if any were missed)" echo "- [ ] Verify any renamed or restructured sections are reflected correctly" echo "" echo "## Sync recommendation" echo "" - echo "\`sync from dfinity/internet-identity — docs/ii-spec.mdx, src/internet_identity/internet_identity.did\`" + echo "\`sync from dfinity/internet-identity — docs/ii-spec.mdx, docs/vc-spec.md, src/internet_identity/internet_identity.did\`" } > /tmp/pr-body.md gh pr create \ diff --git a/AGENTS.md b/AGENTS.md index b06155eb..c18d1303 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -217,7 +217,7 @@ For current release hashes, see `.sources/VERSIONS`. | `.sources/dotskills` | `vincentkoc/dotskills` | `main` | Technical documentation skill (AGPL-3.0 — kept as submodule to avoid license mixing) | | `.agents/skills/icp-brand-voice` | n/a — lives directly in this repo | n/a | ICP / DFINITY brand voice: positioning, vocabulary, banned terms, voice attributes | | `.agents/skills/icp-brand-design` | n/a — lives directly in this repo | n/a | ICP / DFINITY brand design: color tokens, typography, layout, components, accessibility | -| `.sources/internetidentity` | `dfinity/internet-identity` | `main` | Internet Identity spec (`docs/ii-spec.mdx`), Candid interface (`src/internet_identity/internet_identity.did`) | +| `.sources/internetidentity` | `dfinity/internet-identity` | `main` | Internet Identity spec (`docs/ii-spec.mdx`), VC spec (`docs/vc-spec.md`), Candid interface (`src/internet_identity/internet_identity.did`) | ### Submodule initialization @@ -292,7 +292,7 @@ EOF | `candid` | Check for spec changes affecting the Candid reference or type-mapping examples | | `response-verification` | Check for API changes affecting certified variables patterns | | `dotskills` | Check if the `technical-documentation` skill changed in ways that affect review criteria | -| `internetidentity` | Run `npm run sync:ii-spec` — the script handles all link rewrites, Candid inlining, and frontmatter. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` in `scripts/sync-ii-spec.mjs`. The **Sync II spec** workflow (`.github/workflows/sync-ii-spec.yml`) can be triggered manually from GitHub Actions; it only opens a PR when `docs/ii-spec.mdx` or `internet_identity.did` actually changed. | +| `internetidentity` | Run `npm run sync:ii-spec` — the script syncs both `ii-spec.mdx` → `docs/references/internet-identity-spec.md` and `vc-spec.md` → `docs/references/verifiable-credentials-spec.md`, handling all link rewrites, Candid inlining, and frontmatter in one pass. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` (ii-spec) or `vcLinkMap` (vc-spec) in `scripts/sync-ii-spec.mjs`. The **Sync II spec** workflow (`.github/workflows/sync-ii-spec.yml`) can be triggered manually from GitHub Actions; it only opens a PR when `docs/ii-spec.mdx`, `docs/vc-spec.md`, or `internet_identity.did` actually changed — bumping for unrelated upstream code commits is not needed. | | `chain-fusion-signer` | Check for changed canister IDs, API methods, or key derivation patterns | | `papi` | Check for changed payment interface or cycle cost model | | `ic-pub-key` | Check for changed CLI flags or commands | @@ -317,7 +317,7 @@ EOF ``` 4. Run `npm run build` to confirm no broken links. -**Link adaptation for `internet-identity-spec.md`:** Handled automatically by `npm run sync:ii-spec`. The script rewrites all absolute `internetcomputer.org` links to relative paths using `linkMap` in `scripts/sync-ii-spec.mjs`. If a new link pattern appears in the upstream source, the script exits with a warning listing the unhandled URL — add it to `linkMap` with the correct relative path (use `grep -r "{#}" docs/references/ic-interface-spec/` to find which file owns the anchor), then re-run. +**Link adaptation for `internet-identity-spec.md` and `verifiable-credentials-spec.md`:** Both are handled automatically by `npm run sync:ii-spec`. The script rewrites stale `internetcomputer.org` links to internal relative paths using `linkMap` (ii-spec) and `vcLinkMap` (vc-spec) in `scripts/sync-ii-spec.mjs`. If a new unhandled link pattern appears, the script exits with a warning — add it to the appropriate map with the correct relative path (use `grep -r "{#}" docs/references/ic-interface-spec/` to find which file owns a given anchor), then re-run. Stale links in `vc-spec.md` are tracked upstream in `dfinity/internet-identity#3889`; once fixed, the rewrites become harmless no-ops (old URL simply no longer appears in the source). ### Synced files from submodules From 20c2fa35f4e88826ea3e433170212343d22c2190 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 12:57:28 +0200 Subject: [PATCH 11/14] fix(nav): add verifiable-credentials-spec to sidebar and reference index --- docs/references/index.md | 1 + sidebar.mjs | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/references/index.md b/docs/references/index.md index c36ceadc..f78989c4 100644 --- a/docs/references/index.md +++ b/docs/references/index.md @@ -36,6 +36,7 @@ Technical reference material for ICP development. These pages cover exact specif - **[HTTP Gateway Specification](http-gateway-protocol-spec.md)**: How boundary nodes serve canister HTTP responses with certification verification. - **[Candid Specification](candid-spec.md)**: The Candid interface description language: type system, encoding, and subtyping rules. - **[Internet Identity Specification](internet-identity-spec.md)**: Delegation chains, passkey management, and canister signatures. +- **[Verifiable Credentials Specification](verifiable-credentials-spec.md)**: Normative protocol for issuing and verifying credentials on ICP: Issuer Candid API and Identity Provider window.postMessage interface. ## Governance diff --git a/sidebar.mjs b/sidebar.mjs index 6eaa9527..8b30ce22 100644 --- a/sidebar.mjs +++ b/sidebar.mjs @@ -245,6 +245,7 @@ export const sidebar = [ }, { slug: "references/http-gateway-protocol-spec" }, { slug: "references/internet-identity-spec" }, + { slug: "references/verifiable-credentials-spec" }, { slug: "references/glossary" }, ], }, From 0fce40dd96b3332cd6603b411da207317548dd6a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 13:31:31 +0200 Subject: [PATCH 12/14] fix(nav): trim vc-spec index description --- docs/references/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/references/index.md b/docs/references/index.md index f78989c4..b90d56a6 100644 --- a/docs/references/index.md +++ b/docs/references/index.md @@ -36,7 +36,7 @@ Technical reference material for ICP development. These pages cover exact specif - **[HTTP Gateway Specification](http-gateway-protocol-spec.md)**: How boundary nodes serve canister HTTP responses with certification verification. - **[Candid Specification](candid-spec.md)**: The Candid interface description language: type system, encoding, and subtyping rules. - **[Internet Identity Specification](internet-identity-spec.md)**: Delegation chains, passkey management, and canister signatures. -- **[Verifiable Credentials Specification](verifiable-credentials-spec.md)**: Normative protocol for issuing and verifying credentials on ICP: Issuer Candid API and Identity Provider window.postMessage interface. +- **[Verifiable Credentials Specification](verifiable-credentials-spec.md)**: Normative protocol for issuing and verifying credentials on ICP. ## Governance From 7524ad20ae15b7c594b400104d8d3685c00ed1f2 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 14:35:27 +0200 Subject: [PATCH 13/14] refactor(sync-ii-spec): track latest release tag instead of main Switches the internetidentity submodule from main-tracking to release-tracking. Spec changes are only live once the canister is deployed at a release, so pinning to main could expose unreleased changes in docs. - Pins submodule to release-2026-05-08 (f6cf858), reverting an unreleased internet_identity.did change - Updates VERSIONS to record the current release - Workflow now finds the latest release-YYYY-MM-DD tag (not main), uses tag name in branch/commit/PR title, and auto-updates VERSIONS - AGENTS.md: updates pinning strategy description and extra checks row --- .github/workflows/sync-ii-spec.yml | 36 +++++++++++++++---------- .sources/VERSIONS | 8 ++++++ .sources/internetidentity | 2 +- AGENTS.md | 6 ++--- public/references/internet-identity.did | 29 -------------------- 5 files changed, 34 insertions(+), 47 deletions(-) diff --git a/.github/workflows/sync-ii-spec.yml b/.github/workflows/sync-ii-spec.yml index 6b275f79..ac10f379 100644 --- a/.github/workflows/sync-ii-spec.yml +++ b/.github/workflows/sync-ii-spec.yml @@ -30,25 +30,28 @@ jobs: echo "hash=$HASH" >> $GITHUB_OUTPUT echo "short=$(git -C .sources/internetidentity rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Fetch latest main - run: git -C .sources/internetidentity fetch --depth 100 origin main - - - name: Get latest main hash + - name: Fetch latest release tag and resolve hash id: latest run: | - HASH=$(git -C .sources/internetidentity rev-parse FETCH_HEAD) + git -C .sources/internetidentity fetch --tags --depth 100 origin + TAG=$(git -C .sources/internetidentity tag --sort=-version:refname \ + | grep '^release-[0-9][0-9][0-9][0-9]-' | head -1) + echo "Latest release tag: $TAG" + HASH=$(git -C .sources/internetidentity rev-parse "$TAG") + SHORT=$(git -C .sources/internetidentity rev-parse --short "$TAG") + echo "tag=$TAG" >> $GITHUB_OUTPUT echo "hash=$HASH" >> $GITHUB_OUTPUT - echo "short=$(git -C .sources/internetidentity rev-parse --short FETCH_HEAD)" >> $GITHUB_OUTPUT + echo "short=$SHORT" >> $GITHUB_OUTPUT - name: Check if relevant files changed id: check run: | CURRENT="${{ steps.current.outputs.hash }}" LATEST="${{ steps.latest.outputs.hash }}" - BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" + BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.tag }}" if [ "$CURRENT" = "$LATEST" ]; then - echo "Already at latest main. No update needed." + echo "Already at latest release ${{ steps.latest.outputs.tag }}. No update needed." echo "needed=false" >> $GITHUB_OUTPUT exit 0 fi @@ -103,25 +106,30 @@ jobs: if: steps.check.outputs.needed == 'true' run: npm run build + - name: Update VERSIONS + if: steps.check.outputs.needed == 'true' + run: | + sed -i "s|^internetidentity.*|internetidentity ${{ steps.latest.outputs.tag }} ${{ steps.latest.outputs.short }}|" .sources/VERSIONS + - name: Create PR if: steps.check.outputs.needed == 'true' run: | git config user.name "pr-automation-bot-public[bot]" git config user.email "pr-automation-bot-public[bot]@users.noreply.github.com" - BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" + BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.tag }}" git checkout -b "$BRANCH" - git add .sources/internetidentity docs/references/internet-identity-spec.md docs/references/verifiable-credentials-spec.md public/references/internet-identity.did - git commit -m "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" + git add .sources/internetidentity docs/references/internet-identity-spec.md docs/references/verifiable-credentials-spec.md public/references/internet-identity.did .sources/VERSIONS + git commit -m "chore: sync II spec to dfinity/internet-identity ${{ steps.latest.outputs.tag }}" git push -u origin "$BRANCH" CHANGED="${{ steps.check.outputs.changed_files }}" { echo "## Summary" echo "" - echo "Automated sync of the Internet Identity specification from \`dfinity/internet-identity\` main." + echo "Automated sync of the Internet Identity specification from \`dfinity/internet-identity\`." echo "" - echo "**Pin:** \`${{ steps.current.outputs.short }}\` → \`${{ steps.latest.outputs.short }}\`" + echo "**Release:** \`${{ steps.latest.outputs.tag }}\` (pinned from \`${{ steps.current.outputs.short }}\` → \`${{ steps.latest.outputs.short }}\`)" echo "" echo "**Changed upstream files:**" while IFS= read -r f; do @@ -144,7 +152,7 @@ jobs: } > /tmp/pr-body.md gh pr create \ - --title "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" \ + --title "chore: sync II spec to dfinity/internet-identity ${{ steps.latest.outputs.tag }}" \ --body-file /tmp/pr-body.md env: GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.sources/VERSIONS b/.sources/VERSIONS index 4e062b86..47647be3 100644 --- a/.sources/VERSIONS +++ b/.sources/VERSIONS @@ -4,6 +4,13 @@ # version. Those repos are: portal, examples, icskills, dotskills, # icp-cli-recipes, icp-cli-templates, icp-js-sdk-docs. # +# internetidentity — uses date-based release tags (release-YYYY-MM-DD). +# Only ii-spec.mdx, vc-spec.md, and internet_identity.did are synced. +# Pin to the latest release tag, not the tip of main — spec changes are +# only live once the canister is deployed at that release. +# The automated workflow (.github/workflows/sync-ii-spec.yml) finds the +# latest release-YYYY-MM-DD tag and only opens a PR if those files changed. +# # FORMAT: <7-char hash> # # The hash is the authoritative ref — it is what .gitmodules pins and what @@ -55,3 +62,4 @@ motoko-core v2.4.0 cdk-rs ic-cdk v0.20.1 / ic-cdk-timers v1.0.0 / ic-cdk-executor v2.0.0 317f55c candid 2025-12-18 # candid v0.10.20, didc v0.5.4 2e4a2cf response-verification v3.1.0 18c5a37 +internetidentity release-2026-05-08 f6cf858 diff --git a/.sources/internetidentity b/.sources/internetidentity index 74ad6172..f6cf8581 160000 --- a/.sources/internetidentity +++ b/.sources/internetidentity @@ -1 +1 @@ -Subproject commit 74ad617253d2c9555e473600b920a786ab09c448 +Subproject commit f6cf85819113361acc3bb74880ab2f03c89a8658 diff --git a/AGENTS.md b/AGENTS.md index c18d1303..02b550e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -192,7 +192,7 @@ docs/ # All documentation (.md only) — src/content/docs/ All upstream source repos are pinned as **git submodules** under `.sources/`. This ensures every agent reads the exact same content, regardless of when they run. **Two pinning strategies:** -- **Track latest release** — repos that publish versioned releases users install. Pin to the latest release tag. +- **Track latest release** — repos where the release represents the live/deployed state. Pin to the latest release tag. Spec changes are only accurate once released. - **Track main/master** — content repos where the default branch *is* the canonical source. For current release hashes, see `.sources/VERSIONS`. @@ -217,7 +217,7 @@ For current release hashes, see `.sources/VERSIONS`. | `.sources/dotskills` | `vincentkoc/dotskills` | `main` | Technical documentation skill (AGPL-3.0 — kept as submodule to avoid license mixing) | | `.agents/skills/icp-brand-voice` | n/a — lives directly in this repo | n/a | ICP / DFINITY brand voice: positioning, vocabulary, banned terms, voice attributes | | `.agents/skills/icp-brand-design` | n/a — lives directly in this repo | n/a | ICP / DFINITY brand design: color tokens, typography, layout, components, accessibility | -| `.sources/internetidentity` | `dfinity/internet-identity` | `main` | Internet Identity spec (`docs/ii-spec.mdx`), VC spec (`docs/vc-spec.md`), Candid interface (`src/internet_identity/internet_identity.did`) | +| `.sources/internetidentity` | `dfinity/internet-identity` | latest release | Internet Identity spec (`docs/ii-spec.mdx`), VC spec (`docs/vc-spec.md`), Candid interface (`src/internet_identity/internet_identity.did`) | ### Submodule initialization @@ -292,7 +292,7 @@ EOF | `candid` | Check for spec changes affecting the Candid reference or type-mapping examples | | `response-verification` | Check for API changes affecting certified variables patterns | | `dotskills` | Check if the `technical-documentation` skill changed in ways that affect review criteria | -| `internetidentity` | Run `npm run sync:ii-spec` — the script syncs both `ii-spec.mdx` → `docs/references/internet-identity-spec.md` and `vc-spec.md` → `docs/references/verifiable-credentials-spec.md`, handling all link rewrites, Candid inlining, and frontmatter in one pass. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` (ii-spec) or `vcLinkMap` (vc-spec) in `scripts/sync-ii-spec.mjs`. The **Sync II spec** workflow (`.github/workflows/sync-ii-spec.yml`) can be triggered manually from GitHub Actions; it only opens a PR when `docs/ii-spec.mdx`, `docs/vc-spec.md`, or `internet_identity.did` actually changed — bumping for unrelated upstream code commits is not needed. | +| `internetidentity` | Run `npm run sync:ii-spec` — the script syncs both `ii-spec.mdx` → `docs/references/internet-identity-spec.md` and `vc-spec.md` → `docs/references/verifiable-credentials-spec.md`, handling all link rewrites, Candid inlining, and frontmatter in one pass. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` (ii-spec) or `vcLinkMap` (vc-spec) in `scripts/sync-ii-spec.mjs`. Pin to the latest `release-YYYY-MM-DD` tag — spec changes are only live once the canister is deployed at that release. The **Sync II spec** workflow (`.github/workflows/sync-ii-spec.yml`) checks the latest release tag and only opens a PR when `docs/ii-spec.mdx`, `docs/vc-spec.md`, or `internet_identity.did` actually changed. Trigger manually for early sync. | | `chain-fusion-signer` | Check for changed canister IDs, API methods, or key derivation patterns | | `papi` | Check for changed payment interface or cycle cost model | | `ic-pub-key` | Check for changed CLI flags or commands | diff --git a/public/references/internet-identity.did b/public/references/internet-identity.did index 955353bd..3d3ab6af 100644 --- a/public/references/internet-identity.did +++ b/public/references/internet-identity.did @@ -299,35 +299,6 @@ type InternetIdentityInit = record { backend_canister_id : opt principal; // Backend origin, needed to sync configuration with frontend. backend_origin : opt text; - // DNSSEC verification configuration. Trust anchors used by any feature - // that verifies DNS records against the IANA-rooted DNSSEC chain - // (currently the email-recovery DKIM/DMARC flow). See - // `docs/ongoing/email-recovery.md` §7.5. - // - // Wrapped in `opt opt` to match the same set/clear pattern as - // `analytics_config` / `dummy_auth`: outer null keeps the previously - // stored value across an upgrade, `opt null` clears it, `opt opt c` - // sets it to `c`. - dnssec_config : opt opt DnssecConfig; -}; - -// DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS -// records consumes the same anchors; not specific to email recovery. -type DnssecConfig = record { - // IANA root KSK trust anchors. Multiple are accepted simultaneously so - // KSK rollover is a single config change in the next upgrade arg. - root_anchors : vec DnssecRootAnchor; -}; - -// One IANA root KSK trust anchor, in the same shape IANA publishes at -// `data.iana.org/root-anchors/root-anchors.xml`. Only `digest_type = 2` -// (SHA-256) is accepted; the legacy SHA-1 form is rejected at the -// verifier boundary. -type DnssecRootAnchor = record { - key_tag : nat16; - algorithm : nat8; - digest_type : nat8; - digest : blob; }; type ChallengeKey = text; From 1e3709d245d3ac881f0c5309fafb067176236dfb Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 13 May 2026 14:37:20 +0200 Subject: [PATCH 14/14] feat(sync-ii-spec): run weekly on Tuesday in addition to workflow_dispatch --- .github/workflows/sync-ii-spec.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/sync-ii-spec.yml b/.github/workflows/sync-ii-spec.yml index ac10f379..adfc84a8 100644 --- a/.github/workflows/sync-ii-spec.yml +++ b/.github/workflows/sync-ii-spec.yml @@ -1,6 +1,8 @@ name: Sync II spec on: + schedule: + - cron: '0 9 * * 2' # Weekly on Tuesday (II releases are typically Monday/Tuesday) workflow_dispatch: jobs: