ZK/SEC Research notes from zkSecurity
All posts
Bulletproofs · Part 1 of 3

Breaking Down Bulletproofs: No Pairings, No Trusted Setup

bulletproofs

The Bulletproofs protocol allows you to produce these zero-knowledge proofs based only on the discrete logarithm assumption, no trusted setups. This essentially means no pairing (like KZG/Groth16) but a scheme more involved than just using hash functions (like STARKs). This protocol has been used for rangeproofs in Monero, as a polynomial commitment scheme in proof systems like Kimchi (the one at the core of the Mina protocol), and in Zcash's Halo 2. So it's quite versatile as well as well-deployed in the real world.

The easiest way to introduce the Bulletproofs protocol, I believe, is to explain that it's just a protocol to compute an inner product in a verifiable way:

$$\langle \vec{a}, \vec{b} \rangle = c$$

If you don't know what an inner product is, imagine the following example:

$$\langle \begin{pmatrix}a_1 \\ a_2 \\ a_3 \\ a_4\end{pmatrix}, \begin{pmatrix}b_1 \\ b_2 \\ b_3 \\ b_4\end{pmatrix}\rangle = a_1 b_1 + a_2 b_2 + a_3 b_3 + a_4 b_4$$

Using Bulletproofs makes it faster to verify a proof that $\langle \vec{a}, \vec{b} \rangle = c$, rather than computing the inner product yourself. But more than that, Bulletproofs even lets you hide one or both of the inputs, or the output, to obtain interesting ZK protocols.

Furthermore, if computing an inner product doesn't sound that sexy by itself, you can imagine that it is used to do actual useful things like proving that a value lies within a given range (a range proof), or even that a circuit was executed correctly (when used within a general-purpose zero-knowledge proof system). But this is out of scope for this explanation :)

Alright, enough intro, let's get started. Bulletproofs and its variants always "compress" the proof by hiding everything in commitments, such that you have one single point that represents each input/output:

  • A = $\langle \vec{a}, \vec{G} \rangle$
  • B = $\langle \vec{b}, \vec{H} \rangle$
  • C = $\langle \vec{a}, \vec{b} \rangle Q $

where you can see each point $A, B, C$ as a non-hiding Pedersen commitment with independent bases $\vec{G}, \vec{H}, Q$ (so the above calculations are multi-scalar multiplications). To drive the point home, let me repeat: single points instead of long vectors make proofs shorter!

Because we like examples, let me just give you the commitment of $\vec{a}$:

$$\langle \begin{pmatrix}a_1 \\ a_2 \\ a_3 \\ a_4\end{pmatrix}, \begin{pmatrix}G_1 \\ G_2 \\ G_3 \\ G_4\end{pmatrix}\rangle = a_1 G_1 + a_2 G_2 + a_3 G_3 + a_4 G_4$$

Different protocols (like the halo one I talk about in the first post) since bootleproof (the paper that came before bulletproofs) aggregates commitments differently. In the explanation above I didn't aggregate anything, but you can imagine that you could make things even smaller by having a single commitment $P = A + B + C$ to the inputs/output.

At this point, a prover can just reveal both inputs $\vec{a}, \vec{b}$ and the verifier can check that they are valid openings of $A, B$, and that $c = \langle \vec{a}, \vec{b} \rangle$ is a valid opening of $C$. If we had aggregated all commitments into a single commitment $P$ then you would check that aggregated opening.

But as I said earlier, this is not very efficient (you have to perform the inner product as the verifier) and it also is not very compact (you have to send the long vectors $\vec{a}$ and $\vec{b}$).

I know it's also not zero-knowledge, but we will just explain Bulletproofs/IPA without hiding, and for the hiding part we'll just ignore it as we usually do when handwaving explanations of ZKP schemes.

