Bringing Privacy to Non Fungible Tokens — A recap from the ZoKrates Workshop at Zcon1
We’re working on major improvements in how we can provide privacy to users of our NFTs. The work described here is the result of a collaboration with Stefan Deml, contributor to the zokrates-pycrypto library and the ZoKrates project. Together, we worked on a proof of concept for building a tightly integrated zero-knowledge proof using the ZoKrates language and toolchain. It was presented in the “ZoKrates: zkSNARKs for Developers” workshop at Zcon1, June 2019 in Split, Croatia.
This is a rather technical article assuming some knowledge of how zero-knowledge proofs work. If you are unfamiliar with zkSNARKs, I highly recommend reading this blog post.
At Centrifuge, we have strong privacy requirements. We set out to build our protocol to rely on messages exchanged off-chain instead of using a completely public global ledger. While it comes with its own downsides, this allows for transactions across the Centrifuge network to be done completely hidden from any third party. In order to interact with the ecosystem on Ethereum, we allow for assets (such as invoices or purchase orders) to be tokenized and minted as a Non-Fungible Token (NFT). The non-fungible token standard, ER721, is an easy-to-use standard to track ownership and transfer it on Ethereum. Information about the off-chain transactions is provided to an NFT Registry to verify a given set of rules when minting an NFT. For example, when minting an NFT on Centrifuge, you would provide a proof that you are the supplier on an invoice. This exposes the information publicly which is not always desired. In this article, we will show you how one can improve on that by using zero-knowledge proofs.
To add the zero-knowledge capabilities we integrated with ZoKrates. ZoKrates is a toolbox for zkSNARKs on Ethereum. It includes an easy-to-use domain-specific language for developers to leverage the power of zero-knowledge proofs in their decentralized applications.
NFTs with a Credit Score
Centrifuge has “Unpaid Invoice” NFTs, these NFTs tokenize the obligation of the buyer to pay the supplier. By selling this NFT, the supplier gives the new owner of the NFT a claim on the future revenue. This can then be used in #defi as collateral to borrow money from decentralized lenders. In the real world, this is a common practice called factoring and traditionally done by factoring companies or banks. By moving those invoices on-chain, matching between funders and borrowers can be done in a trustless way. However, funders need to have a way to value an asset. One way to do this is to expose additional information on the asset publicly which results in a loss of privacy. Your list of customers and how much each of them is paying is usually a big trade secret and therefore something businesses are not willing to reveal publicly. By having a third party rate the debtor (the customer/invoice recipient) on the NFTs and allowing the suppliers to mint those NFTs without revealing who their buyer is, we make valuing these assets a lot easier and increase the privacy of these on-chain financing transactions. The score that is attached to the NFT can now be used in place of individually valuing each asset. This is a more convenient way of financing assets and preserves the privacy of the users.
Minting an NFT
Every story in cryptography starts out with Alice and Bob. In our example, Alice sends Bob an invoice for $1,000. Alice and Bob exchange this information on the Centrifuge P2P network and cryptographically sign the invoice, a Merkle tree of the invoice is committed on-chain. A smart contract is used to store a root hash for each document identifier. Our article on precise-proofs talks about this in more detail.
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
rand public key
- That she has a valid signature of
documentmade with the key
- A valid Merkle proof of the buyer field in the
- A valid Merkle proof for the invoice amount and that the
nft_amountis 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 sha256def main(private field i) -> (field):
private field i is the private input, the pre-image and the output
(field) 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.
You can’t perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
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.
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.