Issuance
The IOTA DIDComm Specification is in the RFC phase and may undergo changes. Suggestions are welcome at GitHub #464.
- Version: 0.1
- Status:
IN-PROGRESS
- Last Updated: 2021-10-29
Overview
Allows a holder to request a verifiable credential from an issuer. The issuer may alternatively initiate the issuance without a request from the holder. This protocol also allows the issuer to request additional information and to offload the actual signing to a different party.
Relationships
- Presentation: the issuer may request a verifiable presentation from the holder during the course of this protocol if additional information is required.
- Signing: the issuer may delegate signing to another issuer if they lack the needed authority or private key, in which case the issuer takes on the role of trusted-party.
Example Use-Cases
- A university issues a degree to a graduate that can be verified by potential employers.
- A resident requests proof of address from their city council.
- An insurer issues proof that a company has liability insurance.
Roles
- Holder: stores one or more verifiable credentials. A holder is usually but not always the subject of those credentials.
- Issuer: creates verifiable credentials asserting claims about one or more subjects, transmitted to a holder.
Interaction
Messages
1. issuance-request
- Type:
iota/issuance/0.1/issuance-request
- Role: holder
The holder requests a single verifiable credential from the issuer of a particular kind.
Structure
{
"subject": DID, // REQUIRED
"credentialInfo": CredentialInfo, // REQUIRED
}
Field | Description | Required |
---|---|---|
subject | DID of the credential subject1. | ✔ |
credentialInfo | A CredentialInfo object, specifying a credential kind requested by the holder.2 3 4 | ✔ |
1 The holder is usually but not always the subject of the requested credential. There may be custodial, legal guardianship, or delegation situations where a third-party requests, or is issued a credential on behalf of a subject. It is the responsibility of the issuer to ensure authorization in such cases.
2 The credentialInfo
could be hard-coded, communicated in-band, discovered out-of-band, or be pre-sent by an issuer. The issuer SHOULD reject the request with a problem-report
if it does not support the requested credentialInfo
.
3 With CredentialType2021, the type
MAY be under-specified if the exact type is unknown or if the resulting type depends on the identity or information of the subject or holder. E.g. the type
could be as general as ["VerifiableCredential"]
if the issuer issues only a singular type of credential or decides the credential based on other information related to the subject.
4 With CredentialType2021, the holder MAY specify one or more trusted issuers they would like to sign the resulting credential. The issuer SHOULD reject the request with a problem-report
if it supports none of the requested issuer
entries. However, there are circumstances where an issuer
is no longer supported or was compromised, so this behavior should be decided based on the application.
An issuer wanting to preserve privacy regarding which exact credential kinds, types, or issuers they support should be careful with the information they disclose in problem-reports
when rejecting requests. E.g. a problem-report
with only a reject-request
descriptor discloses less information than the reject-request.invalid-type
or reject-request.invalid-trusted-issuer
descriptors, as the latter two could be used to determine supported types or signing issuers by process of elimination.
Examples
- Request any drivers licence credential using CredentialType2021:
{
"subject": "did:example:c6ef1fe11eb22cb711e6e227fbc",
"credentialInfo": {
"credentialInfoType": "CredentialType2021",
"type": ["VerifiableCredential", "DriversLicence"]
}
}
- Request a university degree credential from either supported trusted issuer using CredentialType2021:
{
"subject": "did:example:c6ef1fe11eb22cb711e6e227fbc",
"credentialInfo": {
"credentialInfoType": "CredentialType2021",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential",
"BachelorOfArtsDegreeCredential"
],
"issuer": [
"did:example:76e12ec712ebc6f1c221ebfeb1f",
"did:example:f1befbe122c1f6cbe217ce21e67"
]
}
}
2. issuance-offer
- Type:
iota/issuance/0.1/issuance-offer
- Role: issuer
The issuer offers a single, unsigned credential to the holder, matching the preceding issuance-request
if present. The issuer may set an expiry date for the offer and require non-repudiable proof by the holder that the offer was received.
Structure
{
"unsignedCredential": Credential, // REQUIRED
"offerChallenge": {
"challenge": string, // REQUIRED
"credentialHash": string, // REQUIRED
}, // OPTIONAL
"offerExpiry": DateTime // OPTIONAL
}
Field | Description | Required |
---|---|---|
unsignedCredential | Unsigned credential being offered to the holder. This MUST NOT include a proof section. | ✔ |
offerChallenge | If present, indicates the issuer requires the acceptance of the credential to be signed by the holder in the following issuance-response for non-repudiation.1 | ✖ |
challenge | A random string that should be unique per issuance-offer. | ✔ |
credentialHash | The Base58-encoded SHA-256 digest of the unsignedCredential formatted according to the JSON Canonicalization Scheme. | ✔ |
offerExpiry | A string formatted as an XML DateTime normalized to UTC 00:00:00 and without sub-second decimal precision. E.g: "2021-12-30T19:17:47Z" .2 | ✖ |
1 Issuing challenges should be done with due consideration to security and privacy concerns: not all applications require non-repudiation to third-parties and a holder may wish to deny that they ever requested or accepted a particular credential. The challenge SHOULD NOT be used for authentication of the holder; see the authentication protocol and sender authenticated encryption.
2 If present, an offerExpiry
indicates that the issuer MAY rescind the offer and abandon the protocol if an affirmative issuance-response is not received before the specified datetime. Note that the offerExpiry
should override any default message timeouts.
Examples
- Offer a degree credential:
{
"unsignedCredential": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "6c1a1477-e452-4da7-b2db-65ad0b369d1a",
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
"issuanceDate": "2021-05-03T19:73:24Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
}
}
- A time-limited offer for a degree credential with a signature requested:
{
"unsignedCredential": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "6c1a1477-e452-4da7-b2db-65ad0b369d1a",
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
"issuanceDate": "2021-01-05T19:37:24Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
}
},
"offerChallenge": {
"challenge": "d7b7869e-fec3-4de9-84bb-c3a43bacff33",
"credentialHash": "28Ae7AdqzyMyF9pmnwUNK1Q7VT3EzDDGEj1Huk7uYQT94KYAhQzEPyhoF5Ugs3totUugLPpghGmE9HaG8usJZcZv"
},
"offerExpiry": "2021-01-05T20:07:24Z"
}
3. issuance-response
- Type:
iota/issuance/0.1/issuance-response
- Role: holder
The holder responds to a issuance-offer
by accepting or disputing the offer and optionally signing the response for non-repudiation.
Structure
{
"accepted": bool, // REQUIRED
"disputes": [Dispute], // OPTIONAL
"signature": {
"offerChallenge": {
"challenge": string, // REQUIRED
"credentialHash": string, // REQUIRED
}, // REQUIRED
"proof": Proof, // REQUIRED
} // OPTIONAL
}
Field | Description | Required |
---|---|---|
accepted | Indicates if the holder accepts the offered credential from issuance-offer . MUST be false if any disputes are present. | ✔ |
disputes | Allows the holder to dispute one or more claims in the credential. | ✖ |
signature | This SHOULD be present if a offerChallenge was included in the preceding issuance-offer .1 | ✖ |
offerChallenge | This MUST match the offerChallenge in the preceding issuance-offer . | ✔ |
proof | Signature of the holder on the offerChallenge . | ✔ |
1 A valid signature
allows the issuer to prove that the credential was accepted by the holder. If present, the issuer MUST validate the proof
is correct and signed with an unrevoked verification method, and issue a problem-report if not. The issuer SHOULD terminate the protocol if no signature
is present and a offerChallenge
was included in the preceding issuance-offer message. An explicit signature
is used instead of a signed DIDComm message to avoid the need to store the entire credential for auditing purposes; the hash is sufficient to prove a particular credential was accepted.
Examples
- Accept a credential offer:
{
"accepted": true,
"disputes": []
}
- Accept a credential offer including a signature:
{
"accepted": true,
"disputes": [],
"signature": {
"offerChallenge": {
"challenge": "d7b7869e-fec3-4de9-84bb-c3a43bacff33",
"credentialHash": "28Ae7AdqzyMyF9pmnwUNK1Q7VT3EzDDGEj1Huk7uYQT94KYAhQzEPyhoF5Ugs3totUugLPpghGmE9HaG8usJZcZv",
},
"proof": {...}
}
}
- Reject a credential offer with disputes:
{
"accepted": false,
"disputes": [{
"@context": [
"https://www.w3.org/2018/credentials/v1",
],
"id": "6e8e989e-749e-4ed8-885b-b2a2bb64835f",
"type": ["VerifiableCredential", "DisputeCredential"],
"credentialSubject": {
"id": "6c1a1477-e452-4da7-b2db-65ad0b369d1a",
"currentStatus": "Disputed",
"statusReason": {
"value": "Incorrect name.",
"lang": "en"
},
},
"issuer": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"issuanceDate": "2021-01-05T19:46:24Z",
"proof": {...}
}],
}
4. issuance
- Type:
iota/issuance/0.1/issuance
- Role: issuer
The issuer transmits the signed credential following a issuance-response
by the holder. The issuer may set an expiry until when they expect an acknowledgment and request a cryptographic signature in the acknowledgment for non-repudiation.
Structure
{
"signedCredential": Credential, // REQUIRED
"issuanceChallenge": {
"challenge": string, // REQUIRED
"credentialHash": string, // REQUIRED
}, // OPTIONAL
"issuanceExpiry": DateTime, // OPTIONAL
}
Field | Description | Required |
---|---|---|
signedCredential | Verifiable credential signed by the issuer.1 | ✔ |
issuanceChallenge | If present, indicates the issuer requires the issuance-acknowledgement of the credential to be signed for non-repudiation. | ✖ |
challenge | A random string that should be unique per issuance. | ✔ |
credentialHash | The Base58-encoded SHA-256 digest of the signedCredential , including the proof , formatted according to the JSON Canonicalization Scheme. | ✔ |
issuanceExpiry | A string formatted as an XML Datetime normalized to UTC 00:00:00 and without sub-second decimal precision indicating when the offer expires. E.g: "2021-12-30T19:17:47Z" .2 | ✖ |
1 The holder SHOULD validate both that the proof
on the signedCredential
is correctly signed by a trusted issuer and that the contents match those of the unsignedCredential
from the issuance-offer they accepted. If not, a relevant problem-report should be sent.
2 The issuer SHOULD send a problem-report if the issuanceExpiry
datetime passes without receiving an issuance-acknowledgement message from the holder. The issuer MAY revoke the credential in this case.
Examples
- Issuing a credential including expiry and requesting proof:
{
"unsignedCredential": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "6c1a1477-e452-4da7-b2db-65ad0b369d1a",
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
"issuanceDate": "2021-01-05T19:37:24Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts"
}
},
"proof": {
"type": "JcsEd25519Signature2020",
"verificationMethod": "did:example:ebfeb1f712ebc6f1c276e12ec21#key",
"signatureValue": "3KpeHSW4LybMy1smFEYriRmj5FsFfnxQiEsBnQdYzwkXMnjF3Jjn5RS1KGzheNpUgHW5yua8DoLbfYmZFAvaUVwv"
}
},
"issuanceChallenge": {
"challenge": "6ff5f616-2f9c-4e47-b9d2-5553deeac01d",
"credentialHash": "21DtABsnYNb7oGEY8aybb9Bghq6NJJWvrQgtC2SBdhgQ8v6cZGjnT8RmEmBLZfHyfEYMAik3D1EoNQZCaT4RUKEX"
},
"issuanceExpiry": "2021-01-05T20:07:24Z"
}
5. issuance-acknowledgment
- Type:
iota/issuance/0.1/issuance-acknowledgment
- Role: holder
The holder confirms receipt of a successful credential issuance
, optionally including non-repudiable proof.
Structure
{
"signature": {
"issuanceChallenge": {
"challenge": string, // REQUIRED
"credentialHash": string, // REQUIRED
}, // REQUIRED
"proof": Proof, // REQUIRED
} // OPTIONAL
}
Field | Description | Required |
---|---|---|
signature | This SHOULD be present if a issuanceChallenge was included in the preceding issuance message.1 | ✖ |
issuanceChallenge | This MUST match the issuanceChallenge in the preceding issuance message. | ✔ |
proof | Signature of the holder on the issuanceChallenge . | ✔ |
1 The issuer MUST validate the signature
and MAY revoke the issued credential if a signature
was requested, e.g. for non-repudiation or auditing, and not received or an invalid signature
is received. An explicit signature
is used instead of a signed DIDComm message to avoid the need to store the entire credential for auditing purposes as the hash is sufficient to prove a particular credential was accepted.
Examples
- Acknowledge receipt of the credential:
{}
- Acknowledge receipt of the credential including a signature:
{
"signature": {
"issuanceChallenge": {
"challenge": "6ff5f616-2f9c-4e47-b9d2-5553deeac01d",
"credentialHash": "21DtABsnYNb7oGEY8aybb9Bghq6NJJWvrQgtC2SBdhgQ8v6cZGjnT8RmEmBLZfHyfEYMAik3D1EoNQZCaT4RUKEX",
},
"proof": {...}
}
}
Problem Reports
The following problem-report codes may be raised in the course of this protocol and are expected to be recognised and handled in addition to any general problem-reports. Implementers may also introduce their own application-specific problem-reports.
For guidance on problem-reports and a list of general codes see problem reports.
Code | Message | Description |
---|---|---|
e.p.msg.iota.issuance.reject-request | issuance-request | Issuer rejects a credential request for any reason, e.g. unrecognised or invalid type, trusted issuer, or subject. |
e.p.msg.iota.issuance.reject-request.invalid-subject | issuance-request | Issuer rejects a credential request due to the subject being unrecognised, missing, or otherwise invalid. |
e.p.msg.iota.issuance.reject-request.invalid-type | issuance-request | Issuer rejects a credential request due to the type or @context being unsupported or otherwise invalid. |
e.p.msg.iota.issuance.reject-request.invalid-issuer | issuance-request | Issuer rejects a credential request due to trustedIssuers being unrecognised, unsupported or otherwise invalid. |
e.p.msg.iota.issuance.presentation-failed | issuance-offer | Issuer terminates the protocol due to a failed presentation request for more information prior to a issuance-offer. |
e.p.msg.iota.issuance.reject-response.missing-signature | issuance-response | Issuer rejects an issuance-response missing a signature when offerChallenge was included in the preceding issuance-offer message. |
e.p.msg.iota.issuance.reject-issuance | issuance | Holder rejects a credential issuance for any reason, e.g. mismatch with the credential in the issuance-offer. Note that disputes are handled in issuance-response prior to issuance. |
e.p.msg.iota.issuance.expired | issuance | Issuer notifies the holder that an issuance message has expired without a valid issuance-acknowledgement. |
e.p.msg.iota.issuance.reject-acknowledgement.missing-signature | issuance-acknowledgement | Issuer rejects an issuance-acknowledgement missing a signature when issuanceChallenge was included in the preceding issuance message. |
Unresolved Questions
The
credentialSubject::id
field of a verifiable credential is optional and not always a DID according to the verifiable credential specification. Should we enforce that it is always a DID? This affects presentations are noted in the subject-holder relationships section of the specification. We essentially enforce thenonTransferable
property for all credentials in our presentations currently to prevent verifiers storing and re-presenting credentials as their own.e.p.msg.iota.issuance.reject-request.invalid-type
ande.p.msg.iota.issuance.reject-request.invalid-issuer
are specific to CredentialType2021. Should they be listed here? If yes, should they be marked accordingly?
Related Work
- Aries Hyperledger: