Sign W3C Credential

Sign credentials either as JWT or SD-JWT using the walt.id verifiable credentials lib in Kotlin/Java.

Before we can start singing credentials we need to get a few things in order.

  1. We need to decide on the structure of the raw credential data to be signed, which typically follows a predefined template or schema. You can find a list of common data structures here.
  2. We need to decide whether we want to sign the credential as JWT (JSON-Web-Token) or SD-JWT (Selective-Disclosure JWT). JWTs are commonly used whereas SD-JWTs are more experimental in their current implementation. Learn more about SD-JWT here.
  3. We (the issuer) need to have a key and the related DID to sign the credential, see managing keys and managing DIDs.

Create Credential

To build the credential we will be using the CredentialBuilder which makes the creation of W3C credentials easier, by automatically inserting required fix fields and providing utility functions to add custom data. Next to using the CredentialBuilder, you can also build W3C manually as described in the next section. If you want to update credential data defined via the CredentialBuilder or manually, you can use data functions described here. You won't need this though for most use-cases.

The CredentialBuilder supports the creation of W3C credentials based on version 1.1 or 2.0.

val v1 = CredentialBuilder(CredentialBuilderType.W3CV11CredentialBuilder)
//  OR 
val v2 = CredentialBuilder(CredentialBuilderType.W3CV2CredentialBuilder)

For this example we will be using V1 to create a Open Badge credential.

import id.walt.credentials.CredentialBuilder
import id.walt.credentials.CredentialBuilderType
import id.walt.crypto.utils.JsonUtils.toJsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlin.time.Duration.Companion.days

suspend fun main() {
    val vc = CredentialBuilder(CredentialBuilderType.W3CV11CredentialBuilder).apply {
        // Adds context next to default "https://www.w3.org/2018/credentials/v1"
        addContext("https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json")

        // Adds type next to default "VerifiableCredential"
        addType("OpenBadgeCredential")

        credentialId = "urn:uuid:4177e048-9a4a-474e-9dc6-aed4e61a6439"

        issuerDid = "did:key:z6MksFnax6xCBWdo6hhfZ7pEsbyaq9MMNdmABS1NrcCDZJr3"

        // Sets issuance date to current time - 1.5 min
        validFromNow()

        // Adds expiration date
        validFor(2.days)

        subjectDid = "did:key:z6MksFnax6xCBWdo6hhfZ7pEsbyaq9MMNdmABS1NrcCDZJr3"

        // Used to add any custom data 
        useData("name", JsonPrimitive("JFF x vc-edu PlugFest 3 Interoperability"))

        // Used to insert credential subject data
        useCredentialSubject(
                mapOf(
                        "type" to listOf("AchievementSubject"),
                        "achievement" to mapOf(
                                "id" to "urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926",
                                "type" to listOf("Achievement"),
                                "name" to "JFF x vc-edu PlugFest 3 Interoperability",
                                "description" to "This wallet supports the use of W3C Verifiable Credentials and has demonstrated interoperability during the presentation request workflow during JFF x VC-EDU PlugFest 3.",
                                "criteria" to mapOf(
                                        "type" to "Criteria",
                                        "narrative" to "Wallet solutions providers earned this badge by demonstrating interoperability during the presentation request workflow. This includes successfully receiving a presentation request, allowing the holder to select at least two types of verifiable credentials to create a verifiable presentation, returning the presentation to the requestor, and passing verification of the presentation and the included credentials."
                                ),
                                "image" to mapOf(
                                        "id" to "https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
                                        "type" to "Image"
                                )
                        )
                ).toJsonObject()
        )
    }.buildW3C() // Builds the credential and returns W3CVC object

    println(vc.toPrettyJson())
}

You can also add a Credential Status based on CredentialStatusList2021 standard using the useStatusList2021Revocation. The function tasks the status credential as a first and the index of the to be issued credential as a second. At the moment, we haven't fully integrated the credential revocation functionality yet. Please refer to our roadmap for a timeline.