That being said, the prover will eventually send the two input vectors in the clear, but before doing that they will reduce them. More precisely, the prover and the verifier will do this dance to reduce, together, the problem statement $\langle \vec{a}, \vec{b} \rangle = c$ to a much smaller $\langle \vec{a'}, \vec{b'} \rangle = c'$ where the vectors $\vec{a'}$ and $\vec{b'}$ both have a single entry.

If the original vectors were of size $2^n$ then Bulletproofs will perform $n$ reductions in order to get that final statement (as each reduction halves the size of the vectors), and then will send these "reduced" input vectors $a',b'$. At this point the verifier will produce $c' = \langle a', b' \rangle$ and check that they are valid openings of the reduced commitments $A', B'$ and $C'$.

If you've followed until now, you should now be expecting that we need to answer the following two questions next:

  1. how does the reduction from $A, B, C$ to $A', B', C'$ work?

  2. how is it verifiable?

To reduce stuff, we do almost the same basic operation that we like to do in every "folding" protocol: we split our vector in half and produce a random linear combination of the two halves. Except that here we'll have to also use the inverse $x^{-1}$.

Note

Note The need for the inverse of $x$ is because we're dealing with a much harder algebraic structure, Pedersen commitments, which as pointed in this post are basically random linear combinations of what you're committing to (hidden in the exponent). This makes it hard to perform any linear combinations of its entries without producing wrong results due to the randomness. Fortunately, the use of $x^{-1}$ will nicely allow us to "cancel out" annoying terms as will become apparent very soon.

Here's how we'll fold our inputs:

  • $\vec{a'} = x^{-1} \begin{pmatrix}a_1 \\ a_2\end{pmatrix} + x \begin{pmatrix}a_3 \\ a_4\end{pmatrix}$
  • $\vec{b'} = x \begin{pmatrix}b_1 \\ b_2\end{pmatrix} + x^{-1} \begin{pmatrix}b_3 \\ b_4\end{pmatrix}$

Then you get two new vectors $\vec{a'}, \vec{b'}$ of half the size. This means nothing much so far, so let's look at what their inner product looks like:

$$ \begin{align} \langle \vec{a'}, \vec{b'} \rangle =& \langle \begin{pmatrix}x^{-1} a_1 + x a_3 \\ x^{-1} a_2 + x a_4 \end{pmatrix}, \begin{pmatrix}x b_1 + x^{-1} b_3 \\ x b_2 + x^{-1} b_4 \end{pmatrix} \rangle \\\ =& (a_1b_1 + a_2b_2 + a_3b_3 + a_4b_4) + x^{-2} (a_1b_3 + a_2b_4) + x^2 (a_3b_1 + a_4b_2) \\\ =& \langle \vec{a}, \vec{b} \rangle + x^{-2} (L) + x^2 (R) \end{align} $$

for some cross terms $L$ and $R$ that are independent of the challenge $x$ chosen (so when we will Fiat-Shamir this protocol, the prover will need to produce $L$ and $R$ before sampling $x$).

Wow, did you notice? The new inner product depends on the old one. This means that as a verifier, you can produce the reduced inner product result in a verifiable way by computing

$$\langle \vec{a'}, \vec{b'} \rangle = \langle \vec{a}, \vec{b} \rangle + \text{stuff}$$

If what you have is a commitment $C = \langle \vec{a}, \vec{b} \rangle Q$, then you can produce a reduced commitment $C' = C + \text{stuff}$ where "stuff" is essentially provided by the prover, and is not going to mess with $C$ because of the challenge that's shifting/randomizing that garbage (it'll look like $x^{-2} [L] + x^2 [R]$ where $[L]$ and $[R]$ are commitments to $L$ and $R$).

So we tackled the question of how do we reduce, in a verifiable way, the result of the inner product. But what about the inputs? Today's your lucky day because you can use the same trick for the commitment of $a$ and $b$! So essentially, you get $[\vec{a'}] = \text{reduce}([\vec{a}], x, \text{stuff})$ (and similarly for $\vec{b}$).

We can go over it in a quick example, but it'll pretty much look the same as we did above with the addition of having to reduce the generators $\vec{G}$ and $\vec{H}$ as well.

Let's start with reducing the generators, let's just show how to do it with $\vec{G}$:

$$\vec{G'} = x \begin{pmatrix}G_1 \\ G_2\end{pmatrix} + x^{-1} \begin{pmatrix}G_3 \\ G_4\end{pmatrix}$$

Now let's look at what a Pedersen commitment of our reduced first input $\vec{a'}$ looks like:

$$ \begin{align} \vec{A'} =& \langle \vec{a'}, \vec{G'} \rangle \\\ =& (x^{-1} a_1 + x a_3)(x G_1 + x^{-1} G_3) + (x^{-1} a_2 + x a_4)(x G_2 + x^{-1}G_4) \\\ =& A + x^{-2} (a_1 G_3 + a_2 G_4) + x^2 (a_3 G_1 + a_4 G_2) \\\ =& A + x^{-2} L_a + x^{2} R_a \end{align} $$

In other words, $\langle \vec{a'}, \vec{G'} \rangle = \langle \vec{a}, \vec{G} \rangle + \text{stuff}$ (and similarly for the second input).

Note that in the Bulletproofs protocol, we're dealing with a single commitment $P = A + B + C$ and so we'll reduce that statement to $P' = P + \text{stuff}$ where stuff contains the aggregated $L$ and $R$ for all of the separate commitments.

So just a recap, this is what you're essentially doing with this first round of the protocol:

  1. we start from $P = \langle \vec{a}, \vec{G} \rangle + \langle \vec{b}, \vec{H} \rangle + \langle \vec{a}, \vec{b} \rangle Q$

  2. the prover produces the points $L$ and $R$

  3. the verifier samples a challenge $x$

  4. they both produce $P' = P + x^{-2} L + x^2 R$

At this point the prover can choose to release $\vec{a'}$ and $\vec{b'}$ and the verifier can check that this is a valid opening of $P'$ by comparing it with the expected opening $\langle \vec{a'}, \vec{G'} \rangle + \langle \vec{b'}, \vec{H'} \rangle + \langle \vec{a'}, \vec{b'} \rangle Q$.

Bulletproofs doesn't stop there, instead it notices that the reduced statement looks like another statement you could give to Bulletproofs. So in the full protocol, the prover and the verifier reduce the original statement many times, until they get reduced inputs of size $1$. In practice, of course, you can stop earlier.

Warning

Warning Notice that we computed $\langle \vec{a'}, \vec{b'} \rangle Q$ which is important because you want to make sure that the inner product result is indeed $\langle \vec{a'}, \vec{b'} \rangle$ and not some arbitrary value. Checking this in the reduced form tells you with high probability that this is true as well in the original statement $P$.

One final detail, in real-world implementation, the reductions are not checked one by one by the verifier, instead all the reduction checks are aggregated into a single check. This means that the verifier knows, based on the challenges seen during the many rounds/reductions of the protocol, how to directly compute the final reduced generators, inputs, and cross-terms.

You can quickly see that by taking a simple example: two rounds of generators folding. The diagram below shows you that the eventual product of challenges that will find themselves in front of every reduced element (in this case the generators comprising the vector $\vec{G}$) can be seen as the path taken by a binary tree of challenges:

binary tree of bulletproofs challenges

In the next part, we'll see how things connect in a sagemath playground!

Keep reading
Latest

Circom-Auditor: Open-Source Skills for Finding Vulnerabilities in Circom Code

We are releasing zk-skills, a set of open-source security skills for AI coding agents, starting with circom-auditor: a first line of defense against vulnerabilities in Circom circuits, compatible with both Claude Code and Codex. On the zkbugs benchmark it detects up to 66 of 70 known bugs when pointed at the vulnerable circuits, and up to 40 of 56 when let loose on the full original codebases, far ahead of existing Circom security tools.

Stefanos Chaliasos, Hao Pham, False Witness Team · August 05, 2026

Variants of KZG: Part III, Multilinear Commitments with Zeromorph

In this blog post, we extend univariate KZG commitments to multilinear polynomials through Zeromorph. We introduce the univariatization map, encode the multilinear quotient identity as a univariate identity, and explain why the quotient encodings require degree checks. We then show how Zeromorph batches these checks into a single degree-bounded KZG opening and walk through its end-to-end opening protocol. We conclude by examining its proof size, prover cost, and verifier cost.

Varun Thakore · August 04, 2026

Variants of KZG: Part II, Multilinear Commitments with PST

In this blog post, we extend the ideas behind univariate KZG commitments to multilinear polynomials through the PST commitment scheme. We derive the multilinear quotient identity, explain how PST commits to and opens multilinear polynomials using a specialized multilinear setup and walk through its opening protocol. We conclude by examining the proof size, prover and verifier costs, and the limitations that motivate other multilinear polynomial commitment schemes.

Varun Thakore · July 28, 2026
Recommended

Unfolding the Bulletproofs Magic: A SageMath Deep Dive

In this post, we dive deep into the Inner Product Argument (IPA), the mathematical core of Bulletproofs. Starting from simple vector folding, we build up to a full zero-knowledge proof with Pedersen commitments, explore how the mysterious `L` and `R` terms appear, and finish with smart verifier optimizations. All illustrated with clear, runnable SageMath code.

teddav · October 27, 2025

Stay in Range: Deeper Into Bulletproofs

This article breaks down how Bulletproofs enable range proofs: proofs that a hidden value lies within a range without revealing it. Starting from bit decomposition, it shows how to express and combine constraints into a single inner product, then make the proof zero-knowledge with blinding polynomials and commitments. By the end, you’ll understand how systems like Monero’s confidential transactions prove valid amounts while keeping values private.

teddav · November 12, 2025

Variants of KZG: Part I, Univariate

In this blog post, we dive into the world of polynomial commitment schemes (PCS), which are crucial for constructing most practical SNARKs. We cover the basics of how PCS works, focusing on KZG10, known for its efficiency in proof size and verification time. You'll learn about the essential properties of binding and hiding and explore technical concepts like homomorphism, batching, and unconditionally hiding. We break down various methods to achieve these features, offering insight into how PCS maintains the security and privacy of polynomials in cryptographic systems. Get ready to understand these powerful concepts and their applications in modern cryptography!

Varun Thakore · April 28, 2025
More to explore

Exploring Leo: A Primer on Aleo Program Security

In this blog post, we dive into Aleo, a blockchain platform that leverages zero-knowledge cryptography for creating private and scalable decentralized applications. You'll discover how Leo, its Rust-like programming language, simplifies app development by allowing developers to focus on robust privacy features without delving deep into cryptographic complexities. We also explore Leo's unique design, offering practical tips on avoiding common pitfalls and potential vulnerabilities like underflows and unauthorized access. Whether you're a developer curious about building privacy-focused solutions or just intrigued by blockchain innovation, you'll find valuable insights here.

Suneal Gong · August 07, 2024

WE-KZG: Encrypt to KZG.

Ever wondered if you could create a ciphertext that's only decrypted when a polynomial inside a commitment has a particular value? We’ve explored this notion using KZG commitments in our latest Asiacrypt 2024 paper. Dive into the elegant world of Witness Encryption and see how it can be applied in cool ways like Laconic Oblivious Transfer. This approach keeps things as efficient as regular KZG operations and might just spark some creative applications of your own! Curious to learn more? Let’s explore together!

Mathias Hall-Andersen · October 08, 2024

Public report of Sui's zkLogin audit

We just finished an audit of the Sui Foundation's zkLogin application and we're sharing what we found: the code is well-documented, tested, and specified. The zkLogin is set to make user authentication on the blockchain secure but simple, replacing cryptographic keys with familiar SSO methods like Google or Facebook while preserving user privacy. We also dive into the technical details behind JWT verification, non-native arithmetic for RSA, and vector programming. Plus, learn about the trusted setup process for zkLogin, ensuring maximum security through a decentralized multi-party ceremony. If you're curious about the intricate mechanics behind zkLogin, this is a must-read.

ZK/SEC · November 07, 2023