­­Now Available: Hedera JavaScript DID SDK
Aug 02, 2021
by Daniel Norkin
Co-Founder & CEO at Envision Blockchain Solutions

The Hedera Hashgraph ecosystem has been extremely busy regarding the decentralized development of Hedera’s developer tools/services. Additionally, Hedera’s involvement in standards bodies and associated working groups, such as W3C, have created further alignment in those developments. Looking through the Hedera blog, you’ll read about all of the work being done throughout the ecosystem. Some example industries that ecosystem partners are targeting include sustainability, digital health, gaming, pharmaceutical, and many others.

While vertical solutions are important, horizontal solutions are just as imperative. A great example of a horizontal solution gaining popularity is the Hedera DID Method for Decentralized Identifiers (DIDs) and Verified Credentials (VCs). Up until now, the only way to implement the Hedera DID Method was through the Java DID SDK but many applications are built using other languages, such as JavaScript. We at Envision Blockchain are happy to announce the release of the open-source Hedera JavaScript DID SDK and continued maintenance into the foreseeable future.

A brief history of Decentralized Identifiers (DIDs) and Verified Credentials (VCs)

Before we properly introduce the Hedera JavaScript DID SDK, let’s take a step back and lay the foundation for DIDs and VCs. Some readers may be hearing about DIDs and VCs for the first time — but these identity concepts are nothing new. Data related to births was recorded as far back as the 1500s. In the early 1900s, we saw the first issuances of driver’s licenses and social security cards. And as the world evolved through digitization, so did the way we manage those identities. Digital identification on individuals and devices needed to be met with security and standards. Enter the idea of Decentralized Identifiers (DIDs) and Verified Credentials (VCs).

As written in the W3C Decentralized Identifiers (DIDs) v1.0 documentation, “Decentralized Identifiers (DIDs) are a new type of globally unique identifier that are designed to enable individuals and organizations to generate their own identifiers using systems they trust. These new identifiers enable entities to prove control over them by authenticating using cryptographic proofs such as digital signatures. Since the generation and assertion of Decentralized Identifiers is entity-controlled, each entity can have as many DIDs as necessary to maintain their desired separation of identities, personas, and interactions.”

Attached to DIDs described above are Verified Credentials (VCs). When writing about VCs, W3C explains in the Verifiable Credentials Data Model 1.0 document, “In the physical world, a credential might consist of information related to identifying a subject, information related to the issuing authority, information related to the type of credential, information related to specific attributes about the subject, and evidence related to how the credential was derived. Verified Credentials (VCs) can represent all the same information that a physical credential represents. The addition of technologies, such as digital signatures, makes VCs more tamper-evident and more trustworthy than their physical counterparts. Holders of VCs can generate verifiable presentations (VPs) and then share these VPs with verifiers to prove they possess VCs with certain characteristics. Both VCs and VPs can be transmitted rapidly, making them more convenient than their physical counterparts when trying to establish trust at a distance.”

The Hedera JavaScript DID SDK

Now that you have some background on DIDs and VCs, let’s get back to the Hedera JavaScript DID SDK. When in an implementation scenario, it’s good to have options for your development team. The only way originally to implement the Hedera DID method was using the Java DID SDK. According to this blog post from General Assembly, “What makes Javascript so popular”, there are over 1.8 billion websites in the world, and JavaScript is used on 95% of them. JavaScript is also by far the most used language, according to Github’s 2020 Octoverse Report. We anticipate the release of the Hedera JavaScript DID SDK to be appealing to an extremely large JavaScript development community to roll up their sleeves and get busy hacking away.

If you’re already familiar with the Hedera Java SDK, you can keep scrolling to read about the differences. However, if you are reading about this for the first time, here is a quick recap of what the Hedera JavaScript SDK allows you to do:

  • Creation of identity networks within appnets, that is: creation and initialization of the artifacts mentioned above.

  • Generation of decentralized identifiers for Hedera DID Method and creation of their basic DID documents.

  • Creation (publishing), update, deletion, and resolution of DID documents in appnet identity networks.

  • Issuance, revocation and status verification of Verifiable Credentials.

Just like the Hedera Java DID SDK, the JavaScript SDK does not impose any particular way of how the DID or verifiable credential documents are constructed. Each appnet creator can choose their own way of creating those documents and if these are valid JSON-LD files adhering to W3C standards, they will be handled by the SDK. Let’s look at what is similar and different between the Java and JavaScript SDKs:

You’ll notice that both Java and JavaScript SDK still contain similar Hedera DID Method libraries:

In the /Identity library you’ll notice the same files (Java on the left and JavaScript on the right):

