Wallets & Verifiable Credentials as a Decentralised Semantic Database. JSON-LD and beyond
What do you see here? Just a simple w3c credential? Let's go deeper into the anatomy of WC3 Credentials.
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://schema.affinidi.com/ContentLikeV1-0.jsonld"
],
"id": "claimId:uv2kchuqy6",
"type": [
"VerifiableCredential",
"ContentLike"
],
"holder": {
"id": "did:web:volland.github.io:sdk-notebooks"
},
"credentialSubject": {
"data": {
"@type": [
"VerifiableCredential",
"ContentLike"
],
"url": "https://www.youtube.com/watch?v=owbkzvLhblk",
"date": "2023-04-06T15:05:58.830Z",
"like": true,
"score": 10
}
},
"credentialSchema": {
"id": "https://schema.affinidi.com/ContentLikeV1-0.json",
"type": "JsonSchemaValidator2018"
},
"issuanceDate": "2023-04-06T15:05:58.830Z",
"expirationDate": "2065-09-10T00:00:00.000Z",
"issuer": "did:web:volland.github.io:sdk-notebooks",
"proof": {
"type": "EcdsaSecp256k1Signature2019",
"created": "2023-04-06T15:05:58Z",
"verificationMethod": "did:web:volland.github.io:sdk-notebooks#primary",
"proofPurpose": "assertionMethod",
"jws": "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..paG9PLEkUKiNeCtXpuOUylUWmnN5AAIxlo3iHrn8kIt1dp_mCcYV-gc4q4DVh2w1PPUF-g4ANMq1-zsD4bw_ag"
}
}
The history of Verifiable Credentials starts with the JSON-LD community and the idea of authentic signed JSON-LD data.
I prefer to call VC and Authentic Data Containers. It reflects a better reality as Credential is more identity term and Data Containers is more generic.
Data Container is self-explaining data that sets a
- data meaning and context of data
- set data structure validity
- established data authenticity and integrity via cryptography signatures
Data meaning — JSON-LD
Linked Data refers to publishing structured data on the web in a way that allows different data sources to be connected and easily understood by humans and machines. The primary goal of Linked Data is to enable the creation of a global, interconnected web of data that can be accessed and utilized by applications to provide rich, context-aware experiences.
The key principles of Linked Data involve using URIs (Uniform Resource Identifiers) to name things, using HTTP URIs to make those names accessible, providing helpful information when someone accesses the URI, and linking to other URIs to enable the discovery of more details.
In JSON-LD and Linked Data, context refers to the mapping between terms used in a JSON-LD document and their meanings (i.e., their IRIs). A context in JSON-LD defines how terms, properties, and values within a JSON document should be interpreted as Linked Data. This is crucial for achieving semantic interoperability between different data sources and applications.
By providing context, you can:
- Define how terms in a JSON-LD document map to their corresponding IRIs. This enables a machine to understand the semantic meaning of terms and properties used in the document.
- Specify data types and language tags for values, ensuring a consistent interpretation of data.
- Compact and expand JSON-LD documents, making them more human-readable or machine-optimized.
In JSON-LD, context is typically provided using the “@context” keyword, followed by an object containing key-value pairs or a URI pointing to an external context document.
When working with verifiable credentials, context is essential in ensuring that the properties and values within the credential can be consistently understood and processed by different systems and applications.
Context sets the meaning of Data, not a data structure.
{
"@context": {
"ContentLike": {
"@id": "https://schema.affinidi.com/ContentLikeV1-0.jsonld",
"@context": {
"@version": 1.1,
"@protected": true
}
},
"data": {
"@id": "schema-id:data",
"@context": {
"url": {
"@id": "schema-id:url",
"@type": "https://schema.org/URL"
},
"date": {
"@id": "schema-id:date",
"@type": "https://schema.org/DateTime"
},
"score": {
"@id": "schema-id:score",
"@type": "https://schema.org/Number"
},
"like": {
"@id": "schema-id:like",
"@type": "https://schema.org/Boolean"
}
}
}
}
}
Linked Data and RDF Triplets
Linked Data and RDF (Resource Description Framework) triples are closely related concepts aimed at representing structured data and relationships in a machine-readable way for the Semantic Web. Let’s discuss these concepts and how they relate to each other.
RDF (Resource Description Framework):
RDF is a W3C standard for modeling and representing information about resources on the web. It provides a flexible and extensible way to describe relationships between resources using simple, subject-predicate-object statements called triples. RDF is one of the core technologies enabling the Semantic Web, as it allows applications to merge, query, and consume data easily.
RDF Triples
An RDF triple is the fundamental building block of RDF data, representing a single statement about a resource in the form of a subject-predicate-object structure:
- Subject: The resource that the statement is about. A URI usually represents it.
- Predicate: The property or relationship that connects the subject to the object. A URI also represents it.
- Object: The value or resource the subject relates to via the predicate. It can be either a URI or a literal value.
RDF triples can be serialized in various formats, such as RDF/XML, Turtle, N-Triples, and JSON-LD. Each design has its way of representing the subject-predicate-object structure, but they all convey the same information.
Relationship between Linked Data and RDF Triples
Linked Data and RDF triples work together to create the Semantic Web by representing and connecting structured data in a standardized, machine-readable manner. RDF triples provide a way to express statements and relationships between resources, while Linked Data principles ensure that these resources and their relationships are accessible and discoverable on the web.
We could represent a VC as a RDF graph or even set of connected Graphs
JSON-LD expansion
JSON-LD expansion is a process that transforms a JSON-LD document into a more canonical and general form called “expanded form”. In this process, the document’s terms are replaced with their fully qualified IRIs, and the context is removed. The expanded form ensures consistent interpretation of the JSON-LD data and is particularly useful for merging or comparing different JSON-LD documents.
During the expansion process, the following transformations are applied:
- Terms are replaced with their corresponding IRIs as defined in the “@context”.
- Language tags and data types are explicitly specified for values.
- Compacted forms of arrays, lists, and sets are transformed into their standard representations.
- The “@context” is removed from the document.
[
{
"https://www.w3.org/2018/credentials#credentialSchema": [
{
"@id": "https://schema.affinidi.com/ContentLikeV1-0.json",
"@type": [
"https://www.w3.org/2018/credentials#JsonSchemaValidator2018"
]
}
],
"https://www.w3.org/2018/credentials#credentialSubject": [
{
"schema-id:data": [
{
"@type": [
"https://www.w3.org/2018/credentials#VerifiableCredential",
"https://schema.affinidi.com/ContentLikeV1-0.jsonld"
],
"schema-id:date": [
{
"@type": "https://schema.org/DateTime",
"@value": "2023-04-06T15:05:58.830Z"
}
],
"schema-id:like": [
{
"@type": "https://schema.org/Boolean",
"@value": true
}
],
"schema-id:score": [
{
"@type": "https://schema.org/Number",
"@value": 10
}
],
"schema-id:url": [
{
"@type": "https://schema.org/URL",
"@value": "https://www.youtube.com/watch?v=owbkzvLhblk"
}
]
}
]
}
],
"https://www.w3.org/2018/credentials#expirationDate": [
{
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2065-09-10T00:00:00.000Z"
}
],
"https://www.w3.org/2018/credentials#holder": [
{
"@id": "did:web:volland.github.io:sdk-notebooks"
}
],
"@id": "claimId:uv2kchuqy6",
"https://www.w3.org/2018/credentials#issuanceDate": [
{
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2023-04-06T15:05:58.830Z"
}
],
"https://www.w3.org/2018/credentials#issuer": [
{
"@id": "did:web:volland.github.io:sdk-notebooks"
}
],
"https://w3id.org/security#proof": [
{
"@graph": [
{
"http://purl.org/dc/terms/created": [
{
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2023-04-06T15:05:58Z"
}
],
"https://w3id.org/security#jws": [
{
"@value": "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..paG9PLEkUKiNeCtXpuOUylUWmnN5AAIxlo3iHrn8kIt1dp_mCcYV-gc4q4DVh2w1PPUF-g4ANMq1-zsD4bw_ag"
}
],
"https://w3id.org/security#proofPurpose": [
{
"@id": "https://w3id.org/security#assertionMethod"
}
],
"@type": [
"https://w3id.org/security#EcdsaSecp256k1Signature2019"
],
"https://w3id.org/security#verificationMethod": [
{
"@id": "did:web:volland.github.io:sdk-notebooks#primary"
}
]
}
]
}
],
"@type": [
"https://www.w3.org/2018/credentials#VerifiableCredential",
"https://schema.affinidi.com/ContentLikeV1-0.jsonld"
]
}
]
Expanding a JSON-LD document is crucial in various JSON-LD processing algorithms, such as framing, merging, or comparing records. Many JSON-LD libraries and tools provide functionality for expanding JSON-LD documents programmatically.
The expanded format allows data merging and normalization of data, the same in meaning but different in shape.
Wallet as a Database
We know that PEX allows us to ask questions about VCs in the user's wallet and request VCs from the user. You could go deep dive into how PEX works in my article.
{
id: uuid(),
input_descriptors: [
{
id: 'score',
name: 'videos with score ',
purpose: 'we want all videos that have score bigger that 7',
constraints: {
fields: [
{
path: [
'$.credentialSubject.data.score',
],
purpose: 'score',
filter: {
type: 'number',
minimum: 7
}
}
]
}
},
{
id: 'likes',
name: 'only a liked videos',
purpose: 'with likes',
constraints: {
fields: [
{
path: [
'$.credentialSubject.data.like',
],
filter: {
type: 'boolean',
},
}
]
}
}
]
}
The more challenging question is how to ask for a maximum of scores or three latest VCs, etc. So how to run queries over VCs in a wallet?
We could convert VCs to RDF and use a SPARQL for complex queries
PREFIX schema-id: <schema-id:>
PREFIX schema: https://schema.affinidi.com/ContentLikeV1-0.jsonld
SELECT ?vc ?url ?date ?maxScore
WHERE {
?vc schema-id:data ?data .
?data schema-id:url ?url .
?data schema-id:date ?date .
?data schema-id:score ?score .
FILTER NOT EXISTS {
?otherVc schema-id:data ?otherData .
?otherData schema-id:score ?otherScore .
FILTER (?otherScore > ?score)
}
}
Data Structure — JSON schema
The context focused on meaning, but we still need something to validate a structure and apply constraints on fields. The best part of the VC standard — it is allowed to have JSON-Schema out of the box.
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema.affinidi.com/ContentLikeV1-0.json",
"$metadata": {
"version": 1,
"revision": 0,
"discoverable": true,
"uris": {
"jsonLdContext": "https://schema.affinidi.com/ContentLikeV1-0.jsonld",
"jsonSchema": "https://schema.affinidi.com/ContentLikeV1-0.json"
}
},
"title": "ContentLike",
"description": "Content creator like ",
"type": "object",
"required": [
"@context",
"type",
"issuer",
"issuanceDate",
"credentialSubject"
],
"properties": {
"@context": {
"type": [
"string",
"array",
"object"
]
},
"id": {
"type": "string",
"format": "uri"
},
"type": {
"type": [
"string",
"array"
],
"items": {
"type": "string"
}
},
"issuer": {
"type": [
"string",
"object"
],
"format": "uri",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"format": "uri"
}
}
},
"issuanceDate": {
"type": "string",
"format": "date-time"
},
"expirationDate": {
"type": "string",
"format": "date-time"
},
"credentialSubject": {
"type": "object",
"properties": {
"data": {
"title": "data",
"type": "object",
"description": "",
"properties": {
"url": {
"title": "url",
"type": "string",
"format": "uri",
"description": "content url"
},
"date": {
"title": "date",
"type": "string",
"format": "date-time",
"description": ""
},
"score": {
"title": "score",
"type": "number",
"description": ""
},
"like": {
"title": "like",
"type": "boolean",
"description": ""
}
},
"required": [
"url"
]
}
},
"required": [
]
},
"credentialSchema": {
"type": "object",
"required": [
"id",
"type"
],
"properties": {
"id": {
"type": "string",
"format": "uri"
},
"type": {
"type": "string"
}
}
}
}
}