Signed Bucket Index
AKA "NAF (non-adjacent form) method"
Note: this method is best used when negation for a group is cheap such as for an elliptic curve.
Motivation
When following a bucket-based method like Pippenger's algorithm, the amount of memory required for the buckets is demanding. How can we reduce the number of buckets per window from to , where is the number of bits per window, and reduce overhead?
Explanation
Naive Pippenger's
Let's take a look at an example of creating buckets with Pippenger's algorithm. Let's say the number of windows and the bit size per window . We'll look at the following example where denotes scalars and refers to points on an elliptic curve for the MSM problem.
With buckets , we create our windows :
Introducing NAF for buckets
Note that any computation can be reduced to where is the bit width of .
This means we can define the following NAF bucket method for window as such:
If :
Accumulate into the -th bucket
If :
, then
Accumulate into the -th bucket
Add into the next window
Step 2c computes the part of the reduction and is also known as the "carry."
Let's change our naive Pippenger's to follow this method and generate our buckets and windows:


Notice that this method requires the presence of an extra carry bucket . This extra carry bucket should not exist. We'll take a look later at how to prevent this after this Naive Pippenger's to NAF method is fully shown.
Ashley Jeong: I'm unsure why they don't just add into the bucket. Perhaps this is a source of optimization when implementing MSM.
Bucket Accumulation
Bucket Reduction
Window Reduction
Preventing the formation of an extra bucket
1. Ensuring last window bit size is within limit
The formation of an extra carry bucket depends on the true bit size of the final window in relation to , the total bits of , and , the bits per windows.
Safe Case:
Example: , ⇒
Then , and
Since , carry cannot occur ⇒ It’s safe to assume the final carry is 0.
Carry Case:
Example: , ⇒
Here , and
Since it is possible that , carry can occur
Thus, if the restriction is held, we can ensure there will be no new carry bucket created.
2. Naive Fix: Use Larger Bucket Only for Final Window
Use a larger bit-width bucket for the final window to absorb potential carry.
Cons: This is more complex and can be inefficient due to extra bucket space.
3. Use a Larger Than the Actual Modulus Bit Size
Let’s say we are working with BN254, where and . The actual scalar field is 254 bits.
We express as:
Let be the modulus of BN254. Since the maximum value of is , we have:
This ensures that no overflow occurs in the final window.
4. Use Yrrid’s Trick
Yrrid, the Zprize winner in 2022 for the category "Prize 1a: Accelerating MSM Operations on GPU" explains one more optimization they used for their solution to guarantee that no carry occurs in the final window. When the MSB of is set to 1, they apply the following transformation to reset the MSB to 0, thereby preventing any carry:
Here, is the modulus of the scalar field (i.e., curve order), and .
Let denote the -bit chunk in the final window. Yrrid's trick is said to ensure that we don't overflow to a chunk.
For example, in with , the values where the MSB of is set to 1 are 8, 9, and 10. This means that -8, -9, and -10 will be transformed into 3, 2, and 1, respectively. With this transformation, the new maximum value of becomes . If , then:
In this case with the maximum value of as 7, will be at most 1.
Code Example
Here's a code snippet from Tachyon implementing NAF for bucket MSM:
References
Written by Ryan Kim & Ashley Jeong of Fractalyze
Last updated