Skip to main content

Generate a Message

KILT defines a unicast messaging protocol

Each of the messages sent is encrypted using the DID key agreement key. A message consists of the sender's DID URI, the receiver's DID URI, the message type and the body. There are multiple different message types, each of them with a different structure and containing different information. In this example we are going to build a request-credential message. The message structure is checked and validated on by the KILT SDK to ensure the users are sending correctly structured messages.

The following example here will generate a message by constructing the message content. The message content includes a valid cTypeHash and a list of trusted attesters. The message requires a messageBody, sender and receiver uri.

import * as Kilt from '@kiltprotocol/sdk-js'

export async function generateRequestCredentialMessage(
senderUri: Kilt.DidUri,
receiverUri: Kilt.DidUri,
cTypeHash: Kilt.CTypeHash
) {
// Creating a challenge to submit to the receiver
const challenge = Kilt.Utils.UUID.generate()

// Sender uri is checked if it is a valid URI
Kilt.Did.validateUri(senderUri)
// Receiver uri is checked if it is a valid URI
Kilt.Did.validateUri(receiverUri)

// The content of the 'request-credential' message
// It includes a CType that is being requested, this can be for attestation or verification
// The sender is the trusted attester in the scenario
const requestCredentialContent = {
cTypeHash: cTypeHash,
trustedAttesters: [senderUri]
}

const messageBody: Kilt.IRequestCredential = {
type: 'request-credential',
content: { cTypes: [requestCredentialContent], challenge: challenge }
}

// The message will throw an Error if invalid
const message = Kilt.Message.fromBody(messageBody, senderUri, receiverUri)

console.log(`Generated message: ${JSON.stringify(message, null, 4)}`)

return message
}

Encryption

The messages data are encrypted and decrypted using nacl's 'x25519-xsalsa20-poly1305' algorithm, which provides repudiable authenticated encryption based on an x25519 key agreement protocol. The DID holds keys for the encryption and decryption. The key is called KeyAgreement keys. They may also be known as encryption keys.

The content of the object is converted from a serialized string to a byte array, which is passed into the callback function along with the sender's DID and key agreement public key of the receiver.

The following example here will take a generated message and encrypt the message for the receiver to decrypt later.

import * as Kilt from '@kiltprotocol/sdk-js'
import { useEncryptionCallback } from '../signCallback/useEncryptionCallback'

export async function encryptMessage(
message: Kilt.IMessage,
senderUri: Kilt.DidUri,
receiverUri: Kilt.DidUri,
keyAgreement: Kilt.KiltEncryptionKeypair
): Promise<Kilt.IEncryptedMessage> {
const { document: senderDocument } = await Kilt.Did.resolve(senderUri)

const { document: receiverDocument } = await Kilt.Did.resolve(receiverUri)

const receiverKeyAgreementUri =
`${receiverUri}${receiverDocument.keyAgreement?.[0].id}` as Kilt.DidResourceUri
const senderKeyAgreeementUri =
`${senderUri}${senderDocument.keyAgreement?.[0].id}` as Kilt.DidResourceUri
// encrypt the message
const encryptedMessage = await Kilt.Message.encrypt(
message,
useEncryptionCallback({
keyAgreement,
keyAgreementUri: senderKeyAgreeementUri
}),
receiverKeyAgreementUri
)

console.log(`Encrypted Message: ${JSON.stringify(encryptedMessage, null, 4)}`)

return encryptedMessage
}

The encrypted data is converted into a hex string which is known as the ciphertext along with the nonce that was generated during encryption.

Decryption

The decryption takes the encrypted message and decyphers its content. The following example here will take a encrypted message and decrypt using the private key of the receiver. Once decrypted, it checks the content is a valid message. The decrypted data can be used for additional steps. After decrypting, the receiver may wish to present a credential from the trusted attester list with a given CType.

import * as Kilt from '@kiltprotocol/sdk-js'
import { useDecryptionCallback } from '../signCallback/useDecryptionCallback'

export async function decryptMessage(
encryptedMessage: Kilt.IEncryptedMessage,
keyAgreement: Kilt.KiltEncryptionKeypair
): Promise<Kilt.IMessage> {
// Decrypting the message to retrieve the content
const decryptedMessage = await Kilt.Message.decrypt(
encryptedMessage,
useDecryptionCallback(keyAgreement)
)

// Verifying this is a properly-formatted message
Kilt.Message.verify(decryptedMessage)

console.log(`Decrypted Message: ${JSON.stringify(decryptedMessage, null, 4)}`)

// Checking if the message type matches the expected checks
if (decryptedMessage.body.type !== 'request-credential') {
throw new Error('Not the correct body type')
}

// Destructing the message to receive the cTypes array to see what credentials
// Are valid for the given request
const { cTypes } = decryptedMessage.body.content

const { cTypeHash, trustedAttesters } = cTypes[0]

// The receiver can check if they have a valid credential that matches the cTypeHash
console.log('The sent cType hash :', cTypeHash)

// The trusted attesters is an array that includes the list of trusted entities
// The receiver can check if they have a given credential from the trusted list
console.log(`A list of trusted attesters DID :${trustedAttesters}`)

return decryptedMessage
}