attest

In the previous post, we, as Claimer, made a request to Attester to issue a VC. This time we will be responding to the previous request as Attester, so are you ready, Attesters? Let's get going.

Complete code example: zkid-sdk-example /src/claim-attest/attest.ts

Tutorial

  1. Initialize @noble cryptographic library and wasm;

await initCrypto();
  1. Get attester's DID object from mnemonic;

const mnemonic = process.env.ATTESTER_MNEMONIC as string;
const keyring = new Keyring();
const attester = keys.fromMnemonic(keyring, mnemonic, "ecdsa");

The method used to recover a DID object from a mnemonic is fromMnemonic, which takes two mandatory arguments, a keyring object and a string mnemonic, where the keyring object is used to manage the keypair;

fromMnemonic(keyring: KeyringInstance, mnemonic: string, signingKeyType?: 'ecdsa' | 'ed25519', index?: number)
  1. Get the latest message from the server with receiver is attester and message type is Request_Attestation;

// get latest message
const serverMsg = await getMessage(attester, "Request_Attestation");

// src/utils/messageHelper.ts
export async function getMessage(
  receiver: Did,
  msgType: string,
  page = 1,
  size = 1,
  url = process.env.BASE_URL
) {
  const param = qs.stringify({
    page,
    size,
    receiver: receiver.getKeyUrl("keyAgreement"),
    msgType: msgType,
  });
  const res = await axios.get(`${url}/message/page?${param}`);

  return res.data.data.items.map((value: any) => value.rawData) as Array<
    Message<MessageType>
  >;
}
  1. The attester decrypts the encrypted message obtained from the previous step and gets the RawCredential object wrapped by the claimer;

// decrypt claim message
const decrypted = await decryptMessage(serverMsg[0], attester);

// get rawCredential from decrypt return value
const rawCredential: RawCredential = decrypted.data;
  1. Get the ctype object via ctypeHash in RawCredential;

const ctype = await getCtypeFromHash(rawCredential.ctype);

// 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;
}
  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. If the claim request is approved, the VC is used as the message data to construct the encrypted message, and the corresponding message type is "Response_Approve_Attestation", and if the request is rejected, the "data" field is set to empty and the message type is set to "Response_Reject_Attestation". When constructing the encrypted message, we will use the encryptMessage interface to generate the encrypted message with the following interface parameters:

    1. type: Message type, approval to issue VC corresponds to message type "Response_Approve_Attestation";

    2. data: Message data, here is VC;

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

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

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

    6. resolver: DID resolver.

const message = await encryptMessage(
  "Response_Approve_Attestation",
  vc,
  attester,
  decrypted.sender,
  decrypted.id
);
  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, with the introduction of this tutorial, we have learned how to respond to a claimer's request from the Attester's perspective. For general VC usage scenarios, a trusted Attester will suffice, but for decentralized and high-trust scenarios, we'll need to see how multiple Attenders can work together to endorse a VC. In the next tutorial, you will learn how to make multiple endorsements to a VC.

Last updated