Credential as JSON

{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
  ],
  "type": [
    "VerifiableCredential",
    "OpenBadgeCredential"
  ],
  "credentialSubject": {
    "type": [
      "AchievementSubject"
    ],
    "achievement": {
      "id": "urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926",
      "type": [
        "Achievement"
      ],
      "name": "JFF x vc-edu PlugFest 3 Interoperability",
      "description": "This wallet supports the use of W3C Verifiable Credentials and has demonstrated interoperability during the presentation request workflow during JFF x VC-EDU PlugFest 3.",
      "criteria": {
        "type": "Criteria",
        "narrative": "Wallet solutions providers earned this badge by demonstrating interoperability during the presentation request workflow. This includes successfully receiving a presentation request, allowing the holder to select at least two types of verifiable credentials to create a verifiable presentation, returning the presentation to the requestor, and passing verification of the presentation and the included credentials."
      },
      "image": {
        "id": "https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
        "type": "Image"
      }
    },
    "id": "did:key:z6MksFnax6xCBWdo6hhfZ7pEsbyaq9MMNdmABS1NrcCDZJr3"
  },
  "id": "urn:uuid:4177e048-9a4a-474e-9dc6-aed4e61a6439",
  "issuer": "did:key:z6MksFnax6xCBWdo6hhfZ7pEsbyaq9MMNdmABS1NrcCDZJr3",
  "issuanceDate": "2024-01-16T14:42:50.169834Z",
  "expirationDate": "2024-01-18T14:42:50.172054Z",
  "name": "JFF x vc-edu PlugFest 3 Interoperability"
}

Manual Create Credential

In the example below we are not using the CredentialBuilder, but build the credential manually.

val vc = W3CVC.build(
        context = listOf(
                "https://www.w3.org/2018/credentials/v1",
                "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json"
        ),
        type = listOf("VerifiableCredential", "OpenBadgeCredential"),

        "id" to "urn:uuid:4177e048-9a4a-474e-9dc6-aed4e61a6439",
        "name" to "JFF x vc-edu PlugFest 3 Interoperability",
        "issuer" to myIssuerDid,
        "issuanceDate" to "2023-08-02T08:03:13Z",
        "credentialSubject" to mapOf(
                "type" to listOf("AchievementSubject"),
                "id" to mySubjectDid,
                "achievement" to mapOf(
                        "id" to "urn:uuid:ac254bd5-8fad-4bb1-9d29-efd938536926",
                        "type" to listOf("Achievement"),
                        "name" to "JFF x vc-edu PlugFest 3 Interoperability",
                        "description" to "This wallet supports the use of W3C Verifiable Credentials and has demonstrated interoperability during the presentation request workflow during JFF x VC-EDU PlugFest 3.",
                        "criteria" to mapOf(
                                "type" to "Criteria",
                                "narrative" to "Wallet solutions providers earned this badge by demonstrating interoperability during the presentation request workflow. This includes successfully receiving a presentation request, allowing the holder to select at least two types of verifiable credentials to create a verifiable presentation, returning the presentation to the requestor, and passing verification of the presentation and the included credentials."
                        ),
                        "image" to mapOf(
                                "id" to "https://w3c-ccg.github.io/vc-ed/plugfest-3-2023/images/JFF-VC-EDU-PLUGFEST3-badge-image.png",
                                "type" to "Image"
                        )
                )
        )
)

Sign Credential

To sign the credential we need to bring in a key and did for the issuer. Please refer to the manage Key and manage DID sections for more info.

JWT
SD-JWT
suspend fun main() {

    DidService.minimalInit()

    val myIssuerKey = LocalKey.generate(KeyType.Ed25519)
    val myIssuerDid = DidService.registerByKey("key", myIssuerKey).did

    val mySubjectDid = "did:key:xyz"

    // vc refers to the credential created in the previous section 
    val signed: String = vc.signJws(myIssuerKey, myIssuerDid, mySubjectDid)
}