⚠️ Warning: Mathematics. This is a fairly technical post! I assume a solid understanding of high-school math and a willingness to bear with me :)
This blog reviews the prerequisite mathematics for understanding the set-noninclusion accumulator in Project Tachyon — if you’re already familiar with basic abstract algebra, feel free to skip to the next post.
Some Mathematics Prerequisites
Musical Motivation
In the 18th century, composer Johann Sebastian Bach wrote music that continues to be admired for its elegance and structure. Despite lacking formal mathematical training, Bach often composed with a precision that feels inherently mathematical. One striking example is his Crab Canon from The Musical Offering:
Bach’s Crab Canon from the Musical Offering is a fascinating example of mathematical music. When played forward and backward simultaneously, it creates a perfect palindrome — a musical Möbius strip where the end connects seamlessly to the beginning. This topological structure, where a one-sided surface is created by twisting and joining a strip, mirrors how the canon’s melody can be read in both directions while maintaining musical coherence.
The connection between musical expression and hidden structure has fascinated people for a long time. One of the key ideas here is the Circle of Fifths — a diagram that lays out musical notes so that closely related keys sit next to each other in a loop. It’s a handy way to make sense of harmony, and its circular shape hints at something deeper going on beneath the sound: a kind of pattern that music follows, even when we’re not consciously aware of it.

Circle of fifths clockwise within one octave. Source: Wikipedia.
Building on this, modern visualizations let us see musical motion as something geometric. In the animation below, a major seventh progression moves across the surface of an umbilic torus — a looping, twisted shape that shows how harmony can circle around while still shifting forward. Like Bach’s Canon, it turns music into more than just a line of notes — it becomes a kind of movement through space, shaped by patterns and rules we can start to recognize, even if we can’t name them yet.

This animation shows a major seventh progression traced on an umbilic torus surface — a higher-dimensional visualization of the circle of fifths. The smooth rotation through harmonic space represents tonal motion as continuous geometry, revealing deep symmetries between pitch, interval, and curvature.
These patterns aren’t just beautiful — there’s clearly something deeper going on under the surface. To really make sense of it, we’ll need a new kind of language — one that helps us talk about how music moves, transforms, and loops back on itself. That’s where we’re headed next: group theory.
From Loops to Logic
A group is a set equipped with a rule for combining elements — an operation — that behaves predictably: there’s a way to combine any two elements (closure), the way elements are grouped doesn’t matter (associativity), there’s an identity element that does nothing (identity), and every element has an inverse that undoes it (inverses).
A simple example of a group is a finite cyclic group , the integers modulo which consists of the set
with the action being addition modulo . For example, in , so . Check out Wikipedia for the mathematical definition of a group.
It turns out that cyclic groups are also very useful for cryptography. Suppose that you start with in and keep adding 5. Then you get the sequence:
Eventually, you reach every number in the set. Now, if I asked you to tell how many times you have to add 5 to get the number 8, would you be able to tell me?
This is the Discrete Logarithm Problem. Given a generator (like 5) and a result (like 8), the challenge is to figure out how many times the generator was applied. In this example, the answer is 4, since .
In cryptography, we often write cyclic groups using multiplicative notation, where repeated application of a generator is written as:
This gives all elements of the group if is a generator.
The discrete logarithm problem asks: Given and , find .
For small numbers, this is easy to solve by trial. But in large cyclic groups (especially those built from primes with hundreds of digits) the problem becomes extremely hard. For small numbers, this is easy to solve by trial. But in large cyclic groups (like those used in Ethereum’s BLS signatures, where the modulus is a 381-bit prime) the problem becomes practically impossible to reverse, and that’s exactly what makes it secure against classical computers.
Pedersen Vector Commitment Scheme
Finally, we have the language to talk about tools that rely on group structure to ensure both hiding and binding: the two essential properties of a cryptographic commitment. One of the simplest and most elegant examples is the Pedersen commitment.
Hiding and Binding
Before going further, it’s worth pausing to explain what we mean by hiding and binding.
-
Hiding means the commitment doesn’t reveal any information about the underlying message. Even if someone sees the commitment, they can’t figure out what value was committed — because it’s masked using randomness.
-
Binding means that once you’ve committed to a value, you can’t later change your mind. That is, you can’t open the same commitment to a different value. This ensures the commitment is fixed and can’t be altered after the fact.
In short: hiding protects privacy; binding ensures integrity.
The Pedersen Commitment
Let be a cyclic group of prime order , with generators and such that no one knows the discrete logarithm between them. To commit to a value , choose a random blinding factor and compute:
This is a commitment to that is:
- Perfectly hiding: because is chosen at random, the output reveals nothing about
- Computationally binding: under the discrete log assumption, it’s infeasible to find two different pairs and that yield the same commitment
Additionally, Pedersen commitments are homomorphic:
This means commitments can be added without opening them, a property useful in many protocols.
Now here’s how we implement this in Rust:
pub fn commit(m: Scalar, r: Scalar, g: GroupAffine, h: GroupAffine) -> GroupAffine {
(g * m + h * r).into_affine()
}
From Commitments to Vector Commitments
To commit to a whole vector , we extend the idea by using independent generators , and a single blinding base . The commitment is:
The vector commitment version in Rust takes an array of generators:
pub fn open(v: &[Scalar], r: Scalar, j: usize) -> Result<(Scalar, Scalar, GroupAffine)> {
if j >= v.len() {
return Err(anyhow!("Index out of bounds"));
}
let blind = POINTS[0] * r;
let witness = POINTS[1..].iter().enumerate()
.filter(|(i, _)| *i != j)
.map(|(i, p)| *p * v[i])
.sum::<GroupProjective>() + blind;
Ok((v[j], r, witness.into_affine()))
}
This compactly binds the entire vector into a single group element. It maintains the same properties:
- Hiding, because the random masks the entire vector
- Binding, assuming the generators are independent and the discrete log relationships between them are unknown
And here’s how to verify the commitment:
pub fn check(c: GroupAffine, v_j: Scalar, witness: GroupAffine, h_j: GroupAffine) -> bool {
c == witness + h_j * v_j
}
Algebraic Properties
What makes Pedersen (vector) commitments especially powerful is their algebraic structure:
-
Linearity: Commitments respect linear combinations:
where vector addition is component-wise.
-
Scalability: You can aggregate commitments across multiple vectors:
-
Inner-product compatibility: Because exponentiation distributes over sums, Pedersen commitments can be used inside inner-product arguments (like in Bulletproofs), where both prover and verifier can manipulate commitments algebraically without knowing the underlying messages.
-
Non-interactive opening proofs: Given and , it’s trivial to open the commitment and prove correctness. Zero-knowledge variants can be layered on top if needed. We will see this in the next blog.
These algebraic properties make Pedersen commitments a favorite building block in privacy-preserving protocols, SNARK-friendly constructions, and succinct proofs of integrity over large datasets. We will see how we can build Sean Bowe’s set-noninclusion accumulator from this in the next blog post.