September 16, 2019 Knowledge Center

Monetha for enterprise: Digital identity on Hyperledger Besu (Pantheon) network

Introduction

Previously, we provided a guide to managing digital identities on the Quorum network. We are continuing the series, and this time you will learn how to manage digital identities on the Hyperledger Besu (formerly known as Pantheon) network.

Pantheon is the first public blockchain project that was officially adopted into the Hyperledger ecosystem (as Hyperledger Besu).

Pantheon can be run on the Ethereum public network or private permissioned networks, as well as test networks such as Rinkeby, Ropsten, and Görli. It includes several consensus algorithms, such as Proof of Work, Proof of Authority, and Istanbul Byzantine Fault Tolerance, and has comprehensive permissioning schemes designed specifically for use in a consortium environment.

In this guide, we will provide the basic instructions for building a simple app that you can test on a locally running Pantheon network. Due to the fact that Pantheon can be run on the Ethereum public blockchain, we can be certain that our app will work properly with the Ethereum mainnet.

Prerequisites

In order to follow this guide, you need to have the following tools installed on your machine:

  1. Docker – used to launch the Pantheon network locally. Installation instructions can be found here.
  2. docker-compose – used to launch the Pantheon network locally. Installation instructions can be found here.
  3. node.js – used to deploy smart contracts and develop the sample app. We recommend the latest LTS (Long Term Support) release. You can find the installation instructions here.

Setting up Pantheon

Before we start experimenting with Monetha’s verifiable data SDK on the Pantheon ledger, we need to set up our environment. Here we have a couple of options. We can use the Pantheon quickstart repository to launch the network locally. Or we can use Monetha’s platform-on-docker repository, which not only launches the Pantheon network locally but also deploys Monetha’s digital identity backbone contracts on it, and provides a local scanner instance to browse the digital identity we will create.

We will not be focusing on the details of how to launch the Pantheon ledger—just follow the instructions given in the repositories.

If you use Monetha’s platform-on-docker approach, you can skip the sections about contract deployment and go directly to writing a sample app which will create a digital identity on the locally running Pantheon network.

Deploying the digital identity backbone contracts

To rehash the information given in the Quorum guide, a digital identity (further on, we will be calling it passport) is a smart contract with upgradable logic. It allows to store public and sensitive data from multiple data sources and securely exchange it with others.

In order to use the passport functionality, we first have to deploy three contracts (PassportLogic, PassportLogicRegistry, and PassportFactory) that make up the backbone of the digital identity. You can read more about the architecture and upgradability of passports here.

While there are multiple ways we could deploy these contracts to our local network, in this case we will be using Truffle. As noted before, if you used the platform-on-docker approach to launch the Pantheon network, you can skip the following section and go straight to writing the sample app.

Contract deployment with Truffle

Similarly to the Quorum guide, we need to have a Truffle project prepared. Follow the guide’s “Prepare the Truffle projectsection to do this.

After we have a basic Truffle project structure, we should automate the process of downloading the latest smart contracts from Monetha’s Github reputation-contracts repository and compiling them. This is identical to the Quorum guide’s “Download and compile contractssection. Please follow it.

At this point you should have the Pantheon network started and running on your machine, a basic Truffle project, and a directory called build with all of the Monetha contracts built in it.

Please note: If you are running the Pantheon quickstart repository to start the network, at the end of the start command there is a list of endpoints output to your terminal. Please write down the value of “JSON-RPC HTTP service endpoint” as it will be relevant in the following sections. In our case, it was http://localhost:21000/jsonrpc—this value will be used in the example scripts. Please update it to your value where relevant.

The next step is to update the truffle-config.js file to include the correct configuration that will allow to deploy the compiled contracts to the local Pantheon network:

const PrivateKeyProvider = require("truffle-hdwallet-provider");
const privateKeys = [
  '0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63',
  '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3',
  '0xae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f',
];

module.exports = {
  networks: {
    development: {
      provider: new PrivateKeyProvider(privateKeys, "http://localhost:21000/jsonrpc", 0, privateKeys.length),
      timeoutBlocks: 100,
      network_id: "*",
    }
  }
}

As you will notice, there is an additional package truffle-hdwallet-provider used in the truffle-config.js file. This is due to the fact that Pantheon does not implement private key management. To use Pantheon with Truffle, we will use a Truffle HD wallet.

