Skip to content

Instantly share code, notes, and snippets.

@Exulansis
Last active August 26, 2021 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Exulansis/bec3906fba96a8b63040bad918eec548 to your computer and use it in GitHub Desktop.
Save Exulansis/bec3906fba96a8b63040bad918eec548 to your computer and use it in GitHub Desktop.
Jolocom credential creation flows.

Credential creation flows using the Jolocom Library

Introduction

At Jolocom we model verifiable credentials using W3C Verifiable credential specification, aside from some very recent spec modifications. Below you can see an example of a credential, serialized as JSON-LD, stating that the did did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe is associated with the email address eugeniu@jolocom.com. You might notice that the subject, issuer, and signer of the credential, are the same identity. This can be effectively referred to as a self signed, or self issued, credential.

{
  "@context": [
    "https://w3id.org/identity/v1",
    "https://identity.jolocom.com/terms",
    "https://w3id.org/security/v1",
    "https://w3id.org/credentials/v1",
    "http://schema.org"
  ],
  "id": "claimId:25453fa543da7",
  "name": "Email address",
  "issuer": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe",
  "type": ["Credential", "ProofOfEmailCredential"],
  "claim": {
    "id": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe",
    "email": "eugeniu@jolocom.com"
  },
  "issued": "2018-08-14T15:09:26.709Z",
  "proof": {
    "type": "EcdsaKoblitzSignature2016",
    "created": "2018-08-14T15:09:26.710Z",
    "creator": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe#keys-1",
    "nonce": "d62dab8e29e11",
    "signatureValue": "6UgXjR6668RWLw45PEOsoxytWbX00prza733mbNKO+NpI4QcqPUUbsVEzRQ10E6YF0OZv+d8pzng+djLfweA2w=="
  }
}

In order for a signed credential to be of any use or value, other independent entities must be able to verify the validity of the signature presented in the proof section. In order to do that, they first need to normalize the JSON-LD document. The following section goes into more details about the normalization process and it's importance.

Normalization

JSON-LD is only one way of serializing a linked data graph. Below a number of alternative serialization of the same credential are presented:

N-QUADS:

<claimId:25453fa543da7> <http://schema.org/name> "Email address" .
<claimId:25453fa543da7> <http://schema.org/proof> _:b0 .
<claimId:25453fa543da7> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://identity.jolocom.com/terms/ProofOfEmailCredential> .
<claimId:25453fa543da7> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/credentials#Credential> .
<claimId:25453fa543da7> <https://w3id.org/credentials#claim> <did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> .
<claimId:25453fa543da7> <https://w3id.org/credentials#issued> "2018-08-14T15:09:26.709Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<claimId:25453fa543da7> <https://w3id.org/credentials#issuer> <did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> .

<did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> <http://schema.org/email> "eugeniu@jolocom.com" .

_:b0 <http://purl.org/dc/terms/created> "2018-08-14T15:09:26.710Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
_:b0 <http://schema.org/creator> "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe#keys-1" .
_:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/security#EcdsaKoblitzSignature2016> .
_:b0 <https://w3id.org/security#nonce> "d62dab8e29e11" .
_:b0 <https://w3id.org/security#signatureValue> "6UgXjR6668RWLw45PEOsoxytWbX00prza733mbNKO+NpI4QcqPUUbsVEzRQ10E6YF0OZv+d8pzng+djLfweA2w==" .

Turtle:

@prefix schema: <http://schema.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dc: <http://purl.org/dc/terms/> .
@prefix ns0: <https://w3id.org/security#> .
@prefix ns1: <https://w3id.org/credentials#> .

<claimId:25453fa543da7>
  a <https://w3id.org/credentials#Credential>, <https://identity.jolocom.com/terms/ProofOfEmailCredential> ;
  schema:name "Email address"^^xsd:string ;
  schema:proof [
    a <https://w3id.org/security#EcdsaKoblitzSignature2016> ;
    dc:created "2018-08-14T15:09:26.710Z"^^xsd:dateTime ;
    schema:creator "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe#keys-1"^^xsd:string ;
    ns0:nonce "d62dab8e29e11"^^xsd:string ;
    ns0:signatureValue "6UgXjR6668RWLw45PEOsoxytWbX00prza733mbNKO+NpI4QcqPUUbsVEzRQ10E6YF0OZv+d8pzng+djLfweA2w=="^^xsd:string
  ] ;
  ns1:claim <did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> ;
  ns1:issued "2018-08-14T15:09:26.709Z"^^xsd:dateTime ;
  ns1:issuer <did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> .

<did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> schema:email "eugeniu@jolocom.com"^^xsd:string .

All three documents describe the same credential, and will result in the same signature. This is possible because we are not digesting and signing the serialized document directly, but rather it's canonical or normalized form. Generally speaking we are signing the described graph itself. An useful online tool for comparing the various serialization formats of a RDF graph is available here. We use the same javascript library for normalizing the credentials in the Jolocom library.

After normalization, the same JSON-LD document looks as follows:

