Ana içeriğe geç

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. 📊

🌟 A Glimpse at CType 🌟
  • 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"
}
ipucu

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:

KeyValue
$idThis is the KILT identity of the CType. It represents the digital fingerprint of the CType, making it the most crucial attribute.
$schemaA reference to the meta-schema that describes how a CType might look. There are two versions.
titleThe title of the CType.
propertiesThe attributes a claim conforming to this CType can have.
typeThe type for all CTypes is an object.
additionalPropertiesSet 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:

CTypeSchema
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

generateCType
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

generateCType
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:

  1. attesterAccount: Kilt.KiltKeyringPair: This represents the key pair of the KILT account that will be used to sign transactions.
  2. attesterDid: Kilt.DidUri: Represents the Decentralized Identifier (DID) URI that will be used to authorize transactions.
  3. signCallback: Kilt.SignExtrinsicCallback: This represents the callback function used to sign the transaction.
not

Promise<Kilt.ICType>: The function returns a Promise that delivers the created or already existing CType.

Retrieving the KILT API

generateCType
const api = Kilt.ConfigService.get('api');

This retrieves the KILT API, which will be used to interact with the blockchain.

Getting the CType Schema

generateCType
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

generateCType
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 Not Registered?

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.

generateCType
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 with CType.toChain().
  • tx: To add the CType to the chain, we must prepare its transfer. We can schedule this transfer with the tx.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 the attesterDid 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.

generateCType
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

generateCType
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

generateCType
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

generateCType
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 Take a General Look

Let's look at how the generateCType.ts file we wrote operates with all the code together.

generateCType
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.

Don't Forget

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
What's Next?

Alright, now that we've created the CType, we need a Claimer individual who will request an attestation from us.