Add the package to our Truffle project with the following command:

~$ npm install --save truffle-hdwallet-provider

Also, you can see that we added three private keys to the configuration. These are the default test accounts in the Pantheon network. Do not use these accounts on mainnet or any public network except for testing. The private keys are displayed here, which means the accounts are not secure.

We have the connection information ready, but we still need to create a migration script to indicate which contracts (and with which parameters) Truffle needs to deploy during the migration. Let’s create a file called migrations/2_passports_backbone.js:

// When the script is run by Truffle, it injects a global `artifacts` variable, which allows us to fetch
// contract objects used for deployment.
const PassportLogic = artifacts.require('PassportLogic');
const PassportLogicRegistry = artifacts.require('PassportLogicRegistry');
const PassportFactory = artifacts.require('PassportFactory');


module.exports = (deployer, _, accounts) => {
  // The `accounts` variable holds Ethereum addresses provided by the Truffle framework.
  // In a live application you should provide your own addresses by modifying `truffle-config.js`.
  // See this example at https://github.com/trufflesuite/truffle/tree/develop/packages/truffle-hdwallet-provider
  const ownerAddress = accounts[0];
  // `deployer` allows us to easily deploy contracts using the Pantheon node specified in `truffle-config.js`.
  deployer.deploy(PassportLogic, {
      from: ownerAddress,
    })
    // PassportLogicRegistry takes the initial passport logic version (0.1) with the logic contract's address in the constructor.
    .then(() => deployer.deploy(PassportLogicRegistry, '0.1', PassportLogic.address, {
      from: ownerAddress,
    }))
    // PassportFactory just takes the PassportLogicRegistry address so that it would know what
    // registry to assign to new passports.
    .then(() => deployer.deploy(PassportFactory, PassportLogicRegistry.address, {
      from: ownerAddress,
    }));
}

At this point we have the Pantheon network running, contracts built, and deployment script ready. So, let’s run Truffle migration to deploy them:

~$ truffle migrate

The Truffle migration will output specific information about the four contracts that were deployed. However, the one we care most about is the PassportFactory contract. In the output, it will be the last contract to be deployed:

   Deploying 'PassportFactory'
   ---------------------------
   > transaction hash:    0x3679df03520b52e73455e32fd671e7e6539e2584fb7f77af64218a58fb75e143
   > Blocks: 0            Seconds: 0
   > contract address:    0xfeae27388A65eE984F452f86efFEd42AaBD438FD
   > block number:        567
   > block timestamp:     1567623964
   > account:             0xFE3B557E8Fb62b89F4916B721be55cEb828dBd73
   > balance:             1334
   > gas used:            1159116
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.02318232 ETH

Take note of the PassportFactory contract address—we will need it later for passport creation. In our case it was 0xfeae27388A65eE984F452f86efFEd42AaBD438FD but yours will be a different value.

Here is the full source code of our contract deployer:

Using the SDK with Pantheon

We have a local Pantheon network running and all the necessary contracts deployed on it. As such, let’s implement a sample app which will create a digital identity, write some data into it, and read it. For the sake of simplicity, we will do so by using the node.js console app.

Bootstrap a node.js app

Our app will need three dependencies:

  1. web3 – provides communication with blockchain
  2. truffle-hdwallet-provider – HD Wallet-enabled Web3 provider
  3. verifiable-data – Monetha’s SDK, which provides tools for working with digital identities

Just like we did previously, create a new folder for this project and initiate an empty package.json file:

~$ npm init

And install the necessary dependencies:

~$ npm install [email protected] truffle-hdwallet-provider verifiable-data --save

Now, let’s create the app’s entry point scripts/main.js:

const Web3 = require('web3');
const HDWalletProvider = require('truffle-hdwallet-provider');
const sdk = require('verifiable-data');
// ...
(async () => {
    // ...
})().catch(e => {
    console.error(e);
});

Let’s add all the previously created or used components:

  1. JSON-RPC HTTP service endpoint – this is the endpoint through which we will interact with the local Pantheon network. This is the same endpoint that we used in the Truffle project to deploy the contracts. For us it was http://localhost:21000/jsonrpc. For you it might differ based on the method that you used to start the local Pantheon network.
  2. PassportFactory contract address – the address of the contract that we will use to create a passport. This is the address from the previous section that we indicated to write down if you deployed the contracts yourself. If you used the platform-on-docker repository to launch the network, the contract address was output at the end of the startup sequence. You can retrieve it once more by running the ./list.sh script. For us the value was 0xfeae27388A65eE984F452f86efFEd42AaBD438FD. For you the value most likely will be different.
  3. Pantheon network test accounts – we will use these accounts to perform actions on the local Pantheon network. These are the same accounts that we used in the Truffle migration.

Let’s add these values to the scripts/main.js file below the require statements:

const pantheonEndpoint = 'http://localhost:21000/jsonrpc';
const passportFactoryAddress = '0xfeae27388A65eE984F452f86efFEd42AaBD438FD';
const privateKeys = [
    '0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63',
    '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3',
    '0xae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f',
];

Please note: here we use some hard-coded private keys only for illustration purposes. In production environments remember to ensure the safety of private key management and do not hard-code them anywhere.

In addition to these values, we need to instantiate the provider which we will use to interact with the network. Let’s add it after the private key array:

const provider = new HDWalletProvider(privateKeys, pantheonEndpoint, 0, privateKeys.length);
const web3 = new Web3(provider, null, { transactionConfirmationBlocks: 1, defaultGas: "0x1ffffffffffffe" });

As we are interacting with a local Pantheon network, we declare some web3 options when instantiating it. transactionConfirmationBlocks indicates how many blocks are needed to confirm the transaction. As it’s a local network, 1 block is enough. defaultGas indicates how much gas should be used when submitting the transaction to the network if the value is not included in the transaction configuration.

Create a digital identity

To create a passport (remember, that’s what we call a digital identity), we will need a passport owner’s address—the user/entity that will take ownership of the created passport. We will use the second test account for this. Let’s add the following code after the provider creation:

const passOwnerAddress = provider.addresses[1];

At this point, we have all the information we need to create a passport using the SDK. The important thing to note is that passports are always created through PassportFactory to ensure that the passport is using the latest and greatest passport logic.

(async () => {
    // == Create passport ==
    // prepare the transaction for passport creation
    const generator = new sdk.PassportGenerator(web3, passportFactoryAddress);
    let txConfig = await generator.createPassport(passOwnerAddress);

    // ...
})().catch(e => {
    console.error(e);
});

txConfig is a prepared transaction configuration ready to be executed. The SDK intentionally does not execute the transaction itself and leaves it to the SDK client because the client may want to execute it in a specific way. In this case we will use the standard web3 methods.

Let’s create a transaction execution helper that wraps a few web3 methods for simplicity. Create a file scripts/utils/transaction.js with the following contents:

module.exports.executeTransaction = async (web3, txData) => {
    // there are some issue how Pantheon network estimates gas usage.
    // Due to this, we remove the estimated value and allow web3 to set defaultGas
    delete txData.gas;
    let txHash = await new Promise(async (success, reject) => {
        try {
            web3.eth.sendTransaction(txData)
                .on('transactionHash', async (hash) => {
                    success(hash);
                });
        } catch (e) {
            reject(e);
        };
    });

    let txReceipt = await new Promise(async (success, reject) => {
        const waiter = async () => {
            try {
                const result = await web3.eth.getTransactionReceipt(txHash);
                if (result) {
                    if (!result.status) {
                        console.error(result);
                        throw new Error('Transaction has failed');
                    }
                    success(result);
                    return;
                }
            } catch (e) {
                reject(e);
                return;
            }
            setTimeout(waiter, 100);
        };
        waiter();
    });

    return txReceipt;
}

You will notice that we are removing the gas property from the transaction configuration. When the transaction configuration is generated by the Monetha SDK, it actually queries the local Pantheon network for an estimate of how much gas will be needed to successfully execute the transaction. Unfortunately, the Pantheon network returns an estimate that is not always correct (there are some issues registered for this in Pantheon’s Jira board). In this case, to work around the issue, we just remove the estimated value. This way the web3 provider uses the defaultGas value we indicated previously when submitting the transaction to the network.

Now we can add the helper into our code and execute the transaction:

// ... 
const tx = require('./utils/transaction.js');