<claimId:25453fa543da7> <http://schema.org/name> "Email address" .
<claimId:25453fa543da7> <http://schema.org/proof> _:b0 .
<claimId:25453fa543da7> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://identity.jolocom.com/terms/ProofOfEmailCredential> .
<claimId:25453fa543da7> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/credentials#Credential> .
<claimId:25453fa543da7> <https://w3id.org/credentials#claim> <did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> .
<claimId:25453fa543da7> <https://w3id.org/credentials#issued> "2018-08-14T15:09:26.709Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
<claimId:25453fa543da7> <https://w3id.org/credentials#issuer> <did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> .
<did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> <http://schema.org/email> "eugeniu@jolocom.com" .
_:b0 <http://purl.org/dc/terms/created> "2018-08-14T15:09:26.710Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
_:b0 <http://schema.org/creator> "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe#keys-1" .
_:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/security#EcdsaKoblitzSignature2016> .
_:b0 <https://w3id.org/security#nonce> "d62dab8e29e11" .
_:b0 <https://w3id.org/security#signatureValue> "6UgXjR6668RWLw45PEOsoxytWbX00prza733mbNKO+NpI4QcqPUUbsVEzRQ10E6YF0OZv+d8pzng+djLfweA2w==" .

It is worth noting that all JSON keys, e.g. type, email, nonce, have been expanded to their full form, e.g. <http://schema.org/name>, <http://schema.org/email>, and <https://w3id.org/security#nonce> respectively.

As part of the normalization process, all keys in the JSON-LD document need to be mapped to corresponding RDF predicates / subjects / objects. This is where the @context section comes in. It's purpose is to aid this mapping. In the presented case, all entries are links to external contexts that can be fetched to proceed with the mapping process.

For instance the first url, https://w3id.org/security/v1, once dereferenced will result in the following document:

{
  "@context": {
    "id": "@id",
    "type": "@type",

    "dc": "http://purl.org/dc/terms/",
    "sec": "https://w3id.org/security#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",

    "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016",
    "Ed25519Signature2018": "sec:Ed25519Signature2018",
    "EncryptedMessage": "sec:EncryptedMessage",
    "GraphSignature2012": "sec:GraphSignature2012",
    "LinkedDataSignature2015": "sec:LinkedDataSignature2015",
    "LinkedDataSignature2016": "sec:LinkedDataSignature2016",
    "CryptographicKey": "sec:Key",

    "authenticationTag": "sec:authenticationTag",
    "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm",
    "cipherAlgorithm": "sec:cipherAlgorithm",
    "cipherData": "sec:cipherData",
    "cipherKey": "sec:cipherKey",
    "created": {"@id": "dc:created", "@type": "xsd:dateTime"},
    "creator": {"@id": "dc:creator", "@type": "@id"},
    "digestAlgorithm": "sec:digestAlgorithm",
    "digestValue": "sec:digestValue",
    "domain": "sec:domain",
    "encryptionKey": "sec:encryptionKey",
    "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
    "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
    "initializationVector": "sec:initializationVector",
    "iterationCount": "sec:iterationCount",
    "nonce": "sec:nonce",
    "normalizationAlgorithm": "sec:normalizationAlgorithm",
    "owner": {"@id": "sec:owner", "@type": "@id"},
    "password": "sec:password",
    "privateKey": {"@id": "sec:privateKey", "@type": "@id"},
    "privateKeyPem": "sec:privateKeyPem",
    "publicKey": {"@id": "sec:publicKey", "@type": "@id"},
    "publicKeyBase58": "sec:publicKeyBase58",
    "publicKeyPem": "sec:publicKeyPem",
    "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"},
    "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
    "salt": "sec:salt",
    "signature": "sec:signature",
    "signatureAlgorithm": "sec:signingAlgorithm",
    "signatureValue": "sec:signatureValue"
  }
}

At this point the keys in the original JSON-LD document can be mapped to the corresponding entries in the fetched context. In case a mapping for a key has not been found in any of the fetched contexts, the normalization will continue, but the outcome will be incorrect.

We can verify this by taking the original credential, and removing two entries from the @context array, specifically https://w3id.org/identity/v1 and https://w3id.org/credentials/v1. The resulting credential looks as follows:

{
  "@context": [
    "https://identity.jolocom.com/terms",
    "https://w3id.org/security/v1",
    "http://schema.org"
  ],
  "id": "claimId:25453fa543da7",
  "name": "Email address",
  "issuer": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe",
  "type": ["Credential", "ProofOfEmailCredential"],
  "claim": {
    "id": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe",
    "email": "eugeniu@jolocom.com"
  },
  "issued": "2018-08-14T15:09:26.709Z",
  "proof": {
    "type": "EcdsaKoblitzSignature2016",
    "created": "2018-08-14T15:09:26.710Z",
    "creator": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe#keys-1",
    "nonce": "d62dab8e29e11",
    "signatureValue": "6UgXjR6668RWLw45PEOsoxytWbX00prza733mbNKO+NpI4QcqPUUbsVEzRQ10E6YF0OZv+d8pzng+djLfweA2w=="
  }
}

