Exploring How Private Transaction Works in Quorum

KC Tam
10 min readMay 7, 2018

The first time I heard about Quorum is in the launch video of Enterprise Ethereum Alliance (EEA) back in last February. In that lengthy webcast I saw the demonstration of Quorum and how private transaction is done on top of Quorum (and therefore Ethereum) network.

As EEA currently announced the Architecture Stack, also it is reported that JPMorgan will spin out Quorum. It’s good time to revisit how things go lately.

With knowledge in using geth and how Ethereum works, I begin to explore Quorum, in particular how the private transaction works. Here I am exploring their 7nodes example in their repository.

Quorum: Quick Introduction

There is no better place than its wiki for detailed information on Quorum. Here I just make some highlight.

Quorum is the open source project developed by JPMorgan (link). The objective is to make Ethereum enterprise ready. Today Ethereum is well known with its public and permissionless blockchain, a smart contract platform, its native cryptocurrency and tokens issued on top of it. To make Ethereum enterprise use, certain features need modification and some additional features are required.

Among these features, my focus is on the private transaction: only those specified target, that is, node(s), will see and process the transaction, while that transaction means nothing to other nodes. The target specification is done through the public key of the node(s).

Here is the portion of wiki about the transaction process and how privacy is achieved.

Set Up a VirtualBox VM with Vagrant for 7nodes

Quorum-examples comes with a handy way to setup a VM simulating seven Quorum Nodes (and corresponding Constellation Nodes) using vagrant from hashicorp.

Make sure Vagrant and VirtualBox are installed. Here we clone the quorum-examples.

Here is the Vagrantfile,

We see from the Vagrantfile that,

  • A VM of 2 MB is created on Virtualbox
  • Seven ports (22000–22006) are configured as host port / guest port. They are for the seven nodes being simulated.
  • vagrant/bootstrap.sh is executed after the VM is instantiated.

Here is what’s inside the shell script vagrant/bootstrap.sh,

*** Updated: the original issue of directory change is due to a new vagrant box. And there it is removed. The following works as usual.

To instantiate the VM, use this command,

$ vagrant up

It takes some time to complete the download and setup. Here is the screenshot of finishing.

We are now ready to ssh into this VM.

$ vagrant ssh

Setup of 7nodes Example

Overview of the 7nodes Example

The 7nodes example is a good case for us to explore Quorum.

The architecture of Quorum is quite simple. It is composed of a Quorum Node and Constellation (which is composed of Transaction Manager and Enclave). See here for more information about the architecture.

Source: https://github.com/jpmorganchase/quorum/wiki/Quorum-Overview

Note that Quorum Node is built on top of Go Ethereum (geth) client. We use geth console largely in this article.

In this 7nodes example, each node has its Quorum Node and Constellation. The setup is kept inside qdata/ddn and qdata/cn, where n is 1 to 7. Communication between Quorum Node follows normal ethereum p2p way, with certain modification for permission and private transaction handling. Constellation are communicating one another using https (therefore proper server key is required).

As Quorum is not for public and permissionless use, consensus is not achieved through mining process. Quorum here uses Raft as the consensus protocol. The setup therefore includes all the required Raft configuration.

The wiki contains great introduction on the transaction processing, and how Quorum and Constellation interact on private transaction.

The setup requires both raft-init.sh and raft-start.sh to establish the 7 nodes.

Setup Shell Scripts

Here is raft-init.sh.

We can see in this shell script,

  • Proper files are copied to corresponding directory qdata/ddn.
  • The chain is initialized (with geth) with the same genesis.json. This makes the seven nodes forming a private ethereum network (that is, share the same ethereum blockchain).

Here is raft-start.sh.

We can see in this shell script,

  • Run constellation-start.sh script to setup constellation
  • Run geth client with proper parameters

Finally we take a look on constellation.sh shell script.

We see in this shell script,

  • Create directory qdata/cn (n from 1 to 7)
  • Copy appropriate transaction manager keys to the directories
  • Execute constellation-node with parameters

Therefore, after we run raft-init.sh and raft-start.sh, the 7nodes example is setup properly.

We can check the geth and constellation processes for 7 nodes are running.

Here is how it looks after deploying the 7nodes example. We only show three nodes (Node 1, 4 and 7) here as we will build the illustration on these three nodes later in this article.

Understand the Test Script

Quorum-example comes with a Smart Contract, the very well-known simplestorage.

Here is the Smart Contract (simplestorage.sol).

When the contract is deployed, a value (initVal) is required. This value is stored in the variable storedData. This contract comes with two functions: get(), which returns the storedData, and set(), which modifies the storedData with the supplied value.

Quorum Example provides a JavaScript file to execute the test. Let’s first take a look on script1.js.

On Line 11,

var simple = simpleContract.new(42, {from:web3.eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]}, function(e, contract) { … }

Here we see,

  • Variable simple is a new deployed object of simplestorage contract
  • The initial value is set to 42
  • The contract is deployed by eth.accounts[0]
  • This is a private transaction, as it contains privateFor value