The main difference, of course, is the language-specific implementation of the Hedera DID method. An example can be shown below when diving into the DID Document Base (Java on the top and JavaScript on the bottom).

Code Snippet Background
public static DidDocumentBase fromJson(final String json) {
    Gson gson = JsonUtils.getGson();
    
    DidDocumentBase result = null;

    try {
      JsonObject root = JsonParser.parseString(json).getAsJsonObject();
      result = gson.fromJson(root, DidDocumentBase.class);

      if (root.has(DidDocumentJsonProperties.PUBLIC_KEY)) {
        Iterator<JsonElement> itr = root.getAsJsonArray(DidDocumentJsonProperties.PUBLIC_KEY).iterator();
        while (itr.hasNext()) {
          JsonObject publicKeyObj = itr.next().getAsJsonObject();
          if (publicKeyObj.has(DidDocumentJsonProperties.ID)
                  && publicKeyObj.get(DidDocumentJsonProperties.ID).getAsString()
                  .equals(result.getId() + HcsDidRootKey.DID_ROOT_KEY_NAME)) {
            result.setDidRootKey(gson.fromJson(publicKeyObj, HcsDidRootKey.class));
            break;
          }
        }
      }
    } catch (Exception e) {
      throw new IllegalArgumentException("Given JSON string is not a valid DID document", e);
    }

    return result;
  }


  public static fromJson(json: string): DidDocumentBase {
        let result: DidDocumentBase;

        try {
            const root = JSON.parse(json);
            result = new DidDocumentBase(root.id);
            if (root.hasOwnProperty(DidDocumentJsonProperties.PUBLIC_KEY)) {
                if (!Array.isArray(root[DidDocumentJsonProperties.PUBLIC_KEY])) {
                    throw new Error(`${root[DidDocumentJsonProperties.PUBLIC_KEY]} is not an array`);
                }
                for (let publicKeyObj of root[DidDocumentJsonProperties.PUBLIC_KEY]) {
                    if (publicKeyObj.hasOwnProperty(DidDocumentJsonProperties.ID) && (publicKeyObj[DidDocumentJsonProperties.ID] ===
                        (result.getId() + HcsDidRootKey.DID_ROOT_KEY_NAME))) {
                        const didRootKey = HcsDidRootKey.fromJsonTree(publicKeyObj);
                        result.setDidRootKey(didRootKey);
                        break;
                    }
                }
            }
        } catch (e) {
            throw new Error('Given JSON string is not a valid DID document ' + e.message);
        }

        return result;
    }


Not everything is the same, however. With regards to the Utils library, there are some differences to note due to the translation from Java to JavaScript.

We included a class called array-utils. It’s a popular function to create strings from arrays and arrays from strings.

Code Snippet Background
export class ArraysUtils {
    public static equals(a: Uint8Array, b: Uint8Array): boolean {
        if (a == b) {
            return true
        }
        if (!a || !b) {
            return false
        }
        if (a.length != b.length) {
            return false
        }
        for (let i = 0; i < a.length; i++) {
            if (a[i] != b[i]) return false;
        }
        return true;
    }

    public static toString(array: number[] | Uint8Array): string {
        return Buffer.from(array).toString("utf8");
    }

    public static fromString(text: string): Uint8Array {
        return new Uint8Array(Buffer.from(text, "utf8"))
    }
}


We included a class called hashing. This is a JavaScript function to work with hashes and encode and decode arrays.

Code Snippet Background
import * as crypto from "crypto";
import bs58 from "bs58";
import { Base64 } from "js-base64";

export class Hashing {
    public static readonly base58 = {
        encode: function (data: Uint8Array): string {
            return bs58.encode(data);
        },
        decode: function (data: string): Uint8Array {
            return bs58.decode(data);
        }
    }
    public static readonly sha256 = {
        digest: function (data: Uint8Array | string): Uint8Array {
            const sha256 = crypto
                .createHash('sha256') // may need to change in the future.
                .update(data)
                .digest();
            return sha256;
        }
    }

    public static readonly base64 = {
        decode: function (encodedString: string): string {
            return Base64.fromBase64(encodedString);;
        },
        encode: function (decodedBytes: string): string {
            return Base64.toBase64(decodedBytes);
        }
    }
}


Similar to the Java Utils called InstantTypeAdapter, we included a class called timestamp-utils (which is borrowed from the Hedera JS SDK) for timestamp strings.

Code Snippet Background
import { Timestamp } from "@hashgraph/sdk";
import moment from 'moment';

export class TimestampUtils {
    public static ISO = "YYYY-MM-DDTHH:mm:ss.SSS[Z]";
    public static ISO8601 = "YYYY-MM-DDTHH:mm:ss[Z]";

    public static toJSON(item: Timestamp, format: string = this.ISO): string {
        const d = item.toDate();
        return moment(d).utc().format(format);
    }

    public static fromJson(json: string, format: string = this.ISO): Timestamp {
        const d = moment.utc(json, format).toDate();
        return Timestamp.fromDate(d);
    }

    public static now(): Timestamp {
        return Timestamp.fromDate(new Date());
    }

    public static equals(a: Timestamp, b: Timestamp): boolean {
        if (a == b) {
            return true;
        }
        if (!a || !b) {
            return false;
        }
        return a.seconds.equals(b.seconds) && a.nanos.equals(b.nanos);
    }

    public static lessThan(a: Timestamp, b: Timestamp): boolean {
        if (a == b) {
            return false;
        }
        if (!a || !b) {
            return false;
        }
        if (a.seconds.equals(b.seconds)) {
            return a.nanos.lessThan(b.nanos);
        }
        a.seconds.lessThan(b.seconds);
    }
}


The last notable Utils we added is called validator. This is used to validate fields inside or even outside the SDK.

Code Snippet Background
export class Validator {
    protected validationErrors: string[];

    public addValidationError(errorMessage: string): void {
        if (!this.validationErrors) {
            this.validationErrors = [];
        }
        this.validationErrors.push(errorMessage);
    }

    public checkValidationErrors(prologue: string, validationFunction: (input: Validator) => void): void {
        this.validationErrors = null;

        validationFunction(this);

        if (!this.validationErrors) {
            return;
        }

        const errors = this.validationErrors;
        this.validationErrors = null;

        throw new Error(prologue + ':\n' + errors.join('\n'));
    }

    public require(condition: boolean, errorMessage: string): void {
        if (!condition) {
            this.addValidationError(errorMessage);
        }
    }
}


Again, we are excited to release the Hedera JavaScript DID SDK and start to see the community use it for their DID and VC implementations!

----

About Envision Blockchain

Envision Blockchain - Enterprise Blockchain System Integrator

  • envisionblockchain.com

  • Primary Goal: Envision Blockchain Solutions is a Blockchain Systems Integrator. Our mission is to reshape and align today's Enterprise systems allowing organizations to recognize the new value in tomorrow's Industry Vertical Solutions. We focus on guiding organizations through 3 major milestones on their Blockchain journey: Use Case Analysis, Proof of Concept Development and Enterprise Scaled Deployments.

  • Hedera Project References:
    • HIP - 19: In collaboration with RECDeFi, established a specification to provide a standard way to use Decentralized Identifiers (DIDs) within Hedera state entity memo fields and thereby support the issuance of Verifiable Credentials about Hedera state entities. Just as the owner of a Hedera entity demonstrates that ownership and so claims associated authorizations via signatures with the private key(s) corresponding to the entity, a controller of a DID demonstrates that control via signatures with (likely) a different private key. Consequently, the DID controller can, when engaging in off-Hedera interactions, effectively demonstrate their association to the Hedera state entity without any on-Hedera transaction.

    • JavaScript DID SDK: Developed a JavaScript version of the Java DID SDK. This repository contains the JavaScript SDK for managing DID Documents & Verifiable Credentials registry using the Hedera Consensus Service.

    • MRV for Carbon Offsets: The process used to create an ecological claim that can be validated and verified to be turned into an offset product is called measurement, reporting and verification (MRV). Today, this process of collecting the supporting data for these carbon offsets is heavily manual and prone to errors. Carbon offsets have a double-spend and potentially a double credit problem - currently there’s no way for you to determine that the same tree hasn’t been sold repeatedly. One of the important goals for the Paris Agreement of 2015 is to ensure the avoidance of double spending for carbon offsets. DLT technologies due to its maintenance of a global shared ledger, together with the consensus mechanism, are a perfect technology to resolve the double spending problem. Envision Blockchain has partnered with the Hedera team to develop a solution that addresses these challenges and is compliant to the standards defined in the IWA's Voluntary Ecological Markets taskforce.

    • Reveille Digital: Reveille Digital, an industry leading DLT solutions provider focused on the oil and gas industry, wants to leverage innovative solutions such as Distributed Ledger Technology (DLT), Decentralized Identifiers (DIDs) and Verified Credentials (VCs) to offer trackable and immutable Environmental, Social, and Corporate Governance (ESG) metrics. To accommodate Reveille's strategic vision Envision Blockchain established a long-term relationship that focuses on many areas of the Reveille Digital sustainability practice such as advisory, business analysis, solution designing, solution implementation, and solution support. This implementation will be based on the standards defined in the IWA's Voluntary Ecological Markets taskforce.