// ...

(async () => {
    // == Create passport ==
    // prepare the transaction for passport creation
    const generator = new sdk.PassportGenerator(web3, passportFactoryAddress);
    let txConfig = await generator.createPassport(passOwnerAddress);

    // execute the transaction using the standard web3 methods that we defined
    // in utils/transaction.js file
    let receipt = await tx.executeTransaction(web3, txConfig);

    // The transaction receipt contains the passport address in event data.
    // Here we use an util from the verifiable data SDK to easily extract it
    const passportAddress = sdk.PassportGenerator.getPassportAddressFromReceipt(receipt);
    console.log('Passport address:', passportAddress);

    // ...
})().catch(e => {
    console.error(e);
});

After running the code, a passport will be created on your local Pantheon network. However, the first owner of the passport is the one who deployed the contract—PassportFactory. As such, the passport owner needs to perform a mandatory step—claim the ownership of the passport:

(async () => {
    // ...

    // == Claim ownership ==
    // After creation, the passport is in a "Pending ownership" state. We must
    // claim passport ownership in order for the passport to be fully functional.
    const ownership = new sdk.PassportOwnership(web3, passportAddress);
    txConfig = await ownership.claimOwnership(passOwnerAddress);
    receipt = await tx.executeTransaction(web3, txConfig);

    // ...
})().catch(e => {
    console.error(e);
});

Again, we are using the SDK to prepare the transaction. After this transaction is executed, the passport ownership is transferred to the actual passport owner.

If you used the platform-on-docker approach to launch the Pantheon network, you can go to the local scanner instance to browse the digital identity registry, and check the digital identity we just created. The URL for the local scanner instance was output at the end of the startup sequence. You can retrieve it once more by running the ./list.sh script.

If you did not use the platform-on-docker, then you can launch a local scanner instance by following the instructions in this repository.

Writing and reading data

As we mentioned in the previous guide, the passport holds a data collection provided by various data sources (or fact providers). For example, in Monetha’s reputational identity app, a fact provider “Monetha” writes the user’s rating to their passport. Each fact can be identified by the data source address and data point label.

Let’s try writing some fact into our newly created passport. For this we will need a fact provider. In this case, we will use the third Pantheon test account.

const factProviderAddress = provider.addresses[2];
// ...
(async () => {
  // ...
  // == Write fact ==
  // Let's try writing a fact to passport
  // It is possible to write various types of facts: Integer, Boolean, Address, IPFS, etc..
  // Here we write a String for simplicity
  const writer = new sdk.FactWriter(web3, passportAddress);
  txConfig = await writer.setString('Company name', 'Weyland-Yutani', factProviderAddress);
  receipt = await tx.executeTransaction(web3, txConfig);
  // ...
})().catch(e => {
    console.error(e);
});

After executing the code, the fact will be written into the passport with a data point label “Company name” and value “Weyland-Yutani”.

We can easily read the written facts by using the SDK:

(async () => {
  // ...
  // == Read fact ==
  // Let's read the fact that we just wrote
  const reader = new sdk.FactReader(web3, passportAddress);
  let fact = await reader.getString(factProviderAddress, 'Company name');
  console.log(`Fact "Company name" for passport "${passportAddress}": `, fact);
  // ...
})().catch(e => {
    console.error(e);
});

The Truffle wallet provider needs some additional handling before we exit the app. Let’s add the following line to our app to clean up before exiting:

(async () => {
 // ...
 // cleanup
 provider.engine.stop();
})().catch(e => {
  console.error(e);
});

You can find the full app that we just wrote here:

Now that we have all of the steps covered, let’s try running the app:

~$ node scripts/main.js

The app output should be similar to this:

~$ node scripts/main.js 
Passport address: 0x6213607c2fab2ddd572f7754a608565cd28ba7aa
Fact "Company name" for passport "0x6213607c2fab2ddd572f7754a608565cd28ba7aa":  Weyland-Yutani

We can also verify this using our local scanner instance:

Conclusion

We hope this guide will be useful for you to start creating great dApps with our SDK.

Please feel free to ask questions in the comments and get in touch with our #monethatech team on LinkedIn, Telegram, or Reddit.

Thank you for reading!

Share

Leave a Reply

Your email address will not be published. Required fields are marked *