☰
6 · Generalized Bulletproofs (GBP)
We finally arrived in our last part in which the prover will fold the secrets in a compact and zero-knowledge proof, while the verifier will get the folded proof and be convinced that the proof is legit. Let's do it from both perspectives. First we will go through all the equations once from the prover side and learn why it becomes zero-knowledge and logarithmic in size.From the prover side
Let's start seeing the steps that the prover would do (obviously he will not do each step because they are fully developed here for educational purposes only and the prover will jump directly to the last equations in order to create the proof itself).
The full GBP equation again reads as:
We will go through all the steps here to achieve the compact final equations that the prover uses to create the final proof which will be stored in the blockchain and which contains most of the size of the membership proofs. So let's get started with the steps that we need to perform to achieve that:
1 - Squeeze the circuits equation into one number by dotting with
2 - Squeeze the GBP in the same way, each row weighted by a power of . Then fold both into one line. Notice that the equation holds and everything sums to zero.
3 - Push through each matrix product and name the results. The verifier could compute all of these himself.
4 - The second bracket becomes a sum of separate inner products
5 - Move the constant term to the right, which flips its sign.
Now let's collapse the six inner products into three
6 - The two terms share a right vector, so combine them:
7 - The first term is a product of two secrets. is public, so shift it across:
8 - Rewrite the term in the same basis:
9 - Terms one and three now share the right vector , so collect them:
10 - Add to both sides. It has no witness in it, so the verifier can add it too.
11 - Terms two and three now share :
12 - The first two share the left vector , so collect once more and then only three inner products remain:
Now let's make the three inner products into one via vector polynomials.
13 - We have a sum of inner products that we want to see as a single inner product. Put the left vectors into one vector polynomial and the right vectors into another and this will be the whole trick. Now we manipulate a bit the powers so partners add up to the same total : give left term the power , and its right partner the power :
14 - Now multiply them out. The inner product of two vector polynomials is the ordinary polynomial you get by taking the inner product of each pair of vector-coefficients and adding the powers
15 - Split that double sum into the part and the part
16 - Now you can see what happened. Every diagonal pair lands on and adds itself to that one coefficient. Every off-diagonal pair lands somewhere else. So reading out the coefficient recovers exactly
Just a piece of notation here. means "the coefficient sitting in front of in the polynomial ". For example . It just reads one coefficient out of a polynomial and throws the rest away.
So finally three inner products became the single number , sitting as one coefficient of one polynomial, and evaluating that polynomial at a random point (step 19) turns it into a single inner product of two ordinary vectors.
17 - Now let's run the recipe of steps 13 to 16 on the exact three inner products of step 12. Each pair becomes one term of and its partner one term of , at powers chosen to add up to . Two fresh random blinding vectors are dropped in at power , so they mask the secrets without ever landing on
Match the terms against step 12:
- The circuit secrets pair is the term on both sides.
- The outputs pair splits as at against at
- The tree pair splits one commitment at a time, at against at .
which is exactly the left side of step 12, and step 12 set it equal to . That value is what step 19 names . The blinds do not disturb it: and sit at , and no term pairs with them to reach (their own diagonal lands at ), so they hide and while leaving the coefficient untouched.
18 - Again, let's check the powers add to for each wanted pair
19 - The product is a single polynomial. Call it . Its coefficients are the numbers , and steps 13 to 18 were arranged so that one of them, the coefficient of , is exactly the value of the whole Generalized Bulletproof equation.
So the entire proof has come down to one claim: the coefficient . Every other is a byproduct of the multiplication and doesn't mean much (but we need to store them to prove that the whole thing is correct). Now the prover must convince the verifier of that one coefficient without ever handing over and .
Before we continue let's look what some symbols represent:
- is the polynomial and its coefficients are the numbers and is the single one that carries the equation we care about
- is a committed point that hides one coefficient , where , with the blind inside it.
- is the polynomial's value at a number , the one scalar the prover will open.
20 - The prover's first message is a batch of Pedersen commitments: everything it must pin down before the opening challenge , namely the secret values of the circuit, the tree nodes, and the "useless" coefficients of . In each equation the scalars multiply a generator vector (or a single for a scalar), plus a blind times , so the prover is bound to them while revealing nothing:
Term by term:
- commits the two input wire vectors: on the , on the . The blind is . (proof: w46 C1 · w123 C2)
- commits the output wires on the . The blind is . (proof: w47 C1 · w124 C2)
- commits the fresh random vectors , the masks that will hide the wires when are opened at . The blind is . (proof: w48 C1 · w125 C2)
- are Pedersen vector commitments to the tape on the , with blind . These terms hold the branches of the path, the children of each node the prover climbs, the blinds and the discrete-log decompositions and divisor coefficients that weld those branches to the real tree and re-randomize the output tuple, under fresh random blinds. (proof: w15–w30 C1 · w31–w45 C2)
- Each is a committed point that hides one byproduct coefficient of the polynomial behind a blind . The prover commits one for every byproduct it must hide. The special is not among them, because the verifier rebuilds it from public data in check one. (proof: w49–w101 C1 · w126–w175 C2)
Two reasons everything is committed rather than sent in the clear. The blinds (each times ) make every point reveal nothing about its contents, which is the zero-knowledge. And fixing them all before is drawn is what makes the Fiat-Shamir challenge sound: the prover is pinned to this witness and cannot adapt it to the challenge.
Once those commitments are done, the verifier's challenges are fixed by hashing them, so the prover cannot go back and adjust anything. The table below shows how each challenge is composed. The one we use next is , drawn right after the .
| challenge | equation |
|---|---|
| round |
21 - Open the polynomial at . Evaluating at turns and back into plain vectors and collapses the polynomial to one number, . The prover sends together with the matching blind . This is the blind of the whole opened value, which means, is the blind of all blinds recombined at , namely . Because is settled only after the are fixed, the prover cannot shape the polynomial to fit it.
In the proof: is w104 C1 · w178 C2. is w102 C1 · w176 C2.
22 - Gather the blinds of the point commitments into one scalar , each weighted by the power its commitment occupies in
In the proof: is w103 C1 · w177 C2.
The powers of come from step 17. Its coefficient came from (blind ), its from (blind ), its from (blind ), and each from (blind ). So collects the blinds of exactly those four point commitments and nothing else. It leaves out the blinds, which already sit inside for check one. So the prover sends so that check two can subtract and cancel all four masks in one shot. That cancellation is the zero-knowledge, since the blinds hide the committed values inside each point and then vanish together in the final equation.
23 - Check one. The goal here is to make sure that is the genuine whose coefficient equals . is a public value published by the prover in the proof. The verifier rebuilds as a commitment out of the and one public term on
The here is the constant from step 3: . Each is the constant term of constraint and is the number of constraints. They come from the gadgets that build the circuit, for example the point-on-curve check contributes the curve constant , the equality and offset gadgets contribute their shifts, the divisor gates their intercept, and so on. A constraint that sets no constant contributes as . The verifier reweights them by the same constraint powers it used everywhere else and sums, which is why is a single public number the verifier recomputes on its own.
So this equation checks that the opened scalar is the honest evaluation whose coefficient is the public value . With that, the prover has locked the coefficient and can no longer change it.
24 - Check two. The idea here is to construct a point commitment that opens as for the very hidden inside the committed points. First, let's start constructing , as the verifier would do, with only public information. The idea is to weight each published point by the power of that its coefficients sit and regroup them by generator.
The top line weights each committed point by the power of it occupies in . The middle line folds the public weight vectors onto the generators . The last line subtracts , which cancels the blinds of those four point commitments and glues to through .
Substitute the commitment definitions and every blind cancels, leaving
Step by step: how equation 24 becomes equation 25 (the equation just above)
0. What and are. In step 17, and are vector polynomials: each coefficient of a power is itself a length- vector, one entry per generator. Opening at the challenge (step 21) collapses each polynomial to a single vector, and . Then and are just the -th entries of those two vectors, the parts that ride on and . Reading the coefficients of step 17 component by component:
The index runs over the generator slots, not over the powers of . Everything below rebuilds exactly these two components, on and on .
1. Write out the commitments. Like we did in step 20, just a more compact notation.
2. Substitute them into the top line of step 24. Insert those definitions into and split each term by the generator it lands on
3. Finish the side. The row above is missing only the public piece of 's coefficient. The middle line of step 24 supplies exactly that piece, so the two add up to the full coefficient
4. Finish the side. Everything on is measured against the reweighted generator . The commitment parts are for the moment alone but when added to the public parts from the middle line, each already carrying its , then we can see the forming:
5. Account for the lone . One piece of 's coefficient is still missing: the public . On the reweighted generator it collapses to a constant with no left
That is the bare term in step 24 which coefficient is exactly per generator, not a missing scalar. Adding it completes the side to .
6. Cancel the blinds. The row of step 2 is precisely the scalar of step 22. The last line of step 24 carries , so the two annihilate
This is the zero-knowledge step: the masks vanish together and leave nothing on .
7. Glue to . The remaining term of the last line is , which is once we name .
Result. Collecting steps 3, 4 to 5, 6, and 7
which is exactly equation 25.
So the same is genuinely of the prover's real witness, so it cannot be a number made up to satisfy check one. Together the two checks force on the true witness.
Now the final part, the logarithmic fold.
25 - Regrouped, that point is exactly on the 's, on the reweighted 's and glued to
26 - Rename , . The entire statement is now one inner product
27 - Split each vector in half. The prover sends the two points that cross the split
In the proof: the round are w105–w120 C1 · w179–w192 C2, interleaved .
28 - A fresh challenge for this round is drawn in the same way by (hashing the round's ). Fold vectors and generators with opposite powers.
29 - Why it works: expand one folded inner product and watch the diagonal survive while the off-diagonal splits off
30 - The same expansion on the and sides gives the identical pattern:
31 - Add the three lines: the parts are exactly , the parts are exactly . So the folded statement is the same statement but half the length.
32 - After rounds each vector is a single scalar , sent in the clear. Each original generator survives times a product of the round challenges, chosen by the bits of
the reversed index on the side is because folded with the opposite powers to , so its surviving products come out mirrored.
In the proof: is w121 C1 · w193 C2. is w122 C1 · w194 C2.
Notice that the checks 1 and 2 are each multiplied by an independent random weight and dropped into one accumulator, so the whole proof, is settled by a single multiexponentiation, for each curve, collapsing to the identity, if it holds.
From the verifier point of view
Before we run all of that backwards, let's stop for a second and see clearly what the Generalized Bulletproof equation actually is for each side, because the prover and the verifier are reading the very same line written in two different domains. The prover reads it as arithmetic between secret numbers. The verifier reads it in the exponent, as operations between public points.
And why are these two the same thing? Because a Pedersen commitment is a group homomorphism, . Any linear relation that the prover knows to be in the field becomes a relation between points equal to , and since have no known discrete-log relation between them, the point equation can only collapse to if the scalar coefficients were already zero.
Now an important question: how can we be sure that the prover ran the honest circuit to arrive at that polynomial equation? Well, the matrices and the constants are not in the proof at all. They are the circuit, they are public and fixed, the same for every transaction, and we build them ourselves before even start looking at the proof that the prover gave us. If you look at the code, the verifier reconstructs its weights by himself and never from the proof. The prover chooses the witness and nothing else. So when the folded equation closes to , it closes against the verifier matrices, and the binding of the commitments together with Schwartz-Zippel force his hidden witness to satisfy exactly the constraints that the verifier wrote.
Alright, to get started let's try to follow one real output all the way through and see where it ends
up. is born as an ordinary one-time output on Ed25519, sitting on chain.
When someone spends it, the transaction never names it, the transaction only publishes the re-randomized
tuple and SAL proves the ownership of .
Meanwhile
is one leaf among hundreds of millions in the tree and we use the different circuits to prove that we know a path (in a hidden way) from that leaf up to
our root and that is the honest disguise of it, and doing that it turns 's coordinates
and every node of the path into the wires and the tree openings
. Those wires fold into and , which get sealed into the commitments
and the layer commitments . Finally the two checks and
the logarithmic fold crush everything into the few bytes that are inside the fcmp_pp field:
, the layer commitments, the coefficient commitments , the opened
scalars , and then the round points with the final . And
nowhere in all of that does ever appear itself! It survives only as blinded points and a handful
of folded scalars that look uniformly random to us, and yet, by everything we did above, they were proved
to be a genuine leaf of our own tree.
Now backwards
We previously built the proof forwards (from the prover perspective), now let's do it backwards (from the verifier's perspective) taking what we have on chain as starting point.
Reading what the prover sent:
1 - Read the three wire commitments, then draw the challenges the transcript binds to them:
2 - Rebuild the public weight vectors, folding the fixed circuit with powers of . Note already absorbs the here
3 - Get the one public constant of the whole equation, , from those weights alone:
4 - Read the -polynomial commitments and the challenge that opens them, then read the three opened scalars in the clear.
5 - Check one: the -polynomial identity (random weight )
The same as previously: claims to be the coefficient of and the verifier rebuilds that coefficient as a commitment and demands equality.
Everything on the right is public or a point the prover already sent. Passing this pins to the public value : the prover cannot name any other without breaking the binding of the commitments.
6 - Check two: Rebuild and fold (random weight )
Weight each public point by the power of its coefficients sit at, exactly the prover's step 22. The commitments enter here, at .
7- The weight vectors fold onto the generators, each at the power of its coefficient occupies in and : onto the and together with the tree weights onto the , the whole side reweighted by .
8 - Glue the opened to the independent base and subtract the gathered blind .
Steps 6 to 8 rebuild exactly the point of the prover's step 24, , with , . Nothing secret was used: every coefficient is a public weight or an opened scalar, every point was sent by the prover.
9 - The inner-product argument now folds through rounds. Each is read and enters at and . The final collapse the generators through the challenge products .
Why the proof is valid?
- Check one forces to equal the public value . A wrong would need a second opening of the commitments, that is, a discrete-log relation among independent generators, which nobody can produce.
- Check two forces for the reconstructed , . By the polynomial identity of step 17, , whose coefficient is the whole GBP equation of step 13. So being that coefficient and equal to means the circuit-plus-tree equation held on the committed witness.
- Independence is what makes the batch honest. share no known relation, so a random-weighted sum of the check equations can vanish only if each holds as written. A cheating prover would leave a nonzero coefficient on some generator that no weighting can cancel.
So a proof passes when, and only when, the opened is at once the public constant value (check one) and the inner product of the reconstructed witness vectors (check two), across both curve proofs together. That one scalar is tied above to the root by the Schnorr proof and below to the public tuple by the circuit.
Next we can see how a real proof looks like in the blockchain.