II Verifiable Credential Spec (MVP)
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) An example implementation of the interface is given in demos/vc_issuer.
The Candid interface is as follows, and the subsequent sections describe the services and the corresponding messages in more detail.
// 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, and requests the consent message via Icrc21VcConsentMessageRequest
,
Upon successful response idenity 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-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://<issuer-canister-id>.icp0.io
.
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.
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.
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
the context contains a time-stamped yet unsigned VC, for which the canister signature will be
available only at get_credential
-call.
4: Get Credential
get_credential
-service issues the actual credential requested by the user.
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
"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
"SomeVerifiedProperty": {
"argument_1": "value_1",
"another_argument": 42,
}
For example, for VerifiedAdult
-credential we'd use the following credential spec
"credentialSpec": {
"credentialType": "VerifiedAdult",
"arguments": {
"minAge": 18,
}
and a compliant issuer would issue a VC that contains credentialSubject
with the property
"VerifiedAdult": {
"minAge": 18,
}
Identity Provider API
This section describes the 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)
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:
{
"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 thederivation_origin
canister call, then theorigin
must be a valid alternative origin as per the 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 ofcredentialSubject
(applicable if the relying party uses the Alternative Frontend Origins-feature).
Examples
{
"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"
}
}
{
"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"
}
}
{
"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:- A verifiable credential issued by Internet Identity that relates the requested
credentialSubject
to another alias principal. - A verifiable credential issued by the requested issuer to the alias principal.
- A verifiable credential issued by Internet Identity that relates the requested
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
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"verifiablePresentation": "eyJQ..."
}
}
An example of such a verifiable presentation can be found here.
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
{
"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.