qozymandias
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# Tutorial 4: The Numeric Theoretic Transform. # The Numeric Theoretic Transform (NTT). In the previous tutorial [tut3](https://hackmd.io/@qozymandias/HJFqNUHBa) we covered how to convert programs into polynomials and use this representation to prove correctness. Now we must consider how to efficiently perform operations on polynomials such as "polynomial multiplication", which is a common operation, and we must consider how to do this efficiently. To understand this, we must first reacquaint ourselves with the Fast Fourier transform and learn how to apply it to our Finite Fields. The Numeric Theoretic Transform (NTT) is a specialisation of the Discrete Fourier Transform (DFT) for integral numbers. The Fast Fourier Transform (FFT) is an efficient algorithm for computing the DFT, and therefore we will first learn about the FFT and then understand how to tweak it for integral numbers. In the following sections, we take some time to explain the FFT before we explain the NTT. If you already understand this, then feel free to skip ahead to the NTT section. # Understanding the FFT. This has been detailed on another page: [Tutorial 4 - Understanding the FFT](https://hackmd.io/@qozymandias/B1sv7DoFT) # Understanding the NTT. ## Introduction NTT uses the same underlying FFT algorithm, however, the key difference is the number type. FFT is designed for decimal complex numbers, whereas, NTT is specifically designed for integral numbers, which means no fractional numbers and no imaginary numbers! This seems quite impossible, but, there's always a way with mathematics! The NTT achieves by leveraging finite fields! We have previously covered finite fields, but I will cover the essentials here, for more details I would point to this summary web page, [notes on rings and fields](https://hackmd.io/@YW50b255-base64/ry4jiceN6). But first, why is it so important? Simply put, we need to perform efficient polynomial multiplication in our proving system. The NTT forward transform converts coefficient form into evaluation form, then multiplication is simply down point-wise; and finally, to get the result in coefficient form the inverse NTT is applied. This comes naturally from the fact that NTT is a variant of the FFT. The NTT uses finite field numbers, meaning there are no rounding errors because we don't allow fractional numbers (rather every nonzero element has a perfectly whole multiplicative inverse). This is essential for our polynomial proving system. Additionally, integer operations are more efficient than floating point operations and certain operations can be further optimised. The NTT is the perfect tool for the proving system, so let's get into the details that set it apart from the standard FFT. ## Understanding the maths behind NTT. The most important part to understand of the NTT is the pre-processing. However, there are two scenarios to consider: using an existing finite field such as an elliptic curve, or "constructing" a finite field from the inputs. The former, scenario is straightforward, however, the latter is more involved, and we will focus on this to build understanding. By "constructing" a finite field, I mean selecting a prime number that suites the inputs, more on this soon, but first, an aside about finite fields for the sake of clarity. ### Prime Finite Fields versus Finite Fields. NTT is FFT specialised for finite fields. A finite field is a finite ring with the additional property that every non-zero element has a multiplicative inverse, this is also known as a quotient ring. I have to make all the vocabulary clear here because it can get quite confusing; when they say quotient ring it is that same as finite field, additionally, I have to make a distinction between finite fields and prime finite fields. [Notes on rings and fields](https://hackmd.io/@YW50b255-base64/ry4jiceN6) does a better explanation, so I will quickly outline the difference, and you may refer to that webpage for more details. A finite field, also known as a Galois field, is a field that contains a finite number of elements. The number of elements in a finite field is either a prime number or a power of a prime number (i.e. $p^k$ where $p$ is a prime number and $k$ is a positive integer). For every prime number $p$ and positive integer $n$, there exists a finite field with $p^n$ elements. A prime finite field is a specific type of finite field where the number of elements (also known as the order of the field) is a prime number. The most common examples of prime finite fields are given by the integers modulo $p$ when $p$ is a prime number. These are often denoted as $Z/p$ or $F_p$. NTT can use a finite field provided it chooses a suitable modulus and primitive root of unity, which both fall out easily because of the properties of a finite field. Technically the modulus doesn't have to be prime, [see here](https://en.wikipedia.org/wiki/Discrete_Fourier_transform_over_a_ring#Number-theoretic_transform), but we will only focus on prime finite fields for simplicity. ### Intuition. Now we have some idea about prime finite fields, and we can come up with a mental model to aid our intuition. Take a set of non-negative integers, determine the largest integer, and then calculate the next integer that is greater than this integer and is a prime, let this be $p$. Now, apply $p$ as a modulo operation to every integer in the set, and we have constructed a prime finite field! Remember, applying a modulo operation is simply calculating the remain of the input when divided by the modulo -- this means every integer remain the same value, however any operations with "wrap around". The field is the set of integers from $0$ to $p-1$ (inclusive), and now we must perform addition, subtraction, and multiplication operations under modulo $p$. Division can be a bit tricky: essentially, instead of raw division, the multiplicative inverse is used. Let's unravel the operations with an example. #### Example $GF(5)$. For example, if $p = 5$, the prime finite field would consist of the integers $0, 1, 2, 3, 4$. If we were to add $3$ and $4$ in this field, we would get $2$ (because $3+4=7$, and $7$ modulo $5$ is $2$). Naturally, you can now see how we would derive the field from a set of input integers by simply choosing the next largest prime integer from the given integers -- for example, if our input was $6, 11$ then our chosen prime would be $13$ and the field would be $GF(13)$, which would be made up over the integers $0,1,2,3,4,5,6,7,8,9,10,11,12$. The notation for the finite field is $GF(5)$ which means Galois Field (aka a finite field) of order $5$. ##### $GF(5)$ Addition, subtraction, multiplication. So, addition and multiplication can be perfectly defined by applying modulo to the results, this ensures the results don't "overflow" the field. Subtraction on the other hand may lead to "overflow" in the negative direction, this is only a problem when the underlying type is unsigned, so we add the modulus onto the result of the subtraction before calculating its modulo. For example, if the underlying type is signed then simply calculating $(3 - 4) \space \text{ mod } \space 5$ is sufficient, however, if the type is unsigned, then we must calculate instead $((3 - 4) + 5) \space \text{ mod } \space 5$ which provides the same result. I'm getting ahead of myself, but this is very important to note for the implementation side of things. ##### $GF(5)$ Division with the multiplicative inverse. It is also critical to mention the multiplicative inverse, essentially given the input $a$ we must find a $b$ such that the multiplication of $a$ and $b$ equals $1$ (modulo $p$): $$ (a * b) \space\text{ mod }\space p = 1 $$ For example, say we want to divide $3$ by $2$, first, we need to find the multiplicative inverse of $2$ in $GF(5)$. So we need to find $b$ in $GF(5)$ such that the multiplicative inverse equation is satisfied: $$ (2 * b) \space\text{ mod }\space 5 = 1 $$ By brute force checking this equation with the elements of the field, we find $b=3$ to satisfy the equation, since $2*3 = 6$, and $6$ modulo $5$ is $1$. So, to perform the division of $3$ by $2$, we multiply $3$ by the multiplicative inverse of $2$, which is $3$. Therefore, $3/2 = 3*3 = 9$. But since we’re in $GF(5)$, we take this result modulo $5$, so $9$ modulo $5$ is $4$. Therefore, in the finite field $GF(5)$, $3/2 = 4$. For more complex fields, we can use [extended Euclidean algorithm](https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm "Extended Euclidean algorithm"). However, there is a trick (which we will explain later) that allows us to directly calculate the multiplicative inverse with using the power operator. The inverse of $a$ is: $$ \frac{1}{a} = a^{-1} $$ However, we cannot use division or calculate negative powers directly. So instead we must, employ [Fermat's Little Theorem](https://en.wikipedia.org/wiki/Fermat%27s_little_theorem) as a workaround (since the nonzero elements of $GF(p)$ form a [finite group](https://en.wikipedia.org/wiki/Finite_group "Finite group") with respect to multiplication): $$ \begin{align} a^{p − 1} \space\text{ mod }\space p \equiv 1 && (\text{for }\space a \ne 0) \\ \end{align} $$ Now multiply both sides by $a^{-1}$ $$ \begin{align} a^{p − 1} * a^{-1} \space\text{ mod }\space p \equiv 1 * a^{-1} \end{align} $$ Thus the inverse of $a$ is $$ \begin{align} a^{p − 2} \space\text{ mod }\space p && (\text{where } \space p = P^N) \\ \end{align} $$ $P$ is a prime number (the characteristic of the field) and $N$ is a positive integer (the dimension of the field). If we return to our original example, calculating the inverse of $2$: $$ (2 * b) \space\text{ mod }\space 5 = 1 $$ Then: $$ \begin{align} \text{inverse}(2) &= 2^{5 − 2} \space\text{ mod }\space 5 \\ &= 3 \\ \end{align} $$ Which is the same result as our brute force method! #### $n$'th primitive root of unity. ##### Recap of complex $n$'th primitive root of unity. Let's continue to build our intuition. We have just defined our prime field, $GF(p)$, where our elements are drawn from the set $[0, p - 1]$. Now, let's figure out how to derive the primitive root of unity. In the FFT section, the complex $n$'th roots of unity, are the $n$ complex solutions to the equation: $$ w^n = 1 $$ Where the primitive root of unity would be: $$ w_n = e^{2\pi{}i/n} $$ And we could calculate the $n$'th roots of unity from the primitive root: $$ 1, w^1, w^2, w^3, \ldots, w^{n-2}, w^{n-1} $$ This makes sense for the unit circle because every point is on the circumference of the circle, so its "real" part would always equal 1 (aka the modulus) where the angle changes as the points go around the curve (where the angle is evenly spaced by $\frac{2\pi}{n}$). ##### Primitive root of unity of finite field. Now let's think about it within the context of the finite field, well first off we are mimicking the unit circle used by complex numbers by constructing a finite field with a prime modulus. By this I mean we have essentially taken a set of integers and "bent" them around into a circle, thus making them a cyclic group; that is, they "wrap around" like a clock thanks to the modulo! Previously, I have shown how determining a minimum prime modulus and applying modulo arithmetic for the set of integers creates a prime finite field, and thus we are essentially "rolling around" in a circle! This means we have formed a cyclic [finite group](https://en.wikipedia.org/wiki/Finite_group "Finite group") with respect to multiplication. Because all nonzero elements can be expressed as powers of a single element, called a primitive element of the field -- this is the generator of the multiplicative group. This we have touched on before in terms of an elliptic curve field where $g$ is a generator for the group and every element is some power of $g$. For example, the prime finite field $GF(5)$ has a generator of $2$ and $3$. Because a generator needs to be able to generate all the nonzero elements when raised to different powers: $$ \begin{align} 2^1 \space\text{ mod }\space 5 &= 2 \\ 2^2 \space\text{ mod }\space 5 &= 4 \\ 2^3 \space\text{ mod }\space 5 &= 3 \\ 2^4 \space\text{ mod }\space 5 &= 1 \\ \end{align} $$ This is an important realisation because the complex $n$'th roots of unity are also a cyclic finite group! Now, let's think about what could be the generator for the complex numbers. The group in that regard would simply be the $n$ roots of unity: $$ 1, w^1, w^2, w^3, \ldots, w^{n-2}, w^{n-1} $$ These are given by the formula: $$ \begin{align} w &= e^{2\pi{}k \space i/n} \\ && \text{where } k=0, 1, \ldots, n-1 \\ \end{align} $$ Notice we take $e^{2\pi i / n}$ to powers $0,1,2, \ldots, n-1$ and we can generate all the elements of the group! Thus, the generator $g$ of a finite field is playing the role of the $e^{2\pi i / n}$ for complex numbers. Now it's important to note, the $n$'th primitive root of unity is closely tied to the structure of the finite field. Let's be clear, the complex $n$th roots of unity form this group, so the primitive root is the generator (aka primitive element). However, for a finite field we have just constructed we don't have such a nice property, rather, we have to brute force check our group against the definition of the primitive root. > **Definition Primitive root of unity of finite field.** > If $n$ is a positive integer, an $n$'th primitive root of unity is a solution to the equation: > $$ x^n = 1 $$ and where > $$ \begin{align} x^m & \ne 1 \\ && (\text{where }\space 0 < m < n) \\ \end{align} $$ If $a$ is an $n$'th primitive root of unity in a field $F$, then $F$ contains all the $n$ roots of unity, which are: > $$ 1, a^1, a^2, \ldots, a^{n-1} $$ The field $GF(p)$ contains an $n$'th primitive root of unity if and only if $n$ is a divisor of $p-1$; if $n$ is a divisor of $p-1$, then the number of primitive $n$'th roots of unity in $GF(p)$ is $\varphi(n)$ (Euler's totient function). The number of $n$'th roots of unity in $GF(p)$ is $gcd(n,\space p -1 )$ I don't know about you, but that just goes over my head, so let's try to break it down. Let's go back to the idea of the generator, $g$, for our field $GF(p)$, we can define the field elements as powers of $g$ (modulo $p$): $$ (g^0)\text{ mod }\space p \space, (g^1)\text{ mod }\space p \space, (g^2)\text{ mod }\space p \space, \ldots, (g^{p-1})\text{ mod }\space p $$ So our generator will be able to generate all the nonzero elements, that is, $p - 1$ distinct elements. But we know this just maps to all the integers in our set because we choose $g$ for this very purpose. $$ 1, 2, 3, 4, \ldots, (p-1) $$ Let's remember that this is the actual set. By the way we constructed this finite field, we know all the elements will be not divisible by $p$ because we selected $p$ to be the next largest prime integer. Therefore, we can use [Fermat's Little Theorem](https://en.wikipedia.org/wiki/Fermat%27s_little_theorem), which states that states if $p$ is a prime number and $x$ is an integer that is not divisible by $p$, then $x^{p-1}$ will leave a remainder of $1$ when divided by $p$, in other words, we have the following equivalence: $$ x^{p-1} \space\text{ mod }\space p \equiv 1 $$ This brings us close to the original root of unity equation. Recall, in the FFT section, the complex $n$th roots of unity, are the $n$ complex solutions to the equation: $$ w^n = 1 $$ Where the primitive root of unity is: $$ w_n = e^{2\pi{}i/n} $$ Let's plug this into the above equation: $$ \begin{align} (e^{2\pi{}i/n})^n &= e^{2\pi i } \\ &= 1 \\ \end{align} $$ Which makes sense when considering the polar form. However, our finite field equation is slightly different from the complex definition: $$ w^n = 1 $$ Let's start with our new equivalence from Fermat's little theorem: $$ x^{p-1} \space\text{ mod }\space p \equiv 1 $$ Let's replace $x$ with a value from our set, let's use the generator, aka the primitive element. $$ g^{p-1} \space\text{ mod }\space p \equiv 1 $$ In order to fit the form of the root of unity equation we have to remember the element is taken to the power of $n$, $w^n = 1$, therefore we have to account for this in the equation. So, in order for this to work we have to divide the exponent by $n$ (how many $n$ roots of unity we desire) $n$ is determined by the number of coefficients in the original polynomial. $$ w = g^{ ( p - 1) / n} $$ Plugging this into $w^n = 1$ $$ \begin{align} (g^{ ( p - 1) / n})^n &= 1 \\ g^{p-1} &= 1 \\ \end{align} $$ Which we know is true because of Fermat's little theorem. Therefore, the $n$'th primitive root of unity can be defined by the generator: $$ w_n = g^{ ( p - 1) / n} $$ Where $n$ will be the length of the original polynomial. Now we have "kicked the can down the road", all the difficulty lies in determining the $g$ of our field, this is tricky, in the next section I have outlined how to do it algorithmically. ### Formal procedure The following steps have been paraphrased from [nayuki's excellent implementation and explanation](https://www.nayuki.io/page/number-theoretic-transform-integer-dft). Given a polynomial with $n$ coefficients: $$ P = c_0 x^0 + c_1 x^1 + \ldots + c_{n-1} x^{n-1} $$ 1. Choose a minimum modulus $m$ that is prime, such that $1\le n \le m$ and every coefficient $\{c_0, c_1, \ldots, c_{n-1}\}$ exists in the range $[0, m)$ 2. Determine the generator $g$ within the range $[1, m)$, in $GF(m)$, an element $a \in GF(m)$ is called a primitive element if it is a primitive $(m − 1)$'th root of unity in $GF(m)$ for each $i$ in $[1, m)$ { if $i^{m-1} \space\text{ mod }\space m = 1$ and for each $j$ in $\text{unique\_prime\_factors}(m-1)$ { $i^{(m - 1)/j} \space\text{ mod }\space m \ne 1$ } then $i$ is generator $g$ } 3. Compute $n$'th primitive root with the generator, $g^{(m-1)/n} \space\text{ mod }\space m$ 4. The rest of the NTT procedure is identical to the forward and inverse transforms previous described in the FFT section. # Implementing the NTT. ## Pre-processing of NTT; creating the modulus type. Essentially, NTT consists of two parts: 1. Construct a prime finite field from inputs with an underlying type unsigned whole number type (e.g. unsigned 64 bit) and determine the modulus. This means we avoid fractions completely while maintaining correctness. 2. Apply core FFT algorithm with the primitive $n$'th root of unity of the finite field domain, which can be calculated given the modulus. So we must perform pre-processing of inputs to determine the modulus and the primitive root. The following are sample functions that show how to implement the pre-processing. These are heavily influenced by [nayuki's excellent implementation and explanation](https://www.nayuki.io/page/number-theoretic-transform-integer-dft), please check that page out if you are confused, they have also provided an interactive tool so you can play with the numbers. Computing the modulus: ``` // Returns the smallest modulus mod such that mod = i * len + 1 // for some integer i >= 1, mod > len, and mod is prime. // Although the loop might run for a long time and create arbitrarily large numbers, // Dirichlet's theorem guarantees that such a prime number must exist. pub fn find_modulus(len: usize, minimum: i128) -> i128 { let vl = len as i128; let mut start = (minimum - 1 + vl - 1) / vl; if start < 1 { start = 1; } let mut n = start * vl + 1; loop { if is_prime(n) { return n; } n += vl; } } ``` Computing the primitive root: ``` // Returns an arbitrary primitive degree-th root of unity modulo mod. // totient must be a multiple of degree. If mod is prime, an answer must exist. pub fn find_primitive_root(degree: i128, totient: i128, modulus: i128) -> i128 { let gen = find_generator(totient, modulus); pow_mod(gen, (totient / degree) as i128, modulus) } // Returns an arbitrary generator of the multiplicative group of integers modulo mod. // totient must equal the Euler phi function of mod. If mod is prime, an answer must exist. pub fn find_generator(totient: i128, modulus: i128) -> i128 { for i in 1..modulus { if is_primitive_root(i, totient, modulus) { return i; } } } pub fn is_primitive_root(val: i128, totient: i128, modulus: i128) -> bool { pow_mod(val, totient, modulus) == 1 && unique_prime_factors(totient) .into_iter() .all(|p| pow_mod(val, totient / p, modulus) != 1) } // Returns a list of unique prime factors of the given integer in // ascending order. For example, unique_prime_factors(60) = vec![2, 3, 5]. pub fn unique_prime_factors(mut n: i128) -> Vec<i128> { let mut result = Vec::new(); for i in 2.. { if i * i > n { break; } if n % i == 0 { result.push(i); while n % i == 0 { n /= i; } } } if n > 1 { result.push(n); } result } ``` ## Core operations of Modulus type Additionally, our new integer type must be a modulus integer type, that is for every operation: addition, subtraction, multiplication, and multiplicative inverse, must apply the modulus to the result using the modulus operator. For example, the core operations can be defined as follows: ``` fn add(lhs, rhs) { check_bounds(lhs + rhs) % modulo } fn mul(lhs, rhs) { check_bounds(lhs * rhs) % modulo } fn sub(lhs, rhs) { check_bounds(lhs - rhs) % modulo } ``` If any of the core operations exceed the bounds, we must either subtract or add the modulo. For example, if add or mul results exceed the modulo, then negate the modulo value; if the sub results exceed the modulo, then add the modulo. For example, the pseudocode would look like this: ``` fn check_bounds(input : F) -> F { if (input >= modulo) { return (input - modulo); } if (input < 0) { return (input + modulo); } return input; } ``` ## Core NTT implementation This is the same as the "definitive FFT pseudocode", detailed on another page: [Tutorial 4 - Understanding the FFT](https://hackmd.io/@qozymandias/B1sv7DoFT). However, we must instead use our Modulo integer type we have defined thus far, other than that the key difference is the primitive root has changed as we have now derived it from the finite field. ### Bringing it all together The following example program applies the NTT to the inputs and the INTT to the result. The program demonstrates how to glue the operations together; first step is the pre-processing in which the modulus and primitive root are found, followed by applying the core NTT transforms with the root found. ``` let inputs: Vec<i128> = vec![11, 42, 31, 43, -11, 12, 78, 37]; let mut max_val = 0; for &x in inputs.iter() { if x > max_val { max_val = x; } } let min_mod = max_val * max_val * inputs.len() as i128 + 1; let modulus = find_modulus(inputs.len(), min_mod); let root = find_primitive_root(inputs.len() as i128, modulus - 1, modulus); let temp0 = ntt_recursive(&inputs, root, modulus); let res = intt_recursive(&temp0, root, modulus); ``` # NTT implementation for a predefined Finite Field All of the above pre-processing can be rather tricky and inefficient for generic inputs, so instead it's much easier to use an existing [Finite Field library](https://docs.rs/ff/latest/ff/) and then map our inputs into this field. ## Pre-processing stage of NTT implementation Below is the "pre-processing" stage, where we figure out the primitive root of the field. This is quite obscure because it doesn't follow anything I said so far; this is because it uses existing constants of the library which have been exposed for this very purpose. The method to determine the primitive root for our inputs is rather strange, however, I will explain. The `F::root_of_unity` is given, however it's not "our" primitive root, the documentation is helpful but obscure: ``` /// Returns the `2^s` root of unity. /// /// It can be calculated by exponentiating `Self::multiplicative_generator` by `t`, /// where `t = (modulus - 1) >> Self::S`. fn root_of_unity() -> Self; ``` So it's for `2^s`! Where `s` is `F::S` and this is another constant defined in the library. ``` /// An integer `s` satisfying the equation `2^s * t = modulus - 1` with `t` odd. /// /// This is the number of leading zero bits in the little-endian bit representation of /// `modulus - 1`. const S: u32; ``` We can think of `2^(F::S)` as the "upper bound" of the finite field, and most importantly, we have its primitive root, `(2^(F::S))`'th primitive root of unity. The halo2 library contains a detailed usage of these constants to derive the primitive root for our input polynomial length -- this can be found [here](https://github.com/DelphinusLab/halo2-gpu-specific/blob/a2c3fba52ab2a46e08a3f161f352cf0ecd43c08b/halo2_proofs/src/poly/domain.rs#L44) -- my version takes cues from it but is much simpler for learning purposes. Let's take a look at the comments to better understand the derivation. ``` // Get omega, the 2^{k}'th root of unity (i.e. n'th root of unity) // The loop computes omega = extended_omega ^ {2 ^ (extended_k - k)} // = (omega^{2 ^ (S - extended_k)}) ^ {2 ^ (extended_k - k)} // = omega ^ {2 ^ (S - k)}. // Notice that omega ^ {2^k} = omega ^ {2^S} = 1. ``` The key information to gleam is that the $n$'th primitive root of unity is given by `omega ^ (2 ^ (S - k))`. This was very tricky for me to understand, so here is my interpretation of it: The primitive root is `omega`; `omega` is given by `root_of_unity`, but this is for `2^F::S`. We must move to `2^k` (where `k = log_2(d)` and `d` is the degree of the input polynomial -- or, more specifically, the required evaluation points we need in evaluation form). Because, we want to calculate the `2^{k}`'th root of unity. Intuitively, we know roots can be "moved" by squaring the current root. Think back to the FFT, each "level" of recursion the length of the polynomial halved and its root is squared to ensure it's still a root of unity of the halved polynomial! Therefore, to "move" `omega` and make it the `2^k` 'th primitive root of unity, we have to square it for some number of iterations. The number of iterations would be the difference between `2^F::S` and `2^k`. Since we already have `F::S` we need to determine `k`. This can be computed like so: `k = log_2(degree)`. And this is also the number of "levels" of recursion, i.e. the number of times the polynomial is halved, which makes sense considering our FFT intuition. Now we simply keep squaring `omega` by itself as many times as the difference between `F::S` and `k`. ``` let k = (degree as f64).log(2.0) as u32; for _ in 0..(F::S - k) { omega = omega.square(); } let primitive_root = omega; ``` And we have got the primitive root! Much easier to compute that building everything from scratch as we did before. Now, if we continue squaring by `k` iterations, we which reach `F::S` and `omega` will become `1` because it's a primitive root of unity. This also means as the "bottom" level of recursion `omega` will have to be squared `k` times and will equal `1` which is an essential property for FFT! We assert this fact in the sample code. ``` pub fn new(degree: u32) -> Self { let mut omega = F::root_of_unity(); // evaluation points we need to check. // level = log_2(degree) // g = omega = root of unity // Omega must stasify the equation: g^(2^final_level) = 1 // // w_in = g^(2^level) // w = w_in^idx let k = (degree as f64).log(2.0) as u32; for _ in 0..(F::S - k) { omega = omega.square(); } let primitive_root = omega; if cfg!(debug_assertions) { for _ in (F::S - k)..F::S { omega = omega.square(); } assert_eq!( omega, F::from(1), "Omega must stasify the equation: g^(2^(log_2(degree)) = 1" ); } NTT { primitive_root: primitive_root, } } ``` ## "Core FFT" stage of NTT implementation Below is the "core FFT" stage, where we figure out the primitive root of the field. ``` fn forward_recursive(&self, invec: &Vec<F>, root: &F, n_calls: usize) -> Vec<F> { let n = invec.len(); if n == 1 { return vec![invec[0].clone()]; } let half_n = n / 2; // Separate even and odd coefficients let (even, odd): (Vec<_>, Vec<_>) = invec.iter().enumerate().partition(|(i, _)| *i % 2 == 0); let even: Vec<_> = even.into_iter().map(|(_, v)| v.clone()).collect(); let odd: Vec<_> = odd.into_iter().map(|(_, v)| v.clone()).collect(); let root_squared = root.clone() * root.clone(); // Recursively compute NTT on even and odd parts let even_ntt = self.forward_recursive(&even, &root_squared, n_calls + 1); let odd_ntt = self.forward_recursive(&odd, &root_squared, n_calls + 1); // Combine the results let mut outvec = vec![F::from(0); n]; let mut current_root = F::from(1); for i in 0..half_n { let bfy = current_root * odd_ntt[i]; outvec[i] = even_ntt[i] + bfy; outvec[i + half_n] = even_ntt[i] - bfy; current_root *= root; } outvec } fn inverse_recursive(&self, invec: &Vec<F>, root: &F) -> Vec<F> { let mut outvec = self.forward_recursive(invec, &(root.invert().unwrap()), 0); let scaler = F::from(invec.len() as u64).invert().unwrap(); for i in 0..outvec.len() { outvec[i] *= scaler.clone(); } outvec } ``` # Epilogue: NTT code samples Learning by doing is always recommended, so here are my "toy" programs that demonstrate NTT implementation and usage. - https://github.com/qozymandias/ntt-algorithms - https://github.com/qozymandias/minty

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully