Knowledge Center

Monetha for enterprise: A step-by-step guide to identity and data management on Quorum

Feel free to ask us any questions in the comments or get in touch with us on LinkedIn, Twitter, Telegram | Email: [email protected]

Introduction

This guide provides a reproducible A-to-Z demonstration for creating a digital identity on Quorum and then writing sensitive data to it with permissioned read access. It leverages the Monetha Platform, namely its Verifiable Data layer, to achieve this feat. The guide is complemented with code examples and includes a downloadable app at the end.

The Monetha Platform is our modular technology for building digital identity, data exchange, supply management, and other blockhain-based solutions. It allows businesses to quickly start experimenting with blockchain using our developer-friendly SDKs. The platform also offers additional functionality to derive value from distributed ledger, including sensitive data exchange, flexible data storage schemas, incentivization mechanisms, and a Subjective Logic algorithm for evidence-based scoring.

Note that while this demonstration features Quorum, our technology also works with Ethereum and any other Ethereum-based distributed ledger.

Here’s what you will find in the guide. Feel free to skip around to parts that interest you:

A few words about digital identity

Digital identity is the name of the game these days, with all kinds of companies—from startups to tech and finance giants—scrambling to offer their approach to an all-inclusive electronic proof of existence. And for good reasons.

A blockchain-based digital identity can solve many of the problems burdening the status quo: include the billion or so people without physical identity documents into the system of services we take for granted; return to people ownership and control of their scattered personal data; prevent frequent breaches that result in compromised private information and identity theft; create more accurate and comprehensive representation of ourselves, our organizations, and assets.

Overall, blockhain-powered digital identities have the power to retain the convenience of the digital world, while introducing many other desirable properties—such as privacy and security—to our interactions.

Why Quorum?

Ideally, a global digital identity system should be decentralized to ensure transferability, censorship resistance, and self-sovereignity. A good candidate for such a system would be Ethereum or similar public infrastructure, augmented with additional decentralized tools to preserve data privacy.

However, there are cases when full decentralization is not necessary, and a federated approach, or simply one leveraging blockchain’s immutability and data exchange capabilities, is suitable for the task. For instance, a private blockchain can be used for managing supply chains, exchanging public or sensitive information within a consortium, or creating digital identities for internal personnel, key assets, or users in a jointly-managed network. Whatever the case, a public blockchain is probably not the best tool for the above-mentioned purposes.

This is where Quorum™ comes in. It is an enterprise-focused version of Ethereum, ideal for any application requiring high speed and high throughput processing of private transactions within a permissioned group of known participants.

Setting up Quorum

In order to use our SDK on the Quorum ledger, you need to run a Quorum network. The easiest way to set up a network locally is by using the 7 nodes example provided by the Quorum team.

Prepare the digital identity backbone

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 – responsible for the passport’s main functionality: data storing, private data exchange and claiming passport ownership. It is upgradable, meaning that it is possible to add new functionality by deploying an updated contract and registering it in the PassportLogicRegistry contract. The result will be that all existing and new passports that use the specified registry will automatically have the new functionality. This is essentially the same as updating any software application. PassportLogicRegistry – manages the current active logic of passports. Whenever we deploy a new PassportLogic contract, we must register it here. PassportFactory – responsible for creating new passports and assigning them with a desired PassportLogicRegistry so that the newly created passports know what logic to follow.

You can read more about the architecture and upgradability of passports here.

There are many ways how these contracts can be deployed, but in our example we will use Truffle with a node.js script.

Prepare the Truffle project

First, you should install node.js if you do not already have it. It is recommended to download the latest LTS (Long Term Support) version as it is the most stable version and will prevent you from having unexpected problems.

Then we must install Truffle globally by running:

npm install truffle -g

Now that we have Truffle globally, let’s create a boilerplate Truffle project. Create a new directory in any path and run the project initialization command there:

truffle init

This will create a basic Truffle project structure, which will easily allow us to deploy the passport backbone contracts to Quorum.

Download and compile contracts

Let’s automate the process of downloading the latest smart contracts from Monetha’s Github repository and compiling them. We will download the contents of the repository using the download-git-repo NPM package. Then we will run the command provided by the downloaded project to compile the contracts. Finally, we will move the compiled files to our Truffle project’s build directory using the shelljs package.

