UNPKG

vc-notarization

Version:

Notarize verifiable credentials on DLT for provability and order

105 lines (104 loc) 4.43 kB
import { MAX_TAG_LENGTH, sendData, SingleNodeClient } from "shimmer"; import { Converter } from "@iota/util.js"; import { LocalPowProvider } from "@iota/iota.js"; import { DLT } from '../enums.js'; /** * Startdust update allows proof of inclusion https://wiki.iota.org/shimmer/tutorials/proof-inclusion-of-a-block */ function wait(time) { return new Promise(resolve => setTimeout(resolve, time)); } function getNodeHeaders() { const jwt = process.env.SHIMMER_NODE_JWT; if (jwt) return { 'Authorization': 'Bearer ' + process.env.SHIMMER_NODE_JWT, 'Content-Type': 'application/json' }; return {}; } function getPOWProvider() { const powProvider = process.env.SHIMMER_POW; if (powProvider == 'remote') return undefined; return new LocalPowProvider(); } /** * Converts the index string into bytes and trims it if necessary * @param index * @returns */ function getIndexBytes(index) { const indexBytes = Converter.utf8ToBytes(index); if (indexBytes.length > MAX_TAG_LENGTH) return indexBytes.subarray(0, MAX_TAG_LENGTH - 1); return indexBytes; } export class Shimmer { dlt = DLT.SHIMMER; nodeURL = process.env.SHIMMER_NODE_URL || "https://api.testnet.shimmer.network"; client = new SingleNodeClient(this.nodeURL, { headers: getNodeHeaders(), powProvider: getPOWProvider() }); explorerURL = process.env.SHIMMER_EXPLORER_URL || "https://explorer.shimmer.network/testnet/block/"; // Check for block confirmation and return proof of inclusion, if confirmed after n tries async getNotarization(blockId) { try { let i = 0; while (i < 10) { i++; // Function waits for a certain time between iterations await wait(2000); const blockMetadata = await this.client.blockMetadata(blockId); // If a block was referenced by a milestone, the metadata contains the field 'referencedByMilestoneIndex' if ('referencedByMilestoneIndex' in blockMetadata) { // Call "create" endpoint of PoI plugin with blockId and return the result // Not available on public node -> spin up own node const poiPluginUrl = `${this.nodeURL}/api/poi/v1/create/${blockId}`; const response = await fetch(poiPluginUrl, { headers: getNodeHeaders() }); const result = await response.json(); return result; } } console.warn(`Block was not referenced by a milestone after ${i} seconds. Could not retieve a proof of inclusion.`); return undefined; } catch (error) { console.log(error); } } async notarizeHash(hash, index) { const indexBytes = getIndexBytes(index || hash); const sendResult = await sendData(this.client, indexBytes, Converter.utf8ToBytes(hash)); const proofOfInlcusion = await this.getNotarization(sendResult.blockId); return { dlt: this.dlt, transactionId: sendResult.blockId, proof: proofOfInlcusion, explorerURL: this.explorerURL + sendResult.blockId }; } async getNotarizedTimestamp(hash, index, proof) { if (!proof) throw new Error('Shimmer requires a proof of inclusion object in order retrieve a notarized timestamp!'); const indexHex = '0x' + Converter.bytesToHex(getIndexBytes(index || hash)); const hashHex = '0x' + Converter.utf8ToHex(hash); if (proof.block.payload.tag !== indexHex || proof.block.payload.data !== hashHex) throw new Error('Proof is not matching the requested hash!'); // use the private node's proof of inclusion endpoint const poiPluginUrl = `${this.nodeURL}/api/poi/v1/validate`; const response = await fetch(poiPluginUrl, { method: 'POST', body: JSON.stringify(proof), headers: getNodeHeaders(), }); const result = await response.json(); if (!result.valid) throw new Error('Proof of inclusion failed!'); return new Date(proof.milestone.timestamp * 1000); } async getStatus() { return { node: await this.client.info(), health: await this.client.health(), }; } }