Integrating CORD Blockchain into Your Project Using CORD.Js
Introduction
This demonstration focuses on employing cord.js to engage with the CORD blockchain. It highlights various techniques provided by the SDK and, more significantly, illustrates the capabilities that CORD enables as an application-specific blockchain.
What is the focus of the demonstration?
A situation in which an organization initiates the CORD blockchain involves using a front-end application to issue records to students and cord.js to interact with the blockchain. The individual responsible for issuing the documents assumes the role of an ISSUER, while the recipient of the records is referred to as a HOLDER, and the person who verifies it is called a VERIFIER. For a detailed ecosystem overview, please visit (https://docs.cord.network/techoverview/ecosystemoverview/).
How to execute this demonstration
To execute this demonstration, visit github.com/dhiway/cord.js, clone the repository, and refer to the README.md for instructions on running the demo script(func-test.ts).
Prerequisites
Begin by importing the necessary modules for this demonstration.
import * as Cord from "@cord.network/sdk";import { UUID, Crypto } from "@cord.network/utils";import { randomUUID } from "crypto";import { createDid } from "./utils/generateDid";import { createDidName } from "./utils/generateDidName";import { getDidDocFromName } from "./utils/queryDidName";import { addNetworkMember } from "./utils/createAuthorities";import { createAccount } from "./utils/createAccount";import { requestJudgement, setIdentity, setRegistrar, provideJudgement,} from "./utils/createRegistrar";
A UUID, or Universally Unique Identifier, is a 128-bit identifier that is globally unique. It is standardized by the Open Software Foundation (OSF) as part of the Distributed Computing Environment (DCE). UUIDs are typically represented as a sequence of 32 hexadecimal digits, displayed in five groups separated by hyphens. While UUIDs serve various purposes, in this demonstration, they are utilized to generate distinct identifiers.
function getChallenge(): string { return Cord.Utils.UUID.generate()}
Connection with CORD Blockchain
The Cord.connect method is employed to initiate a connection with the CORD blockchain. It requires a single argument, which is the network address or RPC endpoint. Typically, when starting a local instance, it runs at 127.0.0.1:9944. If you are not running it locally, make sure you have configured the right address.
const networkAddress = "ws://127.0.0.1:9944";await Cord.connect(networkAddress);
Setting up accounts and identites
In the contemporary digital landscape, the conduct of virtually all online interactions is contingent upon the establishment of user accounts, each serving as a distinct digital identity. Noteworthy instances of this paradigm include the necessity to create accounts on prominent platforms such as Facebook, Instagram, and various other social media channels.
Likewise, within blockchain ecosystems, the pivotal function of accounts transcends mere identity representation. Rather, these accounts assume a critical role in expediting transactions, overseeing asset management, and upholding the security and integrity of the decentralized ledger. In essence, the establishment and utilization of accounts are fundamental components in the orchestration of secure and accountable blockchain activities.
Having gained insight into the significance of accounts, it is crucial to delve into distinct identities that assume varied roles in the context of this demonstration. The “authorityAuthorIdentity” functions akin to a sudo account. When setting up a blockchain node, the configuration involves specifying an anchorUri, such as //Alice in this instance, through which the sudo account is instantiated.
// Step 1: Setup Membership// Setup transaction author account - CORD Account.console.log(`\n❄️ New Network Member`);const authorityAuthorIdentity = Cord.Utils.Crypto.makeKeypairFromUri( "//Alice", "sr25519",);
Who can write on the blockchain? Unlike other public blockchains where the write operation can be executed by anyone with sufficient funds to cover transaction fees, this also prevents unnecessary writing on the chain due to the associated high costs (transaction fees). Because CORD is a tokenless blockchain, an on-chain governance model is implemented to determine who can write on the blockchain and the extent of their writing capabilities. This not only addresses the cost concerns associated with unnecessary entries but also introduces a significant level of decentralization. You will gain more detailed insights into these aspects in the upcoming demo.
Furthermore, the Sudo account, using the setRegistrar method, introduces Registrars as the initial members of the governance council. More Registrars can be added, and existing ones can be removed from the council, providing flexibility to adapt the governance structure as needed.
// Setup network authority account.const { account: authorityIdentity } = await createAccount();console.log( `🏦 Member (${authorityIdentity.type}): ${authorityIdentity.address}`,);await addNetworkMember(authorityAuthorIdentity, authorityIdentity.address);await setRegistrar(authorityAuthorIdentity, authorityIdentity.address);console.log("✅ Network Authority created!");
Once the Registrars have been established, the next step involves adding members. A member is an individual with the authority to create chainspaces and include delegates. Anyone interested in becoming a member can create an account and participate in the governance council to get the approval. As we progress through the demo, you will gain a deeper understanding of chainspaces and delegates.
// Setup network member account.const { account: authorIdentity } = await createAccount();console.log(`🏦 Member (${authorIdentity.type}): ${authorIdentity.address}`);await addNetworkMember(authorityAuthorIdentity, authorIdentity.address);console.log(`🔏 Member permissions updated`);await setIdentity(authorIdentity);console.log(`🔏 Member identity info updated`);await requestJudgement(authorIdentity, authorityIdentity.address);console.log(`🔏 Member identity judgement requested`);await provideJudgement(authorityIdentity, authorIdentity.address);console.log(`🔏 Member identity judgement provided`);console.log("✅ Network Member added!");
Setting up identities
Up to this point, we have successfully crafted and configured the governance structure for the blockchain. Now, our focus shifts to the creation of identities, a process akin to establishing accounts. However, it is crucial to recognize that the preceding operation set a high-level hierarchy, dictating who possesses the authority to contribute to the blockchain. The earlier established accounts primarily serve a supervisory role, comparable to a school principal overseeing operations.
In the subsequent steps, we delve into the creation of specific identities, who create the credentials, hold the credentials and verify the credentials.
When engaging with a blockchain, you employ an account; similarly, you utilize a DID to interact with the CORD blockchain. The creation of a DID can occur offline, but its submission to the blockchain is imperative and is facilitated through the authorIdentity.
A DID, or Decentralized Identifier, is a new type of identifier that is created, owned, and managed by the subject of the identifier (the individual or entity). DIDs are designed to be decentralized, providing a way for entities to have self-owned, self-controlled, and verifiable identifiers on the internet.
Here we are creating DIDs for various parties invloved in this demo, for example Issuer, Holder, Verifier and chainspace Delegate.
// Step 2: Setup Identities/* Creating the DIDs for the different parties involved in the demo. */// Create Verifier DIDconst { mnemonic: verifierMnemonic, document: verifierDid } = await createDid(authorIdentity);const verifierKeys = Cord.Utils.Keys.generateKeypairs(verifierMnemonic);
// Create Holder DIDconst { mnemonic: holderMnemonic, document: holderDid } = await createDid(authorIdentity);const holderKeys = Cord.Utils.Keys.generateKeypairs(holderMnemonic);
// Create issuer DIDconst { mnemonic: issuerMnemonic, document: issuerDid } = await createDid(authorIdentity);const issuerKeys = Cord.Utils.Keys.generateKeypairs(issuerMnemonic);
const conformingDidDocument = Cord.Did.exportToDidDocument( issuerDid, "application/json",);
// Create Delegate One DIDconst { mnemonic: delegateOneMnemonic, document: delegateOneDid } = await createDid(authorIdentity);const delegateOneKeys = Cord.Utils.Keys.generateKeypairs(delegateOneMnemonic);
// Create Delegate Two DIDconst { mnemonic: delegateTwoMnemonic, document: delegateTwoDid } = await createDid(authorIdentity);const delegateTwoKeys = Cord.Utils.Keys.generateKeypairs(delegateTwoMnemonic);
// Create Delegate 3 DIDconst { mnemonic: delegate3Mnemonic, document: delegate3Did } = await createDid(authorIdentity);const delegate3Keys = Cord.Utils.Keys.generateKeypairs(delegate3Mnemonic);
A DID name serves as a straightforward alias for the DID, designed to enhance readability, ease of writing, and memorability. It should be noted that this step is not mandatory.
// Step 2: Create a DID name for Issuerconst randomDidName = `solar.sailer.${randomUUID().substring(0, 4)}@cord`;
await createDidName( issuerDid.uri, authorIdentity, randomDidName, async ({ data }) => ({ signature: issuerKeys.authentication.sign(data), keyType: issuerKeys.authentication.type, }),);
await getDidDocFromName(randomDidName);
Creating a Chain Space
Chain Space functions similarly to a folder or namespace within Kubernetes, playing a pivotal role in the CORD ecosystem. These ChainSpaces serve as dedicated areas on the blockchain, each assigned to specific data or assets, and adhere to clearly defined rules and permissions.
// Step 3: Create a new Chain Spaceconst spaceProperties = await Cord.ChainSpace.buildFromProperties( issuerDid.uri,);
// Chain Space Propertiesconst space = await Cord.ChainSpace.dispatchToChain( spaceProperties, issuerDid.uri, authorIdentity, async ({ data }) => ({ signature: issuerKeys.authentication.sign(data), keyType: issuerKeys.authentication.type, }),);
Chain Space Approval
The creation of any chain space is followed by the requirement for approval from the superuser. This approval is carried out as a sudo operation, with the “authorityAuthorIdentity” being the entity responsible in this case.
console.log(`\n❄️ Chain Space Approval `);await Cord.ChainSpace.sudoApproveChainSpace( authorityAuthorIdentity, space.uri, 100,);
Adding a chainspace delegate
A chainspace delegate is a designated entity endowed with the specified permission within a specified space on CORD. The permission empowers the delegate to sign and introduce new entries into the designated space. The inclusion of a delegate undergoes scrutiny through authorization checks, with the entire process overseen and regulated by the administrators of the space.
// Step 4: Add Delelegate Two as Registry Delegate console.log(`\n❄️ Space Delegate Authorization `) const permission: Cord.PermissionType = Cord.Permission.ASSERT const spaceAuthProperties = await Cord.ChainSpace.buildFromAuthorizationProperties( space.uri, delegateTwoDid.uri, permission, issuerDid.uri )
console.log(`\n❄️ Space Delegation To Chain `) const delegateAuth = await Cord.ChainSpace.dispatchDelegateAuthorization( spaceAuthProperties, authorIdentity, space.authorization, async ({ data }) => ({ signature: issuerKeys.capabilityDelegation.sign(data), keyType: issuerKeys.capabilityDelegation.type, }) )
Creating a schema
In this context, a schema resembles a database schema. Generating a schema is an optional step for the data intended to be anchored in the chain. An example of this can be seen in the code snippet below.
// Step 5: Create a new Schemaconsole.log(`\n❄️ Schema Creation `);let newSchemaContent = { $schema: "http://json-schema.org/draft-07/schema#", $metadata: { version: "1.0.0", slug: "test-demo-schema", discoverable: true, }, title: "Test Demo Schema v3", description: "Test Demo Schema", properties: { name: { type: "string", }, age: { type: "integer", }, id: { type: "string", }, country: { type: "string", }, address: { type: "object", properties: { street: { type: "string" }, pin: { type: "integer" }, location: { type: "object", properties: { state: { type: "string" }, country: { type: "string" }, }, }, }, }, }, type: "object",};let newSchemaName = newSchemaContent.title + ":" + Cord.Utils.UUID.generate();newSchemaContent.title = newSchemaName;
let schemaProperties = Cord.Schema.buildFromProperties( newSchemaContent, space.uri, issuerDid.uri,);
const schemaUri = await Cord.Schema.dispatchToChain( schemaProperties.schema, issuerDid.uri, authorIdentity, space.authorization, async ({ data }) => ({ signature: issuerKeys.authentication.sign(data), keyType: issuerKeys.authentication.type, }),);
Creating a statement
Within the CORD ecosystem, Statements denote particular assertions or records securely stored on the blockchain, fulfilling diverse purposes such as attestations, records, and verifiable claims.
To avoid the invasion of privacy, it is advisable not to publish data on a public network. As illustrated in the example below, a Statement is crafted utilizing the data, serving as evidence or proof of the information.
Then the statement gets anchored on the blockchain.
// Step 4: Delegate creates a new Verifiable Document console.log(`\n❄️ Statement Creation `)
let newCredContent = require('../res/cred.json') newCredContent.issuanceDate = new Date().toISOString() const serializedCred = Cord.Utils.Crypto.encodeObjectAsStr(newCredContent) const credHash = Cord.Utils.Crypto.hashStr(serializedCred)
const statementEntry = Cord.Statement.buildFromProperties( credHash, space.uri, issuerDid.uri, schemaUri as Cord.SchemaUri )
const statement = await Cord.Statement.dispatchRegisterToChain( statementEntry, issuerDid.uri, authorIdentity, space.authorization, async ({ data }) => ({ signature: issuerKeys.authentication.sign(data), keyType: issuerKeys.authentication.type, }) )
Statement updation
Imagine a situation where a person’s location changes; in such a case, if the information in the credential is updated, the corresponding statement should also be updated. CORD and cord.js facilitate this by providing the method outlined below.
console.log(`\n❄️ Statement Updation `) let updateCredContent = newCredContent updateCredContent.issuanceDate = new Date().toISOString() updateCredContent.name = 'Bachelor of Science' const serializedUpCred = Cord.Utils.Crypto.encodeObjectAsStr(updateCredContent) const upCredHash = Cord.Utils.Crypto.hashStr(serializedUpCred)
const updatedStatementEntry = Cord.Statement.buildFromUpdateProperties( statementEntry.elementUri, upCredHash, space.uri, delegateTwoDid.uri )
const updatedStatement = await Cord.Statement.dispatchUpdateToChain( updatedStatementEntry, delegateTwoDid.uri, authorIdentity, delegateAuth as Cord.AuthorizationUri, async ({ data }) => ({ signature: delegateTwoKeys.authentication.sign(data), keyType: delegateTwoKeys.authentication.type, }) )
Statement verification
Any person who wishes to verify a statement can use the below method to verify the statement.
console.log(`\n❄️ Statement verification `) const verificationResult = await Cord.Statement.verifyAgainstProperties( statementEntry.elementUri, credHash, issuerDid.uri, space.uri, schemaUri as Cord.SchemaUri )
Revoke statement
Revoking a statement refers to the act of officially nullifying or invalidating a previously made claim.
console.log(`\n❄️ Revoke Statement - ${updatedStatementEntry.elementUri}`) await Cord.Statement.dispatchRevokeToChain( updatedStatementEntry.elementUri, delegateTwoDid.uri, authorIdentity, delegateAuth as Cord.AuthorizationUri, async ({ data }) => ({ signature: delegateTwoKeys.authentication.sign(data), keyType: delegateTwoKeys.authentication.type, }) )
Restoring the statement
Restoring a statement involves reinstating a claim or assertion that was previously revoked
console.log(`\n❄️ Restore Statement - ${updatedStatementEntry.elementUri}`) await Cord.Statement.dispatchRestoreToChain( updatedStatementEntry.elementUri, delegateTwoDid.uri, authorIdentity, delegateAuth as Cord.AuthorizationUri, async ({ data }) => ({ signature: delegateTwoKeys.authentication.sign(data), keyType: delegateTwoKeys.authentication.type, }) ) console.log(`✅ Statement revoked!`)