This proof of concept (POC) is intended to demonstrate how ZSL can complement Quorum, and provide a platform for experimentation and exploration of different use cases. It implements a simplified, stripped-down version of the Zerocash protocol to enable rapid prototyping. There is no formal security proof for the protocol, and it should not be considered “production-ready”.
The contents of this repository are licensed under the Apache License 2.0.
This repository contains:
- Solidity contracts to provide access to ZSL functionality added to Quorum. Currently these have been written with Solidity 0.3.6 to be compatible with Quorum 1.5. In future, they are likely to be updated to Solidity 0.4.0 for Quorum 1.6.
- Example contracts using ZSL to demonstrate an example use-case of a private trade of two different assets.
- Zsl-golang library which adds ZSL functionality to Quorum. Currently developed with go version 1.7.3.
- Utility to create custom parameters for use with ZSL.
Quorum with ZSL provides a JavaScript API accessible under the zsl.* namespace in the Quorum terminal.
- zsl.getRandomness() returns buffer
- zsl.getNewAddrses() returns keypair
- zsl.debugShielding() returns bool
- zsl.debugUnshielding() returns bool
- zsl.debugShieldedTransfer() returns bool
- zsl.loadTracker(filename) returns json
- zsl.saveTracker(filename, json) returns bool
- zsl.getCommitment(rho, pk, value) returns hash
- zsl.getSendNullifier(rho) returns hash
- zsl.getSpendNullifier(rho, sk) returns hash
- zsl.createShielding(rho, pk, value) returns proof
- zsl.createUnshielding(rho, sk, addr, value, treeIndex, authPath) returns proof
- zsl.createShieldedTransfer(rho1, sk1, value1, treeIndex1, authPath1, rho2, sk2, value2, treeIndex2, authPath2, outrho1, outpk1, outvalue1, outrho2, outpk2, outvalue2) returns proof
- zsl.verifyShielding(proof, send_nf, commitment, value) returns bool
- zsl.verifyUnshielding(proof, spend_nf, root, addr, value) returns bool
- zsl.verifyShieldedTransfer(proof, anchor, spend_nf1, spend_nf2, send_nf1, send_nf2, commitment1, commitment2) returns bool
This library provides a Golang API for Quorum to create and verify shielded transactions. Internally, the library makes use of the C++ libsnark library.
System requirements for building are openssl and boost.
- Ubuntu:
sudo apt get install libboost-all-dev libssl-dev
- Fedora:
sudo dnf install openssl-devel boost-devel
Build using a Makefile:
make
Testing the library require proving and verification keys, also known as parameters.
Example parameters can be downloaded from zsl-q-params.
Custom parameters can be generated by using the createparams.go
tool found in the util
folder of the repository.
To create parameters:
go run createparams.go
Copy the parameter files into the current directory, and test the library with:
go test
To use ZSL with 7nodes, one of the Quorum examples, clone the following repositories into a folder, e.g. $HOME/projects/
- Quorum: https://github.com/jpmorganchase/quorum (zsl branch)
- Contracts/Library: https://github.com/jpmorganchase/zsl-q
- Parameters: https://github.com/jpmorganchase/zsl-q-params
- 7Nodes: https://github.com/jpmorganchase/quorum-examples (zsl branch)
For running locally, to build quorum
the installation of golang is necessary. Branch zsl_geth1.6
requires go version 1.7.X.
Build quorum
and copy binaries into a folder in the quorum-examples
project:
mkdir ~/projects/quorum-examples/zsl-tmp/
cd ~/projects/quorum
make all
cp build/bin/* ~/projects/quorum-examples/zsl-tmp/
Download the parameters found in the Github Release
tab of zsl-q-params
and copy into the quorum-examples
folder:
cp shielding.pk ~/projects/quorum-examples/
cp shielding.vk ~/projects/quorum-examples/
cp unshielding.pk ~/projects/quorum-examples/
cp unshielding.vk ~/projects/quorum-examples/
cp transfer.pk ~/projects/quorum-examples/
cp transfer.vk ~/projects/quorum-examples/
Next, follow the set of instructions depending on whether you want to run 7nodes inside a VM or locally on your machine.
Launch the VM.
vagrant up
vagrant ssh
When launching, vagrant/bootstrap.sh
will automatically run and copy geth and bootnode from the zsl-tmp
folder into the VM path `/usr/local/bin'.
Symbolic links should already exist to the parameters, but if not, create them manually:
cd quorum-examples/7nodes
ln -s /vagrant/shielding.pk shielding.pk
ln -s /vagrant/shielding.vk shielding.vk
ln -s /vagrant/unshielding.pk unshielding.pk
ln -s /vagrant/unshielding.vk unshielding.vk
ln -s /vagrant/transfer.pk transfer.pk
ln -s /vagrant/transfer.vk transfer.vk
Set up the Constellation Transaction Manager. The prerequisites are listed here and the precompiled binaries can be found here.
When running the example locally and not in a VM, in the folder quorum-examples/examples/7nodes
:
- delete symbolic links:
rm *.pk *.vk
- move the parameters into the folder to replace the deleted symbolic links.
- ensure the quorum and constellation binaries can be found in your local
$PATH
i.e. copy the quorum binaries into/usr/local/bin/
or add~/projects/quorum/build/bin
to your$PATH
.
In folder quorum-examples/examples/7nodes
enter:
./raft-init.sh
./raft-start.sh
geth attach qdata/dd1/geth.ipc
Now that you have the 7nodes example environment running, you can try out some of the examples below.
Troubleshooting Tip:
Increase the sleep
time in the ./raft-start.sh
script if errors stating the transaction manager isn't running appear.
Some of the examples use the ZSL API, a set of Javascript functions added to Quorum and available under the zsl.*
namespace. You can list them in the Quorum terminal by entering zsl.
and pressing the TAB key.
The examples also make use of ztracker.js
which is a file containing a collection of helpful functions, some of which are grouped under the zdemo.*
namespace.
Ztracker:
- ztracker.shield(ztoken, value)
- ztracker.unshield(note_uuid)
- ztracker.list(ztoken, optional filter amount)
- ztracker.balance(ztoken)
- ztracker.load(filename)
- ztracker.save(filename)
Zdemo:
- zdemo.create_ztoken(tokenName, initialSupply)
- zdemo.get_ztoken(address)
- zdemo.create_zprivatecontract(tracker, bid_constellation_address, bid_token, bid_amount, ask_constellation_address, ask_token, ask_amount)
- zdemo_create_zprivatecontract(tracker, bid_constellation_address, bid_token, bid_amount, ask_constellation_address, ask_token, ask_amount)
- zdemo.get_zprivatecontract(address)
- zdemo.accept_bid(tracker, zprivatecontract)
- zdemo.submit_payment: function(tracker, ztoken, zprivatecontract, noteId)
- zdemo.submit_settlement: function(tracker, ztoken, zprivatecontract, noteId)
- zdemo.watch_events: function(zprivatecontract)
Create the ztoken:
loadScript('ztracker.js')
ztoken = zdemo.create_ztoken('ACME', 100000)
Explore the ztoken:
ztoken.name()
ztoken.balance()
ztoken.size()
ztoken.depth()
Generate a keypair:
keypair = zsl.getNewAddress()
pk = keypair['a_pk']
sk = keypair['a_sk']
Generate some randomness:
rho = zsl.getRandomness()
How much do we want to shield from our funds?
value = 30000
Generate proof for shielding:
result = zsl.createShielding(rho, pk, value)
proof = result['proof']
cm = result['cm']
send_nf = result['send_nf']
Let’s verify this proof manually (the ztoken will automatically perform this step)
zsl.verifyShielding(proof, send_nf, cm, value)
Let's ask the ztoken contract to add this proof and update balances.
ztoken.shield(proof, send_nf, cm, value, {from:eth.accounts[0], gas:54700000})
Once the transaction has been mined, verify the zcontract has been updated.
ztoken.size()
ztoken.balance()
Try to add the same proof again, it won't be allowed.
ztoken.shield(proof, send_nf, cm, value, {from:eth.accounts[0], gas:54700000})
ztoken.size()
ztoken.balance()
Let's create a proof to unshield these funds.
rt = ztoken.root()
witnesses = ztoken.getWitness(cm)
treeIndex = parseInt(witnesses[0])
authPath = witnesses[1]
result = zsl.createUnshielding(rho, sk, eth.accounts[0], value, treeIndex, authPath)
proof = result['proof']
spend_nf = result['spend_nf']
Let's verify this proof manually (the ztoken will automatically perform this step)
zsl.verifyUnshielding(proof, spend_nf, rt, eth.accounts[0], value)
Now ask the ztoken contract to unshield and update balances:
ztoken.unshield(proof, spend_nf, cm, rt, value, {from:eth.accounts[0], gas:54700000})
Once the transaction has been mined, verify the zcontract has been updated.
ztoken.size()
ztoken.balance()
Try to unshield again, it's not allowed.
ztoken.unshield(proof, spend_nf, cm, rt, value, {from:eth.accounts[0], gas:54700000})
ztoken.balance()
Congratulations, you've just completed your first shielding and unshielding!
Alice and Bob will execute a private trade of two different assets. In this example, Alice will offer 300 USD for 100 ACME.
On both Alice's node and Bob's node:
loadScript("zdemo.js")
This will store the address of node account and private constellation address in local variables, as follows:
var alice = "0xed9d02e382b34818e88b88a309c7fe71e65f419d";
var bob = "0xca843569e3427144cead5e4d5999a3d0ccf92b8e";
var alice_constellation = "BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=";
var bob_constellation = "QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=";
The script zdemo.js
will also load ztracker.js
to provide helper functions and a lightweight note tracker which acts like a wallet.
On Alice's node, create two z-contracts:
ztoken_usd = zdemo.create_ztoken("USD",100000)
ztoken_acme = zdemo.create_ztoken("ACME",50000)
On Alice's node, create a note tracker:
tracker = new ztracker();
On Alice's node, shield 10000 USD and transfer 5000 ACME to Bob's node.
tracker.shield(ztoken_usd, 10000)
ztoken_acme.transfer(bob, 5000, {from:alice, gas:5470000});
On Alice's node, create a private contract for a trade, 300 USD for 100 ACME.
zprivatecontract = zdemo.create_zprivatecontract(tracker, alice_constellation, ztoken_usd, 300, bob_constellation, ztoken_acme, 100);
On Alice's node, watch for events that update the state of the trade.
zdemo.watch_events(zprivatecontract)
On Alice's node, obtain the address of the private contract.
a = zprivatecontract.address
On Bob's node, use this address to reference the private contract.
zprivatecontract = zdemo.get_zprivatecontract(a)
On Bob's node, watch private contract events and reference the z-contracts involved in the trade.
zdemo.watch_events(zprivatecontract)
ztoken_usd = zdemo.get_ztoken(zprivatecontract.bidTokenAddress())
ztoken_acme = zdemo.get_ztoken(zprivatecontract.askTokenAddress())
On Bob's node, set up a a note tracker and shield some of the ACME funds received from Alice's node.
tracker = new ztracker()
tracker.shield(ztoken_acme, 1000)
ztoken_acme.balance()
tracker.balance(ztoken_acme)
tracker.list(ztoken_acme, 1111)
tracker.list(ztoken_acme, 500)
On Bob's node, accept the bid of 300 USD for 100 ACME.
zdemo.accept_bid(tracker, zprivatecontract)
The trade has now been agreed, so it's time for both nodes to settle the trade.
On Alice's node, select a note to spend by using its identifier (uuid).
tracker.list(ztoken_usd)
uuid = ...
On Alice's node, submit the 300 USD payment to Bob's node.
zdemo.submit_payment(tracker, ztoken_usd, zprivatecontract, uuid)
tracker.spent
tracker.list(ztoken_usd)
on Bob's node, settle the trade by sending 100 ACME to Alice's node.
tracker.list(ztoken_acme)
uuid = ...
zdemo.submit_settlement(tracker, ztoken_acme, zprivatecontract, uuid)
The trade should now be settled. Now check balances and unshield funds.
On Bob's node, unshield the USD received.
ztoken_usd.balance()
tracker.balance(ztoken_usd)
tracker.list(ztoken_usd)
uuid = ...
tracker.unshield(uuid)
ztoken_usd.balance()
tracker.balance(ztoken_usd)
tracker.list(ztoken_usd)
tracker.spent
on Alice's node, unshield the ACME received.
ztoken_acme.balance()
tracker.list(ztoken_acme)
uuid = ...
tracker.unshield(uuid)
ztoken_acme.balance()
tracker.list(ztoken_acme)
Congratulations, you've completed your first private trade!
Continuing example 2 above, on Alice's node, save the tracker to disk.
tracker.save("alice.tracker")
On Bob's node, create a new tracker from the data on disk.
tmp = new ztracker();
tmp.load("alice.tracker")
Examine the new tracker to confirm that the data matches that of Alice's node.
tmp.list(ztoken_usd)
tmp.list(ztoken_acme)
tmp.keypair
The loading and saving of tracker data to disk is for demo purposes only.