issue

In this tutorial, you will learn how to send a VC to a specific 'holder'. The process of sending VC is VC Issue mode.

Complete code example: zkid-sdk-example /src/issue/issue.ts

Tutorial

  1. Initialize @noble cryptographic library and wasm;

await initCrypto();
  1. Get DID object of holder and attester;

// get holder DID from DidDocument
const holderDidDoc = await resolver.resolve(holderDidUrl);
const holder = fromDidDocument(holderDidDoc);

// get attester DID from DID-Keys-file
const keyring = new Keyring();
const json = readDidKeysFile();
const password = "12345678"; // password to decrypt your DID-keys-file
const attester = restore(keyring, json, password);
  
// src/utils/resolverHelper.ts
export const resolver = new ArweaveDidResolver();

// src/utils/didHelper.ts
export function readDidKeysFile() {
  const attesterKeysFile = fs.readFileSync(
    path.resolve(__dirname, "../../attester-DID-keys-file.json"),
    { encoding: "utf-8" }
  );
  return JSON.parse(attesterKeysFile) as DidKeys$Json;
}

In the above process, the use of the following three APIs is mainly involved:

  • Get resolver object, using resolve method get DID document from DID URL; The resolver can be some APIs parameters. For this kind of API, developers should explicitly specify resolver in their use, especially if you are developing in our development environment. This is because our resolver connects to the production environment by default, and if you don't specify a resolver, it may occurs the DID Method won't be found problem.

const resolver = new ArweaveDidResolver();
const holderDidDoc = await resolver.resolve(holderDidUrl);
  • Recover DID object by DID Document. The corresponding method is fromDidDocument. The DID document is obtained through the resolver's resolve interface.

fromDidDocument(document: DidDocument, keyring?: KeyringInstance)
  • Recover DID object by DID-keys-file. The corresponding method is restore. This method recovers DID object by inputing corresponding DID-keys-file contents and password where the password parameter is the password set when backing up the DID-keys-file;

 restore(keyring: Keyring, json: DidKeys$Json, password: string)

tips: In addition to recovering the DID using the above methods, we also provide an API to recover the DID via mnemonics:

fromMnemonic(keyring: KeyringInstance, mnemonic: string, signingKeyType?: 'ecdsa' | 'ed25519', index?: number)
  1. Get ctype object from ctype hash;

const ctype: CType = await getCtypeFromHash(ctypeHash);

// src/utils/ctypeHelper.ts
export async function getCtypeFromHash(
  hash: string | undefined,
  url = process.env.BASE_URL
): Promise<CType> {
  if (hash === undefined) {
    throw new Error("ctype hash undefined !!!");
  }

  const res = await axios.get(`${url}/ctype?${qs.stringify({ id: hash })}`);
  if (res.status !== 200) {
    throw new Error(`ctype query failed ${hash}`);
  }
  const ctype: CType = res.data.data.rawData;
  return ctype;
}

In this step, ctypeHash will be used as the query parameter of the HTTP GET request to get the ctype object.

  1. Build the Raw object. In this step, we build a Raw object which is used for the subsequent construction of the RawCredential. The following explains each property of the Raw object:

    1. contents: The body of the credential;

    2. owner: holder/claimer, credential receiver;

    3. ctype: The ctype object used for this credential;

    4. hashType: Encryption algorithm type, here choose Keccak256 (we also support Blake2, Blake3, RescuePrimeOptimized and other encryption algorithms. Note: Considering that Keccak256 has the highest hash efficiency on chain, it is recommended to use Keccak256 as the hash when building Raw if your vc usage scenario does not include zk computation, otherwise use RescuePrimeOptimized hash).

const raw = new Raw({
  contents: {
    id: 9870456,
    name: "vss-holder",
  },
  owner: holderDidUrl,
  ctype: ctype,
  hashType: "Keccak256",
});
  1. Build the RawCredential object. In this step, we call toRawCredential API to generate RawCredential object based on the Raw object generated in the previous step. The encryption algorithm used in this step is Keccak256 by default (meanwhile, we also support other encryption algorithms, which are the same as the ones available for building Raw);

const rawCredential: RawCredential = raw.toRawCredential("Keccak256");
  1. Build the vcBuilder object. In this step, we build a vcBuilder object from which subsequent VCs can be built. vcBuilder provides several methods. For general purpose VCs, it is usually set to never expire and the issue time is set to the current time;

const vcBuilder = VerifiableCredentialBuilder.fromRawCredential(
    rawCredential,
    ctype
  )
    .setExpirationDate(null)
    .setIssuanceDate(Date.now());
  1. Build the VC object. In this step, you will use the build API in vcBuilder to build the VC. The two parameters of this API are described below:

    1. issuer: issuer's DID object;

    2. isPublic: true/false, when this parameter is false, the generated VC is private VC. If it is true, then public VC is generated.

const vc: VerifiableCredential<false> = await vcBuilder.build(
  attester,
  false
);
  1. Constructing encrypted messages using VC as message data. We will use the encryptMessage API to generate encrypted messages with the API parameters:

    1. type: Message type, corresponding to the VC's issue mode, is "Send_issuedVC";

    2. data: Message data, here is VC;

    3. sender: Message sender's DID object, here is VC issuer;

    4. receiverUrl: Message receiver's keyAgreement DID URL;

    5. reply: Prefixed message id, this parameter is only used for responding to message scenarios;

    6. resolver: DID resolver.

const message = await encryptMessage(
    "Send_issuedVC",
    vc,
    attester,
    holder.getKeyUrl("keyAgreement"),
    undefined,
    resolver
  );
  1. Send the encrypted message to the server. This process initiates an HTTP POST request.

await sendMessage2Server(message);

// src/utils/messageHelper.ts
export async function sendMessage2Server(
  message: any,
  templateId = -1,
  token = null,
  url = process.env.BASE_URL
): Promise<void> {
  const sendRes = await axios.post(`${url}/message`, {
    templateId,
    msg: message,
    token,
  });
  if (sendRes.status === 200) {
    console.log(`SUCCESS: send encrypted message to server`);
  } else {
    console.log(`send encrypted message response status: ${sendRes.status}`);
  }
}

Well, by following the 9 steps above, you will learn how to utilize the Issue mode of VC to issue a VC directly to the target user.

Last updated