If we look at the normalized form, we can notice that it is different from the initial normalized form, namely the key issuer, amongst others, has been incorrectly expanded to http://schema.org/issuer instead of https://w3id.org/credentials#issued, because no matching mapping was found.

<claimId:25453fa543da7> <http://schema.org/claim> <did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> .
<claimId:25453fa543da7> <http://schema.org/issued> "2018-08-14T15:09:26.709Z" .
<claimId:25453fa543da7> <http://schema.org/issuer> "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe" .
<claimId:25453fa543da7> <http://schema.org/name> "Email address" .
<claimId:25453fa543da7> <http://schema.org/proof> _:c14n0 .
<claimId:25453fa543da7> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Credential> .
<claimId:25453fa543da7> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://identity.jolocom.com/terms/ProofOfEmailCredential> .
<did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe> <http://schema.org/email> "eugeniu@jolocom.com" .
_:c14n0 <http://purl.org/dc/terms/created> "2018-08-14T15:09:26.710Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
_:c14n0 <http://schema.org/creator> "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe#keys-1" .
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/security#EcdsaKoblitzSignature2016> .
_:c14n0 <https://w3id.org/security#nonce> "d62dab8e29e11" .
_:c14n0 <https://w3id.org/security#signatureValue> "6UgXjR6668RWLw45PEOsoxytWbX00prza733mbNKO+NpI4QcqPUUbsVEzRQ10E6YF0OZv+d8pzng+djLfweA2w==" .

This being said, whenever we introduce a custom credential type, or a custom key in general, we need to provide the corresponding mapping through the @context field, otherwise normalization will not go as expected (in this case the keys have been incorrectly mapped to schema.org predicates as a fallback, but we cannot rely on that).

Since the normalized form is what is being digested and signed, the signature generated in the first case will be different from the signature generated in the second one.

Creating custom verifiable credentials using the Jolocom library.

Let's assume you need to create a custom credential that describes the subject's job title. Firstly, you need to define a corresponding metadata object, for instance:

{
    "type": ["Credential", "ProofOfJobTitle"],
    "name": "Job title",
    "context": [
      "https://w3id.org/identity/v1",
      "https://mywebsite.com/terms",
      "https://w3id.org/security/v1",
      "https://w3id.org/credentials/v1",
      "http://schema.org"
    ]
}

Besides defining the claim fields (in this case jobTitle), you will also need to provide a context link or definition that can be used to map the aforementioned field name to the corresponding RDF predicate. In this case the jobTitle key will be mapped to http://schema.org/jobTitle, a valid predicate. In addition, you will need to make sure that the credential type you defined (in this case ProofOfJobTitle) can also be mapped to a RDF subject, e.g. https://mywebsite.com/terms/ProofOfJobTitle.

This is the reason most credentials created by the Jolocom Library contain the https://identity.jolocom.com/terms entry in the @context field, to dereferences custom credential types such as ProofOfEmailCredential or ProofOfMobilePhoneNumberCredential. As of now none of those terms are defined in any other contexts. Furthermore most types shown in the specification document (e.g. ProofOfAgeCredential) lack the mappings as well.

Do I need to publish a new context if I define custom terms?

Actually, no! The following credential format is also fully valid:

 "@context": [
   {
     "ProofOfEmailCredential": "https://identity.jolocom.com/terms/ProofOfEmailCredential"
   }
    "https://w3id.org/identity/v1",
    "https://w3id.org/security/v1",
    "https://w3id.org/credentials/v1",
    "http://schema.org"
  ],
  "id": "claimId:25453fa543da7",
  "name": "Email address",
  "issuer": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe",
  "type": ["Credential", "ProofOfEmailCredential"],
  "claim": {
    "id": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe",
    "email": "eugeniu@jolocom.com"
  },
  "issued": "2018-08-14T15:09:26.709Z",
  "proof": {
    "type": "EcdsaKoblitzSignature2016",
    "created": "2018-08-14T15:09:26.710Z",
    "creator": "did:jolo:ffcc8f84fae1b6ad253561d7b78167a661d72f58e86e60dbd04cd9b81096cdbe#keys-1",
    "nonce": "d62dab8e29e11",
    "signatureValue": "6UgXjR6668RWLw45PEOsoxytWbX00prza733mbNKO+NpI4QcqPUUbsVEzRQ10E6YF0OZv+d8pzng+djLfweA2w=="
  }

The benefit of this approach is that the ProofOfEmailCredential key can be mapped to the corresponding term without making any external http calls.

It is also worth noting that the https://identity.jolocom.com/terms/ProofOfEmailCredential url does not have to be dereferenceable as far as the normalization and signature generation is concerned, and could be deployed at a later point in time.

We are currently actively considering employing this approach at Jolocom as well, to not have to rely on the availability of external contexts / resources .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment