CType 🎯
CType
, or Claim Type, is a specific term for the KILT protocol. In essence, it's a simple concept: CType
is the data model of a claim
. 📊
KILT provides self-sovereign, verifiable credentials and DIDs for businesses and consumers. 🛡️ But what kind of credentials are we talking about? 🤔🔐
How can we store our credentials? 🗂️ Is there a specific form we need to fill out? 📝 We'll answer all these questions! 😎👍
Let's say you want to get some data verified. 📋 This data contains smaller pieces like your name, location. So, how will you present this data to a verifier? 🤷♂️ In which template? 📑
Meet CTypes! 🎉 CTypes are the schemas of credentials. 📋 They can serve as the "form-building tools" you need. 🛠️
CTypes are KILT-specific data types that define the structure of a claim, including its data model. 📊✅ They are based on JSON Schema, which is a standard used to describe and validate JSON documents. 📜
You can create your own CTypes 🛠️ and store them on-chain or use existing CTypes on-chain to meet your needs. 💡🔗
Attesters, before making an attest
or verification
, must determine which CType
format they will support. 🎯 For instance, driving schools can only verify related to driving licenses; they can't verify something related to the living area of a house. 🚗🏠
CTypes are intended to be standardized. 🎯 It's highly recommended to use existing ones before creating a new CType. 👍
In the workshop, we'll create our own CType to learn the process! 🛠️🎉
CType ensures that all necessary information is present within a credential
. 🛡️ For instance, a driving license contains birth date, name, and the type of vehicle one is allowed to drive. 🚗📅 These pieces of information are necessary for the police officer checking our license to verify us. 🚓👮♂️
To create a CType, a full DID
is required on-chain. 🆔 Also, we should ensure that there are PILT
coins in this account because creating a CType will charge us a certain amount. 💰
Let's look at an example CType
For a better understanding, let's examine an example CType, and for this, we can again use the driving license as an example:
{
"$id": "kilt:CType:0x4f1d68ac46daf4613181b33b16faaf10cf94879dc2246d7485dc2ccbb843641d",
"$schema": "ipfs://bafybeiah66wbkhqbqn7idkostj2iqyan2tstc4tpqt65udlhimd7hcxjyq/",
"additionalProperties": false,
"properties": {
"age": {
"type": "integer"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"title": "Drivers License by did:kilt:4t9FPVbcN42UMxt3Z2Y4Wx38qPL8bLduAB11gLZSwn5hVEfH",
"type": "object"
}
If you look at the code structure above, you can see that it resembles the JSON
format, and you'd be right. At their core, CTypes are designed as JavaScript Objects (JSON
).
Breaking down the attributes line by line:
Key | Value |
---|---|
$id | This is the KILT identity of the CType. It represents the digital fingerprint of the CType, making it the most crucial attribute. |
$schema | A reference to the meta-schema that describes how a CType might look. There are two versions. |
title | The title of the CType. |
properties | The attributes a claim conforming to this CType can have. |
type | The type for all CTypes is an object. |
additionalProperties | Set to false by default. This restricts the inclusion of unwanted attributes in a claim. |
CTypes are stored on the KILT Blockchain.
In real life, we would need to fetch CTypes from an existing repository or chain.
In this workshop, our Attester
entity will create its own CType and upload it to the test chain.
Creating a CType
To create a CType, we can establish a structure as shown below:
import * as Kilt from '@kiltprotocol/sdk-js'
// Return CType with the properties matching a given schema.
export function getCTypeSchema(): Kilt.ICType {
return Kilt.CType.fromProperties('Drivers License', {
name: {
type: 'string'
},
age: {
type: 'integer'
}
})
}
Breaking down how this code works:
- We start by importing the KILT-SDK into our code.
- Next, we define a function named
getCTypeSchema
. - Finally, we retrieve the necessary information and convert it into a CType using the
CType.fromProperties
function.
Retrieving the CType
Now, we can write our code to check if the CTypes exist, and if not, create them.
Adding Libraries
import { config as envConfig } from 'dotenv'
import * as Kilt from '@kiltprotocol/sdk-js'
import { generateAccount } from './generateAccount'
import { generateKeypairs } from './generateKeypairs'
import { getCTypeSchema } from './CTypeSchema'
Upon examining the imported packages, we see some familiar ones. The getCTypeSchema
might catch our eye, which accesses the getCTypeSchema
method from the previously written CTypeSchema.ts
file.
The ensureStoredCType
Function
export async function ensureStoredCType(
attesterAccount: Kilt.KiltKeyringPair,
attesterDid: Kilt.DidUri,
signCallback: Kilt.SignExtrinsicCallback
): Promise<Kilt.ICType> {
// ...
}
The ensureStoredCType
function takes three parameters and returns a Promise
. The function's purpose is to check if a CType (Claim Type) is already registered on the KILT blockchain, and if not, to create it. Looking at these parameters:
attesterAccount: Kilt.KiltKeyringPair
: This represents the key pair of the KILT account that will be used to sign transactions.attesterDid: Kilt.DidUri
: Represents the Decentralized Identifier (DID) URI that will be used to authorize transactions.signCallback: Kilt.SignExtrinsicCallback
: This represents the callback function used to sign the transaction.
Promise<Kilt.ICType>
: The function returns a Promise
that delivers the created or already existing CType.
Retrieving the KILT API
const api = Kilt.ConfigService.get('api');
This retrieves the KILT API, which will be used to interact with the blockchain.
Getting the CType Schema
const CType = getCTypeSchema();
This retrieves a previously defined CType schema. It fetches this schema from the CTypeSchema.ts
file we wrote earlier and integrates it into our project.
Checking if the CType Exists
try {
await Kilt.CType.verifyStored(CType)
console.log('CType already stored. Skipping creation')
return CType
} catch {
// ...
}
The types we're about to create might already exist on the chain. Adding a CType to the chain again would incur unnecessary costs in such cases. To prevent this, we can use the CType.verifyStored()
method within a try
block to check if our schema is already stored. The user is informed if it's already stored, and the CType
is returned.
If the CType isn't registered, the catch
block will be executed.
Creating and Storing the CType
If we're sure the CType isn't on the chain, we can add our created CType to the chain.
console.log('CType not present. Creating it now...');
const encodedCType = Kilt.CType.toChain(CType);
const tx = api.tx.CType.add(encodedCType);
const extrinsic = await Kilt.Did.authorizeTx(
attesterDid,
tx,
signCallback,
attesterAccount.address
);
await Kilt.Blockchain.signAndSubmitTx(extrinsic, attesterAccount);
return CType;
Breaking down the code:
encodedCType
: We created the CType as a schema, but we need to encode it in a format that can be added to the chain. We can achieve this withCType.toChain()
.tx
: To add the CType to the chain, we must prepare its transfer. We can schedule this transfer with thetx.CType.add
function.extrinsic
: We use this to format the transaction appropriately for interaction with the chain. Since we'll interact with the chain, we must use theattesterDid
value.signAndSubmitTx
: Finally, we sign the transaction and submit it to the chain.
Main Code Block
Like the others in the code, this section indicates how the generateCType.ts
file will operate if directly invoked.
if (require.main === module) {
;(async () => {
// ...
})()
}
We use an if
structure to check if the code is being run directly. If it is, the code block underneath will execute.
Environment Variables and Connection
envConfig()
await Kilt.connect(process.env.WSS_ADDRESS as string)
Environment variables are loaded, and a connection is established to the KILT blockchain.
Account and DID Creation
const accountMnemonic = process.env.ATTESTER_ACCOUNT_MNEMONIC as string
const { account } = generateAccount(accountMnemonic)
const didMnemonic = process.env.ATTESTER_DID_MNEMONIC as string
const { authentication, assertionMethod } = generateKeypairs(didMnemonic)
const attesterDidUri = Kilt.Did.getFullDidUriFromKey(authentication)
An account and DID (Decentralized Identifier) are created using a mnemonic.
Calling the ensureStoredCType
Function
await ensureStoredCType(account, attesterDidUri, async ({ data }) => ({
signature: assertionMethod.sign(data),
keyType: assertionMethod.type
}))
The ensureStoredCType
function is called, and the necessary processes are performed.
Let's look at how the generateCType.ts
file we wrote operates with all the code together.
import { config as envConfig } from 'dotenv'
import * as Kilt from '@kiltprotocol/sdk-js'
import { generateAccount } from './generateAccount'
import { generateKeypairs } from './generateKeypairs'
import { getCtypeSchema } from './ctypeSchema'
export async function ensureStoredCtype(
attesterAccount: Kilt.KiltKeyringPair,
attesterDid: Kilt.DidUri,
signCallback: Kilt.SignExtrinsicCallback
): Promise<Kilt.ICType> {
const api = Kilt.ConfigService.get('api')
// Get the CTYPE and see if it's stored, if yes return it.
const ctype = getCtypeSchema()
try {
await Kilt.CType.verifyStored(ctype)
console.log('Ctype already stored. Skipping creation')
return ctype
} catch {
console.log('Ctype not present. Creating it now...')
// Authorize the tx.
const encodedCtype = Kilt.CType.toChain(ctype)
const tx = api.tx.ctype.add(encodedCtype)
const extrinsic = await Kilt.Did.authorizeTx(
attesterDid,
tx,
signCallback,
attesterAccount.address
)
// Write to chain then return the CType.
await Kilt.Blockchain.signAndSubmitTx(extrinsic, attesterAccount)
return ctype
}
}
// Don't execute if another file imports this.
if (require.main === module) {
;(async () => {
envConfig()
try {
await Kilt.connect(process.env.WSS_ADDRESS as string)
const accountMnemonic = process.env.ATTESTER_ACCOUNT_MNEMONIC as string
const { account } = generateAccount(accountMnemonic)
const didMnemonic = process.env.ATTESTER_DID_MNEMONIC as string
const { authentication, assertionMethod } = generateKeypairs(didMnemonic)
const attesterDidUri = Kilt.Did.getFullDidUriFromKey(authentication)
await ensureStoredCtype(account, attesterDidUri, async ({ data }) => ({
signature: assertionMethod.sign(data),
keyType: assertionMethod.type
}))
} catch (e) {
console.log('Error while checking on chain ctype')
throw e
}
})()
}
When examining the code in order:
- We start by importing the necessary packages.
- We fetch our schema from the
CTypeSchema.ts
file and integrate it into our code. - We check if the CType we want to create is already on the chain.
- If it's not on the chain, we write the code to add it.
- If the file is run on its own, we write the CType operations to be performed.
In this way, we wrote code that checks if the CType we want to create is on the chain and creates it if it's not.
We need to pay a deposit to add to the chain. So, we need PILT Coins, don't forget.
Execution
To run our code, ensure you're in the kilt-rocks
folder in the terminal, then execute the following command:
yarn ts-node attester/generateCType.ts
Alright, now that we've created the CType, we need a Claimer
individual who will request an attestation
from us.