First, we need to prepare a package.json file to contain the necessary dependencies. Create it by running:

npm init

Enter any answers for the init process. Then add the necessary dependencies:

npm install download-git-repo shelljs --save

Now we need a script which downloads and compiles the Solidity contracts for the passport backbone from Monetha. Create a file in scripts/build-contracts.js :

const download = require('download-git-repo'); const shell = require('shelljs'); const url = 'github:monetha/reputation-contracts'; const tmpDir = 'tmp'; const destDir = 'build'; console.log('Downloading contracts...'); download(url, tmpDir, (err) => { try { if (err) { console.log(err); return; } shell.cd(tmpDir); try { console.log('Installing contract dependencies...'); if (shell.exec('npm install').code !== 0) { console.log('Failed to install dependencies for contracts'); return; } console.log('Compiling contracts...'); if (shell.exec('npm run compile').code !== 0) { console.log('Failed to compile contracts'); return; } } finally { shell.cd('..'); } // Move built contracts to root shell.rm('-rf', destDir); shell.mv(`${tmpDir}/build`, destDir); console.log('Done!'); } finally { // Always remove tmp dir shell.rm('-rf', tmpDir); } });

Finally, let’s run the script:

node scripts/build-contracts.js

As a result, all of the built contracts should now be present in the /build directory.

Configure Truffle for Quorum

Truffle supports contract deployment to Quorum—we just need an appropriate configuration in truffle-config.js :

module.exports = { networks: { development: { // This is the IP address and port of Quorum node that we are going to use for contract deployment. // When running Quorum's "7 nodes" example locally, the first node's address is 127.0.0.1:22000 host: '127.0.0.1', port: 22000, timeoutBlocks: 100, network_id: "*", // Set the gas price to 0 because all Quorum transactions must have 0 gas price. // You may get an error if this number is not 0. gasPrice: 0, // Set type to 'quorum' type: 'quorum', }, }, };

Deploy contracts

As we have already built the contracts, we can deploy them using Truffle’s migration functionality. For this, we need to create a migration script which will instruct how to deploy our contracts. 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'); // Because we want to use our passports with Quorum's private transactions, we need to // deploy contracts using private transactions as well. For this, we need to have a list of Quorum // nodes' public keys, which are allowed to use these contracts. Here are the public keys of the first three nodes from // Quorum's "7 nodes" example (taken from examples/7nodes/keys/tm1.pub, tm2.pub, tm3.pub) const participantNodes = [ 'BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=', 'QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=', '1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg=', ]; 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` and adding a // provider, which would be able to import your accounts using private keys or mnemonics. // 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 Quorum node specified in `truffle-config.js`. // Note the property `privateFor` - there we pass the public keys of Quorum nodes that will be able to access // these contracts, making the contracts private. deployer.deploy(PassportLogic, { from: ownerAddress, privateFor: participantNodes, }) // 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, privateFor: participantNodes, })) // 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, privateFor: participantNodes, })); }

At this point we have the Quorum nodes running, contracts built and deployment script ready. So let’s run it:

truffle migrate

You should get an output similar to this:

Running migration: 1_initial_migration.js Deploying Migrations... Migrations: 0x0d8D238e08d7594Cb519EB21b00012c6773cf6d5 Saving successful migration to network... Saving artifacts... Running migration: 2_passports_backbone.js Deploying PassportLogic... PassportLogic: 0x592eF1a75527aA919Accc329134f7A9B2B15D126 Deploying PassportLogicRegistry... PassportLogicRegistry: 0x73bcb171C0665CB8F970396c29638ADc7e74f236 Deploying PassportFactory... PassportFactory: 0x2e47cF58252CAC4004C4DB54Ca36004467dB5A17 Saving successful migration to network... Saving artifacts...

We can see that 4 contracts were deployed. The first one, Migrations , is Truffle’s internal contract, which keeps track of the migrations that were ran. It’s similar to how SQL database migrators keep track of which scripts were ran in database. If you were to run truffle migrate again, nothing would be deployed because the Migrations contract now includes entries for these two migration scripts—meaning they were already ran.

Take note of the PassportFactory address (here it is 0x2e47cF58252CAC4004C4DB54Ca36004467dB5A17 , but it will be different in your case). We will need it later for passport creation.

Here is the full source code of our contract deployer:

Using the SDK with Quorum

As we have now deployed all the necessary contracts to our local Quorum network, let’s make a sample app which will create a digital identity, write a sample data and read it — all using Quorum’s private transactions. For the sake of simplicity, we will do it using the node.js console app.

Bootstrap a node.js app

Our app will need three dependencies:

web3 – provides communication with blockchain. quorum-js – allows sending private transactions to Quorum nodes through web3. verifiable-data – Monetha’s SDK, which provides tools for working with passports.

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

npm init

And then install the aforementioned dependencies:

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

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

Since we will be submitting Quorum’s private transactions, we need to have such information the following information about each node:

Node’s address – one of the 7 Quorum nodes JSON RPCS where web3 will be connecting. Node’s transaction manager public key – it will be needed when setting permissions for private transactions. Node’s transaction manager enclave address – a place where private transactions get encrypted.

We will use the configuration for three nodes from Quorum’s “7 nodes” example (let’s call them A, B and C). Add it below the require statements in main.js file:

// Our Quorum nodes configuration. // It is configured to use the first three nodes from Quorum's "7 nodes" example. const nodes = { A: { address: 'http://127.0.0.1:22000', enclave: 'http://127.0.0.1:9081', publicKey: 'BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=', }, B: { address: 'http://127.0.0.1:22001', enclave: 'http://127.0.0.1:9082', publicKey: 'QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=', }, C: { address: 'http://127.0.0.1:22002', enclave: 'http://127.0.0.1:9083', publicKey: '1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg=', }, };

Create a digital identity

To create a passport (remember, that’s how we call a digital identity), we will need the following input:

Passport factory – an address of the PassportFactory contract that we will use to create a passport. Since we deployed this contract in the first part of the guide, we will use that address: 0x2e47cF58252CAC4004C4DB54Ca36004467dB5A17 . Your address will be different. Passport owner’s private key and address – the user/entity that will take ownership of the created passport.

NOTE: Here we will use some hardcoded private keys only for illustration purposes. In production, remember to ensure the safety of private key management and do not hardcode them anywhere no matter what.

Add the code below the Quorum’s configuration:

// The address of the passport factory that we created previously const passportFactoryAddress = '0x5a9D939cC99abB0388F64dC61ac50B78fDe45Aa7'; // A random passport owner's private key for the passport that we will create const passOwnerPrivateKey = '0xb60c3d80098c4ee4980067864c0398808e007dbccf00f4521035d0bbc7d62fe6'; const passOwnerAddress = '0x29bD6BDd1148D4DD4ff5040A487a9650D45fb393';

Now we are ready to create a passport using the SDK. Important thing to note that passports are always created through a PassportFactory to ensure that passport is using the latest and greatest passport logic. Let’s prepare a transaction for passport creation using Quorum’s node A:

(async () => { // Initiate web3 instance to node A nodes.A.web3 = new Web3(new Web3.providers.HttpProvider(nodes.A.address)); const generator = new sdk.PassportGenerator(nodes.A.web3, passportFactoryAddress); // txConfig is only a descriptor for the passport creation transaction to execute. 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. For example, Quorum uses a specific field “privateFor” to mark nodes that have access to the transaction—this is a non-standard way to execute a transaction.

Therefore, let’s create a transaction executor that is suitable for executing Quorum’s private transactions. Create a file scripts/utils/quorum.js with the following contents:

const quorumjs = require('quorum-js'); module.exports.executeTx = async (web3, txConfig, privateKey, privateFor, enclaveAddr) => { const account = web3.eth.accounts.privateKeyToAccount(privateKey); // We use RawTransactionManager to send private transactions // To learn more, read https://github.com/jpmorganchase/quorum.js/blob/master/README.md#enclaves const rawTransactionManager = quorumjs.RawTransactionManager(web3, { privateUrl: enclaveAddr }); const tx = await rawTransactionManager.sendRawTransaction({ gasPrice: 0, gasLimit: txConfig.gas, to: txConfig.to, value: txConfig.value, data: txConfig.data, from: account, nonce: txConfig.nonce, isPrivate: true, privateFor: privateFor, }); return tx; }

Now we can use this executor function to submit a private transaction. In this example we will create a passport for only Quorum nodes A and B to access:

// ... const quorumUtils = require('./utils/quorum'); // ... // We will be giving access to the passport only for nodes A and B const allowedQuorumNodes = [nodes.A.publicKey, nodes.B.publicKey]; let receipt = await quorumUtils.executeTx(nodes.A.web3, txConfig, passOwnerPrivateKey, allowedQuorumNodes, nodes.A.enclave); // The transaction receipt contains the passport address in event data. // Here we use an util to easily extract it const passportAddress = sdk.PassportGenerator.getPassportAddressFromReceipt(receipt); console.log('Passport address:', passportAddress);

After running this code, the passport will be created on your local Quorum network. There is one more thing to do to enable its full functionality: the passport’s owner must claim its ownership. This step is mandatory because the first owner of the passport is the one who deployed the contract: PassportFactory:

// 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(nodes.A.web3, passportAddress); txConfig = await ownership.claimOwnership(passOwnerAddress); await quorumUtils.executeTx(nodes.A.web3, txConfig, passOwnerPrivateKey, allowedQuorumNodes, nodes.A.enclave);

Read and write data

The passport holds a data collection provided by various data sources (or fact providers). For example, in Monetha’s dealmaking 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.

In our app, let’s create a new data source representative. To do so, all we need is a private and public key pair and to write a fact using it:

// ... // Another random private key for the fact provider that will write a fact to the passport const factProviderPrivateKey = '0x3e0763e8431abf7ca61a1e3330c3be3eb6cdb2b45dc1e34e8fc505e053ef3178'; const factProviderAddress = '0xaf25cfe1bdea10f091efc5e041f5d3d6e12e6ac2'; // ... // Since the passport is accessible for nodes A and B, let's try writing a fact // and executing a transaction from the B node this time. nodes.B.web3 = new Web3(new Web3.providers.HttpProvider(nodes.B.address)); const writer = new sdk.FactWriter(nodes.B.web3, passportAddress); // It is possible to write various types of facts: Integer, Boolean, Address, IPFS, etc.. // Here we write a String for simplicity txConfig = await writer.setString('species', 'Human', factProviderAddress); await quorumUtils.executeTx(nodes.B.web3, txConfig, factProviderPrivateKey, allowedQuorumNodes, nodes.B.enclave);

This fact is now written in the passport with a data point label “species” from a fact provider “0xaf25cfe1bdea10f091efc5e041f5d3d6e12e6ac2” and a value “Human”. Remember that we made our passport visible only for Quorum nodes A and B — therefore, this fact will only be readable by those nodes, but not by node C. Let’s try reading the fact from all three Quorum nodes and see what results we get:

// Let's read the fact from all three nodes // The result should be that readers for nodes A and B will produce the result 'Human', // but C will produce null, since we did not give access to node C and therefore fact data // is not initialized for it. const readerA = new sdk.FactReader(nodes.A.web3, passportAddress); console.log('Fact from A: ', await readerA.getString(factProviderAddress, 'species')); const readerB = new sdk.FactReader(nodes.B.web3, passportAddress); console.log('Fact from B: ', await readerB.getString(factProviderAddress, 'species')); nodes.C.web3 = new Web3(new Web3.providers.HttpProvider(nodes.C.address)); const readerC = new sdk.FactReader(nodes.C.web3, passportAddress); console.log('Fact from C: ', await readerC.getString(factProviderAddress, 'species'));

Here is the full example application that we were going through:

Now let’s run the app:

node scripts/main

The console will produce the following information (with a different passport address):

Passport address: 0x4e1829a9161f624e8d441f4eaa73140b6807f3b5 Fact from A: Human Fact from B: Human Fact from C: null

It confirms that the data is only accessible by nodes A and B.

Hopefully, this post was useful to you in giving you a bit more understanding about using Quorum’s private transactions with our SDK.

The guide demonstrated how to write and read public data to the Quorum private ledger with a private transaction which allows to view information only to a part of nodes in the network. However, Monetha’s Verifiable Data layer can just as well be used to exchange sensitive data not only between full nodes but also between accounts in the Quorum network.

Feel free to ask us any questions in the comments or get in touch with us on LinkedIn, Twitter, Telegram | Email: [email protected]

Thank you for reading!