We double check and can see the address “ROAZBW…” is the public key of Transaction Manager of Node 7.

Therefore, this transaction is a private transaction, and only Node 7 can receive and understand it.

According to the design, this transaction is still visible in all nodes. But with privateFor specifying Node 7 alone, only Node 7 can see the value while others see nothing. We will verify this.

Instead of running this script, we can deploy the contract by ourselves using geth console. This provides a good way to monitor the behaviour of Quorum when handling different types of transaction.

Test of Transactions in Quorum

We will use geth to perform the two types of transactions:

  1. Deploy a public transaction (every node can see the value)
  2. Deploy a private transaction to one node only

Deploy a Public Transaction

When we do not set the privateFor value, this transaction is deemed public, which means all nodes will receive this transaction and see the value inside the contract object.

We deploy the contract from Node 1, using eth.accounts[0]. We monitor Node 4 and Node 7.

Screen split for Node 1, 4 and 7

Deploy the contract in Node 1, with initial value set to 10. Note that no privateFor is set, which means it is a public transaction.

> var bytecode = <bytecode taken from script1.js>
> var simpleContract = eth.contract(<abi taken from script1.js>)
> var test1 = simpleContract.new(10, {from:eth.accounts[0], data: bytecode, gas: 0x47b760})
// to obtain the deployed address
> test1.address

Here is the deployed address:

“0x1932c48b2bf8102ba33b4a6b545c32236e342f34”

To check the current value, we use test1.get().

On Node 4 and Node 7, we can define test1 variable pointing to that deployed contract, and check the initial value with test1.get().

> var simpleContract = eth.contract(<abi taken from script1.js>)
> var test1 = simpleContract.at(<deployed address>)
> test1.get()
Node 4
Node 7

This is the result we expect. Without privateFor, this is a public transaction, which means all nodes participate in this transaction and can access the deployed contract. And in public transaction, the Transaction Manager on each node is not participating. It is just the normal Ethereum process.

Public Transaction in Quorum

Let’s set a new value from Node 1, and verify that this value is seen in Node 4 and 7.

Node 1
Node 4
Node 7

To further explore the transaction, we obtain Transaction ID from the deployed contract from Node 1, and get the Transaction Detail.

Node 1

From Node 4 and Node 7, we need to key in this Transaction Hash in order to get Transaction detail.

Node 4 and 7

Here we observe that,

  • All three nodes see this transaction.
  • The input field is the exact bytecode when we deploy the contract. That means this data is in clear text, visible in the transaction.
  • The v value is 0x1b (27), which means a public transaction.

Deploy a Private Transaction

Now we deploy a private transaction that only Node 7 can see.

Deploy the contract in Node 1

> var test2 = simpleContract.new(20, {from:eth.accounts[0], data: bytecode, gas: 0x47b760, privateFor: ["ROAZBWtSacxXQrOe3FGAqJDyJjFePR5ce4TSIzmJ0Bc="]})// to obtain the deployed address
> test2.address
// to get the initial value
> test2.get()
Node 1

Here is the deployed address:

“0x1349f3e1b8d71effb47b840594ff27da7e603d17”

Now we define test2 variable in both Node 4 and Node 7, and see whether we can get this initial value.

> var test2 = simpleContract.at(<deployed address>)
> test2.get()
Node 4
Node 7

This is what we wish to achieve! Node 4 cannot see the value on this deployed contract, and Node 7 can. The reason behind is that it is a private contract, and when deployed, Node 1 has specified only Node 7 in this transaction.

Here is what happens. Node 1 encrypts the payload and sends the encrypted payload and the decryption method to Node 7 (and Node 7 only). This is indexed by the hash of the encrypted payload. This hash replaces the payload in the original transaction. This modified transaction (in yellow box in the diagram) follows the normal ethereum process (p2p to all nodes).

Private Transaction in Quorum

If we again go into the Transaction detail

Node 1
Node 4 and 7

We observe that,

  • This private transaction, as other transactions in Ethereum, is visible to all nodes.
  • The input value is no longer the original payload (compared to the public transaction in the value “0x606060…”). Instead, it is a hash value “0x0e22be…”, and this hash is later used as index if the node is the specific recipient.
  • Therefore, no original payload appears even though every node gets this transaction record.
  • The v value is “0x26” (38), which means a private transaction.

Here is what happens when Node 4 and Node 7 process this transaction.

Node 4 finds nothing in its own Transaction Manager on this hash. Therefore the original payload is not visible in Node 4, and Node 4 does not process this transaction. Node 4 simply ignores this.

Node 7, on the other hand, finds in its own Transaction Manager the encrypted payload indexed by this hash. After proper decryption, Transaction Manager presents the original transaction payload to Quorum Node, and Quorum Node will process this transaction.

Summary

Quorum rides on top of Ethereum and makes it more enterprise relevant. In this article we have examined their example 7nodes and see how private transaction is executed.

Again, if you wish to know more about Quorum, here is where you find more in detail.

--

--