Our general approach is to utilize zero-knowledge proofs (ZKPs) as it offers the ability to share proofs without sharing data. In this post, we explore a use case where users are able to prove their minimum balance without exposing their actual balance.
Here are a couple of hypothetical examples that point to a category of use cases that interests us, sometimes known as Proof of Balance or Proof of Solvency:
Satya wants to join the famous Bering Sea Billionaire’s Club, and needs to convince Chloe, the club’s president, that he’s a billionaire. However, he’s not comfortable sharing his actual net worth.
Antoshka wants to go out dancing, and has to prove he’s over 21 to the bouncer. However, his identification is a foreign passport that contains his nationality, address, travel history, birthdate, and ID number, none of which he wants the bouncer to see.
By now the gist is becoming clear – we want to share some property of our private information (such as its magnitude, or how it interacts with other publicly available date) without revealing it. We refer to this as the selective disclosure of confidential information. In a more emotional language: ‘I seek your approval, but I don’t trust you.’
In each of these cases, we have a prover and a verifier. The prover needs to demonstrate evidence of the truth of a certain conjecture (I am 21! I deserve to go dance!) to the verifier. This evidence is referred to as a ‘proof’.
In this context, proofs should be interpreted in the detective sense (‘Do you have any proof of that, Sherlock?’) rather than in the mathematical sense (‘Here is an inductive proof of your theorem, Watkins’). That is, we are interested in empirical proofs rather than formal proofs.
Note: If you are already familiar with Part I of this series, you may skip this section straight to the code.
Let’s continue with the example in which Satya wants to join the Billionaire’s Club. At the moment, what are Satya’s options?
Manual hiding: He could print out his bank balance, meet with Chloe, and place his hand over some of the numbers. Or he could ink over some of the numbers he didn’t want to share.
Trusted third party: He could get a signed letter from the bank attesting his balance is big enough.
Hijinx: He could move all but 1 billion dollars out of the account in question, print the statement, and then return the rest.
The second solution, using the Bank as a trusted third party, seems to be the most reasonable. But what if Satya is joining new clubs all the time and the time and process required to get a signed attestation is cramping his style? Is there a way Satya could have a little more control over the situation, that requires less work and interaction from the bank?
If only Satya could prove his minimum balance without asking his bank to sign custom documents every time! What Satya needs is an on-demand proof generator on his side. So let’s cook one up.
First we need three players:
And then we need some information from our (trusted) bank, namely:
Prover Kit (PK): A proof-generating executable used by a client wishing to demonstrate their proof of balance. The PK takes public two inputs (an encrypted balance and a balance to prove), and one private input (a private key to decrypt the encrypted balance.) The code for the PK is posted publicly and verifiably, so the prover feels safe running it. The PK outputs a confirmation of whether the encrypted balance is greater than or equal the balance to prove, and a proof (π) that the confirmation was correctly calculated.
Verifier Kit (VK): A proof-confirming executable used by someone wishing to verify the authenticity of a balance claim. VK outputs “true” if and only if it is run with the following inputs:
With this kind of system, anytime Satya runs into a new club he wants (proposes) to join, he can use PK to generate a proof π. As long as the club officials have access to the corresponding VK, he can convince them beyond a reasonable doubt.
Note that the verifier cannot verify the proof using someone else’s encrypted balance nor change the balance amount to prove. This is because VK must be run on exactly the same inputs as PK in order to verify properly.
So there can be no funny-business at the Club trying out various numbers with the aim to determine Satya’s actual balance.
Satya’s actual bank balance stays private between the Bank and Satya. However, the proof of the balance stays private between Satya and the Chloe as neither needs to contact the Bank to verify the proof. Thus, the same data, Satya’s bank balance, discloses itself in one way between the Bank and Satya, and in another way between Satya and the Club.
The selective disclosure of confidential information is a key feature of this system.
This kind of proof system actually does exist, and is one example of a general zero-knowledge proof. The approach we used here (ZK-SNARKs) has evolved through years of research, and there are some nice technical explanations of it (like these ones). Some more general intuitive explanations use color-blindness and caves to illustrate the concepts involved.
Here’s a stab at a brief explanation: In a ZKP, the verifier (Chloe) with the help of a trusted third party (bank) asks a bunch of questions to the prover (Satya). In our implementation, one such key question to Satya is: “Can you decrypt your bank balance?”
If Satya didn’t know the right answer, there’s a pretty low chance that he would answer any of the questions correctly, and therefore, a really low chance he could answer all the questions correctly. However, the questions are so obfuscated that the verifier can’t really learn anything about the prover’s secret through the answers to his own questions.
Thus, by answering questions correctly whose right answers demands the knowledge of the secret, the prover can convince the verifier that he indeed knows the secret. In logic, this approach is referred to as proof by elimination. We will come back to this towards the end of this article.
In our demo, the questions are set up by the trusted third party. We’ll go into a little bit more detail, just enough for our demo to make sense, but those of you who want to the understand the underlying math are encouraged to check out the references above.
The source code for our implementation is located here, and is essentially some wrappers around the ZKP implementation done by the fine folks at pepper-project, using the libsnark library. It is based on the theory of verifiable computation, which is a technology to prove the correctness of computations without the need to re-run the computation.
We have prepared a Docker container based on the above repository, which can be run as follows:
# docker run -it stratumn/zk-proof-of-balance bash
A ZKP System consists of:
The same three players as above:
– Prover §
– Verifier (V)
– Trusted third party (T)
– Function L which decrypts P’s balance to check if it is greater than a the amount to be proven
– Public Input x: “1 billion” that is the amount to be proven
– Private Input w: P’s private key to decrypt the amount
Note: The Prove algorithm along with the prover’s key as well as the Verify algorithm with the verifier’s key roughly translates to the Proving Kit and the Verifying Kit respectively as described earlier.
We will assume that the bank’s commitment, the prover’s proposal, the verifier’s challenge and the response is already set in place, since these three steps are external to the core ZKP approach.
Thus, use of this system proceeds in three phases of decreasing computational complexity and resource usage: setup, construction, and verification.
First, T compiles the function L into a binary circuit C whose output is true if and only if its arguments (the public inputs x and the private inputs w) satisfy the logic in the function L. The logic is to decrypt the balance and check the difference with 1 billion. T may even post this function in plaintext and its compiled equivalent publicly for the sake of transparency.
T then runs Generate to generate keys for the prover and verifier. Generate takes some secret information (known only to T) as an argument that would ideally be destroyed later, so no one else could regenerate the prover’s key in the future. The compiled circuit C is also an input to Generate.
T then makes signed copies of σ and τ available to those who might want to prove or verify with this system.
We encapsulate these steps in the following script:
which generates everything necessary for the prover and verifier to continue
Input to Generate:
The body of the function (that is supposed to receive x & w as inputs) to be converted into a binary circuit:
/proof_of_balance/proof_of_balance.c This is equivalent to L.
Bank’s secret for generating the keys
Value of k where k is the security parameter for 1^k key length
Output of Generate:
Compiled R1CS circuit, that gets embedded in the prover’s key σ (compiled from /proofofbalance/proofofbalance.c)
– Verifier τ:
– Prover σ:
– Verifier v:
– Prover p:
When P receives a request for verification from V, P can run Prove with:
In our case, we run the following script:
# ./pob-prove.sh 9999999999 1000000000
The first argument (9999999999) is the prover’s actual balance, and then second argument the balance to prove. The proving script encrypts the actual balance with a public key (generated by the script) and then generates a files with the public and private inputs to the Prove at
prover_verifier_shared/proof_of_balance.inputs and bin/exo0, respectively.
Input to Prove:
Output of Prove:
P can then send π to V, along with the public inputs used in the proof generation. In our case, of course, V is using the same machine.
The secret w is in no way related to the secret in the setup phase. The result r of the computation can be 0 or 1, where 1 indicates success.
Once V receives the relevant information from the P, as well as the verification key (either from the P or T), V checks that the inputs P used were correct. As there is no point in verifying a conjecture that holds no interest to the verifier, V also checks if the result r is successful. If V is satisfied with the inputs and outputs, V runs Verify with inputs as:
Verify will tell the verifier if the proof has been accepted or rejected. It will always fail if the inputs (x) that V provides are different from the inputs that P provided. In this way, V can be sure P ran Prove with the correct inputs, while at the same time P can be sure V cannot learn more about P by running Verify with a variety of inputs to learn more about P’s actual balance.
As the references on ZKP point out, this system has some nice mathematical properties that allow us to trust it, including:
Completeness: A prover should be able to convince a verifier anything that is correct. In other words, you can prove anything that’s right.
If x is in L and w is witness for x, then the proof π produced by the prover on input (x, w) will be accepted by the verifier with, except possibly with some small probability of doubt.
Computational soundness: A prover should not be able to prove a verifier anything is incorrect. In other words, you cannot prove anything that’s wrong.
For any polynomial-time adversary running on input (1^k, pub) and producing a pair (x, π), the probability that x is not in L and that (x, π) is accepted by V is negligible in k, except with some small probability known as the soundness error.
Succinctness: It can be verified easily.
The length of π is polynomial in k.
sZero knowledge: Nothing more than what is proved is learned, that is, no malicious verifier can figure out the exact balance of Satya.
When x ∈ L and the prover is honest, even a malicious verifier V’ “learns nothing” beyond the fact that x ∈ L.
For the sake of Satya’s confidentiality, instead of directly proving his balance, he can prove three things in return:
Knowledge: That he is in possession of a legit bank statement, based on which, he knows his bank balance
Use: That he is using the knowledge of his bank balance as stated in point 1 to claim that he has at least the amount being asked for
Calculation: That he is accurately calculating if he has enough funds. That is, he has to prove his subtraction of the amount asked from his balance has been carried out correctly.
What the prover is essentially saying:
I, Satya, have the knowledge of my statement of balance (as of a certain date and time) signed by the bank that you, the club, have trust in. Using that and only that knowledge, I subtract the amount asked from my balance in order to prove to you that I have at least the amount you are looking for.
In order for the verifier to be convinced that the prover has the right secret, she must be able to check:
Knowledge: the prover’s (Satya’s) secret came from a trustworthy source (the bank)
Use: that he used this trustworthy secret to claim that he has sufficient funds
Calculation: that he calculated if he has enough funds correctly to prove his claim
What the verifier is essentially saying:
I, Chloe, will let you in the club, if you can prove you have enough funds even though I have no access to your account balance, as long as the bank has informed you of your account balance which you are using to calculate the difference correctly.
The centerpiece of every ZKP is figuring out an action that would evaluate to true only if the secret to be proved is absolutely necessary for it. In our case we used the fact that the secret, being Satya’s private key is absolutely necessary to decrypt his balance, and thus we we made the decryption as the centerpiece of the bank’s circuit. With this, what every zero knowledge proof is trying to say is that “you must have had knowledge of the secret to be able to run this circuit and have this particular result”. Every ZKP utilizes this proof by elimination technique as its basis.