The payment terms of the invoice dictate that Bob will pay Alice in 90 days. However, Alice would like to have access to the money earlier and wants to finance it making the claim transferable by minting an NFT that represents a claim on the future revenue. To make on-chain trading of these assets easier, the NFT registry requires a set of proofs about the off-chain invoice. Specifically: the amount, Alice’s customer (Bob) and the invoice due date. Alice submits these Merkle proofs to the registry which then mints an NFT in her name with the metadata attached.

Introducing Privacy & Scalability with zkSNARKs

Requiring Alice to publicly disclose the identity of her customer is a privacy issue. It reveals to anyone exactly how much her customers are paying her. But without revealing that information, how can potential funders value the asset and decide on the interest Alice should be charged?

Let’s introduce Ted. Ted assess the credit-worthiness of different buyers and publishes a list of companies and their credit ratings. This list is serialized as a Merkle tree of which the Merkle root is published in a smart contract. Felix, the funder of the invoice, doesn’t have to trust the rating itself but can go through the list of companies and see for himself that the ratings that Ted publishes are reasonable. If he spots any companies with ratings that he fundamentally disagrees with, he can decide to not accept Ted’s ratings.

Alice now must prove that a company from this list signed the invoice and revealed their rating. The funder Felix knows that the company is one of the companies in the list of credit ratings that Ted published. For this to work, Alice must prove this without revealing anything about the public key that was used to sign the document. Zero-knowledge proofs are the perfect tool for this. zkSNARKs have both private and public inputs. When verifying a proof, the verifier can assert that the given proof is valid for a set of unknown private parameters and some public parameters. In this example, the proof proves that Alice is able to prove without revealing:

There is a leaf in the credit rating Merkle tree with rating r and public key pk

and public key That she has a valid signature of document made with the key pk

made with the key A valid Merkle proof of the buyer field in the document invoice

invoice A valid Merkle proof for the invoice amount and that the nft_amount is less than the invoice amount

ZoKrates and BabyJubJub with pycrypto

Let’s introduce two key components that have allowed us to implement this. ZoKrates greatly simplifies this by providing an imperative programming language that is easy to use and reason about. Instead of designing circuits, you write programs with well-known concepts such as variables, functions or loops. To write your first zero-knowledge program and verify proofs on Ethereum, check out the ZoKrates tutorial.

Here is how to create a zkSNARK that proves that you know the pre-image of a given public hash:

import “hashes/sha256/512bitPacked.code” as sha256 def main(private field[4] i) -> (field[2]):

return sha256(i)

private field[4] i is the private input, the pre-image and the output (field[2]) is the public hash provided to the prover which then generates a proof that the verifier can verify for correctness without ever finding out about the private input.

Zero-Knowledge proofs are computationally expensive. They behave very differently than a regular program and thus require a set of highly optimized cryptographic primitives. Generally, the computational complexity is measured in the number of constraints a circuit has. As an example, the SHA256 hash function is roughly 26'000 constraints while a Pedersen hash is ~2000 constraints. Verifying a secp256k1 signature (the signature scheme used by Ethereum) would be very costly, as the underlying elliptic curve can not be represented efficiently in the available zkSNARK proof system. Hence the community developed “snark-friendly” elliptic curves, which first have been introduced by Zcash, and are also used in this work, specifically the BabyJubJub curve. By using these primitives, you can easily achieve an order of magnitude in performance increase. Pedersen, for example, is more than 10x faster than SHA256. That can be the difference of a proof taking a few seconds or longer than a minute.

Due to these schemes being very specific to the BabyJubJub curve, the hash function and signature schemes are not readily available in standard crypto libraries. In order to make it easy to work with them, the library zokrates-pycrypto makes those cryptographic primitives available in Python. In our proof of concept, we have integrated the Python library with our go node to get started quickly. An at the time incomplete implementation of the BabyJubJub curve is also available by the folks at Iden3. There is an excellent primer on why BabyJubJub is needed and how it works by Stefan Deml.

Building the Circuit

With all those puzzle pieces in place, we can now show you how we’ve built an efficient and privacy preserving NFT Registry. If you’ve followed along until here, I suggest you head over to github to download the source code for the example discussed in the article here. You can find a simple NFT registry based on open-zeppelin’s contract that lets anyone mint an NFT by providing a zkSNARK that validates the NFT completely off-chain. With that, we achieve that the private information in the off-chain document stays private but all NFTs have verifiable attributes.

The file src/circuit/nft.code contains the key computation that needs to be proven. Lines 6–8 deal with packing and unpacking variables and the lines following are the validation of the different Merkle proofs and verifying the values of the leaves.

Resources–Where next?

This article touches on quite a broad range of topics and I sadly haven’t had time to time to go into all of it. Below are a few links that should help you getting started. If you have any questions, feel free to reach out to me on Twitter or shoot me a line.