From 04eeca174606a1989920604de214c201429a5975 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 28 Jun 2023 14:38:42 +0200 Subject: [PATCH 001/173] include 64 bit check for excess term --- book/src/specs/kimchi.md | 38 +++++++-------- kimchi/src/circuits/polynomials/rot.rs | 64 ++++++++++++++++---------- kimchi/src/tests/rot.rs | 28 +++++++++++ 3 files changed, 87 insertions(+), 43 deletions(-) diff --git a/book/src/specs/kimchi.md b/book/src/specs/kimchi.md index 1852146616..f1ebc94dfe 100644 --- a/book/src/specs/kimchi.md +++ b/book/src/specs/kimchi.md @@ -1346,25 +1346,25 @@ which is doable with the constraints in a `RangeCheck0` gate. Since our current is almost empty, we can use it to perform the range check within the same gate. Then, using the following layout and assuming that the gate has a coefficient storing the value $2^{rot}$, which is publicly known -| Gate | `Rot64` | `RangeCheck0` | -| ------ | ------------------- | ---------------- | -| Column | `Curr` | `Next` | -| ------ | ------------------- | ---------------- | -| 0 | copy `word` |`shifted` | -| 1 | copy `rotated` | 0 | -| 2 | `excess` | 0 | -| 3 | `bound_limb0` | `shifted_limb0` | -| 4 | `bound_limb1` | `shifted_limb1` | -| 5 | `bound_limb2` | `shifted_limb2` | -| 6 | `bound_limb3` | `shifted_limb3` | -| 7 | `bound_crumb0` | `shifted_crumb0` | -| 8 | `bound_crumb1` | `shifted_crumb1` | -| 9 | `bound_crumb2` | `shifted_crumb2` | -| 10 | `bound_crumb3` | `shifted_crumb3` | -| 11 | `bound_crumb4` | `shifted_crumb4` | -| 12 | `bound_crumb5` | `shifted_crumb5` | -| 13 | `bound_crumb6` | `shifted_crumb6` | -| 14 | `bound_crumb7` | `shifted_crumb7` | +| Gate | `Rot64` | `RangeCheck0` gadgets (designer's duty) | +| ------ | ------------------- | --------------------------------------------------------- | +| Column | `Curr` | `Next` | `Next` + 1 | `Next`+ 2, if needed | +| ------ | ------------------- | ---------------- | --------------- | -------------------- | +| 0 | copy `word` |`shifted` | copy `excess` | copy `word` | +| 1 | copy `rotated` | 0 | 0 | 0 | +| 2 | `excess` | 0 | 0 | 0 | +| 3 | `bound_limb0` | `shifted_limb0` | `excess_limb0` | `word_limb0` | +| 4 | `bound_limb1` | `shifted_limb1` | `excess_limb1` | `word_limb1` | +| 5 | `bound_limb2` | `shifted_limb2` | `excess_limb2` | `word_limb2` | +| 6 | `bound_limb3` | `shifted_limb3` | `excess_limb3` | `word_limb3` | +| 7 | `bound_crumb0` | `shifted_crumb0` | `excess_crumb0` | `word_crumb0` | +| 8 | `bound_crumb1` | `shifted_crumb1` | `excess_crumb1` | `word_crumb1` | +| 9 | `bound_crumb2` | `shifted_crumb2` | `excess_crumb2` | `word_crumb2` | +| 10 | `bound_crumb3` | `shifted_crumb3` | `excess_crumb3` | `word_crumb3` | +| 11 | `bound_crumb4` | `shifted_crumb4` | `excess_crumb4` | `word_crumb4` | +| 12 | `bound_crumb5` | `shifted_crumb5` | `excess_crumb5` | `word_crumb5` | +| 13 | `bound_crumb6` | `shifted_crumb6` | `excess_crumb6` | `word_crumb6` | +| 14 | `bound_crumb7` | `shifted_crumb7` | `excess_crumb7` | `word_crumb7` | In Keccak, rotations are performed over a 5x5 matrix state of w-bit words each cell. The values used to perform the rotation are fixed, public, and known in advance, according to the following table, diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index 80c4022d41..d529670d13 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -31,11 +31,14 @@ pub enum RotMode { impl CircuitGate { /// Creates a Rot64 gadget to rotate a word /// It will need: - /// - 1 Generic gate to constrain to zero the top 2 limbs of the shifted witness of the rotation + /// - 1 Generic gate to constrain to zero the top 2 limbs of the shifted and excess witness of the rotation /// /// It has: /// - 1 Rot64 gate to rotate the word /// - 1 RangeCheck0 to constrain the size of the shifted witness of the rotation + /// - 1 RangeCheck0 to constrain the size of the excess witness of the rotation + /// Assumes: + /// - the witness word is 64-bits, otherwise, will need to append a new RangeCheck0 for the word pub fn create_rot64(new_row: usize, rot: u32) -> Vec { vec![ CircuitGate { @@ -48,6 +51,11 @@ impl CircuitGate { wires: Wire::for_row(new_row + 1), coeffs: vec![F::zero()], }, + CircuitGate { + typ: GateType::RangeCheck0, + wires: Wire::for_row(new_row + 2), + coeffs: vec![F::zero()], + }, ] } @@ -65,8 +73,12 @@ impl CircuitGate { pub fn extend_rot(gates: &mut Vec, rot: u32, side: RotMode, zero_row: usize) -> usize { let (new_row, mut rot_gates) = Self::create_rot(gates.len(), rot, side); gates.append(&mut rot_gates); - // Check that 2 most significant limbs of shifted are zero + // Check that 2 most significant limbs of shifted and excess are zero + gates.connect_64bit(zero_row, new_row - 2); gates.connect_64bit(zero_row, new_row - 1); + // Connect excess with the Rot64 gate + gates.connect_cell_pair((new_row - 3, 2), (new_row - 1, 0)); + gates.len() } @@ -140,25 +152,25 @@ pub fn lookup_table() -> LookupTable { //~ is almost empty, we can use it to perform the range check within the same gate. Then, using the following layout //~ and assuming that the gate has a coefficient storing the value $2^{rot}$, which is publicly known //~ -//~ | Gate | `Rot64` | `RangeCheck0` | -//~ | ------ | ------------------- | ---------------- | -//~ | Column | `Curr` | `Next` | -//~ | ------ | ------------------- | ---------------- | -//~ | 0 | copy `word` |`shifted` | -//~ | 1 | copy `rotated` | 0 | -//~ | 2 | `excess` | 0 | -//~ | 3 | `bound_limb0` | `shifted_limb0` | -//~ | 4 | `bound_limb1` | `shifted_limb1` | -//~ | 5 | `bound_limb2` | `shifted_limb2` | -//~ | 6 | `bound_limb3` | `shifted_limb3` | -//~ | 7 | `bound_crumb0` | `shifted_crumb0` | -//~ | 8 | `bound_crumb1` | `shifted_crumb1` | -//~ | 9 | `bound_crumb2` | `shifted_crumb2` | -//~ | 10 | `bound_crumb3` | `shifted_crumb3` | -//~ | 11 | `bound_crumb4` | `shifted_crumb4` | -//~ | 12 | `bound_crumb5` | `shifted_crumb5` | -//~ | 13 | `bound_crumb6` | `shifted_crumb6` | -//~ | 14 | `bound_crumb7` | `shifted_crumb7` | +//~ | Gate | `Rot64` | `RangeCheck0` gadgets (designer's duty) | +//~ | ------ | ------------------- | --------------------------------------------------------- | +//~ | Column | `Curr` | `Next` | `Next` + 1 | `Next`+ 2, if needed | +//~ | ------ | ------------------- | ---------------- | --------------- | -------------------- | +//~ | 0 | copy `word` |`shifted` | copy `excess` | copy `word` | +//~ | 1 | copy `rotated` | 0 | 0 | 0 | +//~ | 2 | `excess` | 0 | 0 | 0 | +//~ | 3 | `bound_limb0` | `shifted_limb0` | `excess_limb0` | `word_limb0` | +//~ | 4 | `bound_limb1` | `shifted_limb1` | `excess_limb1` | `word_limb1` | +//~ | 5 | `bound_limb2` | `shifted_limb2` | `excess_limb2` | `word_limb2` | +//~ | 6 | `bound_limb3` | `shifted_limb3` | `excess_limb3` | `word_limb3` | +//~ | 7 | `bound_crumb0` | `shifted_crumb0` | `excess_crumb0` | `word_crumb0` | +//~ | 8 | `bound_crumb1` | `shifted_crumb1` | `excess_crumb1` | `word_crumb1` | +//~ | 9 | `bound_crumb2` | `shifted_crumb2` | `excess_crumb2` | `word_crumb2` | +//~ | 10 | `bound_crumb3` | `shifted_crumb3` | `excess_crumb3` | `word_crumb3` | +//~ | 11 | `bound_crumb4` | `shifted_crumb4` | `excess_crumb4` | `word_crumb4` | +//~ | 12 | `bound_crumb5` | `shifted_crumb5` | `excess_crumb5` | `word_crumb5` | +//~ | 13 | `bound_crumb6` | `shifted_crumb6` | `excess_crumb6` | `word_crumb6` | +//~ | 14 | `bound_crumb7` | `shifted_crumb7` | `excess_crumb7` | `word_crumb7` | //~ //~ In Keccak, rotations are performed over a 5x5 matrix state of w-bit words each cell. The values used //~ to perform the rotation are fixed, public, and known in advance, according to the following table, @@ -254,8 +266,12 @@ where // ROTATION WITNESS COMPUTATION -fn layout_rot64(curr_row: usize) -> [[Box>; COLUMNS]; 2] { - [rot_row(), range_check_0_row("shifted", curr_row + 1)] +fn layout_rot64(curr_row: usize) -> [[Box>; COLUMNS]; 3] { + [ + rot_row(), + range_check_0_row("shifted", curr_row + 1), + range_check_0_row("excess", curr_row + 2), + ] } fn rot_row() -> [Box>; COLUMNS] { @@ -335,7 +351,7 @@ pub fn extend_rot( let bound = 2u128.pow(64) - 2u128.pow(rot); let rot_row = witness[0].len(); - let rot_witness: [Vec; COLUMNS] = array::from_fn(|_| vec![F::zero(); 2]); + let rot_witness: [Vec; COLUMNS] = array::from_fn(|_| vec![F::zero(); 3]); for col in 0..COLUMNS { witness[col].extend(rot_witness[col].iter()); } diff --git a/kimchi/src/tests/rot.rs b/kimchi/src/tests/rot.rs index 741251bfa0..8d8ee17f97 100644 --- a/kimchi/src/tests/rot.rs +++ b/kimchi/src/tests/rot.rs @@ -246,11 +246,17 @@ fn test_bad_constraints() { // modify excess witness[2][1] += PallasField::one(); + witness[0][3] += PallasField::one(); assert_eq!( cs.gates[1].verify_witness::(1, &witness, &cs, &witness[0][0..cs.public]), Err(CircuitGateError::Constraint(GateType::Rot64, 9)) ); + assert_eq!( + cs.gates[3].verify_witness::(3, &witness, &cs, &witness[0][0..cs.public]), + Err(CircuitGateError::Constraint(GateType::RangeCheck0, 9)) + ); witness[2][1] -= PallasField::one(); + witness[0][3] -= PallasField::one(); // modify shifted witness[0][2] += PallasField::one(); @@ -262,6 +268,7 @@ fn test_bad_constraints() { cs.gates[2].verify_witness::(2, &witness, &cs, &witness[0][0..cs.public]), Err(CircuitGateError::Constraint(GateType::RangeCheck0, 9)) ); + witness[0][2] -= PallasField::one(); // modify value of shifted to be more than 64 bits witness[0][2] += PallasField::two_pow(64); @@ -280,6 +287,27 @@ fn test_bad_constraints() { dst: Wire { row: 0, col: 0 } }) ); + witness[2][2] -= PallasField::one(); + witness[0][2] -= PallasField::two_pow(64); + + // modify value of excess to be more than 64 bits + witness[0][3] += PallasField::two_pow(64); + witness[2][1] += PallasField::two_pow(64); + assert_eq!( + cs.gates[3].verify_witness::(3, &witness, &cs, &witness[0][0..cs.public]), + Err(CircuitGateError::Constraint(GateType::RangeCheck0, 9)) + ); + // Update decomposition + witness[2][3] += PallasField::one(); + // Make sure the 64-bit check fails + assert_eq!( + cs.gates[3].verify_witness::(3, &witness, &cs, &witness[0][0..cs.public]), + Err(CircuitGateError::CopyConstraint { + typ: GateType::RangeCheck0, + src: Wire { row: 3, col: 2 }, + dst: Wire { row: 2, col: 2 } + }) + ); } #[test] From 8e4f6bdf0d053ee35a6cc38b2c95afbe58df5a6d Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Thu, 20 Jul 2023 21:37:30 +0200 Subject: [PATCH 002/173] Bump up actions/checkout to v3 Addressing https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/ --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9d130c8810..d99986aa59 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,7 +28,7 @@ jobs: name: Run some basic checks and tests steps: - name: Checkout PR - uses: actions/checkout@v2 + uses: actions/checkout@v3 # as action-rs does not seem to be maintained anymore, building from # scratch the environment using rustup From 1a10f94c185cadad482512cebbb16eb1436db2cf Mon Sep 17 00:00:00 2001 From: Joseandro Luiz Date: Thu, 3 Aug 2023 15:22:16 -0300 Subject: [PATCH 003/173] Removed the old FFMul RFC in favor of the new one in one available in o1-labs/rfcs --- book/src/rfcs/foreign_field_mul.md | 1315 ---------------------------- 1 file changed, 1315 deletions(-) delete mode 100644 book/src/rfcs/foreign_field_mul.md diff --git a/book/src/rfcs/foreign_field_mul.md b/book/src/rfcs/foreign_field_mul.md deleted file mode 100644 index 843a0a7ee8..0000000000 --- a/book/src/rfcs/foreign_field_mul.md +++ /dev/null @@ -1,1315 +0,0 @@ -# Foreign Field Multiplication RFC - -This document explains how we constrain foreign field multiplication (i.e. non-native field multiplication) in the Kimchi proof system. - -**Changelog** - -| Author(s) | Date | Details | -|-|-|-| -| Joseph Spadavecchia and Anais Querol | October 2022 | Design of foreign field multiplication in Kimchi | - -## Overview - -This gate constrains - -$$ -a \cdot b = c \mod f -$$ - -where $a, b, c \in \mathbb{F_f}$, a foreign field with modulus $f$, using the native field $\mathbb{F_n}$ with prime modulus $n$. - -## Approach - -In foreign field multiplication the foreign field modulus $f$ could be bigger or smaller than the native field modulus $n$. When the foreign field modulus is bigger, then we need to emulate foreign field multiplication by splitting the foreign field elements up into limbs that fit into the native field element size. When the foreign modulus is smaller everything can be constrained either in the native field or split up into limbs. - -Since our projected use cases are when the foreign field modulus is bigger (more on this below) we optimize our design around this scenario. For this case, not only must we split foreign field elements up into limbs that fit within the native field, but we must also operate in a space bigger than the foreign field. This is because we check the multiplication of two foreign field elements by constraining its quotient and remainder. That is, renaming $c$ to $r$, we must constrain - -$$ -a \cdot b = q \cdot f + r, -$$ - -where the maximum size of $q$ and $r$ is $f - 1$ and so we have - -$$ -\begin{aligned} -a \cdot b &\le \underbrace{(f - 1)}_q \cdot f + \underbrace{(f - 1)}_r \\ -&\le f^2 - 1. -\end{aligned} -$$ - -Thus, the maximum size of the multiplication is quadratic in the size of foreign field. - -**Naïve approach** - -Naïvely, this implies that we have elements of size $f^2 - 1$ that must split them up into limbs of size at most $n - 1$. For example, if the foreign field modulus is $256$ and the native field modulus is $255$ bits, then we'd need $\log_2((2^{256})^2 - 1) \approx 512$ bits and, thus, require $512/255 \approx 3$ native limbs. However, each limb cannot consume all $255$ bits of the native field element because we need space to perform arithmetic on the limbs themselves while constraining the foreign field multiplication. Therefore, we need to choose a limb size that leaves space for performing these computations. - -Later in this document (see the section entitled "Choosing the limb configuration") we determine the optimal number of limbs that reduces the number of rows and gates required to constrain foreign field multiplication. This results in $\ell = 88$ bits as our optimal limb size. In the section about intermediate products we place some upperbounds on the number of bits required when constraining foreign field multiplication with limbs of size $\ell$ thereby proving that the computations can fit within the native field size. - -Observe that by combining the naïve approach above with a limb size of $88$ bits, we would require $512/88 \approx 6$ limbs for representing foreign field elements. Each limb is stored in a witness cell (a native field element). However, since each limb is necessarily smaller than the native field element size, it must be copied to the range-check gate to constrain its value. Since Kimchi only supports 7 copyable witness cells per row, this means that only one foreign field element can be stored per row. This means a single foreign field multiplication would consume at least 4 rows (just for the operands, quotient and remainder). This is not ideal because we want to limit the number of rows for improved performance. - -**Chinese remainder theorem** - -Fortunately, we can do much better than this, however, by leveraging the chinese remainder theorem (CRT for short) as we will now show. The idea is to reduce the number of bits required by constraining our multiplication modulo two coprime moduli: $2^t$ and $n$. By constraining the multiplication with both moduli the CRT guarantees that the constraints hold with the bigger modulus $2^t \cdot n$. - -The advantage of this approach is that constraining with the native modulus $n$ is very fast, allowing us to speed up our non-native computations. This practically reduces the costs to constraining with limbs over $2^t$, where $t$ is something much smaller than $512$. - -For this to work, we must select a value for $t$ that is big enough. That is, we select $t$ such that $2^t \cdot n > f^2 - 1$. As described later on in this document, by doing so we reduce the number of bits required for our use cases to $264$. With $88$ bit limbs this means that each foreign field element only consumes $3$ witness elements, and that means the foreign field multiplication gate now only consumes $2$ rows. The section entitled "Choosing $t$" describes this in more detail. - -**Overall approach** - -Bringing it all together, our approach is to verify that - -$$ -\begin{align} -a \cdot b = q \cdot f + r -\end{align} -$$ - -over $\mathbb{Z^+}$. In order to do this efficiently we use the CRT, which means that the equation holds mod $M = 2^t \cdot n$. For the equation to hold over the integers we must also check that each side of the equation is less than $2^t \cdot n$. - -The overall steps are therefore - -1. Apply CRT to equation (1) - * Check validity with binary modulus $\mod 2^t$ - * Check validity with native modulus $\mod n$ -2. Check each side of equation (1) is less than $M$ - * $a \cdot b < 2^t \cdot n$ - * $q \cdot f + r < 2^t \cdot n$ - -This then implies that - -$$ -a \cdot b = c \mod f. -$$ - -where $c = r$. That is, $a$ multiplied with $b$ is equal to $c$ where $a,b,c \in \mathbb{F_f}$. There is a lot more to each of these steps. That is the subject of the rest of this document. - -**Other strategies** - -Within our overall approach, aside from the CRT, we also use a number of other strategies to reduce the number and degree of constraints. - -* Avoiding borrows and carries -* Intermediate product elimination -* Combining multiplications - -The rest of this document describes each part in detail. - -## Parameter selection - -This section describes important parameters that we require and how they are computed. - -* *Native field modulus* $n$ -* *Foreign field modulus* $f$ -* *Binary modulus* $2^t$ -* *CRT modulus* $2^t \cdot n$ -* *Limb size* in bits $\ell$ - -#### Choosing $t$ - -Under the hood, we constrain $a \cdot b = q \cdot f + r \mod 2^t \cdot n$. Since this is a multiplication in the foreign field $f$, the maximum size of $q$ and $r$ are $f - 1$, so this means - -$$ -\begin{aligned} -a \cdot b &\le (f - 1) \cdot f + (f - 1) \\ -&\le f^2 - 1. -\end{aligned} -$$ - -Therefore, we need the modulus $2^t \cdot n$ such that - -$$ -2^t \cdot n > f^2 - 1, -$$ - -which is the same as saying, given $f$ and $n$, we must select $t$ such that - -$$ -\begin{aligned} -2^t \cdot n &\ge f^2 \\ -t &\ge 2\log_2(f) - \log_2(n). -\end{aligned} -$$ - -Thus, we have an lower bound on $t$. - -Instead of dynamically selecting $t$ for every $n$ and $f$ combination, we fix a $t$ that will work for the different selections of $n$ and $f$ relevant to our use cases. - -To guide us, from above we know that - -$$ -t_{min} = 2\log_2(f) - \log_2(n) -$$ - -and we know the field moduli for our immediate use cases. - -``` -vesta = 2^254 + 45560315531506369815346746415080538113 (255 bits) -pallas = 2^254 + 45560315531419706090280762371685220353 (255 bits) -secp256k1 = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 (256 bits) -``` - -So we can create a table - -| $n$ | $f$ | $t_{min}$ | -| ----------- | --------------- | --- | -| `vesta` | `secp256k1` | 258 | -| `pallas` | `secp256k1` | 258 | -| `vesta` | `pallas` | 255 | -| `pallas` | `vesta` | 255 | - -and know that to cover our use cases we need $t \ge 258$. - -Next, given our native modulus $n$ and $t$, we can compute the *maximum foreign field modulus supported*. Actually, we compute the maximum supported bit length of the foreign field modulus $f=2^m$. - -$$ -\begin{aligned} -2^t \cdot n &\ge f^2 \\ -&\ge (2^m)^2 = 2^{2m} \\ -t + \log_2(n) &> \log_2(2^{2m}) = 2m -\end{aligned} -$$ - -So we get - -$$ -m_{max} = \frac{t + \log_2(n)}{2}. -$$ - -With $t=258, n=255$ we have  - -$$ -\begin{aligned} -m_{max} &= \frac{258 + 255}{2} = 256.5, -\end{aligned} -$$ - -which is not enough space to handle anything larger than 256 bit moduli. Instead, we will use $t=264$, giving $m_{max} = 259$ bits. - -The above formula is useful for checking the maximum number of bits supported of the foreign field modulus, but it is not useful for computing the maximum foreign field modulus itself (because $2^{m_{max}}$ is too coarse). For these checks, we can compute our maximum foreign field modulus more precisely with - -$$ -max_{mod} = \lfloor \sqrt{2^t \cdot n} \rfloor. -$$ - -The max prime foreign field modulus satisfying the above inequality for both Pallas and Vesta is `926336713898529563388567880069503262826888842373627227613104999999999999999607`. - -#### Choosing the limb configuration - -Choosing the right limb size and the right number of limbs is a careful balance between the number of constraints (i.e. the polynomial degree) and the witness length (i.e. the number of rows). Because one limiting factor that we have in Kimchi is the 12-bit maximum for range check lookups, we could be tempted to introduce 12-bit limbs. However, this would mean having many more limbs, which would consume more witness elements and require significantly more rows. It would also increase the polynomial degree by increasing the number of constraints required for the *intermediate products* (more on this later). - -We need to find a balance between the number of limbs and the size of the limbs. The limb configuration is dependent on the value of $t$ and our maximum foreign modulus (as described in the previous section). The larger the maximum foreign modulus, the more witness rows we will require to constrain the computation. In particular, each limb needs to be constrained by the range check gate and, thus, must be in a copyable (i.e. permuteable) witness cell. We have at most 7 copyable cells per row and gates can operate on at most 2 rows, meaning that we have an upperbound of at most 14 limbs per gate (or 7 limbs per row). - -As stated above, we want the foreign field modulus to fit in as few rows as possible and we need to constrain operands $a, b$, the quotient $q$ and remainder $r$. Each of these will require cells for each limb. Thus, the number of cells required for these is - -$$ -cells = 4 \cdot limbs -$$ - -It is highly advantageous for performance to constrain foreign field multiplication with the minimal number of gates. This not only helps limit the number of rows, but also to keep the gate selector polynomial small. Because of all of this, we aim to constrain foreign field multiplication with a single gate (spanning at most $2$ rows). As mentioned above, we have a maximum of 14 permuteable cells per gate, so we can compute the maximum number of limbs that fit within a single gate like this. - -$$ -\begin{aligned} -limbs_{max} &= \lfloor cells/4 \rfloor \\ - &= \lfloor 14/4 \rfloor \\ - &= 3 \\ -\end{aligned} -$$ - -Thus, the maximum number of limbs possible in a single gate configuration is 3. - -Using $limbs_{max}=3$ and $t=264$ that covers our use cases (see the previous section), we are finally able to derive our limbs size - -$$ -\begin{aligned} -\ell &= \frac{t}{limbs_{max}} \\ -&= 264/3 \\ -&= 88 -\end{aligned} -$$ - -Therefore, our limb configuration is: - - * Limb size $\ell = 88$ bits - * Number of limbs is $3$ - -## Avoiding borrows - -When we constrain $a \cdot b - q \cdot f = r \mod 2^t$ we want to be as efficient as possible. - -Observe that the expansion of $a \cdot b - q \cdot f$ into limbs would also have subtractions between limbs, requiring our constraints to account for borrowing. Dealing with this would create undesired complexity and increase the degree of our constraints. - -In order to avoid the possibility of subtractions we instead use $a \cdot b + q \cdot f'$ where - -$$ -\begin{aligned} -f' &= -f \mod 2^t \\ - &= 2^t - f -\end{aligned} -$$ - -The negated modulus $f'$ becomes part of our gate coefficients and is not constrained because it is publicly auditable. - -Using the substitution of the negated modulus, we now must constrain $a \cdot b + q \cdot f' = r \mod 2^t$. - -> Observe that $f' < 2^t$ since $f < 2^t$ and that $f' > f$ when $f < 2^{t - 1}$. - -## Intermediate products - -This section explains how we expand our constraints into limbs and then eliminate a number of extra terms. - -We must constrain $a \cdot b + q \cdot f' = r \mod 2^t$ on the limbs, rather than as a whole. As described above, each foreign field element $x$ is split into three 88-bit limbs: $x_0, x_1, x_2$, where $x_0$ contains the least significant bits and $x_2$ contains the most significant bits and so on. - -Expanding the right-hand side into limbs we have - -$$ -\begin{aligned} -&(a_0 + a_1 \cdot 2^{\ell} + a_2 \cdot 2^{2\ell}) \cdot (b_0 + b_1 \cdot 2^{\ell} + b_2 \cdot 2^{2\ell}) + (q_0 + q_1 \cdot 2^{\ell} + q_2 \cdot 2^{2\ell}) \cdot (f'_0 + f'_1 \cdot 2^{\ell} + f'_2 \cdot 2^{2\ell}) \\ -&=\\ -&~~~~~ a_0 \cdot b_0 + a_0 \cdot b_1 \cdot 2^{\ell} + a_0 \cdot b_2 \cdot 2^{2\ell} \\ -&~~~~ + a_1 \cdot b_0 \cdot 2^{\ell} + a_1 \cdot b_1 \cdot 2^{2\ell} + a_1 \cdot b_2 \cdot 2^{3\ell} \\ -&~~~~ + a_2 \cdot b_0 \cdot 2^{2\ell} + a_2 \cdot b_1 \cdot 2^{3\ell} + a_2 \cdot b_2 \cdot 2^{4\ell} \\ -&+ \\ -&~~~~~ q_0 \cdot f'_0 + q_0 \cdot f'_1 \cdot 2^{\ell} + q_0 \cdot f'_2 \cdot 2^{2\ell} \\ -&~~~~ + q_1 \cdot f'_0 \cdot 2^{\ell} + q_1 \cdot f'_1 \cdot 2^{2\ell} + q_1 \cdot f'_2 \cdot 2^{3\ell} \\ -&~~~~ + q_2 \cdot f'_0 \cdot 2^{2\ell} + q_2 \cdot f'_1 \cdot 2^{3\ell} + q_2 \cdot f'_2 \cdot 2^{4\ell} \\ -&= \\ -&a_0 \cdot b_0 + q_0 \cdot f'_0 \\ -&+ 2^{\ell} \cdot (a_0 \cdot b_1 + a_1 \cdot b_0 + q_0 \cdot f'_1 + q_1 \cdot f'_0) \\ -&+ 2^{2\ell} \cdot (a_0 \cdot b_2 + a_2 \cdot b_0 + q_0 \cdot f'_2 + q_2 \cdot f'_0 + a_1 \cdot b_1 + q_1 \cdot f'_1) \\ -&+ 2^{3\ell} \cdot (a_1 \cdot b_2 + a_2 \cdot b_1 + q_1 \cdot f'_2 + q_2 \cdot f'_1) \\ -&+ 2^{4\ell} \cdot (a_2 \cdot b_2 + q_2 \cdot f'_2) \\ -\end{aligned} -$$ - -Since $t = 3\ell$, the terms scaled by $2^{3\ell}$ and $2^{4\ell}$ are a multiple of the binary modulus and, thus, congruent to zero $\mod 2^t$. They can be eliminated and we don't need to compute them. So we are left with 3 *intermediate products* that we call $p_0, p_1, p_2$: - -| Term | Scale | Product | -| ----- | ----------- | -------------------------------------------------------- | -| $p_0$ | $1$ | $a_0b_0 + q_0f'_0$ | -| $p_1$ | $2^{\ell}$ | $a_0b_1 + a_1b_0 + q_0f'_1 + q_1f'_0$ | -| $p_2$ | $2^{2\ell}$ | $a_0b_2 + a_2b_0 + q_0f'_2 + q_2f'_0 + a_1b_1 + q_1f'_1$ | - -So far, we have introduced these checked computations to our constraints - -> 1. Computation of $p_0, p_1, p_2$ - -## Constraining $\mod 2^t$ - -Let's call $p := ab + qf' \mod 2^t$. Remember that our goal is to constrain that $p - r = 0 \mod 2^t$ (recall that any more significant bits than the 264th are ignored in $\mod 2^t$). Decomposing that claim into limbs, that means - -$$ -\begin{align} -2^{2\ell}(p_2 - r_2) + 2^{\ell}(p_1 - r_1) + p_0 - r_0 = 0 \mod 2^t. -\end{align} -$$ - -We face two challenges - -* Since $p_0, p_1, p_2$ are at least $2^{\ell}$ bits each, the right side of the equation above does not fit in $\mathbb{F}_n$ -* The subtraction of the remainder's limbs $r_0$ and $r_1$ could require borrowing - -For the moment, let's not worry about the possibility of borrows and instead focus on the first problem. - -## Combining multiplications - -The first problem is that our native field is too small to constrain $2^{2\ell}(p_2 - r_2) + 2^{\ell}(p_1 - r_1) + p_0 - r_0 = 0 \mod 2^t$. We could split this up by multiplying $a \cdot b$ and $q \cdot f'$ separately and create constraints that carefully track borrows and carries between limbs. However, a more efficient approach is combined the whole computation together and accumulate all the carries and borrows in order to reduce their number. - -The trick is to assume a space large enough to hold the computation, view the outcome in binary and then split it up into chunks that fit in the native modulus. - -To this end, it helps to know how many bits these intermediate products require. On the left side of the equation, $p_0$ is at most $2\ell + 1$ bits. We can compute this by substituting the maximum possible binary values (all bits set to 1) into $p_0 = a_0b_0 + q_0f'_0$ like this - -$$ -\begin{aligned} -\mathsf{maxbits}(p_0) &= \log_2(\underbrace{(2^{\ell} - 1)}_{a_{0}} \underbrace{(2^{\ell} - 1)}_{b_{0}} + \underbrace{(2^{\ell} - 1)}_{q_{0}} \underbrace{(2^{\ell} - 1)}_{f'_{0}}) \\ -&= \log_2(2(2^{2\ell} - 2^{\ell + 1} + 1)) \\ -&= \log_2(2^{2\ell + 1} - 2^{\ell + 2} + 2). -\end{aligned} -$$ - -So $p_0$ fits in $2\ell + 1$ bits. Similarly, $p_1$ needs at most $2\ell + 2$ bits and $p_2$ is at most $2\ell + 3$ bits. - - -| Term | $p_0$ | $p_1$ | $p_2$ | -| -------- | ----------- | ----------- | ----------- | -| **Bits** | $2\ell + 1$ | $2\ell + 2$ | $2\ell + 3$ | - -The diagram below shows the right hand side of the zero-sum equality from equation (2). That is, the value $p - r$. Let's look at how the different bits of $p_0, p_1, p_2, r_0, r_1$ and $r_2$ impact it. - -```text -0 L 2L 3L 4L -|-------------|-------------|-------------|-------------|-------------| - : -|--------------p0-----------:-| 2L + 1 - : - |-------------:-p1------------| 2L + 2 - p10➚ : p11➚ - |----------------p2-------------| 2L + 3 - : -|-----r0------| : - : - |-----r1------| - : - |-----r2------| -\__________________________/ \______________________________/ - ≈ h0 ≈ h1 -``` - -Within our native field modulus we can fit up to $2\ell + \delta < \log_2(n)$ bits, for small values of $\delta$ (but sufficient for our case). Thus, we can only constrain approximately half of $p - r$ at a time. In the diagram above the vertical line at 2L bisects $p - r$ into two $\approx2\ell$ bit values: $h_0$ and $h_1$ (the exact definition of these values follow). Our goal is to constrain $h_0$ and $h_1$. - -## Computing the zero-sum halves: $h_0$ and $h_1$ - -Now we can derive how to compute $h_0$ and $h_1$ from $p$ and $r$. - -The direct approach would be to bisect both $p_0$ and $p_1$ and then define $h_0$ as just the sum of the $2\ell$ lower bits of $p_0$ and $p_1$ minus $r_0$ and $r_1$. Similarly $h_1$ would be just the sum of upper bits of $p_0, p_1$ and $p_2$ minus $r_2$. However, each bisection requires constraints for the decomposition and range checks for the two halves. Thus, we would like to avoid bisections as they are expensive. - -Ideally, if our $p$'s lined up on the $2\ell$ boundary, we would not need to bisect at all. However, we are unlucky and it seems like we must bisect both $p_0$ and $p_1$. Fortunately, we can at least avoid bisecting $p_0$ by allowing it to be summed into $h_0$ like this - -$$ -h_0 = p_0 + 2^{\ell}\cdot p_{10} - r_0 - 2^{\ell}\cdot r_1 -$$ - -Note that $h_0$ is actually greater than $2\ell$ bits in length. This may not only be because it contains $p_0$ whose length is $2\ell + 1$, but also because adding $p_{10}$ may cause an overflow. The maximum length of $h_0$ is computed by substituting in the maximum possible binary value of $2^{\ell} - 1$ for the added terms and $0$ for the subtracted terms of the above equation. - -$$ -\begin{aligned} -\mathsf{maxbits}(h_0) &= \log_2(\underbrace{(2^{\ell} - 1)(2^{\ell} - 1) + (2^{\ell} - 1)(2^{\ell} - 1)}_{p_0} + 2^{\ell} \cdot \underbrace{(2^{\ell} - 1)}_{p_{10}}) \\ -&= \log_2(2^{2\ell + 1} - 2^{\ell + 2} + 2 + 2^{2\ell} - 2^\ell) \\ -&= \log_2( 3\cdot 2^{2\ell} - 5 \cdot 2^\ell +2 ) \\ -\end{aligned} -$$ - -which is $2\ell + 2$ bits. - -N.b. This computation assumes correct sizes values for $r_0$ and $r_1$, which we assure by range checks on the limbs. - -Next, we compute $h_1$ as - -$$ -h_1 = p_{11} + p_2 - r_2 -$$ - -The maximum size of $h_1$ is computed as - -$$ -\begin{aligned} -\mathsf{maxbits}(h_1) &= \mathsf{maxbits}(p_{11} + p_2) -\end{aligned} -$$ - -In order to obtain the maximum value of $p_{11}$, we define $p_{11} := \frac{p_1}{2^\ell}$. Since the maximum value of $p_1$ was $2^{2\ell+2}-2^{\ell+3}+4$, then the maximum value of $p_{11}$ is $2^{\ell+2}-8$. For $p_2$, the maximum value was $6\cdot 2^{2\ell} - 12 \cdot 2^\ell + 6$, and thus: - -$$ -\begin{aligned} -\mathsf{maxbits}(h_1) &= log_2(\underbrace{2^{\ell+2}-8}_{p_{11}} + \underbrace{6\cdot 2^{2\ell} - 12 \cdot 2^\ell + 6}_{p_2}) \\ -&= \log_2(6\cdot 2^{2\ell} - 8 \cdot 2^\ell - 2) \\ -\end{aligned} -$$ - -which is $2\ell + 3$ bits. - -| Term | $h_0$ | $h_1$ | -| -------- | ----------- | ----------- | -| **Bits** | $2\ell + 2$ | $2\ell + 3$ | - -Thus far we have the following constraints -> 2. Composition of $p_{10}$ and $p_{11}$ result in $p_1$ -> 3. Range check $p_{11} \in [0, 2^{\ell + 2})$ -> 4. Range check $p_{10} \in [0, 2^{\ell})$ - -For the next step we would like to constrain $h_0$ and $h_1$ to zero. Unfortunately, we are not able to do this! - -* Firstly, as defined $h_0$ may not be zero because we have not bisected it precisely at $2\ell$ bits, but have allowed it to contain the full $2\ell + 2$ bits. Recall that these two additional bits are because $p_0$ is at most $2\ell + 1$ bits long, but also because adding $p_{10}$ increases it to $2\ell + 2$. These two additional bits are not part of the first $2\ell$ bits of $p - r$ and, thus, are not necessarily zero. That is, they are added from the second $2\ell$ bits (i.e. $h_1$). - -* Secondly, whilst the highest $\ell + 3$ bits of $p - r$ would wrap to zero $\mod 2^t$, when placed into the smaller $2\ell + 3$ bit $h_1$ in the native field, this wrapping does not happen. Thus, $h_1$'s $\ell + 3$ highest bits may be nonzero. - -We can deal with this non-zero issue by computing carry witness values. - -## Computing carry witnesses values $v_0$ and $v_1$ - -Instead of constraining $h_0$ and $h_1$ to zero, there must be satisfying witness $v_0$ and $v_1$ such that the following constraints hold. -> 5. There exists $v_0$ such that $h_0 = v_0 \cdot 2^{2\ell}$ -> 6. There exists $v_1$ such that $h_1 = v_1 \cdot 2^{\ell} - v_0$ - -Here $v_0$ is the last two bits of $h_0$'s $2\ell + 2$ bits, i.e., the result of adding the highest bit of $p_0$ and any possible carry bit from the operation of $h_0$. Similarly, $v_1$ corresponds to the highest $\ell + 3$ bits of $h_1$. It looks like this - -```text -0 L 2L 3L 4L -|-------------|-------------|-------------|-------------|-------------| - : -|--------------h0-----------:--| 2L + 2 - : ↖v0 - :-------------h1-------------| 2L + 3 - : \____________/ - : v1➚ -``` - - -Remember we only need to prove the first $3\ell$ bits of $p - r$ are zero, since everything is $\mod 2^t$ and $t = 3\ell$. It may not be clear how this approach proves the $3\ell$ bits are indeed zero because within $h_0$ and $h_1$ there are bits that are nonzero. The key observation is that these bits are too high for $\mod 2^t$. - -By making the argument with $v_0$ and $v_1$ we are proving that $h_0$ is something where the $2\ell$ least significant bits are all zeros and that $h_1 + v_0$ is something where the $\ell$ are also zeros. Any nonzero bits after $3\ell$ do not matter, since everything is $\mod 2^t$. - -All that remains is to range check $v_0$ and $v_1$ -> 7. Range check $v_0 \in [0, 2^2)$ -> 8. Range check $v_1 =\in [0, 2^{\ell + 3})$ - -## Subtractions - -When unconstrained, computing $u_0 = p_0 + 2^{\ell} \cdot p_{10} - (r_0 + 2^{\ell} \cdot r_1)$ could require borrowing. Fortunately, we have the constraint that the $2\ell$ least significant bits of $u_0$ are `0` (i.e. $u_0 = 2^{2\ell} \cdot v_0$), which means borrowing cannot happen. - -Borrowing is prevented because when the the $2\ell$ least significant bits of the result are `0` it is not possible for the minuend to be less than the subtrahend. We can prove this by contradiction. - -Let - -* $x = p_0 + 2^{\ell} \cdot p_{10}$ -* $y = r_0 + 2^{\ell} \cdot r_1$ -* the $2\ell$ least significant bits of $x - y$ be `0` - -Suppose that borrowing occurs, that is, that $x < y$. Recall that the length of $x$ is at most $2\ell + 2$ bits. Therefore, since $x < y$ the top two bits of $x$ must be zero and so we have - -$$ -x - y = x_{2\ell} - y, -$$ - -where $x_{2\ell}$ denotes the $2\ell$ least significant bits of $x$. - -Recall also that the length of $y$ is $2\ell$ bits. We know this because limbs of the result are each constrained to be in $[0, 2^{\ell})$. So the result of this subtraction is $2\ell$ bits. Since the $2\ell$ least significant bits of the subtraction are `0` this means that - -$$ -\begin{aligned} -x - y & = 0 \\ -x &= y, -\end{aligned} -$$ - -which is a contradiction with $x < y$. - -## Costs - -Range checks should be the dominant cost, let's see how many we have. - -Range check (3) requires two range checks for $p_{11} = p_{111} \cdot 2^\ell + p_{110}$ - * a) $p_{110} \in [0, 2^\ell)$ - * b) $p_{111} \in [0, 2^2)$ - -Range check (8) requires two range checks and a decomposition check that is merged in (6). - * a) $v_{10} \in [0, 2^{\ell})$ - * b) $v_{11} \in [0, 2^3)$ - -The range checks on $p_0, p_1$ and $p_2$ follow from the range checks on $a,b$ and $q$. - -So we have 3.a, 3.b, 4, 7, 8.a, 8.b. - -| Range check | Gate type(s) | Witness | Rows | -| ----------- | ------------------------------------------------ | ------------------------- | ---- | -| 7 | $(v_0 - 3)(v_0 - 2)(v_0 - 1)v_0$ | $v_0$ | < 1 | -| 3.a | $(p_{111} - 3)(p_{111} - 2)(p_{111} - 1)p_{111}$ | $p_{111}$ | < 1 | -| 8.b | degree-8 constraint or plookup | $v_{11}$ | 1 | -| 3.b, 4, 8.a | `multi-range-check` | $p_{10}, p_{110}, v_{10}$ | 4 | - -So we have 1 multi-range-check, 1 single-range-check and 2 low-degree range checks. This consumes just over 5 rows. - -## Use CRT to constrain $a \cdot b - q \cdot f - r \equiv 0 \mod n$ - -Until now we have constrained the equation $\mod 2^t$, but remember that our application of the CRT means that we must also constrain the equation $\mod n$. We are leveraging the fact that if the identity holds for all moduli in $\mathcal{M} = \{n, 2^t\}$, then it holds for $\mathtt{lcm} (\mathcal{M}) = 2^t \cdot n = M$. - -Thus, we must check $a \cdot b - q \cdot f - r \equiv 0 \mod n$, which is over $\mathbb{F}_n$. - -This gives us equality $\mod 2^t \cdot n$ as long as the divisors are coprime. That is, as long as $\mathsf{gcd}(2^t, n) = 1$. Since the native modulus $n$ is prime, this is true. - -Thus, to perform this check is simple. We compute - -$$ -\begin{aligned} -a_n &= a \mod n \\ -b_n &= b \mod n \\ -q_n &= q \mod n \\ -r_n &= r \mod n \\ -f_n &= f \mod n -\end{aligned} -$$ - -using our native field modulus with constraints like this - -$$ -\begin{aligned} -a_n &= 2^{2\ell} \cdot a_2 + 2^{\ell} \cdot a_1 + a_0 \\ -b_n &= 2^{2\ell} \cdot b_2 + 2^{\ell} \cdot b_1 + b_0 \\ -q_n &= 2^{2\ell} \cdot q_2 + 2^{\ell} \cdot q_1 + q_0 \\ -r_n & = 2^{2\ell} \cdot r_2 + 2^{\ell} \cdot r_1 + r_0 \\ -f_n &= 2^{2\ell} \cdot f_2 + 2^{\ell} \cdot f_1 + f_0 \\ -\end{aligned} -$$ - -and then constrain - -$$ -a_n \cdot b_n - q_n \cdot f_n - r_n = 0 \mod n. -$$ - -Note that we do not use the negated foreign field modulus here. - -This requires a single constraint of the form - -> 9. $a_n \cdot b_n - q_n \cdot f_n = r_n$ - -with all of the terms expanded into the limbs according the the above equations. The values $a_n, b_n, q_n, f_n$ and $r_n$ do not need to be in the witness. - -## Range check both sides of $a \cdot b = q \cdot f + r$ - -Until now we have constrained that equation $a \cdot b = q \cdot f + r$ holds modulo $2^t$ and modulo $n$, so by the CRT it holds modulo $M = 2^t \cdot n$. Remember that, as outlined in the "Overview" section, we must prove our equation over the positive integers, rather than $\mod M$. By doing so, we assure that our solution is not equal to some $q' \cdot M + r'$ where $q', r' \in F_{M}$, in which case $q$ or $r$ would be invalid. - -First, let's consider the right hand side $q \cdot f + r$. We have - -$$ -q \cdot f + r < 2^t \cdot n -$$ - -Recall that we have parameterized $2^t \cdot n \ge f^2$, so if we can bound $q$ and $r$ such that - -$$ -q \cdot f + r < f^2 -$$ - -then we have achieved our goal. We know that $q$ must be less than $f$, so that is our first check, leaving us with - -$$ -\begin{aligned} -(f - 1) \cdot f + r &< f^2 \\ -r &< f^2 - (f - 1) \cdot f = f -\end{aligned} -$$ - -Therefore, to check $q \cdot f + r < 2^t \cdot n$, we need to check -* $q < f$ -* $r < f$ - -This should come at no surprise, since that is how we parameterized $2^t \cdot n$ earlier on. Note that by checking $q < f$ we assure correctness, while checking $r < f$ assures our solution is unique (sometimes referred to as canonical). - -Next, we must perform the same checks for the left hand side (i.e., $a \cdot b < 2^t \cdot n$). Since $a$ and $b$ must be less than the foreign field modulus $f$, this means checking -* $a < f$ -* $b < f$ - -So we have - -$$ -\begin{aligned} -a \cdot b &\le (f - 1) \cdot (f - 1) = f^2 - 2f + 1 \\ -\end{aligned} -$$ - -Since $2^t \cdot n \ge f^2$ we have - -$$ -\begin{aligned} -&f^2 - 2f + 1 < f^2 \le 2^t \cdot n \\ -&\implies -a \cdot b < 2^t \cdot n -\end{aligned} -$$ - -### Bound checks - -To perform the above range checks we use the *upper bound check* method described in the [Foreign Field Addition RFC](](https://github.com/o1-labs/proof-systems/blob/master/book/src/rfcs/ffadd.md#upper-bound-check)). - -The upper bound check is as follows. We must constrain $0 \le q < f$ over the positive integers, so - -$$ -\begin{aligned} -2^t \le q &+ 2^t < f + 2^t \\ -2^t - f \le q &+ 2^t - f < 2^t \\ -\end{aligned} -$$ - -Remember $f' = 2^t - f$ is our negated foreign field modulus. Thus, we have - -$$ -\begin{aligned} -f' \le q &+ f' < 2^t \\ -\end{aligned} -$$ - -So to check $q < t$ we just need to compute $q' = q + f'$ and check $f' \le q' < 2^t$ - -Observe that - -$$ -0 \le q \implies f' \le q' -$$ - -and that - -$$ -q' < 2^t \implies q < f -$$ - -So we only need to check - -- $0 \le q$ -- $q' < 2^t$ - -The first check is always true since we are operating over the positive integers and $q \in \mathbb{Z^+}$. Observe that the second check also constrains that $q < 2^t$, since $f \le 2^{259} < 2^t$ and thus - -$$ -\begin{aligned} -q' &\le 2^t \\ -q + f' &\le 2^t \\ -q &\le 2^t - (2^t - f) = f\\ -q &< 2^t -\end{aligned} -$$ - -Therefore, to constrain $q < f$ we only need constraints for - -- $q' = q + f'$ -- $q' < 2^t$ - -and we don't require an additional multi-range-check for $q < 2^t$. - -### Cost of bound checks - -This section analyzes the structure and costs of bounds checks for foreign field addition and foreign field multiplication. - -#### Addition - -In our foreign field addition design the operands $a$ and $b$ do not need to be less than $f$. The field overflow bit $\mathcal{o}$ for foreign field addition is at most 1. That is, $a + b = \mathcal{o} \cdot f + r$, where $r$ is allowed to be greater than $f$. Therefore, - -$$ -(f + a) + (f + b) = 1 \cdot f + (f + a + b) -$$ - -These can be chained along $k$ times as desired. The final result - -$$ -r = (\underbrace{f + \cdots + f}_{k} + a_1 + b_1 + \cdots a_k + b_k) -$$ - -Since the bit length of $r$ increases logarithmically with the number of additions, in Kimchi we must only check that the final $r$ in the chain is less than $f$ to constrain the entire chain. - -> **Security note:** In order to defer the $r < f$ check to the end of any chain of additions, it is extremely important to consider the potential impact of wraparound in $\mathbb{F_n}$. That is, we need to consider whether the addition of a large chain of elements greater than the foreign field modulus could wrap around. If this could happen then the $r < f$ check could fail to detect an invalid witness. Below we will show that this is not possible in Kimchi. -> -> Recall that our foreign field elements are comprised of 3 limbs of 88-bits each that are each represented as native field elements in our proof system. In order to wrap around and circumvent the $r < f$ check, the highest limb would need to wrap around. This means that an attacker would need to perform about $k \approx n/2^{\ell}$ additions of elements greater than then foreign field modulus. Since Kimchi's native moduli (Pallas and Vesta) are 255-bits, the attacker would need to provide a witness for about $k \approx 2^{167}$ additions. This length of witness is greater than Kimchi's maximum circuit (resp. witness) length. Thus, it is not possible for the attacker to generate a false proof by causing wraparound with a large chain of additions. - -In summary, for foreign field addition in Kimchi it is sufficient to only bound check the last result $r'$ in a chain of additions (and subtractions) - -- Compute bound $r' = r + f'$ with addition gate (2 rows) -- Range check $r' < 2^t$ (4 rows) - -#### Multiplication - -In foreign field multiplication, the situation is unfortunately different, and we must check that each of $a, b, q$ and $r$ are less than $f$. We cannot adopt the strategy from foreign field addition where the operands are allowed to be greater than $f$ because the bit length of $r$ would increases linearly with the number of multiplications. That is, - -$$ -(a_1 + f) \cdot (a_2 + f) = 1 \cdot f + \underbrace{f^2 + (a_1 + a_2 - 1) \cdot f + a_1 \cdot a_2}_{r} -$$ - -and after a chain of $k$ multiplication we have - -$$ -r = f^k + \ldots + a_1 \cdots a_k -$$ - -where $r > f^k$ quickly overflows our CRT modulus $2^t \cdot n$. For example, assuming our maximum foreign modulus of $f = 2^{259}$ and either of Kimchi's native moduli (i.e. Pallas or Vesta), $f^k > 2^t \cdot n$ for $k > 2$. That is, an overflow is possible for a chain of greater than 1 foreign field multiplication. Thus, we must check $a, b, q$ and $r$ are less than $f$ for each multiplication. - -Fortunately, in many situations the input operands may already be checked either as inputs or as outputs of previous operations, so they may not be required for each multiplication operation. - -Thus, the $q'$ and $r'$ checks (or equivalently $q$ and $r$) are our main focus because they must be done for every multiplication. - -- Compute bound $q' = q + f'$ with addition gate (2 rows) -- Compute bound $r' = r + f'$ with addition gate (2 rows) -- Range check $q' < 2^t$ (4 rows) -- Range check $r' < 2^t$ (4 rows) - -This costs 12 rows per multiplication. In a subsequent section, we will reduce it to 8 rows. - -### 2-limb decomposition - -Due to the limited number of permutable cells per gate, we do not have enough cells for copy constraining $q'$ and $r'$ (or $q$ and $r$) to their respective range check gadgets. To address this issue, we must decompose $q'$ into 2 limbs instead of 3, like so - -$$ -q' = q'_{01} + 2^{2\ell} \cdot q'_2 -$$ - -and - -$$ -q'_{01} = q'_0 + 2^{\ell} \cdot q'_1 -$$ - -Thus, $q'$ is decomposed into two limbs $q'_{01}$ (at most $2\ell$ bits) and $q'_2$ (at most $\ell$ bits). - -Note that $q'$ must be range checked by a `multi-range-check` gadget. To do this the `multi-range-check` gadget must - -- Store a copy of the limbs $q'_0, q'_1$ and $q'_2$ in its witness -- Range check that they are $\ell$ bit each -- Constrain that $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ (this is done with a special mode of the `multi-range-check` gadget) -- Have copy constraints for $q'_{01}$ and $q'_2$ as outputs of the `ForeignFieldMul` gate and inputs to the `multi-range-check` gadget - -Note that since the foreign field multiplication gate computes $q'$ from $q$ which is already in the witness and $q'_{01}$ and $q'_2$ have copy constraints to a `multi-range-check` gadget that fully constrains their decomposition from $q'$, then the `ForeignFieldMul` gate does not need to store an additional copy of $q'_0$ and $q'_1$. - -### An optimization - -Since the $q < f$ and $r < f$ checks must be done for each multiplication it makes sense to integrate them into the foreign field multiplication gate. By doing this we can save 4 rows per multiplication. - -Doing this doesn't require adding a lot more witness data because the operands for the bound computations $q' = q + f'$ and $r' = r + f'$ are already present in the witness of the multiplication gate. We only need to store the bounds $q'$ and $r'$ in permutable witness cells so that they may be copied to multi-range-check gates to check they are each less than $2^t$. - -To constrain $x + f' = x'$, the equation we use is - -$$ -x + 2^t = \mathcal{o} \cdot f + x', -$$ - -where $x$ is the original value, $\mathcal{o}=1$ is the field overflow bit and $x'$ is the remainder and our desired addition result (e.g. the bound). Rearranging things we get - -$$ -x + 2^t - f = x', -$$ - -which is just - -$$ -x + f' = x', -$$ - -Recall from the section "Avoiding borrows" that $f'$ is often larger than $f$. At first this seems like it could be a problem because in multiplication each operation must be less than $f$. However, this is because the maximum size of the multiplication was quadratic in the size of $f$ (we use the CRT, which requires the bound that $a \cdot b < 2^t \cdot n$). However, for addition the result is much smaller and we do not require the CRT nor the assumption that the operands are smaller than $f$. Thus, we have plenty of space in $\ell$-bit limbs to perform our addition. - -So, the equation we need to constrain is - -$$ -x + f' = x'. -$$ - -We can expand the left hand side into the 2 limb format in order to obtain 2 intermediate sums - -$$ -\begin{aligned} -s_{01} = x_{01} + f_{01}' \\ -s_2 = x_2 + f'_2 \\ -\end{aligned} -$$ - -where $x_{01}$ and $f'_{01}$ are defined like this - -$$ -\begin{aligned} -x_{01} = x_0 + 2^{\ell} \cdot x_1 \\ -f'_{01} = f'_0 + 2^{\ell} \cdot f'_1 \\ -\end{aligned} -$$ - -and $x$ and $f'$ are defined like this - -$$ -\begin{aligned} -x = x_{01} + 2^{2\ell} \cdot x_2 \\ -f' = f'_{01} + 2^{2\ell} \cdot f'_2 \\ -\end{aligned} -$$ - -Going back to our intermediate sums, the maximum bit length of sum $s_{01}$ is computed from the maximum bit lengths of $x_{01}$ and $f'_{01}$ - -$$ -\underbrace{(2^{\ell} - 1) + 2^{\ell} \cdot (2^{\ell} - 1)}_{x_{01}} + \underbrace{(2^{\ell} - 1) + 2^{\ell} \cdot (2^{\ell} - 1)}_{f'_{01}} = 2^{2\ell+ 1} - 2, -$$ - -which means $s_{01}$ is at most $2\ell + 1$ bits long. - -Similarly, since $x_2$ and $f'_2$ are less than $2^{\ell}$, the max value of $s_2$ is - -$$ -(2^{\ell} - 1) + (2^{\ell} - 1) = 2^{\ell + 1} - 2, -$$ - -which means $s_2$ is at most $\ell + 1$ bits long. - -Thus, we must constrain - -$$ -s_{01} + 2^{2\ell} \cdot s_2 - x'_{01} - 2^{2\ell} \cdot x'_2 = 0 \mod 2^t. -$$ - -The accumulation of this into parts looks like this. - -```text -0 L 2L 3L=t 4L -|-------------|-------------|-------------|-------------|-------------| - : -|------------s01------------:-| 2L + 1 - : ↖w01 - |------s2-----:-| L + 1 - : ↖w2 - : -|------------x'01-----------| - : - |------x'2----| - : -\____________________________/ - ≈ z01 \_____________/ - ≈ z2 -``` - -The two parts are computed with - -$$ -\begin{aligned} -z_{01} &= s_{01} - x'_{01} \\ -z_2 &= s_2 - x'_2. -\end{aligned} -$$ - -Therefore, there are two carry bits $w_{01}$ and $w_2$ such that - -$$ -\begin{aligned} -z_{01} &= 2^{2\ell} \cdot w_{01} \\ -z_2 + w_{01} &= 2^{\ell} \cdot w_2 -\end{aligned} -$$ - -In this scheme $x'_{01}, x'_2, w_{01}$ and $w_2$ are witness data, whereas $s_{01}$ and $s_2$ are formed from a constrained computation of witness data $x_{01}, x_2$ and constraint system public parameter $f'$. Note that due to carrying, witness $x'_{01}$ and $x'_2$ can be different than the values $s_{01}$ and $s_2$ computed from the limbs. - -Thus, each bound addition $x + f'$ requires the following witness data - -- $x_{01}, x_2$ -- $x'_{01}, x'_2$ -- $w_{01}, w_2$ - -where $f'$ is baked into the gate coefficients. The following constraints are needed - -- $2^{2\ell} \cdot w_{01} = s_{01} - x'_{01}$ -- $2^{\ell} \cdot w_2 = s_2 + w_{01} - x'_2$ -- $x'_{01} \in [0, 2^{2\ell})$ -- $x'_2 \in [0, 2^{\ell})$ -- $w_{01} \in [0, 2)$ -- $w_2 \in [0, 2)$ - -Due to the limited number of copyable witness cells per gate, we are currently only performing this optimization for $q$. - -The witness data is - -- $q_0, q_1, q_2$ -- $q'_{01}, q'_2$ -- $q'_{carry01}, q'_{carry2}$ - -The checks are - -1. $q_0 \in [0, 2^{\ell})$ -2. $q_1 \in [0, 2^{\ell})$ -3. $q'_0 = q_0 + f'_0$ -4. $q'_1 = q_1 + f'_1$ -5. $s_{01} = q'_0 + 2^{\ell} \cdot q'_1$ -6. $q'_{01} \in [0, 2^{2\ell})$ -7. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ -8. $q'_{carry01} \in [0, 2)$ -9. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ -10. $q_2 \in [0, 2^{\ell})$ -11. $s_2 = q_2 + f'_2$ -12. $q'_{carry2} \in [0, 2)$ -13. $2^{\ell} \cdot q'_{carry2} = s_2 + w_{01} - q'_2$ - - -Checks (1) - (5) assure that $s_{01}$ is at most $2\ell + 1$ bits. Whereas checks (10) - (11) assure that $s_2$ is at most $\ell + 1$ bits. Altogether they are comprise a single `multi-range-check` of $q_0, q_1$ and $q_2$. However, as noted above, we do not have enough copyable cells to output $q_1, q_2$ and $q_3$ to the `multi-range-check` gadget. Therefore, we adopt a strategy where the 2 limbs $q'_{01}$ and $q'_2$ are output to the `multi-range-check` gadget where the decomposition of $q'_0$ and $q'_2$ into $q'_{01} = p_0 + 2^{\ell} \cdot p_1$ is constrained and then $q'_0, q'_1$ and $q'_2$ are range checked. - -Although $q_1, q_2$ and $q_3$ are not range checked directly, this is safe because, as shown in the "Bound checks" section, range-checking that $q' \in [0, 2^t)$ also constrains that $q \in [0, 2^t)$. Therefore, the updated checks are - -1. $q_0 \in [0, 2^{\ell})$ `multi-range-check` -2. $q_1 \in [0, 2^{\ell})$ `multi-range-check` -3. $q'_0 = q_0 + f'_0$ `ForeignFieldMul` -4. $q'_1 = q_1 + f'_1$ `ForeignFieldMul` -5. $s_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `ForeignFieldMul` -6. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` -7. $q'_{carry01} \in [0, 2)$ `ForeignFieldMul` -8. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ `ForeignFieldMul` -9. $q_2 \in [0, 2^{\ell})$ `multi-range-check` -10. $s_2 = q_2 + f'_2$ `ForeignFieldMul` -11. $q'_{carry2} \in [0, 2)$ `ForeignFieldMul` -12. $2^{\ell} \cdot q'_{carry2} = s_2 + q'_{carry01} - q'_2$ `ForeignFieldMul` - -Note that we don't need to range-check $q'_{01}$ is at most $2\ell + 1$ bits because it is already implicitly constrained by the `multi-range-check` gadget constraining that $q'_0, q'_1$ and $q'_2$ are each at most $\ell$ bits and that $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$. Furthermore, since constraining the decomposition is already part of the `multi-range-check` gadget, we do not need to do it here also. - -To simplify things further, we can combine some of these checks. Recall our checked computations for the intermediate sums - -$$ -\begin{aligned} -s_{01} &= q_{01} + f'_{01} \\ -s_2 &= q_2 + f'_2 \\ -\end{aligned} -$$ - -where $q_{01} = q_0 + 2^{\ell} \cdot q_1$ and $f'_{01} = f'_0 + 2^{\ell} \cdot f'_1$. These do not need to be separate constraints, but are instead part of existing ones. - -Checks (10) and (11) can be combined into a single constraint $2^{\ell} \cdot q'_{carry2} = (q_2 + f'_2) + q'_{carry01} - q'_2$. Similarly, checks (3) - (5) and (8) can be combined into $2^{2\ell} \cdot q'_{carry01} = q_{01} + f'_{01} - q'_{01}$ with $q_{01}$ and $f'_{01}$ further expanded. The reduced constraints are - -1. $q_0 \in [0, 2^{\ell})$ `multi-range-check` -2. $q_1 \in [0, 2^{\ell})$ `multi-range-check` -3. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` -4. $q'_{carry01} \in [0, 2)$ `ForeignFieldMul` -5. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ `ForeignFieldMul` -6. $q_2 \in [0, 2^{\ell})$ `multi-range-check` -7. $q'_{carry2} \in [0, 2)$ `ForeignFieldMul` -8. $2^{\ell} \cdot q'_{carry2} = s_2 + w_{01} - q'_2$ `ForeignFieldMul` - -Finally, there is one more optimization that we will exploit. This optimization relies on the observation that for bound addition the second carry bit $q'_{carry2}$ is always zero. This this may be obscure, so we will prove it by contradiction. To simplify our work we rename some variables by letting $x_0 = q_{01}$ and $x_1 = q_2$. Thus, $q'_{carry2}$ being non-zero corresponds to a carry in $x_1 + f'_1$. - -> **Proof:** To get a carry in the highest limbs $x_1 + f'_1$ during bound addition, we need -> -> $$ -> 2^{\ell} < x_1 + \phi_0 + f'_1 \le 2^{\ell} - 1 + \phi_0 + f'_1 -> $$ -> -> where $2^{\ell} - 1$ is the maximum possible size of $x_1$ (before it overflows) and $\phi_0$ is the overflow bit from the addition of the least significant limbs $x_0$ and $f'_0$. This means -> -> $$ -> 2^{\ell} - \phi_0 - f'_1 < x_1 < 2^{\ell} -> $$ -> -> We cannot allow $x$ to overflow the foreign field, so we also have -> -> $$ -> x_1 < (f - x_0)/2^{2\ell} -> $$ -> -> Thus, -> -> $$ -> 2^{\ell} - \phi_0 - f'_1 < (f - x_0)/2^{2\ell} = f/2^{2\ell} - x_0/2^{2\ell} -> $$ -> -> Since $x_0/2^{2\ell} = \phi_0$ we have -> -> $$ -> 2^{\ell} - \phi_0 - f'_1 < f/2^{2\ell} - \phi_0 -> $$ -> -> so -> -> $$ -> 2^{\ell} - f'_1 < f/2^{2\ell} -> $$ -> -> Notice that $f/2^{2\ell} = f_1$. Now we have -> -> $$ -> 2^{\ell} - f'_1 < f_1 \\ -> \Longleftrightarrow \\ -> f'_1 > 2^{\ell} - f_1 -> $$ -> -> However, this is a contradiction with the definition of our negated foreign field modulus limb $f'_1 = 2^{\ell} - f_1$. $\blacksquare$ - -We have proven that $q'_{carry2}$ is always zero, so that allows use to simplify our constraints. We now have - -1. $q_0 \in [0, 2^{\ell})$ `multi-range-check` -2. $q_1 \in [0, 2^{\ell})$ `multi-range-check` -3. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` -4. $q'_{carry01} \in [0, 2)$ `ForeignFieldMul` -5. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ `ForeignFieldMul` -6. $q_2 \in [0, 2^{\ell})$ `multi-range-check` -7. $q'_2 = s_2 + w_{01}$ `ForeignFieldMul` - -In other words, we have eliminated constraint (7) and removed $q'_{carry2}$ from the witness. - -Since we already needed to range-check $q$ or $q'$, the total number of new constraints added is 4: 3 added to to `ForeignFieldMul` and 1 added to `multi-range-check` gadget for constraining the decomposition of $q'_{01}$. - -This saves 2 rows per multiplication. - -## Chaining multiplications - -Due to the limited number of cells accessible to gates, we are not able to chain multiplications into multiplications. We can chain foreign field additions into foreign field multiplications, but currently do not support chaining multiplications into additions (though there is a way to do it). - -## Constraining the computation - -Now we collect all of the checks that are required to constrain foreign field multiplication - -### 1. Range constrain $a$ - -If the $a$ operand has not been constrained to $[0, f)$ by any previous foreign field operations, then we constrain it like this -- Compute bound $a' = a + f'$ with addition gate (2 rows) `ForeignFieldAdd` -- Range check $a' \in [0, 2^t)$ (4 rows) `multi-range-check` - -### 2. Range constrain $b$ - -If the $b$ operand has not been constrained to $[0, f)$ by any previous foreign field operations, then we constrain it like this -- Compute bound $b' = b + f'$ with addition gate (2 rows) `ForeignFieldAdd` -- Range check $b' \in [0, 2^t)$ (4 rows) `multi-range-check` - -### 3. Range constrain $q$ - -The quotient $q$ is constrained to $[0, f)$ for each multiplication as part of the multiplication gate -- Compute bound $q' = q + f'$ with `ForeignFieldMul` constraints -- Range check $q' \in [0, 2^t)$ (4 rows) `multi-range-check` -- Range check $q \ge 0$ `ForeignFieldMul` (implicit by storing `q` in witness) - -### 4. Range constrain $r$ - -The remainder $r$ is constrained to $[0, f)$ for each multiplication using an external addition gate. -- Compute bound $r' = r + f'$ with addition gate (2 rows) `ForeignFieldAdd` -- Range check $r' \in [0, 2^t)$ (4 rows) `multi-range-check` - -### 5. Compute intermediate products - -Compute and constrain the intermediate products $p_0, p_1$ and $p_2$ as: - -- $p_0 = a_0 \cdot b_0 + q_0 \cdot f'_0$ `ForeignFieldMul` -- $p_1 = a_0 \cdot b_1 + a_1 \cdot b_0 + q_0 \cdot f'_1 + q_1 \cdot f'_0$ `ForeignFieldMul` -- $p_2 = a_0 \cdot b_2 + a_2 \cdot b_0 + a_1 \cdot b_1 + q_0 \cdot f'_2 + q_2 \cdot f'_0 + q_1 \cdot f'_1$ `ForeignFieldMul` - -where each of them is about $2\ell$-length elements. - -### 6. Native modulus checked computations - -Compute and constrain the native modulus values, which are used to check the constraints modulo $n$ in order to apply the CRT - -- $a_n = 2^{2\ell} \cdot a_2 + 2^{\ell} \cdot a_1 + a_0 \mod n$ -- $b_n = 2^{2\ell} \cdot b_2 + 2^{\ell} \cdot b_1 + b_0 \mod n$ -- $q_n = 2^{2\ell} \cdot q_2 + 2^{\ell} \cdot q_1 + q_0 \mod n$ -- $r_n = 2^{2\ell} \cdot r_2 + 2^{\ell} \cdot r_1 + r_0 \mod n$ -- $f_n = 2^{2\ell} \cdot f_2 + 2^{\ell} \cdot f_1 + f_0 \mod n$ - -### 7. Decompose middle intermediate product - -Check that $p_1 = 2^{\ell} \cdot p_{11} + p_{10}$: - -- $p_1 = 2^\ell \cdot p_{11} + p_{10}$ `ForeignFieldMul` -- Range check $p_{10} \in [0, 2^\ell)$ `multi-range-check` -- Range check $p_{11} \in [0, 2^{\ell+2})$ - - $p_{11} = p_{111} \cdot 2^\ell + p_{110}$ `ForeignFieldMul` - - Range check $p_{110} \in [0, 2^\ell)$ `multi-range-check` - - Range check $p_{111} \in [0, 2^2)$ with a degree-4 constraint `ForeignFieldMul` - -### 8. Zero sum for multiplication - -Now we have to constrain the zero sum - -$$ -(p_0 - r_0) + 2^{88}(p_1 - r_1) + 2^{176}(p_2 - r_2) = 0 \mod 2^t -$$ - -We constrain the first and the second halves as - -- $v_0 \cdot 2^{2\ell} = p_0 + 2^\ell \cdot p_{10} - r_0 - 2^\ell \cdot r_1$ `ForeignFieldMul` -- $v_1 \cdot 2^{\ell} = (p_{111} \cdot 2^\ell + p_{110}) + p_2 - r_2 + v_0$ `ForeignFieldMul` - -And some more range checks - -- Check that $v_0 \in [0, 2^2)$ with a degree-4 constraint `ForeignFieldMul` -- Check that $v_1 \in [0, 2^{\ell + 3})$ - - Check $v_1 = v_{11} \cdot 2^{88} + v_{10}$ `ForeignFieldMul` - - Check $v_{11} \in [0, 2^3]$ `ForeignFieldMul` - - Check $v_{10} < 2^\ell$ with range constraint `multi-range-check` - -To check that $v_{11} \in [0, 2^3)$ (i.e. that $v_{11}$ is at most 3 bits long) we first range-check $v_{11} \in [0, 2^{12})$ with a 12-bit plookup. This means there can be no higher bits set beyond the 12-bits of $v_{11}$. Next, we scale $v_{11}$ by $2^9$ in order to move the highest $12 - 3 = 9$ bits beyond the $12$th bit. Finally, we perform a 12-bit plookup on the resulting value. That is, we have - -- Check $v_{11} \in [0, 2^{12})$ with a 12-bit plookup (to prevent any overflow) -- Check $\mathsf{scaled}_{v_{11}} = 2^9 \cdot v_{11}$ -- Check $\mathsf{scaled}_{v_{11}}$ is a 12-bit value with a 12-bit plookup - -Kimchi's plookup implementation is extremely flexible and supports optional scaling of the lookup target value as part of the lookup operation. Thus, we do not require two witness elements, two lookup columns, nor the $\mathsf{scaled}_{v_{11}} = 2^9 \cdot v_{11}$ custom constraint. Instead we can just store $v_{11}$ in the witness and define this column as a "joint lookup" comprised of one 12-bit plookup on the original cell value and another 12-bit plookup on the cell value scaled by $2^9$, thus, yielding a 3-bit check. This eliminates one plookup column and reduces the total number of constraints. - -### 9. Native modulus constraint - -Using the checked native modulus computations we constrain that - -$$ -a_n \cdot b_n - q_n \cdot f_n - r_n = 0 \mod n. -$$ - -### 10. Compute intermediate sums - -Compute and constrain the intermediate sums $s_{01}$ and $s_2$ as: - -- $s_{01} = q_{01} + f'_{01}$ -- $s_2 = q_2 + f_2'$ -- $q_{01} = q_0 + 2^{\ell} \cdot q_1$ -- $f'_{01} = f'_0 + 2^{\ell} \cdot f'_1$ - -### 11. Decompose the lower quotient bound - -Check that $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$. - -Done by (3) above with the `multi-range-check` on $q'$ -- $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ -- Range check $q'_0 \in [0, 2^\ell)$ -- Range check $q'_1 \in [0, 2^\ell)$ - -### 12. Zero sum for quotient bound addition - -We constrain that - -$$ -s_{01} - q'_{01} + 2^{2\ell} \cdot (s_2 - q'_2) = 0 \mod 2^t -$$ - -by constraining the two halves - -* $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ -* $2^{\ell} \cdot q'_{carry2} = s_2 + q'_{carry01} - q'_2$ - -We also need a couple of small range checks - -- Check that $q'_{carry01}$ is boolean `ForeignFieldMul` -- Check that $q'_2$ is boolean `ForeignFieldMul` - -# Layout - -Based on the constraints above, we need the following 12 values copied from the range check gates. - -``` -a0, a1, a2, b0, b1, b2, q0, q1, q2, r0, r1, r2 -``` -Since we need 12 copied values for the constraints, they must span 2 rows. - -The $q < f$ bound limbs $q'_0, q'_1$ and $q'_2$ must be in copyable cells so they can be range-checked. Similarly, the limbs of the operands $a$, $b$ and the result $r$ must all be in copyable cells. This leaves only 2 remaining copyable cells and, therefore, we cannot compute and output $r' = r + f'$. It must be deferred to an external `ForeignFieldAdd` gate with the $r$ cells copied as an argument. - -NB: the $f$ and $f'$ values are publicly visible in the gate coefficients. - -| | Curr | Next | -| ---------- | ---------------------------------------- | ---------------- | -| **Column** | `ForeignFieldMul` | `Zero` | -| 0 | $a_0$ (copy) | $r_0$ (copy) | -| 1 | $a_1$ (copy) | $r_1$ (copy) | -| 2 | $a_2$ (copy) | $r_2$ (copy) | -| 3 | $b_0$ (copy) | $q'_{01}$ (copy) | -| 4 | $b_1$ (copy) | $q'_2$ (copy) | -| 5 | $b_2$ (copy) | $p_{10}$ (copy) | -| 6 | $v_{10}$ (copy) | $p_{110}$ (copy) | -| 7 | $v_{11}$ (scaled plookup) | | -| 8 | $v_0$ | | -| 9 | $q_0$ | | -| 10 | $q_1$ | | -| 11 | $q_2$ | | -| 12 | $q'_{carry01}$ | | -| 13 | $p_{111}$ | | -| 14 | | | - -# Checked computations - -As described above foreign field multiplication has the following intermediate computations - -1. $p_0 = a_0b_0 + q_0f'_0$ -2. $p_1 = a_0b_1 + a_1b_0 + q_0f'_1 + q_1f'_0$ -3. $p_2 = a_0b_2 + a_2b_0 + a_1b_1 + q_0f'_2 + q_2f'_0 + q_1f'_1$. - -For the $q$ bound addition we must also compute - -1. $s_{01} = q_{01} + f'_{01}$ -2. $s_2 = q_2 + f_2'$ -3. $q_{01} = q_0 + 2^{\ell} \cdot q_1$ -4. $f'_{01} = f'_0 + 2^{\ell} \cdot f'_1$ - -> Note the equivalence -> -> $$ -> \begin{aligned} -> s_{01} &= q_{01} + f'_{01} \\ -> &= q_0 + 2^{\ell} \cdot q_1 + f'_0 + 2^{\ell} \cdot f'_1 \\ -> &= q_0 + f'_0 + 2^{\ell} \cdot (q'_1 + f'_1) \\ -> &= q'_0 + 2^{\ell} \cdot q'_1 -> \end{aligned} -> $$ -> -> where $q'_0 = q_0 + f'_0$ and $q'_1 = q_1 + f'_1$ can be done with checked computations. - -Next, for applying the CRT we compute - -1. $a_n = 2^{2\ell} \cdot a_2 + 2^{\ell} \cdot a_1 + a_0 \mod n$ -2. $b_n = 2^{2\ell} \cdot b_2 + 2^{\ell} \cdot b_1 + b_0 \mod n$ -3. $q_n = 2^{2\ell} \cdot q_2 + 2^{\ell} \cdot q_1 + q_0 \mod n$ -4. $r_n = 2^{2\ell} \cdot r_2 + 2^{\ell} \cdot r_1 + r_0 \mod n$ -5. $f_n = 2^{2\ell} \cdot f_2 + 2^{\ell} \cdot f_1 + f_0 \mod n$ - -# Checks - -In total we require the following checks - -1. $p_{111} \in [0, 2^2)$ -2. $v_{10} \in [0, 2^{\ell})$ `multi-range-check` -3. $p_{110} \in [0, 2^{\ell})$ `multi-range-check` -4. $p_{10} \in [0, 2^{\ell})$ `multi-range-check` -5. $p_{11} = 2^{\ell} \cdot p_{111} + p_{110}$ -6. $p_1 = 2^{\ell} \cdot p_{11} + p_{10}$ -7. $v_0 \in [0, 2^2)$ -8. $2^{2\ell} \cdot v_0 = p_0 + 2^{\ell} \cdot p_{10} - r_0 - 2^{\ell} \cdot r_1$ -9. $v_{11} \in [0, 2^{3})$ -10. $v_1 = 2^{\ell} \cdot v_{11} + v_{10}$ -11. $2^{\ell} \cdot v_1 = v_0 + p_{11} + p_2 - r_2$ -12. $a_n \cdot b_n - q_n \cdot f_n = r_n$ -13. $q'_0 \in [0, 2^{\ell})$ `multi-range-check` -14. $q'_1 \in [0, 2^{\ell})$ `multi-range-check` -15. $q'_2 \in [0, 2^{\ell})$ `multi-range-check` -16. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` -17. $q'_{carry01} \in [0, 2)$ -18. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ -19. $q'_2 = s_2 + q'_{carry01}$ - -# Constraints - -These checks can be condensed into the minimal number of constraints as follows. - -First, we have the range-check corresponding to (1) as a degree-4 constraint - -**C1:** $p_{111} \cdot (p_{111} - 1) \cdot (p_{111} - 2) \cdot (p_{111} - 3)$ - -Checks (2) - (4) are all handled by a single `multi-range-check` gadget. - -**C2:** `multi-range-check` $v_{10}, p_{10}, p_{110}$ - -Next (5) and (6) can be combined into - -**C3:** $2^{\ell} \cdot (2^{\ell} \cdot p_{111} + p_{110}) + p_{10} = p_1$ - -Now we have the range-check corresponding to (7) as another degree-4 constraint - -**C4:** $v_0 \cdot (v_0 - 1) \cdot (v_0 - 2) \cdot (v_0 - 3)$ - -Next we have check (8) - -**C5:** $2^{2\ell} \cdot v_0 = p_0 + 2^{\ell} \cdot p_{10} - r_0 - 2^{\ell} \cdot r_1$ - -Up next, check (9) is a 3-bit range check - -**C6:** Plookup $v_{11}$ (12-bit plookup scaled by $2^9$) - -Now checks (10) and (11) can be combined into - -**C7:** $2^{\ell} \cdot (v_{11} \cdot 2^{\ell} + v_{10}) = p_2 + p_{11} + v_0 - r_2$ - -Next, for our use of the CRT, we must constrain that $a \cdot b = q \cdot f + r \mod n$. Thus, check (12) is - -**C8:** $a_n \cdot b_n - q_n \cdot f_n = r_n$ - -Next we must constrain the quotient bound addition. - -Checks (13) - (16) are all combined into `multi-range-check` gadget - -**C9:** `multi-range-check` $q'_0, q'_1, q'_2$ and $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$. - -Check (17) is a carry bit boolean check - -**C10:** $q'_{carry01} \cdot (q'_{carry01} - 1)$ - -Next, check (18) is - -**C11:** $2^{2\ell} \cdot q'_{carry10} = s_{01} - q'_{01}$ - -Finally, check (19) is - -**C12:** $q'_2 = s_2 + q'_{carry01}$ - -The `Zero` gate has no constraints and is just used to hold values required by the `ForeignFieldMul` gate. - -# External checks - -The following checks must be done with other gates to assure the soundness of the foreign field multiplication - -- Range check input - - `multi-range-check` $a$ - - `multi-range-check` $b$ -- Range check witness data - - `multi-range-check` $q'$ and check $q_{01}' = q'_0 + 2^{\ell} q'_1$ - - `multi-range-check` $\ell$-bit limbs: $p_{10}, p_{110}, p_{111}$ -- Range check output - - `multi-range-check` either $r$ or $r'$ -- Compute and constrain $r'$ the bound on $r$ - - `ForeignFieldAdd` $r + 2^t = 1 \cdot f + r'$ - -Copy constraints must connect the above witness cells to their respective input cells within the corresponding external check gates witnesses. From 7e485c3978e66e7fd4ba6c2f408e913d21ff2093 Mon Sep 17 00:00:00 2001 From: Joseandro Luiz Date: Thu, 3 Aug 2023 16:33:22 -0300 Subject: [PATCH 004/173] Updated broken links --- book/src/SUMMARY.md | 3 +-- book/src/rfcs/foreign_field_add.md | 4 ++-- book/src/specs/kimchi.md | 2 +- .../circuits/polynomials/foreign_field_mul/circuitgates.rs | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 8b6e482e3e..95d99267d6 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -84,8 +84,7 @@ - [RFC 3: Plookup integration in kimchi](./rfcs/3-lookup.md) - [RFC 4: Extended lookup tables](./rfcs/extended-lookup-tables.md) - [RFC 5: Foreign Field Addition](./rfcs/foreign_field_add.md) -- [RFC 6: Foreign Field Multiplication](./rfcs/foreign_field_mul.md) -- [RFC 7: Keccak](./rfcs/keccak.md) +- [RFC 6: Keccak](./rfcs/keccak.md) # Specifications diff --git a/book/src/rfcs/foreign_field_add.md b/book/src/rfcs/foreign_field_add.md index 85fdf1608d..4425df795f 100644 --- a/book/src/rfcs/foreign_field_add.md +++ b/book/src/rfcs/foreign_field_add.md @@ -15,7 +15,7 @@ If $f < n$ then we can easily perform the above computation. But in this gate we - our foreign field will have 256-bit length - our native field has 255-bit length -In other words, using 3 limbs of 88 bits each allows us to represent any foreign field element in the range $[0,2^{264})$ for foreign field addition, but only up to $2^{259}$ for foreign field multiplication. Thus, with the current configuration of our limbs, our foreign field must be smaller than $2^{259}$ (because $2^{264} \cdot 2^{255} > {2^{259}}^2 + 2^{259}$, more on this in the [FFmul RFC](../rfcs/ffmul.md). +In other words, using 3 limbs of 88 bits each allows us to represent any foreign field element in the range $[0,2^{264})$ for foreign field addition, but only up to $2^{259}$ for foreign field multiplication. Thus, with the current configuration of our limbs, our foreign field must be smaller than $2^{259}$ (because $2^{264} \cdot 2^{255} > {2^{259}}^2 + 2^{259}$, more on this in the [FFmul RFC](https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md). ### Splitting the addition @@ -273,7 +273,7 @@ Otherwise, we would need range checks for each new input of the chain, but none | 5n+7..5n+10 | `multi-range-check` for bound | $u$ | -For more details see the Bound Addition section of the [Foreign Field Multiplication RFC](../rfcs/ffmul.md). +For more details see the Bound Addition section of the [Foreign Field Multiplication RFC](https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md). ### Layout diff --git a/book/src/specs/kimchi.md b/book/src/specs/kimchi.md index e5b21cc3c8..ce96da37f5 100644 --- a/book/src/specs/kimchi.md +++ b/book/src/specs/kimchi.md @@ -1217,7 +1217,7 @@ left_input * right_input = quotient * foreign_field_modulus + remainder ##### Documentation -For more details please see the [Foreign Field Multiplication RFC](../rfcs/foreign_field_mul.md) +For more details please see the [Foreign Field Multiplication RFC](https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md) ##### Notations diff --git a/kimchi/src/circuits/polynomials/foreign_field_mul/circuitgates.rs b/kimchi/src/circuits/polynomials/foreign_field_mul/circuitgates.rs index e938d61bcc..4baa6a9eb9 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_mul/circuitgates.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_mul/circuitgates.rs @@ -8,7 +8,7 @@ //~ //~ ##### Documentation //~ -//~ For more details please see the [Foreign Field Multiplication RFC](../rfcs/foreign_field_mul.md) +//~ For more details please see the [Foreign Field Multiplication RFC](https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md) //~ //~ ##### Notations //~ @@ -102,7 +102,7 @@ use std::{array, marker::PhantomData}; /// Compute non-zero intermediate products /// /// For more details see the "Intermediate products" Section of -/// the [Foreign Field Multiplication RFC](../rfcs/foreign_field_mul.md) +/// the [Foreign Field Multiplication RFC](https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md) /// pub fn compute_intermediate_products>( left_input: &[T; 3], From 05e3ecf84116df2e9ed1255370fcc64bae6ae826 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 23 Aug 2023 16:49:46 +0200 Subject: [PATCH 005/173] Add rust 1.70 --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d99986aa59..b081a0537b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ jobs: run_checks: strategy: matrix: - rust_toolchain_version: [1.67, 1.68, 1.69] + rust_toolchain_version: [1.67, 1.68, 1.69, 1.70] # FIXME: currently not available for 5.0.0. # It might be related to boxroot dependency, and we would need to bump # up the ocaml-rs dependency From 043b5700192cff2635f7ba58cf71d87eec4a69fa Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 23 Aug 2023 16:49:57 +0200 Subject: [PATCH 006/173] Make clippy happy --- poly-commitment/src/combine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poly-commitment/src/combine.rs b/poly-commitment/src/combine.rs index 7c6ad08c5e..52f7e19f95 100644 --- a/poly-commitment/src/combine.rs +++ b/poly-commitment/src/combine.rs @@ -339,7 +339,7 @@ fn affine_window_combine_one_endo_base( points } -///! Double an array of curve points in-place. +/// Double an array of curve points in-place. fn batch_double_in_place( denominators: &mut Vec, points: &mut [SWJAffine

], From 6ca750bfcf11edd330a77575d70664b7cdb5a471 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 23 Aug 2023 16:59:03 +0200 Subject: [PATCH 007/173] Use take from Option: default value is None --- kimchi/src/tests/framework.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kimchi/src/tests/framework.rs b/kimchi/src/tests/framework.rs index ba35b238de..158988b94b 100644 --- a/kimchi/src/tests/framework.rs +++ b/kimchi/src/tests/framework.rs @@ -21,7 +21,7 @@ use groupmap::GroupMap; use mina_poseidon::sponge::FqSponge; use num_bigint::BigUint; use poly_commitment::commitment::CommitmentCurve; -use std::{fmt::Write, mem, time::Instant}; +use std::{fmt::Write, time::Instant}; // aliases @@ -100,7 +100,7 @@ where let start = Instant::now(); let lookup_tables = std::mem::take(&mut self.lookup_tables); - let runtime_tables_setup = mem::replace(&mut self.runtime_tables_setup, None); + let runtime_tables_setup = self.runtime_tables_setup.take(); let index = new_index_for_test_with_lookups::( self.gates.take().unwrap(), From f85859f9bf129b37f8d075eb275334c724e90a6b Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 23 Aug 2023 17:00:24 +0200 Subject: [PATCH 008/173] Use double quoted for numbers in CI --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b081a0537b..c1c5b19d54 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,11 +18,11 @@ jobs: run_checks: strategy: matrix: - rust_toolchain_version: [1.67, 1.68, 1.69, 1.70] + rust_toolchain_version: ["1.67", "1.68", "1.69", "1.70"] # FIXME: currently not available for 5.0.0. # It might be related to boxroot dependency, and we would need to bump # up the ocaml-rs dependency - ocaml_version: [4.14] + ocaml_version: ["4.14"] runs-on: ubuntu-latest name: Run some basic checks and tests From 3693a3f58c05ded19fb5a931baa95e549d0ff2ac Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 22 Sep 2023 11:17:25 +0200 Subject: [PATCH 009/173] Fix typo in indices Fix https://github.com/MinaProtocol/mina/issues/14185 --- book/src/fundamentals/custom_constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/fundamentals/custom_constraints.md b/book/src/fundamentals/custom_constraints.md index e2c7256d68..038ec9a2f9 100644 --- a/book/src/fundamentals/custom_constraints.md +++ b/book/src/fundamentals/custom_constraints.md @@ -20,7 +20,7 @@ Then, given such a circuit, PLONK lets you produce proofs for the statement ## Specifying a constraint -Mathematically speaking, a constraint is a multivariate polynomial over the variables $c_{\mathsf{Curr},i}, \dots, v_{\mathsf{Curr}, W+A-1}, v_{\mathsf{Next}, 0}, \dots, v_{\mathsf{Next}, W+A-1}$. In other words, there is one variable corresponding to the value of each column in the "current row" and one variable correspond to the value of each column in the "next row". +Mathematically speaking, a constraint is a multivariate polynomial over the variables $v_{\mathsf{Curr},0}, \dots, v_{\mathsf{Curr}, W+A-1}, v_{\mathsf{Next}, 0}, \dots, v_{\mathsf{Next}, W+A-1}$. In other words, there is one variable corresponding to the value of each column in the "current row" and one variable correspond to the value of each column in the "next row". In Rust, $v_{r, i}$ is written `E::cell(Column::Witness(i), r)`. So, for example, the variable $v_{\mathsf{Next}, 3}$ is written `E::cell(Column::Witness(3), CurrOrNext::Next)`. From db03070a30308b1048ad465fd93e9078d6ee34f5 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 4 Oct 2023 21:08:33 +0200 Subject: [PATCH 010/173] Use 1.71 and 1.72 in CI --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c1c5b19d54..2fd96e8a07 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ jobs: run_checks: strategy: matrix: - rust_toolchain_version: ["1.67", "1.68", "1.69", "1.70"] + rust_toolchain_version: ["1.71", "1.72"] # FIXME: currently not available for 5.0.0. # It might be related to boxroot dependency, and we would need to bump # up the ocaml-rs dependency From 9521cd21b66b7042c583ef1920c098f350b317f4 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 4 Oct 2023 21:12:56 +0200 Subject: [PATCH 011/173] Make clippy happy for 1.72 --- kimchi/src/circuits/lookup/index.rs | 2 +- kimchi/src/tests/turshi.rs | 4 ++-- turshi/src/memory.rs | 2 +- turshi/src/runner.rs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/kimchi/src/circuits/lookup/index.rs b/kimchi/src/circuits/lookup/index.rs index e04816c41d..7dfdca1bc4 100644 --- a/kimchi/src/circuits/lookup/index.rs +++ b/kimchi/src/circuits/lookup/index.rs @@ -224,7 +224,7 @@ impl LookupConstraintSystem { //~ 3. Concatenate runtime lookup tables with the ones used by gates let mut lookup_tables: Vec<_> = gate_lookup_tables .into_iter() - .chain(lookup_tables.into_iter()) + .chain(lookup_tables) .collect(); let mut has_table_id_0 = false; diff --git a/kimchi/src/tests/turshi.rs b/kimchi/src/tests/turshi.rs index 2162ba7fb6..7a6e1f0775 100644 --- a/kimchi/src/tests/turshi.rs +++ b/kimchi/src/tests/turshi.rs @@ -7,7 +7,7 @@ use turshi::{CairoMemory, CairoProgram}; #[test] fn test_cairo_should_fail() { - let instrs = vec![0x480680017fff8000, 10, 0x208b7fff7fff7ffe] + let instrs = [0x480680017fff8000, 10, 0x208b7fff7fff7ffe] .iter() .map(|&i: &i64| F::from(i)) .collect(); @@ -30,7 +30,7 @@ fn test_cairo_should_fail() { #[test] fn test_cairo_gate() { - let instrs = vec![ + let instrs = [ 0x400380007ffc7ffd, 0x482680017ffc8000, 1, diff --git a/turshi/src/memory.rs b/turshi/src/memory.rs index dc515380a5..9e4c7bb1e2 100644 --- a/turshi/src/memory.rs +++ b/turshi/src/memory.rs @@ -115,7 +115,7 @@ mod tests { // end // And checks that memory writing and reading works as expected by completing // the total memory of executing the program - let instrs = vec![0x480680017fff8000, 10, 0x208b7fff7fff7ffe] + let instrs = [0x480680017fff8000, 10, 0x208b7fff7fff7ffe] .iter() .map(|&i: &i64| F::from(i)) .collect(); diff --git a/turshi/src/runner.rs b/turshi/src/runner.rs index d669243883..adc7724f24 100644 --- a/turshi/src/runner.rs +++ b/turshi/src/runner.rs @@ -582,7 +582,7 @@ mod tests { fn test_cairo_step() { // This tests that CairoStep works for a 2 word instruction // tempvar x = 10; - let instrs = vec![0x480680017fff8000, 10, 0x208b7fff7fff7ffe] + let instrs = [0x480680017fff8000, 10, 0x208b7fff7fff7ffe] .iter() .map(|&i: &i64| F::from(i)) .collect(); @@ -604,7 +604,7 @@ mod tests { #[test] fn test_cairo_program() { - let instrs = vec![0x480680017fff8000, 10, 0x208b7fff7fff7ffe] + let instrs = [0x480680017fff8000, 10, 0x208b7fff7fff7ffe] .iter() .map(|&i: &i64| F::from(i)) .collect(); @@ -634,7 +634,7 @@ mod tests { return () end */ - let instrs = vec![ + let instrs = [ 0x400380007ffc7ffd, 0x482680017ffc8000, 1, From 57c18ab7c983ef9660cefde3f973b10260ee801d Mon Sep 17 00:00:00 2001 From: Joseandro Luiz Date: Tue, 10 Oct 2023 09:45:53 -0300 Subject: [PATCH 012/173] Removed the bot that closed stale issues and stale PRs --- .github/workflows/stale.yml | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 6dd1fdf361..0000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,35 +0,0 @@ -# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. -# -# You can adjust the behavior by modifying this file. -# For more information, see: -# https://github.com/actions/stale -name: Mark stale issues and pull requests - -on: - schedule: -# ┌───────────── minute (0 - 59) -# │ ┌───────────── hour (0 - 23) -# │ │ ┌───────────── day of the month (1 - 31) -# │ │ │ ┌───────────── month (1 - 12) -# │ │ │ │ ┌───────────── day of the week (0 - 6) -# │ │ │ │ │ -# │ │ │ │ │ -# │ │ │ │ │ - - cron: '19 7 * * *' - -jobs: - stale: - - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - - steps: - - uses: actions/stale@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'Stale issue message' - stale-pr-message: 'Stale pull request message' - stale-issue-label: 'no-issue-activity' - stale-pr-label: 'no-pr-activity' From 3a3a463d34137f32fec9492314f4186b9031b996 Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 11 Sep 2023 18:20:13 +0200 Subject: [PATCH 013/173] initial specs with layout distribution --- kimchi/src/circuits/polynomials/keccak.rs | 129 +++++++++++++--------- 1 file changed, 77 insertions(+), 52 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 637509344a..ecc0d7f6a0 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -6,10 +6,7 @@ use ark_ff::{PrimeField, SquareRootField}; use crate::circuits::{ gate::{CircuitGate, Connect}, polynomial::COLUMNS, - polynomials::{ - generic::GenericGateSpec, - rot::{self, RotMode}, - }, + polynomials::generic::GenericGateSpec, wires::Wire, }; @@ -29,57 +26,85 @@ pub const ROT_TAB: [[u32; 5]; 5] = [ [27, 20, 39, 8, 14], ]; -impl CircuitGate { - /// Creates Keccak gadget. - /// Right now it only creates an initial generic gate with all zeros starting on `new_row` and then - /// calls the Keccak rotation gadget - pub fn create_keccak(new_row: usize) -> (usize, Vec) { - // Initial Generic gate to constrain the prefix of the output to be zero - let mut gates = vec![CircuitGate::::create_generic_gadget( - Wire::for_row(new_row), - GenericGateSpec::Pub, - None, - )]; - Self::create_keccak_rot(&mut gates, new_row + 1, new_row) - } +//~ +//~ | Columns | [0...440) | [440...1540) | [1540...2440) | 2440 | +//~ | -------- | --------- | ------------ | ------------- | ---- | +//~ | `Keccak` | theta | pirho | chi | iota | +//~ +//~ | Columns | [0...100) | [100...120) | [120...200) | [200...220) | [220...240) | [240...260) | [260...280) | [280...300) | 300...320) | [320...340) | [340...440) | +//~ | -------- | --------- | ----------- | ----------- | ----------- | ----------- | ------------ | ----------- | ------------ | ------------ | ----------- | ----------- | +//~ | theta | state_a | state_c | reset_c | dense_c | quotient_c | remainder_c | bound_c | dense_rot_c | expand_rot_c | state_d | state_e | +//~ +//~ | Columns | [440...840) | [840...940) | [940...1040) | [1040...1140) | [1140...1240) | [1240...1340) | [1440...1540) | +//~ | -------- | ----------- | ----------- | ------------ | ------------- | ------------- | ------------- | ------------- | +//~ | pirho | reset_e | dense_e | quotient_e | remainder_e | bound_e | dense_rot_e | expand_rot_e | +//~ +//~ | Columns | [1540...1940) | [1940...2340) | [2340...2440) | +//~ | -------- | ------------- | ------------- | ------------- | +//~ | chi | reset_b | reset_sum | state_f | +//~ +//~ | Columns | 2440 | +//~ | -------- | ---- | +//~ | iota | g00 | +//~ +#[derive(Default)] +pub struct Keccak(PhantomData); + +impl Argument for Keccak +where + F: PrimeField, +{ + const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Keccak); + const CONSTRAINTS: u32 = 20 + 55 + 100 + 125 + 200 + 4; + + // Constraints for one round of the Keccak permutation function + fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { + // Check that the last 8 columns are 2-bit crumbs + // C1..C8: x * (x - 1) * (x - 2) * (x - 3) = 0 + let mut constraints = (7..COLUMNS) + .map(|i| crumb(&env.witness_curr(i))) + .collect::>(); + + // NOTE: + // If we ever want to make this gate more generic, the power of two for the length + // could be a coefficient of the gate instead of a fixed value in the constraints. + let two_to_64 = T::two_pow(64); + + let word = env.witness_curr(0); + let rotated = env.witness_curr(1); + let excess = env.witness_curr(2); + let shifted = env.witness_next(0); + let two_to_rot = env.coeff(0); + + // Obtains the following checks: + // C9: word * 2^{rot} = (excess * 2^64 + shifted) + // C10: rotated = shifted + excess + constraints.push( + word * two_to_rot.clone() - (excess.clone() * two_to_64.clone() + shifted.clone()), + ); + constraints.push(rotated - (shifted + excess.clone())); - /// Creates Keccak rotation gates for the whole table (skipping the rotation by 0) - pub fn create_keccak_rot( - gates: &mut Vec, - new_row: usize, - zero_row: usize, - ) -> (usize, Vec) { - let mut rot_row = new_row; - for row in ROT_TAB { - for rot in row { - // if rotation by 0 bits, no need to create a gate for it - if rot == 0 { - continue; - } - let mut rot64_gates = Self::create_rot64(rot_row, rot); - rot_row += rot64_gates.len(); - // Append them to the full gates vector - gates.append(&mut rot64_gates); - // Check that 2 most significant limbs of shifted are zero - gates.connect_64bit(zero_row, rot_row - 1); - } + // Compute the bound from the crumbs and limbs + let mut power_of_2 = T::one(); + let mut bound = T::zero(); + + // Sum 2-bit limbs + for i in (7..COLUMNS).rev() { + bound += power_of_2.clone() * env.witness_curr(i); + power_of_2 *= T::two_pow(2); // 2 bits } - (rot_row, gates.to_vec()) - } -} -/// Create a Keccak rotation (whole table) -/// Input: state (5x5) array of words to be rotated -pub fn create_witness_keccak_rot(state: [[u64; 5]; 5]) -> [Vec; COLUMNS] { - // First generic gate with all zeros to constrain that the two most significant limbs of shifted output are zeros - let mut witness: [Vec; COLUMNS] = array::from_fn(|_| vec![F::zero()]); - for (x, row) in ROT_TAB.iter().enumerate() { - for (y, &rot) in row.iter().enumerate() { - if rot == 0 { - continue; - } - rot::extend_rot(&mut witness, state[x][y], rot, RotMode::Left); + // Sum 12-bit limbs + for i in (3..=6).rev() { + bound += power_of_2.clone() * env.witness_curr(i); + power_of_2 *= T::two_pow(12); // 12 bits } + + // Check that excess < 2^rot by checking that bound < 2^64 + // Check RFC of Keccak for more details on the proof of this + // C11:bound = excess - 2^rot + 2^64 + constraints.push(bound - (excess - two_to_rot + two_to_64)); + + constraints } - witness } From 611dc781bae8e1e38533ae4f4e36379632d2be21 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 13:03:06 +0200 Subject: [PATCH 014/173] created macro and env function to access states from layout --- kimchi/src/circuits/argument.rs | 9 ++ kimchi/src/circuits/gate.rs | 7 +- kimchi/src/circuits/polynomials/keccak.rs | 127 +++++++++++++--------- kimchi/src/circuits/polynomials/mod.rs | 1 + 4 files changed, 93 insertions(+), 51 deletions(-) diff --git a/kimchi/src/circuits/argument.rs b/kimchi/src/circuits/argument.rs index 22fd183081..2125bdce6e 100644 --- a/kimchi/src/circuits/argument.rs +++ b/kimchi/src/circuits/argument.rs @@ -81,6 +81,15 @@ impl> ArgumentEnv { T::witness(Next, col, self.data.as_ref()) } + /// Witness cells in current row in an interval [from, to) + pub fn witness_curr_chunk(&self, from: usize, to: usize) -> Vec { + let mut chunk = Vec::with_capacity(to - from); + for i in from..to { + chunk.push(self.witness_curr(i)); + } + chunk + } + /// Coefficient value at index idx pub fn coeff(&self, idx: usize) -> T { T::coeff(idx, self.data.as_ref()) diff --git a/kimchi/src/circuits/gate.rs b/kimchi/src/circuits/gate.rs index fadac16ba2..b4fcdeaaaf 100644 --- a/kimchi/src/circuits/gate.rs +++ b/kimchi/src/circuits/gate.rs @@ -24,7 +24,7 @@ use thiserror::Error; use super::{ argument::ArgumentWitness, expr, - polynomials::{rot, xor}, + polynomials::{keccak, rot, xor}, }; /// A row accessible from a given row, corresponds to the fact that we open all polynomials @@ -112,6 +112,7 @@ pub enum GateType { // Gates for Keccak Xor16, Rot64, + Keccak, } /// Gate error @@ -229,6 +230,9 @@ impl CircuitGate { Rot64 => self .verify_witness::(row, witness, &index.cs, public) .map_err(|e| e.to_string()), + Keccak => self + .verify_witness::(row, witness, &index.cs, public) + .map_err(|e| e.to_string()), } } @@ -321,6 +325,7 @@ impl CircuitGate { } GateType::Xor16 => xor::Xor16::constraint_checks(&env, &mut cache), GateType::Rot64 => rot::Rot64::constraint_checks(&env, &mut cache), + GateType::Keccak => keccak::Keccak::constraint_checks(&env, &mut cache), }; // Check for failed constraints diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index ecc0d7f6a0..a954a279a3 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -1,14 +1,29 @@ //! Keccak gadget -use std::array; - -use ark_ff::{PrimeField, SquareRootField}; - use crate::circuits::{ - gate::{CircuitGate, Connect}, - polynomial::COLUMNS, - polynomials::generic::GenericGateSpec, - wires::Wire, + argument::{Argument, ArgumentEnv, ArgumentType}, + expr::{constraints::ExprOps, Cache}, + gate::GateType, }; +use ark_ff::PrimeField; +use std::marker::PhantomData; + +#[macro_export] +macro_rules! state_from_layout { + ($var:ident, $expr:expr) => { + let $var = $expr; + let $var = |i: usize, x: usize, y: usize, q: usize| { + $var[q + PARTS * (x + DIM * (y + DIM * i))].clone() + }; + }; + ($var:ident) => { + let $var = |i: usize, x: usize, y: usize, q: usize| { + $var[q + PARTS * (x + DIM * (y + DIM * i))].clone() + }; + }; +} + +pub const DIM: usize = 5; +pub const PARTS: usize = 4; /// Creates the 5x5 table of rotation bits for Keccak modulo 64 /// | x \ y | 0 | 1 | 2 | 3 | 4 | @@ -59,51 +74,63 @@ where // Constraints for one round of the Keccak permutation function fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { - // Check that the last 8 columns are 2-bit crumbs - // C1..C8: x * (x - 1) * (x - 2) * (x - 3) = 0 - let mut constraints = (7..COLUMNS) - .map(|i| crumb(&env.witness_curr(i))) - .collect::>(); - - // NOTE: - // If we ever want to make this gate more generic, the power of two for the length - // could be a coefficient of the gate instead of a fixed value in the constraints. - let two_to_64 = T::two_pow(64); - - let word = env.witness_curr(0); - let rotated = env.witness_curr(1); - let excess = env.witness_curr(2); - let shifted = env.witness_next(0); - let two_to_rot = env.coeff(0); - - // Obtains the following checks: - // C9: word * 2^{rot} = (excess * 2^64 + shifted) - // C10: rotated = shifted + excess - constraints.push( - word * two_to_rot.clone() - (excess.clone() * two_to_64.clone() + shifted.clone()), - ); - constraints.push(rotated - (shifted + excess.clone())); - - // Compute the bound from the crumbs and limbs - let mut power_of_2 = T::one(); - let mut bound = T::zero(); + let mut constraints = vec![]; - // Sum 2-bit limbs - for i in (7..COLUMNS).rev() { - bound += power_of_2.clone() * env.witness_curr(i); - power_of_2 *= T::two_pow(2); // 2 bits - } + // LOAD WITNESS LAYOUT + // THETA + let state_a = env.witness_curr_chunk(0, 100); + let state_c = env.witness_curr_chunk(100, 120); + let reset_c = env.witness_curr_chunk(120, 200); + let dense_c = env.witness_curr_chunk(200, 220); + let quotient_c = env.witness_curr_chunk(220, 240); + let remainder_c = env.witness_curr_chunk(240, 260); + let bound_c = env.witness_curr_chunk(260, 280); + let dense_rot_c = env.witness_curr_chunk(280, 300); + let expand_rot_c = env.witness_curr_chunk(300, 320); + let state_d = env.witness_curr_chunk(320, 340); + let state_e = env.witness_curr_chunk(340, 440); + // PI-RHO + let reset_e = env.witness_curr_chunk(440, 840); + let dense_e = env.witness_curr_chunk(840, 940); + let quotient_e = env.witness_curr_chunk(940, 1040); + let remainder_e = env.witness_curr_chunk(1040, 1140); + let bound_e = env.witness_curr_chunk(1140, 1240); + let dense_rot_e = env.witness_curr_chunk(1240, 1340); + let expand_rot_e = env.witness_curr_chunk(1340, 1440); + let state_b = env.witness_curr_chunk(1440, 1540); + // CHI + let reset_b = env.witness_curr_chunk(1540, 1940); + let reset_sum = env.witness_curr_chunk(1940, 2340); + let state_f = env.witness_curr_chunk(2340, 2440); + // IOTA + let g00 = env.witness_curr_chunk(2440, 2444); - // Sum 12-bit limbs - for i in (3..=6).rev() { - bound += power_of_2.clone() * env.witness_curr(i); - power_of_2 *= T::two_pow(12); // 12 bits - } + // LOAD STATES FROM LAYOUT + state_from_layout!(state_a); + state_from_layout!(state_c); + state_from_layout!(reset_c); + state_from_layout!(dense_c); + state_from_layout!(quotient_c); + state_from_layout!(remainder_c); + state_from_layout!(bound_c); + state_from_layout!(dense_rot_c); + state_from_layout!(expand_rot_c); + state_from_layout!(state_d); + state_from_layout!(state_e); + state_from_layout!(reset_e); + state_from_layout!(dense_e); + state_from_layout!(quotient_e); + state_from_layout!(remainder_e); + state_from_layout!(bound_e); + state_from_layout!(dense_rot_e); + state_from_layout!(expand_rot_e); + state_from_layout!(state_b); + state_from_layout!(reset_b); + state_from_layout!(reset_sum); + state_from_layout!(state_f); + state_from_layout!(g00); - // Check that excess < 2^rot by checking that bound < 2^64 - // Check RFC of Keccak for more details on the proof of this - // C11:bound = excess - 2^rot + 2^64 - constraints.push(bound - (excess - two_to_rot + two_to_64)); + // STEP theta constraints } diff --git a/kimchi/src/circuits/polynomials/mod.rs b/kimchi/src/circuits/polynomials/mod.rs index bbbe57939d..05407badfd 100644 --- a/kimchi/src/circuits/polynomials/mod.rs +++ b/kimchi/src/circuits/polynomials/mod.rs @@ -5,6 +5,7 @@ pub mod endosclmul; pub mod foreign_field_add; pub mod foreign_field_mul; pub mod generic; +pub mod keccak; pub mod not; pub mod permutation; pub mod poseidon; From 862fc50dfc33239e27a18047f72adb07bf242d3a Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 13:45:50 +0200 Subject: [PATCH 015/173] created constraints for theta step --- kimchi/src/circuits/polynomials/keccak.rs | 63 ++++++++++++++++++++--- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index a954a279a3..100b749892 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -7,24 +7,24 @@ use crate::circuits::{ use ark_ff::PrimeField; use std::marker::PhantomData; +pub const DIM: usize = 5; +pub const QUARTERS: usize = 4; + #[macro_export] macro_rules! state_from_layout { ($var:ident, $expr:expr) => { let $var = $expr; let $var = |i: usize, x: usize, y: usize, q: usize| { - $var[q + PARTS * (x + DIM * (y + DIM * i))].clone() + $var[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() }; }; ($var:ident) => { let $var = |i: usize, x: usize, y: usize, q: usize| { - $var[q + PARTS * (x + DIM * (y + DIM * i))].clone() + $var[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() }; }; } -pub const DIM: usize = 5; -pub const PARTS: usize = 4; - /// Creates the 5x5 table of rotation bits for Keccak modulo 64 /// | x \ y | 0 | 1 | 2 | 3 | 4 | /// | ----- | -- | -- | -- | -- | -- | @@ -130,8 +130,59 @@ where state_from_layout!(state_f); state_from_layout!(g00); - // STEP theta + // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints + for x in 0..DIM { + let word_c = compose_quarters(dense_c, x, 0); + let quo_c = compose_quarters(quotient_c, x, 0); + let rem_c = compose_quarters(remainder_c, x, 0); + let bnd_c = compose_quarters(bound_c, x, 0); + let rot_c = compose_quarters(dense_rot_c, x, 0); + + constraints + .push(word_c * T::two_pow(1) - (quo_c.clone() * T::two_pow(64) + rem_c.clone())); + constraints.push(rot_c - (quo_c.clone() + rem_c)); + constraints.push(bnd_c - (quo_c + T::two_pow(64) - T::two_pow(1))); + + for q in 0..QUARTERS { + constraints.push( + state_c(0, x, 0, q) + - (state_a(0, x, 0, q) + + state_a(0, x, 1, q) + + state_a(0, x, 2, q) + + state_a(0, x, 3, q) + + state_a(0, x, 4, q)), + ); + constraints.push( + state_c(0, x, 0, q) + - (reset_c(0, x, 0, q) + + T::two_pow(1) * reset_c(1, x, 0, q) + + T::two_pow(2) * reset_c(2, x, 0, q) + + T::two_pow(3) * reset_c(3, x, 0, q)), + ); + constraints.push( + state_d(0, x, 0, q) + - (reset_c(0, (x - 1 + DIM) % DIM, 0, q) + + expand_rot_c(0, (x + 1) % DIM, 0, q)), + ); + + for y in 0..DIM { + constraints + .push(state_e(0, x, y, q) - (state_a(0, x, y, q) + state_d(0, x, 0, q))); + } + } + } constraints } } + +fn compose_quarters>( + quarters: impl Fn(usize, usize, usize, usize) -> T, + x: usize, + y: usize, +) -> T { + quarters(0, x, y, 0) + + T::two_pow(16) * quarters(0, x, y, 1) + + T::two_pow(32) * quarters(0, x, y, 2) + + T::two_pow(48) * quarters(0, x, y, 3) +} From 9e5c63575fcec20a013e7a54b29c7fd90d70d30d Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 16:20:59 +0200 Subject: [PATCH 016/173] pirho constraints --- kimchi/src/circuits/polynomials/keccak.rs | 26 ++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 100b749892..0fd553d464 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -132,18 +132,20 @@ where // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { + // THETA let word_c = compose_quarters(dense_c, x, 0); let quo_c = compose_quarters(quotient_c, x, 0); let rem_c = compose_quarters(remainder_c, x, 0); let bnd_c = compose_quarters(bound_c, x, 0); let rot_c = compose_quarters(dense_rot_c, x, 0); - + // THETA constraints .push(word_c * T::two_pow(1) - (quo_c.clone() * T::two_pow(64) + rem_c.clone())); constraints.push(rot_c - (quo_c.clone() + rem_c)); constraints.push(bnd_c - (quo_c + T::two_pow(64) - T::two_pow(1))); for q in 0..QUARTERS { + // THETA constraints.push( state_c(0, x, 0, q) - (state_a(0, x, 0, q) @@ -166,8 +168,30 @@ where ); for y in 0..DIM { + // THETA constraints .push(state_e(0, x, y, q) - (state_a(0, x, y, q) + state_d(0, x, 0, q))); + // PI-RHO + let word_e = compose_quarters(dense_e, x, y); + let quo_e = compose_quarters(quotient_e, x, y); + let rem_e = compose_quarters(remainder_e, x, y); + let bnd_e = compose_quarters(bound_e, x, y); + let rot_e = compose_quarters(dense_rot_e, x, y); + // PI-RHO + constraints.push( + state_e(0, x, y, q) + - (reset_e(0, x, y, q) + + T::two_pow(1) * reset_e(1, x, y, q) + + T::two_pow(2) * reset_e(2, x, y, q) + + T::two_pow(3) * reset_e(3, x, y, q)), + ); + constraints.push( + word_e * T::two_pow(1) - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), + ); + constraints.push(rot_e - (quo_e.clone() + rem_e)); + constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(1))); + constraints + .push(state_b(0, y, (2 * x + 3 * y) % DIM, q) - expand_rot_e(0, x, y, q)); } } } From aa78a19340552cd56d116b31a0d20971e63b7787 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 16:57:04 +0200 Subject: [PATCH 017/173] expand function and round constants and offsets update --- kimchi/src/circuits/polynomials/keccak.rs | 49 +++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 0fd553d464..14c115994d 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -33,7 +33,7 @@ macro_rules! state_from_layout { /// | 2 | 62 | 6 | 43 | 15 | 61 | /// | 3 | 28 | 55 | 25 | 21 | 56 | /// | 4 | 27 | 20 | 39 | 8 | 14 | -pub const ROT_TAB: [[u32; 5]; 5] = [ +pub const OFF: [[u64; DIM]; DIM] = [ [0, 36, 3, 41, 18], [1, 44, 10, 45, 2], [62, 6, 43, 15, 61], @@ -41,6 +41,33 @@ pub const ROT_TAB: [[u32; 5]; 5] = [ [27, 20, 39, 8, 14], ]; +pub const RC: [u64; 24] = [ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808a, + 0x8000000080008000, + 0x000000000000808b, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008a, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000a, + 0x000000008000808b, + 0x800000000000008b, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800a, + 0x800000008000000a, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +]; + //~ //~ | Columns | [0...440) | [440...1540) | [1540...2440) | 2440 | //~ | -------- | --------- | ------------ | ------------- | ---- | @@ -76,6 +103,9 @@ where fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { let mut constraints = vec![]; + // DEFINE ROUND CONSTANT + let rc = [env.coeff(0), env.coeff(1), env.coeff(2), env.coeff(3)]; + // LOAD WITNESS LAYOUT // THETA let state_a = env.witness_curr_chunk(0, 100); @@ -186,10 +216,11 @@ where + T::two_pow(3) * reset_e(3, x, y, q)), ); constraints.push( - word_e * T::two_pow(1) - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), + word_e * T::two_pow(OFF[x][y]) + - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), ); constraints.push(rot_e - (quo_e.clone() + rem_e)); - constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(1))); + constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(OFF[x][y]))); constraints .push(state_b(0, y, (2 * x + 3 * y) % DIM, q) - expand_rot_e(0, x, y, q)); } @@ -210,3 +241,15 @@ fn compose_quarters>( + T::two_pow(32) * quarters(0, x, y, 2) + T::two_pow(48) * quarters(0, x, y, 3) } + +fn expand>(word: u64) -> Vec { + format!("{:064b}", word) + .chars() + .collect::>() + .chunks(16) + .map(|c| c.iter().collect::()) + .collect::>() + .iter() + .map(|c| T::literal(F::from(u64::from_str_radix(c, 16).unwrap()))) + .collect::>() +} From ecf475df9653000a855d5aee7e80b5e5d8f8eb7a Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 17:16:51 +0200 Subject: [PATCH 018/173] chi constraints --- kimchi/src/circuits/polynomials/keccak.rs | 52 ++++++++++++++--------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 14c115994d..83488d86a4 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -97,7 +97,7 @@ where F: PrimeField, { const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Keccak); - const CONSTRAINTS: u32 = 20 + 55 + 100 + 125 + 200 + 4; + const CONSTRAINTS: u32 = 20 + 55 + 100 + 125 + 300 + 4; // Constraints for one round of the Keccak permutation function fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { @@ -162,20 +162,20 @@ where // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { - // THETA + // THETA definitions let word_c = compose_quarters(dense_c, x, 0); let quo_c = compose_quarters(quotient_c, x, 0); let rem_c = compose_quarters(remainder_c, x, 0); let bnd_c = compose_quarters(bound_c, x, 0); let rot_c = compose_quarters(dense_rot_c, x, 0); - // THETA + // THETA constraints constraints .push(word_c * T::two_pow(1) - (quo_c.clone() * T::two_pow(64) + rem_c.clone())); constraints.push(rot_c - (quo_c.clone() + rem_c)); constraints.push(bnd_c - (quo_c + T::two_pow(64) - T::two_pow(1))); for q in 0..QUARTERS { - // THETA + // THETA constraints constraints.push( state_c(0, x, 0, q) - (state_a(0, x, 0, q) @@ -184,13 +184,7 @@ where + state_a(0, x, 3, q) + state_a(0, x, 4, q)), ); - constraints.push( - state_c(0, x, 0, q) - - (reset_c(0, x, 0, q) - + T::two_pow(1) * reset_c(1, x, 0, q) - + T::two_pow(2) * reset_c(2, x, 0, q) - + T::two_pow(3) * reset_c(3, x, 0, q)), - ); + constraints.push(state_c(0, x, 0, q) - compose_shifts(reset_c, x, 0, q)); constraints.push( state_d(0, x, 0, q) - (reset_c(0, (x - 1 + DIM) % DIM, 0, q) @@ -198,23 +192,17 @@ where ); for y in 0..DIM { - // THETA + // THETA constraints constraints .push(state_e(0, x, y, q) - (state_a(0, x, y, q) + state_d(0, x, 0, q))); - // PI-RHO + // PI-RHO definitions let word_e = compose_quarters(dense_e, x, y); let quo_e = compose_quarters(quotient_e, x, y); let rem_e = compose_quarters(remainder_e, x, y); let bnd_e = compose_quarters(bound_e, x, y); let rot_e = compose_quarters(dense_rot_e, x, y); - // PI-RHO - constraints.push( - state_e(0, x, y, q) - - (reset_e(0, x, y, q) - + T::two_pow(1) * reset_e(1, x, y, q) - + T::two_pow(2) * reset_e(2, x, y, q) - + T::two_pow(3) * reset_e(3, x, y, q)), - ); + // PI-RHO constraints + constraints.push(state_e(0, x, y, q) - compose_shifts(reset_e, x, y, q)); constraints.push( word_e * T::two_pow(OFF[x][y]) - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), @@ -223,6 +211,16 @@ where constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(OFF[x][y]))); constraints .push(state_b(0, y, (2 * x + 3 * y) % DIM, q) - expand_rot_e(0, x, y, q)); + + // CHI definitions + let not = + T::literal(F::from(0x1111111111111111u64)) - reset_b(0, (x + 1) % 5, y, q); + let sum = not + reset_b(1, (x + 2) % 5, y, q); + let and = reset_sum(1, x, y, q); + // CHI constraints + constraints.push(state_b(0, x, y, q) - compose_shifts(reset_b, x, y, q)); + constraints.push(sum - compose_shifts(reset_sum, x, y, q)); + constraints.push(state_f(0, x, y, q) - (reset_b(0, x, y, q) + and)); } } } @@ -242,6 +240,18 @@ fn compose_quarters>( + T::two_pow(48) * quarters(0, x, y, 3) } +fn compose_shifts>( + resets: impl Fn(usize, usize, usize, usize) -> T, + x: usize, + y: usize, + q: usize, +) -> T { + resets(0, x, y, q) + + T::two_pow(1) * resets(1, x, y, q) + + T::two_pow(2) * resets(2, x, y, q) + + T::two_pow(3) * resets(3, x, y, q) +} + fn expand>(word: u64) -> Vec { format!("{:064b}", word) .chars() From be5199362ec778b5d13b74f1de19290d1bd53142 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 17:22:49 +0200 Subject: [PATCH 019/173] iota constraints --- kimchi/src/circuits/polynomials/keccak.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 83488d86a4..4b00dc3907 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -225,6 +225,11 @@ where } } + // STEP iota: 4 constraints + for q in 0..QUARTERS { + constraints.push(g00(0, 0, 0, q) - (state_f(0, 0, 0, q) + rc[q].clone())); + } + constraints } } From 8bac2ffb1d8cb5939164d659b28c70f8ad5bb947 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 17:45:56 +0200 Subject: [PATCH 020/173] fix constraints indentation and add next row wiring --- kimchi/src/circuits/argument.rs | 9 +++ kimchi/src/circuits/polynomials/keccak.rs | 67 ++++++++++++++++------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/kimchi/src/circuits/argument.rs b/kimchi/src/circuits/argument.rs index 2125bdce6e..f74a87c5ba 100644 --- a/kimchi/src/circuits/argument.rs +++ b/kimchi/src/circuits/argument.rs @@ -90,6 +90,15 @@ impl> ArgumentEnv { chunk } + /// Witness cells in current row in an interval [from, to) + pub fn witness_next_chunk(&self, from: usize, to: usize) -> Vec { + let mut chunk = Vec::with_capacity(to - from); + for i in from..to { + chunk.push(self.witness_next(i)); + } + chunk + } + /// Coefficient value at index idx pub fn coeff(&self, idx: usize) -> T { T::coeff(idx, self.data.as_ref()) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 4b00dc3907..d42d3a5933 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -97,7 +97,7 @@ where F: PrimeField, { const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Keccak); - const CONSTRAINTS: u32 = 20 + 55 + 100 + 125 + 300 + 4; + const CONSTRAINTS: u32 = 854; // Constraints for one round of the Keccak permutation function fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { @@ -134,6 +134,8 @@ where let state_f = env.witness_curr_chunk(2340, 2440); // IOTA let g00 = env.witness_curr_chunk(2440, 2444); + // NEXT + let state_a_next = env.witness_next_chunk(0, 100); // LOAD STATES FROM LAYOUT state_from_layout!(state_a); @@ -159,23 +161,21 @@ where state_from_layout!(reset_sum); state_from_layout!(state_f); state_from_layout!(g00); + state_from_layout!(state_a_next); // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { - // THETA definitions let word_c = compose_quarters(dense_c, x, 0); let quo_c = compose_quarters(quotient_c, x, 0); let rem_c = compose_quarters(remainder_c, x, 0); let bnd_c = compose_quarters(bound_c, x, 0); let rot_c = compose_quarters(dense_rot_c, x, 0); - // THETA constraints constraints .push(word_c * T::two_pow(1) - (quo_c.clone() * T::two_pow(64) + rem_c.clone())); constraints.push(rot_c - (quo_c.clone() + rem_c)); constraints.push(bnd_c - (quo_c + T::two_pow(64) - T::two_pow(1))); for q in 0..QUARTERS { - // THETA constraints constraints.push( state_c(0, x, 0, q) - (state_a(0, x, 0, q) @@ -192,42 +192,67 @@ where ); for y in 0..DIM { - // THETA constraints constraints .push(state_e(0, x, y, q) - (state_a(0, x, y, q) + state_d(0, x, 0, q))); - // PI-RHO definitions - let word_e = compose_quarters(dense_e, x, y); - let quo_e = compose_quarters(quotient_e, x, y); - let rem_e = compose_quarters(remainder_e, x, y); - let bnd_e = compose_quarters(bound_e, x, y); - let rot_e = compose_quarters(dense_rot_e, x, y); - // PI-RHO constraints + } + } + } // END theta + + // STEP pirho: 5 * 5 * (3 + 4 * 2) = 275 constraints + for x in 0..DIM { + for y in 0..DIM { + let word_e = compose_quarters(dense_e, x, y); + let quo_e = compose_quarters(quotient_e, x, y); + let rem_e = compose_quarters(remainder_e, x, y); + let bnd_e = compose_quarters(bound_e, x, y); + let rot_e = compose_quarters(dense_rot_e, x, y); + + constraints.push( + word_e * T::two_pow(OFF[x][y]) + - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), + ); + constraints.push(rot_e - (quo_e.clone() + rem_e)); + constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(OFF[x][y]))); + + for q in 0..QUARTERS { constraints.push(state_e(0, x, y, q) - compose_shifts(reset_e, x, y, q)); - constraints.push( - word_e * T::two_pow(OFF[x][y]) - - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), - ); - constraints.push(rot_e - (quo_e.clone() + rem_e)); - constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(OFF[x][y]))); constraints .push(state_b(0, y, (2 * x + 3 * y) % DIM, q) - expand_rot_e(0, x, y, q)); + } + } + } // END pirho - // CHI definitions + // STEP chi: 4 * 5 * 5 * 3 = 300 constraints + for q in 0..QUARTERS { + for x in 0..DIM { + for y in 0..DIM { let not = T::literal(F::from(0x1111111111111111u64)) - reset_b(0, (x + 1) % 5, y, q); let sum = not + reset_b(1, (x + 2) % 5, y, q); let and = reset_sum(1, x, y, q); - // CHI constraints constraints.push(state_b(0, x, y, q) - compose_shifts(reset_b, x, y, q)); constraints.push(sum - compose_shifts(reset_sum, x, y, q)); constraints.push(state_f(0, x, y, q) - (reset_b(0, x, y, q) + and)); } } - } + } // END chi // STEP iota: 4 constraints for q in 0..QUARTERS { constraints.push(g00(0, 0, 0, q) - (state_f(0, 0, 0, q) + rc[q].clone())); + } // END iota + + // WIRE TO NEXT ROUND: 4 * 5 * 5 * 1 = 100 constraints + for q in 0..QUARTERS { + for x in 0..DIM { + for y in 0..DIM { + if x == 0 && y == 0 { + constraints.push(state_a_next(0, 0, 0, q) - g00(0, 0, 0, q)); + } else { + constraints.push(state_a_next(0, x, y, q) - state_f(0, x, y, q)); + } + } + } } constraints From ec06272682ad1a616a965355fce2d956155d88fa Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 12 Sep 2023 18:26:53 +0200 Subject: [PATCH 021/173] include xor of state inside gate --- kimchi/src/circuits/polynomials/keccak.rs | 113 +++++++++++++--------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index d42d3a5933..10a6890bc9 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -1,8 +1,11 @@ //! Keccak gadget -use crate::circuits::{ - argument::{Argument, ArgumentEnv, ArgumentType}, - expr::{constraints::ExprOps, Cache}, - gate::GateType, +use crate::{ + auto_clone_array, + circuits::{ + argument::{Argument, ArgumentEnv, ArgumentType}, + expr::{constraints::ExprOps, Cache}, + gate::GateType, + }, }; use ark_ff::PrimeField; use std::marker::PhantomData; @@ -69,25 +72,29 @@ pub const RC: [u64; 24] = [ ]; //~ -//~ | Columns | [0...440) | [440...1540) | [1540...2440) | 2440 | +//~ | Columns | [0...200) | [0...440) | [440...1540) | [1540...2440) | 2440 | //~ | -------- | --------- | ------------ | ------------- | ---- | -//~ | `Keccak` | theta | pirho | chi | iota | +//~ | `Keccak` | xor | theta | pirho | chi | iota | //~ -//~ | Columns | [0...100) | [100...120) | [120...200) | [200...220) | [220...240) | [240...260) | [260...280) | [280...300) | 300...320) | [320...340) | [340...440) | -//~ | -------- | --------- | ----------- | ----------- | ----------- | ----------- | ------------ | ----------- | ------------ | ------------ | ----------- | ----------- | -//~ | theta | state_a | state_c | reset_c | dense_c | quotient_c | remainder_c | bound_c | dense_rot_c | expand_rot_c | state_d | state_e | +//~ | Columns | [0...100) | [100...200) | +//~ | -------- | --------- | ----------- | +//~ | xor | old_state | new_state | //~ -//~ | Columns | [440...840) | [840...940) | [940...1040) | [1040...1140) | [1140...1240) | [1240...1340) | [1440...1540) | -//~ | -------- | ----------- | ----------- | ------------ | ------------- | ------------- | ------------- | ------------- | -//~ | pirho | reset_e | dense_e | quotient_e | remainder_e | bound_e | dense_rot_e | expand_rot_e | +//~ | Columns | [200...300) | [300...320) | [320...400) | [400...420) | [420...440) | [440...460) | [460...480) | [480...500) | 500...520) | [520...540) | [540...640) | +//~ | -------- | ----------- | ----------- | ----------- | ----------- | ----------- | ------------ | ----------- | ------------ | ------------ | ----------- | ----------- | +//~ | theta | state_a | state_c | reset_c | dense_c | quotient_c | remainder_c | bound_c | dense_rot_c | expand_rot_c | state_d | state_e | //~ -//~ | Columns | [1540...1940) | [1940...2340) | [2340...2440) | +//~ | Columns | [640...1040) | [1040...1140) | [1140...1240) | [1240...1340) | [1340...1440) | [1440...1540) | [1640...1740) | +//~ | -------- | ------------ | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | +//~ | pirho | reset_e | dense_e | quotient_e | remainder_e | bound_e | dense_rot_e | expand_rot_e | +//~ +//~ | Columns | [1740...2140) | [2140...2540) | [2540...2640) | //~ | -------- | ------------- | ------------- | ------------- | //~ | chi | reset_b | reset_sum | state_f | //~ -//~ | Columns | 2440 | -//~ | -------- | ---- | -//~ | iota | g00 | +//~ | Columns | [2640...2644) | +//~ | -------- | ------------- | +//~ | iota | g00 | //~ #[derive(Default)] pub struct Keccak(PhantomData); @@ -97,7 +104,7 @@ where F: PrimeField, { const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Keccak); - const CONSTRAINTS: u32 = 854; + const CONSTRAINTS: u32 = 954; // Constraints for one round of the Keccak permutation function fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { @@ -107,37 +114,42 @@ where let rc = [env.coeff(0), env.coeff(1), env.coeff(2), env.coeff(3)]; // LOAD WITNESS LAYOUT + // XOR + let old_state = env.witness_curr_chunk(0, 100); + let new_state = env.witness_curr_chunk(100, 200); // THETA - let state_a = env.witness_curr_chunk(0, 100); - let state_c = env.witness_curr_chunk(100, 120); - let reset_c = env.witness_curr_chunk(120, 200); - let dense_c = env.witness_curr_chunk(200, 220); - let quotient_c = env.witness_curr_chunk(220, 240); - let remainder_c = env.witness_curr_chunk(240, 260); - let bound_c = env.witness_curr_chunk(260, 280); - let dense_rot_c = env.witness_curr_chunk(280, 300); - let expand_rot_c = env.witness_curr_chunk(300, 320); - let state_d = env.witness_curr_chunk(320, 340); - let state_e = env.witness_curr_chunk(340, 440); + let state_a = env.witness_curr_chunk(200, 300); + let state_c = env.witness_curr_chunk(300, 320); + let reset_c = env.witness_curr_chunk(320, 400); + let dense_c = env.witness_curr_chunk(400, 420); + let quotient_c = env.witness_curr_chunk(420, 440); + let remainder_c = env.witness_curr_chunk(440, 460); + let bound_c = env.witness_curr_chunk(460, 480); + let dense_rot_c = env.witness_curr_chunk(480, 500); + let expand_rot_c = env.witness_curr_chunk(500, 520); + let state_d = env.witness_curr_chunk(520, 540); + let state_e = env.witness_curr_chunk(540, 640); // PI-RHO - let reset_e = env.witness_curr_chunk(440, 840); - let dense_e = env.witness_curr_chunk(840, 940); - let quotient_e = env.witness_curr_chunk(940, 1040); - let remainder_e = env.witness_curr_chunk(1040, 1140); - let bound_e = env.witness_curr_chunk(1140, 1240); - let dense_rot_e = env.witness_curr_chunk(1240, 1340); - let expand_rot_e = env.witness_curr_chunk(1340, 1440); - let state_b = env.witness_curr_chunk(1440, 1540); + let reset_e = env.witness_curr_chunk(640, 1040); + let dense_e = env.witness_curr_chunk(1040, 1140); + let quotient_e = env.witness_curr_chunk(1140, 1240); + let remainder_e = env.witness_curr_chunk(1240, 1340); + let bound_e = env.witness_curr_chunk(1340, 1440); + let dense_rot_e = env.witness_curr_chunk(1440, 1540); + let expand_rot_e = env.witness_curr_chunk(1540, 1640); + let state_b = env.witness_curr_chunk(1640, 1740); // CHI - let reset_b = env.witness_curr_chunk(1540, 1940); - let reset_sum = env.witness_curr_chunk(1940, 2340); - let state_f = env.witness_curr_chunk(2340, 2440); + let reset_b = env.witness_curr_chunk(1740, 2140); + let reset_sum = env.witness_curr_chunk(2140, 2540); + let state_f = env.witness_curr_chunk(2540, 2640); // IOTA - let g00 = env.witness_curr_chunk(2440, 2444); + let g00 = env.witness_curr_chunk(2640, 2644); // NEXT - let state_a_next = env.witness_next_chunk(0, 100); + let next_state = env.witness_next_chunk(0, 100); // LOAD STATES FROM LAYOUT + state_from_layout!(old_state); + state_from_layout!(new_state); state_from_layout!(state_a); state_from_layout!(state_c); state_from_layout!(reset_c); @@ -161,7 +173,18 @@ where state_from_layout!(reset_sum); state_from_layout!(state_f); state_from_layout!(g00); - state_from_layout!(state_a_next); + state_from_layout!(next_state); + + // STEP xor: 100 constraints + for q in 0..QUARTERS { + for x in 0..DIM { + for y in 0..DIM { + constraints.push( + state_a(0, x, y, q) - (old_state(0, x, y, q) + new_state(0, x, y, q)), + ); + } + } + } // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { @@ -247,13 +270,13 @@ where for x in 0..DIM { for y in 0..DIM { if x == 0 && y == 0 { - constraints.push(state_a_next(0, 0, 0, q) - g00(0, 0, 0, q)); + constraints.push(next_state(0, 0, 0, q) - g00(0, 0, 0, q)); } else { - constraints.push(state_a_next(0, x, y, q) - state_f(0, x, y, q)); + constraints.push(next_state(0, x, y, q) - state_f(0, x, y, q)); } } } - } + } // END wiring constraints } From a33120a782ff372d30f85d136bbabfa3a89d56f3 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 13 Sep 2023 11:30:33 +0200 Subject: [PATCH 022/173] fix clippy --- kimchi/src/circuits/polynomials/keccak.rs | 13 +++++-------- kimchi/src/verifier.rs | 1 + 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 10a6890bc9..88b3705ad6 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -1,11 +1,8 @@ //! Keccak gadget -use crate::{ - auto_clone_array, - circuits::{ - argument::{Argument, ArgumentEnv, ArgumentType}, - expr::{constraints::ExprOps, Cache}, - gate::GateType, - }, +use crate::circuits::{ + argument::{Argument, ArgumentEnv, ArgumentType}, + expr::{constraints::ExprOps, Cache}, + gate::GateType, }; use ark_ff::PrimeField; use std::marker::PhantomData; @@ -305,7 +302,7 @@ fn compose_shifts>( + T::two_pow(3) * resets(3, x, y, q) } -fn expand>(word: u64) -> Vec { +fn _expand>(word: u64) -> Vec { format!("{:064b}", word) .chars() .collect::>() diff --git a/kimchi/src/verifier.rs b/kimchi/src/verifier.rs index c2796fccca..bee5f00d66 100644 --- a/kimchi/src/verifier.rs +++ b/kimchi/src/verifier.rs @@ -83,6 +83,7 @@ impl<'a, G: KimchiCurve> Context<'a, G> { ForeignFieldMul => Some(self.verifier_index.foreign_field_mul_comm.as_ref()?), Xor16 => Some(self.verifier_index.xor_comm.as_ref()?), Rot64 => Some(self.verifier_index.rot_comm.as_ref()?), + Keccak => todo!(), } } } From 57327d8796a9a78da350df280f0ce8a67b44031f Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 13 Sep 2023 11:48:04 +0200 Subject: [PATCH 023/173] remove unnecessary ranges in loops --- kimchi/src/circuits/polynomials/keccak.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 88b3705ad6..5cf70997f8 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -219,8 +219,8 @@ where } // END theta // STEP pirho: 5 * 5 * (3 + 4 * 2) = 275 constraints - for x in 0..DIM { - for y in 0..DIM { + for (x, row) in OFF.iter().enumerate() { + for (y, off) in row.iter().enumerate() { let word_e = compose_quarters(dense_e, x, y); let quo_e = compose_quarters(quotient_e, x, y); let rem_e = compose_quarters(remainder_e, x, y); @@ -228,11 +228,10 @@ where let rot_e = compose_quarters(dense_rot_e, x, y); constraints.push( - word_e * T::two_pow(OFF[x][y]) - - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), + word_e * T::two_pow(*off) - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), ); constraints.push(rot_e - (quo_e.clone() + rem_e)); - constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(OFF[x][y]))); + constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(*off))); for q in 0..QUARTERS { constraints.push(state_e(0, x, y, q) - compose_shifts(reset_e, x, y, q)); @@ -258,8 +257,8 @@ where } // END chi // STEP iota: 4 constraints - for q in 0..QUARTERS { - constraints.push(g00(0, 0, 0, q) - (state_f(0, 0, 0, q) + rc[q].clone())); + for (q, c) in rc.iter().enumerate() { + constraints.push(g00(0, 0, 0, q) - (state_f(0, 0, 0, q) + c.clone())); } // END iota // WIRE TO NEXT ROUND: 4 * 5 * 5 * 1 = 100 constraints From c2179767efccbcc3fafeb0b8ef963d8c3c3068c8 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 13 Sep 2023 11:57:10 +0200 Subject: [PATCH 024/173] remove unused function in this pr --- kimchi/src/circuits/polynomials/keccak.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 5cf70997f8..8398d71cf1 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -300,15 +300,3 @@ fn compose_shifts>( + T::two_pow(2) * resets(2, x, y, q) + T::two_pow(3) * resets(3, x, y, q) } - -fn _expand>(word: u64) -> Vec { - format!("{:064b}", word) - .chars() - .collect::>() - .chunks(16) - .map(|c| c.iter().collect::()) - .collect::>() - .iter() - .map(|c| T::literal(F::from(u64::from_str_radix(c, 16).unwrap()))) - .collect::>() -} From 1179b8d97361fd61bf31c8bd22de4b6374f19cab Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 13 Sep 2023 17:58:53 +0200 Subject: [PATCH 025/173] update chainable layout --- kimchi/src/circuits/polynomials/keccak.rs | 125 +++++++++------------- 1 file changed, 49 insertions(+), 76 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 8398d71cf1..9ab9ae1982 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -69,29 +69,31 @@ pub const RC: [u64; 24] = [ ]; //~ -//~ | Columns | [0...200) | [0...440) | [440...1540) | [1540...2440) | 2440 | -//~ | -------- | --------- | ------------ | ------------- | ---- | -//~ | `Keccak` | xor | theta | pirho | chi | iota | +//~ | `KeccakRound` | [0...440) | [440...1540) | [1540...2344) | +//~ | ------------- | --------- | ------------ | ------------- | +//~ | Curr | theta | pirho | chi | //~ -//~ | Columns | [0...100) | [100...200) | -//~ | -------- | --------- | ----------- | -//~ | xor | old_state | new_state | +//~ | `KeccakRound` | [0...100) | +//~ | ------------- | --------- | +//~ | Next | iota | //~ -//~ | Columns | [200...300) | [300...320) | [320...400) | [400...420) | [420...440) | [440...460) | [460...480) | [480...500) | 500...520) | [520...540) | [540...640) | -//~ | -------- | ----------- | ----------- | ----------- | ----------- | ----------- | ------------ | ----------- | ------------ | ------------ | ----------- | ----------- | -//~ | theta | state_a | state_c | reset_c | dense_c | quotient_c | remainder_c | bound_c | dense_rot_c | expand_rot_c | state_d | state_e | +//~ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- //~ -//~ | Columns | [640...1040) | [1040...1140) | [1140...1240) | [1240...1340) | [1340...1440) | [1440...1540) | [1640...1740) | -//~ | -------- | ------------ | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -//~ | pirho | reset_e | dense_e | quotient_e | remainder_e | bound_e | dense_rot_e | expand_rot_e | +//~ | Columns | [0...100) | [100...120) | [120...200) | [200...220) | [220...240) | [240...260) | [260...280) | [280...300) | [300...320) | [320...340) | [340...440) | +//~ | -------- | --------- | ----------- | ----------- | ----------- | ----------- | ------------ | ----------- | ------------ | ------------ | ----------- | ----------- | +//~ | theta | state_a | state_c | reset_c | dense_c | quotient_c | remainder_c | bound_c | dense_rot_c | expand_rot_c | state_d | state_e | //~ -//~ | Columns | [1740...2140) | [2140...2540) | [2540...2640) | -//~ | -------- | ------------- | ------------- | ------------- | -//~ | chi | reset_b | reset_sum | state_f | +//~ | Columns | [440...840) | [840...940) | [940...1040) | [1040...1140) | [1140...1240) | [1240...1340) | [1340...1440) | [1440...1540) | +//~ | -------- | ----------- | ----------- | ------------ | ------------- | ------------- | ------------- | ------------- | ------------- | +//~ | pirho | reset_e | dense_e | quotient_e | remainder_e | bound_e | dense_rot_e | expand_rot_e | state_b | //~ -//~ | Columns | [2640...2644) | -//~ | -------- | ------------- | -//~ | iota | g00 | +//~ | Columns | [1540...1940) | [1940...2340) | [2340...2344 | +//~ | -------- | ------------- | ------------- | ------------ | +//~ | chi | reset_b | reset_sum | f00 | +//~ +//~ | Columns | [0...4) | [4...100) | +//~ | -------- | ------- | --------- | +//~ | iota | g00 | state_f | //~ #[derive(Default)] pub struct Keccak(PhantomData); @@ -101,7 +103,7 @@ where F: PrimeField, { const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Keccak); - const CONSTRAINTS: u32 = 954; + const CONSTRAINTS: u32 = 754; // Constraints for one round of the Keccak permutation function fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { @@ -111,42 +113,38 @@ where let rc = [env.coeff(0), env.coeff(1), env.coeff(2), env.coeff(3)]; // LOAD WITNESS LAYOUT - // XOR - let old_state = env.witness_curr_chunk(0, 100); - let new_state = env.witness_curr_chunk(100, 200); // THETA - let state_a = env.witness_curr_chunk(200, 300); - let state_c = env.witness_curr_chunk(300, 320); - let reset_c = env.witness_curr_chunk(320, 400); - let dense_c = env.witness_curr_chunk(400, 420); - let quotient_c = env.witness_curr_chunk(420, 440); - let remainder_c = env.witness_curr_chunk(440, 460); - let bound_c = env.witness_curr_chunk(460, 480); - let dense_rot_c = env.witness_curr_chunk(480, 500); - let expand_rot_c = env.witness_curr_chunk(500, 520); - let state_d = env.witness_curr_chunk(520, 540); - let state_e = env.witness_curr_chunk(540, 640); + let state_a = env.witness_curr_chunk(0, 100); + let state_c = env.witness_curr_chunk(100, 120); + let reset_c = env.witness_curr_chunk(120, 200); + let dense_c = env.witness_curr_chunk(200, 220); + let quotient_c = env.witness_curr_chunk(220, 240); + let remainder_c = env.witness_curr_chunk(240, 260); + let bound_c = env.witness_curr_chunk(260, 280); + let dense_rot_c = env.witness_curr_chunk(280, 300); + let expand_rot_c = env.witness_curr_chunk(300, 320); + let state_d = env.witness_curr_chunk(320, 340); + let state_e = env.witness_curr_chunk(340, 440); // PI-RHO - let reset_e = env.witness_curr_chunk(640, 1040); - let dense_e = env.witness_curr_chunk(1040, 1140); - let quotient_e = env.witness_curr_chunk(1140, 1240); - let remainder_e = env.witness_curr_chunk(1240, 1340); - let bound_e = env.witness_curr_chunk(1340, 1440); - let dense_rot_e = env.witness_curr_chunk(1440, 1540); - let expand_rot_e = env.witness_curr_chunk(1540, 1640); - let state_b = env.witness_curr_chunk(1640, 1740); + let reset_e = env.witness_curr_chunk(440, 840); + let dense_e = env.witness_curr_chunk(840, 940); + let quotient_e = env.witness_curr_chunk(940, 1040); + let remainder_e = env.witness_curr_chunk(1040, 1140); + let bound_e = env.witness_curr_chunk(1140, 1240); + let dense_rot_e = env.witness_curr_chunk(1240, 1340); + let expand_rot_e = env.witness_curr_chunk(1340, 1440); + let state_b = env.witness_curr_chunk(1440, 1540); // CHI - let reset_b = env.witness_curr_chunk(1740, 2140); - let reset_sum = env.witness_curr_chunk(2140, 2540); - let state_f = env.witness_curr_chunk(2540, 2640); + let reset_b = env.witness_curr_chunk(1540, 1940); + let reset_sum = env.witness_curr_chunk(1940, 2340); + let mut state_f = env.witness_curr_chunk(2340, 2344); + let mut tail = env.witness_next_chunk(4, 100); + state_f.append(&mut tail); // IOTA - let g00 = env.witness_curr_chunk(2640, 2644); - // NEXT - let next_state = env.witness_next_chunk(0, 100); + let mut state_g = env.witness_next_chunk(0, 4); + state_g.append(&mut tail); // LOAD STATES FROM LAYOUT - state_from_layout!(old_state); - state_from_layout!(new_state); state_from_layout!(state_a); state_from_layout!(state_c); state_from_layout!(reset_c); @@ -169,19 +167,7 @@ where state_from_layout!(reset_b); state_from_layout!(reset_sum); state_from_layout!(state_f); - state_from_layout!(g00); - state_from_layout!(next_state); - - // STEP xor: 100 constraints - for q in 0..QUARTERS { - for x in 0..DIM { - for y in 0..DIM { - constraints.push( - state_a(0, x, y, q) - (old_state(0, x, y, q) + new_state(0, x, y, q)), - ); - } - } - } + state_from_layout!(state_g); // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { @@ -258,22 +244,9 @@ where // STEP iota: 4 constraints for (q, c) in rc.iter().enumerate() { - constraints.push(g00(0, 0, 0, q) - (state_f(0, 0, 0, q) + c.clone())); + constraints.push(state_g(0, 0, 0, q) - (state_f(0, 0, 0, q) + c.clone())); } // END iota - // WIRE TO NEXT ROUND: 4 * 5 * 5 * 1 = 100 constraints - for q in 0..QUARTERS { - for x in 0..DIM { - for y in 0..DIM { - if x == 0 && y == 0 { - constraints.push(next_state(0, 0, 0, q) - g00(0, 0, 0, q)); - } else { - constraints.push(next_state(0, x, y, q) - state_f(0, x, y, q)); - } - } - } - } // END wiring - constraints } } From 4c7f074aa4504a8878e3d0efc63e2a56e4ba2a75 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 14 Sep 2023 18:16:10 +0200 Subject: [PATCH 026/173] simplify macro to need half less rows --- kimchi/src/circuits/gate.rs | 4 +- kimchi/src/circuits/polynomials/keccak.rs | 91 ++++++++--------------- 2 files changed, 33 insertions(+), 62 deletions(-) diff --git a/kimchi/src/circuits/gate.rs b/kimchi/src/circuits/gate.rs index b4fcdeaaaf..68b09b8150 100644 --- a/kimchi/src/circuits/gate.rs +++ b/kimchi/src/circuits/gate.rs @@ -112,7 +112,7 @@ pub enum GateType { // Gates for Keccak Xor16, Rot64, - Keccak, + KeccakRound, } /// Gate error @@ -325,7 +325,7 @@ impl CircuitGate { } GateType::Xor16 => xor::Xor16::constraint_checks(&env, &mut cache), GateType::Rot64 => rot::Rot64::constraint_checks(&env, &mut cache), - GateType::Keccak => keccak::Keccak::constraint_checks(&env, &mut cache), + GateType::KeccakRound => keccak::KeccakRound::constraint_checks(&env, &mut cache), }; // Check for failed constraints diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 9ab9ae1982..afac568898 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -12,16 +12,10 @@ pub const QUARTERS: usize = 4; #[macro_export] macro_rules! state_from_layout { - ($var:ident, $expr:expr) => { - let $var = $expr; - let $var = |i: usize, x: usize, y: usize, q: usize| { - $var[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() - }; - }; - ($var:ident) => { - let $var = |i: usize, x: usize, y: usize, q: usize| { - $var[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() - }; + ($expr:expr) => { + |i: usize, x: usize, y: usize, q: usize| { + $expr[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() + } }; } @@ -96,13 +90,13 @@ pub const RC: [u64; 24] = [ //~ | iota | g00 | state_f | //~ #[derive(Default)] -pub struct Keccak(PhantomData); +pub struct KeccakRound(PhantomData); -impl Argument for Keccak +impl Argument for KeccakRound where F: PrimeField, { - const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Keccak); + const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::KeccakRound); const CONSTRAINTS: u32 = 754; // Constraints for one round of the Keccak permutation function @@ -112,62 +106,39 @@ where // DEFINE ROUND CONSTANT let rc = [env.coeff(0), env.coeff(1), env.coeff(2), env.coeff(3)]; - // LOAD WITNESS LAYOUT + // LOAD STATES FROM WITNESS LAYOUT // THETA - let state_a = env.witness_curr_chunk(0, 100); - let state_c = env.witness_curr_chunk(100, 120); - let reset_c = env.witness_curr_chunk(120, 200); - let dense_c = env.witness_curr_chunk(200, 220); - let quotient_c = env.witness_curr_chunk(220, 240); - let remainder_c = env.witness_curr_chunk(240, 260); - let bound_c = env.witness_curr_chunk(260, 280); - let dense_rot_c = env.witness_curr_chunk(280, 300); - let expand_rot_c = env.witness_curr_chunk(300, 320); - let state_d = env.witness_curr_chunk(320, 340); - let state_e = env.witness_curr_chunk(340, 440); + let state_a = state_from_layout!(env.witness_curr_chunk(0, 100)); + let state_c = state_from_layout!(env.witness_curr_chunk(100, 120)); + let reset_c = state_from_layout!(env.witness_curr_chunk(120, 200)); + let dense_c = state_from_layout!(env.witness_curr_chunk(200, 220)); + let quotient_c = state_from_layout!(env.witness_curr_chunk(220, 240)); + let remainder_c = state_from_layout!(env.witness_curr_chunk(240, 260)); + let bound_c = state_from_layout!(env.witness_curr_chunk(260, 280)); + let dense_rot_c = state_from_layout!(env.witness_curr_chunk(280, 300)); + let expand_rot_c = state_from_layout!(env.witness_curr_chunk(300, 320)); + let state_d = state_from_layout!(env.witness_curr_chunk(320, 340)); + let state_e = state_from_layout!(env.witness_curr_chunk(340, 440)); // PI-RHO - let reset_e = env.witness_curr_chunk(440, 840); - let dense_e = env.witness_curr_chunk(840, 940); - let quotient_e = env.witness_curr_chunk(940, 1040); - let remainder_e = env.witness_curr_chunk(1040, 1140); - let bound_e = env.witness_curr_chunk(1140, 1240); - let dense_rot_e = env.witness_curr_chunk(1240, 1340); - let expand_rot_e = env.witness_curr_chunk(1340, 1440); - let state_b = env.witness_curr_chunk(1440, 1540); + let reset_e = state_from_layout!(env.witness_curr_chunk(440, 840)); + let dense_e = state_from_layout!(env.witness_curr_chunk(840, 940)); + let quotient_e = state_from_layout!(env.witness_curr_chunk(940, 1040)); + let remainder_e = state_from_layout!(env.witness_curr_chunk(1040, 1140)); + let bound_e = state_from_layout!(env.witness_curr_chunk(1140, 1240)); + let dense_rot_e = state_from_layout!(env.witness_curr_chunk(1240, 1340)); + let expand_rot_e = state_from_layout!(env.witness_curr_chunk(1340, 1440)); + let state_b = state_from_layout!(env.witness_curr_chunk(1440, 1540)); // CHI - let reset_b = env.witness_curr_chunk(1540, 1940); - let reset_sum = env.witness_curr_chunk(1940, 2340); + let reset_b = state_from_layout!(env.witness_curr_chunk(1540, 1940)); + let reset_sum = state_from_layout!(env.witness_curr_chunk(1940, 2340)); let mut state_f = env.witness_curr_chunk(2340, 2344); let mut tail = env.witness_next_chunk(4, 100); state_f.append(&mut tail); + let state_f = state_from_layout!(state_f); // IOTA let mut state_g = env.witness_next_chunk(0, 4); state_g.append(&mut tail); - - // LOAD STATES FROM LAYOUT - state_from_layout!(state_a); - state_from_layout!(state_c); - state_from_layout!(reset_c); - state_from_layout!(dense_c); - state_from_layout!(quotient_c); - state_from_layout!(remainder_c); - state_from_layout!(bound_c); - state_from_layout!(dense_rot_c); - state_from_layout!(expand_rot_c); - state_from_layout!(state_d); - state_from_layout!(state_e); - state_from_layout!(reset_e); - state_from_layout!(dense_e); - state_from_layout!(quotient_e); - state_from_layout!(remainder_e); - state_from_layout!(bound_e); - state_from_layout!(dense_rot_e); - state_from_layout!(expand_rot_e); - state_from_layout!(state_b); - state_from_layout!(reset_b); - state_from_layout!(reset_sum); - state_from_layout!(state_f); - state_from_layout!(state_g); + let state_g = state_from_layout!(state_g); // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { From 360468ba380588c77d478f9a4c1865107584a9d2 Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 18 Sep 2023 12:32:15 +0200 Subject: [PATCH 027/173] added constraints of KeccakSponge --- kimchi/src/circuits/gate.rs | 1 + kimchi/src/circuits/polynomials/keccak.rs | 79 +++++++++++++++++++++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/kimchi/src/circuits/gate.rs b/kimchi/src/circuits/gate.rs index 68b09b8150..ef89e4ff51 100644 --- a/kimchi/src/circuits/gate.rs +++ b/kimchi/src/circuits/gate.rs @@ -113,6 +113,7 @@ pub enum GateType { Xor16, Rot64, KeccakRound, + KeccakSponge, } /// Gate error diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index afac568898..7387af1f82 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -1,8 +1,11 @@ //! Keccak gadget -use crate::circuits::{ - argument::{Argument, ArgumentEnv, ArgumentType}, - expr::{constraints::ExprOps, Cache}, - gate::GateType, +use crate::{ + auto_clone, auto_clone_array, + circuits::{ + argument::{Argument, ArgumentEnv, ArgumentType}, + expr::{constraints::ExprOps, Cache}, + gate::GateType, + }, }; use ark_ff::PrimeField; use std::marker::PhantomData; @@ -137,6 +140,7 @@ where let state_f = state_from_layout!(state_f); // IOTA let mut state_g = env.witness_next_chunk(0, 4); + let mut tail = env.witness_next_chunk(4, 100); state_g.append(&mut tail); let state_g = state_from_layout!(state_g); @@ -222,6 +226,73 @@ where } } +//~ +//~ | `KeccakSponge` | [0...100) | [100...168) | [168...200) | [200...216) | [216...248] | [248...312) | +//~ | -------------- | --------- | ----------- | ----------- | ----------- | ----------- | ----------- | +//~ | Curr | old_state | new_block | zeros | dense | bytes | reset | +//~ | Next | xor_state | +//~ +#[derive(Default)] +pub struct KeccakSponge(PhantomData); + +impl Argument for KeccakSponge +where + F: PrimeField, +{ + const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::KeccakSponge); + const CONSTRAINTS: u32 = 148; + + // Constraints for one round of the Keccak permutation function + fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { + let mut constraints = vec![]; + + // LOAD WITNESS + let old_state = env.witness_curr_chunk(0, 100); + let mut new_block = env.witness_curr_chunk(100, 168); + let mut zeros = env.witness_curr_chunk(168, 200); + new_block.append(&mut zeros); + let xor_state = env.witness_next_chunk(0, 100); + let dense = env.witness_curr_chunk(200, 216); + let bytes = env.witness_curr_chunk(216, 248); + let reset = env.witness_curr_chunk(248, 312); + auto_clone_array!(old_state); + auto_clone_array!(new_block); + auto_clone_array!(xor_state); + auto_clone_array!(dense); + auto_clone_array!(bytes); + auto_clone_array!(reset); + + // LOAD COEFFICIENTS + let absorb = env.coeff(0); + let squeeze = env.coeff(1); + auto_clone!(absorb); + auto_clone!(squeeze); + + // STEP absorb: 5 * 5 * 4 = 100 constraints + for z in zeros { + constraints.push(absorb() * z); + } + for i in 0..QUARTERS * DIM * DIM { + constraints.push(absorb() * (xor_state(i) - (old_state(i) + new_block(i)))); + } + // STEP squeeze: 32 constraints + for i in 0..16 { + constraints + .push(squeeze() * (dense(i) - (bytes(2 * i) + T::two_pow(8) * bytes(2 * i + 1)))); + constraints.push( + squeeze() + * (old_state(i) + - (reset(4 * i) + + T::two_pow(1) * reset(4 * i + 1) + + T::two_pow(2) * reset(4 * i + 2) + + T::two_pow(3) * reset(4 * i + 3))), + ); + } + + constraints + } +} + fn compose_quarters>( quarters: impl Fn(usize, usize, usize, usize) -> T, x: usize, From 0c360e107f224482a9f661bfaac2da3205fb2696 Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 18 Sep 2023 12:42:08 +0200 Subject: [PATCH 028/173] add variants in all matches for this type --- kimchi/src/circuits/gate.rs | 6 +++++- kimchi/src/verifier.rs | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/gate.rs b/kimchi/src/circuits/gate.rs index ef89e4ff51..c7c2dea092 100644 --- a/kimchi/src/circuits/gate.rs +++ b/kimchi/src/circuits/gate.rs @@ -231,7 +231,10 @@ impl CircuitGate { Rot64 => self .verify_witness::(row, witness, &index.cs, public) .map_err(|e| e.to_string()), - Keccak => self + KeccakRound => self + .verify_witness::(row, witness, &index.cs, public) + .map_err(|e| e.to_string()), + KeccakSponge => self .verify_witness::(row, witness, &index.cs, public) .map_err(|e| e.to_string()), } @@ -327,6 +330,7 @@ impl CircuitGate { GateType::Xor16 => xor::Xor16::constraint_checks(&env, &mut cache), GateType::Rot64 => rot::Rot64::constraint_checks(&env, &mut cache), GateType::KeccakRound => keccak::KeccakRound::constraint_checks(&env, &mut cache), + GateType::KeccakSponge => keccak::KeccakSponge::constraint_checks(&env, &mut cache), }; // Check for failed constraints diff --git a/kimchi/src/verifier.rs b/kimchi/src/verifier.rs index bee5f00d66..1cd83b3ee7 100644 --- a/kimchi/src/verifier.rs +++ b/kimchi/src/verifier.rs @@ -83,7 +83,8 @@ impl<'a, G: KimchiCurve> Context<'a, G> { ForeignFieldMul => Some(self.verifier_index.foreign_field_mul_comm.as_ref()?), Xor16 => Some(self.verifier_index.xor_comm.as_ref()?), Rot64 => Some(self.verifier_index.rot_comm.as_ref()?), - Keccak => todo!(), + KeccakRound => todo!(), + KeccakSponge => todo!(), } } } From 94967f64f910bc0d1ed8bc609ae002d8d97afabe Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 21 Sep 2023 17:18:53 +0200 Subject: [PATCH 029/173] included all bytes in KeccakSponge layout to check composition at absorb layer --- kimchi/src/circuits/polynomials/keccak.rs | 51 ++++++++++++++--------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 7387af1f82..34e99aabfe 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -227,7 +227,7 @@ where } //~ -//~ | `KeccakSponge` | [0...100) | [100...168) | [168...200) | [200...216) | [216...248] | [248...312) | +//~ | `KeccakSponge` | [0...100) | [100...168) | [168...200) | [200...300) | [300...500] | [500...900) | //~ | -------------- | --------- | ----------- | ----------- | ----------- | ----------- | ----------- | //~ | Curr | old_state | new_block | zeros | dense | bytes | reset | //~ | Next | xor_state | @@ -240,7 +240,7 @@ where F: PrimeField, { const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::KeccakSponge); - const CONSTRAINTS: u32 = 148; + const CONSTRAINTS: u32 = 448; // Constraints for one round of the Keccak permutation function fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { @@ -252,9 +252,9 @@ where let mut zeros = env.witness_curr_chunk(168, 200); new_block.append(&mut zeros); let xor_state = env.witness_next_chunk(0, 100); - let dense = env.witness_curr_chunk(200, 216); - let bytes = env.witness_curr_chunk(216, 248); - let reset = env.witness_curr_chunk(248, 312); + let dense = env.witness_curr_chunk(200, 300); + let bytes = env.witness_curr_chunk(300, 500); + let reset = env.witness_curr_chunk(500, 900); auto_clone_array!(old_state); auto_clone_array!(new_block); auto_clone_array!(xor_state); @@ -263,30 +263,33 @@ where auto_clone_array!(reset); // LOAD COEFFICIENTS - let absorb = env.coeff(0); - let squeeze = env.coeff(1); + let root = env.coeff(0); + let absorb = env.coeff(1); + let squeeze = env.coeff(2); + auto_clone!(root); auto_clone!(absorb); auto_clone!(squeeze); - // STEP absorb: 5 * 5 * 4 = 100 constraints + // STEP absorb: 32 + 100 * 4 = 432 for z in zeros { + // Absorb phase pads with zeros the new state constraints.push(absorb() * z); } for i in 0..QUARTERS * DIM * DIM { + // In first absorb, root state is all zeros + constraints.push(root() * old_state(i)); + // Absorbs the new block by performing XOR with the old state constraints.push(absorb() * (xor_state(i) - (old_state(i) + new_block(i)))); + // Check resets correspond to the decomposition of the new state + constraints.push(absorb() * (new_block(i) - compose_shifts_from_vec(reset, i))); + // Both phases: check correctness of each dense term (16 bits) by composing two bytes + constraints.push(dense(i) - (bytes(2 * i) + T::two_pow(8) * bytes(2 * i + 1))); } - // STEP squeeze: 32 constraints + + // STEP squeeze: 16 constraints for i in 0..16 { - constraints - .push(squeeze() * (dense(i) - (bytes(2 * i) + T::two_pow(8) * bytes(2 * i + 1)))); - constraints.push( - squeeze() - * (old_state(i) - - (reset(4 * i) - + T::two_pow(1) * reset(4 * i + 1) - + T::two_pow(2) * reset(4 * i + 2) - + T::two_pow(3) * reset(4 * i + 3))), - ); + // Check resets correspond to the 256-bit prefix digest of the old state (current) + constraints.push(squeeze() * (old_state(i) - compose_shifts_from_vec(reset, i))); } constraints @@ -315,3 +318,13 @@ fn compose_shifts>( + T::two_pow(2) * resets(2, x, y, q) + T::two_pow(3) * resets(3, x, y, q) } + +fn compose_shifts_from_vec>( + resets: impl Fn(usize) -> T, + i: usize, +) -> T { + resets(4 * i) + + T::two_pow(1) * resets(4 * i + 1) + + T::two_pow(2) * resets(4 * i + 2) + + T::two_pow(3) * resets(4 * i + 3) +} From b4a94d86a68cadeb753e8e75fbea1e1ff49b5a5b Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 26 Sep 2023 13:43:59 +0200 Subject: [PATCH 030/173] rename resets for shifts --- kimchi/src/circuits/polynomials/keccak.rs | 64 +++++++++++------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 34e99aabfe..97d4306838 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -78,19 +78,19 @@ pub const RC: [u64; 24] = [ //~ //~ | Columns | [0...100) | [100...120) | [120...200) | [200...220) | [220...240) | [240...260) | [260...280) | [280...300) | [300...320) | [320...340) | [340...440) | //~ | -------- | --------- | ----------- | ----------- | ----------- | ----------- | ------------ | ----------- | ------------ | ------------ | ----------- | ----------- | -//~ | theta | state_a | state_c | reset_c | dense_c | quotient_c | remainder_c | bound_c | dense_rot_c | expand_rot_c | state_d | state_e | +//~ | theta | state_a | state_c | shifts_c | dense_c | quotient_c | remainder_c | bound_c | dense_rot_c | expand_rot_c | state_d | state_e | //~ //~ | Columns | [440...840) | [840...940) | [940...1040) | [1040...1140) | [1140...1240) | [1240...1340) | [1340...1440) | [1440...1540) | //~ | -------- | ----------- | ----------- | ------------ | ------------- | ------------- | ------------- | ------------- | ------------- | -//~ | pirho | reset_e | dense_e | quotient_e | remainder_e | bound_e | dense_rot_e | expand_rot_e | state_b | +//~ | pirho | shifts_e | dense_e | quotient_e | remainder_e | bound_e | dense_rot_e | expand_rot_e | state_b | //~ //~ | Columns | [1540...1940) | [1940...2340) | [2340...2344 | //~ | -------- | ------------- | ------------- | ------------ | -//~ | chi | reset_b | reset_sum | f00 | +//~ | chi | shifts_b | shifts_sum | f00 | //~ //~ | Columns | [0...4) | [4...100) | //~ | -------- | ------- | --------- | -//~ | iota | g00 | state_f | +//~ | iota | g00 | rest_g | //~ #[derive(Default)] pub struct KeccakRound(PhantomData); @@ -113,7 +113,7 @@ where // THETA let state_a = state_from_layout!(env.witness_curr_chunk(0, 100)); let state_c = state_from_layout!(env.witness_curr_chunk(100, 120)); - let reset_c = state_from_layout!(env.witness_curr_chunk(120, 200)); + let shifts_c = state_from_layout!(env.witness_curr_chunk(120, 200)); let dense_c = state_from_layout!(env.witness_curr_chunk(200, 220)); let quotient_c = state_from_layout!(env.witness_curr_chunk(220, 240)); let remainder_c = state_from_layout!(env.witness_curr_chunk(240, 260)); @@ -123,7 +123,7 @@ where let state_d = state_from_layout!(env.witness_curr_chunk(320, 340)); let state_e = state_from_layout!(env.witness_curr_chunk(340, 440)); // PI-RHO - let reset_e = state_from_layout!(env.witness_curr_chunk(440, 840)); + let shifts_e = state_from_layout!(env.witness_curr_chunk(440, 840)); let dense_e = state_from_layout!(env.witness_curr_chunk(840, 940)); let quotient_e = state_from_layout!(env.witness_curr_chunk(940, 1040)); let remainder_e = state_from_layout!(env.witness_curr_chunk(1040, 1140)); @@ -132,8 +132,8 @@ where let expand_rot_e = state_from_layout!(env.witness_curr_chunk(1340, 1440)); let state_b = state_from_layout!(env.witness_curr_chunk(1440, 1540)); // CHI - let reset_b = state_from_layout!(env.witness_curr_chunk(1540, 1940)); - let reset_sum = state_from_layout!(env.witness_curr_chunk(1940, 2340)); + let shifts_b = state_from_layout!(env.witness_curr_chunk(1540, 1940)); + let shifts_sum = state_from_layout!(env.witness_curr_chunk(1940, 2340)); let mut state_f = env.witness_curr_chunk(2340, 2344); let mut tail = env.witness_next_chunk(4, 100); state_f.append(&mut tail); @@ -165,10 +165,10 @@ where + state_a(0, x, 3, q) + state_a(0, x, 4, q)), ); - constraints.push(state_c(0, x, 0, q) - compose_shifts(reset_c, x, 0, q)); + constraints.push(state_c(0, x, 0, q) - compose_shifts(shifts_c, x, 0, q)); constraints.push( state_d(0, x, 0, q) - - (reset_c(0, (x - 1 + DIM) % DIM, 0, q) + - (shifts_c(0, (x - 1 + DIM) % DIM, 0, q) + expand_rot_c(0, (x + 1) % DIM, 0, q)), ); @@ -195,7 +195,7 @@ where constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(*off))); for q in 0..QUARTERS { - constraints.push(state_e(0, x, y, q) - compose_shifts(reset_e, x, y, q)); + constraints.push(state_e(0, x, y, q) - compose_shifts(shifts_e, x, y, q)); constraints .push(state_b(0, y, (2 * x + 3 * y) % DIM, q) - expand_rot_e(0, x, y, q)); } @@ -207,12 +207,12 @@ where for x in 0..DIM { for y in 0..DIM { let not = - T::literal(F::from(0x1111111111111111u64)) - reset_b(0, (x + 1) % 5, y, q); - let sum = not + reset_b(1, (x + 2) % 5, y, q); - let and = reset_sum(1, x, y, q); - constraints.push(state_b(0, x, y, q) - compose_shifts(reset_b, x, y, q)); - constraints.push(sum - compose_shifts(reset_sum, x, y, q)); - constraints.push(state_f(0, x, y, q) - (reset_b(0, x, y, q) + and)); + T::literal(F::from(0x1111111111111111u64)) - shifts_b(0, (x + 1) % 5, y, q); + let sum = not + shifts_b(1, (x + 2) % 5, y, q); + let and = shifts_sum(1, x, y, q); + constraints.push(state_b(0, x, y, q) - compose_shifts(shifts_b, x, y, q)); + constraints.push(sum - compose_shifts(shifts_sum, x, y, q)); + constraints.push(state_f(0, x, y, q) - (shifts_b(0, x, y, q) + and)); } } } // END chi @@ -229,7 +229,7 @@ where //~ //~ | `KeccakSponge` | [0...100) | [100...168) | [168...200) | [200...300) | [300...500] | [500...900) | //~ | -------------- | --------- | ----------- | ----------- | ----------- | ----------- | ----------- | -//~ | Curr | old_state | new_block | zeros | dense | bytes | reset | +//~ | Curr | old_state | new_block | zeros | dense | bytes | shifts | //~ | Next | xor_state | //~ #[derive(Default)] @@ -254,13 +254,13 @@ where let xor_state = env.witness_next_chunk(0, 100); let dense = env.witness_curr_chunk(200, 300); let bytes = env.witness_curr_chunk(300, 500); - let reset = env.witness_curr_chunk(500, 900); + let shifts = env.witness_curr_chunk(500, 900); auto_clone_array!(old_state); auto_clone_array!(new_block); auto_clone_array!(xor_state); auto_clone_array!(dense); auto_clone_array!(bytes); - auto_clone_array!(reset); + auto_clone_array!(shifts); // LOAD COEFFICIENTS let root = env.coeff(0); @@ -281,7 +281,7 @@ where // Absorbs the new block by performing XOR with the old state constraints.push(absorb() * (xor_state(i) - (old_state(i) + new_block(i)))); // Check resets correspond to the decomposition of the new state - constraints.push(absorb() * (new_block(i) - compose_shifts_from_vec(reset, i))); + constraints.push(absorb() * (new_block(i) - compose_shifts_from_vec(shifts, i))); // Both phases: check correctness of each dense term (16 bits) by composing two bytes constraints.push(dense(i) - (bytes(2 * i) + T::two_pow(8) * bytes(2 * i + 1))); } @@ -289,7 +289,7 @@ where // STEP squeeze: 16 constraints for i in 0..16 { // Check resets correspond to the 256-bit prefix digest of the old state (current) - constraints.push(squeeze() * (old_state(i) - compose_shifts_from_vec(reset, i))); + constraints.push(squeeze() * (old_state(i) - compose_shifts_from_vec(shifts, i))); } constraints @@ -308,23 +308,23 @@ fn compose_quarters>( } fn compose_shifts>( - resets: impl Fn(usize, usize, usize, usize) -> T, + shifts: impl Fn(usize, usize, usize, usize) -> T, x: usize, y: usize, q: usize, ) -> T { - resets(0, x, y, q) - + T::two_pow(1) * resets(1, x, y, q) - + T::two_pow(2) * resets(2, x, y, q) - + T::two_pow(3) * resets(3, x, y, q) + shifts(0, x, y, q) + + T::two_pow(1) * shifts(1, x, y, q) + + T::two_pow(2) * shifts(2, x, y, q) + + T::two_pow(3) * shifts(3, x, y, q) } fn compose_shifts_from_vec>( - resets: impl Fn(usize) -> T, + shifts: impl Fn(usize) -> T, i: usize, ) -> T { - resets(4 * i) - + T::two_pow(1) * resets(4 * i + 1) - + T::two_pow(2) * resets(4 * i + 2) - + T::two_pow(3) * resets(4 * i + 3) + shifts(4 * i) + + T::two_pow(1) * shifts(4 * i + 1) + + T::two_pow(2) * shifts(4 * i + 2) + + T::two_pow(3) * shifts(4 * i + 3) } From e1b318a338747b9b1b114e4c5b98a40f3009a701 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 26 Sep 2023 13:45:47 +0200 Subject: [PATCH 031/173] update comments --- kimchi/src/circuits/polynomials/keccak.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 97d4306838..c153d35203 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -280,7 +280,7 @@ where constraints.push(root() * old_state(i)); // Absorbs the new block by performing XOR with the old state constraints.push(absorb() * (xor_state(i) - (old_state(i) + new_block(i)))); - // Check resets correspond to the decomposition of the new state + // Check shifts correspond to the decomposition of the new state constraints.push(absorb() * (new_block(i) - compose_shifts_from_vec(shifts, i))); // Both phases: check correctness of each dense term (16 bits) by composing two bytes constraints.push(dense(i) - (bytes(2 * i) + T::two_pow(8) * bytes(2 * i + 1))); @@ -288,7 +288,7 @@ where // STEP squeeze: 16 constraints for i in 0..16 { - // Check resets correspond to the 256-bit prefix digest of the old state (current) + // Check shifts correspond to the 256-bit prefix digest of the old state (current) constraints.push(squeeze() * (old_state(i) - compose_shifts_from_vec(shifts, i))); } From c593da78417a2bc27ead49cf27feb9eb384657b0 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Oct 2023 13:10:29 +0200 Subject: [PATCH 032/173] update fixes after testing --- kimchi/src/circuits/polynomials/keccak.rs | 65 ++++++++++++----------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index c153d35203..3dc4da7d4a 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -14,7 +14,7 @@ pub const DIM: usize = 5; pub const QUARTERS: usize = 4; #[macro_export] -macro_rules! state_from_layout { +macro_rules! state_from_vec { ($expr:expr) => { |i: usize, x: usize, y: usize, q: usize| { $expr[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() @@ -30,12 +30,13 @@ macro_rules! state_from_layout { /// | 2 | 62 | 6 | 43 | 15 | 61 | /// | 3 | 28 | 55 | 25 | 21 | 56 | /// | 4 | 27 | 20 | 39 | 8 | 14 | -pub const OFF: [[u64; DIM]; DIM] = [ - [0, 36, 3, 41, 18], - [1, 44, 10, 45, 2], - [62, 6, 43, 15, 61], - [28, 55, 25, 21, 56], - [27, 20, 39, 8, 14], +/// Note that the order of the indexing is [y][x] to match the encoding of the witness algorithm +pub(crate) const OFF: [[u64; DIM]; DIM] = [ + [0, 1, 62, 28, 27], + [36, 44, 6, 55, 20], + [3, 10, 43, 25, 39], + [41, 45, 15, 21, 8], + [18, 2, 61, 56, 14], ]; pub const RC: [u64; 24] = [ @@ -111,38 +112,38 @@ where // LOAD STATES FROM WITNESS LAYOUT // THETA - let state_a = state_from_layout!(env.witness_curr_chunk(0, 100)); - let state_c = state_from_layout!(env.witness_curr_chunk(100, 120)); - let shifts_c = state_from_layout!(env.witness_curr_chunk(120, 200)); - let dense_c = state_from_layout!(env.witness_curr_chunk(200, 220)); - let quotient_c = state_from_layout!(env.witness_curr_chunk(220, 240)); - let remainder_c = state_from_layout!(env.witness_curr_chunk(240, 260)); - let bound_c = state_from_layout!(env.witness_curr_chunk(260, 280)); - let dense_rot_c = state_from_layout!(env.witness_curr_chunk(280, 300)); - let expand_rot_c = state_from_layout!(env.witness_curr_chunk(300, 320)); - let state_d = state_from_layout!(env.witness_curr_chunk(320, 340)); - let state_e = state_from_layout!(env.witness_curr_chunk(340, 440)); + let state_a = state_from_vec!(env.witness_curr_chunk(0, 100)); + let state_c = state_from_vec!(env.witness_curr_chunk(100, 120)); + let shifts_c = state_from_vec!(env.witness_curr_chunk(120, 200)); + let dense_c = state_from_vec!(env.witness_curr_chunk(200, 220)); + let quotient_c = state_from_vec!(env.witness_curr_chunk(220, 240)); + let remainder_c = state_from_vec!(env.witness_curr_chunk(240, 260)); + let bound_c = state_from_vec!(env.witness_curr_chunk(260, 280)); + let dense_rot_c = state_from_vec!(env.witness_curr_chunk(280, 300)); + let expand_rot_c = state_from_vec!(env.witness_curr_chunk(300, 320)); + let state_d = state_from_vec!(env.witness_curr_chunk(320, 340)); + let state_e = state_from_vec!(env.witness_curr_chunk(340, 440)); // PI-RHO - let shifts_e = state_from_layout!(env.witness_curr_chunk(440, 840)); - let dense_e = state_from_layout!(env.witness_curr_chunk(840, 940)); - let quotient_e = state_from_layout!(env.witness_curr_chunk(940, 1040)); - let remainder_e = state_from_layout!(env.witness_curr_chunk(1040, 1140)); - let bound_e = state_from_layout!(env.witness_curr_chunk(1140, 1240)); - let dense_rot_e = state_from_layout!(env.witness_curr_chunk(1240, 1340)); - let expand_rot_e = state_from_layout!(env.witness_curr_chunk(1340, 1440)); - let state_b = state_from_layout!(env.witness_curr_chunk(1440, 1540)); + let shifts_e = state_from_vec!(env.witness_curr_chunk(440, 840)); + let dense_e = state_from_vec!(env.witness_curr_chunk(840, 940)); + let quotient_e = state_from_vec!(env.witness_curr_chunk(940, 1040)); + let remainder_e = state_from_vec!(env.witness_curr_chunk(1040, 1140)); + let bound_e = state_from_vec!(env.witness_curr_chunk(1140, 1240)); + let dense_rot_e = state_from_vec!(env.witness_curr_chunk(1240, 1340)); + let expand_rot_e = state_from_vec!(env.witness_curr_chunk(1340, 1440)); + let state_b = state_from_vec!(env.witness_curr_chunk(1440, 1540)); // CHI - let shifts_b = state_from_layout!(env.witness_curr_chunk(1540, 1940)); - let shifts_sum = state_from_layout!(env.witness_curr_chunk(1940, 2340)); + let shifts_b = state_from_vec!(env.witness_curr_chunk(1540, 1940)); + let shifts_sum = state_from_vec!(env.witness_curr_chunk(1940, 2340)); let mut state_f = env.witness_curr_chunk(2340, 2344); let mut tail = env.witness_next_chunk(4, 100); state_f.append(&mut tail); - let state_f = state_from_layout!(state_f); + let state_f = state_from_vec!(state_f); // IOTA let mut state_g = env.witness_next_chunk(0, 4); let mut tail = env.witness_next_chunk(4, 100); state_g.append(&mut tail); - let state_g = state_from_layout!(state_g); + let state_g = state_from_vec!(state_g); // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { @@ -180,8 +181,8 @@ where } // END theta // STEP pirho: 5 * 5 * (3 + 4 * 2) = 275 constraints - for (x, row) in OFF.iter().enumerate() { - for (y, off) in row.iter().enumerate() { + for (y, col) in OFF.iter().enumerate() { + for (x, off) in col.iter().enumerate() { let word_e = compose_quarters(dense_e, x, y); let quo_e = compose_quarters(quotient_e, x, y); let rem_e = compose_quarters(remainder_e, x, y); From 8caedced336a44e5deeb56caf1531af715c5a22f Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Oct 2023 10:44:16 +0200 Subject: [PATCH 033/173] fix bug found during testing --- kimchi/src/circuits/polynomials/keccak.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 3dc4da7d4a..337b29381f 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -169,7 +169,7 @@ where constraints.push(state_c(0, x, 0, q) - compose_shifts(shifts_c, x, 0, q)); constraints.push( state_d(0, x, 0, q) - - (shifts_c(0, (x - 1 + DIM) % DIM, 0, q) + - (shifts_c(0, (x + DIM - 1) % DIM, 0, q) + expand_rot_c(0, (x + 1) % DIM, 0, q)), ); From 431d562e913daefb323b9fd4b35d93b1efba9f6e Mon Sep 17 00:00:00 2001 From: querolita Date: Sat, 7 Oct 2023 12:04:01 +0200 Subject: [PATCH 034/173] add support for 10*1 padding in gate --- kimchi/src/circuits/argument.rs | 9 ++++++ kimchi/src/circuits/polynomials/keccak.rs | 34 +++++++++++------------ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/kimchi/src/circuits/argument.rs b/kimchi/src/circuits/argument.rs index f74a87c5ba..ce9058b997 100644 --- a/kimchi/src/circuits/argument.rs +++ b/kimchi/src/circuits/argument.rs @@ -104,6 +104,15 @@ impl> ArgumentEnv { T::coeff(idx, self.data.as_ref()) } + /// Chunk of consecutive coefficients in an interval [from, to) + pub fn coeff_chunk(&self, from: usize, to: usize) -> Vec { + let mut chunk = Vec::with_capacity(to - from); + for i in from..to { + chunk.push(self.coeff(i)); + } + chunk + } + /// Constant value (see [ConstantExpr] for supported constants) pub fn constant(&self, expr: ConstantExpr) -> T { T::constant(expr, self.data.as_ref()) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index 337b29381f..beaea37297 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -228,9 +228,9 @@ where } //~ -//~ | `KeccakSponge` | [0...100) | [100...168) | [168...200) | [200...300) | [300...500] | [500...900) | -//~ | -------------- | --------- | ----------- | ----------- | ----------- | ----------- | ----------- | -//~ | Curr | old_state | new_block | zeros | dense | bytes | shifts | +//~ | `KeccakSponge` | [0...100) | [100...168) | [168...200) | [200...400] | [400...800) | +//~ | -------------- | --------- | ----------- | ----------- | ----------- | ----------- | +//~ | Curr | old_state | new_block | zeros | bytes | shifts | //~ | Next | xor_state | //~ #[derive(Default)] @@ -241,7 +241,7 @@ where F: PrimeField, { const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::KeccakSponge); - const CONSTRAINTS: u32 = 448; + const CONSTRAINTS: u32 = 568; // Constraints for one round of the Keccak permutation function fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { @@ -253,13 +253,11 @@ where let mut zeros = env.witness_curr_chunk(168, 200); new_block.append(&mut zeros); let xor_state = env.witness_next_chunk(0, 100); - let dense = env.witness_curr_chunk(200, 300); - let bytes = env.witness_curr_chunk(300, 500); - let shifts = env.witness_curr_chunk(500, 900); + let bytes = env.witness_curr_chunk(200, 400); + let shifts = env.witness_curr_chunk(400, 800); auto_clone_array!(old_state); auto_clone_array!(new_block); auto_clone_array!(xor_state); - auto_clone_array!(dense); auto_clone_array!(bytes); auto_clone_array!(shifts); @@ -267,11 +265,15 @@ where let root = env.coeff(0); let absorb = env.coeff(1); let squeeze = env.coeff(2); + let flags = env.coeff_chunk(4, 140); + let pad = env.coeff_chunk(200, 336); auto_clone!(root); auto_clone!(absorb); auto_clone!(squeeze); + auto_clone_array!(flags); + auto_clone_array!(pad); - // STEP absorb: 32 + 100 * 4 = 432 + // 32 + 100 * 4 + 136 = 568 for z in zeros { // Absorb phase pads with zeros the new state constraints.push(absorb() * z); @@ -281,17 +283,15 @@ where constraints.push(root() * old_state(i)); // Absorbs the new block by performing XOR with the old state constraints.push(absorb() * (xor_state(i) - (old_state(i) + new_block(i)))); - // Check shifts correspond to the decomposition of the new state + // In absorb, Check shifts correspond to the decomposition of the new state constraints.push(absorb() * (new_block(i) - compose_shifts_from_vec(shifts, i))); - // Both phases: check correctness of each dense term (16 bits) by composing two bytes - constraints.push(dense(i) - (bytes(2 * i) + T::two_pow(8) * bytes(2 * i + 1))); - } - - // STEP squeeze: 16 constraints - for i in 0..16 { - // Check shifts correspond to the 256-bit prefix digest of the old state (current) + // In squeeze, Check shifts correspond to the 256-bit prefix digest of the old state (current) constraints.push(squeeze() * (old_state(i) - compose_shifts_from_vec(shifts, i))); } + for i in 0..136 { + // Check padding + constraints.push(flags(i) * (pad(i) - bytes(i))); + } constraints } From fafd5be281144ae20c2d39218b8c7c4949408cce Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 18 Sep 2023 13:57:25 +0200 Subject: [PATCH 035/173] interface of circuit including sponge and permutation --- kimchi/src/circuits/polynomials/keccak.rs | 66 ++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index beaea37297..d1f4b0e7fc 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -4,14 +4,17 @@ use crate::{ circuits::{ argument::{Argument, ArgumentEnv, ArgumentType}, expr::{constraints::ExprOps, Cache}, - gate::GateType, + gate::{CircuitGate, GateType}, + wires::Wire, }, }; -use ark_ff::PrimeField; +use ark_ff::{PrimeField, SquareRootField}; use std::marker::PhantomData; pub const DIM: usize = 5; pub const QUARTERS: usize = 4; +pub const ROUNDS: usize = 24; +pub const RATE: usize = 136; #[macro_export] macro_rules! state_from_vec { @@ -66,6 +69,65 @@ pub const RC: [u64; 24] = [ 0x8000000080008008, ]; +fn expand>(word: u64) -> Vec { + format!("{:064b}", word) + .chars() + .collect::>() + .chunks(16) + .map(|c| c.iter().collect::()) + .collect::>() + .iter() + .map(|c| T::literal(F::from(u64::from_str_radix(c, 16).unwrap()))) + .collect::>() +} + +impl CircuitGate { + /// Extends a Keccak circuit to hash one message (already padded to a multiple of 136 bits with 10*1 rule) + pub fn extend_keccak(circuit: &mut Vec, bytelength: usize) -> usize { + // pad + let mut gates = Self::create_keccak(circuit.len(), bytelength); + circuit.append(&mut gates); + circuit.len() + } + + /// Creates a Keccak256 circuit, capacity 512 bits, rate 1088 bits, for a padded message of a given bytelength + fn create_keccak(new_row: usize, bytelength: usize) -> Vec { + let mut gates = vec![]; + for _block in 0..(bytelength / RATE) { + gates.push(Self::create_keccak_absorb(new_row + gates.len())); + for round in 0..ROUNDS { + gates.push(Self::create_keccak_round(new_row + gates.len(), round)); + } + } + gates.push(Self::create_keccak_squeeze(new_row + gates.len())); + gates + } + + fn create_keccak_squeeze(new_row: usize) -> Self { + CircuitGate { + typ: GateType::KeccakSponge, + wires: Wire::for_row(new_row), + coeffs: vec![F::zero(), F::one()], + } + } + + fn create_keccak_absorb(new_row: usize) -> Self { + CircuitGate { + typ: GateType::KeccakSponge, + wires: Wire::for_row(new_row), + coeffs: vec![F::one(), F::zero()], + } + } + + fn create_keccak_round(new_row: usize, round: usize) -> Self { + CircuitGate { + typ: GateType::KeccakRound, + wires: Wire::for_row(new_row), + coeffs: expand(RC[round]), + } + } +} + //~ //~ | `KeccakRound` | [0...440) | [440...1540) | [1540...2344) | //~ | ------------- | --------- | ------------ | ------------- | From 2460a523b675fe7b9c651fa0e8e46af44b191331 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 19 Sep 2023 14:32:00 +0200 Subject: [PATCH 036/173] reorganize the code into individual files --- kimchi/src/circuits/gate.rs | 18 +-- .../{keccak.rs => keccak/circuitgates.rs} | 110 ++---------------- .../src/circuits/polynomials/keccak/gadget.rs | 55 +++++++++ kimchi/src/circuits/polynomials/keccak/mod.rs | 50 ++++++++ 4 files changed, 123 insertions(+), 110 deletions(-) rename kimchi/src/circuits/polynomials/{keccak.rs => keccak/circuitgates.rs} (80%) create mode 100644 kimchi/src/circuits/polynomials/keccak/gadget.rs create mode 100644 kimchi/src/circuits/polynomials/keccak/mod.rs diff --git a/kimchi/src/circuits/gate.rs b/kimchi/src/circuits/gate.rs index c7c2dea092..3a61737f30 100644 --- a/kimchi/src/circuits/gate.rs +++ b/kimchi/src/circuits/gate.rs @@ -5,8 +5,8 @@ use crate::{ argument::{Argument, ArgumentEnv}, constraints::ConstraintSystem, polynomials::{ - complete_add, endomul_scalar, endosclmul, foreign_field_add, foreign_field_mul, - poseidon, range_check, turshi, varbasemul, + complete_add, endomul_scalar, endosclmul, foreign_field_add, foreign_field_mul, keccak, + poseidon, range_check, rot, turshi, varbasemul, xor, }, wires::*, }, @@ -21,11 +21,7 @@ use serde_with::serde_as; use std::io::{Result as IoResult, Write}; use thiserror::Error; -use super::{ - argument::ArgumentWitness, - expr, - polynomials::{keccak, rot, xor}, -}; +use super::{argument::ArgumentWitness, expr}; /// A row accessible from a given row, corresponds to the fact that we open all polynomials /// at `zeta` **and** `omega * zeta`. @@ -329,8 +325,12 @@ impl CircuitGate { } GateType::Xor16 => xor::Xor16::constraint_checks(&env, &mut cache), GateType::Rot64 => rot::Rot64::constraint_checks(&env, &mut cache), - GateType::KeccakRound => keccak::KeccakRound::constraint_checks(&env, &mut cache), - GateType::KeccakSponge => keccak::KeccakSponge::constraint_checks(&env, &mut cache), + GateType::KeccakRound => { + keccak::circuitgates::KeccakRound::constraint_checks(&env, &mut cache) + } + GateType::KeccakSponge => { + keccak::circuitgates::KeccakSponge::constraint_checks(&env, &mut cache) + } }; // Check for failed constraints diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs similarity index 80% rename from kimchi/src/circuits/polynomials/keccak.rs rename to kimchi/src/circuits/polynomials/keccak/circuitgates.rs index d1f4b0e7fc..4988a5aee0 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs @@ -1,21 +1,16 @@ //! Keccak gadget +use super::{DIM, QUARTERS}; use crate::{ auto_clone, auto_clone_array, circuits::{ argument::{Argument, ArgumentEnv, ArgumentType}, expr::{constraints::ExprOps, Cache}, - gate::{CircuitGate, GateType}, - wires::Wire, + gate::GateType, }, }; -use ark_ff::{PrimeField, SquareRootField}; +use ark_ff::PrimeField; use std::marker::PhantomData; -pub const DIM: usize = 5; -pub const QUARTERS: usize = 4; -pub const ROUNDS: usize = 24; -pub const RATE: usize = 136; - #[macro_export] macro_rules! state_from_vec { ($expr:expr) => { @@ -33,101 +28,14 @@ macro_rules! state_from_vec { /// | 2 | 62 | 6 | 43 | 15 | 61 | /// | 3 | 28 | 55 | 25 | 21 | 56 | /// | 4 | 27 | 20 | 39 | 8 | 14 | -/// Note that the order of the indexing is [y][x] to match the encoding of the witness algorithm -pub(crate) const OFF: [[u64; DIM]; DIM] = [ - [0, 1, 62, 28, 27], - [36, 44, 6, 55, 20], - [3, 10, 43, 25, 39], - [41, 45, 15, 21, 8], - [18, 2, 61, 56, 14], -]; - -pub const RC: [u64; 24] = [ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808a, - 0x8000000080008000, - 0x000000000000808b, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008a, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000a, - 0x000000008000808b, - 0x800000000000008b, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800a, - 0x800000008000000a, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, +const OFF: [[u64; DIM]; DIM] = [ + [0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14], ]; -fn expand>(word: u64) -> Vec { - format!("{:064b}", word) - .chars() - .collect::>() - .chunks(16) - .map(|c| c.iter().collect::()) - .collect::>() - .iter() - .map(|c| T::literal(F::from(u64::from_str_radix(c, 16).unwrap()))) - .collect::>() -} - -impl CircuitGate { - /// Extends a Keccak circuit to hash one message (already padded to a multiple of 136 bits with 10*1 rule) - pub fn extend_keccak(circuit: &mut Vec, bytelength: usize) -> usize { - // pad - let mut gates = Self::create_keccak(circuit.len(), bytelength); - circuit.append(&mut gates); - circuit.len() - } - - /// Creates a Keccak256 circuit, capacity 512 bits, rate 1088 bits, for a padded message of a given bytelength - fn create_keccak(new_row: usize, bytelength: usize) -> Vec { - let mut gates = vec![]; - for _block in 0..(bytelength / RATE) { - gates.push(Self::create_keccak_absorb(new_row + gates.len())); - for round in 0..ROUNDS { - gates.push(Self::create_keccak_round(new_row + gates.len(), round)); - } - } - gates.push(Self::create_keccak_squeeze(new_row + gates.len())); - gates - } - - fn create_keccak_squeeze(new_row: usize) -> Self { - CircuitGate { - typ: GateType::KeccakSponge, - wires: Wire::for_row(new_row), - coeffs: vec![F::zero(), F::one()], - } - } - - fn create_keccak_absorb(new_row: usize) -> Self { - CircuitGate { - typ: GateType::KeccakSponge, - wires: Wire::for_row(new_row), - coeffs: vec![F::one(), F::zero()], - } - } - - fn create_keccak_round(new_row: usize, round: usize) -> Self { - CircuitGate { - typ: GateType::KeccakRound, - wires: Wire::for_row(new_row), - coeffs: expand(RC[round]), - } - } -} - //~ //~ | `KeccakRound` | [0...440) | [440...1540) | [1540...2344) | //~ | ------------- | --------- | ------------ | ------------- | diff --git a/kimchi/src/circuits/polynomials/keccak/gadget.rs b/kimchi/src/circuits/polynomials/keccak/gadget.rs new file mode 100644 index 0000000000..f3a9d59694 --- /dev/null +++ b/kimchi/src/circuits/polynomials/keccak/gadget.rs @@ -0,0 +1,55 @@ +//! Keccak gadget +use crate::circuits::{ + gate::{CircuitGate, GateType}, + wires::Wire, +}; +use ark_ff::{PrimeField, SquareRootField}; + +use super::{expand, RATE, RC, ROUNDS}; + +impl CircuitGate { + /// Extends a Keccak circuit to hash one message (already padded to a multiple of 136 bits with 10*1 rule) + pub fn extend_keccak(circuit: &mut Vec, bytelength: usize) -> usize { + // pad + let mut gates = Self::create_keccak(circuit.len(), bytelength); + circuit.append(&mut gates); + circuit.len() + } + + /// Creates a Keccak256 circuit, capacity 512 bits, rate 1088 bits, for a padded message of a given bytelength + fn create_keccak(new_row: usize, bytelength: usize) -> Vec { + let mut gates = vec![]; + for _block in 0..(bytelength / RATE) { + gates.push(Self::create_keccak_absorb(new_row + gates.len())); + for round in 0..ROUNDS { + gates.push(Self::create_keccak_round(new_row + gates.len(), round)); + } + } + gates.push(Self::create_keccak_squeeze(new_row + gates.len())); + gates + } + + fn create_keccak_squeeze(new_row: usize) -> Self { + CircuitGate { + typ: GateType::KeccakSponge, + wires: Wire::for_row(new_row), + coeffs: vec![F::zero(), F::one()], + } + } + + fn create_keccak_absorb(new_row: usize) -> Self { + CircuitGate { + typ: GateType::KeccakSponge, + wires: Wire::for_row(new_row), + coeffs: vec![F::one(), F::zero()], + } + } + + fn create_keccak_round(new_row: usize, round: usize) -> Self { + CircuitGate { + typ: GateType::KeccakRound, + wires: Wire::for_row(new_row), + coeffs: expand(RC[round]), + } + } +} diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs new file mode 100644 index 0000000000..339e63f175 --- /dev/null +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -0,0 +1,50 @@ +//! Keccak hash module +pub mod circuitgates; +pub mod gadget; + +pub const DIM: usize = 5; +pub const QUARTERS: usize = 4; +pub const ROUNDS: usize = 24; +pub const RATE: usize = 136; + +use crate::circuits::expr::constraints::ExprOps; +use ark_ff::PrimeField; + +pub const RC: [u64; 24] = [ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808a, + 0x8000000080008000, + 0x000000000000808b, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008a, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000a, + 0x000000008000808b, + 0x800000000000008b, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800a, + 0x800000008000000a, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +]; + +pub(crate) fn expand>(word: u64) -> Vec { + format!("{:064b}", word) + .chars() + .collect::>() + .chunks(16) + .map(|c| c.iter().collect::()) + .collect::>() + .iter() + .map(|c| T::literal(F::from(u64::from_str_radix(c, 16).unwrap()))) + .collect::>() +} From 448023e2e0486202cb1667fff0ebcdd79d71148d Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 21 Sep 2023 17:22:04 +0200 Subject: [PATCH 037/173] add gadget to create a root sponge gate at the beginning of the hashing --- .../src/circuits/polynomials/keccak/gadget.rs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/gadget.rs b/kimchi/src/circuits/polynomials/keccak/gadget.rs index f3a9d59694..4cfa3f8c3d 100644 --- a/kimchi/src/circuits/polynomials/keccak/gadget.rs +++ b/kimchi/src/circuits/polynomials/keccak/gadget.rs @@ -19,8 +19,12 @@ impl CircuitGate { /// Creates a Keccak256 circuit, capacity 512 bits, rate 1088 bits, for a padded message of a given bytelength fn create_keccak(new_row: usize, bytelength: usize) -> Vec { let mut gates = vec![]; - for _block in 0..(bytelength / RATE) { - gates.push(Self::create_keccak_absorb(new_row + gates.len())); + for block in 0..(bytelength / RATE) { + if block == 0 { + gates.push(Self::create_keccak_root(new_row + gates.len())); + } else { + gates.push(Self::create_keccak_absorb(new_row + gates.len())); + } for round in 0..ROUNDS { gates.push(Self::create_keccak_round(new_row + gates.len(), round)); } @@ -33,7 +37,7 @@ impl CircuitGate { CircuitGate { typ: GateType::KeccakSponge, wires: Wire::for_row(new_row), - coeffs: vec![F::zero(), F::one()], + coeffs: vec![F::zero(), F::zero(), F::one()], } } @@ -41,7 +45,15 @@ impl CircuitGate { CircuitGate { typ: GateType::KeccakSponge, wires: Wire::for_row(new_row), - coeffs: vec![F::one(), F::zero()], + coeffs: vec![F::zero(), F::one(), F::zero()], + } + } + + fn create_keccak_root(new_row: usize) -> Self { + CircuitGate { + typ: GateType::KeccakSponge, + wires: Wire::for_row(new_row), + coeffs: vec![F::one(), F::zero(), F::zero()], } } From 0215f20e2468df6f1cef0a44405c7a02610dd2af Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Oct 2023 13:10:29 +0200 Subject: [PATCH 038/173] update fixes after testing --- .../src/circuits/polynomials/keccak/circuitgates.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs index 4988a5aee0..16c4b70aad 100644 --- a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs +++ b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs @@ -28,12 +28,13 @@ macro_rules! state_from_vec { /// | 2 | 62 | 6 | 43 | 15 | 61 | /// | 3 | 28 | 55 | 25 | 21 | 56 | /// | 4 | 27 | 20 | 39 | 8 | 14 | -const OFF: [[u64; DIM]; DIM] = [ - [0, 36, 3, 41, 18], - [1, 44, 10, 45, 2], - [62, 6, 43, 15, 61], - [28, 55, 25, 21, 56], - [27, 20, 39, 8, 14], +/// Note that the order of the indexing is [y][x] to match the encoding of the witness algorithm +pub(crate) const OFF: [[u64; DIM]; DIM] = [ + [0, 1, 62, 28, 27], + [36, 44, 6, 55, 20], + [3, 10, 43, 25, 39], + [41, 45, 15, 21, 8], + [18, 2, 61, 56, 14], ]; //~ From 1ce42e71c640b17dff512a8123b4de483868bfd2 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 3 Oct 2023 13:15:25 +0200 Subject: [PATCH 039/173] update after testing --- .../polynomials/keccak/circuitgates.rs | 10 +- .../src/circuits/polynomials/keccak/gadget.rs | 7 +- kimchi/src/circuits/polynomials/keccak/mod.rs | 139 ++++++++++++++++-- 3 files changed, 135 insertions(+), 21 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs index 16c4b70aad..917a7c43da 100644 --- a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs +++ b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs @@ -7,19 +7,11 @@ use crate::{ expr::{constraints::ExprOps, Cache}, gate::GateType, }, + state_from_vec, }; use ark_ff::PrimeField; use std::marker::PhantomData; -#[macro_export] -macro_rules! state_from_vec { - ($expr:expr) => { - |i: usize, x: usize, y: usize, q: usize| { - $expr[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() - } - }; -} - /// Creates the 5x5 table of rotation bits for Keccak modulo 64 /// | x \ y | 0 | 1 | 2 | 3 | 4 | /// | ----- | -- | -- | -- | -- | -- | diff --git a/kimchi/src/circuits/polynomials/keccak/gadget.rs b/kimchi/src/circuits/polynomials/keccak/gadget.rs index 4cfa3f8c3d..810653b247 100644 --- a/kimchi/src/circuits/polynomials/keccak/gadget.rs +++ b/kimchi/src/circuits/polynomials/keccak/gadget.rs @@ -5,10 +5,13 @@ use crate::circuits::{ }; use ark_ff::{PrimeField, SquareRootField}; -use super::{expand, RATE, RC, ROUNDS}; +use super::{expand_word, RATE, RC, ROUNDS}; impl CircuitGate { /// Extends a Keccak circuit to hash one message (already padded to a multiple of 136 bits with 10*1 rule) + /// Note: + /// Requires at least one more row after the keccak gadget so that + /// constraints can access the next row in the squeeze pub fn extend_keccak(circuit: &mut Vec, bytelength: usize) -> usize { // pad let mut gates = Self::create_keccak(circuit.len(), bytelength); @@ -61,7 +64,7 @@ impl CircuitGate { CircuitGate { typ: GateType::KeccakRound, wires: Wire::for_row(new_row), - coeffs: expand(RC[round]), + coeffs: expand_word(RC[round]), } } } diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 339e63f175..98c9474a6a 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -5,12 +5,40 @@ pub mod gadget; pub const DIM: usize = 5; pub const QUARTERS: usize = 4; pub const ROUNDS: usize = 24; -pub const RATE: usize = 136; +pub const RATE: usize = 1088 / 8; +pub const CAPACITY: usize = 512 / 8; +pub const KECCAK_COLS: usize = 2344; use crate::circuits::expr::constraints::ExprOps; use ark_ff::PrimeField; -pub const RC: [u64; 24] = [ +#[macro_export] +macro_rules! state_from_vec { + ($expr:expr) => { + |i: usize, x: usize, y: usize, q: usize| { + $expr[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() + } + }; +} + +/// Creates the 5x5 table of rotation bits for Keccak modulo 64 +/// | x \ y | 0 | 1 | 2 | 3 | 4 | +/// | ----- | -- | -- | -- | -- | -- | +/// | 0 | 0 | 36 | 3 | 41 | 18 | +/// | 1 | 1 | 44 | 10 | 45 | 2 | +/// | 2 | 62 | 6 | 43 | 15 | 61 | +/// | 3 | 28 | 55 | 25 | 21 | 56 | +/// | 4 | 27 | 20 | 39 | 8 | 14 | +/// Note that the order of the indexing is [y][x] to match the encoding of the witness algorithm +pub(crate) const OFF: [[u64; DIM]; DIM] = [ + [0, 1, 62, 28, 27], + [36, 44, 6, 55, 20], + [3, 10, 43, 25, 39], + [41, 45, 15, 21, 8], + [18, 2, 61, 56, 14], +]; + +pub(crate) const RC: [u64; 24] = [ 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, @@ -37,14 +65,105 @@ pub const RC: [u64; 24] = [ 0x8000000080008008, ]; -pub(crate) fn expand>(word: u64) -> Vec { - format!("{:064b}", word) - .chars() - .collect::>() - .chunks(16) - .map(|c| c.iter().collect::()) - .collect::>() +// Composes a vector of 4 dense quarters into the dense full u64 word +pub(crate) fn compose(quarters: &[u64]) -> u64 { + quarters[0] + (1 << 16) * quarters[1] + (1 << 32) * quarters[2] + (1 << 48) * quarters[3] +} + +// Takes a dense u64 word and decomposes it into a vector of 4 dense quarters +pub(crate) fn decompose(word: u64) -> Vec { + let mut quarters = vec![]; + quarters.push(word % (1 << 16)); + quarters.push((word / (1 << 16)) % (1 << 16)); + quarters.push((word / (1 << 32)) % (1 << 16)); + quarters.push((word / (1 << 48)) % (1 << 16)); + quarters +} + +/// Expands a quarter of a word into the sparse representation as a u64 +pub(crate) fn expand(quarter: u64) -> u64 { + u64::from_str_radix(&format!("{:b}", quarter), 16).unwrap() +} + +pub(crate) fn expand_word>(word: u64) -> Vec { + decompose(word) .iter() - .map(|c| T::literal(F::from(u64::from_str_radix(c, 16).unwrap()))) + .map(|q| T::literal(F::from(expand(*q)))) .collect::>() } + +/// Pads the message with the 10*1 rule until reaching a length that is a multiple of the rate +pub(crate) fn pad(message: &[u8]) -> Vec { + let mut padded = message.to_vec(); + padded.push(0x01); + while padded.len() % 136 != 0 { + padded.push(0x00); + } + let last = padded.len() - 1; + padded[last] += 0x80; + padded +} + +/// From each quarter in sparse representation, it computes its 4 resets. +/// The resulting vector contains 4 times as many elements as the input. +/// The output is placed in the vector as [reset0, reset1, reset2, reset3] +pub(crate) fn shift(state: &[u64]) -> Vec { + let mut shifts = vec![vec![]; 4]; + let aux = expand(0xFFFF); + for term in state { + shifts[0].push(aux & term); // shift0 = reset0 + shifts[1].push(((aux << 1) & term) / 2); // shift1 = reset1/2 + shifts[2].push(((aux << 2) & term) / 4); // shift2 = reset2/4 + shifts[3].push(((aux << 3) & term) / 8); // shift3 = reset3/8 + } + shifts.iter().flatten().map(|x| *x).collect::>() +} + +/// From a vector of shifts, resets the underlying value returning only shift0 +pub(crate) fn reset(shifts: &[u64]) -> Vec { + shifts + .to_vec() + .into_iter() + .take(shifts.len() / 4) + .collect::>() +} + +/// From a reset0 state, obtain the corresponding 16-bit dense terms +pub(crate) fn collapse(state: &[u64]) -> Vec { + let mut dense = vec![]; + for reset in state { + dense.push(u64::from_str_radix(&format!("{:x}", reset), 2).unwrap()); + } + dense +} + +/// Outputs the state into dense quarters of 16-bits each in little endian order +pub(crate) fn quarters(state: &[u8]) -> Vec { + let mut quarters = vec![]; + for pair in state.chunks(2) { + quarters.push(u16::from_le_bytes([pair[0], pair[1]]) as u64); + } + quarters +} + +/// On input a vector of 16-bit dense quarters, outputs a vector of 8-bit bytes in the right order for Keccak +pub(crate) fn bytestring(dense: &[u64]) -> Vec { + dense + .iter() + .map(|x| vec![x % 256, x / 256]) + .collect::>>() + .iter() + .flatten() + .map(|x| *x as u64) + .collect::>() +} + +/// On input a 200-byte vector, generates a vector of 100 expanded quarters representing the 1600-bit state +pub(crate) fn expand_state(state: &[u8]) -> Vec { + let mut expanded = vec![]; + for pair in state.chunks(2) { + let quarter = u16::from_le_bytes([pair[0], pair[1]]); + expanded.push(expand(quarter as u64)); + } + expanded +} From 18f8f172871bd90552f7a808099335ff32764a0c Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Oct 2023 11:00:44 +0200 Subject: [PATCH 040/173] remove unused (witness) functions in this pr --- kimchi/src/circuits/polynomials/keccak/mod.rs | 98 ------------------- 1 file changed, 98 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 98c9474a6a..dfe3165c21 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -21,23 +21,6 @@ macro_rules! state_from_vec { }; } -/// Creates the 5x5 table of rotation bits for Keccak modulo 64 -/// | x \ y | 0 | 1 | 2 | 3 | 4 | -/// | ----- | -- | -- | -- | -- | -- | -/// | 0 | 0 | 36 | 3 | 41 | 18 | -/// | 1 | 1 | 44 | 10 | 45 | 2 | -/// | 2 | 62 | 6 | 43 | 15 | 61 | -/// | 3 | 28 | 55 | 25 | 21 | 56 | -/// | 4 | 27 | 20 | 39 | 8 | 14 | -/// Note that the order of the indexing is [y][x] to match the encoding of the witness algorithm -pub(crate) const OFF: [[u64; DIM]; DIM] = [ - [0, 1, 62, 28, 27], - [36, 44, 6, 55, 20], - [3, 10, 43, 25, 39], - [41, 45, 15, 21, 8], - [18, 2, 61, 56, 14], -]; - pub(crate) const RC: [u64; 24] = [ 0x0000000000000001, 0x0000000000008082, @@ -65,11 +48,6 @@ pub(crate) const RC: [u64; 24] = [ 0x8000000080008008, ]; -// Composes a vector of 4 dense quarters into the dense full u64 word -pub(crate) fn compose(quarters: &[u64]) -> u64 { - quarters[0] + (1 << 16) * quarters[1] + (1 << 32) * quarters[2] + (1 << 48) * quarters[3] -} - // Takes a dense u64 word and decomposes it into a vector of 4 dense quarters pub(crate) fn decompose(word: u64) -> Vec { let mut quarters = vec![]; @@ -91,79 +69,3 @@ pub(crate) fn expand_word>(word: u64) -> Vec { .map(|q| T::literal(F::from(expand(*q)))) .collect::>() } - -/// Pads the message with the 10*1 rule until reaching a length that is a multiple of the rate -pub(crate) fn pad(message: &[u8]) -> Vec { - let mut padded = message.to_vec(); - padded.push(0x01); - while padded.len() % 136 != 0 { - padded.push(0x00); - } - let last = padded.len() - 1; - padded[last] += 0x80; - padded -} - -/// From each quarter in sparse representation, it computes its 4 resets. -/// The resulting vector contains 4 times as many elements as the input. -/// The output is placed in the vector as [reset0, reset1, reset2, reset3] -pub(crate) fn shift(state: &[u64]) -> Vec { - let mut shifts = vec![vec![]; 4]; - let aux = expand(0xFFFF); - for term in state { - shifts[0].push(aux & term); // shift0 = reset0 - shifts[1].push(((aux << 1) & term) / 2); // shift1 = reset1/2 - shifts[2].push(((aux << 2) & term) / 4); // shift2 = reset2/4 - shifts[3].push(((aux << 3) & term) / 8); // shift3 = reset3/8 - } - shifts.iter().flatten().map(|x| *x).collect::>() -} - -/// From a vector of shifts, resets the underlying value returning only shift0 -pub(crate) fn reset(shifts: &[u64]) -> Vec { - shifts - .to_vec() - .into_iter() - .take(shifts.len() / 4) - .collect::>() -} - -/// From a reset0 state, obtain the corresponding 16-bit dense terms -pub(crate) fn collapse(state: &[u64]) -> Vec { - let mut dense = vec![]; - for reset in state { - dense.push(u64::from_str_radix(&format!("{:x}", reset), 2).unwrap()); - } - dense -} - -/// Outputs the state into dense quarters of 16-bits each in little endian order -pub(crate) fn quarters(state: &[u8]) -> Vec { - let mut quarters = vec![]; - for pair in state.chunks(2) { - quarters.push(u16::from_le_bytes([pair[0], pair[1]]) as u64); - } - quarters -} - -/// On input a vector of 16-bit dense quarters, outputs a vector of 8-bit bytes in the right order for Keccak -pub(crate) fn bytestring(dense: &[u64]) -> Vec { - dense - .iter() - .map(|x| vec![x % 256, x / 256]) - .collect::>>() - .iter() - .flatten() - .map(|x| *x as u64) - .collect::>() -} - -/// On input a 200-byte vector, generates a vector of 100 expanded quarters representing the 1600-bit state -pub(crate) fn expand_state(state: &[u8]) -> Vec { - let mut expanded = vec![]; - for pair in state.chunks(2) { - let quarter = u16::from_le_bytes([pair[0], pair[1]]); - expanded.push(expand(quarter as u64)); - } - expanded -} From 85277411d70872f64c4147bb21918417d87e53a1 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 5 Oct 2023 11:10:42 +0200 Subject: [PATCH 041/173] simplify decomposition into quarters thanks to clippy --- kimchi/src/circuits/polynomials/keccak/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index dfe3165c21..e23a71058e 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -50,12 +50,12 @@ pub(crate) const RC: [u64; 24] = [ // Takes a dense u64 word and decomposes it into a vector of 4 dense quarters pub(crate) fn decompose(word: u64) -> Vec { - let mut quarters = vec![]; - quarters.push(word % (1 << 16)); - quarters.push((word / (1 << 16)) % (1 << 16)); - quarters.push((word / (1 << 32)) % (1 << 16)); - quarters.push((word / (1 << 48)) % (1 << 16)); - quarters + vec![ + word % (1 << 16), + (word / (1 << 16)) % (1 << 16), + (word / (1 << 32)) % (1 << 16), + (word / (1 << 48)) % (1 << 16), + ] } /// Expands a quarter of a word into the sparse representation as a u64 From b64854200c6309f597af694d0df1d6e9523a79d7 Mon Sep 17 00:00:00 2001 From: querolita Date: Sat, 7 Oct 2023 12:40:33 +0200 Subject: [PATCH 042/173] reorganize the code into individual files --- .../polynomials/keccak/circuitgates.rs | 13 ++--- .../src/circuits/polynomials/keccak/gadget.rs | 56 ++++++++++++------- kimchi/src/circuits/polynomials/keccak/mod.rs | 6 ++ 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs index 917a7c43da..89586f2cc7 100644 --- a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs +++ b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs @@ -20,13 +20,12 @@ use std::marker::PhantomData; /// | 2 | 62 | 6 | 43 | 15 | 61 | /// | 3 | 28 | 55 | 25 | 21 | 56 | /// | 4 | 27 | 20 | 39 | 8 | 14 | -/// Note that the order of the indexing is [y][x] to match the encoding of the witness algorithm -pub(crate) const OFF: [[u64; DIM]; DIM] = [ - [0, 1, 62, 28, 27], - [36, 44, 6, 55, 20], - [3, 10, 43, 25, 39], - [41, 45, 15, 21, 8], - [18, 2, 61, 56, 14], +const OFF: [[u64; DIM]; DIM] = [ + [0, 36, 3, 41, 18], + [1, 44, 10, 45, 2], + [62, 6, 43, 15, 61], + [28, 55, 25, 21, 56], + [27, 20, 39, 8, 14], ]; //~ diff --git a/kimchi/src/circuits/polynomials/keccak/gadget.rs b/kimchi/src/circuits/polynomials/keccak/gadget.rs index 810653b247..4a36b910b5 100644 --- a/kimchi/src/circuits/polynomials/keccak/gadget.rs +++ b/kimchi/src/circuits/polynomials/keccak/gadget.rs @@ -5,29 +5,34 @@ use crate::circuits::{ }; use ark_ff::{PrimeField, SquareRootField}; -use super::{expand_word, RATE, RC, ROUNDS}; +use super::{expand_word, padded_length, RATE, RC, ROUNDS}; impl CircuitGate { - /// Extends a Keccak circuit to hash one message (already padded to a multiple of 136 bits with 10*1 rule) + /// Extends a Keccak circuit to hash one message /// Note: - /// Requires at least one more row after the keccak gadget so that + /// Requires at least one more row after the Keccak gadget so that /// constraints can access the next row in the squeeze pub fn extend_keccak(circuit: &mut Vec, bytelength: usize) -> usize { - // pad let mut gates = Self::create_keccak(circuit.len(), bytelength); circuit.append(&mut gates); circuit.len() } - /// Creates a Keccak256 circuit, capacity 512 bits, rate 1088 bits, for a padded message of a given bytelength + /// Creates a Keccak256 circuit, capacity 512 bits, rate 1088 bits, message of a given bytelength fn create_keccak(new_row: usize, bytelength: usize) -> Vec { + let padded_len = padded_length(bytelength); + let extra_bytes = padded_len - bytelength; + let num_blocks = padded_len / RATE; let mut gates = vec![]; - for block in 0..(bytelength / RATE) { - if block == 0 { - gates.push(Self::create_keccak_root(new_row + gates.len())); - } else { - gates.push(Self::create_keccak_absorb(new_row + gates.len())); - } + for block in 0..num_blocks { + let root = block == 0; + let pad = block == num_blocks - 1; + gates.push(Self::create_keccak_absorb( + new_row + gates.len(), + root, + pad, + extra_bytes, + )); for round in 0..ROUNDS { gates.push(Self::create_keccak_round(new_row + gates.len(), round)); } @@ -40,23 +45,32 @@ impl CircuitGate { CircuitGate { typ: GateType::KeccakSponge, wires: Wire::for_row(new_row), - coeffs: vec![F::zero(), F::zero(), F::one()], + coeffs: vec![F::zero(), F::one()], } } - fn create_keccak_absorb(new_row: usize) -> Self { - CircuitGate { - typ: GateType::KeccakSponge, - wires: Wire::for_row(new_row), - coeffs: vec![F::zero(), F::one(), F::zero()], + fn create_keccak_absorb(new_row: usize, root: bool, pad: bool, pad_bytes: usize) -> Self { + let mut coeffs = vec![F::zero(); 336]; + coeffs[0] = F::one(); // absorb + if root { + coeffs[2] = F::one(); // root + } + if pad { + // Check pad 0x01 (0x00 ... 0x00)* 0x80 or 0x81 if only one byte for padding + for i in 0..pad_bytes { + coeffs[140 - i] = F::one(); // flag for padding + if i == 0 { + coeffs[335 - i] += F::from(0x80u8); // pad + } + if i == pad_bytes - 1 { + coeffs[335 - i] += F::one(); // pad + } + } } - } - - fn create_keccak_root(new_row: usize) -> Self { CircuitGate { typ: GateType::KeccakSponge, wires: Wire::for_row(new_row), - coeffs: vec![F::one(), F::zero(), F::zero()], + coeffs, } } diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index e23a71058e..95d7698220 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -63,9 +63,15 @@ pub(crate) fn expand(quarter: u64) -> u64 { u64::from_str_radix(&format!("{:b}", quarter), 16).unwrap() } +/// Expands a u64 word into a vector of 4 sparse u64 quarters pub(crate) fn expand_word>(word: u64) -> Vec { decompose(word) .iter() .map(|q| T::literal(F::from(expand(*q)))) .collect::>() } + +/// On input a length, returns the smallest multiple of RATE that is greater than the bytelength +pub(crate) fn padded_length(bytelength: usize) -> usize { + (bytelength / RATE + 1) * RATE +} From 1e23d22652dec56b5d76ef685a1ae8f97faa1d27 Mon Sep 17 00:00:00 2001 From: querolita Date: Sat, 7 Oct 2023 12:44:24 +0200 Subject: [PATCH 043/173] move OFF table to mod --- .../polynomials/keccak/circuitgates.rs | 18 +----------------- kimchi/src/circuits/polynomials/keccak/mod.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs index 89586f2cc7..280eb38369 100644 --- a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs +++ b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs @@ -1,5 +1,5 @@ //! Keccak gadget -use super::{DIM, QUARTERS}; +use super::{DIM, OFF, QUARTERS}; use crate::{ auto_clone, auto_clone_array, circuits::{ @@ -12,22 +12,6 @@ use crate::{ use ark_ff::PrimeField; use std::marker::PhantomData; -/// Creates the 5x5 table of rotation bits for Keccak modulo 64 -/// | x \ y | 0 | 1 | 2 | 3 | 4 | -/// | ----- | -- | -- | -- | -- | -- | -/// | 0 | 0 | 36 | 3 | 41 | 18 | -/// | 1 | 1 | 44 | 10 | 45 | 2 | -/// | 2 | 62 | 6 | 43 | 15 | 61 | -/// | 3 | 28 | 55 | 25 | 21 | 56 | -/// | 4 | 27 | 20 | 39 | 8 | 14 | -const OFF: [[u64; DIM]; DIM] = [ - [0, 36, 3, 41, 18], - [1, 44, 10, 45, 2], - [62, 6, 43, 15, 61], - [28, 55, 25, 21, 56], - [27, 20, 39, 8, 14], -]; - //~ //~ | `KeccakRound` | [0...440) | [440...1540) | [1540...2344) | //~ | ------------- | --------- | ------------ | ------------- | diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 95d7698220..538159e1b5 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -21,6 +21,23 @@ macro_rules! state_from_vec { }; } +/// Creates the 5x5 table of rotation bits for Keccak modulo 64 +/// | x \ y | 0 | 1 | 2 | 3 | 4 | +/// | ----- | -- | -- | -- | -- | -- | +/// | 0 | 0 | 36 | 3 | 41 | 18 | +/// | 1 | 1 | 44 | 10 | 45 | 2 | +/// | 2 | 62 | 6 | 43 | 15 | 61 | +/// | 3 | 28 | 55 | 25 | 21 | 56 | +/// | 4 | 27 | 20 | 39 | 8 | 14 | +/// Note that the order of the indexing is [y][x] to match the encoding of the witness algorithm +pub(crate) const OFF: [[u64; DIM]; DIM] = [ + [0, 1, 62, 28, 27], + [36, 44, 6, 55, 20], + [3, 10, 43, 25, 39], + [41, 45, 15, 21, 8], + [18, 2, 61, 56, 14], +]; + pub(crate) const RC: [u64; 24] = [ 0x0000000000000001, 0x0000000000008082, From 9ce51a3d10a0f02323c0054b1d4788196522bcd3 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 19 Sep 2023 15:32:52 +0200 Subject: [PATCH 044/173] preliminar work on array cell for witness layout --- kimchi/src/circuits/witness/array_cell.rs | 23 +++++++++++++++++++++++ kimchi/src/circuits/witness/mod.rs | 1 + 2 files changed, 24 insertions(+) create mode 100644 kimchi/src/circuits/witness/array_cell.rs diff --git a/kimchi/src/circuits/witness/array_cell.rs b/kimchi/src/circuits/witness/array_cell.rs new file mode 100644 index 0000000000..7bc31443d8 --- /dev/null +++ b/kimchi/src/circuits/witness/array_cell.rs @@ -0,0 +1,23 @@ +use super::{variables::Variables, WitnessCell}; +use crate::circuits::polynomial::COLUMNS; +use ark_ff::Field; + +/// A chunk of witness cells assigned from a variable that is an array +/// See [Variables] for more details +pub struct ArrayCell<'a> { + name: &'a str, + length: usize, +} + +impl<'a> ArrayCell<'a> { + /// Create witness cell assigned from a variable name + pub fn create(name: &'a str, length: usize) -> Box> { + Box::new(ArrayCell { name, length }) + } +} + +impl<'a, F: Field> WitnessCell for ArrayCell<'a> { + fn value(&self, _witness: &mut [Vec; COLUMNS], variables: &Variables) -> F { + variables[self.name] + } +} diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index a85a932db9..7767a44e22 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -1,4 +1,5 @@ use ark_ff::{Field, PrimeField}; +mod array_cell; mod constant_cell; mod copy_bits_cell; mod copy_cell; From 96002d7c8e6b875cb0c105a3810c9fe0a434c099 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 19 Sep 2023 17:34:54 +0200 Subject: [PATCH 045/173] constant generic for number of columns for witness interface --- .../polynomials/foreign_field_add/witness.rs | 4 +-- .../polynomials/foreign_field_mul/witness.rs | 2 +- .../polynomials/range_check/witness.rs | 4 +-- kimchi/src/circuits/polynomials/rot.rs | 6 ++-- kimchi/src/circuits/polynomials/xor.rs | 12 +++++-- kimchi/src/circuits/witness/array_cell.rs | 5 ++- kimchi/src/circuits/witness/constant_cell.rs | 5 ++- kimchi/src/circuits/witness/copy_bits_cell.rs | 5 ++- kimchi/src/circuits/witness/copy_cell.rs | 5 ++- .../src/circuits/witness/copy_shift_cell.rs | 5 ++- kimchi/src/circuits/witness/mod.rs | 32 +++++++++---------- .../circuits/witness/variable_bits_cell.rs | 5 ++- kimchi/src/circuits/witness/variable_cell.rs | 5 ++- 13 files changed, 48 insertions(+), 47 deletions(-) diff --git a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs index 38d171dabe..56657ce0ce 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs @@ -185,7 +185,7 @@ fn init_ffadd_row( overflow: F, carry: F, ) { - let witness_shape: Vec<[Box>; COLUMNS]> = vec![ + let witness_shape: Vec<[Box>; COLUMNS]> = vec![ // ForeignFieldAdd row [ VariableCell::create("left_lo"), @@ -221,7 +221,7 @@ fn init_bound_rows( bound: &[F; 3], carry: &F, ) { - let witness_shape: Vec<[Box>; COLUMNS]> = vec![ + let witness_shape: Vec<[Box>; COLUMNS]> = vec![ [ // ForeignFieldAdd row VariableCell::create("result_lo"), diff --git a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs index e202f3f7f7..c6bd2794f3 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs @@ -41,7 +41,7 @@ use super::circuitgates; // // so that most significant limb, q2, is in W[2][0]. // -fn create_layout() -> [[Box>; COLUMNS]; 2] { +fn create_layout() -> [[Box>; COLUMNS]; 2] { [ // ForeignFieldMul row [ diff --git a/kimchi/src/circuits/polynomials/range_check/witness.rs b/kimchi/src/circuits/polynomials/range_check/witness.rs index 8c142236cf..5799942310 100644 --- a/kimchi/src/circuits/polynomials/range_check/witness.rs +++ b/kimchi/src/circuits/polynomials/range_check/witness.rs @@ -29,7 +29,7 @@ use o1_utils::foreign_field::BigUintForeignFieldHelpers; /// For example, we can convert the `RangeCheck0` circuit gate into /// a 64-bit lookup by adding two copy constraints to constrain /// columns 1 and 2 to zero. -fn layout() -> Vec<[Box>; COLUMNS]> { +fn layout() -> Vec<[Box>; COLUMNS]> { vec![ /* row 1, RangeCheck0 row */ range_check_0_row("v0", 0), @@ -86,7 +86,7 @@ fn layout() -> Vec<[Box>; COLUMNS]> { pub fn range_check_0_row( limb_name: &'static str, row: usize, -) -> [Box>; COLUMNS] { +) -> [Box>; COLUMNS] { [ VariableCell::create(limb_name), /* 12-bit copies */ diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index d529670d13..a3c089fa92 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -266,7 +266,9 @@ where // ROTATION WITNESS COMPUTATION -fn layout_rot64(curr_row: usize) -> [[Box>; COLUMNS]; 3] { +fn layout_rot64( + curr_row: usize, +) -> [[Box>; COLUMNS]; 3] { [ rot_row(), range_check_0_row("shifted", curr_row + 1), @@ -274,7 +276,7 @@ fn layout_rot64(curr_row: usize) -> [[Box>; CO ] } -fn rot_row() -> [Box>; COLUMNS] { +fn rot_row() -> [Box>; COLUMNS] { [ VariableCell::create("word"), VariableCell::create("rotated"), diff --git a/kimchi/src/circuits/polynomials/xor.rs b/kimchi/src/circuits/polynomials/xor.rs index ea5fbc2cbd..0b2b403ebf 100644 --- a/kimchi/src/circuits/polynomials/xor.rs +++ b/kimchi/src/circuits/polynomials/xor.rs @@ -169,7 +169,10 @@ where } // Witness layout -fn layout(curr_row: usize, bits: usize) -> Vec<[Box>; COLUMNS]> { +fn layout( + curr_row: usize, + bits: usize, +) -> Vec<[Box>; COLUMNS]> { let num_xor = num_xors(bits); let mut layout = (0..num_xor) .map(|i| xor_row(i, curr_row + i)) @@ -178,7 +181,10 @@ fn layout(curr_row: usize, bits: usize) -> Vec<[Box(nybble: usize, curr_row: usize) -> [Box>; COLUMNS] { +fn xor_row( + nybble: usize, + curr_row: usize, +) -> [Box>; COLUMNS] { let start = nybble * 16; [ VariableBitsCell::create("in1", start, None), @@ -199,7 +205,7 @@ fn xor_row(nybble: usize, curr_row: usize) -> [Box() -> [Box>; COLUMNS] { +fn zero_row() -> [Box>; COLUMNS] { [ ConstantCell::create(F::zero()), ConstantCell::create(F::zero()), diff --git a/kimchi/src/circuits/witness/array_cell.rs b/kimchi/src/circuits/witness/array_cell.rs index 7bc31443d8..5ae2bf02d9 100644 --- a/kimchi/src/circuits/witness/array_cell.rs +++ b/kimchi/src/circuits/witness/array_cell.rs @@ -1,5 +1,4 @@ use super::{variables::Variables, WitnessCell}; -use crate::circuits::polynomial::COLUMNS; use ark_ff::Field; /// A chunk of witness cells assigned from a variable that is an array @@ -16,8 +15,8 @@ impl<'a> ArrayCell<'a> { } } -impl<'a, F: Field> WitnessCell for ArrayCell<'a> { - fn value(&self, _witness: &mut [Vec; COLUMNS], variables: &Variables) -> F { +impl<'a, const N: usize, F: Field> WitnessCell for ArrayCell<'a> { + fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { variables[self.name] } } diff --git a/kimchi/src/circuits/witness/constant_cell.rs b/kimchi/src/circuits/witness/constant_cell.rs index 15ac14e61f..e17e8adfb9 100644 --- a/kimchi/src/circuits/witness/constant_cell.rs +++ b/kimchi/src/circuits/witness/constant_cell.rs @@ -1,5 +1,4 @@ use super::{variables::Variables, WitnessCell}; -use crate::circuits::polynomial::COLUMNS; use ark_ff::Field; /// Witness cell with constant value @@ -14,8 +13,8 @@ impl ConstantCell { } } -impl WitnessCell for ConstantCell { - fn value(&self, _witness: &mut [Vec; COLUMNS], _variables: &Variables) -> F { +impl WitnessCell for ConstantCell { + fn value(&self, _witness: &mut [Vec; N], _variables: &Variables) -> F { self.value } } diff --git a/kimchi/src/circuits/witness/copy_bits_cell.rs b/kimchi/src/circuits/witness/copy_bits_cell.rs index 5cb6064f02..a53c0f32b3 100644 --- a/kimchi/src/circuits/witness/copy_bits_cell.rs +++ b/kimchi/src/circuits/witness/copy_bits_cell.rs @@ -2,7 +2,6 @@ use ark_ff::Field; use o1_utils::FieldHelpers; use super::{variables::Variables, WitnessCell}; -use crate::circuits::polynomial::COLUMNS; /// Witness cell copied from bits of another witness cell pub struct CopyBitsCell { @@ -24,8 +23,8 @@ impl CopyBitsCell { } } -impl WitnessCell for CopyBitsCell { - fn value(&self, witness: &mut [Vec; COLUMNS], _variables: &Variables) -> F { +impl WitnessCell for CopyBitsCell { + fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { F::from_bits(&witness[self.col][self.row].to_bits()[self.start..self.end]) .expect("failed to deserialize field bits for copy bits cell") } diff --git a/kimchi/src/circuits/witness/copy_cell.rs b/kimchi/src/circuits/witness/copy_cell.rs index d5ea353b56..438b774acd 100644 --- a/kimchi/src/circuits/witness/copy_cell.rs +++ b/kimchi/src/circuits/witness/copy_cell.rs @@ -1,7 +1,6 @@ use ark_ff::Field; use super::{variables::Variables, WitnessCell}; -use crate::circuits::polynomial::COLUMNS; /// Witness cell copied from another witness cell pub struct CopyCell { @@ -16,8 +15,8 @@ impl CopyCell { } } -impl WitnessCell for CopyCell { - fn value(&self, witness: &mut [Vec; COLUMNS], _variables: &Variables) -> F { +impl WitnessCell for CopyCell { + fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { witness[self.col][self.row] } } diff --git a/kimchi/src/circuits/witness/copy_shift_cell.rs b/kimchi/src/circuits/witness/copy_shift_cell.rs index 822833b521..c9aeef616d 100644 --- a/kimchi/src/circuits/witness/copy_shift_cell.rs +++ b/kimchi/src/circuits/witness/copy_shift_cell.rs @@ -1,5 +1,4 @@ use super::{variables::Variables, WitnessCell}; -use crate::circuits::polynomial::COLUMNS; use ark_ff::Field; /// Witness cell copied from another cell and shifted @@ -16,8 +15,8 @@ impl CopyShiftCell { } } -impl WitnessCell for CopyShiftCell { - fn value(&self, witness: &mut [Vec; COLUMNS], _variables: &Variables) -> F { +impl WitnessCell for CopyShiftCell { + fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { F::from(2u32).pow([self.shift]) * witness[self.col][self.row] } } diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index 7767a44e22..041e17b255 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -9,6 +9,7 @@ mod variable_cell; mod variables; pub use self::{ + array_cell::ArrayCell, constant_cell::ConstantCell, copy_bits_cell::CopyBitsCell, copy_cell::CopyCell, @@ -18,43 +19,41 @@ pub use self::{ variables::{variable_map, variables, Variables}, }; -use super::polynomial::COLUMNS; - /// Witness cell interface -pub trait WitnessCell { - fn value(&self, witness: &mut [Vec; COLUMNS], variables: &Variables) -> F; +pub trait WitnessCell { + fn value(&self, witness: &mut [Vec; N], variables: &Variables) -> F; } /// Initialize a witness cell based on layout and computed variables -pub fn init_cell( - witness: &mut [Vec; COLUMNS], +pub fn init_cell( + witness: &mut [Vec; N], offset: usize, row: usize, col: usize, - layout: &[[Box>; COLUMNS]], + layout: &[[Box>; N]], variables: &Variables, ) { witness[col][row + offset] = layout[row][col].value(witness, variables); } /// Initialize a witness row based on layout and computed variables -pub fn init_row( - witness: &mut [Vec; COLUMNS], +pub fn init_row( + witness: &mut [Vec; N], offset: usize, row: usize, - layout: &[[Box>; COLUMNS]], + layout: &[[Box>; N]], variables: &Variables, ) { - for col in 0..COLUMNS { + for col in 0..N { init_cell(witness, offset, row, col, layout, variables); } } /// Initialize a witness based on layout and computed variables -pub fn init( - witness: &mut [Vec; COLUMNS], +pub fn init( + witness: &mut [Vec; N], offset: usize, - layout: &[[Box>; COLUMNS]], + layout: &[[Box>; N]], variables: &Variables, ) { for row in 0..layout.len() { @@ -68,6 +67,7 @@ mod tests { use super::*; + use crate::circuits::polynomial::COLUMNS; use ark_ec::AffineCurve; use ark_ff::{Field, One, Zero}; use mina_curves::pasta::Pallas; @@ -75,7 +75,7 @@ mod tests { #[test] fn zero_layout() { - let layout: Vec<[Box>; COLUMNS]> = vec![[ + let layout: Vec<[Box>; COLUMNS]> = vec![[ ConstantCell::create(PallasField::zero()), ConstantCell::create(PallasField::zero()), ConstantCell::create(PallasField::zero()), @@ -118,7 +118,7 @@ mod tests { #[test] fn mixed_layout() { - let layout: Vec<[Box>; COLUMNS]> = vec![ + let layout: Vec<[Box>; COLUMNS]> = vec![ [ ConstantCell::create(PallasField::from(12u32)), ConstantCell::create(PallasField::from(0xa5a3u32)), diff --git a/kimchi/src/circuits/witness/variable_bits_cell.rs b/kimchi/src/circuits/witness/variable_bits_cell.rs index 1e1cfe31f1..ecfe580202 100644 --- a/kimchi/src/circuits/witness/variable_bits_cell.rs +++ b/kimchi/src/circuits/witness/variable_bits_cell.rs @@ -1,5 +1,4 @@ use super::{variables::Variables, WitnessCell}; -use crate::circuits::polynomial::COLUMNS; use ark_ff::Field; use o1_utils::FieldHelpers; @@ -19,8 +18,8 @@ impl<'a> VariableBitsCell<'a> { } } -impl<'a, F: Field> WitnessCell for VariableBitsCell<'a> { - fn value(&self, _witness: &mut [Vec; COLUMNS], variables: &Variables) -> F { +impl<'a, const N: usize, F: Field> WitnessCell for VariableBitsCell<'a> { + fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { let bits = if let Some(end) = self.end { F::from_bits(&variables[self.name].to_bits()[self.start..end]) } else { diff --git a/kimchi/src/circuits/witness/variable_cell.rs b/kimchi/src/circuits/witness/variable_cell.rs index 372ff98bdf..0462d757bd 100644 --- a/kimchi/src/circuits/witness/variable_cell.rs +++ b/kimchi/src/circuits/witness/variable_cell.rs @@ -1,5 +1,4 @@ use super::{variables::Variables, WitnessCell}; -use crate::circuits::polynomial::COLUMNS; use ark_ff::Field; /// Witness cell assigned from a variable @@ -15,8 +14,8 @@ impl<'a> VariableCell<'a> { } } -impl<'a, F: Field> WitnessCell for VariableCell<'a> { - fn value(&self, _witness: &mut [Vec; COLUMNS], variables: &Variables) -> F { +impl<'a, const N: usize, F: Field> WitnessCell for VariableCell<'a> { + fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { variables[self.name] } } From 1c717f0124d51a6f225b480ff013dbaaee698d86 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 19 Sep 2023 21:16:12 +0200 Subject: [PATCH 046/173] preliminary work with macro for layout, does not work --- kimchi/src/circuits/witness/array_cell.rs | 22 ---------------------- kimchi/src/circuits/witness/mod.rs | 4 +--- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 kimchi/src/circuits/witness/array_cell.rs diff --git a/kimchi/src/circuits/witness/array_cell.rs b/kimchi/src/circuits/witness/array_cell.rs deleted file mode 100644 index 5ae2bf02d9..0000000000 --- a/kimchi/src/circuits/witness/array_cell.rs +++ /dev/null @@ -1,22 +0,0 @@ -use super::{variables::Variables, WitnessCell}; -use ark_ff::Field; - -/// A chunk of witness cells assigned from a variable that is an array -/// See [Variables] for more details -pub struct ArrayCell<'a> { - name: &'a str, - length: usize, -} - -impl<'a> ArrayCell<'a> { - /// Create witness cell assigned from a variable name - pub fn create(name: &'a str, length: usize) -> Box> { - Box::new(ArrayCell { name, length }) - } -} - -impl<'a, const N: usize, F: Field> WitnessCell for ArrayCell<'a> { - fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { - variables[self.name] - } -} diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index 041e17b255..1ae1aaea31 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -1,5 +1,4 @@ use ark_ff::{Field, PrimeField}; -mod array_cell; mod constant_cell; mod copy_bits_cell; mod copy_cell; @@ -9,7 +8,6 @@ mod variable_cell; mod variables; pub use self::{ - array_cell::ArrayCell, constant_cell::ConstantCell, copy_bits_cell::CopyBitsCell, copy_cell::CopyCell, @@ -30,7 +28,7 @@ pub fn init_cell( offset: usize, row: usize, col: usize, - layout: &[[Box>; N]], + layout: &[[Box>; N]], variables: &Variables, ) { witness[col][row + offset] = layout[row][col].value(witness, variables); From 11da7d15544f9474e9b7f9edbd80e8ed543d28d7 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 20 Sep 2023 10:31:48 +0200 Subject: [PATCH 047/173] add generic to WitnessCell to support vectors inside VariableCell --- .../polynomials/foreign_field_add/witness.rs | 4 +- .../polynomials/foreign_field_mul/witness.rs | 2 +- .../polynomials/range_check/witness.rs | 4 +- kimchi/src/circuits/polynomials/rot.rs | 4 +- kimchi/src/circuits/polynomials/xor.rs | 6 +-- kimchi/src/circuits/witness/array_cell.rs | 22 +++++++++ kimchi/src/circuits/witness/constant_cell.rs | 2 +- kimchi/src/circuits/witness/copy_bits_cell.rs | 2 +- kimchi/src/circuits/witness/copy_cell.rs | 2 +- .../src/circuits/witness/copy_shift_cell.rs | 2 +- kimchi/src/circuits/witness/mod.rs | 49 ++++++++++--------- .../circuits/witness/variable_bits_cell.rs | 2 +- kimchi/src/circuits/witness/variable_cell.rs | 2 +- kimchi/src/circuits/witness/variables.rs | 14 +++--- 14 files changed, 71 insertions(+), 46 deletions(-) create mode 100644 kimchi/src/circuits/witness/array_cell.rs diff --git a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs index 56657ce0ce..1487b9f85e 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs @@ -185,7 +185,7 @@ fn init_ffadd_row( overflow: F, carry: F, ) { - let witness_shape: Vec<[Box>; COLUMNS]> = vec![ + let witness_shape: Vec<[Box>; COLUMNS]> = vec![ // ForeignFieldAdd row [ VariableCell::create("left_lo"), @@ -221,7 +221,7 @@ fn init_bound_rows( bound: &[F; 3], carry: &F, ) { - let witness_shape: Vec<[Box>; COLUMNS]> = vec![ + let witness_shape: Vec<[Box>; COLUMNS]> = vec![ [ // ForeignFieldAdd row VariableCell::create("result_lo"), diff --git a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs index c6bd2794f3..7a8e64710a 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs @@ -41,7 +41,7 @@ use super::circuitgates; // // so that most significant limb, q2, is in W[2][0]. // -fn create_layout() -> [[Box>; COLUMNS]; 2] { +fn create_layout() -> [[Box>; COLUMNS]; 2] { [ // ForeignFieldMul row [ diff --git a/kimchi/src/circuits/polynomials/range_check/witness.rs b/kimchi/src/circuits/polynomials/range_check/witness.rs index 5799942310..aacbe2ffd5 100644 --- a/kimchi/src/circuits/polynomials/range_check/witness.rs +++ b/kimchi/src/circuits/polynomials/range_check/witness.rs @@ -29,7 +29,7 @@ use o1_utils::foreign_field::BigUintForeignFieldHelpers; /// For example, we can convert the `RangeCheck0` circuit gate into /// a 64-bit lookup by adding two copy constraints to constrain /// columns 1 and 2 to zero. -fn layout() -> Vec<[Box>; COLUMNS]> { +fn layout() -> Vec<[Box>; COLUMNS]> { vec![ /* row 1, RangeCheck0 row */ range_check_0_row("v0", 0), @@ -86,7 +86,7 @@ fn layout() -> Vec<[Box>; COLUMNS]> { pub fn range_check_0_row( limb_name: &'static str, row: usize, -) -> [Box>; COLUMNS] { +) -> [Box>; COLUMNS] { [ VariableCell::create(limb_name), /* 12-bit copies */ diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index a3c089fa92..c60106efc2 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -268,7 +268,7 @@ where fn layout_rot64( curr_row: usize, -) -> [[Box>; COLUMNS]; 3] { +) -> [[Box>; COLUMNS]; 3] { [ rot_row(), range_check_0_row("shifted", curr_row + 1), @@ -276,7 +276,7 @@ fn layout_rot64( ] } -fn rot_row() -> [Box>; COLUMNS] { +fn rot_row() -> [Box>; COLUMNS] { [ VariableCell::create("word"), VariableCell::create("rotated"), diff --git a/kimchi/src/circuits/polynomials/xor.rs b/kimchi/src/circuits/polynomials/xor.rs index 0b2b403ebf..1ade505af8 100644 --- a/kimchi/src/circuits/polynomials/xor.rs +++ b/kimchi/src/circuits/polynomials/xor.rs @@ -172,7 +172,7 @@ where fn layout( curr_row: usize, bits: usize, -) -> Vec<[Box>; COLUMNS]> { +) -> Vec<[Box>; COLUMNS]> { let num_xor = num_xors(bits); let mut layout = (0..num_xor) .map(|i| xor_row(i, curr_row + i)) @@ -184,7 +184,7 @@ fn layout( fn xor_row( nybble: usize, curr_row: usize, -) -> [Box>; COLUMNS] { +) -> [Box>; COLUMNS] { let start = nybble * 16; [ VariableBitsCell::create("in1", start, None), @@ -205,7 +205,7 @@ fn xor_row( ] } -fn zero_row() -> [Box>; COLUMNS] { +fn zero_row() -> [Box>; COLUMNS] { [ ConstantCell::create(F::zero()), ConstantCell::create(F::zero()), diff --git a/kimchi/src/circuits/witness/array_cell.rs b/kimchi/src/circuits/witness/array_cell.rs new file mode 100644 index 0000000000..f1ab0c1ac4 --- /dev/null +++ b/kimchi/src/circuits/witness/array_cell.rs @@ -0,0 +1,22 @@ +use super::{variables::Variables, WitnessCell}; +use ark_ff::Field; + +/// Witness cell assigned from a variable +/// See [Variables] for more details +pub struct ArrayCell<'a> { + name: &'a str, + index: usize, +} + +impl<'a> ArrayCell<'a> { + /// Create witness cell assigned from a variable name + pub fn create(name: &'a str, index: usize) -> Box> { + Box::new(ArrayCell { name, index }) + } +} + +impl<'a, const N: usize, F: Field> WitnessCell> for ArrayCell<'a> { + fn value(&self, _witness: &mut [Vec; N], variables: &Variables>) -> F { + variables[self.name][self.index] + } +} diff --git a/kimchi/src/circuits/witness/constant_cell.rs b/kimchi/src/circuits/witness/constant_cell.rs index e17e8adfb9..2feaee094a 100644 --- a/kimchi/src/circuits/witness/constant_cell.rs +++ b/kimchi/src/circuits/witness/constant_cell.rs @@ -13,7 +13,7 @@ impl ConstantCell { } } -impl WitnessCell for ConstantCell { +impl WitnessCell for ConstantCell { fn value(&self, _witness: &mut [Vec; N], _variables: &Variables) -> F { self.value } diff --git a/kimchi/src/circuits/witness/copy_bits_cell.rs b/kimchi/src/circuits/witness/copy_bits_cell.rs index a53c0f32b3..0ff09f080c 100644 --- a/kimchi/src/circuits/witness/copy_bits_cell.rs +++ b/kimchi/src/circuits/witness/copy_bits_cell.rs @@ -23,7 +23,7 @@ impl CopyBitsCell { } } -impl WitnessCell for CopyBitsCell { +impl WitnessCell for CopyBitsCell { fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { F::from_bits(&witness[self.col][self.row].to_bits()[self.start..self.end]) .expect("failed to deserialize field bits for copy bits cell") diff --git a/kimchi/src/circuits/witness/copy_cell.rs b/kimchi/src/circuits/witness/copy_cell.rs index 438b774acd..a1157658fc 100644 --- a/kimchi/src/circuits/witness/copy_cell.rs +++ b/kimchi/src/circuits/witness/copy_cell.rs @@ -15,7 +15,7 @@ impl CopyCell { } } -impl WitnessCell for CopyCell { +impl WitnessCell for CopyCell { fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { witness[self.col][self.row] } diff --git a/kimchi/src/circuits/witness/copy_shift_cell.rs b/kimchi/src/circuits/witness/copy_shift_cell.rs index c9aeef616d..caa77812d1 100644 --- a/kimchi/src/circuits/witness/copy_shift_cell.rs +++ b/kimchi/src/circuits/witness/copy_shift_cell.rs @@ -15,7 +15,7 @@ impl CopyShiftCell { } } -impl WitnessCell for CopyShiftCell { +impl WitnessCell for CopyShiftCell { fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { F::from(2u32).pow([self.shift]) * witness[self.col][self.row] } diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index 1ae1aaea31..8825ad09ac 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -1,4 +1,5 @@ use ark_ff::{Field, PrimeField}; +mod array_cell; mod constant_cell; mod copy_bits_cell; mod copy_cell; @@ -8,6 +9,7 @@ mod variable_cell; mod variables; pub use self::{ + array_cell::ArrayCell, constant_cell::ConstantCell, copy_bits_cell::CopyBitsCell, copy_cell::CopyCell, @@ -18,8 +20,8 @@ pub use self::{ }; /// Witness cell interface -pub trait WitnessCell { - fn value(&self, witness: &mut [Vec; N], variables: &Variables) -> F; +pub trait WitnessCell { + fn value(&self, witness: &mut [Vec; N], variables: &Variables) -> F; } /// Initialize a witness cell based on layout and computed variables @@ -28,7 +30,7 @@ pub fn init_cell( offset: usize, row: usize, col: usize, - layout: &[[Box>; N]], + layout: &[[Box>; N]], variables: &Variables, ) { witness[col][row + offset] = layout[row][col].value(witness, variables); @@ -39,7 +41,7 @@ pub fn init_row( witness: &mut [Vec; N], offset: usize, row: usize, - layout: &[[Box>; N]], + layout: &[[Box>; N]], variables: &Variables, ) { for col in 0..N { @@ -51,7 +53,7 @@ pub fn init_row( pub fn init( witness: &mut [Vec; N], offset: usize, - layout: &[[Box>; N]], + layout: &[[Box>; N]], variables: &Variables, ) { for row in 0..layout.len() { @@ -73,23 +75,24 @@ mod tests { #[test] fn zero_layout() { - let layout: Vec<[Box>; COLUMNS]> = vec![[ - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ]]; + let layout: Vec<[Box>; COLUMNS]> = + vec![[ + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ]]; let mut witness: [Vec; COLUMNS] = array::from_fn(|_| vec![PallasField::one(); 1]); @@ -116,7 +119,7 @@ mod tests { #[test] fn mixed_layout() { - let layout: Vec<[Box>; COLUMNS]> = vec![ + let layout: Vec<[Box>; COLUMNS]> = vec![ [ ConstantCell::create(PallasField::from(12u32)), ConstantCell::create(PallasField::from(0xa5a3u32)), diff --git a/kimchi/src/circuits/witness/variable_bits_cell.rs b/kimchi/src/circuits/witness/variable_bits_cell.rs index ecfe580202..e8fc420f8d 100644 --- a/kimchi/src/circuits/witness/variable_bits_cell.rs +++ b/kimchi/src/circuits/witness/variable_bits_cell.rs @@ -18,7 +18,7 @@ impl<'a> VariableBitsCell<'a> { } } -impl<'a, const N: usize, F: Field> WitnessCell for VariableBitsCell<'a> { +impl<'a, const N: usize, F: Field> WitnessCell for VariableBitsCell<'a> { fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { let bits = if let Some(end) = self.end { F::from_bits(&variables[self.name].to_bits()[self.start..end]) diff --git a/kimchi/src/circuits/witness/variable_cell.rs b/kimchi/src/circuits/witness/variable_cell.rs index 0462d757bd..d4dbad7786 100644 --- a/kimchi/src/circuits/witness/variable_cell.rs +++ b/kimchi/src/circuits/witness/variable_cell.rs @@ -14,7 +14,7 @@ impl<'a> VariableCell<'a> { } } -impl<'a, const N: usize, F: Field> WitnessCell for VariableCell<'a> { +impl<'a, const N: usize, F: Field> WitnessCell for VariableCell<'a> { fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { variables[self.name] } diff --git a/kimchi/src/circuits/witness/variables.rs b/kimchi/src/circuits/witness/variables.rs index 773b79c0bd..7a13c6e215 100644 --- a/kimchi/src/circuits/witness/variables.rs +++ b/kimchi/src/circuits/witness/variables.rs @@ -50,28 +50,28 @@ use std::{ /// /// Map of witness values (used by VariableCells) /// name (String) -> value (F) -pub struct Variables<'a, F: Field>(HashMap<&'a str, F>); +pub struct Variables<'a, T>(HashMap<&'a str, T>); -impl<'a, F: Field> Variables<'a, F> { +impl<'a, T> Variables<'a, T> { /// Create a layout variable map - pub fn create() -> Variables<'a, F> { + pub fn create() -> Variables<'a, T> { Variables(HashMap::new()) } /// Insert a variable and corresponding value into the variable map - pub fn insert(&mut self, name: &'a str, value: F) { + pub fn insert(&mut self, name: &'a str, value: T) { self.0.insert(name, value); } } -impl<'a, F: Field> Index<&'a str> for Variables<'a, F> { - type Output = F; +impl<'a, T> Index<&'a str> for Variables<'a, T> { + type Output = T; fn index(&self, name: &'a str) -> &Self::Output { &self.0[name] } } -impl<'a, F: Field> IndexMut<&'a str> for Variables<'a, F> { +impl<'a, T> IndexMut<&'a str> for Variables<'a, T> { fn index_mut(&mut self, name: &'a str) -> &mut Self::Output { self.0.get_mut(name).expect("failed to get witness value") } From 4980e5bfa00378e05334412b7654869ef8d2ceff Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 20 Sep 2023 10:40:04 +0200 Subject: [PATCH 048/173] rename ArrayCell to IndexCell --- .../witness/{array_cell.rs => index_cell.rs} | 12 ++++++------ kimchi/src/circuits/witness/mod.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) rename kimchi/src/circuits/witness/{array_cell.rs => index_cell.rs} (64%) diff --git a/kimchi/src/circuits/witness/array_cell.rs b/kimchi/src/circuits/witness/index_cell.rs similarity index 64% rename from kimchi/src/circuits/witness/array_cell.rs rename to kimchi/src/circuits/witness/index_cell.rs index f1ab0c1ac4..aed97ec44b 100644 --- a/kimchi/src/circuits/witness/array_cell.rs +++ b/kimchi/src/circuits/witness/index_cell.rs @@ -1,21 +1,21 @@ use super::{variables::Variables, WitnessCell}; use ark_ff::Field; -/// Witness cell assigned from a variable +/// Witness cell assigned from an indexable variable /// See [Variables] for more details -pub struct ArrayCell<'a> { +pub struct IndexCell<'a> { name: &'a str, index: usize, } -impl<'a> ArrayCell<'a> { +impl<'a> IndexCell<'a> { /// Create witness cell assigned from a variable name - pub fn create(name: &'a str, index: usize) -> Box> { - Box::new(ArrayCell { name, index }) + pub fn create(name: &'a str, index: usize) -> Box> { + Box::new(IndexCell { name, index }) } } -impl<'a, const N: usize, F: Field> WitnessCell> for ArrayCell<'a> { +impl<'a, const N: usize, F: Field> WitnessCell> for IndexCell<'a> { fn value(&self, _witness: &mut [Vec; N], variables: &Variables>) -> F { variables[self.name][self.index] } diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index 8825ad09ac..dce8cdc18e 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -1,19 +1,19 @@ use ark_ff::{Field, PrimeField}; -mod array_cell; mod constant_cell; mod copy_bits_cell; mod copy_cell; mod copy_shift_cell; +mod index_cell; mod variable_bits_cell; mod variable_cell; mod variables; pub use self::{ - array_cell::ArrayCell, constant_cell::ConstantCell, copy_bits_cell::CopyBitsCell, copy_cell::CopyCell, copy_shift_cell::CopyShiftCell, + index_cell::IndexCell, variable_bits_cell::VariableBitsCell, variable_cell::VariableCell, variables::{variable_map, variables, Variables}, From 6e51a783236e3114aac1f9af64ff712b223c439f Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 20 Sep 2023 11:36:49 +0200 Subject: [PATCH 049/173] give visibility to some effort trying to make WitnessCell compatible with vectors. Too many changes required. --- kimchi/src/circuits/witness/constant_cell.rs | 2 +- kimchi/src/circuits/witness/copy_bits_cell.rs | 2 +- kimchi/src/circuits/witness/copy_cell.rs | 2 +- kimchi/src/circuits/witness/copy_shift_cell.rs | 2 +- kimchi/src/circuits/witness/index_cell.rs | 13 +++++++------ kimchi/src/circuits/witness/mod.rs | 17 +++++++++++++---- .../src/circuits/witness/variable_bits_cell.rs | 2 +- kimchi/src/circuits/witness/variable_cell.rs | 2 +- 8 files changed, 26 insertions(+), 16 deletions(-) diff --git a/kimchi/src/circuits/witness/constant_cell.rs b/kimchi/src/circuits/witness/constant_cell.rs index 2feaee094a..f35d2179f4 100644 --- a/kimchi/src/circuits/witness/constant_cell.rs +++ b/kimchi/src/circuits/witness/constant_cell.rs @@ -14,7 +14,7 @@ impl ConstantCell { } impl WitnessCell for ConstantCell { - fn value(&self, _witness: &mut [Vec; N], _variables: &Variables) -> F { + fn value(&self, _witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { self.value } } diff --git a/kimchi/src/circuits/witness/copy_bits_cell.rs b/kimchi/src/circuits/witness/copy_bits_cell.rs index 0ff09f080c..69aae2a975 100644 --- a/kimchi/src/circuits/witness/copy_bits_cell.rs +++ b/kimchi/src/circuits/witness/copy_bits_cell.rs @@ -24,7 +24,7 @@ impl CopyBitsCell { } impl WitnessCell for CopyBitsCell { - fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { + fn value(&self, witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { F::from_bits(&witness[self.col][self.row].to_bits()[self.start..self.end]) .expect("failed to deserialize field bits for copy bits cell") } diff --git a/kimchi/src/circuits/witness/copy_cell.rs b/kimchi/src/circuits/witness/copy_cell.rs index a1157658fc..fd594a3844 100644 --- a/kimchi/src/circuits/witness/copy_cell.rs +++ b/kimchi/src/circuits/witness/copy_cell.rs @@ -16,7 +16,7 @@ impl CopyCell { } impl WitnessCell for CopyCell { - fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { + fn value(&self, witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { witness[self.col][self.row] } } diff --git a/kimchi/src/circuits/witness/copy_shift_cell.rs b/kimchi/src/circuits/witness/copy_shift_cell.rs index caa77812d1..b285092d2f 100644 --- a/kimchi/src/circuits/witness/copy_shift_cell.rs +++ b/kimchi/src/circuits/witness/copy_shift_cell.rs @@ -16,7 +16,7 @@ impl CopyShiftCell { } impl WitnessCell for CopyShiftCell { - fn value(&self, witness: &mut [Vec; N], _variables: &Variables) -> F { + fn value(&self, witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { F::from(2u32).pow([self.shift]) * witness[self.col][self.row] } } diff --git a/kimchi/src/circuits/witness/index_cell.rs b/kimchi/src/circuits/witness/index_cell.rs index aed97ec44b..fd2a5dc843 100644 --- a/kimchi/src/circuits/witness/index_cell.rs +++ b/kimchi/src/circuits/witness/index_cell.rs @@ -5,18 +5,19 @@ use ark_ff::Field; /// See [Variables] for more details pub struct IndexCell<'a> { name: &'a str, - index: usize, + length: usize, } impl<'a> IndexCell<'a> { - /// Create witness cell assigned from a variable name - pub fn create(name: &'a str, index: usize) -> Box> { - Box::new(IndexCell { name, index }) + /// Create witness cell assigned from a variable name a length + pub fn create(name: &'a str, length: usize) -> Box> { + Box::new(IndexCell { name, length }) } } impl<'a, const N: usize, F: Field> WitnessCell> for IndexCell<'a> { - fn value(&self, _witness: &mut [Vec; N], variables: &Variables>) -> F { - variables[self.name][self.index] + fn value(&self, _witness: &mut [Vec; N], variables: &Variables>, index: usize) -> F { + assert!(index < self.length, "index out of bounds of `IndexCell`"); + variables[self.name][index] } } diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index dce8cdc18e..2a34cdc350 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -1,4 +1,6 @@ use ark_ff::{Field, PrimeField}; +use std::any::{type_name, type_name_of_val}; + mod constant_cell; mod copy_bits_cell; mod copy_cell; @@ -21,7 +23,7 @@ pub use self::{ /// Witness cell interface pub trait WitnessCell { - fn value(&self, witness: &mut [Vec; N], variables: &Variables) -> F; + fn value(&self, witness: &mut [Vec; N], variables: &Variables, index: usize) -> F; } /// Initialize a witness cell based on layout and computed variables @@ -30,10 +32,11 @@ pub fn init_cell( offset: usize, row: usize, col: usize, + index: usize, layout: &[[Box>; N]], variables: &Variables, ) { - witness[col][row + offset] = layout[row][col].value(witness, variables); + witness[col + index][row + offset] = layout[row][col].value(witness, variables, index); } /// Initialize a witness row based on layout and computed variables @@ -44,8 +47,14 @@ pub fn init_row( layout: &[[Box>; N]], variables: &Variables, ) { - for col in 0..N { - init_cell(witness, offset, row, col, layout, variables); + for col in 0..layout[0].len() { + if type_name_of_val(&layout[row][col]) == type_name::() { + for index in 0..layout[row][col].length { + init_cell(witness, offset, row, col, index, layout, variables); + } + } else { + init_cell(witness, offset, row, col, 0, layout, variables); + } } } diff --git a/kimchi/src/circuits/witness/variable_bits_cell.rs b/kimchi/src/circuits/witness/variable_bits_cell.rs index e8fc420f8d..bd414a7281 100644 --- a/kimchi/src/circuits/witness/variable_bits_cell.rs +++ b/kimchi/src/circuits/witness/variable_bits_cell.rs @@ -19,7 +19,7 @@ impl<'a> VariableBitsCell<'a> { } impl<'a, const N: usize, F: Field> WitnessCell for VariableBitsCell<'a> { - fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { + fn value(&self, _witness: &mut [Vec; N], variables: &Variables, _index: usize) -> F { let bits = if let Some(end) = self.end { F::from_bits(&variables[self.name].to_bits()[self.start..end]) } else { diff --git a/kimchi/src/circuits/witness/variable_cell.rs b/kimchi/src/circuits/witness/variable_cell.rs index d4dbad7786..c875326583 100644 --- a/kimchi/src/circuits/witness/variable_cell.rs +++ b/kimchi/src/circuits/witness/variable_cell.rs @@ -15,7 +15,7 @@ impl<'a> VariableCell<'a> { } impl<'a, const N: usize, F: Field> WitnessCell for VariableCell<'a> { - fn value(&self, _witness: &mut [Vec; N], variables: &Variables) -> F { + fn value(&self, _witness: &mut [Vec; N], variables: &Variables, _index: usize) -> F { variables[self.name] } } From 7e0958f9223630762809bdd34ee9df2b8d84842f Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 20 Sep 2023 14:41:28 +0200 Subject: [PATCH 050/173] made WitnessCell more generic to support variables with arbitrary type and any number of columns --- .../polynomials/foreign_field_add/witness.rs | 14 ++-- .../polynomials/foreign_field_mul/witness.rs | 6 +- .../polynomials/range_check/witness.rs | 12 +-- kimchi/src/circuits/polynomials/rot.rs | 8 +- kimchi/src/circuits/polynomials/xor.rs | 10 +-- kimchi/src/circuits/witness/index_cell.rs | 3 + kimchi/src/circuits/witness/mod.rs | 74 +++++++++---------- 7 files changed, 63 insertions(+), 64 deletions(-) diff --git a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs index 1487b9f85e..57af9b6ff0 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs @@ -185,9 +185,9 @@ fn init_ffadd_row( overflow: F, carry: F, ) { - let witness_shape: Vec<[Box>; COLUMNS]> = vec![ + let layout: [Vec>>; 1] = [ // ForeignFieldAdd row - [ + vec![ VariableCell::create("left_lo"), VariableCell::create("left_mi"), VariableCell::create("left_hi"), @@ -209,7 +209,7 @@ fn init_ffadd_row( witness::init( witness, offset, - &witness_shape, + &layout, &variable_map!["left_lo" => left[LO], "left_mi" => left[MI], "left_hi" => left[HI], "right_lo" => right[LO], "right_mi" => right[MI], "right_hi" => right[HI], "overflow" => overflow, "carry" => carry], ); } @@ -221,8 +221,8 @@ fn init_bound_rows( bound: &[F; 3], carry: &F, ) { - let witness_shape: Vec<[Box>; COLUMNS]> = vec![ - [ + let layout: [Vec>>; 2] = [ + vec![ // ForeignFieldAdd row VariableCell::create("result_lo"), VariableCell::create("result_mi"), @@ -240,7 +240,7 @@ fn init_bound_rows( ConstantCell::create(F::zero()), ConstantCell::create(F::zero()), ], - [ + vec![ // Zero Row VariableCell::create("bound_lo"), VariableCell::create("bound_mi"), @@ -263,7 +263,7 @@ fn init_bound_rows( witness::init( witness, offset, - &witness_shape, + &layout, &variable_map!["carry" => *carry, "result_lo" => result[LO], "result_mi" => result[MI], "result_hi" => result[HI], "bound_lo" => bound[LO], "bound_mi" => bound[MI], "bound_hi" => bound[HI]], ); } diff --git a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs index 7a8e64710a..50fb4d2305 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs @@ -41,10 +41,10 @@ use super::circuitgates; // // so that most significant limb, q2, is in W[2][0]. // -fn create_layout() -> [[Box>; COLUMNS]; 2] { +fn create_layout() -> [Vec>>; 2] { [ // ForeignFieldMul row - [ + vec![ // Copied for multi-range-check VariableCell::create("left_input0"), VariableCell::create("left_input1"), @@ -64,7 +64,7 @@ fn create_layout() -> [[Box>; COLU VariableBitsCell::create("carry1", 90, None), ], // Zero row - [ + vec![ // Copied for multi-range-check VariableCell::create("remainder01"), VariableCell::create("remainder2"), diff --git a/kimchi/src/circuits/polynomials/range_check/witness.rs b/kimchi/src/circuits/polynomials/range_check/witness.rs index aacbe2ffd5..e6fd60987f 100644 --- a/kimchi/src/circuits/polynomials/range_check/witness.rs +++ b/kimchi/src/circuits/polynomials/range_check/witness.rs @@ -29,14 +29,14 @@ use o1_utils::foreign_field::BigUintForeignFieldHelpers; /// For example, we can convert the `RangeCheck0` circuit gate into /// a 64-bit lookup by adding two copy constraints to constrain /// columns 1 and 2 to zero. -fn layout() -> Vec<[Box>; COLUMNS]> { - vec![ +fn layout() -> [Vec>>; 4] { + [ /* row 1, RangeCheck0 row */ range_check_0_row("v0", 0), /* row 2, RangeCheck0 row */ range_check_0_row("v1", 1), /* row 3, RangeCheck1 row */ - [ + vec![ VariableCell::create("v2"), VariableCell::create("v12"), // optional /* 2-bit crumbs (placed here to keep lookup pattern */ @@ -58,7 +58,7 @@ fn layout() -> Vec<[Box>; COLUMNS] CopyBitsCell::create(2, 0, 22, 24), ], /* row 4, Zero row */ - [ + vec![ CopyBitsCell::create(2, 0, 20, 22), /* 2-bit crumbs (placed here to keep lookup pattern */ /* the same as RangeCheck0) */ @@ -86,8 +86,8 @@ fn layout() -> Vec<[Box>; COLUMNS] pub fn range_check_0_row( limb_name: &'static str, row: usize, -) -> [Box>; COLUMNS] { - [ +) -> Vec>> { + vec![ VariableCell::create(limb_name), /* 12-bit copies */ // Copy cells are required because we have a limit diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index c60106efc2..a0bc442a24 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -266,9 +266,7 @@ where // ROTATION WITNESS COMPUTATION -fn layout_rot64( - curr_row: usize, -) -> [[Box>; COLUMNS]; 3] { +fn layout_rot64(curr_row: usize) -> [Vec>>; 3] { [ rot_row(), range_check_0_row("shifted", curr_row + 1), @@ -276,8 +274,8 @@ fn layout_rot64( ] } -fn rot_row() -> [Box>; COLUMNS] { - [ +fn rot_row() -> Vec>> { + vec![ VariableCell::create("word"), VariableCell::create("rotated"), VariableCell::create("excess"), diff --git a/kimchi/src/circuits/polynomials/xor.rs b/kimchi/src/circuits/polynomials/xor.rs index 1ade505af8..a7913f92a7 100644 --- a/kimchi/src/circuits/polynomials/xor.rs +++ b/kimchi/src/circuits/polynomials/xor.rs @@ -172,7 +172,7 @@ where fn layout( curr_row: usize, bits: usize, -) -> Vec<[Box>; COLUMNS]> { +) -> Vec>>> { let num_xor = num_xors(bits); let mut layout = (0..num_xor) .map(|i| xor_row(i, curr_row + i)) @@ -184,9 +184,9 @@ fn layout( fn xor_row( nybble: usize, curr_row: usize, -) -> [Box>; COLUMNS] { +) -> Vec>> { let start = nybble * 16; - [ + vec![ VariableBitsCell::create("in1", start, None), VariableBitsCell::create("in2", start, None), VariableBitsCell::create("out", start, None), @@ -205,8 +205,8 @@ fn xor_row( ] } -fn zero_row() -> [Box>; COLUMNS] { - [ +fn zero_row() -> Vec>> { + vec![ ConstantCell::create(F::zero()), ConstantCell::create(F::zero()), ConstantCell::create(F::zero()), diff --git a/kimchi/src/circuits/witness/index_cell.rs b/kimchi/src/circuits/witness/index_cell.rs index fd2a5dc843..7ec17ed6c2 100644 --- a/kimchi/src/circuits/witness/index_cell.rs +++ b/kimchi/src/circuits/witness/index_cell.rs @@ -20,4 +20,7 @@ impl<'a, const N: usize, F: Field> WitnessCell> for IndexCell<'a> { assert!(index < self.length, "index out of bounds of `IndexCell`"); variables[self.name][index] } + fn length(&self) -> usize { + self.length + } } diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index 2a34cdc350..e764382f49 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -1,5 +1,4 @@ use ark_ff::{Field, PrimeField}; -use std::any::{type_name, type_name_of_val}; mod constant_cell; mod copy_bits_cell; @@ -24,46 +23,46 @@ pub use self::{ /// Witness cell interface pub trait WitnessCell { fn value(&self, witness: &mut [Vec; N], variables: &Variables, index: usize) -> F; + + fn length(&self) -> usize { + 1 + } } /// Initialize a witness cell based on layout and computed variables -pub fn init_cell( +pub fn init_cell( witness: &mut [Vec; N], offset: usize, row: usize, col: usize, index: usize, - layout: &[[Box>; N]], - variables: &Variables, + layout: &[Vec>>], + variables: &Variables, ) { witness[col + index][row + offset] = layout[row][col].value(witness, variables, index); } /// Initialize a witness row based on layout and computed variables -pub fn init_row( +pub fn init_row( witness: &mut [Vec; N], offset: usize, row: usize, - layout: &[[Box>; N]], - variables: &Variables, + layout: &[Vec>>], + variables: &Variables, ) { for col in 0..layout[0].len() { - if type_name_of_val(&layout[row][col]) == type_name::() { - for index in 0..layout[row][col].length { - init_cell(witness, offset, row, col, index, layout, variables); - } - } else { - init_cell(witness, offset, row, col, 0, layout, variables); + for index in 0..layout[row][col].length() { + init_cell(witness, offset, row, col, index, layout, variables); } } } /// Initialize a witness based on layout and computed variables -pub fn init( +pub fn init( witness: &mut [Vec; N], offset: usize, - layout: &[[Box>; N]], - variables: &Variables, + layout: &[Vec>>], + variables: &Variables, ) { for row in 0..layout.len() { init_row(witness, offset, row, layout, variables); @@ -84,24 +83,23 @@ mod tests { #[test] fn zero_layout() { - let layout: Vec<[Box>; COLUMNS]> = - vec![[ - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ConstantCell::create(PallasField::zero()), - ]]; + let layout: Vec>>> = vec![vec![ + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ConstantCell::create(PallasField::zero()), + ]]; let mut witness: [Vec; COLUMNS] = array::from_fn(|_| vec![PallasField::one(); 1]); @@ -113,7 +111,7 @@ mod tests { } // Set a single cell to zero - init_cell(&mut witness, 0, 0, 4, &layout, &variables!()); + init_cell(&mut witness, 0, 0, 4, 0, &layout, &variables!()); assert_eq!(witness[4][0], PallasField::zero()); // Set all the cells to zero @@ -128,8 +126,8 @@ mod tests { #[test] fn mixed_layout() { - let layout: Vec<[Box>; COLUMNS]> = vec![ - [ + let layout: Vec>>> = vec![ + vec![ ConstantCell::create(PallasField::from(12u32)), ConstantCell::create(PallasField::from(0xa5a3u32)), ConstantCell::create(PallasField::from(0x800u32)), @@ -146,7 +144,7 @@ mod tests { ConstantCell::create(PallasField::zero()), ConstantCell::create(PallasField::zero()), ], - [ + vec![ CopyCell::create(0, 0), CopyBitsCell::create(0, 1, 4, 8), CopyShiftCell::create(0, 2, 8), From 78e3a5746f3bec054d427121997baf0aece17cd3 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 20 Sep 2023 14:49:37 +0200 Subject: [PATCH 051/173] modified IndexCell creation to have from-to, and create sponge layout --- kimchi/src/circuits/witness/index_cell.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/witness/index_cell.rs b/kimchi/src/circuits/witness/index_cell.rs index 7ec17ed6c2..e865495b88 100644 --- a/kimchi/src/circuits/witness/index_cell.rs +++ b/kimchi/src/circuits/witness/index_cell.rs @@ -10,8 +10,11 @@ pub struct IndexCell<'a> { impl<'a> IndexCell<'a> { /// Create witness cell assigned from a variable name a length - pub fn create(name: &'a str, length: usize) -> Box> { - Box::new(IndexCell { name, length }) + pub fn create(name: &'a str, from: usize, to: usize) -> Box> { + Box::new(IndexCell { + name, + length: from - to, + }) } } From 34862813f2e4397c2995b7b8be5cd7a9d4f01adf Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 20 Sep 2023 16:10:41 +0200 Subject: [PATCH 052/173] add support for arbitrary shape layouts for cell initialization --- kimchi/src/circuits/witness/mod.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index e764382f49..2e75d02cdb 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -30,16 +30,26 @@ pub trait WitnessCell { } /// Initialize a witness cell based on layout and computed variables +/// Inputs: +/// - witness: the witness to initialize with values +/// - offset: the row offset of the witness before initialization +/// - row: the row index inside the partial layout +/// - col: the column index inside the witness +/// - cell: the cell index inside the partial layout (for any but IndexCell, it must be the same as col) +/// - index: the index within the variable (for IndexCell, 0 otherwise) +/// - layout: the partial layout to initialize from +/// - variables: the hashmap of variables to get the values from pub fn init_cell( witness: &mut [Vec; N], offset: usize, row: usize, col: usize, + cell: usize, index: usize, layout: &[Vec>>], variables: &Variables, ) { - witness[col + index][row + offset] = layout[row][col].value(witness, variables, index); + witness[col][row + offset] = layout[row][cell].value(witness, variables, index); } /// Initialize a witness row based on layout and computed variables @@ -50,9 +60,12 @@ pub fn init_row( layout: &[Vec>>], variables: &Variables, ) { - for col in 0..layout[0].len() { - for index in 0..layout[row][col].length() { - init_cell(witness, offset, row, col, index, layout, variables); + let mut col = 0; + for cell in 0..layout[row].len() { + // The loop will only run more than once if the cell is an IndexCell + for index in 0..layout[row][cell].length() { + init_cell(witness, offset, row, col, cell, index, layout, variables); + col += 1; } } } @@ -111,7 +124,7 @@ mod tests { } // Set a single cell to zero - init_cell(&mut witness, 0, 0, 4, 0, &layout, &variables!()); + init_cell(&mut witness, 0, 0, 4, 4, 0, &layout, &variables!()); assert_eq!(witness[4][0], PallasField::zero()); // Set all the cells to zero From 980ba5dc7ad9317d35d3463d6b82c651800c768c Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 20 Sep 2023 18:17:44 +0200 Subject: [PATCH 053/173] fix clippy errors --- kimchi/src/circuits/witness/mod.rs | 1 + kimchi/src/circuits/witness/variables.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index 2e75d02cdb..ec470330d9 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -39,6 +39,7 @@ pub trait WitnessCell { /// - index: the index within the variable (for IndexCell, 0 otherwise) /// - layout: the partial layout to initialize from /// - variables: the hashmap of variables to get the values from +#[allow(clippy::too_many_arguments)] pub fn init_cell( witness: &mut [Vec; N], offset: usize, diff --git a/kimchi/src/circuits/witness/variables.rs b/kimchi/src/circuits/witness/variables.rs index 7a13c6e215..2327d62315 100644 --- a/kimchi/src/circuits/witness/variables.rs +++ b/kimchi/src/circuits/witness/variables.rs @@ -1,4 +1,3 @@ -use ark_ff::Field; /// Layout variable handling /// /// First, you use "anchor" names for the variables when specifying From 56c63dab5b4ddac05b0afde4f4dfad90b58b92d0 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 26 Sep 2023 18:09:34 +0200 Subject: [PATCH 054/173] fix typo causing overflow at subtraction --- kimchi/src/circuits/witness/index_cell.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kimchi/src/circuits/witness/index_cell.rs b/kimchi/src/circuits/witness/index_cell.rs index e865495b88..a039354c8f 100644 --- a/kimchi/src/circuits/witness/index_cell.rs +++ b/kimchi/src/circuits/witness/index_cell.rs @@ -13,7 +13,7 @@ impl<'a> IndexCell<'a> { pub fn create(name: &'a str, from: usize, to: usize) -> Box> { Box::new(IndexCell { name, - length: from - to, + length: to - from, }) } } From fdf2849d3118cc7d9b6c55a74e54f913b9b87fc2 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 19 Oct 2023 13:13:14 +0200 Subject: [PATCH 055/173] keccak witness layout --- kimchi/src/circuits/polynomials/keccak/mod.rs | 5 ++- .../circuits/polynomials/keccak/witness.rs | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 kimchi/src/circuits/polynomials/keccak/witness.rs diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 538159e1b5..db4dbdbec5 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -1,13 +1,14 @@ //! Keccak hash module pub mod circuitgates; pub mod gadget; +pub mod witness; pub const DIM: usize = 5; pub const QUARTERS: usize = 4; pub const ROUNDS: usize = 24; -pub const RATE: usize = 1088 / 8; -pub const CAPACITY: usize = 512 / 8; +pub const RATE: usize = 136; pub const KECCAK_COLS: usize = 2344; +pub const CAPACITY: usize = 512 / 8; use crate::circuits::expr::constraints::ExprOps; use ark_ff::PrimeField; diff --git a/kimchi/src/circuits/polynomials/keccak/witness.rs b/kimchi/src/circuits/polynomials/keccak/witness.rs new file mode 100644 index 0000000000..7d756f639f --- /dev/null +++ b/kimchi/src/circuits/polynomials/keccak/witness.rs @@ -0,0 +1,45 @@ +//! Keccak witness computation + +use crate::circuits::witness::{IndexCell, WitnessCell}; +use ark_ff::PrimeField; + +use super::KECCAK_COLS; + +type _Layout = Vec>>>; + +fn _layout_round() -> _Layout { + vec![ + IndexCell::create("state_a", 0, 100), + IndexCell::create("state_c", 100, 120), + IndexCell::create("shifts_c", 120, 200), + IndexCell::create("dense_c", 200, 220), + IndexCell::create("quotient_c", 220, 240), + IndexCell::create("remainder_c", 240, 260), + IndexCell::create("bound_c", 260, 280), + IndexCell::create("dense_rot_c", 280, 300), + IndexCell::create("expand_rot_c", 300, 320), + IndexCell::create("state_d", 320, 340), + IndexCell::create("state_e", 340, 440), + IndexCell::create("shifts_e", 440, 840), + IndexCell::create("dense_e", 840, 940), + IndexCell::create("quotient_e", 940, 1040), + IndexCell::create("remainder_e", 1040, 1140), + IndexCell::create("bound_e", 1140, 1240), + IndexCell::create("dense_rot_e", 1240, 1340), + IndexCell::create("expand_rot_e", 1340, 1440), + IndexCell::create("state_b", 1440, 1540), + IndexCell::create("shifts_b", 1540, 1940), + IndexCell::create("shifts_sum", 1940, 2340), + IndexCell::create("f00", 2340, 2344), + ] +} + +fn _layout_sponge() -> _Layout { + vec![ + IndexCell::create("old_state", 0, 100), + IndexCell::create("new_state", 100, 200), + IndexCell::create("dense", 200, 300), + IndexCell::create("bytes", 300, 500), + IndexCell::create("shifts", 500, 900), + ] +} From f17b5e0c6728847fdf8a93e775412b20b8bb7498 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 19 Oct 2023 13:18:57 +0200 Subject: [PATCH 056/173] add structs to compute witness modularly for each permutation step, and auxiliary functions to handle the sparse state --- kimchi/src/circuits/polynomials/keccak/mod.rs | 85 +++- .../circuits/polynomials/keccak/witness.rs | 422 +++++++++++++++++- 2 files changed, 493 insertions(+), 14 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index db4dbdbec5..d610c3b737 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -6,9 +6,9 @@ pub mod witness; pub const DIM: usize = 5; pub const QUARTERS: usize = 4; pub const ROUNDS: usize = 24; -pub const RATE: usize = 136; -pub const KECCAK_COLS: usize = 2344; +pub const RATE: usize = 1088 / 8; pub const CAPACITY: usize = 512 / 8; +pub const KECCAK_COLS: usize = 2344; use crate::circuits::expr::constraints::ExprOps; use ark_ff::PrimeField; @@ -66,6 +66,11 @@ pub(crate) const RC: [u64; 24] = [ 0x8000000080008008, ]; +// Composes a vector of 4 dense quarters into the dense full u64 word +pub(crate) fn compose(quarters: &[u64]) -> u64 { + quarters[0] + (1 << 16) * quarters[1] + (1 << 32) * quarters[2] + (1 << 48) * quarters[3] +} + // Takes a dense u64 word and decomposes it into a vector of 4 dense quarters pub(crate) fn decompose(word: u64) -> Vec { vec![ @@ -89,6 +94,82 @@ pub(crate) fn expand_word>(word: u64) -> Vec { .collect::>() } +/// Pads the message with the 10*1 rule until reaching a length that is a multiple of the rate +pub(crate) fn pad(message: &[u8]) -> Vec { + let mut padded = message.to_vec(); + padded.push(0x01); + while padded.len() % 136 != 0 { + padded.push(0x00); + } + let last = padded.len() - 1; + padded[last] += 0x80; + padded +} + +/// From each quarter in sparse representation, it computes its 4 resets. +/// The resulting vector contains 4 times as many elements as the input. +/// The output is placed in the vector as [reset0, reset1, reset2, reset3] +pub(crate) fn shift(state: &[u64]) -> Vec { + let mut shifts = vec![vec![]; 4]; + let aux = expand(0xFFFF); + for term in state { + shifts[0].push(aux & term); // shift0 = reset0 + shifts[1].push(((aux << 1) & term) / 2); // shift1 = reset1/2 + shifts[2].push(((aux << 2) & term) / 4); // shift2 = reset2/4 + shifts[3].push(((aux << 3) & term) / 8); // shift3 = reset3/8 + } + shifts.iter().flatten().copied().collect() +} + +/// From a vector of shifts, resets the underlying value returning only shift0 +pub(crate) fn reset(shifts: &[u64]) -> Vec { + shifts + .iter() + .copied() + .take(shifts.len() / 4) + .collect::>() +} + +/// From a reset0 state, obtain the corresponding 16-bit dense terms +pub(crate) fn collapse(state: &[u64]) -> Vec { + let mut dense = vec![]; + for reset in state { + dense.push(u64::from_str_radix(&format!("{:x}", reset), 2).unwrap()); + } + dense +} + +/// Outputs the state into dense quarters of 16-bits each in little endian order +pub(crate) fn quarters(state: &[u8]) -> Vec { + let mut quarters = vec![]; + for pair in state.chunks(2) { + quarters.push(u16::from_le_bytes([pair[0], pair[1]]) as u64); + } + quarters +} + +/// On input a vector of 16-bit dense quarters, outputs a vector of 8-bit bytes in the right order for Keccak +pub(crate) fn bytestring(dense: &[u64]) -> Vec { + dense + .iter() + .map(|x| vec![x % 256, x / 256]) + .collect::>>() + .iter() + .flatten() + .copied() + .collect() +} + +/// On input a 200-byte vector, generates a vector of 100 expanded quarters representing the 1600-bit state +pub(crate) fn expand_state(state: &[u8]) -> Vec { + let mut expanded = vec![]; + for pair in state.chunks(2) { + let quarter = u16::from_le_bytes([pair[0], pair[1]]); + expanded.push(expand(quarter as u64)); + } + expanded +} + /// On input a length, returns the smallest multiple of RATE that is greater than the bytelength pub(crate) fn padded_length(bytelength: usize) -> usize { (bytelength / RATE + 1) * RATE diff --git a/kimchi/src/circuits/polynomials/keccak/witness.rs b/kimchi/src/circuits/polynomials/keccak/witness.rs index 7d756f639f..861318b82e 100644 --- a/kimchi/src/circuits/polynomials/keccak/witness.rs +++ b/kimchi/src/circuits/polynomials/keccak/witness.rs @@ -1,14 +1,28 @@ //! Keccak witness computation -use crate::circuits::witness::{IndexCell, WitnessCell}; +use std::array; + +use crate::circuits::polynomials::keccak::{compose, decompose, expand_state, quarters, RC}; +use crate::{ + auto_clone, + circuits::{ + polynomials::keccak::ROUNDS, + witness::{self, IndexCell, Variables, WitnessCell}, + }, + state_from_vec, variable_map, +}; use ark_ff::PrimeField; +use num_bigint::BigUint; -use super::KECCAK_COLS; +use super::{ + bytestring, collapse, expand, pad, reset, shift, CAPACITY, DIM, KECCAK_COLS, OFF, QUARTERS, + RATE, +}; -type _Layout = Vec>>>; +type Layout = Vec>>>; -fn _layout_round() -> _Layout { - vec![ +fn layout_round() -> [Layout; 1] { + [vec![ IndexCell::create("state_a", 0, 100), IndexCell::create("state_c", 100, 120), IndexCell::create("shifts_c", 120, 200), @@ -31,15 +45,399 @@ fn _layout_round() -> _Layout { IndexCell::create("shifts_b", 1540, 1940), IndexCell::create("shifts_sum", 1940, 2340), IndexCell::create("f00", 2340, 2344), - ] + ]] } -fn _layout_sponge() -> _Layout { - vec![ +fn layout_sponge() -> [Layout; 1] { + [vec![ IndexCell::create("old_state", 0, 100), IndexCell::create("new_state", 100, 200), - IndexCell::create("dense", 200, 300), - IndexCell::create("bytes", 300, 500), - IndexCell::create("shifts", 500, 900), - ] + IndexCell::create("bytes", 200, 400), + IndexCell::create("shifts", 400, 800), + ]] +} + +// Transforms a vector of u64 into a vector of field elements +fn field(input: &[u64]) -> Vec { + input.iter().map(|x| F::from(*x)).collect::>() +} + +// Contains the quotient, remainder, bound, dense rotated as quarters of at most 16 bits each +// Contains the expansion of the rotated word +struct Rotation { + quotient: Vec, + remainder: Vec, + bound: Vec, + dense_rot: Vec, + expand_rot: Vec, +} + +impl Rotation { + // Returns rotation of 0 bits + fn none(dense: &[u64]) -> Self { + Self { + quotient: vec![0; QUARTERS], + remainder: dense.to_vec(), + bound: vec![0xFFFF; QUARTERS], + dense_rot: dense.to_vec(), + expand_rot: dense.iter().map(|x| expand(*x)).collect(), + } + } + + // On input the dense quarters of a word, rotate the word offset bits to the left + fn new(dense: &[u64], offset: u32) -> Self { + if offset == 0 { + return Self::none(dense); + } + let word = compose(dense); + let rem = (word as u128 * 2u128.pow(offset) % 2u128.pow(64)) as u64; + let quo = word / 2u64.pow(64 - offset); + let bnd = (quo as u128) + 2u128.pow(64) - 2u128.pow(offset); + let rot = rem + quo; + assert!(rot == word.rotate_left(offset)); + + Self { + quotient: decompose(quo), + remainder: decompose(rem), + bound: decompose(bnd as u64), + dense_rot: decompose(rot), + expand_rot: decompose(rot).iter().map(|x| expand(*x)).collect(), + } + } + + // On input the dense quarters of many words, rotate the word offset bits to the left + fn many(words: &[u64], offsets: &[u32]) -> Self { + assert!(words.len() == QUARTERS * offsets.len()); + let mut quotient = vec![]; + let mut remainder = vec![]; + let mut bound = vec![]; + let mut dense_rot = vec![]; + let mut expand_rot = vec![]; + for (word, offset) in words.chunks(QUARTERS).zip(offsets.iter()) { + let mut rot = Self::new(word, *offset); + quotient.append(&mut rot.quotient); + remainder.append(&mut rot.remainder); + bound.append(&mut rot.bound); + dense_rot.append(&mut rot.dense_rot); + expand_rot.append(&mut rot.expand_rot); + } + Self { + quotient, + remainder, + bound, + dense_rot, + expand_rot, + } + } +} + +struct Theta { + state_c: Vec, + shifts_c: Vec, + dense_c: Vec, + quotient_c: Vec, + remainder_c: Vec, + bound_c: Vec, + dense_rot_c: Vec, + expand_rot_c: Vec, + state_d: Vec, + state_e: Vec, +} + +impl Theta { + fn create(state_a: &[u64]) -> Self { + let state_c = Self::compute_state_c(state_a); + let shifts_c = shift(&state_c); + let dense_c = collapse(&reset(&shifts_c)); + let rotation_c = Rotation::many(&dense_c, &[1; DIM]); + let state_d = Self::compute_state_d(&shifts_c, &rotation_c.expand_rot); + let state_e = Self::compute_state_e(state_a, &state_d); + Self { + state_c, + shifts_c, + dense_c, + quotient_c: rotation_c.quotient, + remainder_c: rotation_c.remainder, + bound_c: rotation_c.bound, + dense_rot_c: rotation_c.dense_rot, + expand_rot_c: rotation_c.expand_rot, + state_d, + state_e, + } + } + + fn compute_state_c(state_a: &[u64]) -> Vec { + let state_a = state_from_vec!(state_a); + let mut state_c = vec![]; + for x in 0..DIM { + for q in 0..QUARTERS { + state_c.push( + state_a(0, x, 0, q) + + state_a(0, x, 1, q) + + state_a(0, x, 2, q) + + state_a(0, x, 3, q) + + state_a(0, x, 4, q), + ); + } + } + state_c + } + + fn compute_state_d(shifts_c: &[u64], expand_rot_c: &[u64]) -> Vec { + let shifts_c = state_from_vec!(shifts_c); + let expand_rot_c = state_from_vec!(expand_rot_c); + let mut state_d = vec![]; + for x in 0..DIM { + for q in 0..QUARTERS { + state_d.push( + shifts_c(0, (x + DIM - 1) % DIM, 0, q) + expand_rot_c(0, (x + 1) % DIM, 0, q), + ); + } + } + state_d + } + + fn compute_state_e(state_a: &[u64], state_d: &[u64]) -> Vec { + let state_a = state_from_vec!(state_a); + let state_d = state_from_vec!(state_d); + let mut state_e = vec![]; + for y in 0..DIM { + for x in 0..DIM { + for q in 0..QUARTERS { + state_e.push(state_a(0, x, y, q) + state_d(0, x, 0, q)); + } + } + } + state_e + } +} + +struct PiRho { + shifts_e: Vec, + dense_e: Vec, + quotient_e: Vec, + remainder_e: Vec, + bound_e: Vec, + dense_rot_e: Vec, + expand_rot_e: Vec, + state_b: Vec, +} + +impl PiRho { + fn create(state_e: &[u64]) -> Self { + let shifts_e = shift(state_e); + let dense_e = collapse(&reset(&shifts_e)); + let rotation_e = Rotation::many( + &dense_e, + &OFF.iter() + .flatten() + .map(|x| *x as u32) + .collect::>(), + ); + + let mut state_b = vec![vec![vec![0; QUARTERS]; DIM]; DIM]; + let aux = state_from_vec!(rotation_e.expand_rot); + for y in 0..DIM { + for x in 0..DIM { + for q in 0..QUARTERS { + state_b[(2 * x + 3 * y) % DIM][y][q] = aux(0, x, y, q); + } + } + } + let state_b = state_b.iter().flatten().flatten().copied().collect(); + + Self { + shifts_e, + dense_e, + quotient_e: rotation_e.quotient, + remainder_e: rotation_e.remainder, + bound_e: rotation_e.bound, + dense_rot_e: rotation_e.dense_rot, + expand_rot_e: rotation_e.expand_rot, + state_b, + } + } +} + +struct Chi { + shifts_b: Vec, + shifts_sum: Vec, + state_f: Vec, +} + +impl Chi { + fn create(state_b: &[u64]) -> Self { + let shifts_b = shift(state_b); + let shiftsb = state_from_vec!(shifts_b); + let mut sum = vec![]; + for y in 0..DIM { + for x in 0..DIM { + for q in 0..QUARTERS { + let not = 0x1111111111111111u64 - shiftsb(0, (x + 1) % DIM, y, q); + sum.push(not + shiftsb(0, (x + 2) % DIM, y, q)); + } + } + } + let shifts_sum = shift(&sum); + let shiftsum = state_from_vec!(shifts_sum); + let mut state_f = vec![]; + for y in 0..DIM { + for x in 0..DIM { + for q in 0..QUARTERS { + let and = shiftsum(1, x, y, q); + state_f.push(shiftsb(0, x, y, q) + and); + } + } + } + + Self { + shifts_b, + shifts_sum, + state_f, + } + } +} + +struct Iota { + state_g: Vec, +} + +impl Iota { + fn create(state_f: Vec, round: usize) -> Self { + let rc = decompose(RC[round]) + .iter() + .map(|x| expand(*x)) + .collect::>(); + let mut state_g = state_f.clone(); + for (i, c) in rc.iter().enumerate() { + state_g[i] = state_f[i] + *c; + } + Self { state_g } + } +} + +/// Creates a witness for the Keccak hash function +/// Input: +/// - message: the message to be hashed +/// Note: +/// Requires at least one more row after the keccak gadget so that +/// constraints can access the next row in the squeeze +pub fn extend_keccak_witness(witness: &mut [Vec; KECCAK_COLS], message: BigUint) { + let padded = pad(&message.to_bytes_be()); + let chunks = padded.chunks(RATE); + + // The number of rows that need to be added to the witness correspond to + // - Absorb phase: + // - 1 per block for the sponge row + // - 24 for the rounds + // - Squeeze phase: + // - 1 for the final sponge row + let rows: usize = chunks.len() * (ROUNDS + 1) + 1; + + let mut keccak_witness = array::from_fn(|_| vec![F::zero(); rows]); + + // Absorb phase + let mut row = 0; + let mut state = vec![0; QUARTERS * DIM * DIM]; + for chunk in chunks { + let mut block = chunk.to_vec(); + // Pad the block until reaching 200 bytes + block.append(&mut vec![0; CAPACITY]); + let dense = quarters(&block); + let new_state = expand_state(&block); + auto_clone!(new_state); + let shifts = shift(&new_state()); + let bytes = block.iter().map(|b| *b as u64).collect::>(); + + // Initialize the absorb sponge row + witness::init( + &mut keccak_witness, + row, + &layout_sponge(), + &variable_map!["old_state" => field(&state), "new_state" => field(&new_state()), "dense" => field(&dense), "bytes" => field(&bytes), "shifts" => field(&shifts)], + ); + row += 1; + + let xor_state = state + .iter() + .zip(new_state()) + .map(|(x, y)| x + y) + .collect::>(); + + let mut ini_state = xor_state.clone(); + + for round in 0..ROUNDS { + // Theta + let theta = Theta::create(&ini_state); + + // PiRho + let pirho = PiRho::create(&theta.state_e); + + // Chi + let chi = Chi::create(&pirho.state_b); + let f00 = chi + .state_f + .clone() + .into_iter() + .take(QUARTERS) + .collect::>(); + + // Iota + let iota = Iota::create(chi.state_f, round); + + // Initialize the round row + witness::init( + &mut keccak_witness, + row, + &layout_round(), + &variable_map![ + "state_a" => field(&ini_state), + "state_c" => field(&theta.state_c), + "shifts_c" => field(&theta.shifts_c), + "dense_c" => field(&theta.dense_c), + "quotient_c" => field(&theta.quotient_c), + "remainder_c" => field(&theta.remainder_c), + "bound_c" => field(&theta.bound_c), + "dense_rot_c" => field(&theta.dense_rot_c), + "expand_rot_c" => field(&theta.expand_rot_c), + "state_d" => field(&theta.state_d), + "state_e" => field(&theta.state_e), + "shifts_e" => field(&pirho.shifts_e), + "dense_e" => field(&pirho.dense_e), + "quotient_e" => field(&pirho.quotient_e), + "remainder_e" => field(&pirho.remainder_e), + "bound_e" => field(&pirho.bound_e), + "dense_rot_e" => field(&pirho.dense_rot_e), + "expand_rot_e" => field(&pirho.expand_rot_e), + "state_b" => field(&pirho.state_b), + "shifts_b" => field(&chi.shifts_b), + "shifts_sum" => field(&chi.shifts_sum), + "f00" => field(&f00) + ], + ); + row += 1; + ini_state = iota.state_g; + } + // update state after rounds + state = ini_state; + } + + // Squeeze phase + + let new_state = vec![0; QUARTERS * DIM * DIM]; + let shifts = shift(&state); + let dense = collapse(&reset(&shifts)); + let bytes = bytestring(&dense); + + // Initialize the squeeze sponge row + witness::init( + &mut keccak_witness, + row, + &layout_sponge(), + &variable_map!["old_state" => field(&state), "new_state" => field(&new_state), "bytes" => field(&bytes), "shifts" => field(&shifts)], + ); + + for col in 0..KECCAK_COLS { + witness[col].extend(keccak_witness[col].iter()); + } } From 87e3e2513f79674cbf32f7ae05bd60ff83ef4a71 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 19 Oct 2023 13:39:19 +0200 Subject: [PATCH 057/173] enable test for keccak table and move it inside rotation tests --- kimchi/src/tests/keccak.rs | 42 +------------------------- kimchi/src/tests/mod.rs | 1 + kimchi/src/tests/rot.rs | 61 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index b39ecc4f84..f5fa597068 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -3,7 +3,7 @@ use std::array; use crate::circuits::{ constraints::ConstraintSystem, gate::CircuitGate, - polynomials::keccak::{self, ROT_TAB}, + polynomials::keccak::{self, OFF}, wires::Wire, }; use ark_ec::AffineCurve; @@ -12,43 +12,3 @@ use rand::Rng; //use super::framework::TestFramework; type PallasField = ::BaseField; - -fn create_test_constraint_system() -> ConstraintSystem { - let (mut next_row, mut gates) = { CircuitGate::::create_keccak(0) }; - - ConstraintSystem::create(gates).build().unwrap() -} - -#[test] -// Test that all of the offsets in the rotation table work fine -fn test_keccak_table() { - let cs = create_test_constraint_system(); - let state = array::from_fn(|_| { - array::from_fn(|_| rand::thread_rng().gen_range(0..2u128.pow(64)) as u64) - }); - let witness = keccak::create_witness_keccak_rot(state); - for row in 0..=48 { - assert_eq!( - cs.gates[row].verify_witness::( - row, - &witness, - &cs, - &witness[0][0..cs.public].to_vec() - ), - Ok(()) - ); - } - let mut rot = 0; - for (x, row) in ROT_TAB.iter().enumerate() { - for (y, &bits) in row.iter().enumerate() { - if bits == 0 { - continue; - } - assert_eq!( - PallasField::from(state[x][y].rotate_left(bits)), - witness[1][1 + 2 * rot], - ); - rot += 1; - } - } -} diff --git a/kimchi/src/tests/mod.rs b/kimchi/src/tests/mod.rs index f102cfa78f..8e86206d3b 100644 --- a/kimchi/src/tests/mod.rs +++ b/kimchi/src/tests/mod.rs @@ -6,6 +6,7 @@ mod foreign_field_add; mod foreign_field_mul; mod framework; mod generic; +mod keccak; mod lookup; mod not; mod poseidon; diff --git a/kimchi/src/tests/rot.rs b/kimchi/src/tests/rot.rs index 8d8ee17f97..0a3ef74f02 100644 --- a/kimchi/src/tests/rot.rs +++ b/kimchi/src/tests/rot.rs @@ -8,6 +8,7 @@ use crate::{ polynomial::COLUMNS, polynomials::{ generic::GenericGateSpec, + keccak::{DIM, OFF}, rot::{self, RotMode}, }, wires::Wire, @@ -384,3 +385,63 @@ fn test_rot_finalization() { .prove_and_verify::() .unwrap(); } + +#[test] +// Test that all of the offsets in the rotation table work fine +fn test_keccak_table() { + let zero_row = 0; + let mut gates = vec![CircuitGate::::create_generic_gadget( + Wire::for_row(zero_row), + GenericGateSpec::Pub, + None, + )]; + let mut rot_row = zero_row + 1; + for col in OFF { + for rot in col { + // if rotation by 0 bits, no need to create a gate for it + if rot == 0 { + continue; + } + let mut rot64_gates = CircuitGate::create_rot64(rot_row, rot as u32); + rot_row += rot64_gates.len(); + // Append them to the full gates vector + gates.append(&mut rot64_gates); + // Check that 2 most significant limbs of shifted are zero + gates.connect_64bit(zero_row, rot_row - 1); + } + } + let cs = ConstraintSystem::create(gates).build().unwrap(); + + let state: [[u64; DIM]; DIM] = array::from_fn(|_| { + array::from_fn(|_| rand::thread_rng().gen_range(0..2u128.pow(64)) as u64) + }); + let mut witness: [Vec; COLUMNS] = array::from_fn(|_| vec![PallasField::zero()]); + for (y, col) in OFF.iter().enumerate() { + for (x, &rot) in col.iter().enumerate() { + if rot == 0 { + continue; + } + rot::extend_rot(&mut witness, state[x][y], rot as u32, RotMode::Left); + } + } + + for row in 0..=48 { + assert_eq!( + cs.gates[row].verify_witness::(row, &witness, &cs, &witness[0][0..cs.public]), + Ok(()) + ); + } + let mut rot = 0; + for (y, col) in OFF.iter().enumerate() { + for (x, &bits) in col.iter().enumerate() { + if bits == 0 { + continue; + } + assert_eq!( + PallasField::from(state[x][y].rotate_left(bits as u32)), + witness[1][1 + 3 * rot], + ); + rot += 1; + } + } +} From e35b0a554789a7fe8d332966fc2c803291fcf3dd Mon Sep 17 00:00:00 2001 From: querolita Date: Fri, 20 Oct 2023 12:48:39 +0200 Subject: [PATCH 058/173] refactor macros to fix wrong index accesses, and cleaner code to work with --- kimchi/src/circuits/polynomials/keccak.rs | 214 +++++++++++----------- 1 file changed, 112 insertions(+), 102 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak.rs b/kimchi/src/circuits/polynomials/keccak.rs index beaea37297..7f6f18ddf7 100644 --- a/kimchi/src/circuits/polynomials/keccak.rs +++ b/kimchi/src/circuits/polynomials/keccak.rs @@ -14,11 +14,58 @@ pub const DIM: usize = 5; pub const QUARTERS: usize = 4; #[macro_export] -macro_rules! state_from_vec { - ($expr:expr) => { - |i: usize, x: usize, y: usize, q: usize| { - $expr[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() +macro_rules! grid { + (20, $v:expr) => {{ + |x: usize, q: usize| $v[q + QUARTERS * x].clone() + }}; + (80, $v:expr) => {{ + |i: usize, x: usize, q: usize| $v[q + QUARTERS * (x + DIM * i)].clone() + }}; + (100, $v:expr) => {{ + |y: usize, x: usize, q: usize| $v[q + QUARTERS * (x + DIM * y)].clone() + }}; + (400, $v:expr) => {{ + |i: usize, y: usize, x: usize, q: usize| { + $v[q + QUARTERS * (x + DIM * (y + DIM * i))].clone() } + }}; +} + +#[macro_export] +macro_rules! from_quarters { + ($quarters:ident, $x:ident) => { + $quarters($x, 0) + + T::two_pow(16) * $quarters($x, 1) + + T::two_pow(32) * $quarters($x, 2) + + T::two_pow(48) * $quarters($x, 3) + }; + ($quarters:ident, $y:ident, $x:ident) => { + $quarters($y, $x, 0) + + T::two_pow(16) * $quarters($y, $x, 1) + + T::two_pow(32) * $quarters($y, $x, 2) + + T::two_pow(48) * $quarters($y, $x, 3) + }; +} + +#[macro_export] +macro_rules! from_shifts { + ($shifts:ident, $i:ident) => { + $shifts($i) + + T::two_pow(1) * $shifts(100 + $i) + + T::two_pow(2) * $shifts(200 + $i) + + T::two_pow(3) * $shifts(300 + $i) + }; + ($shifts:ident, $x:ident, $q:ident) => { + $shifts(0, $x, $q) + + T::two_pow(1) * $shifts(1, $x, $q) + + T::two_pow(2) * $shifts(2, $x, $q) + + T::two_pow(3) * $shifts(3, $x, $q) + }; + ($shifts:ident, $y:ident, $x:ident, $q:ident) => { + $shifts(0, $y, $x, $q) + + T::two_pow(1) * $shifts(1, $y, $x, $q) + + T::two_pow(2) * $shifts(2, $y, $x, $q) + + T::two_pow(3) * $shifts(3, $y, $x, $q) }; } @@ -112,46 +159,46 @@ where // LOAD STATES FROM WITNESS LAYOUT // THETA - let state_a = state_from_vec!(env.witness_curr_chunk(0, 100)); - let state_c = state_from_vec!(env.witness_curr_chunk(100, 120)); - let shifts_c = state_from_vec!(env.witness_curr_chunk(120, 200)); - let dense_c = state_from_vec!(env.witness_curr_chunk(200, 220)); - let quotient_c = state_from_vec!(env.witness_curr_chunk(220, 240)); - let remainder_c = state_from_vec!(env.witness_curr_chunk(240, 260)); - let bound_c = state_from_vec!(env.witness_curr_chunk(260, 280)); - let dense_rot_c = state_from_vec!(env.witness_curr_chunk(280, 300)); - let expand_rot_c = state_from_vec!(env.witness_curr_chunk(300, 320)); - let state_d = state_from_vec!(env.witness_curr_chunk(320, 340)); - let state_e = state_from_vec!(env.witness_curr_chunk(340, 440)); + let state_a = grid!(100, env.witness_curr_chunk(0, 100)); + let state_c = grid!(20, env.witness_curr_chunk(100, 120)); + let shifts_c = grid!(80, env.witness_curr_chunk(120, 200)); + let dense_c = grid!(20, env.witness_curr_chunk(200, 220)); + let quotient_c = grid!(20, env.witness_curr_chunk(220, 240)); + let remainder_c = grid!(20, env.witness_curr_chunk(240, 260)); + let bound_c = grid!(20, env.witness_curr_chunk(260, 280)); + let dense_rot_c = grid!(20, env.witness_curr_chunk(280, 300)); + let expand_rot_c = grid!(20, env.witness_curr_chunk(300, 320)); + let state_d = grid!(20, env.witness_curr_chunk(320, 340)); + let state_e = grid!(100, env.witness_curr_chunk(340, 440)); // PI-RHO - let shifts_e = state_from_vec!(env.witness_curr_chunk(440, 840)); - let dense_e = state_from_vec!(env.witness_curr_chunk(840, 940)); - let quotient_e = state_from_vec!(env.witness_curr_chunk(940, 1040)); - let remainder_e = state_from_vec!(env.witness_curr_chunk(1040, 1140)); - let bound_e = state_from_vec!(env.witness_curr_chunk(1140, 1240)); - let dense_rot_e = state_from_vec!(env.witness_curr_chunk(1240, 1340)); - let expand_rot_e = state_from_vec!(env.witness_curr_chunk(1340, 1440)); - let state_b = state_from_vec!(env.witness_curr_chunk(1440, 1540)); + let shifts_e = grid!(400, env.witness_curr_chunk(440, 840)); + let dense_e = grid!(100, env.witness_curr_chunk(840, 940)); + let quotient_e = grid!(100, env.witness_curr_chunk(940, 1040)); + let remainder_e = grid!(100, env.witness_curr_chunk(1040, 1140)); + let bound_e = grid!(100, env.witness_curr_chunk(1140, 1240)); + let dense_rot_e = grid!(100, env.witness_curr_chunk(1240, 1340)); + let expand_rot_e = grid!(100, env.witness_curr_chunk(1340, 1440)); + let state_b = grid!(100, env.witness_curr_chunk(1440, 1540)); // CHI - let shifts_b = state_from_vec!(env.witness_curr_chunk(1540, 1940)); - let shifts_sum = state_from_vec!(env.witness_curr_chunk(1940, 2340)); - let mut state_f = env.witness_curr_chunk(2340, 2344); + let shifts_b = grid!(400, env.witness_curr_chunk(1540, 1940)); + let shifts_sum = grid!(400, env.witness_curr_chunk(1940, 2340)); + let mut state_f: Vec = env.witness_curr_chunk(2340, 2344); let mut tail = env.witness_next_chunk(4, 100); state_f.append(&mut tail); - let state_f = state_from_vec!(state_f); + let state_f = grid!(100, state_f); // IOTA let mut state_g = env.witness_next_chunk(0, 4); let mut tail = env.witness_next_chunk(4, 100); state_g.append(&mut tail); - let state_g = state_from_vec!(state_g); + let state_g = grid!(100, state_g); // STEP theta: 5 * ( 3 + 4 * (3 + 5 * 1) ) = 175 constraints for x in 0..DIM { - let word_c = compose_quarters(dense_c, x, 0); - let quo_c = compose_quarters(quotient_c, x, 0); - let rem_c = compose_quarters(remainder_c, x, 0); - let bnd_c = compose_quarters(bound_c, x, 0); - let rot_c = compose_quarters(dense_rot_c, x, 0); + let word_c = from_quarters!(dense_c, x); + let quo_c = from_quarters!(quotient_c, x); + let rem_c = from_quarters!(remainder_c, x); + let bnd_c = from_quarters!(bound_c, x); + let rot_c = from_quarters!(dense_rot_c, x); constraints .push(word_c * T::two_pow(1) - (quo_c.clone() * T::two_pow(64) + rem_c.clone())); constraints.push(rot_c - (quo_c.clone() + rem_c)); @@ -159,23 +206,21 @@ where for q in 0..QUARTERS { constraints.push( - state_c(0, x, 0, q) - - (state_a(0, x, 0, q) - + state_a(0, x, 1, q) - + state_a(0, x, 2, q) - + state_a(0, x, 3, q) - + state_a(0, x, 4, q)), + state_c(x, q) + - (state_a(0, x, q) + + state_a(1, x, q) + + state_a(2, x, q) + + state_a(3, x, q) + + state_a(4, x, q)), ); - constraints.push(state_c(0, x, 0, q) - compose_shifts(shifts_c, x, 0, q)); + constraints.push(state_c(x, q) - from_shifts!(shifts_c, x, q)); constraints.push( - state_d(0, x, 0, q) - - (shifts_c(0, (x + DIM - 1) % DIM, 0, q) - + expand_rot_c(0, (x + 1) % DIM, 0, q)), + state_d(x, q) + - (shifts_c(0, (x + DIM - 1) % DIM, q) + expand_rot_c((x + 1) % DIM, q)), ); for y in 0..DIM { - constraints - .push(state_e(0, x, y, q) - (state_a(0, x, y, q) + state_d(0, x, 0, q))); + constraints.push(state_e(y, x, q) - (state_a(y, x, q) + state_d(x, q))); } } } // END theta @@ -183,11 +228,11 @@ where // STEP pirho: 5 * 5 * (3 + 4 * 2) = 275 constraints for (y, col) in OFF.iter().enumerate() { for (x, off) in col.iter().enumerate() { - let word_e = compose_quarters(dense_e, x, y); - let quo_e = compose_quarters(quotient_e, x, y); - let rem_e = compose_quarters(remainder_e, x, y); - let bnd_e = compose_quarters(bound_e, x, y); - let rot_e = compose_quarters(dense_rot_e, x, y); + let word_e = from_quarters!(dense_e, y, x); + let quo_e = from_quarters!(quotient_e, y, x); + let rem_e = from_quarters!(remainder_e, y, x); + let bnd_e = from_quarters!(bound_e, y, x); + let rot_e = from_quarters!(dense_rot_e, y, x); constraints.push( word_e * T::two_pow(*off) - (quo_e.clone() * T::two_pow(64) + rem_e.clone()), @@ -196,9 +241,8 @@ where constraints.push(bnd_e - (quo_e + T::two_pow(64) - T::two_pow(*off))); for q in 0..QUARTERS { - constraints.push(state_e(0, x, y, q) - compose_shifts(shifts_e, x, y, q)); - constraints - .push(state_b(0, y, (2 * x + 3 * y) % DIM, q) - expand_rot_e(0, x, y, q)); + constraints.push(state_e(y, x, q) - from_shifts!(shifts_e, y, x, q)); + constraints.push(state_b((2 * x + 3 * y) % DIM, y, q) - expand_rot_e(y, x, q)); } } } // END pirho @@ -207,20 +251,20 @@ where for q in 0..QUARTERS { for x in 0..DIM { for y in 0..DIM { - let not = - T::literal(F::from(0x1111111111111111u64)) - shifts_b(0, (x + 1) % 5, y, q); - let sum = not + shifts_b(1, (x + 2) % 5, y, q); - let and = shifts_sum(1, x, y, q); - constraints.push(state_b(0, x, y, q) - compose_shifts(shifts_b, x, y, q)); - constraints.push(sum - compose_shifts(shifts_sum, x, y, q)); - constraints.push(state_f(0, x, y, q) - (shifts_b(0, x, y, q) + and)); + let not = T::literal(F::from(0x1111111111111111u64)) + - shifts_b(0, y, (x + 1) % DIM, q); + let sum = not + shifts_b(0, y, (x + 2) % DIM, q); + let and = shifts_sum(1, y, x, q); + constraints.push(state_b(y, x, q) - from_shifts!(shifts_b, y, x, q)); + constraints.push(sum - from_shifts!(shifts_sum, y, x, q)); + constraints.push(state_f(y, x, q) - (shifts_b(0, y, x, q) + and)); } } } // END chi // STEP iota: 4 constraints for (q, c) in rc.iter().enumerate() { - constraints.push(state_g(0, 0, 0, q) - (state_f(0, 0, 0, q) + c.clone())); + constraints.push(state_g(0, 0, q) - (state_f(0, 0, q) + c.clone())); } // END iota constraints @@ -249,9 +293,8 @@ where // LOAD WITNESS let old_state = env.witness_curr_chunk(0, 100); - let mut new_block = env.witness_curr_chunk(100, 168); - let mut zeros = env.witness_curr_chunk(168, 200); - new_block.append(&mut zeros); + let new_block = env.witness_curr_chunk(100, 200); + let zeros = env.witness_curr_chunk(168, 200); let xor_state = env.witness_next_chunk(0, 100); let bytes = env.witness_curr_chunk(200, 400); let shifts = env.witness_curr_chunk(400, 800); @@ -262,9 +305,9 @@ where auto_clone_array!(shifts); // LOAD COEFFICIENTS - let root = env.coeff(0); - let absorb = env.coeff(1); - let squeeze = env.coeff(2); + let absorb = env.coeff(0); + let squeeze = env.coeff(1); + let root = env.coeff(2); let flags = env.coeff_chunk(4, 140); let pad = env.coeff_chunk(200, 336); auto_clone!(root); @@ -284,9 +327,9 @@ where // Absorbs the new block by performing XOR with the old state constraints.push(absorb() * (xor_state(i) - (old_state(i) + new_block(i)))); // In absorb, Check shifts correspond to the decomposition of the new state - constraints.push(absorb() * (new_block(i) - compose_shifts_from_vec(shifts, i))); + constraints.push(absorb() * (new_block(i) - from_shifts!(shifts, i))); // In squeeze, Check shifts correspond to the 256-bit prefix digest of the old state (current) - constraints.push(squeeze() * (old_state(i) - compose_shifts_from_vec(shifts, i))); + constraints.push(squeeze() * (old_state(i) - from_shifts!(shifts, i))); } for i in 0..136 { // Check padding @@ -296,36 +339,3 @@ where constraints } } - -fn compose_quarters>( - quarters: impl Fn(usize, usize, usize, usize) -> T, - x: usize, - y: usize, -) -> T { - quarters(0, x, y, 0) - + T::two_pow(16) * quarters(0, x, y, 1) - + T::two_pow(32) * quarters(0, x, y, 2) - + T::two_pow(48) * quarters(0, x, y, 3) -} - -fn compose_shifts>( - shifts: impl Fn(usize, usize, usize, usize) -> T, - x: usize, - y: usize, - q: usize, -) -> T { - shifts(0, x, y, q) - + T::two_pow(1) * shifts(1, x, y, q) - + T::two_pow(2) * shifts(2, x, y, q) - + T::two_pow(3) * shifts(3, x, y, q) -} - -fn compose_shifts_from_vec>( - shifts: impl Fn(usize) -> T, - i: usize, -) -> T { - shifts(4 * i) - + T::two_pow(1) * shifts(4 * i + 1) - + T::two_pow(2) * shifts(4 * i + 2) - + T::two_pow(3) * shifts(4 * i + 3) -} From 823695206766b77e0fdc4b01bb6845c53c38eeea Mon Sep 17 00:00:00 2001 From: querolita Date: Fri, 20 Oct 2023 13:15:43 +0200 Subject: [PATCH 059/173] fix length of coefficients in sponge to avoid index out of bounds --- kimchi/src/circuits/polynomials/keccak/gadget.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kimchi/src/circuits/polynomials/keccak/gadget.rs b/kimchi/src/circuits/polynomials/keccak/gadget.rs index 4a36b910b5..83b75e3294 100644 --- a/kimchi/src/circuits/polynomials/keccak/gadget.rs +++ b/kimchi/src/circuits/polynomials/keccak/gadget.rs @@ -7,6 +7,8 @@ use ark_ff::{PrimeField, SquareRootField}; use super::{expand_word, padded_length, RATE, RC, ROUNDS}; +const SPONGE_COEFFS: usize = 336; + impl CircuitGate { /// Extends a Keccak circuit to hash one message /// Note: @@ -45,7 +47,11 @@ impl CircuitGate { CircuitGate { typ: GateType::KeccakSponge, wires: Wire::for_row(new_row), - coeffs: vec![F::zero(), F::one()], + coeffs: { + let mut c = vec![F::zero(); SPONGE_COEFFS]; + c[1] = F::one(); + c + }, } } From fe85e7eaf8358efd830cb936c3cf99c66e47a8fc Mon Sep 17 00:00:00 2001 From: querolita Date: Fri, 20 Oct 2023 13:19:27 +0200 Subject: [PATCH 060/173] update witness code to use grid macro --- .../circuits/polynomials/keccak/witness.rs | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/witness.rs b/kimchi/src/circuits/polynomials/keccak/witness.rs index 861318b82e..e0e235549f 100644 --- a/kimchi/src/circuits/polynomials/keccak/witness.rs +++ b/kimchi/src/circuits/polynomials/keccak/witness.rs @@ -9,7 +9,7 @@ use crate::{ polynomials::keccak::ROUNDS, witness::{self, IndexCell, Variables, WitnessCell}, }, - state_from_vec, variable_map, + grid, variable_map, }; use ark_ff::PrimeField; use num_bigint::BigUint; @@ -167,16 +167,16 @@ impl Theta { } fn compute_state_c(state_a: &[u64]) -> Vec { - let state_a = state_from_vec!(state_a); + let state_a = grid!(100, state_a); let mut state_c = vec![]; for x in 0..DIM { for q in 0..QUARTERS { state_c.push( - state_a(0, x, 0, q) - + state_a(0, x, 1, q) - + state_a(0, x, 2, q) - + state_a(0, x, 3, q) - + state_a(0, x, 4, q), + state_a(0, x, q) + + state_a(1, x, q) + + state_a(2, x, q) + + state_a(3, x, q) + + state_a(4, x, q), ); } } @@ -184,27 +184,25 @@ impl Theta { } fn compute_state_d(shifts_c: &[u64], expand_rot_c: &[u64]) -> Vec { - let shifts_c = state_from_vec!(shifts_c); - let expand_rot_c = state_from_vec!(expand_rot_c); + let shifts_c = grid!(20, shifts_c); + let expand_rot_c = grid!(20, expand_rot_c); let mut state_d = vec![]; for x in 0..DIM { for q in 0..QUARTERS { - state_d.push( - shifts_c(0, (x + DIM - 1) % DIM, 0, q) + expand_rot_c(0, (x + 1) % DIM, 0, q), - ); + state_d.push(shifts_c((x + DIM - 1) % DIM, q) + expand_rot_c((x + 1) % DIM, q)); } } state_d } fn compute_state_e(state_a: &[u64], state_d: &[u64]) -> Vec { - let state_a = state_from_vec!(state_a); - let state_d = state_from_vec!(state_d); + let state_a = grid!(100, state_a); + let state_d = grid!(20, state_d); let mut state_e = vec![]; for y in 0..DIM { for x in 0..DIM { for q in 0..QUARTERS { - state_e.push(state_a(0, x, y, q) + state_d(0, x, 0, q)); + state_e.push(state_a(y, x, q) + state_d(x, q)); } } } @@ -236,11 +234,11 @@ impl PiRho { ); let mut state_b = vec![vec![vec![0; QUARTERS]; DIM]; DIM]; - let aux = state_from_vec!(rotation_e.expand_rot); + let aux = grid!(100, rotation_e.expand_rot); for y in 0..DIM { for x in 0..DIM { for q in 0..QUARTERS { - state_b[(2 * x + 3 * y) % DIM][y][q] = aux(0, x, y, q); + state_b[(2 * x + 3 * y) % DIM][y][q] = aux(y, x, q); } } } @@ -268,24 +266,24 @@ struct Chi { impl Chi { fn create(state_b: &[u64]) -> Self { let shifts_b = shift(state_b); - let shiftsb = state_from_vec!(shifts_b); + let shiftsb = grid!(400, shifts_b); let mut sum = vec![]; for y in 0..DIM { for x in 0..DIM { for q in 0..QUARTERS { - let not = 0x1111111111111111u64 - shiftsb(0, (x + 1) % DIM, y, q); - sum.push(not + shiftsb(0, (x + 2) % DIM, y, q)); + let not = 0x1111111111111111u64 - shiftsb(0, y, (x + 1) % DIM, q); + sum.push(not + shiftsb(0, y, (x + 2) % DIM, q)); } } } let shifts_sum = shift(&sum); - let shiftsum = state_from_vec!(shifts_sum); + let shiftsum = grid!(400, shifts_sum); let mut state_f = vec![]; for y in 0..DIM { for x in 0..DIM { for q in 0..QUARTERS { - let and = shiftsum(1, x, y, q); - state_f.push(shiftsb(0, x, y, q) + and); + let and = shiftsum(1, y, x, q); + state_f.push(shiftsb(0, y, x, q) + and); } } } From abd91eae176fdfb686644dd630bb2bb0e517f86f Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 29 Sep 2023 22:07:44 +0200 Subject: [PATCH 061/173] Remove snarky --- book/src/SUMMARY.md | 11 -- book/src/snarky/api.md | 2 - book/src/snarky/booleans.md | 73 -------- book/src/snarky/circuit-generation.md | 29 ---- book/src/snarky/kimchi-backend.md | 234 -------------------------- book/src/snarky/overview.md | 32 ---- book/src/snarky/snarky-wrapper.md | 70 -------- book/src/snarky/vars.md | 135 --------------- book/src/snarky/witness-generation.md | 21 --- 9 files changed, 607 deletions(-) delete mode 100644 book/src/snarky/api.md delete mode 100644 book/src/snarky/booleans.md delete mode 100644 book/src/snarky/circuit-generation.md delete mode 100644 book/src/snarky/kimchi-backend.md delete mode 100644 book/src/snarky/overview.md delete mode 100644 book/src/snarky/snarky-wrapper.md delete mode 100644 book/src/snarky/vars.md delete mode 100644 book/src/snarky/witness-generation.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 95d99267d6..aeccb68783 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -58,17 +58,6 @@ - [Permutation](./kimchi/permut.md) - [Lookup](./kimchi/lookup.md) -# Snarky - -- [Overview](./snarky/overview.md) -- [API](./snarky/api.md) -- [snarky wrapper](./snarky/snarky-wrapper.md) -- [Kimchi backend](./snarky/kimchi-backend.md) -- [Vars](./snarky/vars.md) -- [Booleans](./snarky/booleans.md) -- [Circuit generation](./snarky/circuit-generation.md) -- [Witness generation](./snarky/witness-generation.md) - # Pickles & Inductive Proof Systems - [Overview](./fundamentals/zkbook_ips.md) diff --git a/book/src/snarky/api.md b/book/src/snarky/api.md deleted file mode 100644 index e8b981a474..0000000000 --- a/book/src/snarky/api.md +++ /dev/null @@ -1,2 +0,0 @@ -# API of Snarky - diff --git a/book/src/snarky/booleans.md b/book/src/snarky/booleans.md deleted file mode 100644 index 7b503f0580..0000000000 --- a/book/src/snarky/booleans.md +++ /dev/null @@ -1,73 +0,0 @@ -# Booleans - -Booleans are a good example of a [snarky variable](./vars.md#snarky-vars). - -```rust -pub struct Boolean(CVar); - -impl SnarkyType for Boolean -where - F: PrimeField, -{ - type Auxiliary = (); - - type OutOfCircuit = bool; - - const SIZE_IN_FIELD_ELEMENTS: usize = 1; - - fn to_cvars(&self) -> (Vec>, Self::Auxiliary) { - (vec![self.0.clone()], ()) - } - - fn from_cvars_unsafe(cvars: Vec>, _aux: Self::Auxiliary) -> Self { - assert_eq!(cvars.len(), Self::SIZE_IN_FIELD_ELEMENTS); - Self(cvars[0].clone()) - } - - fn check(&self, cs: &mut RunState) { - // TODO: annotation? - cs.assert_(Some("boolean check"), vec![BasicSnarkyConstraint::Boolean(self.0.clone())]); - } - - fn deserialize(&self) -> (Self::OutOfCircuit, Self::Auxiliary) { - todo!() - } - - fn serialize(out_of_circuit: Self::OutOfCircuit, aux: Self::Auxiliary) -> Self { - todo!() - } - - fn constraint_system_auxiliary() -> Self::Auxiliary { - todo!() - } - - fn value_to_field_elements(x: &Self::OutOfCircuit) -> (Vec, Self::Auxiliary) { - todo!() - } - - fn value_of_field_elements(x: (Vec, Self::Auxiliary)) -> Self::OutOfCircuit { - todo!() - } -} -``` - -## Check - -The `check()` function is simply constraining the `CVar` $x$ to be either $0$ or $1$ using the following constraint: - -$$x ( x - 1) = 0$$ - -It is trivial to use the [double generic gate](../specs/kimchi.md#double-generic-gate) for this. - -## And - -$$x \land y = x \times y$$ - -## Not - -$$\sim x = 1 - x$$ - -## Or - -* $\sim x \land \sim y = b$ -* $x \lor y = \sim b$ diff --git a/book/src/snarky/circuit-generation.md b/book/src/snarky/circuit-generation.md deleted file mode 100644 index e81793aa03..0000000000 --- a/book/src/snarky/circuit-generation.md +++ /dev/null @@ -1,29 +0,0 @@ -# Circuit generation - -In circuit generation mode, the `has_witness` field of `RunState` is set to the default `CircuitGeneration`, and the program of the user is ran to completion. - -During the execution, the different snarky functions called on `RunState` will create [internal variables](./vars.md) as well as constraints. - -## Creation of variables - -[Variables](./vars.md) can be created via the `compute()` function, which takes two arguments: - -* A `TypeCreation` toggle, which is either set to `Checked` or `Unsafe`. We will describe this below. -* A closure representing the actual computation behind the variable. This computation will only take place when real values are computed, and can be non-deterministic (e.g. random, or external values provided by the user). Note that the closure takes one argument: a `WitnessGeneration`, a structure that allows you to read the runtime values of any variables that were previously created in your program. - -The `compute()` function also needs a type hint to understand what type of [snarky type](./vars.md#snarky-vars) it is creating. - -It then performs the following steps: - -* creates enough [`CVar`](./vars#circuit-vars) to hold the value to be created -* retrieves the auxiliary data needed to create the snarky type (TODO: explain auxiliary data) and create the [`snarky variable`](./vars.md#snarky-vars) out of the `CVar`s and the auxiliary data -* if the `TypeCreation` is set to `Checked`, call the `check()` function on the snarky type (which will constrain the value created), if it is set to `Unsafe` do nothing (in which case we're trusting that the value cannot be malformed, this is mostly used internally and it is highly-likely that users directly making use of `Unsafe` are writing bugs) - -```admonish -At this point we only created variables to hold future values, and made sure that they are constrained. -The actual values will fill the room created by the `CVar` only during the [witness generation](./witness-generation.md). -``` - -## Constraints - -All other functions exposed by the API are basically here to operate on variables and create constraints in doing so. diff --git a/book/src/snarky/kimchi-backend.md b/book/src/snarky/kimchi-backend.md deleted file mode 100644 index 2d2ebf789a..0000000000 --- a/book/src/snarky/kimchi-backend.md +++ /dev/null @@ -1,234 +0,0 @@ -# Kimchi Backend - -![](https://i.imgur.com/KmKU5Pl.jpg) - -Underneath the snarky wrapper (in `snarky/checked_runner.rs`) lies what we used to call the `plonk_constraint_system` or `kimchi_backend` in `snarky/constraint_systen.rs`. - -```admonish -It is good to note that we're planning on removing this abstract separation between the snarky wrapper and the constraint system. -``` - -The logic in the kimchi backend serves two purposes: - -* **Circuit generation**. It is the logic that adds gates to our list of gates (representing the circuit). For most of these gates, the variables used are passed to the backend by the snarky wrapper, but some of them are created by the backend itself (see more in the [variables section](#variables)). -* **Witness generation**. It is the logic that creates the witness - -One can also perform two additional operations once the constraint system has been compiled: - -* Generate the prover and verifier index for the system. -* Get a hash of the constraint system (this includes the circuit, the number of public input) (TODO: verify that this is true) (TODO: what else should be in that hash? a version of snarky and a version of kimchi?). - -## A circuit - -A circuit is either being built, or has been contructed during a circuit generation phase: - -```rust -enum Circuit -where - F: PrimeField, -{ - /** A circuit still being written. */ - Unfinalized(Vec>), - /** Once finalized, a circuit is represented as a digest - and a list of gates that corresponds to the circuit. - */ - Compiled([u8; 32], Vec>), -} -``` - -## State - -The state of the kimchi backend looks like this: - -```rust -where - Field: PrimeField, -{ - /// A counter used to track variables - /// (similar to the one in the snarky wrapper) - next_internal_var: usize, - - /// Instruction on how to compute each internal variable - /// (as a linear combination of other variables). - /// Used during witness generation. - internal_vars: HashMap, Option)>, - - /// The symbolic execution trace table. - /// Each cell is a variable that takes a value during witness generation. - /// (if not set, it will take the value 0). - rows: Vec>>, - - /// The circuit once compiled - gates: Circuit, - - /// The row to use the next time we add a constraint. - // TODO: I think we can delete this - next_row: usize, - - /// The size of the public input - /// (which fills the first rows of our constraint system. - public_input_size: Option, - - // omitted values... -} -``` - -## Variables - -In the backend, there's two types of variables: - -```rust -enum V { - /// An external variable - /// (generated by snarky, via [exists]). - External(usize), - - /// An internal variable is generated to hold an intermediate value, - /// (e.g. in reducing linear combinations to single PLONK positions). - Internal(InternalVar), -} -``` - -Internal variables are basically a `usize` pointing to a hashmap in the state. - -That hashmap tells you how to compute the internal variable during witness generation: it is always a linear combination of other variables (and a constant). - -## Circuit generation - -During circuit generation, the snarky wrapper will make calls to the `add_constraint()` or `add_basic_snarky_constraint` function of the kimchi backend, specifying what gate to use and what variables to use in that gate. - -At this point, the snarky wrapper might have some variables that are not yet tracked as such (with a counter). -Rather, they are constants, or they are a combination of other variables. -You can see that as a small AST representing how to compute a variable. -(See the [variables section](./vars.md#circuit-vars) for more details). - -For this reason, they can hide a number of operations that haven't been constrained yet. -It is the role of the `add_constrain` logic to enforce that at this point constants, as well as linear combinations or scalings of variables, are encoded in the circuit. -This is done by adding enough generic gates (using the `reduce_lincom()` or `reduce_to_var()` functions). - -```admonish -This is a remnant of an optimization targetting R1CS (in which additions are for free). -An issue with this approach is the following: imagine that two circuit variables are created from the same circuit variable, imagine also that the original circuit variable contained a long AST, then both variables might end up creating the same constraints to convert that AST. -Currently, snarkyjs and pickles expose a `seal()` function that allows you to reduce this issue, at the cost of some manual work and mental tracking on the developer. -We should probably get rid of this, while making sure that we can continue to optimize generic gates -(in some cases you can merge two generic gates in one (TODO: give an example of where that can happen)). -Another solution is to keep track of what was reduced, and reuse previous reductions (similar to how we handle constants). -``` - -It is during this "reducing" step that internal variables (known only to the kimchi backend) are created. - -```admonish -The process is quite safe, as the kimchi backend cannot use the snarky wrapper variables directly (which are of type `CVar`). -Since the expected format (see the [variables section](#variables) is a number (of type `usize`), the only way to convert a non-tracked variable (constant, or scale, or linear combination) is to reduce it (and in the process constraining its value). -``` - -Depending on the gate being used, several constraints might be added via the `add_row()` function which does three things: - -1. figure out if there's any wiring to be done -2. add a gate to our list of gates (representing the circuit) -3. add the variables to our _symbolic_ execution trace table (symbolic in the sense that nothing has values yet) - -This process happens as the circuit is "parsed" and the constraint functions of the kimchi backend are called. - -This does not lead to a finalized circuit, see the next section to see how that is done. - -(TODO: ideally this should happen in the same step) - -## Finalization of the circuit. - -So far we've only talked about adding specific constraints to the circuit, but not about how public input are handled. - -The `finalization()` function of the kimchi backend does the following: - -* add as many generic rows as there are public inputs. -* construct the permutation -* computes a cache of the circuit (TODO: this is so unecessary) -* and other things that are not that important - -## Witness generation - -Witness generation happens by taking the finalized state (in the `compute_witness()` function) with a callback that can be used to retrieve the values of external variables (public input and public output). - -The algorithm follows these steps using the symbolic execution table we built during circuit generation: - -1. it initializes the execution trace table with zeros -2. go through the rows related to the public input and set the most-left column values to the ones obtained by the callback. -3. go through the other rows and compute the value of the variables left in the table - -Variables in step 3. should either: - -* be absent (`None`) and evaluated to the default value 0 -* point to an external variable, in which case the closure passed can be used to retrieve the value -* be an internal variable, in which case the value is computed by evaluating the AST that was used to create it. - -## Permutation - -The permutation is used to wire cells of the execution trace table (specifically, cells belonging to the first 7 columns). -It is also known as "copy constraints". - -```admonish -In snarky, the permutation is represented differently from kimchi, and thus needs to be converted to the kimchi's format before a proof can be created. -TODO: merge the representations -``` - -We use the permutation in ingenious ways to optimize circuits. -For example, we use it to encode each constants once, and wire it to places where it is used. -Another example, is that we use it to assert equality between two cells. - -## Implementation details - -There's two aspect of the implementation of the permutation, the first one is a hashmap of equivalence classes, which is used to track all the positions of a variable, the second one is making use of a [union find]() data structure to link variables that are equivalent (we'll talk about that after). - -The two data structures are in the kimchi backend's state: - -```rust -pub struct SnarkyConstraintSystem -where - Field: PrimeField, -{ - equivalence_classes: HashMap>>, - union_finds: disjoint_set::DisjointSet, - // omitted fields... -} -``` - -### equivalence classes - -As said previously, during circuit generation a symbolic execution trace table is created. It should look a bit like this (if there were only 3 columns and 4 rows): - -| | 0 | 1 | 2 | -| :-: | :-: | :-: | :-:| -| 0 | v1 | v1 | | -| 1 | | v2 | | -| 2 | | v2 | | -| 3 | | | v1 | - -From that, it should be clear that all the cells containing the variable `v1` should be connected, -and all the cells containing the variable `v2` should be as well. - -The format that the permutation expects is a [cycle](https://en.wikipedia.org/wiki/Cyclic_permutation): a list of cells where each cell is linked to the next, the last one wrapping around and linking to the first one. - -For example, a cycle for the `v1` variable could be: - -``` -(0, 0) -> (0, 1) -(0, 1) -> (3, 2) -(3, 2) -> (0, 0) -``` - -During circuit generation, a hashmap (called `equivalence_classes`) is used to track all the positions (row and column) of each variable. - -During finalization, all the different cycles are created by looking at all the variables existing in the hashmap. - -### Union finds - -Sometimes, we know that two variables will have equivalent values due to an `assert_equal()` being called to link them. -Since we link two variables together, they need to be part of the same cycle, and as such we need to be able to detect that to construct correct cycles. - -To do this, we use a [union find]() data structure, which allows us to easily find the unions of equivalent variables. - -When an `assert_equal()` is called, we link the two variables together using the `union_finds` data structure. - -During finalization, when we create the cycles, we use the `union_finds` data structure to find the equivalent variables. -We then create a new equivalence classes hashmap to merge the keys (variables) that are in the same set. -This is done before using the equivalence classes hashmap to construct the cycles. diff --git a/book/src/snarky/overview.md b/book/src/snarky/overview.md deleted file mode 100644 index b67c1fa30b..0000000000 --- a/book/src/snarky/overview.md +++ /dev/null @@ -1,32 +0,0 @@ -# Snarky - -Snarky is a frontend to the [kimchi proof system](../kimchi/overview.md). - -It allows users to write circuits that can be proven using kimchi. - -This part of the Mina book documents both how to use snarky, and how its internals work. - -```admonish -Snarky was originally an OCaml library. It also is known as a typescript library: SnarkyJS. -This documentation talks about the Rust implementation, which one can refer to as snarky-rs (but we will just say snarky from now on). -``` - -## High-level design - -Snarky is divided into two parts: - -* **Circuit-generation**: which is also called the setup or compilation phase. It is when snarky turn code written using its library, to a circuit that kimchi can understand. This can later be used by kimchi to produce prover and verifier keys. -* **Witness-generation**: which is also called the proving, or runtime phase. It is when snarky executes the written program and records its state at various point in time to create an execution trace of the program (which we call witness here). This can later be used by kimchi, with a proving key, to produce a zero-knowledge proof. - -A snarky program is constructed using functions exposed by the library. -The API of snarky that one can use to design circuits can be split in three categories: - -* creation of snarky variables (via `compute()`) -* creation of constraints (via `assert` type-functions) -* manipulation of snarky variables (which can sometimes create constraints) - -Snarky itself is divided into three parts: - -* [The high-level API](./api.md) that you can find in `api.rs` and `traits.rs` -* [The snarky wrapper](./snarky-wrapper.md), which contains the logic for creating user variables and composed types (see the section on [Snarky vars](./vars.md#snarky-vars)). -* [The kimchi backend](./kimchi-backend.md), which contains the logic for constructing the circuit as well as the witness. diff --git a/book/src/snarky/snarky-wrapper.md b/book/src/snarky/snarky-wrapper.md deleted file mode 100644 index 725f7c35ec..0000000000 --- a/book/src/snarky/snarky-wrapper.md +++ /dev/null @@ -1,70 +0,0 @@ -# Snarky wrapper - -Snarky, as of today, is constructed as two parts: - -* a snarky wrapper, which is explained in this document -* a backend underneath that wrapper, explained in the [kimchi backend section](./kimchi-backend.md) - -```admonish -This separation exists for legacy reasons, and ideally we should merge the two into a single library. -``` - -The snarky wrapper mostly exists in `checked_runner.rs`, and has the following state: - -```rust -where - F: PrimeField, -{ - /// The constraint system used to build the circuit. - /// If not set, the constraint system is not built. - system: Option>, - - /// The public input of the circuit used in witness generation. - // TODO: can we merge public_input and private_input? - public_input: Vec, - - // TODO: we could also just store `usize` here - pub(crate) public_output: Vec>, - - /// The private input of the circuit used in witness generation. Still not sure what that is, or why we care about this. - private_input: Vec, - - /// If set, the witness generation will check if the constraints are satisfied. - /// This is useful to simulate running the circuit and return an error if an assertion fails. - eval_constraints: bool, - - /// The number of public inputs. - num_public_inputs: usize, - - /// A counter used to track variables (this includes public inputs) as they're being created. - next_var: usize, - - /// Indication that we're running the witness generation (as opposed to the circuit creation). - mode: Mode, -} -``` - -The wrapper is designed to be used in different ways, depending on the fields set. - -```admonish -Ideally, we would like to only run this once and obtain a result that's an immutable compiled artifact. -Currently, `public_input`, `private_input`, `eval_constriants`, `next_var`, and `mode` all need to be mutable. -In the future these should be passed as arguments to functions, and should not exist in the state. -``` - -## Public output - -The support for public output is implemented as kind of a hack. - -When the developer writes a circuit, they have to specify the type of the public output. - -This allows the API to save enough room at the end of the public input, and store the variables used in the public output in the state. - -When the API calls the circuit written by the developer, it expects the public output (as a snarky type) to be returned by the function. -The compilation or proving API that ends up calling that function, can thus obtain the variables of the public output. -With that in hand, the API can continue to write the circuit to enforce an equality constraint between these variables being returned and the public output variable that it had previously stored in the state. - -Essentially, the kimchi backend will turn this into as many wiring as there are `CVar` in the public output. - -During witness generation, we need a way to modify the witness once we know the values of the public output. -As the public output `CVar`s were generated from the snarky wrapper (and not from the kimchi backend), the snarky wrapper should know their values after running the given circuit. diff --git a/book/src/snarky/vars.md b/book/src/snarky/vars.md deleted file mode 100644 index 7a1e3a3be7..0000000000 --- a/book/src/snarky/vars.md +++ /dev/null @@ -1,135 +0,0 @@ -# Vars - -In this section we will introduce two types of variables: - -* Circuit vars, or `CVar`s, which are low-level variables representing field elements. -* Snarky vars, which are high-level variables that user can use to create more meaningful programs. - -## Circuit vars - -In snarky, we first define circuit variables (TODO: rename Field variable?) which represent field elements in a circuit. -These circuit variables, or cvars, can be represented differently in the system: - -```rust -pub enum CVar -where - F: PrimeField, -{ - /// A constant. - Constant(F), - - /// A variable that can be refered to via a `usize`. - Var(usize), - - /// The addition of two other [CVar]s. - Add(Box>, Box>), - - /// Scaling of a [CVar]. - Scale(F, Box>), -} -``` - -One can see a CVar as an AST, where two atoms exist: a `Var(usize)` which represents a private input, an a `Constant(F)` which represents a constant. -Anything else represents combinations of these two atoms. - -### Constants - -Note that a circuit variable does not represent a value that has been constrained in the circuit (yet). -This is why we need to know if a cvar is a constant, so that we can avoid constraining it too early. -For example, the following code does not encode 2 or 1 in the circuit, but will encode 3: - -```rust -let x: CVar = state.exists(|_| 2) + state.exists(|_| 3); -state.assert_eq(x, y); // 3 and y will be encoded in the circuit -``` - -whereas the following code will encode all variables: - -```rust -let x = y + y; -let one: CVar = state.exists(|_| 1); -assert_eq(x, one); -``` - -### Non-constants - -Right after being created, a `CVar` is not constrained yet, and needs to be constrained by the application. -That is unless the application wants the `CVar` to be a constant that will not need to be constrained (see previous example) or because the application wants the `CVar` to be a random value (unlikely) (TODO: we should add a "rand" function for that). - -In any case, a circuit variable which is not a constant has a value that is not known yet at circuit-generation time. -In some situations, we might not want to constrain the - - -### When do variables get constrained? - -In general, a circuit variable only gets constrained by an assertion call like `assert` or `assert_equals`. - -When variables are added together, or scaled, they do not directly get constrained. -This is due to optimizations targetting R1CS (which we don't support anymore) that were implemented in the original snarky library, and that we have kept in snarky-rs. - -Imagine the following example: - -```rust -let y = x1 + x2 + x3 +.... ; -let z = y + 3; -assert_eq(y, 6); -assert_eq(z, 7); -``` - -The first two lines will not create constraints, but simply create minimal ASTs that track all of the additions. - -Both assert calls will then reduce the variables to a single circuit variable, creating the same constraints twice. - -For this reason, there's a function `seal()` defined in pickles and snarkyjs. (TODO: more about `seal()`, and why is it not in snarky?) (TODO: remove the R1CS optimization) - -## Snarky vars - -Handling `CVar`s can be cumbersome, as they can only represent a single field element. -We might want to represent values that are either in a smaller range (e.g. [booleans](./booleans.md)) or that are made out of several `CVar`s. - -For this, snarky's API exposes the following trait, which allows users to define their own types: - -```rust -pub trait SnarkyType: Sized -where - F: PrimeField, -{ - /// ? - type Auxiliary; - - /// The equivalent type outside of the circuit. - type OutOfCircuit; - - const SIZE_IN_FIELD_ELEMENTS: usize; - - fn to_cvars(&self) -> (Vec>, Self::Auxiliary); - - fn from_cvars_unsafe(cvars: Vec>, aux: Self::Auxiliary) -> Self; - - fn check(&self, cs: &mut RunState); - - fn deserialize(&self) -> (Self::OutOfCircuit, Self::Auxiliary); - - fn serialize(out_of_circuit: Self::OutOfCircuit, aux: Self::Auxiliary) -> Self; - - fn constraint_system_auxiliary() -> Self::Auxiliary; - - fn value_to_field_elements(x: &Self::OutOfCircuit) -> (Vec, Self::Auxiliary); - - fn value_of_field_elements(x: (Vec, Self::Auxiliary)) -> Self::OutOfCircuit; -} -``` - -Such types are always handled as `OutOfCircuit` types (e.g. `bool`) by the users, and as a type implementing `SnarkyType` by snarky (e.g. [`Boolean`](./booleans.md)). -Thus, the user can pass them to snarky in two ways: - -**As public inputs**. In this case they will be serialized into field elements for snarky before [witness-generation](./witness-generation.md) (via the `value_to_field_elements()` function) - -**As private inputs**. In this case, they must be created using the `compute()` function with a closure returning an `OutOfCircuit` value by the user. -The call to `compute()` will need to have some type hint, for snarky to understand what `SnarkyType` it is creating. -This is because the relationship is currently only one-way: a `SnarkyType` knows what out-of-circuit type it relates to, but not the other way is not true. -(TODO: should we implement that though?) - -A `SnarkyType` always implements a `check()` function, which is called by snarky when `compute()` is called to create such a type. -The `check()` function is responsible for creating the constraints that sanitize the newly-created `SnarkyType` (and its underlying `CVar`s). -For example, creating a boolean would make sure that the underlying `CVar` is either 0 or 1. diff --git a/book/src/snarky/witness-generation.md b/book/src/snarky/witness-generation.md deleted file mode 100644 index 41fbc3b5f1..0000000000 --- a/book/src/snarky/witness-generation.md +++ /dev/null @@ -1,21 +0,0 @@ -# Witness generation - -In snarky, currently, the same code is run through again to generate the witness. - -That is, the `RunState` contains a few changes: - -* **`public_input: Vec`**: now contains concrete values (instead of being empty). -* **`has_witness`**: is set to `WitnessGeneration`. - -Additionaly, if we want to verify that the arguments are actually correct (and that the program implemented does not fail) we can also set `eval_constraints` to `true` (defaults to `false`) to verify that the program has a correct state at all point in time. - -If we do not do this, the user will only detect failure during proof generation (specifically when the [composition polynomial](../specs/kimchi.md#proof-creation) is divided by the [vanishing polynomial](../specs/kimchi.md#proof-creation)). - -```admonish -This is implemented by simply checking that each [generic gate](../specs/kimchi.md#double-generic-gate) encountered is correct, in relation to the witness values observed in that row. -In other words $c_0 l + c_1 r + c_2 o + c_3 l r + c_4 = 0$ (extrapolated to the [double generic gate](../specs/kimchi.md#double-generic-gate)). -Note that other custom gates are not checked, as they are wrapped by [gadgets](../specs/kimchi.md#gates) which fill in witness values instead of the user. -Thus there is no room for user error (i.e. the user entering a wrong private input). -``` - -Due to the `has_witness` variable set to `WitnessGeneration`, functions will behave differently and compute actual values instead of generating constraints. From 1491b17747dda19e5e2b80b03a043e925a896cb4 Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 23 Oct 2023 17:48:28 +0200 Subject: [PATCH 062/173] switch N to W for column width --- kimchi/src/circuits/witness/constant_cell.rs | 4 ++-- kimchi/src/circuits/witness/copy_bits_cell.rs | 4 ++-- kimchi/src/circuits/witness/copy_cell.rs | 4 ++-- .../src/circuits/witness/copy_shift_cell.rs | 4 ++-- kimchi/src/circuits/witness/index_cell.rs | 4 ++-- kimchi/src/circuits/witness/mod.rs | 22 +++++++++---------- .../circuits/witness/variable_bits_cell.rs | 4 ++-- kimchi/src/circuits/witness/variable_cell.rs | 4 ++-- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/kimchi/src/circuits/witness/constant_cell.rs b/kimchi/src/circuits/witness/constant_cell.rs index f35d2179f4..ea14b5de8c 100644 --- a/kimchi/src/circuits/witness/constant_cell.rs +++ b/kimchi/src/circuits/witness/constant_cell.rs @@ -13,8 +13,8 @@ impl ConstantCell { } } -impl WitnessCell for ConstantCell { - fn value(&self, _witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { +impl WitnessCell for ConstantCell { + fn value(&self, _witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { self.value } } diff --git a/kimchi/src/circuits/witness/copy_bits_cell.rs b/kimchi/src/circuits/witness/copy_bits_cell.rs index 69aae2a975..964e2f26bf 100644 --- a/kimchi/src/circuits/witness/copy_bits_cell.rs +++ b/kimchi/src/circuits/witness/copy_bits_cell.rs @@ -23,8 +23,8 @@ impl CopyBitsCell { } } -impl WitnessCell for CopyBitsCell { - fn value(&self, witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { +impl WitnessCell for CopyBitsCell { + fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { F::from_bits(&witness[self.col][self.row].to_bits()[self.start..self.end]) .expect("failed to deserialize field bits for copy bits cell") } diff --git a/kimchi/src/circuits/witness/copy_cell.rs b/kimchi/src/circuits/witness/copy_cell.rs index fd594a3844..ffa8339094 100644 --- a/kimchi/src/circuits/witness/copy_cell.rs +++ b/kimchi/src/circuits/witness/copy_cell.rs @@ -15,8 +15,8 @@ impl CopyCell { } } -impl WitnessCell for CopyCell { - fn value(&self, witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { +impl WitnessCell for CopyCell { + fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { witness[self.col][self.row] } } diff --git a/kimchi/src/circuits/witness/copy_shift_cell.rs b/kimchi/src/circuits/witness/copy_shift_cell.rs index b285092d2f..b0ed5d055a 100644 --- a/kimchi/src/circuits/witness/copy_shift_cell.rs +++ b/kimchi/src/circuits/witness/copy_shift_cell.rs @@ -15,8 +15,8 @@ impl CopyShiftCell { } } -impl WitnessCell for CopyShiftCell { - fn value(&self, witness: &mut [Vec; N], _variables: &Variables, _index: usize) -> F { +impl WitnessCell for CopyShiftCell { + fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { F::from(2u32).pow([self.shift]) * witness[self.col][self.row] } } diff --git a/kimchi/src/circuits/witness/index_cell.rs b/kimchi/src/circuits/witness/index_cell.rs index a039354c8f..9d6ebefea5 100644 --- a/kimchi/src/circuits/witness/index_cell.rs +++ b/kimchi/src/circuits/witness/index_cell.rs @@ -18,8 +18,8 @@ impl<'a> IndexCell<'a> { } } -impl<'a, const N: usize, F: Field> WitnessCell> for IndexCell<'a> { - fn value(&self, _witness: &mut [Vec; N], variables: &Variables>, index: usize) -> F { +impl<'a, const W: usize, F: Field> WitnessCell> for IndexCell<'a> { + fn value(&self, _witness: &mut [Vec; W], variables: &Variables>, index: usize) -> F { assert!(index < self.length, "index out of bounds of `IndexCell`"); variables[self.name][index] } diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index ec470330d9..8dad3a2a40 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -21,8 +21,8 @@ pub use self::{ }; /// Witness cell interface -pub trait WitnessCell { - fn value(&self, witness: &mut [Vec; N], variables: &Variables, index: usize) -> F; +pub trait WitnessCell { + fn value(&self, witness: &mut [Vec; W], variables: &Variables, index: usize) -> F; fn length(&self) -> usize { 1 @@ -40,25 +40,25 @@ pub trait WitnessCell { /// - layout: the partial layout to initialize from /// - variables: the hashmap of variables to get the values from #[allow(clippy::too_many_arguments)] -pub fn init_cell( - witness: &mut [Vec; N], +pub fn init_cell( + witness: &mut [Vec; W], offset: usize, row: usize, col: usize, cell: usize, index: usize, - layout: &[Vec>>], + layout: &[Vec>>], variables: &Variables, ) { witness[col][row + offset] = layout[row][cell].value(witness, variables, index); } /// Initialize a witness row based on layout and computed variables -pub fn init_row( - witness: &mut [Vec; N], +pub fn init_row( + witness: &mut [Vec; W], offset: usize, row: usize, - layout: &[Vec>>], + layout: &[Vec>>], variables: &Variables, ) { let mut col = 0; @@ -72,10 +72,10 @@ pub fn init_row( } /// Initialize a witness based on layout and computed variables -pub fn init( - witness: &mut [Vec; N], +pub fn init( + witness: &mut [Vec; W], offset: usize, - layout: &[Vec>>], + layout: &[Vec>>], variables: &Variables, ) { for row in 0..layout.len() { diff --git a/kimchi/src/circuits/witness/variable_bits_cell.rs b/kimchi/src/circuits/witness/variable_bits_cell.rs index bd414a7281..1fef513607 100644 --- a/kimchi/src/circuits/witness/variable_bits_cell.rs +++ b/kimchi/src/circuits/witness/variable_bits_cell.rs @@ -18,8 +18,8 @@ impl<'a> VariableBitsCell<'a> { } } -impl<'a, const N: usize, F: Field> WitnessCell for VariableBitsCell<'a> { - fn value(&self, _witness: &mut [Vec; N], variables: &Variables, _index: usize) -> F { +impl<'a, const W: usize, F: Field> WitnessCell for VariableBitsCell<'a> { + fn value(&self, _witness: &mut [Vec; W], variables: &Variables, _index: usize) -> F { let bits = if let Some(end) = self.end { F::from_bits(&variables[self.name].to_bits()[self.start..end]) } else { diff --git a/kimchi/src/circuits/witness/variable_cell.rs b/kimchi/src/circuits/witness/variable_cell.rs index c875326583..c24ce57d42 100644 --- a/kimchi/src/circuits/witness/variable_cell.rs +++ b/kimchi/src/circuits/witness/variable_cell.rs @@ -14,8 +14,8 @@ impl<'a> VariableCell<'a> { } } -impl<'a, const N: usize, F: Field> WitnessCell for VariableCell<'a> { - fn value(&self, _witness: &mut [Vec; N], variables: &Variables, _index: usize) -> F { +impl<'a, const W: usize, F: Field> WitnessCell for VariableCell<'a> { + fn value(&self, _witness: &mut [Vec; W], variables: &Variables, _index: usize) -> F { variables[self.name] } } From 43572f8ebd051b701f48a908c70ff95e1f735e92 Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 23 Oct 2023 17:54:43 +0200 Subject: [PATCH 063/173] rename layout generic N for W --- kimchi/src/circuits/polynomials/keccak/witness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kimchi/src/circuits/polynomials/keccak/witness.rs b/kimchi/src/circuits/polynomials/keccak/witness.rs index 7d756f639f..d6aa7971e2 100644 --- a/kimchi/src/circuits/polynomials/keccak/witness.rs +++ b/kimchi/src/circuits/polynomials/keccak/witness.rs @@ -5,7 +5,7 @@ use ark_ff::PrimeField; use super::KECCAK_COLS; -type _Layout = Vec>>>; +type _Layout = Vec>>>; fn _layout_round() -> _Layout { vec![ From b16635c5fbf731a24a211554ceec7124155d7160 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 23 Oct 2023 17:27:55 +0100 Subject: [PATCH 064/173] Initial optimism layout --- Cargo.lock | 115 ++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + optimism/Cargo.toml | 29 +++++++++++ optimism/src/lib.rs | 0 optimism/src/main.rs | 6 +++ 5 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 optimism/Cargo.toml create mode 100644 optimism/src/lib.rs create mode 100644 optimism/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index c8b7f9249a..8028b4f6d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,54 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "ark-algebra-test-templates" version = "0.3.0" @@ -417,7 +465,7 @@ dependencies = [ "atty", "bitflags 1.3.2", "clap_derive", - "clap_lex", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim 0.10.0", @@ -425,6 +473,27 @@ dependencies = [ "textwrap 0.16.0", ] +[[package]] +name = "clap" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex 0.5.1", + "strsim 0.10.0", +] + [[package]] name = "clap_derive" version = "3.2.25" @@ -447,6 +516,18 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "colored" version = "2.0.4" @@ -727,6 +808,12 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "elf" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6e7d85896690fe195447717af8eceae0593ac2196fd42fe88c184e904406ce" + [[package]] name = "entities" version = "1.0.1" @@ -1159,6 +1246,26 @@ dependencies = [ "tinytemplate", ] +[[package]] +name = "kimchi_optimism" +version = "0.1.0" +dependencies = [ + "ark-ff", + "ark-poly", + "clap 4.4.6", + "elf", + "groupmap", + "hex", + "kimchi", + "mina-curves", + "mina-poseidon", + "poly-commitment", + "rmp-serde", + "serde", + "serde_json", + "serde_with", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2636,6 +2743,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index c50da37068..851713bf6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "groupmap", "hasher", "kimchi", + "optimism", "poseidon", "poseidon/export_test_vectors", "poly-commitment", diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml new file mode 100644 index 0000000000..9b45ab1d29 --- /dev/null +++ b/optimism/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "kimchi_optimism" +version = "0.1.0" +description = "MIPS demo" +repository = "https://github.com/o1-labs/proof-systems" +homepage = "https://o1-labs.github.io/proof-systems/" +documentation = "https://o1-labs.github.io/proof-systems/rustdoc/" +readme = "README.md" +edition = "2021" +license = "Apache-2.0" + +[lib] +path = "src/lib.rs" + +[dependencies] +kimchi = { path = "../kimchi", version = "0.1.0" } +poly-commitment = { path = "../poly-commitment", version = "0.1.0" } +groupmap = { path = "../groupmap", version = "0.1.0" } +mina-curves = { path = "../curves", version = "0.1.0" } +mina-poseidon = { path = "../poseidon", version = "0.1.0" } +elf = "0.7.2" +rmp-serde = "1.1.1" +serde_json = "1.0.91" +serde = "1.0.130" +serde_with = "1.10.0" +ark-poly = { version = "0.3.0", features = [ "parallel" ] } +ark-ff = { version = "0.3.0", features = [ "parallel" ] } +clap = "4.4.6" +hex = "0.4.3" diff --git a/optimism/src/lib.rs b/optimism/src/lib.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/optimism/src/main.rs b/optimism/src/main.rs new file mode 100644 index 0000000000..9301ced20f --- /dev/null +++ b/optimism/src/main.rs @@ -0,0 +1,6 @@ +use std:: process::ExitCode; + +pub fn main() -> ExitCode { + // TODO: Logic + ExitCode::FAILURE +} From eac8542b0bf7da2235bab978618f15df2433fa9a Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 23 Oct 2023 17:35:43 +0100 Subject: [PATCH 065/173] Checkout optimism v1.1.6 --- .gitmodules | 3 +++ optimism/ethereum-optimism | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 optimism/ethereum-optimism diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..5b1dec4f59 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "optimism/ethereum-optimism"] + path = optimism/ethereum-optimism + url = https://github.com/ethereum-optimism/optimism.git diff --git a/optimism/ethereum-optimism b/optimism/ethereum-optimism new file mode 160000 index 0000000000..c83cd947d4 --- /dev/null +++ b/optimism/ethereum-optimism @@ -0,0 +1 @@ +Subproject commit c83cd947d419aa2c213583a32872bc350a69e566 From e55460bbc025a73addd1f199d56e81f85ae9ad7a Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 23 Oct 2023 18:02:56 +0100 Subject: [PATCH 066/173] Check-in runner --- optimism/.gitignore | 1 + optimism/run-code.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 optimism/.gitignore create mode 100755 optimism/run-code.sh diff --git a/optimism/.gitignore b/optimism/.gitignore new file mode 100644 index 0000000000..53df36bb78 --- /dev/null +++ b/optimism/.gitignore @@ -0,0 +1 @@ +rpcs.sh diff --git a/optimism/run-code.sh b/optimism/run-code.sh new file mode 100755 index 0000000000..1452f2933f --- /dev/null +++ b/optimism/run-code.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +make -C ./ethereum-optimism/op-program op-program + +source rpcs.sh + +# L2 output oracle on Goerli +# L2_OUTPUT_ORACLE=0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0 +# L2 output oracle on Sepolia +L2_OUTPUT_ORACLE=0x90E9c4f8a994a250F6aEfd61CAFb4F2e895D458F + +L2_FINALIZED_NUMBER=$(cast block finalized --rpc-url "${L2RPC}" -f number) +echo "Finalize number: ${L2_FINALIZED_NUMBER}" +L2_FINALIZED_HASH=$(cast block "${L2_FINALIZED_NUMBER}" --rpc-url "${L2RPC}" -f hash) + +L1_FINALIZED_NUMBER=$(cast block finalized --rpc-url "${L1RPC}" -f number) +L1_FINALIZED_HASH=$(cast block "${L1_FINALIZED_NUMBER}" --rpc-url "${L1RPC}" -f hash) + +OUTPUT_INDEX=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2OutputIndexAfter(uint256) returns(uint256)' "${L2_FINALIZED_NUMBER}") +OUTPUT_INDEX=$((OUTPUT_INDEX-1)) + +OUTPUT=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2Output(uint256) returns(bytes32,uint128,uint128)' "${OUTPUT_INDEX}") +OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) +OUTPUT_TIMESTAMP=$(echo ${OUTPUT} | cut -d' ' -f 2) +OUTPUT_L2BLOCK_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) + +L1_HEAD=$L1_FINALIZED_HASH +L2_CLAIM=$OUTPUT_ROOT +L2_BLOCK_NUMBER=$OUTPUT_L2BLOCK_NUMBER + +STARTING_L2BLOCK_NUMBER=$((L2_BLOCK_NUMBER-100)) +STARTING_OUTPUT_INDEX=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2OutputIndexAfter(uint256) returns(uint256)' "${STARTING_L2BLOCK_NUMBER}") +STARTING_OUTPUT=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2Output(uint256) returns(bytes32,uint128,uint128)' "${STARTING_OUTPUT_INDEX}") +STARTING_OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) +L2_HEAD_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) +L2_HEAD=$(cast block "${L2_HEAD_NUMBER}" --rpc-url "${L2RPC}" -f hash) + +TODAY=$(date +"%Y-%m-%d-%H-%M-%S") +FILENAME=${TODAY}-op-program-data-log.sh +OP_PROGRAM_DATA_DIR=$(pwd)/op-program-db-sepolia-${TODAY} + +echo "L1_HEAD=${L1_HEAD}" >> ${FILENAME} +echo "L2_HEAD=${L2_HEAD}" >> ${FILENAME} +echo "L2_BLOCK_NUMBER=${L2_BLOCK_NUMBER}" >> ${FILENAME} +echo "STARTING_OUTPUT_ROOT=${STARTING_OUTPUT_ROOT}" >> ${FILENAME} +echo "L2_CLAIM=${L2_CLAIM}" >> ${FILENAME} +echo "OP_PROGRAM_DATA_DIR=${OP_PROGRAM_DATA_DIR}" >> ${FILENAME} +echo "L1RPC=${L1RPC}" >> ${FILENAME} +echo "L2RPC=${L2RPC}" >> ${FILENAME} + +set -x +./ethereum-optimism/op-program/bin/op-program \ + --log.level DEBUG \ + --l1 $L1RPC \ + --l2 $L2RPC \ + --network sepolia \ + --datadir ${OP_PROGRAM_DATA_DIR} \ + --l1.head $L1_HEAD \ + --l2.head $L2_HEAD \ + --l2.outputroot $STARTING_OUTPUT_ROOT \ + --l2.claim $L2_CLAIM \ + --l2.blocknumber $L2_BLOCK_NUMBER From 4fc60e939ed2ca457ff37fbc64c9b0b0c048d085 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 23 Oct 2023 18:07:27 +0100 Subject: [PATCH 067/173] Split out op-program runner --- optimism/run-code.sh | 47 ++++++++++++++------------------------ optimism/run-op-program.sh | 17 ++++++++++++++ 2 files changed, 34 insertions(+), 30 deletions(-) create mode 100755 optimism/run-op-program.sh diff --git a/optimism/run-code.sh b/optimism/run-code.sh index 1452f2933f..43cd300fe2 100755 --- a/optimism/run-code.sh +++ b/optimism/run-code.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -make -C ./ethereum-optimism/op-program op-program - source rpcs.sh # L2 output oracle on Goerli @@ -25,39 +23,28 @@ OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) OUTPUT_TIMESTAMP=$(echo ${OUTPUT} | cut -d' ' -f 2) OUTPUT_L2BLOCK_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) -L1_HEAD=$L1_FINALIZED_HASH -L2_CLAIM=$OUTPUT_ROOT -L2_BLOCK_NUMBER=$OUTPUT_L2BLOCK_NUMBER +export L1_HEAD=$L1_FINALIZED_HASH +export L2_CLAIM=$OUTPUT_ROOT +export L2_BLOCK_NUMBER=$OUTPUT_L2BLOCK_NUMBER STARTING_L2BLOCK_NUMBER=$((L2_BLOCK_NUMBER-100)) STARTING_OUTPUT_INDEX=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2OutputIndexAfter(uint256) returns(uint256)' "${STARTING_L2BLOCK_NUMBER}") STARTING_OUTPUT=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2Output(uint256) returns(bytes32,uint128,uint128)' "${STARTING_OUTPUT_INDEX}") -STARTING_OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) +export STARTING_OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) L2_HEAD_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) -L2_HEAD=$(cast block "${L2_HEAD_NUMBER}" --rpc-url "${L2RPC}" -f hash) +export L2_HEAD=$(cast block "${L2_HEAD_NUMBER}" --rpc-url "${L2RPC}" -f hash) TODAY=$(date +"%Y-%m-%d-%H-%M-%S") FILENAME=${TODAY}-op-program-data-log.sh -OP_PROGRAM_DATA_DIR=$(pwd)/op-program-db-sepolia-${TODAY} - -echo "L1_HEAD=${L1_HEAD}" >> ${FILENAME} -echo "L2_HEAD=${L2_HEAD}" >> ${FILENAME} -echo "L2_BLOCK_NUMBER=${L2_BLOCK_NUMBER}" >> ${FILENAME} -echo "STARTING_OUTPUT_ROOT=${STARTING_OUTPUT_ROOT}" >> ${FILENAME} -echo "L2_CLAIM=${L2_CLAIM}" >> ${FILENAME} -echo "OP_PROGRAM_DATA_DIR=${OP_PROGRAM_DATA_DIR}" >> ${FILENAME} -echo "L1RPC=${L1RPC}" >> ${FILENAME} -echo "L2RPC=${L2RPC}" >> ${FILENAME} - -set -x -./ethereum-optimism/op-program/bin/op-program \ - --log.level DEBUG \ - --l1 $L1RPC \ - --l2 $L2RPC \ - --network sepolia \ - --datadir ${OP_PROGRAM_DATA_DIR} \ - --l1.head $L1_HEAD \ - --l2.head $L2_HEAD \ - --l2.outputroot $STARTING_OUTPUT_ROOT \ - --l2.claim $L2_CLAIM \ - --l2.blocknumber $L2_BLOCK_NUMBER +export OP_PROGRAM_DATA_DIR=$(pwd)/op-program-db-sepolia-${TODAY} + +echo "export L1_HEAD=${L1_HEAD}" >> ${FILENAME} +echo "export L2_HEAD=${L2_HEAD}" >> ${FILENAME} +echo "export L2_BLOCK_NUMBER=${L2_BLOCK_NUMBER}" >> ${FILENAME} +echo "export STARTING_OUTPUT_ROOT=${STARTING_OUTPUT_ROOT}" >> ${FILENAME} +echo "export L2_CLAIM=${L2_CLAIM}" >> ${FILENAME} +echo "export OP_PROGRAM_DATA_DIR=${OP_PROGRAM_DATA_DIR}" >> ${FILENAME} +echo "export L1RPC=${L1RPC}" >> ${FILENAME} +echo "export L2RPC=${L2RPC}" >> ${FILENAME} + +./run-op-program.sh diff --git a/optimism/run-op-program.sh b/optimism/run-op-program.sh new file mode 100755 index 0000000000..f7936fee6e --- /dev/null +++ b/optimism/run-op-program.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +make -C ./ethereum-optimism/op-program op-program + +set -x +./ethereum-optimism/op-program/bin/op-program \ + --log.level DEBUG \ + --l1 $L1RPC \ + --l2 $L2RPC \ + --network sepolia \ + --datadir ${OP_PROGRAM_DATA_DIR} \ + --l1.head $L1_HEAD \ + --l2.head $L2_HEAD \ + --l2.outputroot $STARTING_OUTPUT_ROOT \ + --l2.claim $L2_CLAIM \ + --l2.blocknumber $L2_BLOCK_NUMBER From 0a715b74ff609e14a980aca2bf4e2bb5f9865dfd Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 23 Oct 2023 18:16:23 +0100 Subject: [PATCH 068/173] Separate out runner --- optimism/generate-config.sh | 50 +++++++++++++++++++++++++++++++++++++ optimism/run-code.sh | 44 ++------------------------------ 2 files changed, 52 insertions(+), 42 deletions(-) create mode 100755 optimism/generate-config.sh diff --git a/optimism/generate-config.sh b/optimism/generate-config.sh new file mode 100755 index 0000000000..e71f8a90ed --- /dev/null +++ b/optimism/generate-config.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -euo pipefail + +source rpcs.sh + +# L2 output oracle on Goerli +# L2_OUTPUT_ORACLE=0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0 +# L2 output oracle on Sepolia +L2_OUTPUT_ORACLE=0x90E9c4f8a994a250F6aEfd61CAFb4F2e895D458F + +L2_FINALIZED_NUMBER=$(cast block finalized --rpc-url "${L2RPC}" -f number) +echo "Finalize number: ${L2_FINALIZED_NUMBER}" 1>&2 +L2_FINALIZED_HASH=$(cast block "${L2_FINALIZED_NUMBER}" --rpc-url "${L2RPC}" -f hash) + +L1_FINALIZED_NUMBER=$(cast block finalized --rpc-url "${L1RPC}" -f number) +L1_FINALIZED_HASH=$(cast block "${L1_FINALIZED_NUMBER}" --rpc-url "${L1RPC}" -f hash) + +OUTPUT_INDEX=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2OutputIndexAfter(uint256) returns(uint256)' "${L2_FINALIZED_NUMBER}") +OUTPUT_INDEX=$((OUTPUT_INDEX-1)) + +OUTPUT=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2Output(uint256) returns(bytes32,uint128,uint128)' "${OUTPUT_INDEX}") +OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) +OUTPUT_TIMESTAMP=$(echo ${OUTPUT} | cut -d' ' -f 2) +OUTPUT_L2BLOCK_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) + +L1_HEAD=$L1_FINALIZED_HASH +L2_CLAIM=$OUTPUT_ROOT +L2_BLOCK_NUMBER=$OUTPUT_L2BLOCK_NUMBER + +STARTING_L2BLOCK_NUMBER=$((L2_BLOCK_NUMBER-100)) +STARTING_OUTPUT_INDEX=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2OutputIndexAfter(uint256) returns(uint256)' "${STARTING_L2BLOCK_NUMBER}") +STARTING_OUTPUT=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2Output(uint256) returns(bytes32,uint128,uint128)' "${STARTING_OUTPUT_INDEX}") +STARTING_OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) +L2_HEAD_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) +L2_HEAD=$(cast block "${L2_HEAD_NUMBER}" --rpc-url "${L2RPC}" -f hash) + +TODAY=$(date +"%Y-%m-%d-%H-%M-%S") +FILENAME=${TODAY}-op-program-data-log.sh +OP_PROGRAM_DATA_DIR=$(pwd)/op-program-db-sepolia-${TODAY} + +echo "export L1_HEAD=${L1_HEAD}" >> ${FILENAME} +echo "export L2_HEAD=${L2_HEAD}" >> ${FILENAME} +echo "export L2_BLOCK_NUMBER=${L2_BLOCK_NUMBER}" >> ${FILENAME} +echo "export STARTING_OUTPUT_ROOT=${STARTING_OUTPUT_ROOT}" >> ${FILENAME} +echo "export L2_CLAIM=${L2_CLAIM}" >> ${FILENAME} +echo "export OP_PROGRAM_DATA_DIR=${OP_PROGRAM_DATA_DIR}" >> ${FILENAME} +echo "export L1RPC=${L1RPC}" >> ${FILENAME} +echo "export L2RPC=${L2RPC}" >> ${FILENAME} + +echo "${FILENAME}" diff --git a/optimism/run-code.sh b/optimism/run-code.sh index 43cd300fe2..1af79e85d9 100755 --- a/optimism/run-code.sh +++ b/optimism/run-code.sh @@ -3,48 +3,8 @@ set -euo pipefail source rpcs.sh -# L2 output oracle on Goerli -# L2_OUTPUT_ORACLE=0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0 -# L2 output oracle on Sepolia -L2_OUTPUT_ORACLE=0x90E9c4f8a994a250F6aEfd61CAFb4F2e895D458F +FILENAME="$(./generate-config.sh)" -L2_FINALIZED_NUMBER=$(cast block finalized --rpc-url "${L2RPC}" -f number) -echo "Finalize number: ${L2_FINALIZED_NUMBER}" -L2_FINALIZED_HASH=$(cast block "${L2_FINALIZED_NUMBER}" --rpc-url "${L2RPC}" -f hash) - -L1_FINALIZED_NUMBER=$(cast block finalized --rpc-url "${L1RPC}" -f number) -L1_FINALIZED_HASH=$(cast block "${L1_FINALIZED_NUMBER}" --rpc-url "${L1RPC}" -f hash) - -OUTPUT_INDEX=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2OutputIndexAfter(uint256) returns(uint256)' "${L2_FINALIZED_NUMBER}") -OUTPUT_INDEX=$((OUTPUT_INDEX-1)) - -OUTPUT=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2Output(uint256) returns(bytes32,uint128,uint128)' "${OUTPUT_INDEX}") -OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) -OUTPUT_TIMESTAMP=$(echo ${OUTPUT} | cut -d' ' -f 2) -OUTPUT_L2BLOCK_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) - -export L1_HEAD=$L1_FINALIZED_HASH -export L2_CLAIM=$OUTPUT_ROOT -export L2_BLOCK_NUMBER=$OUTPUT_L2BLOCK_NUMBER - -STARTING_L2BLOCK_NUMBER=$((L2_BLOCK_NUMBER-100)) -STARTING_OUTPUT_INDEX=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2OutputIndexAfter(uint256) returns(uint256)' "${STARTING_L2BLOCK_NUMBER}") -STARTING_OUTPUT=$(cast call --rpc-url "${L1RPC}" "${L2_OUTPUT_ORACLE}" 'getL2Output(uint256) returns(bytes32,uint128,uint128)' "${STARTING_OUTPUT_INDEX}") -export STARTING_OUTPUT_ROOT=$(echo ${OUTPUT} | cut -d' ' -f 1) -L2_HEAD_NUMBER=$(echo ${OUTPUT} | cut -d' ' -f 3) -export L2_HEAD=$(cast block "${L2_HEAD_NUMBER}" --rpc-url "${L2RPC}" -f hash) - -TODAY=$(date +"%Y-%m-%d-%H-%M-%S") -FILENAME=${TODAY}-op-program-data-log.sh -export OP_PROGRAM_DATA_DIR=$(pwd)/op-program-db-sepolia-${TODAY} - -echo "export L1_HEAD=${L1_HEAD}" >> ${FILENAME} -echo "export L2_HEAD=${L2_HEAD}" >> ${FILENAME} -echo "export L2_BLOCK_NUMBER=${L2_BLOCK_NUMBER}" >> ${FILENAME} -echo "export STARTING_OUTPUT_ROOT=${STARTING_OUTPUT_ROOT}" >> ${FILENAME} -echo "export L2_CLAIM=${L2_CLAIM}" >> ${FILENAME} -echo "export OP_PROGRAM_DATA_DIR=${OP_PROGRAM_DATA_DIR}" >> ${FILENAME} -echo "export L1RPC=${L1RPC}" >> ${FILENAME} -echo "export L2RPC=${L2RPC}" >> ${FILENAME} +source $FILENAME ./run-op-program.sh From 7af8026becbf24924e4a27a79004ec68fe55731f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 23 Oct 2023 18:24:29 +0100 Subject: [PATCH 069/173] Add cannon to runner --- optimism/run-op-program.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/optimism/run-op-program.sh b/optimism/run-op-program.sh index f7936fee6e..c16072e8c9 100755 --- a/optimism/run-op-program.sh +++ b/optimism/run-op-program.sh @@ -2,6 +2,7 @@ set -euo pipefail make -C ./ethereum-optimism/op-program op-program +make -C ./ethereum-optimism/cannon cannon set -x ./ethereum-optimism/op-program/bin/op-program \ @@ -15,3 +16,24 @@ set -x --l2.outputroot $STARTING_OUTPUT_ROOT \ --l2.claim $L2_CLAIM \ --l2.blocknumber $L2_BLOCK_NUMBER + +./ethereum-optimism/cannon/bin/cannon load-elf --path=./ethereum-optimism/op-program/bin/op-program-client.elf + +./ethereum-optimism/cannon/bin/cannon run \ + --pprof.cpu \ + --info-at '%10000000' \ + --proof-at never \ + --input ./state.json \ + -- \ + ./ethereum-optimism/op-program/bin/op-program \ + --log.level DEBUG \ + --l1 ${L1RPC} \ + --l2 ${L2RPC} \ + --network sepolia \ + --datadir ${OP_PROGRAM_DATA_DIR} \ + --l1.head ${L1_HEAD} \ + --l2.head ${L2_HEAD} \ + --l2.outputroot ${STARTING_OUTPUT_ROOT} \ + --l2.claim ${L2_CLAIM} \ + --l2.blocknumber ${L2_BLOCK_NUMBER} \ + --server From 85841b02ac2b08b17bb66a86ef5293148ea3fadf Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 23 Oct 2023 18:27:30 +0100 Subject: [PATCH 070/173] Allow an explicit filename to override --- optimism/run-code.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/optimism/run-code.sh b/optimism/run-code.sh index 1af79e85d9..a07555070f 100755 --- a/optimism/run-code.sh +++ b/optimism/run-code.sh @@ -3,7 +3,11 @@ set -euo pipefail source rpcs.sh -FILENAME="$(./generate-config.sh)" +set +u +if [ -z "${FILENAME}" ]; then + FILENAME="$(./generate-config.sh)" +fi +set -u source $FILENAME From d2aac11e5582a26bb7b26c68f721ddef0ccad09b Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 15:22:51 +0100 Subject: [PATCH 071/173] Import parsing for CLI --- Cargo.lock | 21 ++++---- optimism/Cargo.toml | 1 + optimism/src/cannon.rs | 118 ++++++++++++++++++++++++++++++++++++++++ optimism/src/lib.rs | 1 + optimism/src/main.rs | 119 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 optimism/src/cannon.rs diff --git a/Cargo.lock b/Cargo.lock index 8028b4f6d9..03396e2f2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1260,6 +1260,7 @@ dependencies = [ "mina-curves", "mina-poseidon", "poly-commitment", + "regex", "rmp-serde", "serde", "serde_json", @@ -1325,9 +1326,9 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -2100,25 +2101,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.7.3", + "regex-syntax 0.8.2", ] [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.3", + "regex-syntax 0.8.2", ] [[package]] @@ -2129,9 +2130,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rmp" diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml index 9b45ab1d29..f1edb563cd 100644 --- a/optimism/Cargo.toml +++ b/optimism/Cargo.toml @@ -27,3 +27,4 @@ ark-poly = { version = "0.3.0", features = [ "parallel" ] } ark-ff = { version = "0.3.0", features = [ "parallel" ] } clap = "4.4.6" hex = "0.4.3" +regex = "1.10.2" diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs new file mode 100644 index 0000000000..c7693f3cab --- /dev/null +++ b/optimism/src/cannon.rs @@ -0,0 +1,118 @@ +// Data structure and stuff for compatibility with Cannon + +use regex::Regex; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Page { + pub index: u32, + pub data: String, +} + +// The renaming below keeps compatibility with OP Cannon's state format +#[derive(Serialize, Deserialize, Debug)] +pub struct State { + pub memory: Vec, + #[serde(rename = "preimageKey")] + pub preimage_key: String, + #[serde(rename = "preimageOffset")] + pub preimage_offset: u32, + pub pc: u32, + #[serde(rename = "nextPC")] + next_pc: u32, // + pub lo: u32, + pub hi: u32, + pub heap: u32, + exit: u8, + pub exited: bool, + pub step: u64, + pub registers: [u32; 32], + pub last_hint: Option>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum StepFrequency { + Never, + Always, + Exactly(u64), + Every(u64), +} + +// Simple parser for Cannon's "frequency format" +// A frequency input is either +// - never/always +// - = (only at step n) +// - % (every steps multiple of n) +pub fn step_frequency_parser(s: &str) -> std::result::Result { + use StepFrequency::*; + + let mod_re = Regex::new(r"%([0-9]+)").unwrap(); + let eq_re = Regex::new(r"=([0-9]+)").unwrap(); + + match s { + "never" => Ok(Never), + "always" => Ok(Always), + s => { + if let Some(m) = mod_re.captures(s) { + Ok(Every(m[1].parse::().unwrap())) + } else if let Some(m) = eq_re.captures(s) { + Ok(Exactly(m[1].parse::().unwrap())) + } else { + Err(format!("Unknown frequency format {}", s)) + } + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn sp_parser() { + use StepFrequency::*; + assert_eq!(step_frequency_parser("never"), Ok(Never)); + assert_eq!(step_frequency_parser("always"), Ok(Always)); + assert_eq!(step_frequency_parser("=123"), Ok(Exactly(123))); + assert_eq!(step_frequency_parser("%123"), Ok(Every(123))); + assert!(step_frequency_parser("@123").is_err()); + } +} + +impl ToString for State { + // A very debatable and incomplete, but serviceable, `to_string` implementation. + fn to_string(&self) -> String { + format!( + "memory_size (length): {}\nfirst page size: {}\npreimage key: {}\npreimage offset:{}\npc: {}\nlo: {}\nhi: {}\nregisters:{:#?} ", + self.memory.len(), + self.memory[0].data.len(), + self.preimage_key, + self.preimage_offset, + self.pc, + self.lo, + self.hi, + self.registers + ) + } +} + +#[derive(Debug)] +pub struct HostProgram { + pub name: String, + pub arguments: Vec, +} + +#[derive(Debug)] +pub struct VmConfiguration { + pub input_state_file: String, + pub output_state_file: String, + pub metadata_file: String, + pub proof_at: StepFrequency, + pub stop_at: StepFrequency, + pub info_at: StepFrequency, + pub proof_fmt: String, + pub snapshot_fmt: String, + pub pprof_cpu: bool, + pub host: Option, +} diff --git a/optimism/src/lib.rs b/optimism/src/lib.rs index e69de29bb2..3bec498068 100644 --- a/optimism/src/lib.rs +++ b/optimism/src/lib.rs @@ -0,0 +1 @@ +pub mod cannon; diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 9301ced20f..f7692547cf 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -1,6 +1,123 @@ -use std:: process::ExitCode; +use clap::{arg, value_parser, Arg, ArgAction, Command}; +use kimchi_optimism::cannon::VmConfiguration; +use std::process::ExitCode; + +fn cli() -> VmConfiguration { + use kimchi_optimism::cannon::*; + + let app_name = "zkvm"; + let cli = Command::new(app_name) + .version("0.1") + .about("MIPS-based zkvm") + .arg(arg!(--input "initial state file").default_value("state.json")) + .arg(arg!(--output "output state file").default_value("out.json")) + .arg(arg!(--meta "metadata file").default_value("meta.json")) + // The CLI arguments below this line are ignored at this point + .arg( + Arg::new("proof-at") + .short('p') + .long("proof-at") + .value_name("FREQ") + .default_value("never") + .value_parser(step_frequency_parser), + ) + .arg( + Arg::new("proof-fmt") + .long("proof-fmt") + .value_name("FORMAT") + .default_value("proof-%d.json"), + ) + .arg( + Arg::new("snapshot-fmt") + .long("snapshot-fmt") + .value_name("FORMAT") + .default_value("state-%d.json"), + ) + .arg( + Arg::new("stop-at") + .long("stop-at") + .value_name("FREQ") + .default_value("never") + .value_parser(step_frequency_parser), + ) + .arg( + Arg::new("info-at") + .long("info-at") + .value_name("FREQ") + .default_value("never") + .value_parser(step_frequency_parser), + ) + .arg( + Arg::new("pprof-cpu") + .long("pprof-cpu") + .action(ArgAction::SetTrue), + ) + .arg( + arg!(host: [HOST] "host program specification [host program arguments]") + .num_args(1..) + .last(true) + .value_parser(value_parser!(String)), + ); + + let cli = cli.get_matches(); + + let input_state_file = cli + .get_one::("input") + .expect("Default ensures there is always a value"); + + let output_state_file = cli + .get_one::("output") + .expect("Default ensures there is always a value"); + + let metadata_file = cli + .get_one::("meta") + .expect("Default ensures there is always a value"); + + let proof_at = cli.get_one::("proof-at").expect(""); + let info_at = cli.get_one::("info-at").expect(""); + let stop_at = cli.get_one::("stop-at").expect(""); + + let proof_fmt = cli.get_one::("proof-fmt").expect(""); + let snapshot_fmt = cli.get_one::("snapshot-fmt").expect(""); + let pprof_cpu = cli.get_one::("pprof-cpu").expect(""); + + let host_spec = cli + .get_many::("host") + .map(|vals| vals.collect::>()) + .unwrap_or_default(); + + let host = if host_spec.is_empty() { + None + } else { + Some(HostProgram { + name: host_spec[0].to_string(), + arguments: host_spec[1..] + .to_vec() + .iter() + .map(|x| x.to_string()) + .collect(), + }) + }; + + VmConfiguration { + input_state_file: input_state_file.to_string(), + output_state_file: output_state_file.to_string(), + metadata_file: metadata_file.to_string(), + proof_at: proof_at.clone(), + stop_at: stop_at.clone(), + info_at: info_at.clone(), + proof_fmt: proof_fmt.to_string(), + snapshot_fmt: snapshot_fmt.to_string(), + pprof_cpu: *pprof_cpu, + host, + } +} pub fn main() -> ExitCode { + let configuration = cli(); + + println!("configuration\n{:#?}", configuration); + // TODO: Logic ExitCode::FAILURE } From 1cd783279528a05bb1fea7d2756fc72bf745ca40 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 15:31:10 +0100 Subject: [PATCH 072/173] Load the state declared in the flags --- optimism/src/main.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/optimism/src/main.rs b/optimism/src/main.rs index f7692547cf..6989806a70 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -1,6 +1,6 @@ use clap::{arg, value_parser, Arg, ArgAction, Command}; -use kimchi_optimism::cannon::VmConfiguration; -use std::process::ExitCode; +use kimchi_optimism::cannon::{State, VmConfiguration}; +use std::{fs::File, io::BufReader, process::ExitCode}; fn cli() -> VmConfiguration { use kimchi_optimism::cannon::*; @@ -118,6 +118,23 @@ pub fn main() -> ExitCode { println!("configuration\n{:#?}", configuration); + let file = File::open(configuration.input_state_file).expect("file"); + + let reader = BufReader::new(file); + // Read the JSON contents of the file as an instance of `State`. + let state: State = serde_json::from_reader(reader).expect("Error reading input state file"); + + if let Some(host_program) = configuration.host { + println!("Launching host program {}", host_program.name); + + let _child = std::process::Command::new(host_program.name) + .args(host_program.arguments) + .spawn() + .expect("Could not spawn host process"); + }; + + println!("{}", state.to_string()); + // TODO: Logic ExitCode::FAILURE } From 391ea4e2b14d63da72a9fc2eddf812dfc47878a3 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 15:34:13 +0100 Subject: [PATCH 073/173] Run the VM CLI from the main script --- optimism/run-code.sh | 2 ++ optimism/run-vm.sh | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100755 optimism/run-vm.sh diff --git a/optimism/run-code.sh b/optimism/run-code.sh index a07555070f..c664e58025 100755 --- a/optimism/run-code.sh +++ b/optimism/run-code.sh @@ -12,3 +12,5 @@ set -u source $FILENAME ./run-op-program.sh + +./run-vm.sh diff --git a/optimism/run-vm.sh b/optimism/run-vm.sh new file mode 100755 index 0000000000..4f24506b4e --- /dev/null +++ b/optimism/run-vm.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +cargo run -p kimchi_optimism -- \ + --pprof-cpu \ + --info-at '%10000000' \ + --proof-at never \ + --input ./state.json \ + -- \ + ./ethereum-optimism/op-program/bin/op-program \ + --log.level DEBUG \ + --l1 ${L1RPC} \ + --l2 ${L2RPC} \ + --network sepolia \ + --datadir ${OP_PROGRAM_DATA_DIR} \ + --l1.head ${L1_HEAD} \ + --l2.head ${L2_HEAD} \ + --l2.outputroot ${STARTING_OUTPUT_ROOT} \ + --l2.claim ${L2_CLAIM} \ + --l2.blocknumber ${L2_BLOCK_NUMBER} \ + --server From 4a54b24d685a9f24cc20180a7b9077e51d11df17 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 00:20:48 +0100 Subject: [PATCH 074/173] Abstract ExprError over Column --- kimchi/src/circuits/expr.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index f331d96b1b..81ff2da386 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -21,7 +21,11 @@ use o1_utils::{foreign_field::ForeignFieldHelpers, FieldHelpers}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; use std::ops::{Add, AddAssign, Mul, Neg, Sub}; -use std::{cmp::Ordering, fmt, iter::FromIterator}; +use std::{ + cmp::Ordering, + fmt::{self, Debug}, + iter::FromIterator, +}; use std::{ collections::{HashMap, HashSet}, ops::MulAssign, @@ -32,7 +36,7 @@ use CurrOrNext::{Curr, Next}; use self::constraints::ExprOps; #[derive(Debug, Error)] -pub enum ExprError { +pub enum ExprError { #[error("Empty stack")] EmptyStack, @@ -668,7 +672,7 @@ impl Variable { fn evaluate( &self, evals: &ProofEvaluations>, - ) -> Result { + ) -> Result> { let point_evaluations = { use Column::*; match self.col { @@ -745,7 +749,7 @@ impl PolishToken { pt: F, evals: &ProofEvaluations>, c: &Constants, - ) -> Result { + ) -> Result> { let mut stack = vec![]; let mut cache: Vec = vec![]; @@ -1562,7 +1566,7 @@ impl Expr> { pt: F, evals: &ProofEvaluations>, env: &Environment, - ) -> Result { + ) -> Result> { self.evaluate_(d, pt, evals, &env.constants) } @@ -1573,7 +1577,7 @@ impl Expr> { pt: F, evals: &ProofEvaluations>, c: &Constants, - ) -> Result { + ) -> Result> { use Expr::*; match self { Double(x) => x.evaluate_(d, pt, evals, c).map(|x| x.double()), @@ -1642,7 +1646,7 @@ impl Expr { pt: F, zk_rows: u64, evals: &ProofEvaluations>, - ) -> Result { + ) -> Result> { use Expr::*; match self { Constant(x) => Ok(*x), @@ -2145,7 +2149,7 @@ impl + Clone + One + Zero + PartialEq> Expr { pub fn linearize( &self, evaluated: HashSet, - ) -> Result>, ExprError> { + ) -> Result>, ExprError> { let mut res: HashMap> = HashMap::new(); let mut constant_term: Expr = Self::zero(); let monomials = self.monomials(&evaluated); From 163457bcf4440b38cd38db7684c0e76692a7d551 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 00:26:21 +0100 Subject: [PATCH 075/173] Abstract Variable over Column --- kimchi/src/circuits/expr.rs | 18 +++++++++--------- kimchi/src/circuits/polynomials/varbasemul.rs | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 81ff2da386..ee20be142b 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -50,7 +50,7 @@ pub enum ExprError { MissingIndexEvaluation(Column), #[error("Linearization failed (too many unevaluated columns: {0:?}")] - FailedLinearization(Vec), + FailedLinearization(Vec>), #[error("runtime table not available")] MissingRuntime, @@ -235,14 +235,14 @@ impl Column { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] /// A type representing a variable which can appear in a constraint. It specifies a column /// and a relative position (Curr or Next) -pub struct Variable { +pub struct Variable { /// The column of this variable pub col: Column, /// The row (Curr of Next) of this variable pub row: CurrOrNext, } -impl Variable { +impl Variable { fn ocaml(&self) -> String { format!("var({:?}, {:?})", self.col, self.row) } @@ -482,7 +482,7 @@ pub struct RowOffset { #[derive(Clone, Debug, PartialEq)] pub enum Expr { Constant(C), - Cell(Variable), + Cell(Variable), Double(Box>), Square(Box>), BinOp(Op2, Box>, Box>), @@ -652,7 +652,7 @@ pub enum PolishToken { col: usize, }, Literal(F), - Cell(Variable), + Cell(Variable), Dup, Pow(u64), Add, @@ -668,7 +668,7 @@ pub enum PolishToken { SkipIfNot(FeatureFlag, usize), } -impl Variable { +impl Variable { fn evaluate( &self, evals: &ProofEvaluations>, @@ -1983,7 +1983,7 @@ impl Expr { } } -type Monomials = HashMap, Expr>; +type Monomials = HashMap>, Expr>; fn mul_monomials + Clone + One + Zero + PartialEq>( e1: &Monomials, @@ -2024,8 +2024,8 @@ impl + Clone + One + Zero + PartialEq> Expr { } } - fn monomials(&self, ev: &HashSet) -> HashMap, Expr> { - let sing = |v: Vec, c: Expr| { + fn monomials(&self, ev: &HashSet) -> HashMap>, Expr> { + let sing = |v: Vec>, c: Expr| { let mut h = HashMap::new(); h.insert(v, c); h diff --git a/kimchi/src/circuits/polynomials/varbasemul.rs b/kimchi/src/circuits/polynomials/varbasemul.rs index 22b9522195..0ed90e711c 100644 --- a/kimchi/src/circuits/polynomials/varbasemul.rs +++ b/kimchi/src/circuits/polynomials/varbasemul.rs @@ -12,7 +12,7 @@ use crate::circuits::{ argument::{Argument, ArgumentEnv, ArgumentType}, - expr::{constraints::ExprOps, Cache, Column, Variable}, + expr::{constraints::ExprOps, Cache, Column, Variable as VariableGen}, gate::{CircuitGate, CurrOrNext, GateType}, wires::{GateWires, COLUMNS}, }; @@ -20,6 +20,8 @@ use ark_ff::{FftField, PrimeField}; use std::marker::PhantomData; use CurrOrNext::{Curr, Next}; +type Variable = VariableGen; + //~ We implement custom Plonk constraints for short Weierstrass curve variable base scalar multiplication. //~ //~ Given a finite field $\mathbb{F}_q$ of order $q$, if the order is not a multiple of 2 nor 3, then an From 7b5cc8fb3c174daa2bf5e4de50524f323193a3f6 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 00:51:42 +0100 Subject: [PATCH 076/173] Abstract Expr over Column --- kimchi/src/circuits/expr.rs | 130 +++++++++++++++++++----------------- kimchi/src/linearization.rs | 2 +- 2 files changed, 69 insertions(+), 63 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index ee20be142b..9c5d62aadc 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -480,24 +480,26 @@ pub struct RowOffset { /// the corresponding combination of the polynomials corresponding to /// the above variables should vanish on the PLONK domain. #[derive(Clone, Debug, PartialEq)] -pub enum Expr { +pub enum Expr { Constant(C), Cell(Variable), - Double(Box>), - Square(Box>), - BinOp(Op2, Box>, Box>), + Double(Box>), + Square(Box>), + BinOp(Op2, Box>, Box>), VanishesOnZeroKnowledgeAndPreviousRows, /// UnnormalizedLagrangeBasis(i) is /// (x^n - 1) / (x - omega^i) UnnormalizedLagrangeBasis(RowOffset), - Pow(Box>, u64), - Cache(CacheId, Box>), + Pow(Box>, u64), + Cache(CacheId, Box>), /// If the feature flag is enabled, return the first expression; otherwise, return the second. - IfFeature(FeatureFlag, Box>, Box>), + IfFeature(FeatureFlag, Box>, Box>), } -impl + PartialEq + Clone> Expr { - fn apply_feature_flags_inner(&self, features: &FeatureFlags) -> (Expr, bool) { +impl + PartialEq + Clone, Column: Clone + PartialEq> + Expr +{ + fn apply_feature_flags_inner(&self, features: &FeatureFlags) -> (Expr, bool) { use Expr::*; match self { Constant(_) @@ -631,7 +633,7 @@ impl + PartialEq + Clone> Expr { } } } - pub fn apply_feature_flags(&self, features: &FeatureFlags) -> Expr { + pub fn apply_feature_flags(&self, features: &FeatureFlags) -> Expr { let (res, _) = self.apply_feature_flags_inner(features); res } @@ -831,12 +833,14 @@ impl PolishToken { } } -impl Expr { +impl Expr { /// Convenience function for constructing cell variables. - pub fn cell(col: Column, row: CurrOrNext) -> Expr { + pub fn cell(col: Column, row: CurrOrNext) -> Expr { Expr::Cell(Variable { col, row }) } +} +impl Expr { pub fn double(self) -> Self { Expr::Double(Box::new(self)) } @@ -846,7 +850,7 @@ impl Expr { } /// Convenience function for constructing constant expressions. - pub fn constant(c: C) -> Expr { + pub fn constant(c: C) -> Expr { Expr::Constant(c) } @@ -872,7 +876,7 @@ impl Expr { } } -impl fmt::Display for Expr> +impl fmt::Display for Expr, Column> where F: PrimeField, { @@ -1428,7 +1432,7 @@ fn get_domain(d: Domain, env: &Environment) -> D { } } -impl Expr> { +impl Expr, Column> { /// Convenience function for constructing expressions from literal /// field elements. pub fn literal(x: F) -> Self { @@ -1438,7 +1442,7 @@ impl Expr> { /// Combines multiple constraints `[c0, ..., cn]` into a single constraint /// `alpha^alpha0 * c0 + alpha^{alpha0 + 1} * c1 + ... + alpha^{alpha0 + n} * cn`. pub fn combine_constraints(alphas: impl Iterator, cs: Vec) -> Self { - let zero = Expr::>::zero(); + let zero = Expr::, Column>::zero(); cs.into_iter() .zip_eq(alphas) .map(|(c, i)| Expr::Constant(ConstantExpr::Alpha.pow(i as u64)) * c) @@ -1446,7 +1450,7 @@ impl Expr> { } } -impl Expr> { +impl Expr, Column> { /// Compile an expression to an RPN expression. pub fn to_polish(&self) -> Vec> { let mut res = vec![]; @@ -1536,7 +1540,7 @@ impl Expr> { Expr::Constant(ConstantExpr::Beta) } - fn evaluate_constants_(&self, c: &Constants) -> Expr { + fn evaluate_constants_(&self, c: &Constants) -> Expr { use Expr::*; // TODO: Use cache match self { @@ -1623,7 +1627,7 @@ impl Expr> { } /// Evaluate the constant expressions in this expression down into field elements. - pub fn evaluate_constants(&self, env: &Environment) -> Expr { + pub fn evaluate_constants(&self, env: &Environment) -> Expr { self.evaluate_constants_(&env.constants) } @@ -1638,7 +1642,7 @@ enum Either { Right(B), } -impl Expr { +impl Expr { /// Evaluate an expression into a field element. pub fn evaluate( &self, @@ -1901,10 +1905,10 @@ impl Linearization { } } -impl Linearization>> { +impl Linearization, Column>> { /// Evaluate the constants in a linearization with `ConstantExpr` coefficients down /// to literal field elements. - pub fn evaluate_constants(&self, env: &Environment) -> Linearization> { + pub fn evaluate_constants(&self, env: &Environment) -> Linearization> { self.map(|e| e.evaluate_constants(env)) } } @@ -1939,7 +1943,7 @@ impl Linearization>> { } } -impl Linearization>> { +impl Linearization, Column>> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. pub fn to_polynomial( @@ -1971,7 +1975,7 @@ impl Linearization>> { } } -impl Expr { +impl Expr { /// Exponentiate an expression #[must_use] pub fn pow(self, p: u64) -> Self { @@ -1983,27 +1987,27 @@ impl Expr { } } -type Monomials = HashMap>, Expr>; +type Monomials = HashMap>, Expr>; fn mul_monomials + Clone + One + Zero + PartialEq>( e1: &Monomials, e2: &Monomials, ) -> Monomials { - let mut res: HashMap<_, Expr> = HashMap::new(); + let mut res: HashMap<_, Expr> = HashMap::new(); for (m1, c1) in e1.iter() { for (m2, c2) in e2.iter() { let mut m = m1.clone(); m.extend(m2); m.sort(); let c1c2 = c1.clone() * c2.clone(); - let v = res.entry(m).or_insert_with(Expr::::zero); + let v = res.entry(m).or_insert_with(Expr::::zero); *v = v.clone() + c1c2; } } res } -impl + Clone + One + Zero + PartialEq> Expr { +impl + Clone + One + Zero + PartialEq> Expr { // TODO: This function (which takes linear time) // is called repeatedly in monomials, yielding quadratic behavior for // that function. It's ok for now as we only call that function once on @@ -2024,13 +2028,13 @@ impl + Clone + One + Zero + PartialEq> Expr { } } - fn monomials(&self, ev: &HashSet) -> HashMap>, Expr> { - let sing = |v: Vec>, c: Expr| { + fn monomials(&self, ev: &HashSet) -> HashMap>, Expr> { + let sing = |v: Vec>, c: Expr| { let mut h = HashMap::new(); h.insert(v, c); h }; - let constant = |e: Expr| sing(vec![], e); + let constant = |e: Expr| sing(vec![], e); use Expr::*; if self.is_constant(ev) { @@ -2040,7 +2044,7 @@ impl + Clone + One + Zero + PartialEq> Expr { match self { Pow(x, d) => { // Run the multiplication logic with square and multiply - let mut acc = sing(vec![], Expr::::one()); + let mut acc = sing(vec![], Expr::::one()); let mut acc_is_one = true; let x = x.monomials(ev); @@ -2149,9 +2153,9 @@ impl + Clone + One + Zero + PartialEq> Expr { pub fn linearize( &self, evaluated: HashSet, - ) -> Result>, ExprError> { - let mut res: HashMap> = HashMap::new(); - let mut constant_term: Expr = Self::zero(); + ) -> Result>, ExprError> { + let mut res: HashMap> = HashMap::new(); + let mut constant_term: Expr = Self::zero(); let monomials = self.monomials(&evaluated); for (m, c) in monomials { @@ -2283,7 +2287,7 @@ impl Mul> for ConstantExpr { } } -impl Zero for Expr { +impl Zero for Expr { fn zero() -> Self { Expr::Constant(F::zero()) } @@ -2296,7 +2300,7 @@ impl Zero for Expr { } } -impl One for Expr { +impl One for Expr { fn one() -> Self { Expr::Constant(F::one()) } @@ -2309,10 +2313,10 @@ impl One for Expr { } } -impl> Neg for Expr { - type Output = Expr; +impl, Column> Neg for Expr { + type Output = Expr; - fn neg(self) -> Expr { + fn neg(self) -> Expr { match self { Expr::Constant(x) => Expr::Constant(x.neg()), e => Expr::BinOp( @@ -2324,8 +2328,8 @@ impl> Neg for Expr { } } -impl Add> for Expr { - type Output = Expr; +impl Add> for Expr { + type Output = Expr; fn add(self, other: Self) -> Self { if self.is_zero() { return other; @@ -2337,7 +2341,7 @@ impl Add> for Expr { } } -impl AddAssign> for Expr { +impl AddAssign> for Expr { fn add_assign(&mut self, other: Self) { if self.is_zero() { *self = other; @@ -2347,8 +2351,8 @@ impl AddAssign> for Expr { } } -impl Mul> for Expr { - type Output = Expr; +impl Mul> for Expr { + type Output = Expr; fn mul(self, other: Self) -> Self { if self.is_zero() || other.is_zero() { return Self::zero(); @@ -2364,9 +2368,10 @@ impl Mul> for Expr { } } -impl MulAssign> for Expr +impl MulAssign> for Expr where F: Zero + One + PartialEq + Clone, + Column: PartialEq + Clone, { fn mul_assign(&mut self, other: Self) { if self.is_zero() || other.is_zero() { @@ -2379,8 +2384,8 @@ where } } -impl Sub> for Expr { - type Output = Expr; +impl Sub> for Expr { + type Output = Expr; fn sub(self, other: Self) -> Self { if other.is_zero() { return self; @@ -2389,13 +2394,13 @@ impl Sub> for Expr { } } -impl From for Expr { +impl From for Expr { fn from(x: u64) -> Self { Expr::Constant(F::from(x)) } } -impl From for Expr> { +impl From for Expr, Column> { fn from(x: u64) -> Self { Expr::Constant(ConstantExpr::Literal(F::from(x))) } @@ -2407,8 +2412,8 @@ impl From for ConstantExpr { } } -impl Mul for Expr> { - type Output = Expr>; +impl Mul for Expr, Column> { + type Output = Expr, Column>; fn mul(self, y: F) -> Self::Output { Expr::Constant(ConstantExpr::Literal(y)) * self @@ -2484,7 +2489,7 @@ where } } -impl Expr> +impl Expr, Column> where F: PrimeField, { @@ -2511,7 +2516,7 @@ where /// Recursively print the expression, /// except for the cached expression that are stored in the `cache`. - fn ocaml(&self, cache: &mut HashMap>>) -> String { + fn ocaml(&self, cache: &mut HashMap, Column>>) -> String { use Expr::*; match self { Double(x) => format!("double({})", x.ocaml(cache)), @@ -2564,7 +2569,7 @@ where res } - fn latex(&self, cache: &mut HashMap>>) -> String { + fn latex(&self, cache: &mut HashMap, Column>>) -> String { use Expr::*; match self { Double(x) => format!("2 ({})", x.latex(cache)), @@ -2600,7 +2605,7 @@ where /// Recursively print the expression, /// except for the cached expression that are stored in the `cache`. - fn text(&self, cache: &mut HashMap>>) -> String { + fn text(&self, cache: &mut HashMap, Column>>) -> String { use Expr::*; match self { Double(x) => format!("double({})", x.text(cache)), @@ -2733,24 +2738,25 @@ pub mod constraints { fn cache(&self, cache: &mut Cache) -> Self; } - impl ExprOps for Expr> + impl ExprOps for Expr, Column> where F: PrimeField, + Expr, Column>: std::fmt::Display, { fn two_pow(pow: u64) -> Self { - Expr::>::literal(>::two_pow(pow)) + Expr::, Column>::literal(>::two_pow(pow)) } fn two_to_limb() -> Self { - Expr::>::literal(>::two_to_limb()) + Expr::, Column>::literal(>::two_to_limb()) } fn two_to_2limb() -> Self { - Expr::>::literal(>::two_to_2limb()) + Expr::, Column>::literal(>::two_to_2limb()) } fn two_to_3limb() -> Self { - Expr::>::literal(>::two_to_3limb()) + Expr::, Column>::literal(>::two_to_3limb()) } fn double(&self) -> Self { @@ -2886,7 +2892,7 @@ pub mod constraints { // /// An alias for the intended usage of the expression type in constructing constraints. -pub type E = Expr>; +pub type E = Expr, Column>; /// Convenience function to create a constant as [Expr]. pub fn constant(x: F) -> E { diff --git a/kimchi/src/linearization.rs b/kimchi/src/linearization.rs index 566ef58216..1ed07bf53f 100644 --- a/kimchi/src/linearization.rs +++ b/kimchi/src/linearization.rs @@ -38,7 +38,7 @@ use ark_ff::{FftField, PrimeField, SquareRootField, Zero}; pub fn constraints_expr( feature_flags: Option<&FeatureFlags>, generic: bool, -) -> (Expr>, Alphas) { +) -> (Expr, Column>, Alphas) { // register powers of alpha so that we don't reuse them across mutually inclusive constraints let mut powers_of_alpha = Alphas::::default(); From 85c10bfed1ddb2a4c07d00ad919fe8ab746bf09d Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 00:58:49 +0100 Subject: [PATCH 077/173] Generalize PolishToken over Column --- book/src/specs/kimchi.md | 4 ++-- kimchi/src/circuits/expr.rs | 24 +++++++++++++++--------- kimchi/src/linearization.rs | 2 +- kimchi/src/prover_index.rs | 4 ++-- kimchi/src/verifier_index.rs | 4 ++-- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/book/src/specs/kimchi.md b/book/src/specs/kimchi.md index 4bd760e137..fc965cab55 100644 --- a/book/src/specs/kimchi.md +++ b/book/src/specs/kimchi.md @@ -1710,7 +1710,7 @@ pub struct ProverIndex> { /// The symbolic linearization of our circuit, which can compile to concrete types once certain values are learned in the protocol. #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>>, /// The mapping between powers of alpha and constraints #[serde(skip)] @@ -1856,7 +1856,7 @@ pub struct VerifierIndex> { pub lookup_index: Option>, #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>>, /// The mapping between powers of alpha and constraints #[serde(skip)] pub powers_of_alpha: Alphas, diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 9c5d62aadc..a6713e5acb 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -290,7 +290,7 @@ pub enum ConstantExpr { } impl ConstantExpr { - fn to_polish_(&self, res: &mut Vec>) { + fn to_polish_(&self, res: &mut Vec>) { match self { ConstantExpr::Alpha => res.push(PolishToken::Alpha), ConstantExpr::Beta => res.push(PolishToken::Beta), @@ -425,7 +425,7 @@ pub enum Op2 { } impl Op2 { - fn to_polish(&self) -> PolishToken { + fn to_polish(&self) -> PolishToken { use Op2::*; match self { Add => PolishToken::Add, @@ -643,7 +643,7 @@ impl + PartialEq + Clone, Column: Clone + Partia /// [reverse Polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation) /// expressions, which are vectors of the below tokens. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum PolishToken { +pub enum PolishToken { Alpha, Beta, Gamma, @@ -743,10 +743,10 @@ impl Variable { } } -impl PolishToken { +impl PolishToken { /// Evaluate an RPN expression to a field element. pub fn evaluate( - toks: &[PolishToken], + toks: &[PolishToken], d: D, pt: F, evals: &ProofEvaluations>, @@ -1450,16 +1450,20 @@ impl Expr, Column> { } } -impl Expr, Column> { +impl Expr, Column> { /// Compile an expression to an RPN expression. - pub fn to_polish(&self) -> Vec> { + pub fn to_polish(&self) -> Vec> { let mut res = vec![]; let mut cache = HashMap::new(); self.to_polish_(&mut cache, &mut res); res } - fn to_polish_(&self, cache: &mut HashMap, res: &mut Vec>) { + fn to_polish_( + &self, + cache: &mut HashMap, + res: &mut Vec>, + ) { match self { Expr::Double(x) => { x.to_polish_(cache, res); @@ -1539,7 +1543,9 @@ impl Expr, Column> { pub fn beta() -> Self { Expr::Constant(ConstantExpr::Beta) } +} +impl Expr, Column> { fn evaluate_constants_(&self, c: &Constants) -> Expr { use Expr::*; // TODO: Use cache @@ -1913,7 +1919,7 @@ impl Linearization, Column>> { } } -impl Linearization>> { +impl Linearization>> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. pub fn to_polynomial( diff --git a/kimchi/src/linearization.rs b/kimchi/src/linearization.rs index 1ed07bf53f..34a68a6317 100644 --- a/kimchi/src/linearization.rs +++ b/kimchi/src/linearization.rs @@ -339,7 +339,7 @@ pub fn linearization_columns( pub fn expr_linearization( feature_flags: Option<&FeatureFlags>, generic: bool, -) -> (Linearization>>, Alphas) { +) -> (Linearization>>, Alphas) { let evaluated_cols = linearization_columns::(feature_flags); let (expr, powers_of_alpha) = constraints_expr(feature_flags, generic); diff --git a/kimchi/src/prover_index.rs b/kimchi/src/prover_index.rs index 523d583e18..169abb335d 100644 --- a/kimchi/src/prover_index.rs +++ b/kimchi/src/prover_index.rs @@ -4,7 +4,7 @@ use crate::{ alphas::Alphas, circuits::{ constraints::{ColumnEvaluations, ConstraintSystem}, - expr::{Linearization, PolishToken}, + expr::{Column, Linearization, PolishToken}, }, curve::KimchiCurve, linearization::expr_linearization, @@ -28,7 +28,7 @@ pub struct ProverIndex> { /// The symbolic linearization of our circuit, which can compile to concrete types once certain values are learned in the protocol. #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>>, /// The mapping between powers of alpha and constraints #[serde(skip)] diff --git a/kimchi/src/verifier_index.rs b/kimchi/src/verifier_index.rs index d0e5f89172..37973e5fae 100644 --- a/kimchi/src/verifier_index.rs +++ b/kimchi/src/verifier_index.rs @@ -4,7 +4,7 @@ use crate::{ alphas::Alphas, circuits::{ - expr::{Linearization, PolishToken}, + expr::{Column, Linearization, PolishToken}, lookup::{index::LookupSelectors, lookups::LookupInfo}, polynomials::permutation::{vanishes_on_last_n_rows, zk_w}, wires::{COLUMNS, PERMUTS}, @@ -144,7 +144,7 @@ pub struct VerifierIndex> { pub lookup_index: Option>, #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>>, /// The mapping between powers of alpha and constraints #[serde(skip)] pub powers_of_alpha: Alphas, From 748269d50f886ba149a11630c0b852dbb4c66ddb Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 01:22:49 +0100 Subject: [PATCH 078/173] ColumnEnvironment trait --- kimchi/src/circuits/expr.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index a6713e5acb..6b80c759b5 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -119,7 +119,14 @@ pub struct Environment<'a, F: FftField> { pub lookup: Option>, } -impl<'a, F: FftField> Environment<'a, F> { +trait ColumnEnvironment<'a, F: FftField> { + type Column; + fn get_column(&self, col: &Column) -> Option<&'a Evaluations>>; +} + +impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { + type Column = Column; + fn get_column(&self, col: &Column) -> Option<&'a Evaluations>> { use Column::*; let lookup = self.lookup.as_ref(); From 21502a30264fe62403dff77c41fda115281fc28d Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 01:36:22 +0100 Subject: [PATCH 079/173] ColumnEvaluations trait --- kimchi/src/circuits/expr.rs | 130 +++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 61 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 6b80c759b5..c51310dfa2 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -677,72 +677,80 @@ pub enum PolishToken { SkipIfNot(FeatureFlag, usize), } +trait ColumnEvaluations { + type Column; + fn evaluate(&self, col: Self::Column) -> Result, ExprError>; +} + +impl ColumnEvaluations for ProofEvaluations> { + type Column = Column; + fn evaluate(&self, col: Self::Column) -> Result, ExprError> { + use Column::*; + match col { + Witness(i) => Ok(self.w[i]), + Z => Ok(self.z), + LookupSorted(i) => self.lookup_sorted[i].ok_or(ExprError::MissingIndexEvaluation(col)), + LookupAggreg => self + .lookup_aggregation + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupTable => self + .lookup_table + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupRuntimeTable => self + .runtime_lookup_table + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::Poseidon) => Ok(self.poseidon_selector), + Index(GateType::Generic) => Ok(self.generic_selector), + Index(GateType::CompleteAdd) => Ok(self.complete_add_selector), + Index(GateType::VarBaseMul) => Ok(self.mul_selector), + Index(GateType::EndoMul) => Ok(self.emul_selector), + Index(GateType::EndoMulScalar) => Ok(self.endomul_scalar_selector), + Index(GateType::RangeCheck0) => self + .range_check0_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::RangeCheck1) => self + .range_check1_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::ForeignFieldAdd) => self + .foreign_field_add_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::ForeignFieldMul) => self + .foreign_field_mul_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::Xor16) => self + .xor_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::Rot64) => self + .rot_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Permutation(i) => Ok(self.s[i]), + Coefficient(i) => Ok(self.coefficients[i]), + LookupKindIndex(LookupPattern::Xor) => self + .xor_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupKindIndex(LookupPattern::Lookup) => self + .lookup_gate_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupKindIndex(LookupPattern::RangeCheck) => self + .range_check_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupKindIndex(LookupPattern::ForeignFieldMul) => self + .foreign_field_mul_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupRuntimeSelector => self + .runtime_lookup_table_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(_) => Err(ExprError::MissingIndexEvaluation(col)), + } + } +} + impl Variable { fn evaluate( &self, evals: &ProofEvaluations>, ) -> Result> { - let point_evaluations = { - use Column::*; - match self.col { - Witness(i) => Ok(evals.w[i]), - Z => Ok(evals.z), - LookupSorted(i) => { - evals.lookup_sorted[i].ok_or(ExprError::MissingIndexEvaluation(self.col)) - } - LookupAggreg => evals - .lookup_aggregation - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - LookupTable => evals - .lookup_table - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - LookupRuntimeTable => evals - .runtime_lookup_table - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Index(GateType::Poseidon) => Ok(evals.poseidon_selector), - Index(GateType::Generic) => Ok(evals.generic_selector), - Index(GateType::CompleteAdd) => Ok(evals.complete_add_selector), - Index(GateType::VarBaseMul) => Ok(evals.mul_selector), - Index(GateType::EndoMul) => Ok(evals.emul_selector), - Index(GateType::EndoMulScalar) => Ok(evals.endomul_scalar_selector), - Index(GateType::RangeCheck0) => evals - .range_check0_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Index(GateType::RangeCheck1) => evals - .range_check1_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Index(GateType::ForeignFieldAdd) => evals - .foreign_field_add_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Index(GateType::ForeignFieldMul) => evals - .foreign_field_mul_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Index(GateType::Xor16) => evals - .xor_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Index(GateType::Rot64) => evals - .rot_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Permutation(i) => Ok(evals.s[i]), - Coefficient(i) => Ok(evals.coefficients[i]), - Column::LookupKindIndex(LookupPattern::Xor) => evals - .xor_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Column::LookupKindIndex(LookupPattern::Lookup) => evals - .lookup_gate_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Column::LookupKindIndex(LookupPattern::RangeCheck) => evals - .range_check_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Column::LookupKindIndex(LookupPattern::ForeignFieldMul) => evals - .foreign_field_mul_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Column::LookupRuntimeSelector => evals - .runtime_lookup_table_selector - .ok_or(ExprError::MissingIndexEvaluation(self.col)), - Index(_) => Err(ExprError::MissingIndexEvaluation(self.col)), - } - }?; + let point_evaluations = evals.evaluate(self.col)?; match self.row { CurrOrNext::Curr => Ok(point_evaluations.zeta), CurrOrNext::Next => Ok(point_evaluations.zeta_omega), From 06b0137bf66dba6183dfa56d21d94af23be61729 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 01:40:05 +0100 Subject: [PATCH 080/173] Generalize PolishToken::evaluate --- kimchi/src/circuits/expr.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index c51310dfa2..367ccd81e3 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -677,7 +677,7 @@ pub enum PolishToken { SkipIfNot(FeatureFlag, usize), } -trait ColumnEvaluations { +pub trait ColumnEvaluations { type Column; fn evaluate(&self, col: Self::Column) -> Result, ExprError>; } @@ -745,10 +745,10 @@ impl ColumnEvaluations for ProofEvaluations> { } } -impl Variable { - fn evaluate( +impl Variable { + fn evaluate>( &self, - evals: &ProofEvaluations>, + evals: &Evaluations, ) -> Result> { let point_evaluations = evals.evaluate(self.col)?; match self.row { @@ -758,13 +758,13 @@ impl Variable { } } -impl PolishToken { +impl PolishToken { /// Evaluate an RPN expression to a field element. - pub fn evaluate( + pub fn evaluate>( toks: &[PolishToken], d: D, pt: F, - evals: &ProofEvaluations>, + evals: &Evaluations, c: &Constants, ) -> Result> { let mut stack = vec![]; From bf1a5a32fd94ce010a8ede8ca9f0f002a2a00658 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 01:41:10 +0100 Subject: [PATCH 081/173] Generalize Expr::cell --- kimchi/src/circuits/expr.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 367ccd81e3..9dd3e22aa6 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -848,14 +848,12 @@ impl PolishToken { } } -impl Expr { +impl Expr { /// Convenience function for constructing cell variables. pub fn cell(col: Column, row: CurrOrNext) -> Expr { Expr::Cell(Variable { col, row }) } -} -impl Expr { pub fn double(self) -> Self { Expr::Double(Box::new(self)) } From 04566d957cb94c75abb365fcae4f2a9532732c4f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Mar 2023 06:07:00 +0100 Subject: [PATCH 082/173] Generalize to_polynomial --- kimchi/src/circuits/expr.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 9dd3e22aa6..48842f5437 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -1932,14 +1932,14 @@ impl Linearization, Column>> { } } -impl Linearization>> { +impl Linearization>> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. - pub fn to_polynomial( + pub fn to_polynomial>( &self, env: &Environment, pt: F, - evals: &ProofEvaluations>, + evals: &ColEvaluations, ) -> (F, Evaluations>) { let cs = &env.constants; let n = env.domain.d1.size(); From ff637ad64720c9dda1392fbad0235d2c9b536353 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 02:53:49 +0100 Subject: [PATCH 083/173] Generalize evaluate_ --- kimchi/src/circuits/expr.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 48842f5437..48af5ef9f7 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -1445,7 +1445,7 @@ fn get_domain(d: Domain, env: &Environment) -> D { } } -impl Expr, Column> { +impl Expr, Column> { /// Convenience function for constructing expressions from literal /// field elements. pub fn literal(x: F) -> Self { @@ -1594,11 +1594,11 @@ impl Expr, Column> { } /// Evaluate an expression as a field element against the constants. - pub fn evaluate_( + pub fn evaluate_>( &self, d: D, pt: F, - evals: &ProofEvaluations>, + evals: &Evaluations, c: &Constants, ) -> Result> { use Expr::*; From f8cb64b4a77842c45ec672eadf0ebf99fe59460e Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 03:01:48 +0100 Subject: [PATCH 084/173] Generalize remaining ProofEvaluations uses --- kimchi/src/circuits/expr.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 48af5ef9f7..395b2209ed 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -1583,11 +1583,11 @@ impl Expr, Column> { } /// Evaluate an expression as a field element against an environment. - pub fn evaluate( + pub fn evaluate>( &self, d: D, pt: F, - evals: &ProofEvaluations>, + evals: &Evaluations, env: &Environment, ) -> Result> { self.evaluate_(d, pt, evals, &env.constants) @@ -1663,12 +1663,12 @@ enum Either { impl Expr { /// Evaluate an expression into a field element. - pub fn evaluate( + pub fn evaluate>( &self, d: D, pt: F, zk_rows: u64, - evals: &ProofEvaluations>, + evals: &Evaluations, ) -> Result> { use Expr::*; match self { @@ -1965,11 +1965,11 @@ impl Linearization impl Linearization, Column>> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. - pub fn to_polynomial( + pub fn to_polynomial>( &self, env: &Environment, pt: F, - evals: &ProofEvaluations>, + evals: &ColEvaluations, ) -> (F, DensePolynomial) { let cs = &env.constants; let n = env.domain.d1.size(); From 50bb0ea9be4a9e7f7a0f2576c0fc14691d6c2baf Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 03:11:09 +0100 Subject: [PATCH 085/173] Move get_domain into ColumnEnvironment --- kimchi/src/circuits/expr.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 395b2209ed..12a1602413 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -122,6 +122,7 @@ pub struct Environment<'a, F: FftField> { trait ColumnEnvironment<'a, F: FftField> { type Column; fn get_column(&self, col: &Column) -> Option<&'a Evaluations>>; + fn get_domain(&self, d: Domain) -> D; } impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { @@ -147,6 +148,15 @@ impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { Permutation(_) => None, } } + + fn get_domain(&self, d: Domain) -> D { + match d { + Domain::D1 => self.domain.d1, + Domain::D2 => self.domain.d2, + Domain::D4 => self.domain.d4, + Domain::D8 => self.domain.d8, + } + } } // In this file, we define... @@ -984,7 +994,7 @@ fn unnormalized_lagrange_evals( Domain::D4 => 4, Domain::D8 => 8, }; - let res_domain = get_domain(res_domain, env); + let res_domain = env.get_domain(res_domain); let d1 = env.domain.d1; let n = d1.size; @@ -1436,15 +1446,6 @@ impl<'a, F: FftField> EvalResult<'a, F> { } } -fn get_domain(d: Domain, env: &Environment) -> D { - match d { - Domain::D1 => env.domain.d1, - Domain::D2 => env.domain.d2, - Domain::D4 => env.domain.d4, - Domain::D8 => env.domain.d8, - } -} - impl Expr, Column> { /// Convenience function for constructing expressions from literal /// field elements. @@ -1740,13 +1741,13 @@ impl Expr { assert_eq!(domain, d); evals } - EvalResult::Constant(x) => EvalResult::init_((d, get_domain(d, env)), |_| x), + EvalResult::Constant(x) => EvalResult::init_((d, env.get_domain(d)), |_| x), EvalResult::SubEvals { evals, domain: d_sub, shift: s, } => { - let res_domain = get_domain(d, env); + let res_domain = env.get_domain(d); let scale = (d_sub as usize) / (d as usize); assert!(scale != 0); EvalResult::init_((d, res_domain), |i| { @@ -1765,7 +1766,7 @@ impl Expr { where 'a: 'b, { - let dom = (d, get_domain(d, env)); + let dom = (d, env.get_domain(d)); let res: EvalResult<'a, F> = match self { Expr::Square(x) => match x.evaluations_helper(cache, d, env) { @@ -1827,9 +1828,9 @@ impl Expr { Expr::Pow(x, p) => { let x = x.evaluations_helper(cache, d, env); match x { - Either::Left(x) => x.pow(*p, (d, get_domain(d, env))), + Either::Left(x) => x.pow(*p, (d, env.get_domain(d))), Either::Right(id) => { - id.get_from(cache).unwrap().pow(*p, (d, get_domain(d, env))) + id.get_from(cache).unwrap().pow(*p, (d, env.get_domain(d))) } } } @@ -1864,7 +1865,7 @@ impl Expr { } } Expr::BinOp(op, e1, e2) => { - let dom = (d, get_domain(d, env)); + let dom = (d, env.get_domain(d)); let f = |x: EvalResult, y: EvalResult| match op { Op2::Mul => x.mul(y, dom), Op2::Add => x.add(y, dom), From accad129bda4fe3aeaa7b1e5802cbc8885455e9d Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 03:12:47 +0100 Subject: [PATCH 086/173] Generalize unnormalized_lagrange_evals over Environment --- kimchi/src/circuits/expr.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 12a1602413..5dfa78613a 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -982,11 +982,11 @@ pub fn pows(x: F, n: usize) -> Vec { /// = (omega^{q n} omega_8^{r n} - 1) / (omega_8^k - omega^i) /// = ((omega_8^n)^r - 1) / (omega_8^k - omega^i) /// = ((omega_8^n)^r - 1) / (omega^q omega_8^r - omega^i) -fn unnormalized_lagrange_evals( +fn unnormalized_lagrange_evals<'a, F: FftField, Environment: ColumnEnvironment<'a, F>>( l0_1: F, i: i32, res_domain: Domain, - env: &Environment, + env: &Environment, ) -> Evaluations> { let k = match res_domain { Domain::D1 => 1, @@ -996,7 +996,7 @@ fn unnormalized_lagrange_evals( }; let res_domain = env.get_domain(res_domain); - let d1 = env.domain.d1; + let d1 = env.get_domain(Domain::D1); let n = d1.size; // Renormalize negative values to wrap around at domain size let i = if i < 0 { From f49371a9067bf35796f78d585d0b3b61c091c33e Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 03:54:50 +0100 Subject: [PATCH 087/173] Generalize to_polynomials over Environment --- kimchi/src/circuits/expr.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 5dfa78613a..8f8151275d 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -119,10 +119,11 @@ pub struct Environment<'a, F: FftField> { pub lookup: Option>, } -trait ColumnEnvironment<'a, F: FftField> { +pub trait ColumnEnvironment<'a, F: FftField> { type Column; fn get_column(&self, col: &Column) -> Option<&'a Evaluations>>; fn get_domain(&self, d: Domain) -> D; + fn get_constants(&self) -> &Constants; } impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { @@ -157,6 +158,10 @@ impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { Domain::D8 => self.domain.d8, } } + + fn get_constants(&self) -> &Constants { + &self.constants + } } // In this file, we define... @@ -909,7 +914,7 @@ where } #[derive(Clone, Copy, Debug, PartialEq, FromPrimitive, ToPrimitive)] -enum Domain { +pub enum Domain { D1 = 1, D2 = 2, D4 = 4, @@ -1936,17 +1941,22 @@ impl Linearization, Column>> { impl Linearization>> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. - pub fn to_polynomial>( + pub fn to_polynomial< + 'a, + ColEvaluations: ColumnEvaluations, + Environment: ColumnEnvironment<'a, F, Column = Column>, + >( &self, - env: &Environment, + env: &Environment, pt: F, evals: &ColEvaluations, ) -> (F, Evaluations>) { - let cs = &env.constants; - let n = env.domain.d1.size(); + let cs = env.get_constants(); + let d1 = env.get_domain(Domain::D1); + let n = d1.size(); let mut res = vec![F::zero(); n]; self.index_terms.iter().for_each(|(idx, c)| { - let c = PolishToken::evaluate(c, env.domain.d1, pt, evals, cs).unwrap(); + let c = PolishToken::evaluate(c, d1, pt, evals, cs).unwrap(); let e = env .get_column(idx) .unwrap_or_else(|| panic!("Index polynomial {idx:?} not found")); @@ -1955,9 +1965,9 @@ impl Linearization .enumerate() .for_each(|(i, r)| *r += c * e.evals[scale * i]); }); - let p = Evaluations::>::from_vec_and_domain(res, env.domain.d1); + let p = Evaluations::>::from_vec_and_domain(res, d1); ( - PolishToken::evaluate(&self.constant_term, env.domain.d1, pt, evals, cs).unwrap(), + PolishToken::evaluate(&self.constant_term, d1, pt, evals, cs).unwrap(), p, ) } From f02d1d0c7da06f9af3fc964983bb599266d06025 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 03:57:30 +0100 Subject: [PATCH 088/173] Generalize evaluate over Environment --- kimchi/src/circuits/expr.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 8f8151275d..7fd243b08c 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -1589,14 +1589,18 @@ impl Expr, Column> { } /// Evaluate an expression as a field element against an environment. - pub fn evaluate>( + pub fn evaluate< + 'a, + Evaluations: ColumnEvaluations, + Environment: ColumnEnvironment<'a, F, Column = Column>, + >( &self, d: D, pt: F, evals: &Evaluations, - env: &Environment, + env: &Environment, ) -> Result> { - self.evaluate_(d, pt, evals, &env.constants) + self.evaluate_(d, pt, evals, &env.get_constants()) } /// Evaluate an expression as a field element against the constants. @@ -1652,8 +1656,11 @@ impl Expr, Column> { } /// Evaluate the constant expressions in this expression down into field elements. - pub fn evaluate_constants(&self, env: &Environment) -> Expr { - self.evaluate_constants_(&env.constants) + pub fn evaluate_constants<'a, Environment: ColumnEnvironment<'a, F, Column = Column>>( + &self, + env: &Environment, + ) -> Expr { + self.evaluate_constants_(env.get_constants()) } /// Compute the polynomial corresponding to this expression, in evaluation form. From ba32966f5e145507d4f90a07e78e3c2acd08d8cc Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 04:04:56 +0100 Subject: [PATCH 089/173] Generalize to_polynomial --- kimchi/src/circuits/expr.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 7fd243b08c..fd78255c43 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -1983,17 +1983,22 @@ impl Linearization impl Linearization, Column>> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. - pub fn to_polynomial>( + pub fn to_polynomial< + 'a, + ColEvaluations: ColumnEvaluations, + Environment: ColumnEnvironment<'a, F, Column = Column>, + >( &self, - env: &Environment, + env: &Environment, pt: F, evals: &ColEvaluations, ) -> (F, DensePolynomial) { - let cs = &env.constants; - let n = env.domain.d1.size(); + let cs = env.get_constants(); + let d1 = env.get_domain(Domain::D1); + let n = d1.size(); let mut res = vec![F::zero(); n]; self.index_terms.iter().for_each(|(idx, c)| { - let c = c.evaluate_(env.domain.d1, pt, evals, cs).unwrap(); + let c = c.evaluate_(d1, pt, evals, cs).unwrap(); let e = env .get_column(idx) .unwrap_or_else(|| panic!("Index polynomial {idx:?} not found")); @@ -2002,13 +2007,8 @@ impl Linearization, Column>> { .enumerate() .for_each(|(i, r)| *r += c * e.evals[scale * i]) }); - let p = Evaluations::>::from_vec_and_domain(res, env.domain.d1).interpolate(); - ( - self.constant_term - .evaluate_(env.domain.d1, pt, evals, cs) - .unwrap(), - p, - ) + let p = Evaluations::>::from_vec_and_domain(res, d1).interpolate(); + (self.constant_term.evaluate_(d1, pt, evals, cs).unwrap(), p) } } From 840b18f29d3587bbfaabd5690e9b08c7a2c7fa01 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 04:09:16 +0100 Subject: [PATCH 090/173] Generalize evaluations_helper over Environment --- kimchi/src/circuits/expr.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index fd78255c43..ccfef79d47 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -124,6 +124,8 @@ pub trait ColumnEnvironment<'a, F: FftField> { fn get_column(&self, col: &Column) -> Option<&'a Evaluations>>; fn get_domain(&self, d: Domain) -> D; fn get_constants(&self) -> &Constants; + fn vanishes_on_zero_knowledge_and_previous_rows(&self) -> &'a Evaluations>; + fn l0_1(&self) -> F; } impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { @@ -162,6 +164,14 @@ impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { fn get_constants(&self) -> &Constants { &self.constants } + + fn vanishes_on_zero_knowledge_and_previous_rows(&self) -> &'a Evaluations> { + &self.vanishes_on_zero_knowledge_and_previous_rows + } + + fn l0_1(&self) -> F { + self.l0_1 + } } // In this file, we define... @@ -1769,11 +1779,11 @@ impl Expr { } } - fn evaluations_helper<'a, 'b>( + fn evaluations_helper<'a, 'b, Environment: ColumnEnvironment<'a, F, Column = Column>>( &self, cache: &'b mut HashMap>, d: Domain, - env: &Environment<'a, F>, + env: &Environment, ) -> Either, CacheId> where 'a: 'b, @@ -1849,18 +1859,18 @@ impl Expr { Expr::VanishesOnZeroKnowledgeAndPreviousRows => EvalResult::SubEvals { domain: Domain::D8, shift: 0, - evals: env.vanishes_on_zero_knowledge_and_previous_rows, + evals: env.vanishes_on_zero_knowledge_and_previous_rows(), }, Expr::Constant(x) => EvalResult::Constant(*x), Expr::UnnormalizedLagrangeBasis(i) => { let offset = if i.zk_rows { - -(env.constants.zk_rows as i32) + i.offset + -(env.get_constants().zk_rows as i32) + i.offset } else { i.offset }; EvalResult::Evals { domain: d, - evals: unnormalized_lagrange_evals(env.l0_1, offset, d, env), + evals: unnormalized_lagrange_evals(env.l0_1(), offset, d, env), } } Expr::Cell(Variable { col, row }) => { From 473c435cedaa080654e79e7abf1b758d300235c8 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 04:25:19 +0100 Subject: [PATCH 091/173] Generalize Linearization over column --- book/src/specs/kimchi.md | 4 +-- kimchi/src/circuits/expr.rs | 53 ++++++++++++++++++++++++------------ kimchi/src/linearization.rs | 5 +++- kimchi/src/prover_index.rs | 2 +- kimchi/src/verifier_index.rs | 2 +- 5 files changed, 43 insertions(+), 23 deletions(-) diff --git a/book/src/specs/kimchi.md b/book/src/specs/kimchi.md index fc965cab55..842ecbb171 100644 --- a/book/src/specs/kimchi.md +++ b/book/src/specs/kimchi.md @@ -1710,7 +1710,7 @@ pub struct ProverIndex> { /// The symbolic linearization of our circuit, which can compile to concrete types once certain values are learned in the protocol. #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>, Column>, /// The mapping between powers of alpha and constraints #[serde(skip)] @@ -1856,7 +1856,7 @@ pub struct VerifierIndex> { pub lookup_index: Option>, #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>, Column>, /// The mapping between powers of alpha and constraints #[serde(skip)] pub powers_of_alpha: Alphas, diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index ccfef79d47..093bc972cb 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -121,7 +121,7 @@ pub struct Environment<'a, F: FftField> { pub trait ColumnEnvironment<'a, F: FftField> { type Column; - fn get_column(&self, col: &Column) -> Option<&'a Evaluations>>; + fn get_column(&self, col: &Self::Column) -> Option<&'a Evaluations>>; fn get_domain(&self, d: Domain) -> D; fn get_constants(&self) -> &Constants; fn vanishes_on_zero_knowledge_and_previous_rows(&self) -> &'a Evaluations>; @@ -131,7 +131,7 @@ pub trait ColumnEnvironment<'a, F: FftField> { impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { type Column = Column; - fn get_column(&self, col: &Column) -> Option<&'a Evaluations>> { + fn get_column(&self, col: &Self::Column) -> Option<&'a Evaluations>> { use Column::*; let lookup = self.lookup.as_ref(); match col { @@ -218,7 +218,11 @@ pub enum Column { Permutation(usize), } -impl Column { +pub trait GenericColumn { + fn domain(&self) -> Domain; +} + +impl GenericColumn for Column { fn domain(&self) -> Domain { match self { Column::Index(GateType::Generic) => Domain::D4, @@ -226,7 +230,9 @@ impl Column { _ => Domain::D8, } } +} +impl Column { fn latex(&self) -> String { match self { Column::Witness(i) => format!("w_{{{i}}}"), @@ -1574,7 +1580,7 @@ impl Expr, Column> { } } -impl Expr, Column> { +impl Expr, Column> { fn evaluate_constants_(&self, c: &Constants) -> Expr { use Expr::*; // TODO: Use cache @@ -1674,7 +1680,10 @@ impl Expr, Column> { } /// Compute the polynomial corresponding to this expression, in evaluation form. - pub fn evaluations(&self, env: &Environment<'_, F>) -> Evaluations> { + pub fn evaluations<'a, Environment: ColumnEnvironment<'a, F, Column = Column>>( + &self, + env: &Environment, + ) -> Evaluations> { self.evaluate_constants(env).evaluations(env) } } @@ -1684,7 +1693,7 @@ enum Either { Right(B), } -impl Expr { +impl Expr { /// Evaluate an expression into a field element. pub fn evaluate>( &self, @@ -1738,9 +1747,12 @@ impl Expr { } /// Compute the polynomial corresponding to this expression, in evaluation form. - pub fn evaluations(&self, env: &Environment<'_, F>) -> Evaluations> { - let d1_size = env.domain.d1.size; - let deg = self.degree(d1_size, env.constants.zk_rows); + pub fn evaluations<'a, Environment: ColumnEnvironment<'a, F, Column = Column>>( + &self, + env: &Environment, + ) -> Evaluations> { + let d1_size = env.get_domain(Domain::D1).size; + let deg = self.degree(d1_size, env.get_constants().zk_rows); let d = if deg <= d1_size { Domain::D1 } else if deg <= 4 * d1_size { @@ -1923,12 +1935,12 @@ impl Expr { #[derive(Clone, Debug, Serialize, Deserialize)] /// A "linearization", which is linear combination with `E` coefficients of /// columns. -pub struct Linearization { +pub struct Linearization { pub constant_term: E, pub index_terms: Vec<(Column, E)>, } -impl Default for Linearization { +impl Default for Linearization { fn default() -> Self { Linearization { constant_term: E::default(), @@ -1937,9 +1949,9 @@ impl Default for Linearization { } } -impl Linearization { +impl Linearization { /// Apply a function to all the coefficients in the linearization. - pub fn map B>(&self, f: F) -> Linearization { + pub fn map B>(&self, f: F) -> Linearization { Linearization { constant_term: f(&self.constant_term), index_terms: self.index_terms.iter().map(|(c, x)| (*c, f(x))).collect(), @@ -1947,15 +1959,20 @@ impl Linearization { } } -impl Linearization, Column>> { +impl + Linearization, Column>, Column> +{ /// Evaluate the constants in a linearization with `ConstantExpr` coefficients down /// to literal field elements. - pub fn evaluate_constants(&self, env: &Environment) -> Linearization> { + pub fn evaluate_constants<'a, Environment: ColumnEnvironment<'a, F, Column = Column>>( + &self, + env: &Environment, + ) -> Linearization, Column> { self.map(|e| e.evaluate_constants(env)) } } -impl Linearization>> { +impl Linearization>, Column> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. pub fn to_polynomial< @@ -1990,7 +2007,7 @@ impl Linearization } } -impl Linearization, Column>> { +impl Linearization, Column>, Column> { /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. pub fn to_polynomial< @@ -2200,7 +2217,7 @@ impl + Clone + One + Zero + PartialEq> Expr { pub fn linearize( &self, evaluated: HashSet, - ) -> Result>, ExprError> { + ) -> Result, Column>, ExprError> { let mut res: HashMap> = HashMap::new(); let mut constant_term: Expr = Self::zero(); let monomials = self.monomials(&evaluated); diff --git a/kimchi/src/linearization.rs b/kimchi/src/linearization.rs index 34a68a6317..b9657adedc 100644 --- a/kimchi/src/linearization.rs +++ b/kimchi/src/linearization.rs @@ -339,7 +339,10 @@ pub fn linearization_columns( pub fn expr_linearization( feature_flags: Option<&FeatureFlags>, generic: bool, -) -> (Linearization>>, Alphas) { +) -> ( + Linearization>, Column>, + Alphas, +) { let evaluated_cols = linearization_columns::(feature_flags); let (expr, powers_of_alpha) = constraints_expr(feature_flags, generic); diff --git a/kimchi/src/prover_index.rs b/kimchi/src/prover_index.rs index 169abb335d..42fe06c1e9 100644 --- a/kimchi/src/prover_index.rs +++ b/kimchi/src/prover_index.rs @@ -28,7 +28,7 @@ pub struct ProverIndex> { /// The symbolic linearization of our circuit, which can compile to concrete types once certain values are learned in the protocol. #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>, Column>, /// The mapping between powers of alpha and constraints #[serde(skip)] diff --git a/kimchi/src/verifier_index.rs b/kimchi/src/verifier_index.rs index 37973e5fae..8f6b664974 100644 --- a/kimchi/src/verifier_index.rs +++ b/kimchi/src/verifier_index.rs @@ -144,7 +144,7 @@ pub struct VerifierIndex> { pub lookup_index: Option>, #[serde(skip)] - pub linearization: Linearization>>, + pub linearization: Linearization>, Column>, /// The mapping between powers of alpha and constraints #[serde(skip)] pub powers_of_alpha: Alphas, From 7a4f0e2335e9bf7a7a5ab1f580176d3eb824b225 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 04:30:39 +0100 Subject: [PATCH 092/173] Generalize to_polynomial over Column --- kimchi/src/circuits/expr.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 093bc972cb..32e3927c83 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -2007,7 +2007,9 @@ impl Linearization } } -impl Linearization, Column>, Column> { +impl + Linearization, Column>, Column> +{ /// Given a linearization and an environment, compute the polynomial corresponding to the /// linearization, in evaluation form. pub fn to_polynomial< From 1d700a346a5771c5adf69b01dba27d97e76d830d Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 04:37:23 +0100 Subject: [PATCH 093/173] Generalize Monomials over Column --- kimchi/src/circuits/expr.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 32e3927c83..4f2f96dfaa 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -2053,12 +2053,15 @@ impl Expr { } } -type Monomials = HashMap>, Expr>; - -fn mul_monomials + Clone + One + Zero + PartialEq>( - e1: &Monomials, - e2: &Monomials, -) -> Monomials { +type Monomials = HashMap>, Expr>; + +fn mul_monomials< + F: Neg + Clone + One + Zero + PartialEq, + Column: Ord + Copy + std::hash::Hash, +>( + e1: &Monomials, + e2: &Monomials, +) -> Monomials { let mut res: HashMap<_, Expr> = HashMap::new(); for (m1, c1) in e1.iter() { for (m2, c2) in e2.iter() { @@ -2073,7 +2076,9 @@ fn mul_monomials + Clone + One + Zero + PartialEq>( res } -impl + Clone + One + Zero + PartialEq> Expr { +impl + Clone + One + Zero + PartialEq, Column: Ord + Copy + std::hash::Hash> + Expr +{ // TODO: This function (which takes linear time) // is called repeatedly in monomials, yielding quadratic behavior for // that function. It's ok for now as we only call that function once on From 23614b61b4d778efd6b07cca56ab7874fedba23f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 06:57:39 +0100 Subject: [PATCH 094/173] Move expr::Columns to berkeley_columns --- kimchi/src/circuits/berkeley_columns.rs | 93 ++++++++++++ kimchi/src/circuits/expr.rs | 143 +++++------------- kimchi/src/circuits/lookup/constraints.rs | 3 +- kimchi/src/circuits/lookup/runtime_tables.rs | 5 +- kimchi/src/circuits/mod.rs | 1 + kimchi/src/circuits/polynomials/turshi.rs | 3 +- kimchi/src/circuits/polynomials/varbasemul.rs | 3 +- kimchi/src/error.rs | 4 +- kimchi/src/linearization.rs | 3 +- kimchi/src/proof.rs | 2 +- kimchi/src/prover_index.rs | 3 +- kimchi/src/verifier.rs | 3 +- kimchi/src/verifier_index.rs | 3 +- 13 files changed, 150 insertions(+), 119 deletions(-) create mode 100644 kimchi/src/circuits/berkeley_columns.rs diff --git a/kimchi/src/circuits/berkeley_columns.rs b/kimchi/src/circuits/berkeley_columns.rs new file mode 100644 index 0000000000..1224744a6b --- /dev/null +++ b/kimchi/src/circuits/berkeley_columns.rs @@ -0,0 +1,93 @@ +use crate::circuits::{ + expr::{self, Domain, GenericColumn}, + gate::{CurrOrNext, GateType}, + lookup::lookups::LookupPattern, +}; +use serde::{Deserialize, Serialize}; +use CurrOrNext::{Curr, Next}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +/// A type representing one of the polynomials involved in the PLONK IOP. +pub enum Column { + Witness(usize), + Z, + LookupSorted(usize), + LookupAggreg, + LookupTable, + LookupKindIndex(LookupPattern), + LookupRuntimeSelector, + LookupRuntimeTable, + Index(GateType), + Coefficient(usize), + Permutation(usize), +} + +impl GenericColumn for Column { + fn domain(&self) -> Domain { + match self { + Column::Index(GateType::Generic) => Domain::D4, + Column::Index(GateType::CompleteAdd) => Domain::D4, + _ => Domain::D8, + } + } +} + +impl Column { + pub fn latex(&self) -> String { + match self { + Column::Witness(i) => format!("w_{{{i}}}"), + Column::Z => "Z".to_string(), + Column::LookupSorted(i) => format!("s_{{{i}}}"), + Column::LookupAggreg => "a".to_string(), + Column::LookupTable => "t".to_string(), + Column::LookupKindIndex(i) => format!("k_{{{i:?}}}"), + Column::LookupRuntimeSelector => "rts".to_string(), + Column::LookupRuntimeTable => "rt".to_string(), + Column::Index(gate) => { + format!("{gate:?}") + } + Column::Coefficient(i) => format!("c_{{{i}}}"), + Column::Permutation(i) => format!("sigma_{{{i}}}"), + } + } + + pub fn text(&self) -> String { + match self { + Column::Witness(i) => format!("w[{i}]"), + Column::Z => "Z".to_string(), + Column::LookupSorted(i) => format!("s[{i}]"), + Column::LookupAggreg => "a".to_string(), + Column::LookupTable => "t".to_string(), + Column::LookupKindIndex(i) => format!("k[{i:?}]"), + Column::LookupRuntimeSelector => "rts".to_string(), + Column::LookupRuntimeTable => "rt".to_string(), + Column::Index(gate) => { + format!("{gate:?}") + } + Column::Coefficient(i) => format!("c[{i}]"), + Column::Permutation(i) => format!("sigma_[{i}]"), + } + } +} + +impl expr::Variable { + pub fn ocaml(&self) -> String { + format!("var({:?}, {:?})", self.col, self.row) + } + + pub fn latex(&self) -> String { + let col = self.col.latex(); + match self.row { + Curr => col, + Next => format!("\\tilde{{{col}}}"), + } + } + + pub fn text(&self) -> String { + let col = self.col.text(); + match self.row { + Curr => format!("Curr({col})"), + Next => format!("Next({col})"), + } + } +} diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index 4f2f96dfaa..d84512d5fe 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -1,5 +1,6 @@ use crate::{ circuits::{ + berkeley_columns, constraints::FeatureFlags, domains::EvaluationDomains, gate::{CurrOrNext, GateType}, @@ -129,10 +130,10 @@ pub trait ColumnEnvironment<'a, F: FftField> { } impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { - type Column = Column; + type Column = berkeley_columns::Column; fn get_column(&self, col: &Self::Column) -> Option<&'a Evaluations>> { - use Column::*; + use berkeley_columns::Column::*; let lookup = self.lookup.as_ref(); match col { Witness(i) => Some(&self.witness[*i]), @@ -202,74 +203,10 @@ fn unnormalized_lagrange_basis(domain: &D, i: i32, pt: &F) -> F domain.evaluate_vanishing_polynomial(*pt) / (*pt - omega_i) } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -/// A type representing one of the polynomials involved in the PLONK IOP. -pub enum Column { - Witness(usize), - Z, - LookupSorted(usize), - LookupAggreg, - LookupTable, - LookupKindIndex(LookupPattern), - LookupRuntimeSelector, - LookupRuntimeTable, - Index(GateType), - Coefficient(usize), - Permutation(usize), -} - pub trait GenericColumn { fn domain(&self) -> Domain; } -impl GenericColumn for Column { - fn domain(&self) -> Domain { - match self { - Column::Index(GateType::Generic) => Domain::D4, - Column::Index(GateType::CompleteAdd) => Domain::D4, - _ => Domain::D8, - } - } -} - -impl Column { - fn latex(&self) -> String { - match self { - Column::Witness(i) => format!("w_{{{i}}}"), - Column::Z => "Z".to_string(), - Column::LookupSorted(i) => format!("s_{{{i}}}"), - Column::LookupAggreg => "a".to_string(), - Column::LookupTable => "t".to_string(), - Column::LookupKindIndex(i) => format!("k_{{{i:?}}}"), - Column::LookupRuntimeSelector => "rts".to_string(), - Column::LookupRuntimeTable => "rt".to_string(), - Column::Index(gate) => { - format!("{gate:?}") - } - Column::Coefficient(i) => format!("c_{{{i}}}"), - Column::Permutation(i) => format!("sigma_{{{i}}}"), - } - } - - fn text(&self) -> String { - match self { - Column::Witness(i) => format!("w[{i}]"), - Column::Z => "Z".to_string(), - Column::LookupSorted(i) => format!("s[{i}]"), - Column::LookupAggreg => "a".to_string(), - Column::LookupTable => "t".to_string(), - Column::LookupKindIndex(i) => format!("k[{i:?}]"), - Column::LookupRuntimeSelector => "rts".to_string(), - Column::LookupRuntimeTable => "rt".to_string(), - Column::Index(gate) => { - format!("{gate:?}") - } - Column::Coefficient(i) => format!("c[{i}]"), - Column::Permutation(i) => format!("sigma_[{i}]"), - } - } -} - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] /// A type representing a variable which can appear in a constraint. It specifies a column /// and a relative position (Curr or Next) @@ -280,28 +217,6 @@ pub struct Variable { pub row: CurrOrNext, } -impl Variable { - fn ocaml(&self) -> String { - format!("var({:?}, {:?})", self.col, self.row) - } - - fn latex(&self) -> String { - let col = self.col.latex(); - match self.row { - Curr => col, - Next => format!("\\tilde{{{col}}}"), - } - } - - fn text(&self) -> String { - let col = self.col.text(); - match self.row { - Curr => format!("Curr({col})"), - Next => format!("Next({col})"), - } - } -} - #[derive(Clone, Debug, PartialEq)] /// An arithmetic expression over /// @@ -714,9 +629,9 @@ pub trait ColumnEvaluations { } impl ColumnEvaluations for ProofEvaluations> { - type Column = Column; + type Column = berkeley_columns::Column; fn evaluate(&self, col: Self::Column) -> Result, ExprError> { - use Column::*; + use berkeley_columns::Column::*; match col { Witness(i) => Ok(self.w[i]), Z => Ok(self.z), @@ -920,7 +835,7 @@ impl Expr { } } -impl fmt::Display for Expr, Column> +impl fmt::Display for Expr, berkeley_columns::Column> where F: PrimeField, { @@ -2560,7 +2475,7 @@ where } } -impl Expr, Column> +impl Expr, berkeley_columns::Column> where F: PrimeField, { @@ -2587,7 +2502,10 @@ where /// Recursively print the expression, /// except for the cached expression that are stored in the `cache`. - fn ocaml(&self, cache: &mut HashMap, Column>>) -> String { + fn ocaml( + &self, + cache: &mut HashMap, berkeley_columns::Column>>, + ) -> String { use Expr::*; match self { Double(x) => format!("double({})", x.ocaml(cache)), @@ -2640,7 +2558,10 @@ where res } - fn latex(&self, cache: &mut HashMap, Column>>) -> String { + fn latex( + &self, + cache: &mut HashMap, berkeley_columns::Column>>, + ) -> String { use Expr::*; match self { Double(x) => format!("2 ({})", x.latex(cache)), @@ -2676,7 +2597,10 @@ where /// Recursively print the expression, /// except for the cached expression that are stored in the `cache`. - fn text(&self, cache: &mut HashMap, Column>>) -> String { + fn text( + &self, + cache: &mut HashMap, berkeley_columns::Column>>, + ) -> String { use Expr::*; match self { Double(x) => format!("double({})", x.text(cache)), @@ -2809,25 +2733,34 @@ pub mod constraints { fn cache(&self, cache: &mut Cache) -> Self; } - impl ExprOps for Expr, Column> + impl ExprOps for Expr, berkeley_columns::Column> where F: PrimeField, - Expr, Column>: std::fmt::Display, + Expr, berkeley_columns::Column>: std::fmt::Display, { fn two_pow(pow: u64) -> Self { - Expr::, Column>::literal(>::two_pow(pow)) + Expr::, berkeley_columns::Column>::literal(>::two_pow(pow)) } fn two_to_limb() -> Self { - Expr::, Column>::literal(>::two_to_limb()) + Expr::, berkeley_columns::Column>::literal(>::two_to_limb( + )) } fn two_to_2limb() -> Self { - Expr::, Column>::literal(>::two_to_2limb()) + Expr::, berkeley_columns::Column>::literal(>::two_to_2limb( + )) } fn two_to_3limb() -> Self { - Expr::, Column>::literal(>::two_to_3limb()) + Expr::, berkeley_columns::Column>::literal(>::two_to_3limb( + )) } fn double(&self) -> Self { @@ -2963,7 +2896,7 @@ pub mod constraints { // /// An alias for the intended usage of the expression type in constructing constraints. -pub type E = Expr, Column>; +pub type E = Expr, berkeley_columns::Column>; /// Convenience function to create a constant as [Expr]. pub fn constant(x: F) -> E { @@ -2972,7 +2905,7 @@ pub fn constant(x: F) -> E { /// Helper function to quickly create an expression for a witness. pub fn witness(i: usize, row: CurrOrNext) -> E { - E::::cell(Column::Witness(i), row) + E::::cell(berkeley_columns::Column::Witness(i), row) } /// Same as [witness] but for the current row. @@ -2987,11 +2920,11 @@ pub fn witness_next(i: usize) -> E { /// Handy function to quickly create an expression for a gate. pub fn index(g: GateType) -> E { - E::::cell(Column::Index(g), CurrOrNext::Curr) + E::::cell(berkeley_columns::Column::Index(g), CurrOrNext::Curr) } pub fn coeff(i: usize) -> E { - E::::cell(Column::Coefficient(i), CurrOrNext::Curr) + E::::cell(berkeley_columns::Column::Coefficient(i), CurrOrNext::Curr) } /// Auto clone macro - Helps make constraints more readable diff --git a/kimchi/src/circuits/lookup/constraints.rs b/kimchi/src/circuits/lookup/constraints.rs index 2ba98b08c4..3cbf3f34ad 100644 --- a/kimchi/src/circuits/lookup/constraints.rs +++ b/kimchi/src/circuits/lookup/constraints.rs @@ -1,6 +1,7 @@ use crate::{ circuits::{ - expr::{prologue::*, Column, ConstantExpr, RowOffset}, + berkeley_columns::Column, + expr::{prologue::*, ConstantExpr, RowOffset}, gate::{CircuitGate, CurrOrNext}, lookup::lookups::{ JointLookup, JointLookupSpec, JointLookupValue, LocalPosition, LookupInfo, diff --git a/kimchi/src/circuits/lookup/runtime_tables.rs b/kimchi/src/circuits/lookup/runtime_tables.rs index 98b018ed8a..f8123d75d8 100644 --- a/kimchi/src/circuits/lookup/runtime_tables.rs +++ b/kimchi/src/circuits/lookup/runtime_tables.rs @@ -2,10 +2,7 @@ //! The setup has to prepare for their presence using [`RuntimeTableCfg`]. //! At proving time, the prover can use [`RuntimeTable`] to specify the actual tables. -use crate::circuits::{ - expr::{prologue::*, Column}, - gate::CurrOrNext, -}; +use crate::circuits::{berkeley_columns::Column, expr::prologue::*, gate::CurrOrNext}; use ark_ff::Field; use serde::{Deserialize, Serialize}; diff --git a/kimchi/src/circuits/mod.rs b/kimchi/src/circuits/mod.rs index 83dbc91c63..7bad1cfd9f 100644 --- a/kimchi/src/circuits/mod.rs +++ b/kimchi/src/circuits/mod.rs @@ -2,6 +2,7 @@ pub mod macros; pub mod argument; +pub mod berkeley_columns; pub mod constraints; pub mod domain_constant_evaluation; pub mod domains; diff --git a/kimchi/src/circuits/polynomials/turshi.rs b/kimchi/src/circuits/polynomials/turshi.rs index da51465f92..645e5649c1 100644 --- a/kimchi/src/circuits/polynomials/turshi.rs +++ b/kimchi/src/circuits/polynomials/turshi.rs @@ -82,8 +82,9 @@ use crate::{ alphas::Alphas, circuits::{ argument::{Argument, ArgumentEnv, ArgumentType}, + berkeley_columns::Column, constraints::ConstraintSystem, - expr::{self, constraints::ExprOps, Cache, Column, E}, + expr::{self, constraints::ExprOps, Cache, E}, gate::{CircuitGate, GateType}, wires::{GateWires, Wire, COLUMNS}, }, diff --git a/kimchi/src/circuits/polynomials/varbasemul.rs b/kimchi/src/circuits/polynomials/varbasemul.rs index 0ed90e711c..c211fb1ef8 100644 --- a/kimchi/src/circuits/polynomials/varbasemul.rs +++ b/kimchi/src/circuits/polynomials/varbasemul.rs @@ -12,7 +12,8 @@ use crate::circuits::{ argument::{Argument, ArgumentEnv, ArgumentType}, - expr::{constraints::ExprOps, Cache, Column, Variable as VariableGen}, + berkeley_columns::Column, + expr::{constraints::ExprOps, Cache, Variable as VariableGen}, gate::{CircuitGate, CurrOrNext, GateType}, wires::{GateWires, COLUMNS}, }; diff --git a/kimchi/src/error.rs b/kimchi/src/error.rs index 8c801fd38f..3c0b352eb7 100644 --- a/kimchi/src/error.rs +++ b/kimchi/src/error.rs @@ -73,13 +73,13 @@ pub enum VerifyError { IncorrectRuntimeProof, #[error("the evaluation for {0:?} is missing")] - MissingEvaluation(crate::circuits::expr::Column), + MissingEvaluation(crate::circuits::berkeley_columns::Column), #[error("the evaluation for PublicInput is missing")] MissingPublicInputEvaluation, #[error("the commitment for {0:?} is missing")] - MissingCommitment(crate::circuits::expr::Column), + MissingCommitment(crate::circuits::berkeley_columns::Column), } /// Errors that can arise when preparing the setup diff --git a/kimchi/src/linearization.rs b/kimchi/src/linearization.rs index b9657adedc..50218e1c05 100644 --- a/kimchi/src/linearization.rs +++ b/kimchi/src/linearization.rs @@ -23,8 +23,9 @@ use crate::circuits::polynomials::{ }; use crate::circuits::{ + berkeley_columns::Column, constraints::FeatureFlags, - expr::{Column, ConstantExpr, Expr, FeatureFlag, Linearization, PolishToken}, + expr::{ConstantExpr, Expr, FeatureFlag, Linearization, PolishToken}, gate::GateType, wires::COLUMNS, }; diff --git a/kimchi/src/proof.rs b/kimchi/src/proof.rs index 5829178786..f7ace0c5fd 100644 --- a/kimchi/src/proof.rs +++ b/kimchi/src/proof.rs @@ -1,7 +1,7 @@ //! This module implements the data structures of a proof. use crate::circuits::{ - expr::Column, + berkeley_columns::Column, gate::GateType, lookup::lookups::LookupPattern, wires::{COLUMNS, PERMUTS}, diff --git a/kimchi/src/prover_index.rs b/kimchi/src/prover_index.rs index 42fe06c1e9..05db4f5b6b 100644 --- a/kimchi/src/prover_index.rs +++ b/kimchi/src/prover_index.rs @@ -3,8 +3,9 @@ use crate::{ alphas::Alphas, circuits::{ + berkeley_columns::Column, constraints::{ColumnEvaluations, ConstraintSystem}, - expr::{Column, Linearization, PolishToken}, + expr::{Linearization, PolishToken}, }, curve::KimchiCurve, linearization::expr_linearization, diff --git a/kimchi/src/verifier.rs b/kimchi/src/verifier.rs index 0c9cd4aef3..3a9fc386a1 100644 --- a/kimchi/src/verifier.rs +++ b/kimchi/src/verifier.rs @@ -3,8 +3,9 @@ use crate::{ circuits::{ argument::ArgumentType, + berkeley_columns::Column, constraints::ConstraintSystem, - expr::{Column, Constants, PolishToken}, + expr::{Constants, PolishToken}, gate::GateType, lookup::{lookups::LookupPattern, tables::combine_table}, polynomials::permutation, diff --git a/kimchi/src/verifier_index.rs b/kimchi/src/verifier_index.rs index 8f6b664974..ff4aaca48a 100644 --- a/kimchi/src/verifier_index.rs +++ b/kimchi/src/verifier_index.rs @@ -4,7 +4,8 @@ use crate::{ alphas::Alphas, circuits::{ - expr::{Column, Linearization, PolishToken}, + berkeley_columns::Column, + expr::{Linearization, PolishToken}, lookup::{index::LookupSelectors, lookups::LookupInfo}, polynomials::permutation::{vanishes_on_last_n_rows, zk_w}, wires::{COLUMNS, PERMUTS}, From ce6fdfa17c459a3a4adc53bd71857c9ac3081c89 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 30 Mar 2023 07:02:33 +0100 Subject: [PATCH 095/173] Move ColumnEvaluations to berkeley_columns --- kimchi/src/circuits/berkeley_columns.rs | 74 +++++++++++++++++++++++-- kimchi/src/circuits/expr.rs | 65 +--------------------- 2 files changed, 71 insertions(+), 68 deletions(-) diff --git a/kimchi/src/circuits/berkeley_columns.rs b/kimchi/src/circuits/berkeley_columns.rs index 1224744a6b..6558f4c367 100644 --- a/kimchi/src/circuits/berkeley_columns.rs +++ b/kimchi/src/circuits/berkeley_columns.rs @@ -1,7 +1,10 @@ -use crate::circuits::{ - expr::{self, Domain, GenericColumn}, - gate::{CurrOrNext, GateType}, - lookup::lookups::LookupPattern, +use crate::{ + circuits::{ + expr::{self, ColumnEvaluations, Domain, ExprError, GenericColumn}, + gate::{CurrOrNext, GateType}, + lookup::lookups::LookupPattern, + }, + proof::{PointEvaluations, ProofEvaluations}, }; use serde::{Deserialize, Serialize}; use CurrOrNext::{Curr, Next}; @@ -91,3 +94,66 @@ impl expr::Variable { } } } + +impl ColumnEvaluations for ProofEvaluations> { + type Column = Column; + fn evaluate(&self, col: Self::Column) -> Result, ExprError> { + use Column::*; + match col { + Witness(i) => Ok(self.w[i]), + Z => Ok(self.z), + LookupSorted(i) => self.lookup_sorted[i].ok_or(ExprError::MissingIndexEvaluation(col)), + LookupAggreg => self + .lookup_aggregation + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupTable => self + .lookup_table + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupRuntimeTable => self + .runtime_lookup_table + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::Poseidon) => Ok(self.poseidon_selector), + Index(GateType::Generic) => Ok(self.generic_selector), + Index(GateType::CompleteAdd) => Ok(self.complete_add_selector), + Index(GateType::VarBaseMul) => Ok(self.mul_selector), + Index(GateType::EndoMul) => Ok(self.emul_selector), + Index(GateType::EndoMulScalar) => Ok(self.endomul_scalar_selector), + Index(GateType::RangeCheck0) => self + .range_check0_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::RangeCheck1) => self + .range_check1_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::ForeignFieldAdd) => self + .foreign_field_add_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::ForeignFieldMul) => self + .foreign_field_mul_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::Xor16) => self + .xor_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(GateType::Rot64) => self + .rot_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Permutation(i) => Ok(self.s[i]), + Coefficient(i) => Ok(self.coefficients[i]), + LookupKindIndex(LookupPattern::Xor) => self + .xor_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupKindIndex(LookupPattern::Lookup) => self + .lookup_gate_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupKindIndex(LookupPattern::RangeCheck) => self + .range_check_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupKindIndex(LookupPattern::ForeignFieldMul) => self + .foreign_field_mul_lookup_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + LookupRuntimeSelector => self + .runtime_lookup_table_selector + .ok_or(ExprError::MissingIndexEvaluation(col)), + Index(_) => Err(ExprError::MissingIndexEvaluation(col)), + } + } +} diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index d84512d5fe..defdae6b13 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -11,7 +11,7 @@ use crate::{ polynomials::permutation::eval_vanishes_on_last_n_rows, wires::COLUMNS, }, - proof::{PointEvaluations, ProofEvaluations}, + proof::PointEvaluations, }; use ark_ff::{FftField, Field, One, PrimeField, Zero}; use ark_poly::{ @@ -628,69 +628,6 @@ pub trait ColumnEvaluations { fn evaluate(&self, col: Self::Column) -> Result, ExprError>; } -impl ColumnEvaluations for ProofEvaluations> { - type Column = berkeley_columns::Column; - fn evaluate(&self, col: Self::Column) -> Result, ExprError> { - use berkeley_columns::Column::*; - match col { - Witness(i) => Ok(self.w[i]), - Z => Ok(self.z), - LookupSorted(i) => self.lookup_sorted[i].ok_or(ExprError::MissingIndexEvaluation(col)), - LookupAggreg => self - .lookup_aggregation - .ok_or(ExprError::MissingIndexEvaluation(col)), - LookupTable => self - .lookup_table - .ok_or(ExprError::MissingIndexEvaluation(col)), - LookupRuntimeTable => self - .runtime_lookup_table - .ok_or(ExprError::MissingIndexEvaluation(col)), - Index(GateType::Poseidon) => Ok(self.poseidon_selector), - Index(GateType::Generic) => Ok(self.generic_selector), - Index(GateType::CompleteAdd) => Ok(self.complete_add_selector), - Index(GateType::VarBaseMul) => Ok(self.mul_selector), - Index(GateType::EndoMul) => Ok(self.emul_selector), - Index(GateType::EndoMulScalar) => Ok(self.endomul_scalar_selector), - Index(GateType::RangeCheck0) => self - .range_check0_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - Index(GateType::RangeCheck1) => self - .range_check1_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - Index(GateType::ForeignFieldAdd) => self - .foreign_field_add_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - Index(GateType::ForeignFieldMul) => self - .foreign_field_mul_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - Index(GateType::Xor16) => self - .xor_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - Index(GateType::Rot64) => self - .rot_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - Permutation(i) => Ok(self.s[i]), - Coefficient(i) => Ok(self.coefficients[i]), - LookupKindIndex(LookupPattern::Xor) => self - .xor_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - LookupKindIndex(LookupPattern::Lookup) => self - .lookup_gate_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - LookupKindIndex(LookupPattern::RangeCheck) => self - .range_check_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - LookupKindIndex(LookupPattern::ForeignFieldMul) => self - .foreign_field_mul_lookup_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - LookupRuntimeSelector => self - .runtime_lookup_table_selector - .ok_or(ExprError::MissingIndexEvaluation(col)), - Index(_) => Err(ExprError::MissingIndexEvaluation(col)), - } - } -} - impl Variable { fn evaluate>( &self, From 5ab1b2d6a990dc06f6b630d7d832eb60ba4af630 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 17:41:16 +0100 Subject: [PATCH 096/173] Use unwrap instead of empty string expects --- optimism/src/main.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 6989806a70..32c86b9f9e 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -61,25 +61,19 @@ fn cli() -> VmConfiguration { let cli = cli.get_matches(); - let input_state_file = cli - .get_one::("input") - .expect("Default ensures there is always a value"); + let input_state_file = cli.get_one::("input").unwrap(); - let output_state_file = cli - .get_one::("output") - .expect("Default ensures there is always a value"); + let output_state_file = cli.get_one::("output").unwrap(); - let metadata_file = cli - .get_one::("meta") - .expect("Default ensures there is always a value"); + let metadata_file = cli.get_one::("meta").unwrap(); - let proof_at = cli.get_one::("proof-at").expect(""); - let info_at = cli.get_one::("info-at").expect(""); - let stop_at = cli.get_one::("stop-at").expect(""); + let proof_at = cli.get_one::("proof-at").unwrap(); + let info_at = cli.get_one::("info-at").unwrap(); + let stop_at = cli.get_one::("stop-at").unwrap(); - let proof_fmt = cli.get_one::("proof-fmt").expect(""); - let snapshot_fmt = cli.get_one::("snapshot-fmt").expect(""); - let pprof_cpu = cli.get_one::("pprof-cpu").expect(""); + let proof_fmt = cli.get_one::("proof-fmt").unwrap(); + let snapshot_fmt = cli.get_one::("snapshot-fmt").unwrap(); + let pprof_cpu = cli.get_one::("pprof-cpu").unwrap(); let host_spec = cli .get_many::("host") @@ -118,7 +112,7 @@ pub fn main() -> ExitCode { println!("configuration\n{:#?}", configuration); - let file = File::open(configuration.input_state_file).expect("file"); + let file = File::open(configuration.input_state_file).expect("Error opening input state file "); let reader = BufReader::new(file); // Read the JSON contents of the file as an instance of `State`. From a5f48d1dab3d23a4d26369697814cc05f3248307 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 17:48:06 +0100 Subject: [PATCH 097/173] Mini readme --- optimism/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 optimism/README.md diff --git a/optimism/README.md b/optimism/README.md new file mode 100644 index 0000000000..2dbf25c375 --- /dev/null +++ b/optimism/README.md @@ -0,0 +1,18 @@ +To run the demo: +* create an executable file `rpcs.sh` that looks like + ```bash + #!/usr/bin/env bash + export L1RPC=http://xxxxxxxxx + export L2RPC=http://xxxxxxxxx + ``` +* run the `run-code.sh` script. + +This will +* generate the initial state, +* execute the OP program, +* execute the OP program through the cannon MIPS VM, +* execute the OP program through the kimchi MIPS VM prover. + +The initial state will be output to a file with format `YYYY-MM-DD-HH-MM-SS-op-program-data-log.sh`. + +If you want to re-run against an existing state, pass the environment variable `FILENAME=YYYY-MM-DD-HH-MM-SS-op-program-data-log.sh` to the `run-code.sh` script. From c7254f725b5a802d53853232cc0680f8d03c44d1 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 17:49:01 +0100 Subject: [PATCH 098/173] Update script to use env --- optimism/generate-config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimism/generate-config.sh b/optimism/generate-config.sh index e71f8a90ed..3e8dae0221 100755 --- a/optimism/generate-config.sh +++ b/optimism/generate-config.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail source rpcs.sh From cbf3b56c834b6232d9da4c19fe337b0c18d50ebf Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 17:55:37 +0100 Subject: [PATCH 099/173] Clippy!!!!! --- kimchi/src/circuits/expr.rs | 4 ++-- kimchi/src/linearization.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/expr.rs b/kimchi/src/circuits/expr.rs index defdae6b13..cd0e5ec2ef 100644 --- a/kimchi/src/circuits/expr.rs +++ b/kimchi/src/circuits/expr.rs @@ -167,7 +167,7 @@ impl<'a, F: FftField> ColumnEnvironment<'a, F> for Environment<'a, F> { } fn vanishes_on_zero_knowledge_and_previous_rows(&self) -> &'a Evaluations> { - &self.vanishes_on_zero_knowledge_and_previous_rows + self.vanishes_on_zero_knowledge_and_previous_rows } fn l0_1(&self) -> F { @@ -1468,7 +1468,7 @@ impl Expr evals: &Evaluations, env: &Environment, ) -> Result> { - self.evaluate_(d, pt, evals, &env.get_constants()) + self.evaluate_(d, pt, evals, env.get_constants()) } /// Evaluate an expression as a field element against the constants. diff --git a/kimchi/src/linearization.rs b/kimchi/src/linearization.rs index 50218e1c05..65ffe41284 100644 --- a/kimchi/src/linearization.rs +++ b/kimchi/src/linearization.rs @@ -337,6 +337,7 @@ pub fn linearization_columns( /// # Panics /// /// Will panic if the `linearization` process fails. +#[allow(clippy::type_complexity)] pub fn expr_linearization( feature_flags: Option<&FeatureFlags>, generic: bool, From 0a0b8b33b7cf75fd0fba5c20a46716c1d2c734b8 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 24 Oct 2023 20:13:28 +0200 Subject: [PATCH 100/173] Implement deserializer --- optimism/Cargo.toml | 2 ++ optimism/src/cannon.rs | 20 ++++++++++++++++++-- optimism/src/main.rs | 4 +--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml index f1edb563cd..55fe049a9a 100644 --- a/optimism/Cargo.toml +++ b/optimism/Cargo.toml @@ -28,3 +28,5 @@ ark-ff = { version = "0.3.0", features = [ "parallel" ] } clap = "4.4.6" hex = "0.4.3" regex = "1.10.2" +libflate = "2" +base64 = "0.21.5" diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index c7693f3cab..3ccd9a152f 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -1,12 +1,28 @@ // Data structure and stuff for compatibility with Cannon +use base64::{engine::general_purpose, Engine as _}; +use libflate::zlib::Decoder; use regex::Regex; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::io::Read; #[derive(Serialize, Deserialize, Debug)] pub struct Page { pub index: u32, - pub data: String, + #[serde(deserialize_with = "from_base64")] + pub data: Vec, +} + +fn from_base64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer)?; + let b64_decoded = general_purpose::STANDARD.decode(s).unwrap(); + let mut decoder = Decoder::new(&b64_decoded[..]).unwrap(); + let mut data = Vec::new(); + decoder.read_to_end(&mut data).unwrap(); + Ok(data) } // The renaming below keeps compatibility with OP Cannon's state format diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 32c86b9f9e..78f9d837d6 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -116,7 +116,7 @@ pub fn main() -> ExitCode { let reader = BufReader::new(file); // Read the JSON contents of the file as an instance of `State`. - let state: State = serde_json::from_reader(reader).expect("Error reading input state file"); + let _state: State = serde_json::from_reader(reader).expect("Error reading input state file"); if let Some(host_program) = configuration.host { println!("Launching host program {}", host_program.name); @@ -127,8 +127,6 @@ pub fn main() -> ExitCode { .expect("Could not spawn host process"); }; - println!("{}", state.to_string()); - // TODO: Logic ExitCode::FAILURE } From 9d714610a85174fbb298e145046a87c1cd95e7ad Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 24 Oct 2023 20:13:41 +0200 Subject: [PATCH 101/173] Update cargo.lock --- Cargo.lock | 174 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 134 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03396e2f2a..1a8d682020 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "ahash" version = "0.7.6" @@ -28,6 +34,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.0.2" @@ -204,7 +222,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd4e5f0bf8285d5ed538d27fab7411f3e297908fd93c62195de8bee3f199e82" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", ] @@ -238,7 +256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" dependencies = [ "askama_shared", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "syn 1.0.109", ] @@ -261,7 +279,7 @@ dependencies = [ "nom", "num-traits", "percent-encoding", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "serde", "syn 1.0.109", @@ -302,9 +320,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bcs" @@ -502,7 +520,7 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", ] @@ -590,6 +608,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.9" @@ -748,7 +775,7 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "strsim 0.10.0", "syn 1.0.109", @@ -765,13 +792,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dary_heap" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca" + [[package]] name = "derivative" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", ] @@ -890,9 +923,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -994,7 +1027,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -1003,7 +1036,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.5", ] [[package]] @@ -1252,11 +1294,13 @@ version = "0.1.0" dependencies = [ "ark-ff", "ark-poly", + "base64", "clap 4.4.6", "elf", "groupmap", "hex", "kimchi", + "libflate", "mina-curves", "mina-poseidon", "poly-commitment", @@ -1285,6 +1329,30 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libflate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7d5654ae1795afc7ff76f4365c2c8791b0feb18e8996a96adad8ffd7c3b2bf" +dependencies = [ + "adler32", + "core2", + "crc32fast", + "dary_heap", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5f52fb8c451576ec6b79d3f4deb327398bc05bbdbd99021a6e77a4c855d524" +dependencies = [ + "core2", + "hashbrown 0.13.2", + "rle-decode-fast", +] + [[package]] name = "libm" version = "0.2.7" @@ -1365,7 +1433,7 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", ] @@ -1572,7 +1640,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", ] @@ -1667,7 +1735,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b40aa99a001268b85eb18414ecd190dc21fceaeaf81214ca28233b6feb25a998" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", "synstructure", @@ -1692,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1894efdef5c9d83d17932c5f5db16d16eb5c8ae1a625ce44d9d1715e85d9d8dc" dependencies = [ "convert_case", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", ] @@ -1803,9 +1871,9 @@ checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", - "syn 2.0.25", + "syn 2.0.38", ] [[package]] @@ -1909,7 +1977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", "version_check", @@ -1921,7 +1989,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "version_check", ] @@ -1943,9 +2011,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -2011,7 +2079,7 @@ version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", ] [[package]] @@ -2134,6 +2202,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + [[package]] name = "rmp" version = "0.8.11" @@ -2304,9 +2378,9 @@ version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", - "syn 2.0.25", + "syn 2.0.38", ] [[package]] @@ -2337,7 +2411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", ] @@ -2405,7 +2479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "rustversion", "syn 1.0.109", @@ -2462,18 +2536,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.25" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "unicode-ident", ] @@ -2484,7 +2558,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", "syn 1.0.109", "unicode-xid 0.2.4", @@ -2593,9 +2667,9 @@ version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", - "syn 2.0.25", + "syn 2.0.38", ] [[package]] @@ -2806,9 +2880,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", - "syn 2.0.25", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -2828,9 +2902,9 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", - "syn 2.0.25", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2997,6 +3071,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8db0ac2df3d060f81ec0380ccc5b71c2a7c092cfced671feeee1320e95559c87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b6093bc6d5265ff40b479c834cdd25d8e20784781a2a29a8106327393d0a9ff" +dependencies = [ + "proc-macro2 1.0.69", + "quote 1.0.29", + "syn 2.0.38", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -3012,7 +3106,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.69", "quote 1.0.29", - "syn 2.0.25", + "syn 2.0.38", ] From 44891fb8dd28a3b86de81bd179f1f7e5b1bd9825 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 24 Oct 2023 20:35:27 +0200 Subject: [PATCH 102/173] Check page size --- optimism/src/cannon.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 3ccd9a152f..6b0049c040 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -6,6 +6,8 @@ use regex::Regex; use serde::{Deserialize, Deserializer, Serialize}; use std::io::Read; +pub const PAGE_SIZE: usize = 4096; + #[derive(Serialize, Deserialize, Debug)] pub struct Page { pub index: u32, @@ -22,6 +24,7 @@ where let mut decoder = Decoder::new(&b64_decoded[..]).unwrap(); let mut data = Vec::new(); decoder.read_to_end(&mut data).unwrap(); + assert_eq!(data.len(), PAGE_SIZE); Ok(data) } From fafd2a4992eb0a0157cb6bd76ecad670ab23e367 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 24 Oct 2023 19:34:33 +0100 Subject: [PATCH 103/173] Add initial witness::Env --- Cargo.lock | 1 + optimism/Cargo.toml | 3 +- optimism/src/lib.rs | 1 + optimism/src/main.rs | 15 +++++- optimism/src/mips/mod.rs | 2 + optimism/src/mips/registers.rs | 47 ++++++++++++++++ optimism/src/mips/witness.rs | 99 ++++++++++++++++++++++++++++++++++ 7 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 optimism/src/mips/mod.rs create mode 100644 optimism/src/mips/registers.rs create mode 100644 optimism/src/mips/witness.rs diff --git a/Cargo.lock b/Cargo.lock index 1a8d682020..77da43c308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1292,6 +1292,7 @@ dependencies = [ name = "kimchi_optimism" version = "0.1.0" dependencies = [ + "ark-bn254", "ark-ff", "ark-poly", "base64", diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml index 55fe049a9a..da634f3030 100644 --- a/optimism/Cargo.toml +++ b/optimism/Cargo.toml @@ -13,7 +13,8 @@ license = "Apache-2.0" path = "src/lib.rs" [dependencies] -kimchi = { path = "../kimchi", version = "0.1.0" } +ark-bn254 = { version = "0.3.0" } +kimchi = { path = "../kimchi", version = "0.1.0", features = [ "bn254" ] } poly-commitment = { path = "../poly-commitment", version = "0.1.0" } groupmap = { path = "../groupmap", version = "0.1.0" } mina-curves = { path = "../curves", version = "0.1.0" } diff --git a/optimism/src/lib.rs b/optimism/src/lib.rs index 3bec498068..da4f2ad95d 100644 --- a/optimism/src/lib.rs +++ b/optimism/src/lib.rs @@ -1 +1,2 @@ pub mod cannon; +pub mod mips; diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 78f9d837d6..732ceddb3d 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -1,5 +1,8 @@ use clap::{arg, value_parser, Arg, ArgAction, Command}; -use kimchi_optimism::cannon::{State, VmConfiguration}; +use kimchi_optimism::{ + cannon::{State, VmConfiguration}, + mips::witness, +}; use std::{fs::File, io::BufReader, process::ExitCode}; fn cli() -> VmConfiguration { @@ -116,7 +119,7 @@ pub fn main() -> ExitCode { let reader = BufReader::new(file); // Read the JSON contents of the file as an instance of `State`. - let _state: State = serde_json::from_reader(reader).expect("Error reading input state file"); + let state: State = serde_json::from_reader(reader).expect("Error reading input state file"); if let Some(host_program) = configuration.host { println!("Launching host program {}", host_program.name); @@ -127,6 +130,14 @@ pub fn main() -> ExitCode { .expect("Could not spawn host process"); }; + let page_size = 1 << 12; + + let mut env = witness::Env::::create(page_size, state); + + while !env.halt { + env.step(); + } + // TODO: Logic ExitCode::FAILURE } diff --git a/optimism/src/mips/mod.rs b/optimism/src/mips/mod.rs new file mode 100644 index 0000000000..2499222bb4 --- /dev/null +++ b/optimism/src/mips/mod.rs @@ -0,0 +1,2 @@ +pub mod registers; +pub mod witness; diff --git a/optimism/src/mips/registers.rs b/optimism/src/mips/registers.rs new file mode 100644 index 0000000000..89a1ee23b8 --- /dev/null +++ b/optimism/src/mips/registers.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; +use std::ops::{Index, IndexMut}; + +pub const NUM_REGISTERS: usize = 34; + +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct Registers { + pub general_purpose: [T; 32], + pub hi: T, + pub lo: T, +} + +impl Registers { + pub fn iter(&self) -> impl Iterator { + self.general_purpose.iter().chain([&self.hi, &self.lo]) + } +} + +impl Index for Registers { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + if index < 32 { + &self.general_purpose[index] + } else if index == 32 { + &self.hi + } else if index == 33 { + &self.lo + } else { + panic!("Index out of bounds"); + } + } +} + +impl IndexMut for Registers { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + if index < 32 { + &mut self.general_purpose[index] + } else if index == 32 { + &mut self.hi + } else if index == 33 { + &mut self.lo + } else { + panic!("Index out of bounds"); + } + } +} diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs new file mode 100644 index 0000000000..dbaa7b30a8 --- /dev/null +++ b/optimism/src/mips/witness.rs @@ -0,0 +1,99 @@ +use crate::{cannon::State, mips::registers::Registers}; +use ark_ff::Field; +use std::array; + +pub const NUM_GLOBAL_LOOKUP_TERMS: usize = 1; +pub const NUM_DECODING_LOOKUP_TERMS: usize = 2; +pub const NUM_INSTRUCTION_LOOKUP_TERMS: usize = 5; +pub const NUM_LOOKUP_TERMS: usize = + NUM_GLOBAL_LOOKUP_TERMS + NUM_DECODING_LOOKUP_TERMS + NUM_INSTRUCTION_LOOKUP_TERMS; +pub const SCRATCH_SIZE: usize = 25; + +#[derive(Clone)] +pub struct SyscallEnv { + pub heap: u32, // Heap pointer (actually unused in Cannon as of [2023-10-18]) + pub preimage_offset: u32, + pub preimage_key: Vec, + pub last_hint: Option>, +} + +impl SyscallEnv { + pub fn create(state: &State) -> Self { + SyscallEnv { + heap: state.heap, + preimage_key: state.preimage_key.as_bytes().to_vec(), // Might not be correct + preimage_offset: state.preimage_offset, + last_hint: state.last_hint.clone(), + } + } +} + +#[derive(Clone)] +pub struct Env { + pub instruction_counter: usize, + pub memory: Vec<(u32, Vec)>, + pub memory_write_index: Vec<(u32, Vec)>, + pub registers: Registers, + pub registers_write_index: Registers, + pub instruction_pointer: u32, + pub scratch_state_idx: usize, + pub scratch_state: [Fp; SCRATCH_SIZE], + pub halt: bool, + pub syscall_env: SyscallEnv, +} + +fn fresh_scratch_state() -> [Fp; N] { + array::from_fn(|_| Fp::zero()) +} + +impl Env { + pub fn create(page_size: usize, state: State) -> Self { + let initial_instruction_pointer = state.pc; + + let syscall_env = SyscallEnv::create(&state); + + let mut initial_memory: Vec<(u32, Vec)> = state + .memory + .into_iter() + // Check that the conversion from page data is correct + .map(|page| (page.index, page.data)) + .collect(); + + for (_address, initial_memory) in initial_memory.iter_mut() { + initial_memory.extend((0..(page_size - initial_memory.len())).map(|_| 0u8)); + assert_eq!(initial_memory.len(), page_size); + } + + let memory_offsets = initial_memory + .iter() + .map(|(offset, _)| *offset) + .collect::>(); + + let initial_registers = Registers { + lo: state.lo, + hi: state.hi, + general_purpose: state.registers, + }; + + Env { + instruction_counter: state.step as usize, + memory: initial_memory.clone(), + memory_write_index: memory_offsets + .iter() + .map(|offset| (*offset, vec![0usize; page_size])) + .collect(), + registers: initial_registers.clone(), + registers_write_index: Registers::default(), + instruction_pointer: initial_instruction_pointer, + scratch_state_idx: 0, + scratch_state: fresh_scratch_state(), + halt: state.exited, + syscall_env, + } + } + + pub fn step(&mut self) { + // TODO + self.halt = true; + } +} From bd2e00960999b0deee8164789b8c5eaab68e7c03 Mon Sep 17 00:00:00 2001 From: querolita Date: Wed, 25 Oct 2023 11:21:15 +0200 Subject: [PATCH 104/173] add support for trivial rotation by 0 and 64 --- kimchi/src/circuits/polynomials/rot.rs | 6 ++---- kimchi/src/tests/rot.rs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index d529670d13..9683d765d5 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -328,8 +328,6 @@ pub fn extend_rot( rot: u32, side: RotMode, ) { - assert!(rot < 64, "Rotation value must be less than 64"); - assert_ne!(rot, 0, "Rotation value must be non-zero"); let rot = if side == RotMode::Right { 64 - rot } else { @@ -343,8 +341,8 @@ pub fn extend_rot( // shifted [------] * 2^rot // rot = [------|000] // + [---] excess - let shifted = (word as u128 * 2u128.pow(rot) % 2u128.pow(64)) as u64; - let excess = word / 2u64.pow(64 - rot); + let shifted = (word as u128) * 2u128.pow(rot) % 2u128.pow(64); + let excess = (word as u128) / 2u128.pow(64 - rot); let rotated = shifted + excess; // Value for the added value for the bound // Right input of the "FFAdd" for the bound equation diff --git a/kimchi/src/tests/rot.rs b/kimchi/src/tests/rot.rs index 8d8ee17f97..0b037cc15e 100644 --- a/kimchi/src/tests/rot.rs +++ b/kimchi/src/tests/rot.rs @@ -169,22 +169,20 @@ fn test_rot_random() { test_rot::(word, rot, RotMode::Right); } -#[should_panic] #[test] // Test that a bad rotation fails as expected fn test_zero_rot() { let rng = &mut StdRng::from_seed(RNG_SEED); let word = rng.gen_range(0..2u128.pow(64)) as u64; - create_rot_witness::(word, 0, RotMode::Left); + test_rot::(word, 0, RotMode::Left); } -#[should_panic] #[test] // Test that a bad rotation fails as expected fn test_large_rot() { let rng = &mut StdRng::from_seed(RNG_SEED); let word = rng.gen_range(0..2u128.pow(64)) as u64; - create_rot_witness::(word, 64, RotMode::Left); + test_rot::(word, 64, RotMode::Left); } #[test] From 5eb5fcc7b6368c5ee517d457b67fe6eaa3fbb68d Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 26 Oct 2023 11:01:49 +0200 Subject: [PATCH 105/173] update assert to force <= 64 rotation offset --- kimchi/src/circuits/polynomials/rot.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index 9683d765d5..f7cae6c7eb 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -328,6 +328,8 @@ pub fn extend_rot( rot: u32, side: RotMode, ) { + assert!(rot <= 64, "Rotation value must be less or equal than 64"); + let rot = if side == RotMode::Right { 64 - rot } else { From 7d3b563f81f329061e6794653a8dc759087ba1bb Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 26 Oct 2023 14:40:54 +0200 Subject: [PATCH 106/173] update length of vector with constant instead of hard coded --- kimchi/src/circuits/polynomials/keccak/gadget.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/gadget.rs b/kimchi/src/circuits/polynomials/keccak/gadget.rs index 83b75e3294..a620645d81 100644 --- a/kimchi/src/circuits/polynomials/keccak/gadget.rs +++ b/kimchi/src/circuits/polynomials/keccak/gadget.rs @@ -56,7 +56,7 @@ impl CircuitGate { } fn create_keccak_absorb(new_row: usize, root: bool, pad: bool, pad_bytes: usize) -> Self { - let mut coeffs = vec![F::zero(); 336]; + let mut coeffs = vec![F::zero(); SPONGE_COEFFS]; coeffs[0] = F::one(); // absorb if root { coeffs[2] = F::one(); // root @@ -66,10 +66,10 @@ impl CircuitGate { for i in 0..pad_bytes { coeffs[140 - i] = F::one(); // flag for padding if i == 0 { - coeffs[335 - i] += F::from(0x80u8); // pad + coeffs[SPONGE_COEFFS - 1 - i] += F::from(0x80u8); // pad } if i == pad_bytes - 1 { - coeffs[335 - i] += F::one(); // pad + coeffs[SPONGE_COEFFS - 1 - i] += F::one(); // pad } } } From facc966c2e4bd68dbcaee783270a375e404cd934 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 26 Oct 2023 18:59:17 +0200 Subject: [PATCH 107/173] make WitnessCell variable generic to be F by default --- .../circuits/polynomials/foreign_field_add/witness.rs | 4 ++-- .../circuits/polynomials/foreign_field_mul/witness.rs | 2 +- kimchi/src/circuits/polynomials/range_check/witness.rs | 4 ++-- kimchi/src/circuits/polynomials/rot.rs | 4 ++-- kimchi/src/circuits/polynomials/xor.rs | 9 +++------ kimchi/src/circuits/witness/constant_cell.rs | 2 +- kimchi/src/circuits/witness/copy_bits_cell.rs | 2 +- kimchi/src/circuits/witness/copy_cell.rs | 2 +- kimchi/src/circuits/witness/copy_shift_cell.rs | 2 +- kimchi/src/circuits/witness/mod.rs | 9 +++++---- kimchi/src/circuits/witness/variable_bits_cell.rs | 2 +- kimchi/src/circuits/witness/variable_cell.rs | 2 +- 12 files changed, 21 insertions(+), 23 deletions(-) diff --git a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs index 57af9b6ff0..40f08e013b 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs @@ -185,7 +185,7 @@ fn init_ffadd_row( overflow: F, carry: F, ) { - let layout: [Vec>>; 1] = [ + let layout: [Vec>>; 1] = [ // ForeignFieldAdd row vec![ VariableCell::create("left_lo"), @@ -221,7 +221,7 @@ fn init_bound_rows( bound: &[F; 3], carry: &F, ) { - let layout: [Vec>>; 2] = [ + let layout: [Vec>>; 2] = [ vec![ // ForeignFieldAdd row VariableCell::create("result_lo"), diff --git a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs index 50fb4d2305..ef0fc02a26 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs @@ -41,7 +41,7 @@ use super::circuitgates; // // so that most significant limb, q2, is in W[2][0]. // -fn create_layout() -> [Vec>>; 2] { +fn create_layout() -> [Vec>>; 2] { [ // ForeignFieldMul row vec![ diff --git a/kimchi/src/circuits/polynomials/range_check/witness.rs b/kimchi/src/circuits/polynomials/range_check/witness.rs index e6fd60987f..55d99b2b7f 100644 --- a/kimchi/src/circuits/polynomials/range_check/witness.rs +++ b/kimchi/src/circuits/polynomials/range_check/witness.rs @@ -29,7 +29,7 @@ use o1_utils::foreign_field::BigUintForeignFieldHelpers; /// For example, we can convert the `RangeCheck0` circuit gate into /// a 64-bit lookup by adding two copy constraints to constrain /// columns 1 and 2 to zero. -fn layout() -> [Vec>>; 4] { +fn layout() -> [Vec>>; 4] { [ /* row 1, RangeCheck0 row */ range_check_0_row("v0", 0), @@ -86,7 +86,7 @@ fn layout() -> [Vec>>; 4] { pub fn range_check_0_row( limb_name: &'static str, row: usize, -) -> Vec>> { +) -> Vec>> { vec![ VariableCell::create(limb_name), /* 12-bit copies */ diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index a0bc442a24..8b72a94e95 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -266,7 +266,7 @@ where // ROTATION WITNESS COMPUTATION -fn layout_rot64(curr_row: usize) -> [Vec>>; 3] { +fn layout_rot64(curr_row: usize) -> [Vec>>; 3] { [ rot_row(), range_check_0_row("shifted", curr_row + 1), @@ -274,7 +274,7 @@ fn layout_rot64(curr_row: usize) -> [Vec() -> Vec>> { +fn rot_row() -> Vec>> { vec![ VariableCell::create("word"), VariableCell::create("rotated"), diff --git a/kimchi/src/circuits/polynomials/xor.rs b/kimchi/src/circuits/polynomials/xor.rs index a7913f92a7..d7276cee12 100644 --- a/kimchi/src/circuits/polynomials/xor.rs +++ b/kimchi/src/circuits/polynomials/xor.rs @@ -172,7 +172,7 @@ where fn layout( curr_row: usize, bits: usize, -) -> Vec>>> { +) -> Vec>>> { let num_xor = num_xors(bits); let mut layout = (0..num_xor) .map(|i| xor_row(i, curr_row + i)) @@ -181,10 +181,7 @@ fn layout( layout } -fn xor_row( - nybble: usize, - curr_row: usize, -) -> Vec>> { +fn xor_row(nybble: usize, curr_row: usize) -> Vec>> { let start = nybble * 16; vec![ VariableBitsCell::create("in1", start, None), @@ -205,7 +202,7 @@ fn xor_row( ] } -fn zero_row() -> Vec>> { +fn zero_row() -> Vec>> { vec![ ConstantCell::create(F::zero()), ConstantCell::create(F::zero()), diff --git a/kimchi/src/circuits/witness/constant_cell.rs b/kimchi/src/circuits/witness/constant_cell.rs index ea14b5de8c..1ec69d797c 100644 --- a/kimchi/src/circuits/witness/constant_cell.rs +++ b/kimchi/src/circuits/witness/constant_cell.rs @@ -13,7 +13,7 @@ impl ConstantCell { } } -impl WitnessCell for ConstantCell { +impl WitnessCell for ConstantCell { fn value(&self, _witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { self.value } diff --git a/kimchi/src/circuits/witness/copy_bits_cell.rs b/kimchi/src/circuits/witness/copy_bits_cell.rs index 964e2f26bf..e97eed7940 100644 --- a/kimchi/src/circuits/witness/copy_bits_cell.rs +++ b/kimchi/src/circuits/witness/copy_bits_cell.rs @@ -23,7 +23,7 @@ impl CopyBitsCell { } } -impl WitnessCell for CopyBitsCell { +impl WitnessCell for CopyBitsCell { fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { F::from_bits(&witness[self.col][self.row].to_bits()[self.start..self.end]) .expect("failed to deserialize field bits for copy bits cell") diff --git a/kimchi/src/circuits/witness/copy_cell.rs b/kimchi/src/circuits/witness/copy_cell.rs index ffa8339094..f5b75ad829 100644 --- a/kimchi/src/circuits/witness/copy_cell.rs +++ b/kimchi/src/circuits/witness/copy_cell.rs @@ -15,7 +15,7 @@ impl CopyCell { } } -impl WitnessCell for CopyCell { +impl WitnessCell for CopyCell { fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { witness[self.col][self.row] } diff --git a/kimchi/src/circuits/witness/copy_shift_cell.rs b/kimchi/src/circuits/witness/copy_shift_cell.rs index b0ed5d055a..e151d0cd57 100644 --- a/kimchi/src/circuits/witness/copy_shift_cell.rs +++ b/kimchi/src/circuits/witness/copy_shift_cell.rs @@ -15,7 +15,7 @@ impl CopyShiftCell { } } -impl WitnessCell for CopyShiftCell { +impl WitnessCell for CopyShiftCell { fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { F::from(2u32).pow([self.shift]) * witness[self.col][self.row] } diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index 8dad3a2a40..daea8be7fa 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -20,10 +20,11 @@ pub use self::{ variables::{variable_map, variables, Variables}, }; -/// Witness cell interface -pub trait WitnessCell { +/// Witness cell interface. By default, the witness cell is a single element of type F. +pub trait WitnessCell { fn value(&self, witness: &mut [Vec; W], variables: &Variables, index: usize) -> F; + // Length is 1 by default (T is single F element) unless overridden fn length(&self) -> usize { 1 } @@ -97,7 +98,7 @@ mod tests { #[test] fn zero_layout() { - let layout: Vec>>> = vec![vec![ + let layout: Vec>>> = vec![vec![ ConstantCell::create(PallasField::zero()), ConstantCell::create(PallasField::zero()), ConstantCell::create(PallasField::zero()), @@ -140,7 +141,7 @@ mod tests { #[test] fn mixed_layout() { - let layout: Vec>>> = vec![ + let layout: Vec>>> = vec![ vec![ ConstantCell::create(PallasField::from(12u32)), ConstantCell::create(PallasField::from(0xa5a3u32)), diff --git a/kimchi/src/circuits/witness/variable_bits_cell.rs b/kimchi/src/circuits/witness/variable_bits_cell.rs index 1fef513607..cef394cfb9 100644 --- a/kimchi/src/circuits/witness/variable_bits_cell.rs +++ b/kimchi/src/circuits/witness/variable_bits_cell.rs @@ -18,7 +18,7 @@ impl<'a> VariableBitsCell<'a> { } } -impl<'a, const W: usize, F: Field> WitnessCell for VariableBitsCell<'a> { +impl<'a, const W: usize, F: Field> WitnessCell for VariableBitsCell<'a> { fn value(&self, _witness: &mut [Vec; W], variables: &Variables, _index: usize) -> F { let bits = if let Some(end) = self.end { F::from_bits(&variables[self.name].to_bits()[self.start..end]) diff --git a/kimchi/src/circuits/witness/variable_cell.rs b/kimchi/src/circuits/witness/variable_cell.rs index c24ce57d42..88b17ccf9f 100644 --- a/kimchi/src/circuits/witness/variable_cell.rs +++ b/kimchi/src/circuits/witness/variable_cell.rs @@ -14,7 +14,7 @@ impl<'a> VariableCell<'a> { } } -impl<'a, const W: usize, F: Field> WitnessCell for VariableCell<'a> { +impl<'a, const W: usize, F: Field> WitnessCell for VariableCell<'a> { fn value(&self, _witness: &mut [Vec; W], variables: &Variables, _index: usize) -> F { variables[self.name] } From 95bfeb5ae9d524d87362ff205480baa06ee00d49 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 26 Oct 2023 19:11:20 +0200 Subject: [PATCH 108/173] making column width generic in witness cell by default, smaller diff, cleaner interface --- .../polynomials/foreign_field_add/witness.rs | 4 ++-- .../polynomials/foreign_field_mul/witness.rs | 2 +- .../polynomials/range_check/witness.rs | 4 ++-- kimchi/src/circuits/polynomials/rot.rs | 4 ++-- kimchi/src/circuits/polynomials/xor.rs | 9 +++------ kimchi/src/circuits/witness/constant_cell.rs | 2 +- kimchi/src/circuits/witness/copy_bits_cell.rs | 2 +- kimchi/src/circuits/witness/copy_cell.rs | 2 +- .../src/circuits/witness/copy_shift_cell.rs | 2 +- kimchi/src/circuits/witness/index_cell.rs | 2 +- kimchi/src/circuits/witness/mod.rs | 20 ++++++++++--------- .../circuits/witness/variable_bits_cell.rs | 2 +- kimchi/src/circuits/witness/variable_cell.rs | 2 +- 13 files changed, 28 insertions(+), 29 deletions(-) diff --git a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs index 40f08e013b..950f7a265d 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_add/witness.rs @@ -185,7 +185,7 @@ fn init_ffadd_row( overflow: F, carry: F, ) { - let layout: [Vec>>; 1] = [ + let layout: [Vec>>; 1] = [ // ForeignFieldAdd row vec![ VariableCell::create("left_lo"), @@ -221,7 +221,7 @@ fn init_bound_rows( bound: &[F; 3], carry: &F, ) { - let layout: [Vec>>; 2] = [ + let layout: [Vec>>; 2] = [ vec![ // ForeignFieldAdd row VariableCell::create("result_lo"), diff --git a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs index ef0fc02a26..2c1e0e328d 100644 --- a/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs +++ b/kimchi/src/circuits/polynomials/foreign_field_mul/witness.rs @@ -41,7 +41,7 @@ use super::circuitgates; // // so that most significant limb, q2, is in W[2][0]. // -fn create_layout() -> [Vec>>; 2] { +fn create_layout() -> [Vec>>; 2] { [ // ForeignFieldMul row vec![ diff --git a/kimchi/src/circuits/polynomials/range_check/witness.rs b/kimchi/src/circuits/polynomials/range_check/witness.rs index 55d99b2b7f..459c95435f 100644 --- a/kimchi/src/circuits/polynomials/range_check/witness.rs +++ b/kimchi/src/circuits/polynomials/range_check/witness.rs @@ -29,7 +29,7 @@ use o1_utils::foreign_field::BigUintForeignFieldHelpers; /// For example, we can convert the `RangeCheck0` circuit gate into /// a 64-bit lookup by adding two copy constraints to constrain /// columns 1 and 2 to zero. -fn layout() -> [Vec>>; 4] { +fn layout() -> [Vec>>; 4] { [ /* row 1, RangeCheck0 row */ range_check_0_row("v0", 0), @@ -86,7 +86,7 @@ fn layout() -> [Vec>>; 4] { pub fn range_check_0_row( limb_name: &'static str, row: usize, -) -> Vec>> { +) -> Vec>> { vec![ VariableCell::create(limb_name), /* 12-bit copies */ diff --git a/kimchi/src/circuits/polynomials/rot.rs b/kimchi/src/circuits/polynomials/rot.rs index 8b72a94e95..1df1376cb6 100644 --- a/kimchi/src/circuits/polynomials/rot.rs +++ b/kimchi/src/circuits/polynomials/rot.rs @@ -266,7 +266,7 @@ where // ROTATION WITNESS COMPUTATION -fn layout_rot64(curr_row: usize) -> [Vec>>; 3] { +fn layout_rot64(curr_row: usize) -> [Vec>>; 3] { [ rot_row(), range_check_0_row("shifted", curr_row + 1), @@ -274,7 +274,7 @@ fn layout_rot64(curr_row: usize) -> [Vec() -> Vec>> { +fn rot_row() -> Vec>> { vec![ VariableCell::create("word"), VariableCell::create("rotated"), diff --git a/kimchi/src/circuits/polynomials/xor.rs b/kimchi/src/circuits/polynomials/xor.rs index d7276cee12..6750a511bc 100644 --- a/kimchi/src/circuits/polynomials/xor.rs +++ b/kimchi/src/circuits/polynomials/xor.rs @@ -169,10 +169,7 @@ where } // Witness layout -fn layout( - curr_row: usize, - bits: usize, -) -> Vec>>> { +fn layout(curr_row: usize, bits: usize) -> Vec>>> { let num_xor = num_xors(bits); let mut layout = (0..num_xor) .map(|i| xor_row(i, curr_row + i)) @@ -181,7 +178,7 @@ fn layout( layout } -fn xor_row(nybble: usize, curr_row: usize) -> Vec>> { +fn xor_row(nybble: usize, curr_row: usize) -> Vec>> { let start = nybble * 16; vec![ VariableBitsCell::create("in1", start, None), @@ -202,7 +199,7 @@ fn xor_row(nybble: usize, curr_row: usize) -> Vec() -> Vec>> { +fn zero_row() -> Vec>> { vec![ ConstantCell::create(F::zero()), ConstantCell::create(F::zero()), diff --git a/kimchi/src/circuits/witness/constant_cell.rs b/kimchi/src/circuits/witness/constant_cell.rs index 1ec69d797c..cbc7aaa7c8 100644 --- a/kimchi/src/circuits/witness/constant_cell.rs +++ b/kimchi/src/circuits/witness/constant_cell.rs @@ -13,7 +13,7 @@ impl ConstantCell { } } -impl WitnessCell for ConstantCell { +impl WitnessCell for ConstantCell { fn value(&self, _witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { self.value } diff --git a/kimchi/src/circuits/witness/copy_bits_cell.rs b/kimchi/src/circuits/witness/copy_bits_cell.rs index e97eed7940..a61ca183a8 100644 --- a/kimchi/src/circuits/witness/copy_bits_cell.rs +++ b/kimchi/src/circuits/witness/copy_bits_cell.rs @@ -23,7 +23,7 @@ impl CopyBitsCell { } } -impl WitnessCell for CopyBitsCell { +impl WitnessCell for CopyBitsCell { fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { F::from_bits(&witness[self.col][self.row].to_bits()[self.start..self.end]) .expect("failed to deserialize field bits for copy bits cell") diff --git a/kimchi/src/circuits/witness/copy_cell.rs b/kimchi/src/circuits/witness/copy_cell.rs index f5b75ad829..d3fad71654 100644 --- a/kimchi/src/circuits/witness/copy_cell.rs +++ b/kimchi/src/circuits/witness/copy_cell.rs @@ -15,7 +15,7 @@ impl CopyCell { } } -impl WitnessCell for CopyCell { +impl WitnessCell for CopyCell { fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { witness[self.col][self.row] } diff --git a/kimchi/src/circuits/witness/copy_shift_cell.rs b/kimchi/src/circuits/witness/copy_shift_cell.rs index e151d0cd57..b0c87587d1 100644 --- a/kimchi/src/circuits/witness/copy_shift_cell.rs +++ b/kimchi/src/circuits/witness/copy_shift_cell.rs @@ -15,7 +15,7 @@ impl CopyShiftCell { } } -impl WitnessCell for CopyShiftCell { +impl WitnessCell for CopyShiftCell { fn value(&self, witness: &mut [Vec; W], _variables: &Variables, _index: usize) -> F { F::from(2u32).pow([self.shift]) * witness[self.col][self.row] } diff --git a/kimchi/src/circuits/witness/index_cell.rs b/kimchi/src/circuits/witness/index_cell.rs index 9d6ebefea5..fd2628a316 100644 --- a/kimchi/src/circuits/witness/index_cell.rs +++ b/kimchi/src/circuits/witness/index_cell.rs @@ -18,7 +18,7 @@ impl<'a> IndexCell<'a> { } } -impl<'a, const W: usize, F: Field> WitnessCell> for IndexCell<'a> { +impl<'a, F: Field, const W: usize> WitnessCell, W> for IndexCell<'a> { fn value(&self, _witness: &mut [Vec; W], variables: &Variables>, index: usize) -> F { assert!(index < self.length, "index out of bounds of `IndexCell`"); variables[self.name][index] diff --git a/kimchi/src/circuits/witness/mod.rs b/kimchi/src/circuits/witness/mod.rs index daea8be7fa..830e2af5e7 100644 --- a/kimchi/src/circuits/witness/mod.rs +++ b/kimchi/src/circuits/witness/mod.rs @@ -20,8 +20,10 @@ pub use self::{ variables::{variable_map, variables, Variables}, }; +use super::polynomial::COLUMNS; + /// Witness cell interface. By default, the witness cell is a single element of type F. -pub trait WitnessCell { +pub trait WitnessCell { fn value(&self, witness: &mut [Vec; W], variables: &Variables, index: usize) -> F; // Length is 1 by default (T is single F element) unless overridden @@ -41,25 +43,25 @@ pub trait WitnessCell { /// - layout: the partial layout to initialize from /// - variables: the hashmap of variables to get the values from #[allow(clippy::too_many_arguments)] -pub fn init_cell( +pub fn init_cell( witness: &mut [Vec; W], offset: usize, row: usize, col: usize, cell: usize, index: usize, - layout: &[Vec>>], + layout: &[Vec>>], variables: &Variables, ) { witness[col][row + offset] = layout[row][cell].value(witness, variables, index); } /// Initialize a witness row based on layout and computed variables -pub fn init_row( +pub fn init_row( witness: &mut [Vec; W], offset: usize, row: usize, - layout: &[Vec>>], + layout: &[Vec>>], variables: &Variables, ) { let mut col = 0; @@ -73,10 +75,10 @@ pub fn init_row( } /// Initialize a witness based on layout and computed variables -pub fn init( +pub fn init( witness: &mut [Vec; W], offset: usize, - layout: &[Vec>>], + layout: &[Vec>>], variables: &Variables, ) { for row in 0..layout.len() { @@ -98,7 +100,7 @@ mod tests { #[test] fn zero_layout() { - let layout: Vec>>> = vec![vec![ + let layout: Vec>>> = vec![vec![ ConstantCell::create(PallasField::zero()), ConstantCell::create(PallasField::zero()), ConstantCell::create(PallasField::zero()), @@ -141,7 +143,7 @@ mod tests { #[test] fn mixed_layout() { - let layout: Vec>>> = vec![ + let layout: Vec>>> = vec![ vec![ ConstantCell::create(PallasField::from(12u32)), ConstantCell::create(PallasField::from(0xa5a3u32)), diff --git a/kimchi/src/circuits/witness/variable_bits_cell.rs b/kimchi/src/circuits/witness/variable_bits_cell.rs index cef394cfb9..584920592d 100644 --- a/kimchi/src/circuits/witness/variable_bits_cell.rs +++ b/kimchi/src/circuits/witness/variable_bits_cell.rs @@ -18,7 +18,7 @@ impl<'a> VariableBitsCell<'a> { } } -impl<'a, const W: usize, F: Field> WitnessCell for VariableBitsCell<'a> { +impl<'a, F: Field, const W: usize> WitnessCell for VariableBitsCell<'a> { fn value(&self, _witness: &mut [Vec; W], variables: &Variables, _index: usize) -> F { let bits = if let Some(end) = self.end { F::from_bits(&variables[self.name].to_bits()[self.start..end]) diff --git a/kimchi/src/circuits/witness/variable_cell.rs b/kimchi/src/circuits/witness/variable_cell.rs index 88b17ccf9f..fcb6ee9f21 100644 --- a/kimchi/src/circuits/witness/variable_cell.rs +++ b/kimchi/src/circuits/witness/variable_cell.rs @@ -14,7 +14,7 @@ impl<'a> VariableCell<'a> { } } -impl<'a, const W: usize, F: Field> WitnessCell for VariableCell<'a> { +impl<'a, F: Field, const W: usize> WitnessCell for VariableCell<'a> { fn value(&self, _witness: &mut [Vec; W], variables: &Variables, _index: usize) -> F { variables[self.name] } From 0276c756bb3f58cfc667daa9d31efe66ab96af0e Mon Sep 17 00:00:00 2001 From: querolita Date: Mon, 30 Oct 2023 20:37:10 +0100 Subject: [PATCH 109/173] adapt order of generics in witness layout --- kimchi/src/circuits/polynomials/keccak/witness.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/witness.rs b/kimchi/src/circuits/polynomials/keccak/witness.rs index d6aa7971e2..bd0df8463c 100644 --- a/kimchi/src/circuits/polynomials/keccak/witness.rs +++ b/kimchi/src/circuits/polynomials/keccak/witness.rs @@ -5,9 +5,9 @@ use ark_ff::PrimeField; use super::KECCAK_COLS; -type _Layout = Vec>>>; +type _Layout = Vec, COLUMNS>>>; -fn _layout_round() -> _Layout { +fn _layout_round() -> _Layout { vec![ IndexCell::create("state_a", 0, 100), IndexCell::create("state_c", 100, 120), @@ -34,7 +34,7 @@ fn _layout_round() -> _Layout { ] } -fn _layout_sponge() -> _Layout { +fn _layout_sponge() -> _Layout { vec![ IndexCell::create("old_state", 0, 100), IndexCell::create("new_state", 100, 200), From 4433f41f16f3cec363d7d952f09e257628c3290e Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 31 Oct 2023 20:22:12 +0000 Subject: [PATCH 110/173] Separate out instruction pre-fetching --- Cargo.lock | 2 + optimism/Cargo.toml | 2 + optimism/src/mips/interpreter.rs | 95 +++++++++++++++++++++ optimism/src/mips/mod.rs | 1 + optimism/src/mips/witness.rs | 136 ++++++++++++++++++++++++++++++- 5 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 optimism/src/mips/interpreter.rs diff --git a/Cargo.lock b/Cargo.lock index 77da43c308..72a48265e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1310,6 +1310,8 @@ dependencies = [ "serde", "serde_json", "serde_with", + "strum", + "strum_macros", ] [[package]] diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml index da634f3030..ca3653e116 100644 --- a/optimism/Cargo.toml +++ b/optimism/Cargo.toml @@ -31,3 +31,5 @@ hex = "0.4.3" regex = "1.10.2" libflate = "2" base64 = "0.21.5" +strum = "0.24.0" +strum_macros = "0.24.0" diff --git a/optimism/src/mips/interpreter.rs b/optimism/src/mips/interpreter.rs new file mode 100644 index 0000000000..e28197fd06 --- /dev/null +++ b/optimism/src/mips/interpreter.rs @@ -0,0 +1,95 @@ +use strum_macros::{EnumCount, EnumIter}; + +pub const FD_STDIN: u32 = 0; +pub const FD_STDOUT: u32 = 1; +pub const FD_STDERR: u32 = 2; +pub const FD_HINT_READ: u32 = 3; +pub const FD_HINT_WRITE: u32 = 4; +pub const FD_PREIMAGE_READ: u32 = 5; +pub const FD_PREIMAGE_WRITE: u32 = 6; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Instruction { + RType(RTypeInstruction), + JType(JTypeInstruction), + IType(ITypeInstruction), +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, EnumCount, EnumIter)] +pub enum RTypeInstruction { + ShiftLeftLogical, // sll + ShiftRightLogical, // srl + ShiftRightArithmetic, // sra + ShiftLeftLogicalVariable, // sllv + ShiftRightLogicalVariable, // srlv + ShiftRightArithmeticVariable, // srav + JumpRegister, // jr + JumpAndLinkRegister, // jalr + SyscallMmap, // syscall (Mmap) + SyscallExitGroup, // syscall (ExitGroup) + SyscallReadPrimage, // syscall (Read 5) + SyscallReadOther, // syscall (Read ?) + SyscallWriteHint, // syscall (Write 4) + SyscallWritePreimage, // syscall (Write 6) + SyscallWriteOther, // syscall (Write ?) + SyscallFcntl, // syscall (Fcntl) + SyscallOther, // syscall (Brk, Clone, ?) + MoveZero, // movz + MoveNonZero, // movn + Sync, // sync + MoveFromHi, // mfhi + MoveToHi, // mthi + MoveFromLo, // mflo + MoveToLo, // mtlo + Multiply, // mult + MultiplyUnsigned, // multu + Div, // div + DivUnsigned, // divu + Add, // add + AddUnsigned, // addu + Sub, // sub + SubUnsigned, // subu + And, // and + Or, // or + Xor, // xor + Nor, // nor + SetLessThan, // slt + SetLessThanUnsigned, // sltu + MultiplyToRegister, // mul + CountLeadingOnes, // clo + CountLeadingZeros, // clz +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, EnumCount, EnumIter)] +pub enum JTypeInstruction { + Jump, // j + JumpAndLink, // jal +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, EnumCount, EnumIter)] +pub enum ITypeInstruction { + BranchEq, // beq + BranchNeq, // bne + BranchLeqZero, // blez + BranchGtZero, // bgtz + AddImmediate, // addi + AddImmediateUnsigned, // addiu + SetLessThanImmediate, // slti + SetLessThanImmediateUnsigned, // sltiu + AndImmediate, // andi + OrImmediate, // ori + XorImmediate, // xori + LoadUpperImmediate, // lui + Load8, // lb + Load16, // lh + Load32, // lw + Load8Unsigned, // lbu + Load16Unsigned, // lhu + LoadWordLeft, // lwl + LoadWordRight, // lwr + Store8, // sb + Store16, // sh + Store32, // sw + StoreWordLeft, // swl + StoreWordRight, // swr +} diff --git a/optimism/src/mips/mod.rs b/optimism/src/mips/mod.rs index 2499222bb4..e4b4a77241 100644 --- a/optimism/src/mips/mod.rs +++ b/optimism/src/mips/mod.rs @@ -1,2 +1,3 @@ +pub mod interpreter; pub mod registers; pub mod witness; diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index dbaa7b30a8..68a8714394 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -1,4 +1,10 @@ -use crate::{cannon::State, mips::registers::Registers}; +use crate::{ + cannon::State, + mips::{ + interpreter::{self, ITypeInstruction, Instruction, JTypeInstruction, RTypeInstruction}, + registers::Registers, + }, +}; use ark_ff::Field; use std::array; @@ -92,7 +98,135 @@ impl Env { } } + pub fn get_memory_direct(&self, addr: u32) -> u8 { + const PAGE_ADDRESS_SIZE: u32 = 12; + const PAGE_SIZE: u32 = 1 << PAGE_ADDRESS_SIZE; + const PAGE_ADDRESS_MASK: u32 = PAGE_SIZE - 1; + let page = (addr >> PAGE_ADDRESS_SIZE) as u32; + let page_address = (addr & PAGE_ADDRESS_MASK) as usize; + for (page_index, memory) in self.memory.iter() { + if *page_index == page { + return memory[page_address]; + } + } + panic!("Could not access address") + } + + pub fn decode_instruction(&self) -> Instruction { + let instruction = ((self.get_memory_direct(self.instruction_pointer) as u32) << 24) + | ((self.get_memory_direct(self.instruction_pointer + 1) as u32) << 16) + | ((self.get_memory_direct(self.instruction_pointer + 2) as u32) << 8) + | (self.get_memory_direct(self.instruction_pointer + 3) as u32); + match instruction >> 26 { + 0x00 => match instruction & 0x3F { + 0x00 => Instruction::RType(RTypeInstruction::ShiftLeftLogical), + 0x02 => Instruction::RType(RTypeInstruction::ShiftRightLogical), + 0x03 => Instruction::RType(RTypeInstruction::ShiftRightArithmetic), + 0x04 => Instruction::RType(RTypeInstruction::ShiftLeftLogicalVariable), + 0x06 => Instruction::RType(RTypeInstruction::ShiftRightLogicalVariable), + 0x07 => Instruction::RType(RTypeInstruction::ShiftRightArithmeticVariable), + 0x08 => Instruction::RType(RTypeInstruction::JumpRegister), + 0x09 => Instruction::RType(RTypeInstruction::JumpAndLinkRegister), + 0x0a => Instruction::RType(RTypeInstruction::MoveZero), + 0x0b => Instruction::RType(RTypeInstruction::MoveNonZero), + 0x0c => match self.registers.general_purpose[2] { + 4090 => Instruction::RType(RTypeInstruction::SyscallMmap), + 4045 => { + // sysBrk + Instruction::RType(RTypeInstruction::SyscallOther) + } + 4120 => { + // sysClone + Instruction::RType(RTypeInstruction::SyscallOther) + } + 4246 => Instruction::RType(RTypeInstruction::SyscallExitGroup), + 4003 => match self.registers.general_purpose[4] { + interpreter::FD_PREIMAGE_READ => { + Instruction::RType(RTypeInstruction::SyscallReadPrimage) + } + _ => Instruction::RType(RTypeInstruction::SyscallReadOther), + }, + 4004 => match self.registers.general_purpose[4] { + interpreter::FD_PREIMAGE_WRITE => { + Instruction::RType(RTypeInstruction::SyscallWritePreimage) + } + interpreter::FD_HINT_WRITE => { + Instruction::RType(RTypeInstruction::SyscallWriteHint) + } + _ => Instruction::RType(RTypeInstruction::SyscallWriteOther), + }, + 4055 => Instruction::RType(RTypeInstruction::SyscallFcntl), + _ => { + // NB: This has well-defined behavior. Don't panic! + Instruction::RType(RTypeInstruction::SyscallOther) + } + }, + 0x0f => Instruction::RType(RTypeInstruction::Sync), + 0x10 => Instruction::RType(RTypeInstruction::MoveFromHi), + 0x11 => Instruction::RType(RTypeInstruction::MoveToHi), + 0x12 => Instruction::RType(RTypeInstruction::MoveFromLo), + 0x13 => Instruction::RType(RTypeInstruction::MoveToLo), + 0x18 => Instruction::RType(RTypeInstruction::Multiply), + 0x19 => Instruction::RType(RTypeInstruction::MultiplyUnsigned), + 0x1a => Instruction::RType(RTypeInstruction::Div), + 0x1b => Instruction::RType(RTypeInstruction::DivUnsigned), + 0x20 => Instruction::RType(RTypeInstruction::Add), + 0x21 => Instruction::RType(RTypeInstruction::AddUnsigned), + 0x22 => Instruction::RType(RTypeInstruction::Sub), + 0x23 => Instruction::RType(RTypeInstruction::SubUnsigned), + 0x24 => Instruction::RType(RTypeInstruction::And), + 0x25 => Instruction::RType(RTypeInstruction::Or), + 0x26 => Instruction::RType(RTypeInstruction::Xor), + 0x2a => Instruction::RType(RTypeInstruction::SetLessThan), + 0x2b => Instruction::RType(RTypeInstruction::SetLessThanUnsigned), + _ => { + panic!("Unhandled instruction {:#X}", instruction) + } + }, + 0x02 => Instruction::JType(JTypeInstruction::Jump), + 0x03 => Instruction::JType(JTypeInstruction::JumpAndLink), + 0x08 => Instruction::IType(ITypeInstruction::AddImmediate), + 0x09 => Instruction::IType(ITypeInstruction::AddImmediateUnsigned), + 0x0A => Instruction::IType(ITypeInstruction::SetLessThanImmediate), + 0x0B => Instruction::IType(ITypeInstruction::SetLessThanImmediateUnsigned), + 0x0C => Instruction::IType(ITypeInstruction::AndImmediate), + 0x0D => Instruction::IType(ITypeInstruction::OrImmediate), + 0x0E => Instruction::IType(ITypeInstruction::XorImmediate), + 0x0F => Instruction::IType(ITypeInstruction::LoadUpperImmediate), + 0x1C => match instruction & 0x3F { + 0x02 => Instruction::RType(RTypeInstruction::MultiplyToRegister), + 0x20 => Instruction::RType(RTypeInstruction::CountLeadingZeros), + 0x21 => Instruction::RType(RTypeInstruction::CountLeadingOnes), + _ => panic!("Unhandled instruction {:#X}", instruction), + }, + 0x20 => Instruction::IType(ITypeInstruction::Load8), + 0x21 => Instruction::IType(ITypeInstruction::Load16), + 0x22 => Instruction::IType(ITypeInstruction::LoadWordLeft), + 0x23 => Instruction::IType(ITypeInstruction::Load32), + 0x24 => Instruction::IType(ITypeInstruction::Load8Unsigned), + 0x25 => Instruction::IType(ITypeInstruction::Load16Unsigned), + 0x26 => Instruction::IType(ITypeInstruction::LoadWordRight), + 0x28 => Instruction::IType(ITypeInstruction::Store8), + 0x29 => Instruction::IType(ITypeInstruction::Store16), + 0x2a => Instruction::IType(ITypeInstruction::StoreWordLeft), + 0x2b => Instruction::IType(ITypeInstruction::Store32), + 0x2e => Instruction::IType(ITypeInstruction::StoreWordRight), + 0x30 => { + // Note: This is ll (LoadLinked), but we're only simulating a single processor. + Instruction::IType(ITypeInstruction::Load32) + } + 0x38 => { + // Note: This is sc (StoreConditional), but we're only simulating a single processor. + Instruction::IType(ITypeInstruction::Store32) + } + _ => { + panic!("Unhandled instruction {:#X}", instruction) + } + } + } + pub fn step(&mut self) { + println!("instruction: {:?}", self.decode_instruction()); // TODO self.halt = true; } From 1a64113b9d389df99a0e08314afe09af4c5f0688 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 19 Oct 2023 13:41:09 +0200 Subject: [PATCH 111/173] add functionality to convert from hexadecimal string BigUints --- utils/src/biguint_helpers.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/src/biguint_helpers.rs b/utils/src/biguint_helpers.rs index 73c5955e2a..be97df1625 100644 --- a/utils/src/biguint_helpers.rs +++ b/utils/src/biguint_helpers.rs @@ -7,6 +7,9 @@ pub trait BigUintHelpers { /// Returns the minimum number of bits required to represent a BigUint /// As opposed to BigUint::bits, this function returns 1 for the input zero fn bitlen(&self) -> usize; + + /// Creates a BigUint from an hexadecimal string in big endian + fn from_hex(s: &str) -> Self; } impl BigUintHelpers for BigUint { @@ -17,4 +20,7 @@ impl BigUintHelpers for BigUint { self.bits() as usize } } + fn from_hex(s: &str) -> Self { + BigUint::parse_bytes(s.as_bytes(), 16).unwrap() + } } From 5ecf0ad80851fa0791db41645b3ae96c1a359b54 Mon Sep 17 00:00:00 2001 From: querolita Date: Thu, 19 Oct 2023 13:42:35 +0200 Subject: [PATCH 112/173] add a few tests for the keccak witness generation code, and printing tools --- kimchi/src/tests/keccak.rs | 243 +++++++++++++++++++++++++++++++++++-- 1 file changed, 233 insertions(+), 10 deletions(-) diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index f5fa597068..06eb16b618 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -1,14 +1,237 @@ use std::array; -use crate::circuits::{ - constraints::ConstraintSystem, - gate::CircuitGate, - polynomials::keccak::{self, OFF}, - wires::Wire, +use crate::{ + circuits::{ + constraints::ConstraintSystem, + gate::{CircuitGate, GateType}, + polynomials::keccak::{ + collapse, compose, decompose, expand, pad, reset, shift, + witness::extend_keccak_witness, KECCAK_COLS, QUARTERS, + }, + wires::Wire, + }, + curve::KimchiCurve, }; -use ark_ec::AffineCurve; -use mina_curves::pasta::{Fp, Pallas, Vesta}; -use rand::Rng; +use ark_ff::{Field, PrimeField, Zero}; +use mina_curves::pasta::Pallas; +use num_bigint::BigUint; +use o1_utils::{BigUintHelpers, FieldHelpers}; -//use super::framework::TestFramework; -type PallasField = ::BaseField; +fn create_test_constraint_system( + bytelength: usize, +) -> ConstraintSystem +where + G::BaseField: PrimeField, +{ + let mut gates = vec![]; + let next_row = CircuitGate::extend_keccak(&mut gates, bytelength); + // Adding dummy row to avoid out of bounds in squeeze constraints accessing Next row + gates.push(CircuitGate { + typ: GateType::Zero, + wires: Wire::for_row(next_row), + coeffs: vec![], + }); + + ConstraintSystem::create(gates).build().unwrap() +} + +fn create_keccak_witness(message: BigUint) -> [Vec; KECCAK_COLS] +where + G::BaseField: PrimeField, +{ + let mut witness: [Vec; KECCAK_COLS] = + array::from_fn(|_| vec![G::ScalarField::zero(); 0]); + extend_keccak_witness(&mut witness, message); + // Adding dummy row to avoid out of bounds in squeeze constraints accessing Next row + let dummy_row: [Vec; KECCAK_COLS] = + array::from_fn(|_| vec![G::ScalarField::zero()]); + for col in 0..KECCAK_COLS { + witness[col].extend(dummy_row[col].iter()); + } + witness +} + +fn print_witness(witness: &[Vec; KECCAK_COLS], round: usize) { + fn to_u64(elem: F) -> u64 { + let mut bytes = FieldHelpers::::to_bytes(&elem); + bytes.reverse(); + bytes.iter().fold(0, |acc: u64, x| (acc << 8) + *x as u64) + } + fn print_line(state: &[u64]) { + print!(" "); + for x in 0..5 { + let quarters = &state[4 * x..4 * (x + 1)]; + let word = compose(&collapse(&reset(&shift(quarters)))); + print!("{:016x} ", word); + } + println!(); + } + fn print_matrix(state: &[u64]) { + for x in 0..5 { + print!(" "); + for y in 0..5 { + let quarters = &state[4 * (5 * y + x)..4 * (5 * y + x) + 4]; + let word = compose(&collapse(&reset(&shift(quarters)))); + print!("{:016x} ", word); + } + println!(); + } + } + + let row = witness + .iter() + .map(|x| to_u64::(x[round])) + .collect::>(); + let next = witness + .iter() + .map(|x| to_u64::(x[round + 1])) + .collect::>(); + + println!("----------------------------------------"); + println!("ROUND {}", round); + println!("State A:"); + print_matrix(&row[0..100]); + println!("State C:"); + print_line(&row[100..120]); + println!("State D:"); + print_line(&row[320..340]); + println!("State E:"); + print_matrix(&row[340..440]); + println!("State B:"); + print_matrix(&row[1440..1540]); + + let mut state_f = row[2340..2344].to_vec(); + let mut tail = next[4..100].to_vec(); + state_f.append(&mut tail); + + println!("State F:"); + print_matrix(&state_f); + println!("State G:"); + print_matrix(&next[0..100]); +} + +// Sets up test for a given message and desired input bytelength +fn test_keccak(message: BigUint) -> BigUint +where + G::BaseField: PrimeField, +{ + let bytelength = message.to_bytes_be().len(); + let padded_len = { + let mut sized = message.to_bytes_be(); + sized.resize(bytelength - sized.len(), 0); + pad(&sized).len() + }; + let _index = create_test_constraint_system::(padded_len); + let witness = create_keccak_witness::(message); + + for r in 1..=24 { + print_witness::(&witness, r); + } + + let mut hash = vec![]; + let hash_row = witness[0].len() - 2; // Hash row is dummy row + println!(); + println!("----------------------------------------"); + print!("Hash: "); + for b in 0..32 { + hash.push(FieldHelpers::to_bytes(&witness[200 + b][hash_row])[0]); + print!("{:02x}", hash[b]); + } + println!(); + println!(); + + BigUint::from_bytes_be(&hash) +} + +#[test] +fn test_bitwise_sparse_representation() { + assert_eq!(expand(0xFFFF), 0x1111111111111111); + + let word_a: u64 = 0x70d324ac9215fd8e; + let dense_a = decompose(word_a); + let real_dense_a = [0xfd8e, 0x9215, 0x24ac, 0x70d3]; + for i in 0..QUARTERS { + assert_eq!(dense_a[i], real_dense_a[i]); + } + assert_eq!(word_a, compose(&dense_a)); + + let sparse_a = dense_a.iter().map(|x| expand(*x)).collect::>(); + let real_sparse_a: Vec = vec![ + 0x1111110110001110, + 0x1001001000010101, + 0x10010010101100, + 0x111000011010011, + ]; + for i in 0..QUARTERS { + assert_eq!(sparse_a[i], real_sparse_a[i]); + } + + let word_b: u64 = 0x11c76438a7f9e94d; + let dense_b = decompose(word_b); + let sparse_b = dense_b.iter().map(|x| expand(*x)).collect::>(); + + let xor_ab: u64 = word_a ^ word_b; + assert_eq!(xor_ab, 0x6114409435ec14c3); + + let sparse_xor = decompose(xor_ab) + .iter() + .map(|x| expand(*x)) + .collect::>(); + let real_sparse_xor = [ + 0x1010011000011, + 0x11010111101100, + 0x100000010010100, + 0x110000100010100, + ]; + for i in 0..QUARTERS { + assert_eq!(sparse_xor[i], real_sparse_xor[i]); + } + + let sparse_sum_ab = sparse_a + .iter() + .zip(sparse_b.iter()) + .map(|(a, b)| a + b) + .collect::>(); + let shifts_sum_ab = shift(&sparse_sum_ab); + let reset_sum_ab = reset(&shifts_sum_ab); + assert_eq!(sparse_xor, reset_sum_ab); + + for i in 0..QUARTERS { + assert_eq!( + sparse_sum_ab[i], + shifts_sum_ab[i] + + shifts_sum_ab[4 + i] * 2 + + shifts_sum_ab[8 + i] * 4 + + shifts_sum_ab[12 + i] * 8 + ) + } +} + +#[test] +// Tests a random block of 1080 bits +fn test_random_block() { + let claim_random = test_keccak::( + BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126"), + ); + let hash_random = + BigUint::from_hex("845e9dd4e22b4917a80c5419a0ddb3eebf5f4f7cc6035d827314a18b718f751f"); + assert_eq!(claim_random, hash_random); +} + +#[test] +// Test hash of message zero with 1 byte +fn test_dummy() { + let claim1 = test_keccak::(BigUint::from_bytes_be(&[0x00])); + let hash1 = + BigUint::from_hex("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"); + assert_eq!(claim1, hash1); +} + +#[test] +// Test hash of message zero with 1 byte +fn test_blocks() { + let claim_3blocks = test_keccak::(BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f")); + let hash_3blocks = + BigUint::from_hex("7e369e1a4362148fca24c67c76f14dbe24b75c73e9b0efdb8c46056c8514287e"); + assert_eq!(claim_3blocks, hash_3blocks); +} From ed53123bae46aa770c08a6eeb6de23f83c15b69f Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Tue, 31 Oct 2023 16:45:46 +0100 Subject: [PATCH 113/173] cannon: adding clone traits --- optimism/src/cannon.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 6b0049c040..c031a78a77 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -116,13 +116,13 @@ impl ToString for State { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HostProgram { pub name: String, pub arguments: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct VmConfiguration { pub input_state_file: String, pub output_state_file: String, From 75e5c048d7c81d9b337cc4d6432996b44f6b8a40 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Tue, 31 Oct 2023 16:46:00 +0100 Subject: [PATCH 114/173] optimism: dispatching configuration for info pp --- optimism/src/main.rs | 10 +++++----- optimism/src/mips/witness.rs | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 732ceddb3d..20e509cacf 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -115,17 +115,17 @@ pub fn main() -> ExitCode { println!("configuration\n{:#?}", configuration); - let file = File::open(configuration.input_state_file).expect("Error opening input state file "); + let file = File::open(&configuration.input_state_file).expect("Error opening input state file "); let reader = BufReader::new(file); // Read the JSON contents of the file as an instance of `State`. let state: State = serde_json::from_reader(reader).expect("Error reading input state file"); - if let Some(host_program) = configuration.host { + if let Some(host_program) = &configuration.host { println!("Launching host program {}", host_program.name); - let _child = std::process::Command::new(host_program.name) - .args(host_program.arguments) + let _child = std::process::Command::new(&host_program.name) + .args(&host_program.arguments) .spawn() .expect("Could not spawn host process"); }; @@ -135,7 +135,7 @@ pub fn main() -> ExitCode { let mut env = witness::Env::::create(page_size, state); while !env.halt { - env.step(); + env.step(configuration.clone()); } // TODO: Logic diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 68a8714394..0e6c13aba2 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -1,5 +1,5 @@ use crate::{ - cannon::State, + cannon::{State, StepFrequency, VmConfiguration}, mips::{ interpreter::{self, ITypeInstruction, Instruction, JTypeInstruction, RTypeInstruction}, registers::Registers, @@ -227,7 +227,25 @@ impl Env { pub fn step(&mut self) { println!("instruction: {:?}", self.decode_instruction()); + + self.pp_info(config.info_at); // TODO self.halt = true; } + + fn at(& self, at: StepFrequency) -> bool { + let m:u64 = self.instruction_counter as u64; + match at { + StepFrequency::Never => false, + StepFrequency::Always => true, + StepFrequency::Exactly(n) => n == m , + StepFrequency::Every(n) => m % n == 0, + } + } + + fn pp_info(& self, at: StepFrequency) { + if self.at(at) { + println!("Info"); + } + } } From d645c1d9d39655645ea7853a282f8d7567fe048d Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Tue, 31 Oct 2023 16:58:19 +0100 Subject: [PATCH 115/173] optimism: basic logging in place now needs to compute the right data :-) --- Cargo.lock | 5 +++-- optimism/Cargo.toml | 1 + optimism/src/main.rs | 3 ++- optimism/src/mips/witness.rs | 22 ++++++++++++++++++---- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72a48265e0..316ea39e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1302,6 +1302,7 @@ dependencies = [ "hex", "kimchi", "libflate", + "log", "mina-curves", "mina-poseidon", "poly-commitment", @@ -1391,9 +1392,9 @@ checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml index ca3653e116..7f29aeb4d3 100644 --- a/optimism/Cargo.toml +++ b/optimism/Cargo.toml @@ -33,3 +33,4 @@ libflate = "2" base64 = "0.21.5" strum = "0.24.0" strum_macros = "0.24.0" +log = "0.4.20" diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 20e509cacf..4879c74eb1 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -115,7 +115,8 @@ pub fn main() -> ExitCode { println!("configuration\n{:#?}", configuration); - let file = File::open(&configuration.input_state_file).expect("Error opening input state file "); + let file = + File::open(&configuration.input_state_file).expect("Error opening input state file "); let reader = BufReader::new(file); // Read the JSON contents of the file as an instance of `State`. diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 0e6c13aba2..61b3f3790e 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -6,6 +6,7 @@ use crate::{ }, }; use ark_ff::Field; +use log::info; use std::array; pub const NUM_GLOBAL_LOOKUP_TERMS: usize = 1; @@ -229,23 +230,36 @@ impl Env { println!("instruction: {:?}", self.decode_instruction()); self.pp_info(config.info_at); + // TODO self.halt = true; } - fn at(& self, at: StepFrequency) -> bool { - let m:u64 = self.instruction_counter as u64; + fn at(&self, at: StepFrequency) -> bool { + let m: u64 = self.instruction_counter as u64; match at { StepFrequency::Never => false, StepFrequency::Always => true, - StepFrequency::Exactly(n) => n == m , + StepFrequency::Exactly(n) => n == m, StepFrequency::Every(n) => m % n == 0, } } - fn pp_info(& self, at: StepFrequency) { + fn pp_info(&self, at: StepFrequency) { if self.at(at) { println!("Info"); + let elapsed = 1.0; + let step = self.instruction_counter; + let pc = self.instruction_pointer; + let insn = 0xffffff; + let ips = 0.0 / elapsed; + let pages = self.memory.len(); + let mem = 0; + let name = "unsupported"; + info!( + "processing step {} pc {} insn {} ips {} page {} mem {} name {}", + step, pc, insn, ips, pages, mem, name + ); } } } From bd3091d25a9a09f9dc4c258dbb8b11be10e13179 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Tue, 31 Oct 2023 22:33:15 +0100 Subject: [PATCH 116/173] optimism: computes correct time, ips, memory data Lacks symbol support --- optimism/src/cannon.rs | 15 ++++++++++ optimism/src/main.rs | 11 ++++---- optimism/src/mips/witness.rs | 53 ++++++++++++++++++++++++++++++------ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index c031a78a77..a536f1cfd1 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -135,3 +135,18 @@ pub struct VmConfiguration { pub pprof_cpu: bool, pub host: Option, } + +#[derive(Debug, Clone)] +pub struct Start { + pub time: std::time::Instant, + pub step: usize, +} + +impl Start { + pub fn create(step: usize) -> Start { + Start { + time: std::time::Instant::now(), + step, + } + } +} diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 4879c74eb1..b77c317494 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -1,6 +1,6 @@ use clap::{arg, value_parser, Arg, ArgAction, Command}; use kimchi_optimism::{ - cannon::{State, VmConfiguration}, + cannon::{self, Start, State, VmConfiguration}, mips::witness, }; use std::{fs::File, io::BufReader, process::ExitCode}; @@ -113,8 +113,6 @@ fn cli() -> VmConfiguration { pub fn main() -> ExitCode { let configuration = cli(); - println!("configuration\n{:#?}", configuration); - let file = File::open(&configuration.input_state_file).expect("Error opening input state file "); @@ -131,12 +129,13 @@ pub fn main() -> ExitCode { .expect("Could not spawn host process"); }; - let page_size = 1 << 12; + // Initialize some data used for statistical computations + let start = Start::create(state.step as usize); - let mut env = witness::Env::::create(page_size, state); + let mut env = witness::Env::::create(cannon::PAGE_SIZE, state); while !env.halt { - env.step(configuration.clone()); + env.step(configuration.clone(), &start); } // TODO: Logic diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 61b3f3790e..f2d7e09824 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -1,5 +1,5 @@ use crate::{ - cannon::{State, StepFrequency, VmConfiguration}, + cannon::{Start, State, StepFrequency, VmConfiguration, PAGE_SIZE}, mips::{ interpreter::{self, ITypeInstruction, Instruction, JTypeInstruction, RTypeInstruction}, registers::Registers, @@ -226,10 +226,16 @@ impl Env { } } - pub fn step(&mut self) { + pub fn step(&mut self, config: VmConfiguration, start: &Start) { println!("instruction: {:?}", self.decode_instruction()); - self.pp_info(config.info_at); + self.pp_info(config.info_at, start); + + // Force stops at given iteration + if self.at(config.stop_at) { + self.halt = true; + return; + } // TODO self.halt = true; @@ -245,19 +251,48 @@ impl Env { } } - fn pp_info(&self, at: StepFrequency) { + const UNIT: usize = 1024; // a "unit" of memory is 1024 bytes + const PREFIXES: &str = "KMGTPE"; // prefixes for memory quantities KiB, MiB, GiB, ... + + fn memory_usage(&self) -> String { + let total = self.memory.len() * PAGE_SIZE; + + if total < Self::UNIT { + format!("{total} B") + } else { + // Compute the index in the prefixes string above + let mut idx = 0; + let mut d = Self::UNIT; + let mut n = total / Self::UNIT; + + while n >= Self::UNIT { + d *= Self::UNIT; + idx += 1; + n /= Self::UNIT; + } + + let value = total as f64 / d as f64; + let prefix = Self::PREFIXES.chars().nth(idx).unwrap(); + + format!("{:.1} {}iB", value, prefix) + } + } + + fn pp_info(&self, at: StepFrequency, start: &Start) { if self.at(at) { println!("Info"); - let elapsed = 1.0; + let elapsed = start.time.elapsed(); let step = self.instruction_counter; let pc = self.instruction_pointer; let insn = 0xffffff; - let ips = 0.0 / elapsed; + let how_many_steps = step - start.step; + // Approximate instruction per seconds + let ips = how_many_steps as f64 / elapsed.as_secs() as f64; let pages = self.memory.len(); - let mem = 0; - let name = "unsupported"; + let mem = self.memory_usage(); + let name = "unsupported"; // TODO: implement symbol lookups info!( - "processing step {} pc {} insn {} ips {} page {} mem {} name {}", + "processing step {} pc {:#10x} insn {:#10x} ips {:.2} page {} mem {} name {}", step, pc, insn, ips, pages, mem, name ); } From d2d7ea677da53f94e4c5904215f5a5c9a4151f5f Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 1 Nov 2023 11:35:25 +0100 Subject: [PATCH 117/173] optimism: move memory size representation in its own outer function --- optimism/src/mips/witness.rs | 65 ++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index f2d7e09824..a245914052 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -53,6 +53,36 @@ fn fresh_scratch_state() -> [Fp; N] { array::from_fn(|_| Fp::zero()) } +const KUNIT: usize = 1024; // a kunit of memory is 1024 things (bytes, kilobytes, ...) +const PREFIXES: &str = "KMGTPEZY"; // prefixes for memory quantities KiB, MiB, GiB, ... + +// Create a human-readable string representation of the memory size +fn memory_size(total: usize) -> String { + if total < KUNIT { + format!("{total} B") + } else { + // Compute the index in the prefixes string above + let mut idx = 0; + let mut d = KUNIT; + let mut n = total / KUNIT; + + while n >= KUNIT { + d *= KUNIT; + idx += 1; + n /= KUNIT; + } + + let value = total as f64 / d as f64; + + let prefix = + // Famous last words: 1023 yottabytes ought to be enough for anybody + // Corollary: unwrap() below shouldn't fail + PREFIXES.chars().nth(idx).unwrap(); + + format!("{:.1} {}iB", value, prefix) + } +} + impl Env { pub fn create(page_size: usize, state: State) -> Self { let initial_instruction_pointer = state.pc; @@ -232,7 +262,7 @@ impl Env { self.pp_info(config.info_at, start); // Force stops at given iteration - if self.at(config.stop_at) { + if self.should_trigger_at(config.stop_at) { self.halt = true; return; } @@ -241,7 +271,7 @@ impl Env { self.halt = true; } - fn at(&self, at: StepFrequency) -> bool { + fn should_trigger_at(&self, at: StepFrequency) -> bool { let m: u64 = self.instruction_counter as u64; match at { StepFrequency::Never => false, @@ -251,36 +281,14 @@ impl Env { } } - const UNIT: usize = 1024; // a "unit" of memory is 1024 bytes - const PREFIXES: &str = "KMGTPE"; // prefixes for memory quantities KiB, MiB, GiB, ... - + // Compute memory usage fn memory_usage(&self) -> String { let total = self.memory.len() * PAGE_SIZE; - - if total < Self::UNIT { - format!("{total} B") - } else { - // Compute the index in the prefixes string above - let mut idx = 0; - let mut d = Self::UNIT; - let mut n = total / Self::UNIT; - - while n >= Self::UNIT { - d *= Self::UNIT; - idx += 1; - n /= Self::UNIT; - } - - let value = total as f64 / d as f64; - let prefix = Self::PREFIXES.chars().nth(idx).unwrap(); - - format!("{:.1} {}iB", value, prefix) - } + memory_size(total) } fn pp_info(&self, at: StepFrequency, start: &Start) { - if self.at(at) { - println!("Info"); + if self.should_trigger_at(at) { let elapsed = start.time.elapsed(); let step = self.instruction_counter; let pc = self.instruction_pointer; @@ -290,7 +298,8 @@ impl Env { let ips = how_many_steps as f64 / elapsed.as_secs() as f64; let pages = self.memory.len(); let mem = self.memory_usage(); - let name = "unsupported"; // TODO: implement symbol lookups + let name = "symbols are not supported yet"; // TODO: implement symbol lookups + info!( "processing step {} pc {:#10x} insn {:#10x} ips {:.2} page {} mem {} name {}", step, pc, insn, ips, pages, mem, name From 919f352de0c629dae95fc4f743f526e79d5045e5 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 1 Nov 2023 11:46:35 +0100 Subject: [PATCH 118/173] optimism: testing memory_size function And a bit of fun in the comments :-) --- optimism/src/mips/witness.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index a245914052..1c0f0d1c29 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -54,7 +54,7 @@ fn fresh_scratch_state() -> [Fp; N] { } const KUNIT: usize = 1024; // a kunit of memory is 1024 things (bytes, kilobytes, ...) -const PREFIXES: &str = "KMGTPEZY"; // prefixes for memory quantities KiB, MiB, GiB, ... +const PREFIXES: &str = "KMGTPE"; // prefixes for memory quantities KiB, MiB, GiB, ... // Create a human-readable string representation of the memory size fn memory_size(total: usize) -> String { @@ -75,9 +75,14 @@ fn memory_size(total: usize) -> String { let value = total as f64 / d as f64; let prefix = - // Famous last words: 1023 yottabytes ought to be enough for anybody - // Corollary: unwrap() below shouldn't fail - PREFIXES.chars().nth(idx).unwrap(); + //////////////////////////////////////////////////////////////////////////// + // Famous last words: 1023 exabytes ought to be enough for anybody // + // // + // Corollary: unwrap() below shouldn't fail // + // // + // The maximum representation for usize corresponds to 16 exabytes anyway // + //////////////////////////////////////////////////////////////////////////// + PREFIXES.chars().nth(idx).unwrap(); format!("{:.1} {}iB", value, prefix) } @@ -307,3 +312,18 @@ impl Env { } } } + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_memory_size() { + assert_eq!(memory_size(1023_usize), "1023 B"); + assert_eq!(memory_size(1024_usize), "1.0 KiB"); + assert_eq!(memory_size(1024 * 1024_usize), "1.0 MiB"); + assert_eq!(memory_size(2100 * 1024 * 1024_usize), "2.1 GiB"); + assert_eq!(memory_size(std::usize::MAX), "16.0 EiB"); + } +} From b4a19d85d53ea456857d96a1b78c05c480eb21ce Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 1 Nov 2023 14:23:32 +0100 Subject: [PATCH 119/173] optimism: info now displays the opcode correctly in its output --- optimism/src/cannon.rs | 4 +++- optimism/src/mips/witness.rs | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index a536f1cfd1..b176ab9503 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -6,7 +6,9 @@ use regex::Regex; use serde::{Deserialize, Deserializer, Serialize}; use std::io::Read; -pub const PAGE_SIZE: usize = 4096; +pub const PAGE_ADDRESS_SIZE: usize = 12; +pub const PAGE_SIZE: usize = 1 << PAGE_ADDRESS_SIZE; +pub const PAGE_ADDRESS_MASK: usize = PAGE_SIZE - 1; #[derive(Serialize, Deserialize, Debug)] pub struct Page { diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 1c0f0d1c29..b7df53748b 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -1,5 +1,8 @@ use crate::{ - cannon::{Start, State, StepFrequency, VmConfiguration, PAGE_SIZE}, + cannon::{ + Start, State, StepFrequency, VmConfiguration, PAGE_ADDRESS_MASK, PAGE_ADDRESS_SIZE, + PAGE_SIZE, + }, mips::{ interpreter::{self, ITypeInstruction, Instruction, JTypeInstruction, RTypeInstruction}, registers::Registers, @@ -292,16 +295,41 @@ impl Env { memory_size(total) } - fn pp_info(&self, at: StepFrequency, start: &Start) { + fn page_address(&self) -> (u32, usize) { + let address = self.instruction_pointer; + let page = (address >> PAGE_ADDRESS_SIZE) as u32; + let page_address = (address & (PAGE_ADDRESS_MASK as u32)) as usize; + (page, page_address) + } + + fn get_opcode(&mut self) -> Option { + let (page_id, page_address) = self.page_address(); + for (page_index, memory) in self.memory.iter_mut() { + if page_id == *page_index { + let memory_slice: [u8; 4] = memory[page_address..page_address + 4] + .try_into() + .expect("Couldn't read 4 bytes at given address"); + return Some(u32::from_be_bytes(memory_slice)); + } + } + None + } + + fn pp_info(&mut self, at: StepFrequency, start: &Start) { if self.should_trigger_at(at) { let elapsed = start.time.elapsed(); let step = self.instruction_counter; let pc = self.instruction_pointer; - let insn = 0xffffff; - let how_many_steps = step - start.step; + + // Get the 32-bits opcode + let insn = self.get_opcode().unwrap(); + // Approximate instruction per seconds + let how_many_steps = step - start.step; let ips = how_many_steps as f64 / elapsed.as_secs() as f64; + let pages = self.memory.len(); + let mem = self.memory_usage(); let name = "symbols are not supported yet"; // TODO: implement symbol lookups From 83476ce1c79b841e3d8a32c68ff8b3e91b17f9eb Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 1 Nov 2023 15:27:08 +0100 Subject: [PATCH 120/173] logging: using env_logger for simple logging --- Cargo.lock | 20 ++++++++++++++++++++ optimism/Cargo.toml | 1 + optimism/src/main.rs | 3 +++ 3 files changed, 24 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 316ea39e5b..5bf22e44bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,6 +853,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "errno" version = "0.3.1" @@ -1093,6 +1106,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "iai" version = "0.1.1" @@ -1298,6 +1317,7 @@ dependencies = [ "base64", "clap 4.4.6", "elf", + "env_logger", "groupmap", "hex", "kimchi", diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml index 7f29aeb4d3..37fe645aee 100644 --- a/optimism/Cargo.toml +++ b/optimism/Cargo.toml @@ -34,3 +34,4 @@ base64 = "0.21.5" strum = "0.24.0" strum_macros = "0.24.0" log = "0.4.20" +env_logger = "0.10.0" diff --git a/optimism/src/main.rs b/optimism/src/main.rs index b77c317494..0d3fab3064 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -132,6 +132,9 @@ pub fn main() -> ExitCode { // Initialize some data used for statistical computations let start = Start::create(state.step as usize); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .init(); + let mut env = witness::Env::::create(cannon::PAGE_SIZE, state); while !env.halt { From fdee244d6a61d904c0227464dd2e5ed7a51604d8 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 1 Nov 2023 15:27:24 +0100 Subject: [PATCH 121/173] optimism: updating displayed information --- optimism/src/main.rs | 3 +-- optimism/src/mips/witness.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 0d3fab3064..27a0946323 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -132,8 +132,7 @@ pub fn main() -> ExitCode { // Initialize some data used for statistical computations let start = Start::create(state.step as usize); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) - .init(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let mut env = witness::Env::::create(cannon::PAGE_SIZE, state); diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index b7df53748b..1d2b007875 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -331,10 +331,10 @@ impl Env { let pages = self.memory.len(); let mem = self.memory_usage(); - let name = "symbols are not supported yet"; // TODO: implement symbol lookups + let name = "N/A"; // TODO: implement symbol lookups info!( - "processing step {} pc {:#10x} insn {:#10x} ips {:.2} page {} mem {} name {}", + "processing step {} pc {:#010x} insn {:#010x} ips {:.2} page {} mem {} name {}", step, pc, insn, ips, pages, mem, name ); } From ed84dfb76a9b08a47882b0ecafd9c3670da0fe82 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 1 Nov 2023 16:56:26 +0100 Subject: [PATCH 122/173] use iter() instead of iter_mut() --- optimism/src/mips/witness.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 1d2b007875..2dc914f570 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -141,7 +141,7 @@ impl Env { const PAGE_ADDRESS_SIZE: u32 = 12; const PAGE_SIZE: u32 = 1 << PAGE_ADDRESS_SIZE; const PAGE_ADDRESS_MASK: u32 = PAGE_SIZE - 1; - let page = (addr >> PAGE_ADDRESS_SIZE) as u32; + let page = addr >> PAGE_ADDRESS_SIZE; let page_address = (addr & PAGE_ADDRESS_MASK) as usize; for (page_index, memory) in self.memory.iter() { if *page_index == page { @@ -297,14 +297,14 @@ impl Env { fn page_address(&self) -> (u32, usize) { let address = self.instruction_pointer; - let page = (address >> PAGE_ADDRESS_SIZE) as u32; + let page = address >> PAGE_ADDRESS_SIZE; let page_address = (address & (PAGE_ADDRESS_MASK as u32)) as usize; (page, page_address) } fn get_opcode(&mut self) -> Option { let (page_id, page_address) = self.page_address(); - for (page_index, memory) in self.memory.iter_mut() { + for (page_index, memory) in self.memory.iter() { if page_id == *page_index { let memory_slice: [u8; 4] = memory[page_address..page_address + 4] .try_into() From 474c3990735c7880213c30696550d9d066213629 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Wed, 1 Nov 2023 17:20:29 +0100 Subject: [PATCH 123/173] Bump up actions/@checkout to 4.1.1 --- .github/workflows/benches.yml | 2 +- .github/workflows/coverage.yml.disabled | 2 +- .github/workflows/gh-page.yml | 2 +- .github/workflows/rust.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index e8ac86e6b9..7fd1fe8e2e 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -17,7 +17,7 @@ jobs: if: github.event.label.name == 'benchmark' steps: - name: Checkout PR - uses: actions/checkout@v2 + uses: actions/checkout@v4.1.1 # as action-rs does not seem to be maintained anymore, building from # scratch the environment using rustup diff --git a/.github/workflows/coverage.yml.disabled b/.github/workflows/coverage.yml.disabled index f9f00e3503..837124ea24 100644 --- a/.github/workflows/coverage.yml.disabled +++ b/.github/workflows/coverage.yml.disabled @@ -17,7 +17,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v4.1.1 with: persist-credentials: false diff --git a/.github/workflows/gh-page.yml b/.github/workflows/gh-page.yml index edbccf8ca3..7077069ded 100644 --- a/.github/workflows/gh-page.yml +++ b/.github/workflows/gh-page.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v4.1.1 # as action-rs does not seem to be maintained anymore, building from # scratch the environment using rustup diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3490171728..422940b2b8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -26,7 +26,7 @@ jobs: name: Run some basic checks and tests steps: - name: Checkout PR - uses: actions/checkout@v3 + uses: actions/checkout@v4.1.1 # as action-rs does not seem to be maintained anymore, building from # scratch the environment using rustup From 84fbbbb7d85b9ed9dacd733b590868431d038169 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Mon, 25 Sep 2023 10:45:56 +0200 Subject: [PATCH 124/173] Add link to blog post --- book/src/fundamentals/zkbook_ips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/fundamentals/zkbook_ips.md b/book/src/fundamentals/zkbook_ips.md index bcb68b68fe..bb8329e53c 100644 --- a/book/src/fundamentals/zkbook_ips.md +++ b/book/src/fundamentals/zkbook_ips.md @@ -79,4 +79,4 @@ Actually there is a distinction between an inductive set $A$, the type underlyin --- -See this blog post (TODO: copy content of blogpost here) +See this blog post: https://zkproof.org/2020/06/08/recursive-snarks/ From 6f819bae9aceb2ef8b6ff2e6d0943a64e2b4e1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Querol?= Date: Fri, 3 Nov 2023 15:00:02 +0100 Subject: [PATCH 125/173] fix typo in comments of witness_next_chunk Co-authored-by: Danny Willems --- kimchi/src/circuits/argument.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kimchi/src/circuits/argument.rs b/kimchi/src/circuits/argument.rs index ce9058b997..9ed50a3203 100644 --- a/kimchi/src/circuits/argument.rs +++ b/kimchi/src/circuits/argument.rs @@ -90,7 +90,7 @@ impl> ArgumentEnv { chunk } - /// Witness cells in current row in an interval [from, to) + /// Witness cells in next row in an interval [from, to) pub fn witness_next_chunk(&self, from: usize, to: usize) -> Vec { let mut chunk = Vec::with_capacity(to - from); for i in from..to { From 4de9b748cdd6dc1d4e5a6313028f7b59d142b10e Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Fri, 3 Nov 2023 17:38:23 +0100 Subject: [PATCH 126/173] cannon: basic support for metadata --- optimism/src/cannon.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index b176ab9503..7c9afd5bfa 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -1,6 +1,7 @@ // Data structure and stuff for compatibility with Cannon use base64::{engine::general_purpose, Engine as _}; + use libflate::zlib::Decoder; use regex::Regex; use serde::{Deserialize, Deserializer, Serialize}; @@ -152,3 +153,26 @@ impl Start { } } } + +#[derive(Debug, Clone, Deserialize)] +pub struct Symbol { + pub name: String, + pub start: u32, + pub size: usize, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Meta { + symbols: Vec, +} + +impl Meta { + pub fn find_address_symbol(&self, address: u32) -> Option { + for e in self.symbols.iter() { + if address >= e.start && address <= (e.start + e.size as u32) { + return Some(e.name.to_string()); + } + } + None + } +} From 4f6bcf28302d34681f7183a8d013783cf1fb47d9 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Fri, 3 Nov 2023 17:38:38 +0100 Subject: [PATCH 127/173] optimism: read metadata and use for info printing --- optimism/src/main.rs | 18 ++++++++++++++++-- optimism/src/mips/witness.rs | 12 +++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 27a0946323..1b07d961f7 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -1,6 +1,6 @@ use clap::{arg, value_parser, Arg, ArgAction, Command}; use kimchi_optimism::{ - cannon::{self, Start, State, VmConfiguration}, + cannon::{self, Meta, Start, State, VmConfiguration}, mips::witness, }; use std::{fs::File, io::BufReader, process::ExitCode}; @@ -120,6 +120,20 @@ pub fn main() -> ExitCode { // Read the JSON contents of the file as an instance of `State`. let state: State = serde_json::from_reader(reader).expect("Error reading input state file"); + let meta_file = File::open(&configuration.metadata_file).unwrap_or_else(|_| { + panic!( + "Could not open metadata file {}", + &configuration.metadata_file + ) + }); + + let meta: Meta = serde_json::from_reader(BufReader::new(meta_file)).unwrap_or_else(|_| { + panic!( + "Error deserializing metadata file {}", + &configuration.metadata_file + ) + }); + if let Some(host_program) = &configuration.host { println!("Launching host program {}", host_program.name); @@ -137,7 +151,7 @@ pub fn main() -> ExitCode { let mut env = witness::Env::::create(cannon::PAGE_SIZE, state); while !env.halt { - env.step(configuration.clone(), &start); + env.step(configuration.clone(), &meta, &start); } // TODO: Logic diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 2dc914f570..9e90004470 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -1,6 +1,6 @@ use crate::{ cannon::{ - Start, State, StepFrequency, VmConfiguration, PAGE_ADDRESS_MASK, PAGE_ADDRESS_SIZE, + Meta, Start, State, StepFrequency, VmConfiguration, PAGE_ADDRESS_MASK, PAGE_ADDRESS_SIZE, PAGE_SIZE, }, mips::{ @@ -264,10 +264,10 @@ impl Env { } } - pub fn step(&mut self, config: VmConfiguration, start: &Start) { + pub fn step(&mut self, config: VmConfiguration, metadata: &Meta, start: &Start) { println!("instruction: {:?}", self.decode_instruction()); - self.pp_info(config.info_at, start); + self.pp_info(config.info_at, metadata, start); // Force stops at given iteration if self.should_trigger_at(config.stop_at) { @@ -315,7 +315,7 @@ impl Env { None } - fn pp_info(&mut self, at: StepFrequency, start: &Start) { + fn pp_info(&mut self, at: StepFrequency, meta: &Meta, start: &Start) { if self.should_trigger_at(at) { let elapsed = start.time.elapsed(); let step = self.instruction_counter; @@ -331,7 +331,9 @@ impl Env { let pages = self.memory.len(); let mem = self.memory_usage(); - let name = "N/A"; // TODO: implement symbol lookups + let name = meta + .find_address_symbol(pc) + .unwrap_or_else(|| "n/a".to_string()); info!( "processing step {} pc {:#010x} insn {:#010x} ips {:.2} page {} mem {} name {}", From 1ca6b2e98ef14867594a26ae0d6c8d5fa3b30fd6 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 10:46:57 +0100 Subject: [PATCH 128/173] cannon: add sample test for metadata reading --- optimism/src/cannon.rs | 92 +++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 7c9afd5bfa..98c700c230 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -86,22 +86,6 @@ pub fn step_frequency_parser(s: &str) -> std::result::Result String { @@ -154,14 +138,14 @@ impl Start { } } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, PartialEq, Clone, Deserialize)] pub struct Symbol { pub name: String, pub start: u32, pub size: usize, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, PartialEq, Clone, Deserialize)] pub struct Meta { symbols: Vec, } @@ -176,3 +160,75 @@ impl Meta { None } } + +#[cfg(test)] +mod tests { + + use super::*; + use std::fs::File; + use std::io::{BufReader, Write}; + + #[test] + fn sp_parser() { + use StepFrequency::*; + assert_eq!(step_frequency_parser("never"), Ok(Never)); + assert_eq!(step_frequency_parser("always"), Ok(Always)); + assert_eq!(step_frequency_parser("=123"), Ok(Exactly(123))); + assert_eq!(step_frequency_parser("%123"), Ok(Every(123))); + assert!(step_frequency_parser("@123").is_err()); + } + + // This sample is a subset taken from a Cannon-generated "meta.json" file + const META_SAMPLE: &str = r#"{ + "symbols": [ + { + "name": "go.go", + "start": 0, + "size": 0 + }, + { + "name": "internal/cpu.processOptions", + "start": 69632, + "size": 1872 + }, + { + "name": "runtime.text", + "start": 69632, + "size": 0 + }]}"#; + + #[test] + fn test_meta_deserialize_from_file() { + let path = "meta_test.json"; + let mut output = + File::create(path).unwrap_or_else(|_| panic!("Could not create file {path}")); + write!(output, "{}", META_SAMPLE) + .unwrap_or_else(|_| panic!("Could not write to file {path}")); + + let input = File::open(path).unwrap_or_else(|_| panic!("Could not open file {path}")); + let buffered = BufReader::new(input); + let read: Meta = serde_json::from_reader(buffered) + .unwrap_or_else(|_| panic!("Failed to deserialize metadata from file {path}")); + + let expected = Meta { + symbols: vec![ + Symbol { + name: "go.go".to_string(), + start: 0_u32, + size: 0, + }, + Symbol { + name: "internal/cpu.processOptions".to_string(), + start: 69632, + size: 1872, + }, + Symbol { + name: "runtime.text".to_string(), + start: 69632, + size: 0, + }, + ], + }; + assert_eq!(read, expected); + } +} From 2abba7ef45084927c84ebf9d987fec12a4fac055 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 10:51:59 +0100 Subject: [PATCH 129/173] cannon: add test for find_address_symbol --- optimism/src/cannon.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 98c700c230..493920c39b 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -197,6 +197,10 @@ mod tests { "size": 0 }]}"#; + fn deserialize_meta_sample() -> Meta { + serde_json::from_str::(META_SAMPLE).unwrap() + } + #[test] fn test_meta_deserialize_from_file() { let path = "meta_test.json"; @@ -231,4 +235,15 @@ mod tests { }; assert_eq!(read, expected); } + + #[test] + fn test_find_address_symbol() { + let meta = deserialize_meta_sample(); + + assert_eq!( + meta.find_address_symbol(69633), + Some("internal/cpu.processOptions".to_string()) + ); + assert_eq!(meta.find_address_symbol(42), None); + } } From f278cc6e3b82eecdf10683657b6d84091dca23c6 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 11:10:11 +0100 Subject: [PATCH 130/173] cannon: use Vec::binary_search to find symbol names --- optimism/src/cannon.rs | 60 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 493920c39b..6516e7aa34 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -152,12 +152,31 @@ pub struct Meta { impl Meta { pub fn find_address_symbol(&self, address: u32) -> Option { - for e in self.symbols.iter() { - if address >= e.start && address <= (e.start + e.size as u32) { - return Some(e.name.to_string()); - } + use std::cmp::Ordering; + + let res = self.symbols.binary_search_by( + |Symbol { + start, + size, + name: _, + }| { + if address < *start { + Ordering::Greater + } else { + let end = *start + *size as u32; + if address >= end { + Ordering::Less + } else { + Ordering::Equal + } + } + }, + ); + + match res { + Ok(idx) => Some(self.symbols[idx].name.to_string()), + Err(_) => None, } - None } } @@ -195,6 +214,26 @@ mod tests { "name": "runtime.text", "start": 69632, "size": 0 + }, + { + "name": "runtime/internal/atomic.(*Uint8).Load", + "start": 71504, + "size": 28 + }, + { + "name": "runtime/internal/atomic.(*Uint8).Store", + "start": 71532, + "size": 28 + }, + { + "name": "runtime/internal/atomic.(*Uint8).And", + "start": 71560, + "size": 88 + }, + { + "name": "runtime/internal/atomic.(*Uint8).Or", + "start": 71648, + "size": 72 }]}"#; fn deserialize_meta_sample() -> Meta { @@ -233,7 +272,12 @@ mod tests { }, ], }; - assert_eq!(read, expected); + + // just test the 3 first symbols + let read_test = Meta { + symbols: read.symbols[0..3].to_vec(), + }; + assert_eq!(read_test, expected); } #[test] @@ -244,6 +288,10 @@ mod tests { meta.find_address_symbol(69633), Some("internal/cpu.processOptions".to_string()) ); + assert_eq!( + meta.find_address_symbol(69632), + Some("internal/cpu.processOptions".to_string()) + ); assert_eq!(meta.find_address_symbol(42), None); } } From 3516781644665fbe217725cbabde4d7ef34acb6e Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 14:20:25 +0100 Subject: [PATCH 131/173] cannon: add comment about expected invariant --- optimism/src/cannon.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 6516e7aa34..ae35e4c1ad 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -147,7 +147,7 @@ pub struct Symbol { #[derive(Debug, PartialEq, Clone, Deserialize)] pub struct Meta { - symbols: Vec, + symbols: Vec, // Needs to be in ascending order w.r.t start address } impl Meta { From 02ca2b33e6555cbbf3b9021faface2ce1a0e46ae Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 14:28:49 +0100 Subject: [PATCH 132/173] cannon: use combinators within find_address_symbol --- optimism/src/cannon.rs | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index ae35e4c1ad..f3493f0510 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -154,29 +154,26 @@ impl Meta { pub fn find_address_symbol(&self, address: u32) -> Option { use std::cmp::Ordering; - let res = self.symbols.binary_search_by( - |Symbol { - start, - size, - name: _, - }| { - if address < *start { - Ordering::Greater - } else { - let end = *start + *size as u32; - if address >= end { - Ordering::Less + self.symbols + .binary_search_by( + |Symbol { + start, + size, + name: _, + }| { + if address < *start { + Ordering::Greater } else { - Ordering::Equal + let end = *start + *size as u32; + if address >= end { + Ordering::Less + } else { + Ordering::Equal + } } - } - }, - ); - - match res { - Ok(idx) => Some(self.symbols[idx].name.to_string()), - Err(_) => None, - } + }, + ) + .map_or_else(|_| None, |idx| Some(self.symbols[idx].name.to_string())) } } From 53cb0226518320a244f0d01cb8ade6398e9ae2d4 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 14:32:38 +0100 Subject: [PATCH 133/173] cannon: make symbols field public --- optimism/src/cannon.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index f3493f0510..56f58c4a25 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -147,7 +147,7 @@ pub struct Symbol { #[derive(Debug, PartialEq, Clone, Deserialize)] pub struct Meta { - symbols: Vec, // Needs to be in ascending order w.r.t start address + pub symbols: Vec, // Needs to be in ascending order w.r.t start address } impl Meta { From 28189a1f2db6c23c9c761d9899b04e64d9cfcff8 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 15:00:55 +0100 Subject: [PATCH 134/173] cannon: make sure symbols are in ascending order and not 0-size --- optimism/src/cannon.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 56f58c4a25..e8853b0d0e 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -147,9 +147,22 @@ pub struct Symbol { #[derive(Debug, PartialEq, Clone, Deserialize)] pub struct Meta { + #[serde(deserialize_with = "filtered_ordered")] pub symbols: Vec, // Needs to be in ascending order w.r.t start address } +// Make sure that deserialized data are ordered in ascending order and that we +// have removed 0-size symbols +fn filtered_ordered<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let v: Vec = Deserialize::deserialize(deserializer)?; + let mut filtered: Vec = v.into_iter().filter(|e| e.size != 0).collect(); + filtered.sort_by(|a, b| a.start.cmp(&b.start)); + Ok(filtered) +} + impl Meta { pub fn find_address_symbol(&self, address: u32) -> Option { use std::cmp::Ordering; @@ -195,6 +208,8 @@ mod tests { } // This sample is a subset taken from a Cannon-generated "meta.json" file + // Interestingly, it contains 0-size symbols - there are removed by + // deserialization. const META_SAMPLE: &str = r#"{ "symbols": [ { From dc62aa74f1ff94f7495967fd408f8e9916ec1c62 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 6 Nov 2023 15:30:08 +0100 Subject: [PATCH 135/173] cannon: update tests --- optimism/src/cannon.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index e8853b0d0e..1337bdbd40 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -267,29 +267,35 @@ mod tests { let expected = Meta { symbols: vec![ - Symbol { - name: "go.go".to_string(), - start: 0_u32, - size: 0, - }, Symbol { name: "internal/cpu.processOptions".to_string(), start: 69632, size: 1872, }, Symbol { - name: "runtime.text".to_string(), - start: 69632, - size: 0, + name: "runtime/internal/atomic.(*Uint8).Load".to_string(), + start: 71504, + size: 28, + }, + Symbol { + name: "runtime/internal/atomic.(*Uint8).Store".to_string(), + start: 71532, + size: 28, + }, + Symbol { + name: "runtime/internal/atomic.(*Uint8).And".to_string(), + start: 71560, + size: 88, + }, + Symbol { + name: "runtime/internal/atomic.(*Uint8).Or".to_string(), + start: 71648, + size: 72, }, ], }; - // just test the 3 first symbols - let read_test = Meta { - symbols: read.symbols[0..3].to_vec(), - }; - assert_eq!(read_test, expected); + assert_eq!(read, expected); } #[test] From cb688fbb65073aec52fdcf665272180af0ec4f5e Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Thu, 9 Nov 2023 15:54:04 +0100 Subject: [PATCH 136/173] Update book/src/fundamentals/zkbook_ips.md Co-authored-by: Richard Bonichon --- book/src/fundamentals/zkbook_ips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/fundamentals/zkbook_ips.md b/book/src/fundamentals/zkbook_ips.md index bb8329e53c..237bad63ec 100644 --- a/book/src/fundamentals/zkbook_ips.md +++ b/book/src/fundamentals/zkbook_ips.md @@ -79,4 +79,4 @@ Actually there is a distinction between an inductive set $A$, the type underlyin --- -See this blog post: https://zkproof.org/2020/06/08/recursive-snarks/ +See [this blog post](https://zkproof.org/2020/06/08/recursive-snarks/) From 5342a48e9448c0c37a588c8a8ecf81c3c7e23f7e Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Mon, 13 Nov 2023 23:26:57 +0100 Subject: [PATCH 137/173] Fix clippy and fix typo --- optimism/src/mips/interpreter.rs | 2 +- optimism/src/mips/witness.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/optimism/src/mips/interpreter.rs b/optimism/src/mips/interpreter.rs index e28197fd06..1c83f36328 100644 --- a/optimism/src/mips/interpreter.rs +++ b/optimism/src/mips/interpreter.rs @@ -27,7 +27,7 @@ pub enum RTypeInstruction { JumpAndLinkRegister, // jalr SyscallMmap, // syscall (Mmap) SyscallExitGroup, // syscall (ExitGroup) - SyscallReadPrimage, // syscall (Read 5) + SyscallReadPreimage, // syscall (Read 5) SyscallReadOther, // syscall (Read ?) SyscallWriteHint, // syscall (Write 4) SyscallWritePreimage, // syscall (Write 6) diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 68a8714394..8fd8377561 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -102,7 +102,7 @@ impl Env { const PAGE_ADDRESS_SIZE: u32 = 12; const PAGE_SIZE: u32 = 1 << PAGE_ADDRESS_SIZE; const PAGE_ADDRESS_MASK: u32 = PAGE_SIZE - 1; - let page = (addr >> PAGE_ADDRESS_SIZE) as u32; + let page = addr >> PAGE_ADDRESS_SIZE; let page_address = (addr & PAGE_ADDRESS_MASK) as usize; for (page_index, memory) in self.memory.iter() { if *page_index == page { @@ -142,7 +142,7 @@ impl Env { 4246 => Instruction::RType(RTypeInstruction::SyscallExitGroup), 4003 => match self.registers.general_purpose[4] { interpreter::FD_PREIMAGE_READ => { - Instruction::RType(RTypeInstruction::SyscallReadPrimage) + Instruction::RType(RTypeInstruction::SyscallReadPreimage) } _ => Instruction::RType(RTypeInstruction::SyscallReadOther), }, From 60fa8693c3ea8559f03f32d85de4398b5c64861c Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 12:42:27 +0100 Subject: [PATCH 138/173] add unit bytes in RATE and CAPACITY constants --- kimchi/src/circuits/polynomials/keccak/gadget.rs | 4 ++-- kimchi/src/circuits/polynomials/keccak/mod.rs | 6 +++--- kimchi/src/circuits/polynomials/keccak/witness.rs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/gadget.rs b/kimchi/src/circuits/polynomials/keccak/gadget.rs index a620645d81..8aacaff14f 100644 --- a/kimchi/src/circuits/polynomials/keccak/gadget.rs +++ b/kimchi/src/circuits/polynomials/keccak/gadget.rs @@ -5,7 +5,7 @@ use crate::circuits::{ }; use ark_ff::{PrimeField, SquareRootField}; -use super::{expand_word, padded_length, RATE, RC, ROUNDS}; +use super::{expand_word, padded_length, RATE_IN_BYTES, RC, ROUNDS}; const SPONGE_COEFFS: usize = 336; @@ -24,7 +24,7 @@ impl CircuitGate { fn create_keccak(new_row: usize, bytelength: usize) -> Vec { let padded_len = padded_length(bytelength); let extra_bytes = padded_len - bytelength; - let num_blocks = padded_len / RATE; + let num_blocks = padded_len / RATE_IN_BYTES; let mut gates = vec![]; for block in 0..num_blocks { let root = block == 0; diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 3cd8bcdf30..eb4278ea29 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -6,8 +6,8 @@ pub mod witness; pub const DIM: usize = 5; pub const QUARTERS: usize = 4; pub const ROUNDS: usize = 24; -pub const RATE: usize = 1088 / 8; -pub const CAPACITY: usize = 512 / 8; +pub const RATE_IN_BYTES: usize = 1088 / 8; +pub const CAPACITY_IN_BYTES: usize = 512 / 8; pub const KECCAK_COLS: usize = 2344; use crate::circuits::expr::constraints::ExprOps; @@ -181,5 +181,5 @@ pub(crate) fn expand_state(state: &[u8]) -> Vec { /// On input a length, returns the smallest multiple of RATE that is greater than the bytelength pub(crate) fn padded_length(bytelength: usize) -> usize { - (bytelength / RATE + 1) * RATE + (bytelength / RATE_IN_BYTES + 1) * RATE_IN_BYTES } diff --git a/kimchi/src/circuits/polynomials/keccak/witness.rs b/kimchi/src/circuits/polynomials/keccak/witness.rs index a22713039f..e050841313 100644 --- a/kimchi/src/circuits/polynomials/keccak/witness.rs +++ b/kimchi/src/circuits/polynomials/keccak/witness.rs @@ -15,8 +15,8 @@ use ark_ff::PrimeField; use num_bigint::BigUint; use super::{ - bytestring, collapse, expand, pad, reset, shift, CAPACITY, DIM, KECCAK_COLS, OFF, QUARTERS, - RATE, + bytestring, collapse, expand, pad, reset, shift, CAPACITY_IN_BYTES, DIM, KECCAK_COLS, OFF, + QUARTERS, RATE_IN_BYTES, }; type Layout = Vec, COLUMNS>>>; @@ -322,7 +322,7 @@ impl Iota { /// constraints can access the next row in the squeeze pub fn extend_keccak_witness(witness: &mut [Vec; KECCAK_COLS], message: BigUint) { let padded = pad(&message.to_bytes_be()); - let chunks = padded.chunks(RATE); + let chunks = padded.chunks(RATE_IN_BYTES); // The number of rows that need to be added to the witness correspond to // - Absorb phase: @@ -340,7 +340,7 @@ pub fn extend_keccak_witness(witness: &mut [Vec; KECCAK_COLS], for chunk in chunks { let mut block = chunk.to_vec(); // Pad the block until reaching 200 bytes - block.append(&mut vec![0; CAPACITY]); + block.append(&mut vec![0; CAPACITY_IN_BYTES]); let dense = quarters(&block); let new_state = expand_state(&block); auto_clone!(new_state); From 3e4f1d2877d6064662230578c2e58e944c132e74 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 12:58:05 +0100 Subject: [PATCH 139/173] move bitwise-related tests to the mod file for a better scoping --- kimchi/src/circuits/polynomials/keccak/mod.rs | 70 +++++++++++++++++++ kimchi/src/tests/keccak.rs | 67 +----------------- 2 files changed, 71 insertions(+), 66 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index eb4278ea29..4ad22b34d8 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -183,3 +183,73 @@ pub(crate) fn expand_state(state: &[u8]) -> Vec { pub(crate) fn padded_length(bytelength: usize) -> usize { (bytelength / RATE_IN_BYTES + 1) * RATE_IN_BYTES } + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_bitwise_sparse_representation() { + assert_eq!(expand(0xFFFF), 0x1111111111111111); + + let word_a: u64 = 0x70d324ac9215fd8e; + let dense_a = decompose(word_a); + let real_dense_a = [0xfd8e, 0x9215, 0x24ac, 0x70d3]; + for i in 0..QUARTERS { + assert_eq!(dense_a[i], real_dense_a[i]); + } + assert_eq!(word_a, compose(&dense_a)); + + let sparse_a = dense_a.iter().map(|x| expand(*x)).collect::>(); + let real_sparse_a: Vec = vec![ + 0x1111110110001110, + 0x1001001000010101, + 0x10010010101100, + 0x111000011010011, + ]; + for i in 0..QUARTERS { + assert_eq!(sparse_a[i], real_sparse_a[i]); + } + + let word_b: u64 = 0x11c76438a7f9e94d; + let dense_b = decompose(word_b); + let sparse_b = dense_b.iter().map(|x| expand(*x)).collect::>(); + + let xor_ab: u64 = word_a ^ word_b; + assert_eq!(xor_ab, 0x6114409435ec14c3); + + let sparse_xor = decompose(xor_ab) + .iter() + .map(|x| expand(*x)) + .collect::>(); + let real_sparse_xor = [ + 0x1010011000011, + 0x11010111101100, + 0x100000010010100, + 0x110000100010100, + ]; + for i in 0..QUARTERS { + assert_eq!(sparse_xor[i], real_sparse_xor[i]); + } + + let sparse_sum_ab = sparse_a + .iter() + .zip(sparse_b.iter()) + .map(|(a, b)| a + b) + .collect::>(); + let shifts_sum_ab = shift(&sparse_sum_ab); + let reset_sum_ab = reset(&shifts_sum_ab); + assert_eq!(sparse_xor, reset_sum_ab); + + for i in 0..QUARTERS { + assert_eq!( + sparse_sum_ab[i], + shifts_sum_ab[i] + + shifts_sum_ab[4 + i] * 2 + + shifts_sum_ab[8 + i] * 4 + + shifts_sum_ab[12 + i] * 8 + ) + } + } +} diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index 06eb16b618..d56c334bbe 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -5,8 +5,7 @@ use crate::{ constraints::ConstraintSystem, gate::{CircuitGate, GateType}, polynomials::keccak::{ - collapse, compose, decompose, expand, pad, reset, shift, - witness::extend_keccak_witness, KECCAK_COLS, QUARTERS, + collapse, compose, pad, reset, shift, witness::extend_keccak_witness, KECCAK_COLS, }, wires::Wire, }, @@ -143,70 +142,6 @@ where BigUint::from_bytes_be(&hash) } -#[test] -fn test_bitwise_sparse_representation() { - assert_eq!(expand(0xFFFF), 0x1111111111111111); - - let word_a: u64 = 0x70d324ac9215fd8e; - let dense_a = decompose(word_a); - let real_dense_a = [0xfd8e, 0x9215, 0x24ac, 0x70d3]; - for i in 0..QUARTERS { - assert_eq!(dense_a[i], real_dense_a[i]); - } - assert_eq!(word_a, compose(&dense_a)); - - let sparse_a = dense_a.iter().map(|x| expand(*x)).collect::>(); - let real_sparse_a: Vec = vec![ - 0x1111110110001110, - 0x1001001000010101, - 0x10010010101100, - 0x111000011010011, - ]; - for i in 0..QUARTERS { - assert_eq!(sparse_a[i], real_sparse_a[i]); - } - - let word_b: u64 = 0x11c76438a7f9e94d; - let dense_b = decompose(word_b); - let sparse_b = dense_b.iter().map(|x| expand(*x)).collect::>(); - - let xor_ab: u64 = word_a ^ word_b; - assert_eq!(xor_ab, 0x6114409435ec14c3); - - let sparse_xor = decompose(xor_ab) - .iter() - .map(|x| expand(*x)) - .collect::>(); - let real_sparse_xor = [ - 0x1010011000011, - 0x11010111101100, - 0x100000010010100, - 0x110000100010100, - ]; - for i in 0..QUARTERS { - assert_eq!(sparse_xor[i], real_sparse_xor[i]); - } - - let sparse_sum_ab = sparse_a - .iter() - .zip(sparse_b.iter()) - .map(|(a, b)| a + b) - .collect::>(); - let shifts_sum_ab = shift(&sparse_sum_ab); - let reset_sum_ab = reset(&shifts_sum_ab); - assert_eq!(sparse_xor, reset_sum_ab); - - for i in 0..QUARTERS { - assert_eq!( - sparse_sum_ab[i], - shifts_sum_ab[i] - + shifts_sum_ab[4 + i] * 2 - + shifts_sum_ab[8 + i] * 4 - + shifts_sum_ab[12 + i] * 8 - ) - } -} - #[test] // Tests a random block of 1080 bits fn test_random_block() { From 8d793778d4d0df19f4f00a06acadda435d097c33 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 13:02:46 +0100 Subject: [PATCH 140/173] fix copy-paste typo in comment about sponge constraints --- kimchi/src/circuits/polynomials/keccak/circuitgates.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs index 289d107efc..1d87960079 100644 --- a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs +++ b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs @@ -224,7 +224,7 @@ where const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::KeccakSponge); const CONSTRAINTS: u32 = 568; - // Constraints for one round of the Keccak permutation function + // Constraints for the Keccak sponge fn constraint_checks>(env: &ArgumentEnv, _cache: &mut Cache) -> Vec { let mut constraints = vec![]; From a89a05bbf6a4986388b18be32abd13951c66d404 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 13:06:21 +0100 Subject: [PATCH 141/173] use QUARTERS instead of harcoded 4 --- kimchi/src/circuits/polynomials/keccak/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 4ad22b34d8..b763dd5482 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -119,7 +119,7 @@ pub(crate) fn pad(message: &[u8]) -> Vec { /// The resulting vector contains 4 times as many elements as the input. /// The output is placed in the vector as [reset0, reset1, reset2, reset3] pub(crate) fn shift(state: &[u64]) -> Vec { - let mut shifts = vec![vec![]; 4]; + let mut shifts = vec![vec![]; QUARTERS]; let aux = expand(0xFFFF); for term in state { shifts[0].push(aux & term); // shift0 = reset0 @@ -135,7 +135,7 @@ pub(crate) fn reset(shifts: &[u64]) -> Vec { shifts .iter() .copied() - .take(shifts.len() / 4) + .take(shifts.len() / QUARTERS) .collect::>() } From 42db1852472e62d57c3ad4c5278fe674e4c6fff6 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 14:01:58 +0100 Subject: [PATCH 142/173] include comment in docs alerting about the minimum field size required for the constraints to work --- .../src/circuits/polynomials/keccak/circuitgates.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs index 1d87960079..03b1cb866d 100644 --- a/kimchi/src/circuits/polynomials/keccak/circuitgates.rs +++ b/kimchi/src/circuits/polynomials/keccak/circuitgates.rs @@ -1,4 +1,13 @@ //! Keccak gadget +//! ------------- +//! The Keccak gadget is a circuit that implements the Keccak hash function +//! for 64-bit words, output length of 256 bits and bit rate of 1088 bits. +//! +//! It is composed of 1 absorb sponge gate, followed by 24 rounds of permutation per block +//! and 1 final squeeze sponge gate that outputs the 256-bit hash. +//! +//! NOTE: The constraints used in this gadget assume a field size of at least 65 bits to be sound. +//! use super::{DIM, OFF, QUARTERS}; use crate::{ auto_clone, auto_clone_array, @@ -50,7 +59,6 @@ macro_rules! from_shifts { }; } -//~ //~ | `KeccakRound` | [0...440) | [440...1540) | [1540...2344) | //~ | ------------- | --------- | ------------ | ------------- | //~ | Curr | theta | pirho | chi | @@ -58,7 +66,6 @@ macro_rules! from_shifts { //~ | `KeccakRound` | [0...100) | //~ | ------------- | --------- | //~ | Next | iota | -//~ //~ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- //~ //~ | Columns | [0...100) | [100...120) | [120...200) | [200...220) | [220...240) | [240...260) | [260...280) | [280...300) | [300...320) | [320...340) | [340...440) | From 2b4bf9e4d8ae5583e8c212af87a047f806a14e42 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 14:06:10 +0100 Subject: [PATCH 143/173] clarify order of quarters in decomposition --- kimchi/src/circuits/polynomials/keccak/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index b763dd5482..fe2a74a602 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -75,12 +75,13 @@ pub(crate) const RC: [u64; 24] = [ 0x8000000080008008, ]; -// Composes a vector of 4 dense quarters into the dense full u64 word +/// Composes a vector of 4 dense quarters into the dense full u64 word pub(crate) fn compose(quarters: &[u64]) -> u64 { quarters[0] + (1 << 16) * quarters[1] + (1 << 32) * quarters[2] + (1 << 48) * quarters[3] } -// Takes a dense u64 word and decomposes it into a vector of 4 dense quarters +/// Takes a dense u64 word and decomposes it into a vector of 4 dense quarters. +/// The first element of the vector corresponds to the 16 least significant bits. pub(crate) fn decompose(word: u64) -> Vec { vec![ word % (1 << 16), From f72f3a7066514e5df77f059167744d061dbb87db Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 14:08:30 +0100 Subject: [PATCH 144/173] rename claim/real for expected in tests --- kimchi/src/circuits/polynomials/keccak/mod.rs | 8 ++++---- kimchi/src/tests/keccak.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index fe2a74a602..69a4c801a8 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -196,21 +196,21 @@ mod tests { let word_a: u64 = 0x70d324ac9215fd8e; let dense_a = decompose(word_a); - let real_dense_a = [0xfd8e, 0x9215, 0x24ac, 0x70d3]; + let expected_dense_a = [0xfd8e, 0x9215, 0x24ac, 0x70d3]; for i in 0..QUARTERS { - assert_eq!(dense_a[i], real_dense_a[i]); + assert_eq!(dense_a[i], expected_dense_a[i]); } assert_eq!(word_a, compose(&dense_a)); let sparse_a = dense_a.iter().map(|x| expand(*x)).collect::>(); - let real_sparse_a: Vec = vec![ + let expected_sparse_a: Vec = vec![ 0x1111110110001110, 0x1001001000010101, 0x10010010101100, 0x111000011010011, ]; for i in 0..QUARTERS { - assert_eq!(sparse_a[i], real_sparse_a[i]); + assert_eq!(sparse_a[i], expected_sparse_a[i]); } let word_b: u64 = 0x11c76438a7f9e94d; diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index d56c334bbe..435ec97c2c 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -145,28 +145,28 @@ where #[test] // Tests a random block of 1080 bits fn test_random_block() { - let claim_random = test_keccak::( + let expected_random = test_keccak::( BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126"), ); let hash_random = BigUint::from_hex("845e9dd4e22b4917a80c5419a0ddb3eebf5f4f7cc6035d827314a18b718f751f"); - assert_eq!(claim_random, hash_random); + assert_eq!(expected_random, hash_random); } #[test] // Test hash of message zero with 1 byte fn test_dummy() { - let claim1 = test_keccak::(BigUint::from_bytes_be(&[0x00])); + let expected1 = test_keccak::(BigUint::from_bytes_be(&[0x00])); let hash1 = BigUint::from_hex("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"); - assert_eq!(claim1, hash1); + assert_eq!(expected1, hash1); } #[test] // Test hash of message zero with 1 byte fn test_blocks() { - let claim_3blocks = test_keccak::(BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f")); + let expected_3blocks = test_keccak::(BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f")); let hash_3blocks = BigUint::from_hex("7e369e1a4362148fca24c67c76f14dbe24b75c73e9b0efdb8c46056c8514287e"); - assert_eq!(claim_3blocks, hash_3blocks); + assert_eq!(expected_3blocks, hash_3blocks); } From e298118acf2e18ca9032c179e3200371d894300c Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 14:10:02 +0100 Subject: [PATCH 145/173] copy-paste typo in comment of test blocks --- kimchi/src/tests/keccak.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index 435ec97c2c..5dbae41c04 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -163,7 +163,7 @@ fn test_dummy() { } #[test] -// Test hash of message zero with 1 byte +// Test hash of message using 3 blocks fn test_blocks() { let expected_3blocks = test_keccak::(BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f")); let hash_3blocks = From ffb6180cf9c4c01ddc622ac13144aa7f7ca0659b Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 14:10:22 +0100 Subject: [PATCH 146/173] rename dummy test with zero --- kimchi/src/tests/keccak.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index 5dbae41c04..96e4841789 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -154,8 +154,8 @@ fn test_random_block() { } #[test] -// Test hash of message zero with 1 byte -fn test_dummy() { +// Test hash of message zero with 1 byte input length +fn test_zero() { let expected1 = test_keccak::(BigUint::from_bytes_be(&[0x00])); let hash1 = BigUint::from_hex("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"); From 1909a1e4134fd4838eb8b23af03048c79acb4aa1 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 14:12:44 +0100 Subject: [PATCH 147/173] print to stderr in tests instead --- kimchi/src/tests/keccak.rs | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index 96e4841789..267dd7074e 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -50,30 +50,30 @@ where witness } -fn print_witness(witness: &[Vec; KECCAK_COLS], round: usize) { +fn eprint_witness(witness: &[Vec; KECCAK_COLS], round: usize) { fn to_u64(elem: F) -> u64 { let mut bytes = FieldHelpers::::to_bytes(&elem); bytes.reverse(); bytes.iter().fold(0, |acc: u64, x| (acc << 8) + *x as u64) } - fn print_line(state: &[u64]) { - print!(" "); + fn eprint_line(state: &[u64]) { + eprint!(" "); for x in 0..5 { let quarters = &state[4 * x..4 * (x + 1)]; let word = compose(&collapse(&reset(&shift(quarters)))); - print!("{:016x} ", word); + eprint!("{:016x} ", word); } - println!(); + eprintln!(); } - fn print_matrix(state: &[u64]) { + fn eprint_matrix(state: &[u64]) { for x in 0..5 { - print!(" "); + eprint!(" "); for y in 0..5 { let quarters = &state[4 * (5 * y + x)..4 * (5 * y + x) + 4]; let word = compose(&collapse(&reset(&shift(quarters)))); - print!("{:016x} ", word); + eprint!("{:016x} ", word); } - println!(); + eprintln!(); } } @@ -86,27 +86,27 @@ fn print_witness(witness: &[Vec; KECCAK_COLS], round: usize) { .map(|x| to_u64::(x[round + 1])) .collect::>(); - println!("----------------------------------------"); - println!("ROUND {}", round); - println!("State A:"); - print_matrix(&row[0..100]); - println!("State C:"); - print_line(&row[100..120]); - println!("State D:"); - print_line(&row[320..340]); - println!("State E:"); - print_matrix(&row[340..440]); - println!("State B:"); - print_matrix(&row[1440..1540]); + eprintln!("----------------------------------------"); + eprintln!("ROUND {}", round); + eprintln!("State A:"); + eprint_matrix(&row[0..100]); + eprintln!("State C:"); + eprint_line(&row[100..120]); + eprintln!("State D:"); + eprint_line(&row[320..340]); + eprintln!("State E:"); + eprint_matrix(&row[340..440]); + eprintln!("State B:"); + eprint_matrix(&row[1440..1540]); let mut state_f = row[2340..2344].to_vec(); let mut tail = next[4..100].to_vec(); state_f.append(&mut tail); - println!("State F:"); - print_matrix(&state_f); - println!("State G:"); - print_matrix(&next[0..100]); + eprintln!("State F:"); + eprint_matrix(&state_f); + eprintln!("State G:"); + eprint_matrix(&next[0..100]); } // Sets up test for a given message and desired input bytelength @@ -124,20 +124,20 @@ where let witness = create_keccak_witness::(message); for r in 1..=24 { - print_witness::(&witness, r); + eprint_witness::(&witness, r); } let mut hash = vec![]; let hash_row = witness[0].len() - 2; // Hash row is dummy row - println!(); - println!("----------------------------------------"); - print!("Hash: "); + eprintln!(); + eprintln!("----------------------------------------"); + eprint!("Hash: "); for b in 0..32 { hash.push(FieldHelpers::to_bytes(&witness[200 + b][hash_row])[0]); - print!("{:02x}", hash[b]); + eprint!("{:02x}", hash[b]); } - println!(); - println!(); + eprintln!(); + eprintln!(); BigUint::from_bytes_be(&hash) } From 1e8657902488442726f181c9e9313edba6c2d885 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 14:14:22 +0100 Subject: [PATCH 148/173] rename test_keccak for setup_keccak_test --- kimchi/src/tests/keccak.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kimchi/src/tests/keccak.rs b/kimchi/src/tests/keccak.rs index 267dd7074e..cb4a56bc2f 100644 --- a/kimchi/src/tests/keccak.rs +++ b/kimchi/src/tests/keccak.rs @@ -110,7 +110,7 @@ fn eprint_witness(witness: &[Vec; KECCAK_COLS], round: usize) { } // Sets up test for a given message and desired input bytelength -fn test_keccak(message: BigUint) -> BigUint +fn setup_keccak_test(message: BigUint) -> BigUint where G::BaseField: PrimeField, { @@ -145,7 +145,7 @@ where #[test] // Tests a random block of 1080 bits fn test_random_block() { - let expected_random = test_keccak::( + let expected_random = setup_keccak_test::( BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126"), ); let hash_random = @@ -156,7 +156,7 @@ fn test_random_block() { #[test] // Test hash of message zero with 1 byte input length fn test_zero() { - let expected1 = test_keccak::(BigUint::from_bytes_be(&[0x00])); + let expected1 = setup_keccak_test::(BigUint::from_bytes_be(&[0x00])); let hash1 = BigUint::from_hex("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"); assert_eq!(expected1, hash1); @@ -165,7 +165,7 @@ fn test_zero() { #[test] // Test hash of message using 3 blocks fn test_blocks() { - let expected_3blocks = test_keccak::(BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f")); + let expected_3blocks = setup_keccak_test::(BigUint::from_hex("832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f423423a214325d13523aadb21414124aaadf32523126832588523900cca2ea9b8c0395d295aa39f9a9285a982b71cc8475067a8175f38f235a2234abc982a2dfaaddff2895a28598021895206a733a22bccd21f124df1413858a8f9a1134df285a888b099a8c2235eecdf2345f3afd32f3ae323526689172850672938104892357aad32523523f")); let hash_3blocks = BigUint::from_hex("7e369e1a4362148fca24c67c76f14dbe24b75c73e9b0efdb8c46056c8514287e"); assert_eq!(expected_3blocks, hash_3blocks); From 00279f41ee66bc9ba591aa89b45babdf650a8e3e Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 16:55:32 +0100 Subject: [PATCH 149/173] separate tests in individual smaller tests, add more coverage, and include PBT --- kimchi/src/circuits/polynomials/keccak/mod.rs | 204 ++++++++++++++---- .../circuits/polynomials/keccak/witness.rs | 9 +- 2 files changed, 164 insertions(+), 49 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 69a4c801a8..606f700bbb 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -116,6 +116,14 @@ pub(crate) fn pad(message: &[u8]) -> Vec { padded } +/// Returns the expansion of the 4 dense decomposed quarters of a word +pub(crate) fn sparse(word: u64) -> Vec { + decompose(word) + .iter() + .map(|q| expand(*q)) + .collect::>() +} + /// From each quarter in sparse representation, it computes its 4 resets. /// The resulting vector contains 4 times as many elements as the input. /// The output is placed in the vector as [reset0, reset1, reset2, reset3] @@ -140,7 +148,7 @@ pub(crate) fn reset(shifts: &[u64]) -> Vec { .collect::>() } -/// From a reset0 state, obtain the corresponding 16-bit dense terms +/// From a canonical expanded state, obtain the corresponding 16-bit dense terms pub(crate) fn collapse(state: &[u64]) -> Vec { let mut dense = vec![]; for reset in state { @@ -188,69 +196,179 @@ pub(crate) fn padded_length(bytelength: usize) -> usize { #[cfg(test)] mod tests { + use rand::{rngs::StdRng, Rng}; + use rand_core::SeedableRng; + use super::*; + const RNG_SEED: [u8; 32] = [ + 211, 31, 143, 75, 29, 255, 0, 126, 237, 193, 86, 160, 1, 90, 131, 221, 186, 168, 4, 95, 50, + 48, 89, 29, 13, 250, 215, 172, 130, 24, 164, 162, + ]; + #[test] + // Shows that the expansion of the 16-bit dense quarters into 64-bit sparse quarters + // corresponds to the binary representation of the 16-bit dense quarter. fn test_bitwise_sparse_representation() { assert_eq!(expand(0xFFFF), 0x1111111111111111); + assert_eq!(expand(0x0000), 0x0000000000000000); + assert_eq!(expand(0x1234), 0x0001001000110100) + } - let word_a: u64 = 0x70d324ac9215fd8e; - let dense_a = decompose(word_a); - let expected_dense_a = [0xfd8e, 0x9215, 0x24ac, 0x70d3]; + #[test] + // Tests that composing and decomposition are the inverse of each other, + // and the order of the quarters is the desired one. + fn test_compose_decompose() { + let word: u64 = 0x70d324ac9215fd8e; + let dense = decompose(word); + let expected_dense = [0xfd8e, 0x9215, 0x24ac, 0x70d3]; for i in 0..QUARTERS { - assert_eq!(dense_a[i], expected_dense_a[i]); + assert_eq!(dense[i], expected_dense[i]); } - assert_eq!(word_a, compose(&dense_a)); - - let sparse_a = dense_a.iter().map(|x| expand(*x)).collect::>(); - let expected_sparse_a: Vec = vec![ - 0x1111110110001110, - 0x1001001000010101, - 0x10010010101100, - 0x111000011010011, + assert_eq!(word, compose(&dense)); + } + + #[test] + // Tests that expansion works as expected with one quarter word + fn test_quarter_expansion() { + let quarter: u16 = 0b01011010111011011; // 0xB5DB + let expected_expansion = 0b0001000000010001000000010000000100010001000000010001000000010001; // 0x01011010111011011 + assert_eq!(expected_expansion, expand(quarter as u64)); + } + + #[test] + // Tests that expansion of decomposed quarters works as expected + fn test_sparse() { + let word: u64 = 0x1234567890abcdef; + let sparse = sparse(word); + let expected_sparse: Vec = vec![ + 0x1100110111101111, // 0xcdef + 0x1001000010101011, // 0x90ab + 0x0101011001111000, // 0x5678 + 0x0001001000110100, // 0x1234 ]; for i in 0..QUARTERS { - assert_eq!(sparse_a[i], expected_sparse_a[i]); + assert_eq!(sparse[i], expected_sparse[i]); } + } + + #[test] + // Tests that the shifts are computed correctly + fn test_shifts() { + let rng = &mut StdRng::from_seed(RNG_SEED); + let word: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let sparse = sparse(word); + let shifts = shift(&sparse); + for i in 0..QUARTERS { + assert_eq!( + sparse[i], + shifts[i] + shifts[4 + i] * 2 + shifts[8 + i] * 4 + shifts[12 + i] * 8 + ) + } + } - let word_b: u64 = 0x11c76438a7f9e94d; - let dense_b = decompose(word_b); - let sparse_b = dense_b.iter().map(|x| expand(*x)).collect::>(); + #[test] + // Checks that reset function returns shift0, as the first positions of the shifts vector + fn test_reset() { + let rng = &mut StdRng::from_seed(RNG_SEED); + let word: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let shifts = shift(&sparse(word)); + let reset = reset(&shifts); + assert_eq!(reset.len(), 4); + assert_eq!(shifts.len(), 16); + for i in 0..QUARTERS { + assert_eq!(reset[i], shifts[i]) + } + } - let xor_ab: u64 = word_a ^ word_b; - assert_eq!(xor_ab, 0x6114409435ec14c3); + #[test] + // Checks that one can obtain the original word from the resets of the expanded word + fn test_collapse() { + let rng = &mut StdRng::from_seed(RNG_SEED); + let word: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let dense = compose(&collapse(&reset(&shift(&sparse(word))))); + assert_eq!(word, dense); + } - let sparse_xor = decompose(xor_ab) + #[test] + // Checks that concatenating the maximum number of carries (15 per bit) result + // in the same original dense word, and just one more carry results in a different word + fn test_max_carries() { + let rng = &mut StdRng::from_seed(RNG_SEED); + let word: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let carries = 0xEEEE; + // add a few carry bits to the canonical representation + let mut sparse = sparse(word) .iter() - .map(|x| expand(*x)) + .map(|x| *x + carries) .collect::>(); - let real_sparse_xor = [ - 0x1010011000011, - 0x11010111101100, - 0x100000010010100, - 0x110000100010100, - ]; - for i in 0..QUARTERS { - assert_eq!(sparse_xor[i], real_sparse_xor[i]); - } + let dense = compose(&collapse(&reset(&shift(&sparse)))); + assert_eq!(word, dense); + + sparse[0] += 1; + let wrong_dense = compose(&collapse(&reset(&shift(&sparse)))); + assert_ne!(word, wrong_dense); + } + + #[test] + // Tests that the XOR can be represented in the 4i-th + // positions of the addition of sparse representations + fn test_sparse_xor() { + let rng = &mut StdRng::from_seed(RNG_SEED); + let a: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let b: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let xor = a ^ b; + + let sparse_a = sparse(a); + let sparse_b = sparse(b); - let sparse_sum_ab = sparse_a + // compute xor as sum of a and b + let sparse_sum = sparse_a .iter() .zip(sparse_b.iter()) .map(|(a, b)| a + b) .collect::>(); - let shifts_sum_ab = shift(&sparse_sum_ab); - let reset_sum_ab = reset(&shifts_sum_ab); - assert_eq!(sparse_xor, reset_sum_ab); + let reset_sum = reset(&shift(&sparse_sum)); - for i in 0..QUARTERS { - assert_eq!( - sparse_sum_ab[i], - shifts_sum_ab[i] - + shifts_sum_ab[4 + i] * 2 - + shifts_sum_ab[8 + i] * 4 - + shifts_sum_ab[12 + i] * 8 - ) - } + assert_eq!(sparse(xor), reset_sum); + } + + #[test] + // Tests that the AND can be represented in the (4i+1)-th positions of the + // addition of canonical sparse representations + fn test_sparse_and() { + let rng = &mut StdRng::from_seed(RNG_SEED); + let a: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let b: u64 = rng.gen_range(0..2u128.pow(64)) as u64; + let and = a & b; + + let sparse_a = sparse(a); + let sparse_b = sparse(b); + + // compute and as carries of sum of a and b + let sparse_sum = sparse_a + .iter() + .zip(sparse_b.iter()) + .map(|(a, b)| a + b) + .collect::>(); + let carries_sum = &shift(&sparse_sum)[4..8]; + + assert_eq!(sparse(and), carries_sum); + } + + #[test] + // Tests that the NOT can be represented as subtraction with the expansion of + // the 16-bit dense quarter. + fn test_sparse_not() { + let rng = &mut StdRng::from_seed(RNG_SEED); + let word = rng.gen_range(0..2u64.pow(16)); + let expanded = expand(word); + + // compute not as subtraction with expand all ones + let all_ones = 0xFFFF; + let not = all_ones - word; + let sparse_not = expand(all_ones) - expanded; + + assert_eq!(not, collapse(&[sparse_not])[0]); } } diff --git a/kimchi/src/circuits/polynomials/keccak/witness.rs b/kimchi/src/circuits/polynomials/keccak/witness.rs index e050841313..9f272c8736 100644 --- a/kimchi/src/circuits/polynomials/keccak/witness.rs +++ b/kimchi/src/circuits/polynomials/keccak/witness.rs @@ -15,8 +15,8 @@ use ark_ff::PrimeField; use num_bigint::BigUint; use super::{ - bytestring, collapse, expand, pad, reset, shift, CAPACITY_IN_BYTES, DIM, KECCAK_COLS, OFF, - QUARTERS, RATE_IN_BYTES, + bytestring, collapse, expand, pad, reset, shift, sparse, CAPACITY_IN_BYTES, DIM, KECCAK_COLS, + OFF, QUARTERS, RATE_IN_BYTES, }; type Layout = Vec, COLUMNS>>>; @@ -302,10 +302,7 @@ struct Iota { impl Iota { fn create(state_f: Vec, round: usize) -> Self { - let rc = decompose(RC[round]) - .iter() - .map(|x| expand(*x)) - .collect::>(); + let rc = sparse(RC[round]); let mut state_g = state_f.clone(); for (i, c) in rc.iter().enumerate() { state_g[i] = state_f[i] + *c; From bf641654e5426b628ebb6f7f4ae313ee6c2c0dad Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 16:57:10 +0100 Subject: [PATCH 150/173] clarification about length of shifts --- kimchi/src/circuits/polynomials/keccak/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 606f700bbb..bbb221bc61 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -140,6 +140,7 @@ pub(crate) fn shift(state: &[u64]) -> Vec { } /// From a vector of shifts, resets the underlying value returning only shift0 +/// Note that shifts is always a vector whose length is a multiple of 4. pub(crate) fn reset(shifts: &[u64]) -> Vec { shifts .iter() From 8f33db51c39a79064ec852e1d0b0c3745632e03f Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 17:08:24 +0100 Subject: [PATCH 151/173] getting rid of flatten/copy in shifts and reset --- kimchi/src/circuits/polynomials/keccak/mod.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index bbb221bc61..ac0d4302a1 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -126,26 +126,26 @@ pub(crate) fn sparse(word: u64) -> Vec { /// From each quarter in sparse representation, it computes its 4 resets. /// The resulting vector contains 4 times as many elements as the input. -/// The output is placed in the vector as [reset0, reset1, reset2, reset3] +/// The output is placed in the vector as [shift0, shift1, shift2, shift3] pub(crate) fn shift(state: &[u64]) -> Vec { - let mut shifts = vec![vec![]; QUARTERS]; + let n = state.len(); + let mut shifts = vec![0; QUARTERS * n]; let aux = expand(0xFFFF); - for term in state { - shifts[0].push(aux & term); // shift0 = reset0 - shifts[1].push(((aux << 1) & term) / 2); // shift1 = reset1/2 - shifts[2].push(((aux << 2) & term) / 4); // shift2 = reset2/4 - shifts[3].push(((aux << 3) & term) / 8); // shift3 = reset3/8 + for (i, term) in state.iter().enumerate() { + shifts[i] = aux & term; // shift0 = reset0 + shifts[n + i] = ((aux << 1) & term) / 2; // shift1 = reset1/2 + shifts[2 * n + i] = ((aux << 2) & term) / 4; // shift2 = reset2/4 + shifts[3 * n + i] = ((aux << 3) & term) / 8; // shift3 = reset3/8 } - shifts.iter().flatten().copied().collect() + shifts } /// From a vector of shifts, resets the underlying value returning only shift0 /// Note that shifts is always a vector whose length is a multiple of 4. pub(crate) fn reset(shifts: &[u64]) -> Vec { - shifts + shifts[0..shifts.len() / QUARTERS] .iter() - .copied() - .take(shifts.len() / QUARTERS) + .map(|&x| x) .collect::>() } From 9cac8668ecbd18ec0d1c0d86ef0b57e5b8487ac8 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 17:17:03 +0100 Subject: [PATCH 152/173] add test for padding length and clarify docs --- kimchi/src/circuits/polynomials/keccak/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index ac0d4302a1..e63bc9ca51 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -189,7 +189,9 @@ pub(crate) fn expand_state(state: &[u8]) -> Vec { expanded } -/// On input a length, returns the smallest multiple of RATE that is greater than the bytelength +/// On input a length, returns the smallest multiple of RATE_IN_BYTES that is greater than the bytelength. +/// That means that if the input has a length that is a multiple of the RATE_IN_BYTES, then +/// it needs to add one whole block of RATE_IN_BYTES bytes just for padding purposes. pub(crate) fn padded_length(bytelength: usize) -> usize { (bytelength / RATE_IN_BYTES + 1) * RATE_IN_BYTES } @@ -372,4 +374,16 @@ mod tests { assert_eq!(not, collapse(&[sparse_not])[0]); } + + #[test] + // Checks that the padding length is correctly computed + fn test_pad_length() { + assert_eq!(padded_length(0), RATE_IN_BYTES); + assert_eq!(padded_length(1), RATE_IN_BYTES); + assert_eq!(padded_length(RATE_IN_BYTES - 1), RATE_IN_BYTES); + // If input is already a multiple of RATE bytes, it needs to add a whole new block just for padding + assert_eq!(padded_length(RATE_IN_BYTES), 2 * RATE_IN_BYTES); + assert_eq!(padded_length(RATE_IN_BYTES * 2 - 1), 2 * RATE_IN_BYTES); + assert_eq!(padded_length(RATE_IN_BYTES * 2), 3 * RATE_IN_BYTES); + } } From f61500d2a5ba6a290c2eadcc2e5aef7060eb8f5c Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 17:26:16 +0100 Subject: [PATCH 153/173] write pad function in functional friendly format and add test for it --- kimchi/src/circuits/polynomials/keccak/mod.rs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index e63bc9ca51..4a67cc7c54 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -104,18 +104,6 @@ pub(crate) fn expand_word>(word: u64) -> Vec { .collect::>() } -/// Pads the message with the 10*1 rule until reaching a length that is a multiple of the rate -pub(crate) fn pad(message: &[u8]) -> Vec { - let mut padded = message.to_vec(); - padded.push(0x01); - while padded.len() % 136 != 0 { - padded.push(0x00); - } - let last = padded.len() - 1; - padded[last] += 0x80; - padded -} - /// Returns the expansion of the 4 dense decomposed quarters of a word pub(crate) fn sparse(word: u64) -> Vec { decompose(word) @@ -196,6 +184,20 @@ pub(crate) fn padded_length(bytelength: usize) -> usize { (bytelength / RATE_IN_BYTES + 1) * RATE_IN_BYTES } +/// Pads the message with the 10*1 rule until reaching a length that is a multiple of the rate +pub(crate) fn pad(message: &[u8]) -> Vec { + let msg_len = message.len(); + let pad_len = padded_length(msg_len); + let mut padded = vec![0; pad_len]; + for (i, byte) in message.iter().enumerate() { + padded[i] = *byte; + } + padded[msg_len] = 0x01; + padded[pad_len - 1] += 0x80; + + padded +} + #[cfg(test)] mod tests { @@ -386,4 +388,19 @@ mod tests { assert_eq!(padded_length(RATE_IN_BYTES * 2 - 1), 2 * RATE_IN_BYTES); assert_eq!(padded_length(RATE_IN_BYTES * 2), 3 * RATE_IN_BYTES); } + + #[test] + // Checks that the padding is correctly computed + fn test_pad() { + let message = vec![0xFF; RATE_IN_BYTES - 1]; + let padded = pad(&message); + assert_eq!(padded.len(), RATE_IN_BYTES); + assert_eq!(padded[padded.len() - 1], 0x81); + + let message = vec![0x01; RATE_IN_BYTES]; + let padded = pad(&message); + assert_eq!(padded.len(), 2 * RATE_IN_BYTES); + assert_eq!(padded[message.len()], 0x01); + assert_eq!(padded[padded.len() - 1], 0x80); + } } From 0855d7e9580ef4e264045f2dce282d205a5e2899 Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 17:30:35 +0100 Subject: [PATCH 154/173] refactor collapse in a more functional friendly manner --- kimchi/src/circuits/polynomials/keccak/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index 4a67cc7c54..ba2009570e 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -139,11 +139,10 @@ pub(crate) fn reset(shifts: &[u64]) -> Vec { /// From a canonical expanded state, obtain the corresponding 16-bit dense terms pub(crate) fn collapse(state: &[u64]) -> Vec { - let mut dense = vec![]; - for reset in state { - dense.push(u64::from_str_radix(&format!("{:x}", reset), 2).unwrap()); - } - dense + state + .iter() + .map(|&reset| u64::from_str_radix(&format!("{:x}", reset), 2).unwrap()) + .collect::>() } /// Outputs the state into dense quarters of 16-bits each in little endian order From 319c2683ea53b7a95242de5234a8c9c1a811d96a Mon Sep 17 00:00:00 2001 From: querolita Date: Tue, 14 Nov 2023 19:55:26 +0100 Subject: [PATCH 155/173] address clippy --- kimchi/src/circuits/polynomials/keccak/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kimchi/src/circuits/polynomials/keccak/mod.rs b/kimchi/src/circuits/polynomials/keccak/mod.rs index ba2009570e..dc9468a12a 100644 --- a/kimchi/src/circuits/polynomials/keccak/mod.rs +++ b/kimchi/src/circuits/polynomials/keccak/mod.rs @@ -131,10 +131,7 @@ pub(crate) fn shift(state: &[u64]) -> Vec { /// From a vector of shifts, resets the underlying value returning only shift0 /// Note that shifts is always a vector whose length is a multiple of 4. pub(crate) fn reset(shifts: &[u64]) -> Vec { - shifts[0..shifts.len() / QUARTERS] - .iter() - .map(|&x| x) - .collect::>() + shifts[0..shifts.len() / QUARTERS].to_vec() } /// From a canonical expanded state, obtain the corresponding 16-bit dense terms From fb33252d5c699470629f85e381da6cb3b9d6c67b Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Nov 2023 22:34:25 +0100 Subject: [PATCH 156/173] Use nightly 2023-10-10 for doc --- .github/workflows/gh-page.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-page.yml b/.github/workflows/gh-page.yml index 7077069ded..3c77893c7a 100644 --- a/.github/workflows/gh-page.yml +++ b/.github/workflows/gh-page.yml @@ -7,7 +7,9 @@ on: env: OCAML_VERSION: "4.14.0" - RUST_TOOLCHAIN_VERSION: "nightly" + # This version has been chosen randomly. It seems that with 2023-11-16, it is + # broken. The compiler crashes. Feel free to pick any newer working version. + RUST_TOOLCHAIN_VERSION: "nightly-2023-10-10" jobs: release: @@ -42,7 +44,7 @@ jobs: - name: Build Rust Documentation run: | eval $(opam env) - RUSTDOCFLAGS="--enable-index-page -Zunstable-options" cargo +nightly doc --all --no-deps + RUSTDOCFLAGS="--enable-index-page -Zunstable-options" cargo doc --all --no-deps - name: Build the mdbook run: | From ed64ecb998124faca7bd73d356477ac01ae41a11 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Nov 2023 22:46:02 +0100 Subject: [PATCH 157/173] Build doc on any branch, but deploy only on master --- .github/workflows/gh-page.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gh-page.yml b/.github/workflows/gh-page.yml index 3c77893c7a..987b27ce2a 100644 --- a/.github/workflows/gh-page.yml +++ b/.github/workflows/gh-page.yml @@ -3,7 +3,7 @@ name: Deploy Specifications & Docs to GitHub Pages on: push: branches: - - master + - "*" env: OCAML_VERSION: "4.14.0" @@ -59,6 +59,7 @@ jobs: - name: Deploy uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/master' with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./book/book/html From aff44544d8ba45710fa7661beaa9c24755f9ec66 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Tue, 7 Nov 2023 10:34:00 +0100 Subject: [PATCH 158/173] cannon: deserialize preimage key into [u8;32] --- optimism/src/cannon.rs | 26 +++++++++++++++++++++++--- optimism/src/mips/witness.rs | 4 ++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 1337bdbd40..9b4b7f0091 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -35,8 +35,8 @@ where #[derive(Serialize, Deserialize, Debug)] pub struct State { pub memory: Vec, - #[serde(rename = "preimageKey")] - pub preimage_key: String, + #[serde(rename = "preimageKey", deserialize_with = "to_preimage_key")] + pub preimage_key: [u8; 32], #[serde(rename = "preimageOffset")] pub preimage_offset: u32, pub pc: u32, @@ -52,6 +52,26 @@ pub struct State { pub last_hint: Option>, } +fn to_preimage_key<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> +where + D: Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer)?; + let parts = s.split("x").collect::>(); + let hex_value: &str = if parts.len() == 1 { + parts[0] + } else { + assert!(parts.len() == 2); + parts[1] + }; + assert!(hex_value.len() == 64); // check this is an hexadecimal representation of 32 bytes + let h = hex::decode(hex_value).unwrap_or_else(|_| panic!("Could not hex decode {hex_value}")); + let res: [u8; 32] = h + .try_into() + .unwrap_or_else(|_| panic!("Could not cast vector into 32 bytes array")); + Ok(res) +} + #[derive(Clone, Debug, PartialEq)] pub enum StepFrequency { Never, @@ -90,7 +110,7 @@ impl ToString for State { // A very debatable and incomplete, but serviceable, `to_string` implementation. fn to_string(&self) -> String { format!( - "memory_size (length): {}\nfirst page size: {}\npreimage key: {}\npreimage offset:{}\npc: {}\nlo: {}\nhi: {}\nregisters:{:#?} ", + "memory_size (length): {}\nfirst page size: {}\npreimage key: {:#?}\npreimage offset:{}\npc: {}\nlo: {}\nhi: {}\nregisters:{:#?} ", self.memory.len(), self.memory[0].data.len(), self.preimage_key, diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index ab0d3fec35..bf233f5dd5 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -23,7 +23,7 @@ pub const SCRATCH_SIZE: usize = 25; pub struct SyscallEnv { pub heap: u32, // Heap pointer (actually unused in Cannon as of [2023-10-18]) pub preimage_offset: u32, - pub preimage_key: Vec, + pub preimage_key: [u8; 32], pub last_hint: Option>, } @@ -31,7 +31,7 @@ impl SyscallEnv { pub fn create(state: &State) -> Self { SyscallEnv { heap: state.heap, - preimage_key: state.preimage_key.as_bytes().to_vec(), // Might not be correct + preimage_key: state.preimage_key, preimage_offset: state.preimage_offset, last_hint: state.last_hint.clone(), } From 4e9d5e656ea64dfbb480c570af862e47e2ca9680 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Tue, 7 Nov 2023 13:53:20 +0100 Subject: [PATCH 159/173] cannon: refine serialization - implement FromStr trait - test implementation --- optimism/src/cannon.rs | 87 +++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 9b4b7f0091..1708048e26 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -52,24 +52,65 @@ pub struct State { pub last_hint: Option>, } +#[derive(Debug, PartialEq, Eq)] +pub struct ParsePreimageKeyError(String); + +#[derive(Debug, PartialEq)] +pub struct PreimageKey([u8; 32]); + +use std::str::FromStr; + +impl FromStr for PreimageKey { + type Err = ParsePreimageKeyError; + + fn from_str(s: &str) -> Result { + let parts = s.split('x').collect::>(); + let hex_value: &str = if parts.len() == 1 { + parts[0] + } else { + if parts.len() != 2 { + return Err(ParsePreimageKeyError( + format!("Badly structured value to convert {s}").to_string(), + )); + }; + parts[1] + }; + // We only handle a hexadecimal representations of exactly 32 bytes (no auto-padding) + if hex_value.len() == 64 { + hex::decode(hex_value).map_or_else( + |_| { + Err(ParsePreimageKeyError( + format!("Could not hex decode {hex_value}").to_string(), + )) + }, + |h| { + h.clone().try_into().map_or_else( + |_| { + Err(ParsePreimageKeyError( + format!("Could not cast vector {:#?} into 32 bytes array", h) + .to_string(), + )) + }, + |res| Ok(PreimageKey(res)), + ) + }, + ) + } else { + Err(ParsePreimageKeyError( + format!("{hex_value} is not 32-bytes long").to_string(), + )) + } + } +} + fn to_preimage_key<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> where D: Deserializer<'de>, { let s: String = Deserialize::deserialize(deserializer)?; - let parts = s.split("x").collect::>(); - let hex_value: &str = if parts.len() == 1 { - parts[0] - } else { - assert!(parts.len() == 2); - parts[1] - }; - assert!(hex_value.len() == 64); // check this is an hexadecimal representation of 32 bytes - let h = hex::decode(hex_value).unwrap_or_else(|_| panic!("Could not hex decode {hex_value}")); - let res: [u8; 32] = h - .try_into() - .unwrap_or_else(|_| panic!("Could not cast vector into 32 bytes array")); - Ok(res) + let p = PreimageKey::from_str(s.as_str()) + .unwrap_or_else(|_| panic!("Parsing {s} as preimage key failed")); + Ok(p.0) } #[derive(Clone, Debug, PartialEq)] @@ -332,4 +373,24 @@ mod tests { ); assert_eq!(meta.find_address_symbol(42), None); } + + #[test] + fn test_parse_preimagekey() { + assert_eq!( + PreimageKey::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ), + Ok(PreimageKey([0; 32])) + ); + assert_eq!( + PreimageKey::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000001" + ), + Ok(PreimageKey([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1 + ])) + ); + assert!(PreimageKey::from_str("0x01").is_err()); + } } From b46a860e076a18f0518ce556f8c25b282cc57b49 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 15 Nov 2023 14:42:50 +0000 Subject: [PATCH 160/173] Track next_instruction_pointer --- optimism/src/cannon.rs | 2 +- optimism/src/mips/witness.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 1337bdbd40..850abeb127 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -41,7 +41,7 @@ pub struct State { pub preimage_offset: u32, pub pc: u32, #[serde(rename = "nextPC")] - next_pc: u32, // + pub next_pc: u32, pub lo: u32, pub hi: u32, pub heap: u32, diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index ab0d3fec35..b7bb702e36 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -46,6 +46,7 @@ pub struct Env { pub registers: Registers, pub registers_write_index: Registers, pub instruction_pointer: u32, + pub next_instruction_pointer: u32, pub scratch_state_idx: usize, pub scratch_state: [Fp; SCRATCH_SIZE], pub halt: bool, @@ -94,6 +95,7 @@ fn memory_size(total: usize) -> String { impl Env { pub fn create(page_size: usize, state: State) -> Self { let initial_instruction_pointer = state.pc; + let next_instruction_pointer = state.next_pc; let syscall_env = SyscallEnv::create(&state); @@ -130,6 +132,7 @@ impl Env { registers: initial_registers.clone(), registers_write_index: Registers::default(), instruction_pointer: initial_instruction_pointer, + next_instruction_pointer, scratch_state_idx: 0, scratch_state: fresh_scratch_state(), halt: state.exited, From 0823cb22f3283afb5657652fe22062177bc0b119 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 22 Nov 2023 21:44:47 +0100 Subject: [PATCH 161/173] Basic support for preimage oracle process within witness environment --- Cargo.lock | 33 +++++++++++++ optimism/Cargo.toml | 2 + optimism/src/cannon.rs | 5 ++ optimism/src/lib.rs | 1 + optimism/src/main.rs | 13 ++--- optimism/src/mips/witness.rs | 6 ++- optimism/src/preimage_oracle.rs | 87 +++++++++++++++++++++++++++++++++ 7 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 optimism/src/preimage_oracle.rs diff --git a/Cargo.lock b/Cargo.lock index 5bf22e44bf..885f193826 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -557,6 +557,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "command-fds" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f190f3c954f7bca3c6296d0ec561c739bdbe6c7e990294ed168d415f6e1b5b01" +dependencies = [ + "nix", + "thiserror", +] + [[package]] name = "comrak" version = "0.13.2" @@ -1316,6 +1326,7 @@ dependencies = [ "ark-poly", "base64", "clap 4.4.6", + "command-fds", "elf", "env_logger", "groupmap", @@ -1325,6 +1336,7 @@ dependencies = [ "log", "mina-curves", "mina-poseidon", + "os_pipe", "poly-commitment", "regex", "rmp-serde", @@ -1617,6 +1629,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.3.3", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1843,6 +1866,16 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "os_pipe" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "os_str_bytes" version = "6.5.1" diff --git a/optimism/Cargo.toml b/optimism/Cargo.toml index 37fe645aee..d8937dfd48 100644 --- a/optimism/Cargo.toml +++ b/optimism/Cargo.toml @@ -35,3 +35,5 @@ strum = "0.24.0" strum_macros = "0.24.0" log = "0.4.20" env_logger = "0.10.0" +command-fds = "0.2.3" +os_pipe = "1.1.4" diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 290e3f5728..38764685f3 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -394,3 +394,8 @@ mod tests { assert!(PreimageKey::from_str("0x01").is_err()); } } + +pub const HINT_CLIENT_READ_FD: i32 = 3; +pub const HINT_CLIENT_WRITE_FD: i32 = 4; +pub const PREIMAGE_CLIENT_READ_FD: i32 = 5; +pub const PREIMAGE_CLIENT_WRITE_FD: i32 = 6; diff --git a/optimism/src/lib.rs b/optimism/src/lib.rs index da4f2ad95d..e63351e7fe 100644 --- a/optimism/src/lib.rs +++ b/optimism/src/lib.rs @@ -1,2 +1,3 @@ pub mod cannon; pub mod mips; +pub mod preimage_oracle; diff --git a/optimism/src/main.rs b/optimism/src/main.rs index 1b07d961f7..977bbfb256 100644 --- a/optimism/src/main.rs +++ b/optimism/src/main.rs @@ -2,6 +2,7 @@ use clap::{arg, value_parser, Arg, ArgAction, Command}; use kimchi_optimism::{ cannon::{self, Meta, Start, State, VmConfiguration}, mips::witness, + preimage_oracle::PreImageOracle, }; use std::{fs::File, io::BufReader, process::ExitCode}; @@ -134,21 +135,15 @@ pub fn main() -> ExitCode { ) }); - if let Some(host_program) = &configuration.host { - println!("Launching host program {}", host_program.name); - - let _child = std::process::Command::new(&host_program.name) - .args(&host_program.arguments) - .spawn() - .expect("Could not spawn host process"); - }; + let mut po = PreImageOracle::create(&configuration.host); + let _child = po.start(); // Initialize some data used for statistical computations let start = Start::create(state.step as usize); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - let mut env = witness::Env::::create(cannon::PAGE_SIZE, state); + let mut env = witness::Env::::create(cannon::PAGE_SIZE, state, po); while !env.halt { env.step(configuration.clone(), &meta, &start); diff --git a/optimism/src/mips/witness.rs b/optimism/src/mips/witness.rs index 1a82fe955f..1cb359f773 100644 --- a/optimism/src/mips/witness.rs +++ b/optimism/src/mips/witness.rs @@ -7,6 +7,7 @@ use crate::{ interpreter::{self, ITypeInstruction, Instruction, JTypeInstruction, RTypeInstruction}, registers::Registers, }, + preimage_oracle::PreImageOracle, }; use ark_ff::Field; use log::info; @@ -38,7 +39,6 @@ impl SyscallEnv { } } -#[derive(Clone)] pub struct Env { pub instruction_counter: usize, pub memory: Vec<(u32, Vec)>, @@ -51,6 +51,7 @@ pub struct Env { pub scratch_state: [Fp; SCRATCH_SIZE], pub halt: bool, pub syscall_env: SyscallEnv, + pub preimage_oracle: PreImageOracle, } fn fresh_scratch_state() -> [Fp; N] { @@ -93,7 +94,7 @@ fn memory_size(total: usize) -> String { } impl Env { - pub fn create(page_size: usize, state: State) -> Self { + pub fn create(page_size: usize, state: State, preimage_oracle: PreImageOracle) -> Self { let initial_instruction_pointer = state.pc; let next_instruction_pointer = state.next_pc; @@ -137,6 +138,7 @@ impl Env { scratch_state: fresh_scratch_state(), halt: state.exited, syscall_env, + preimage_oracle, } } diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs new file mode 100644 index 0000000000..8c1385d0c9 --- /dev/null +++ b/optimism/src/preimage_oracle.rs @@ -0,0 +1,87 @@ +use crate::cannon::{ + HostProgram, HINT_CLIENT_READ_FD, HINT_CLIENT_WRITE_FD, PREIMAGE_CLIENT_READ_FD, + PREIMAGE_CLIENT_WRITE_FD, +}; +use command_fds::{CommandFdExt, FdMapping}; +use os_pipe::{PipeReader, PipeWriter}; +use std::os::fd::AsRawFd; +use std::process::{Child, Command}; +pub struct PreImageOracle { + pub cmd: Command, + pub oracle_client: RW, + pub hint_writer: RW, +} + +pub struct ReadWrite { + reader: R, + writer: W, +} + +pub struct RW(pub ReadWrite); + +impl RW { + pub fn create() -> Option { + let (reader, writer) = os_pipe::pipe().ok()?; + Some(RW(ReadWrite { reader, writer })) + } +} + +impl PreImageOracle { + pub fn create(hp_opt: &Option) -> PreImageOracle { + let host_program = hp_opt.as_ref().expect("No host program given"); + + let mut cmd = Command::new(host_program.name.to_string()); + cmd.args(&host_program.arguments); + + let p_client = RW::create().expect(""); + let p_oracle = RW::create().expect(""); + let h_client = RW::create().expect(""); + let h_oracle = RW::create().expect(""); + + // file descriptors 0, 1, 2 respectively correspond to the inherited stdin, + // stdout, stderr. + // We need to map 3, 4, 5, 6 in the child process + let RW(ReadWrite { + reader: h_reader, + writer: h_writer, + }) = h_oracle; + let RW(ReadWrite { + reader: p_reader, + writer: p_writer, + }) = p_oracle; + + // Use constant defined + cmd.fd_mappings(vec![ + FdMapping { + parent_fd: h_reader.as_raw_fd(), + child_fd: HINT_CLIENT_READ_FD, + }, + FdMapping { + parent_fd: h_writer.as_raw_fd(), + child_fd: HINT_CLIENT_WRITE_FD, + }, + FdMapping { + parent_fd: p_reader.as_raw_fd(), + child_fd: PREIMAGE_CLIENT_READ_FD, + }, + FdMapping { + parent_fd: p_writer.as_raw_fd(), + child_fd: PREIMAGE_CLIENT_WRITE_FD, + }, + ]) + .unwrap_or_else(|_| panic!("Could not map file descriptors to server process")); + + PreImageOracle { + cmd, + oracle_client: p_client, + hint_writer: h_client, + } + } + + pub fn start(&mut self) -> Child { + // Spawning inherits the current process's stdin/stdout/stderr descriptors + self.cmd + .spawn() + .expect("Could not spawn pre-image oracle process") + } +} From 7ca6211d525ce383d5b9d6dad305415a25bfc2c6 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Wed, 22 Nov 2023 23:13:10 +0100 Subject: [PATCH 162/173] Implement simple get_preimage --- optimism/src/preimage_oracle.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 8c1385d0c9..764dc6ed56 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -4,7 +4,9 @@ use crate::cannon::{ }; use command_fds::{CommandFdExt, FdMapping}; use os_pipe::{PipeReader, PipeWriter}; +use std::io::{Read, Write}; use std::os::fd::AsRawFd; + use std::process::{Child, Command}; pub struct PreImageOracle { pub cmd: Command, @@ -13,8 +15,8 @@ pub struct PreImageOracle { } pub struct ReadWrite { - reader: R, - writer: W, + pub reader: R, + pub writer: W, } pub struct RW(pub ReadWrite); @@ -30,7 +32,7 @@ impl PreImageOracle { pub fn create(hp_opt: &Option) -> PreImageOracle { let host_program = hp_opt.as_ref().expect("No host program given"); - let mut cmd = Command::new(host_program.name.to_string()); + let mut cmd = Command::new(&host_program.name); cmd.args(&host_program.arguments); let p_client = RW::create().expect(""); @@ -84,4 +86,26 @@ impl PreImageOracle { .spawn() .expect("Could not spawn pre-image oracle process") } + + pub fn get_preimage(&mut self, key: [u8; 32]) -> Vec { + let RW(ReadWrite { reader, writer }) = &mut self.oracle_client; + + let mut msg_key = vec![2_u8]; // Assumes Keccak Key + msg_key.extend_from_slice(&key[1..31]); + let _ = writer.write(&msg_key); + + let mut buf = [0_u8; 8]; + let _ = reader.read_exact(&mut buf); + + let length: u64 = u64::from_be_bytes(buf); + + let mut handle = reader.take(length); + + let mut v = vec![0_u8; length as usize]; + + let _ = handle.read(&mut v); + v + } + + pub fn hint(&mut self, _hint: Vec) {} } From 6b0252ee6e33da80f417e166ad0a920357965617 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Thu, 23 Nov 2023 16:27:17 +0100 Subject: [PATCH 163/173] Add type for preimage --- optimism/src/cannon.rs | 12 ++++++++++++ optimism/src/preimage_oracle.rs | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 38764685f3..59c26aba09 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -399,3 +399,15 @@ pub const HINT_CLIENT_READ_FD: i32 = 3; pub const HINT_CLIENT_WRITE_FD: i32 = 4; pub const PREIMAGE_CLIENT_READ_FD: i32 = 5; pub const PREIMAGE_CLIENT_WRITE_FD: i32 = 6; + +pub struct Preimage(Vec); + +impl Preimage { + pub fn create(v: Vec) -> Self { + Preimage(v) + } + + pub fn get(self) -> Vec { + self.0 + } +} diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 764dc6ed56..21bab42250 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -1,5 +1,5 @@ use crate::cannon::{ - HostProgram, HINT_CLIENT_READ_FD, HINT_CLIENT_WRITE_FD, PREIMAGE_CLIENT_READ_FD, + HostProgram, Preimage, HINT_CLIENT_READ_FD, HINT_CLIENT_WRITE_FD, PREIMAGE_CLIENT_READ_FD, PREIMAGE_CLIENT_WRITE_FD, }; use command_fds::{CommandFdExt, FdMapping}; @@ -87,7 +87,7 @@ impl PreImageOracle { .expect("Could not spawn pre-image oracle process") } - pub fn get_preimage(&mut self, key: [u8; 32]) -> Vec { + pub fn get_preimage(&mut self, key: [u8; 32]) -> Preimage { let RW(ReadWrite { reader, writer }) = &mut self.oracle_client; let mut msg_key = vec![2_u8]; // Assumes Keccak Key @@ -104,7 +104,7 @@ impl PreImageOracle { let mut v = vec![0_u8; length as usize]; let _ = handle.read(&mut v); - v + Preimage::create(v) } pub fn hint(&mut self, _hint: Vec) {} From 64b2a3acdf38ac4fdd7b963851092cec3cddec20 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Thu, 23 Nov 2023 16:34:06 +0100 Subject: [PATCH 164/173] preimage: add some documentation --- optimism/src/preimage_oracle.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 21bab42250..084126942c 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -87,6 +87,11 @@ impl PreImageOracle { .expect("Could not spawn pre-image oracle process") } + // The preimage protocol goes as follows + // 1. Ask for data through a key + // 2. Get the answers as a + // a. a 64-bit integer indicating the length of the actual data + // b. the preimage data, with a size of bits pub fn get_preimage(&mut self, key: [u8; 32]) -> Preimage { let RW(ReadWrite { reader, writer }) = &mut self.oracle_client; @@ -97,13 +102,14 @@ impl PreImageOracle { let mut buf = [0_u8; 8]; let _ = reader.read_exact(&mut buf); - let length: u64 = u64::from_be_bytes(buf); - + let length = u64::from_be_bytes(buf); let mut handle = reader.take(length); - let mut v = vec![0_u8; length as usize]; - let _ = handle.read(&mut v); + + // We should have read exactly bytes + assert_eq!(v.len(), length as usize); + Preimage::create(v) } From e83295119ddd27b6fb8b18b6037cbb0e458abace Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Fri, 24 Nov 2023 10:31:55 +0100 Subject: [PATCH 165/173] cannon: add hint datatype --- optimism/src/cannon.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/optimism/src/cannon.rs b/optimism/src/cannon.rs index 59c26aba09..2a60a21818 100644 --- a/optimism/src/cannon.rs +++ b/optimism/src/cannon.rs @@ -411,3 +411,15 @@ impl Preimage { self.0 } } + +pub struct Hint(Vec); + +impl Hint { + pub fn create(v: Vec) -> Self { + Hint(v) + } + + pub fn get(self) -> Vec { + self.0 + } +} From 22fa41ba868e39604137535f43a5d5bf757027d6 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Fri, 24 Nov 2023 10:39:25 +0100 Subject: [PATCH 166/173] Implement hint writing --- optimism/src/preimage_oracle.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 084126942c..8ea0d3f64e 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -1,6 +1,6 @@ use crate::cannon::{ - HostProgram, Preimage, HINT_CLIENT_READ_FD, HINT_CLIENT_WRITE_FD, PREIMAGE_CLIENT_READ_FD, - PREIMAGE_CLIENT_WRITE_FD, + Hint, HostProgram, Preimage, HINT_CLIENT_READ_FD, HINT_CLIENT_WRITE_FD, + PREIMAGE_CLIENT_READ_FD, PREIMAGE_CLIENT_WRITE_FD, }; use command_fds::{CommandFdExt, FdMapping}; use os_pipe::{PipeReader, PipeWriter}; @@ -113,5 +113,20 @@ impl PreImageOracle { Preimage::create(v) } - pub fn hint(&mut self, _hint: Vec) {} + pub fn hint(&mut self, hint: Hint) { + let mut hint_bytes = hint.get(); + let hint_length = hint_bytes.len(); + + let mut msg: Vec = vec![]; + msg.append(&mut u64::to_be_bytes(hint_length as u64).to_vec()); + msg.append(&mut hint_bytes); + + let RW(ReadWrite { reader, writer }) = &mut self.hint_writer; + + let _ = writer.write(&msg); + + // read single byte response + let mut buf = [0_u8]; + let _ = reader.read_exact(&mut buf); + } } From 1a0d2caa9f20f851fc2c01f88ce55507fa705872 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Fri, 24 Nov 2023 12:13:20 +0100 Subject: [PATCH 167/173] Some more documentation --- optimism/src/preimage_oracle.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 8ea0d3f64e..0b40e6fe10 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -89,7 +89,10 @@ impl PreImageOracle { // The preimage protocol goes as follows // 1. Ask for data through a key - // 2. Get the answers as a + // 2. Get the answers in the following format + // +------------+--------------------+ + // | length <8> | pre-image | + // +---------------------------------+ // a. a 64-bit integer indicating the length of the actual data // b. the preimage data, with a size of bits pub fn get_preimage(&mut self, key: [u8; 32]) -> Preimage { @@ -104,16 +107,26 @@ impl PreImageOracle { let length = u64::from_be_bytes(buf); let mut handle = reader.take(length); - let mut v = vec![0_u8; length as usize]; - let _ = handle.read(&mut v); + let mut preimage = vec![0_u8; length as usize]; + let _ = handle.read(&mut preimage); // We should have read exactly bytes - assert_eq!(v.len(), length as usize); + assert_eq!(preimage.len(), length as usize); - Preimage::create(v) + Preimage::create(preimage) } + // The hint protocol goes as follows: + // 1. Write a hint request with the following byte-stream format + // +------------+---------------+ + // | length <8> | hint | + // +----------------------------+ + // + // 2. Get back a single ack byte informing the the hint has been processed. pub fn hint(&mut self, hint: Hint) { + let RW(ReadWrite { reader, writer }) = &mut self.hint_writer; + + // Write hint request let mut hint_bytes = hint.get(); let hint_length = hint_bytes.len(); @@ -121,11 +134,9 @@ impl PreImageOracle { msg.append(&mut u64::to_be_bytes(hint_length as u64).to_vec()); msg.append(&mut hint_bytes); - let RW(ReadWrite { reader, writer }) = &mut self.hint_writer; - let _ = writer.write(&msg); - // read single byte response + // Read single byte acknowledgment response let mut buf = [0_u8]; let _ = reader.read_exact(&mut buf); } From 8ea84f15ac2afc727eb1d4ed5df7ae8547779a0d Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Fri, 24 Nov 2023 16:44:57 +0100 Subject: [PATCH 168/173] preimage_oracle: nicer failure messages Just in case --- optimism/src/preimage_oracle.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 0b40e6fe10..986657a2c5 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -35,10 +35,10 @@ impl PreImageOracle { let mut cmd = Command::new(&host_program.name); cmd.args(&host_program.arguments); - let p_client = RW::create().expect(""); - let p_oracle = RW::create().expect(""); - let h_client = RW::create().expect(""); - let h_oracle = RW::create().expect(""); + let p_client = RW::create().expect("Could not create preimage client channel"); + let p_oracle = RW::create().expect("Could not create preimage oracle channel"); + let h_client = RW::create().expect("Could not create hint client channel"); + let h_oracle = RW::create().expect("Could not create hint oracle channel"); // file descriptors 0, 1, 2 respectively correspond to the inherited stdin, // stdout, stderr. From 0f08aaaa451ed18f4959ac49e85b2309cb6fdbe2 Mon Sep 17 00:00:00 2001 From: Richard Bonichon Date: Mon, 27 Nov 2023 10:30:46 +0100 Subject: [PATCH 169/173] preimage_oracle: handle several key types --- optimism/src/preimage_oracle.rs | 52 ++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 986657a2c5..5ac087524c 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -6,8 +6,34 @@ use command_fds::{CommandFdExt, FdMapping}; use os_pipe::{PipeReader, PipeWriter}; use std::io::{Read, Write}; use std::os::fd::AsRawFd; - use std::process::{Child, Command}; + +pub enum Key { + Keccak([u8; 32]), + Local([u8; 32]), + Global([u8; 32]), +} + +impl Key { + pub fn contents(&self) -> [u8; 32] { + use Key::*; + match self { + Keccak(v) => *v, + Local(v) => *v, + Global(v) => *v, + } + } + + pub fn typ(&self) -> u8 { + use Key::*; + match self { + Keccak(_) => 2_u8, + Local(_) => 1_u8, + Global(_) => 3_u8, + } + } +} + pub struct PreImageOracle { pub cmd: Command, pub oracle_client: RW, @@ -40,9 +66,6 @@ impl PreImageOracle { let h_client = RW::create().expect("Could not create hint client channel"); let h_oracle = RW::create().expect("Could not create hint oracle channel"); - // file descriptors 0, 1, 2 respectively correspond to the inherited stdin, - // stdout, stderr. - // We need to map 3, 4, 5, 6 in the child process let RW(ReadWrite { reader: h_reader, writer: h_writer, @@ -52,7 +75,9 @@ impl PreImageOracle { writer: p_writer, }) = p_oracle; - // Use constant defined + // file descriptors 0, 1, 2 respectively correspond to the inherited stdin, + // stdout, stderr. + // We need to map 3, 4, 5, 6 in the child process cmd.fd_mappings(vec![ FdMapping { parent_fd: h_reader.as_raw_fd(), @@ -95,11 +120,14 @@ impl PreImageOracle { // +---------------------------------+ // a. a 64-bit integer indicating the length of the actual data // b. the preimage data, with a size of bits - pub fn get_preimage(&mut self, key: [u8; 32]) -> Preimage { + pub fn get_preimage(&mut self, key: Key) -> Preimage { let RW(ReadWrite { reader, writer }) = &mut self.oracle_client; - let mut msg_key = vec![2_u8]; // Assumes Keccak Key - msg_key.extend_from_slice(&key[1..31]); + let key_contents = key.contents(); + let key_type = key.typ(); + + let mut msg_key = vec![key_type]; + msg_key.extend_from_slice(&key_contents[1..31]); let _ = writer.write(&msg_key); let mut buf = [0_u8; 8]; @@ -141,3 +169,11 @@ impl PreImageOracle { let _ = reader.read_exact(&mut buf); } } + +#[cfg(test)] +mod tests { + + // TODO + #[test] + fn test_preimage_get() {} +} From c9d1cef1c999950cd2e8da200f961b8a19c7d5a9 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 27 Nov 2023 19:19:56 +0000 Subject: [PATCH 170/173] Add cross-process pipes, ensure that they aren't free'd --- optimism/src/preimage_oracle.rs | 72 +++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/optimism/src/preimage_oracle.rs b/optimism/src/preimage_oracle.rs index 5ac087524c..e5618ff5db 100644 --- a/optimism/src/preimage_oracle.rs +++ b/optimism/src/preimage_oracle.rs @@ -36,8 +36,10 @@ impl Key { pub struct PreImageOracle { pub cmd: Command, - pub oracle_client: RW, - pub hint_writer: RW, + pub preimage_write: RW, + pub preimage_read: RW, + pub hint_write: RW, + pub hint_read: RW, } pub struct ReadWrite { @@ -61,47 +63,41 @@ impl PreImageOracle { let mut cmd = Command::new(&host_program.name); cmd.args(&host_program.arguments); - let p_client = RW::create().expect("Could not create preimage client channel"); - let p_oracle = RW::create().expect("Could not create preimage oracle channel"); - let h_client = RW::create().expect("Could not create hint client channel"); - let h_oracle = RW::create().expect("Could not create hint oracle channel"); + let preimage_write = RW::create().expect("Could not create preimage write channel"); + let preimage_read = RW::create().expect("Could not create preimage read channel"); - let RW(ReadWrite { - reader: h_reader, - writer: h_writer, - }) = h_oracle; - let RW(ReadWrite { - reader: p_reader, - writer: p_writer, - }) = p_oracle; + let hint_write = RW::create().expect("Could not create hint write channel"); + let hint_read = RW::create().expect("Could not create hint read channel"); // file descriptors 0, 1, 2 respectively correspond to the inherited stdin, // stdout, stderr. // We need to map 3, 4, 5, 6 in the child process cmd.fd_mappings(vec![ FdMapping { - parent_fd: h_reader.as_raw_fd(), - child_fd: HINT_CLIENT_READ_FD, - }, - FdMapping { - parent_fd: h_writer.as_raw_fd(), + parent_fd: hint_read.0.writer.as_raw_fd(), child_fd: HINT_CLIENT_WRITE_FD, }, FdMapping { - parent_fd: p_reader.as_raw_fd(), - child_fd: PREIMAGE_CLIENT_READ_FD, + parent_fd: hint_write.0.reader.as_raw_fd(), + child_fd: HINT_CLIENT_READ_FD, }, FdMapping { - parent_fd: p_writer.as_raw_fd(), + parent_fd: preimage_read.0.writer.as_raw_fd(), child_fd: PREIMAGE_CLIENT_WRITE_FD, }, + FdMapping { + parent_fd: preimage_write.0.reader.as_raw_fd(), + child_fd: PREIMAGE_CLIENT_READ_FD, + }, ]) .unwrap_or_else(|_| panic!("Could not map file descriptors to server process")); PreImageOracle { cmd, - oracle_client: p_client, - hint_writer: h_client, + preimage_read, + preimage_write, + hint_read, + hint_write, } } @@ -121,20 +117,27 @@ impl PreImageOracle { // a. a 64-bit integer indicating the length of the actual data // b. the preimage data, with a size of bits pub fn get_preimage(&mut self, key: Key) -> Preimage { - let RW(ReadWrite { reader, writer }) = &mut self.oracle_client; + let RW(ReadWrite { + reader: _, + writer: preimage_writer, + }) = &mut self.preimage_write; + let RW(ReadWrite { + reader: preimage_reader, + writer: _, + }) = &mut self.preimage_read; let key_contents = key.contents(); let key_type = key.typ(); let mut msg_key = vec![key_type]; msg_key.extend_from_slice(&key_contents[1..31]); - let _ = writer.write(&msg_key); + let _ = preimage_writer.write(&msg_key); let mut buf = [0_u8; 8]; - let _ = reader.read_exact(&mut buf); + let _ = preimage_reader.read_exact(&mut buf); let length = u64::from_be_bytes(buf); - let mut handle = reader.take(length); + let mut handle = preimage_reader.take(length); let mut preimage = vec![0_u8; length as usize]; let _ = handle.read(&mut preimage); @@ -152,7 +155,14 @@ impl PreImageOracle { // // 2. Get back a single ack byte informing the the hint has been processed. pub fn hint(&mut self, hint: Hint) { - let RW(ReadWrite { reader, writer }) = &mut self.hint_writer; + let RW(ReadWrite { + reader: _, + writer: hint_writer, + }) = &mut self.hint_write; + let RW(ReadWrite { + reader: hint_reader, + writer: _, + }) = &mut self.hint_read; // Write hint request let mut hint_bytes = hint.get(); @@ -162,11 +172,11 @@ impl PreImageOracle { msg.append(&mut u64::to_be_bytes(hint_length as u64).to_vec()); msg.append(&mut hint_bytes); - let _ = writer.write(&msg); + let _ = hint_writer.write(&msg); // Read single byte acknowledgment response let mut buf = [0_u8]; - let _ = reader.read_exact(&mut buf); + let _ = hint_reader.read_exact(&mut buf); } } From 9d72d4373471aa639a0d0872be2781719db342ec Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Tue, 31 Oct 2023 14:32:26 +0000 Subject: [PATCH 171/173] Initially move sections around [MinaProtocol/mina#14442] --- book/src/SUMMARY.md | 29 +- .../extended-lookup-tables.md | 4 +- book/src/{plonk => kimchi}/final_check.md | 0 .../src/{rfcs => kimchi}/foreign_field_add.md | 0 book/src/kimchi/foreign_field_mul.md | 1316 +++++++++++++++++ book/src/{rfcs => kimchi}/keccak.md | 0 book/src/kimchi/lookup.md | 319 +++- book/src/{plonk => kimchi}/maller_15.md | 0 book/src/{plonk => kimchi}/zkpm.md | 0 book/src/rfcs/3-lookup.md | 291 ---- 10 files changed, 1649 insertions(+), 310 deletions(-) rename book/src/{rfcs => kimchi}/extended-lookup-tables.md (99%) rename book/src/{plonk => kimchi}/final_check.md (100%) rename book/src/{rfcs => kimchi}/foreign_field_add.md (100%) create mode 100644 book/src/kimchi/foreign_field_mul.md rename book/src/{rfcs => kimchi}/keccak.md (100%) rename book/src/{plonk => kimchi}/maller_15.md (100%) rename book/src/{plonk => kimchi}/zkpm.md (100%) delete mode 100644 book/src/rfcs/3-lookup.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index aeccb68783..4cde7e7502 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -53,10 +53,18 @@ # Kimchi - [Overview](./kimchi/overview.md) - - [Arguments](./kimchi/arguments.md) - - [Custom gates](./kimchi/gates.md) - - [Permutation](./kimchi/permut.md) - - [Lookup](./kimchi/lookup.md) +- [Arguments](./kimchi/arguments.md) +- [Zero-Column Approach to Zero-Knowledge](./kimchi/zkpm.md) +- [Final Check](./kimchi/final_check.md) +- [Maller's Optimization for Kimchi](./kimchi/maller_15.md) +- [Permutation](./kimchi/permut.md) +- [Lookup Tables](./kimchi/lookup.md) + - [Extended Lookup Tables](./kimchi/extended-lookup-tables.md) +- [Custom Gates](./kimchi/gates.md) + - [Foreign Field Addition](./kimchi/foreign_field_add.md) + - [Foreign Field Multiplication](./kimchi/foreign_field_mul.md) + - [Keccak](./rfcs/keccak.md) + # Pickles & Inductive Proof Systems @@ -65,17 +73,8 @@ - [Deferred Computation](./pickles/deferred.md) - [Passthough & Me-Only](./pickles/passthrough.md) -# RFCs - -- [RFC 0: Alternative zero-knowledge](./plonk/zkpm.md) -- [RFC 1: Final check](./plonk/final_check.md) -- [RFC 2: Maller's optimization for kimchi](./plonk/maller_15.md) -- [RFC 3: Plookup integration in kimchi](./rfcs/3-lookup.md) -- [RFC 4: Extended lookup tables](./rfcs/extended-lookup-tables.md) -- [RFC 5: Foreign Field Addition](./rfcs/foreign_field_add.md) -- [RFC 6: Keccak](./rfcs/keccak.md) - -# Specifications + +# Technical Specifications - [Poseidon hash](./specs/poseidon.md) - [Polynomial commitment](./specs/poly-commitment.md) diff --git a/book/src/rfcs/extended-lookup-tables.md b/book/src/kimchi/extended-lookup-tables.md similarity index 99% rename from book/src/rfcs/extended-lookup-tables.md rename to book/src/kimchi/extended-lookup-tables.md index ed8d29bf7c..a2205ddebd 100644 --- a/book/src/rfcs/extended-lookup-tables.md +++ b/book/src/kimchi/extended-lookup-tables.md @@ -1,6 +1,6 @@ -# RFC: Extended lookup tables +# Extended lookup tables -This RFC proposes an extension to our use of lookup tables using the PLOOKUP +This (old) RFC proposes an extension to our use of lookup tables using the PLOOKUP multiset inclusion argument, so that values within lookup tables can be chosen after the constraint system for a circuit has been fixed. diff --git a/book/src/plonk/final_check.md b/book/src/kimchi/final_check.md similarity index 100% rename from book/src/plonk/final_check.md rename to book/src/kimchi/final_check.md diff --git a/book/src/rfcs/foreign_field_add.md b/book/src/kimchi/foreign_field_add.md similarity index 100% rename from book/src/rfcs/foreign_field_add.md rename to book/src/kimchi/foreign_field_add.md diff --git a/book/src/kimchi/foreign_field_mul.md b/book/src/kimchi/foreign_field_mul.md new file mode 100644 index 0000000000..4569be8055 --- /dev/null +++ b/book/src/kimchi/foreign_field_mul.md @@ -0,0 +1,1316 @@ +# Foreign Field Multiplication + +This document is an original RFC explaining how we constrain foreign field multiplication (i.e. non-native field multiplication) in the Kimchi proof system. +For a more recent RFC, see [FFmul RFC](https://github.com/o1-labs/rfcs/blob/main/0006-ffmul-revised.md). + +**Changelog** + +| Author(s) | Date | Details | +|-|-|-| +| Joseph Spadavecchia and Anais Querol | October 2022 | Design of foreign field multiplication in Kimchi | + +## Overview + +This gate constrains + +$$ +a \cdot b = c \mod f +$$ + +where $a, b, c \in \mathbb{F_f}$, a foreign field with modulus $f$, using the native field $\mathbb{F_n}$ with prime modulus $n$. + +## Approach + +In foreign field multiplication the foreign field modulus $f$ could be bigger or smaller than the native field modulus $n$. When the foreign field modulus is bigger, then we need to emulate foreign field multiplication by splitting the foreign field elements up into limbs that fit into the native field element size. When the foreign modulus is smaller everything can be constrained either in the native field or split up into limbs. + +Since our projected use cases are when the foreign field modulus is bigger (more on this below) we optimize our design around this scenario. For this case, not only must we split foreign field elements up into limbs that fit within the native field, but we must also operate in a space bigger than the foreign field. This is because we check the multiplication of two foreign field elements by constraining its quotient and remainder. That is, renaming $c$ to $r$, we must constrain + +$$ +a \cdot b = q \cdot f + r, +$$ + +where the maximum size of $q$ and $r$ is $f - 1$ and so we have + +$$ +\begin{aligned} +a \cdot b &\le \underbrace{(f - 1)}_q \cdot f + \underbrace{(f - 1)}_r \\ +&\le f^2 - 1. +\end{aligned} +$$ + +Thus, the maximum size of the multiplication is quadratic in the size of foreign field. + +**Naïve approach** + +Naïvely, this implies that we have elements of size $f^2 - 1$ that must split them up into limbs of size at most $n - 1$. For example, if the foreign field modulus is $256$ and the native field modulus is $255$ bits, then we'd need $\log_2((2^{256})^2 - 1) \approx 512$ bits and, thus, require $512/255 \approx 3$ native limbs. However, each limb cannot consume all $255$ bits of the native field element because we need space to perform arithmetic on the limbs themselves while constraining the foreign field multiplication. Therefore, we need to choose a limb size that leaves space for performing these computations. + +Later in this document (see the section entitled "Choosing the limb configuration") we determine the optimal number of limbs that reduces the number of rows and gates required to constrain foreign field multiplication. This results in $\ell = 88$ bits as our optimal limb size. In the section about intermediate products we place some upperbounds on the number of bits required when constraining foreign field multiplication with limbs of size $\ell$ thereby proving that the computations can fit within the native field size. + +Observe that by combining the naïve approach above with a limb size of $88$ bits, we would require $512/88 \approx 6$ limbs for representing foreign field elements. Each limb is stored in a witness cell (a native field element). However, since each limb is necessarily smaller than the native field element size, it must be copied to the range-check gate to constrain its value. Since Kimchi only supports 7 copyable witness cells per row, this means that only one foreign field element can be stored per row. This means a single foreign field multiplication would consume at least 4 rows (just for the operands, quotient and remainder). This is not ideal because we want to limit the number of rows for improved performance. + +**Chinese remainder theorem** + +Fortunately, we can do much better than this, however, by leveraging the chinese remainder theorem (CRT for short) as we will now show. The idea is to reduce the number of bits required by constraining our multiplication modulo two coprime moduli: $2^t$ and $n$. By constraining the multiplication with both moduli the CRT guarantees that the constraints hold with the bigger modulus $2^t \cdot n$. + +The advantage of this approach is that constraining with the native modulus $n$ is very fast, allowing us to speed up our non-native computations. This practically reduces the costs to constraining with limbs over $2^t$, where $t$ is something much smaller than $512$. + +For this to work, we must select a value for $t$ that is big enough. That is, we select $t$ such that $2^t \cdot n > f^2 - 1$. As described later on in this document, by doing so we reduce the number of bits required for our use cases to $264$. With $88$ bit limbs this means that each foreign field element only consumes $3$ witness elements, and that means the foreign field multiplication gate now only consumes $2$ rows. The section entitled "Choosing $t$" describes this in more detail. + +**Overall approach** + +Bringing it all together, our approach is to verify that + +$$ +\begin{align} +a \cdot b = q \cdot f + r +\end{align} +$$ + +over $\mathbb{Z^+}$. In order to do this efficiently we use the CRT, which means that the equation holds mod $M = 2^t \cdot n$. For the equation to hold over the integers we must also check that each side of the equation is less than $2^t \cdot n$. + +The overall steps are therefore + +1. Apply CRT to equation (1) + * Check validity with binary modulus $\mod 2^t$ + * Check validity with native modulus $\mod n$ +2. Check each side of equation (1) is less than $M$ + * $a \cdot b < 2^t \cdot n$ + * $q \cdot f + r < 2^t \cdot n$ + +This then implies that + +$$ +a \cdot b = c \mod f. +$$ + +where $c = r$. That is, $a$ multiplied with $b$ is equal to $c$ where $a,b,c \in \mathbb{F_f}$. There is a lot more to each of these steps. That is the subject of the rest of this document. + +**Other strategies** + +Within our overall approach, aside from the CRT, we also use a number of other strategies to reduce the number and degree of constraints. + +* Avoiding borrows and carries +* Intermediate product elimination +* Combining multiplications + +The rest of this document describes each part in detail. + +## Parameter selection + +This section describes important parameters that we require and how they are computed. + +* *Native field modulus* $n$ +* *Foreign field modulus* $f$ +* *Binary modulus* $2^t$ +* *CRT modulus* $2^t \cdot n$ +* *Limb size* in bits $\ell$ + +#### Choosing $t$ + +Under the hood, we constrain $a \cdot b = q \cdot f + r \mod 2^t \cdot n$. Since this is a multiplication in the foreign field $f$, the maximum size of $q$ and $r$ are $f - 1$, so this means + +$$ +\begin{aligned} +a \cdot b &\le (f - 1) \cdot f + (f - 1) \\ +&\le f^2 - 1. +\end{aligned} +$$ + +Therefore, we need the modulus $2^t \cdot n$ such that + +$$ +2^t \cdot n > f^2 - 1, +$$ + +which is the same as saying, given $f$ and $n$, we must select $t$ such that + +$$ +\begin{aligned} +2^t \cdot n &\ge f^2 \\ +t &\ge 2\log_2(f) - \log_2(n). +\end{aligned} +$$ + +Thus, we have an lower bound on $t$. + +Instead of dynamically selecting $t$ for every $n$ and $f$ combination, we fix a $t$ that will work for the different selections of $n$ and $f$ relevant to our use cases. + +To guide us, from above we know that + +$$ +t_{min} = 2\log_2(f) - \log_2(n) +$$ + +and we know the field moduli for our immediate use cases. + +``` +vesta = 2^254 + 45560315531506369815346746415080538113 (255 bits) +pallas = 2^254 + 45560315531419706090280762371685220353 (255 bits) +secp256k1 = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 (256 bits) +``` + +So we can create a table + +| $n$ | $f$ | $t_{min}$ | +| ----------- | --------------- | --- | +| `vesta` | `secp256k1` | 258 | +| `pallas` | `secp256k1` | 258 | +| `vesta` | `pallas` | 255 | +| `pallas` | `vesta` | 255 | + +and know that to cover our use cases we need $t \ge 258$. + +Next, given our native modulus $n$ and $t$, we can compute the *maximum foreign field modulus supported*. Actually, we compute the maximum supported bit length of the foreign field modulus $f=2^m$. + +$$ +\begin{aligned} +2^t \cdot n &\ge f^2 \\ +&\ge (2^m)^2 = 2^{2m} \\ +t + \log_2(n) &> \log_2(2^{2m}) = 2m +\end{aligned} +$$ + +So we get + +$$ +m_{max} = \frac{t + \log_2(n)}{2}. +$$ + +With $t=258, n=255$ we have + +$$ +\begin{aligned} +m_{max} &= \frac{258 + 255}{2} = 256.5, +\end{aligned} +$$ + +which is not enough space to handle anything larger than 256 bit moduli. Instead, we will use $t=264$, giving $m_{max} = 259$ bits. + +The above formula is useful for checking the maximum number of bits supported of the foreign field modulus, but it is not useful for computing the maximum foreign field modulus itself (because $2^{m_{max}}$ is too coarse). For these checks, we can compute our maximum foreign field modulus more precisely with + +$$ +max_{mod} = \lfloor \sqrt{2^t \cdot n} \rfloor. +$$ + +The max prime foreign field modulus satisfying the above inequality for both Pallas and Vesta is `926336713898529563388567880069503262826888842373627227613104999999999999999607`. + +#### Choosing the limb configuration + +Choosing the right limb size and the right number of limbs is a careful balance between the number of constraints (i.e. the polynomial degree) and the witness length (i.e. the number of rows). Because one limiting factor that we have in Kimchi is the 12-bit maximum for range check lookups, we could be tempted to introduce 12-bit limbs. However, this would mean having many more limbs, which would consume more witness elements and require significantly more rows. It would also increase the polynomial degree by increasing the number of constraints required for the *intermediate products* (more on this later). + +We need to find a balance between the number of limbs and the size of the limbs. The limb configuration is dependent on the value of $t$ and our maximum foreign modulus (as described in the previous section). The larger the maximum foreign modulus, the more witness rows we will require to constrain the computation. In particular, each limb needs to be constrained by the range check gate and, thus, must be in a copyable (i.e. permuteable) witness cell. We have at most 7 copyable cells per row and gates can operate on at most 2 rows, meaning that we have an upperbound of at most 14 limbs per gate (or 7 limbs per row). + +As stated above, we want the foreign field modulus to fit in as few rows as possible and we need to constrain operands $a, b$, the quotient $q$ and remainder $r$. Each of these will require cells for each limb. Thus, the number of cells required for these is + +$$ +cells = 4 \cdot limbs +$$ + +It is highly advantageous for performance to constrain foreign field multiplication with the minimal number of gates. This not only helps limit the number of rows, but also to keep the gate selector polynomial small. Because of all of this, we aim to constrain foreign field multiplication with a single gate (spanning at most $2$ rows). As mentioned above, we have a maximum of 14 permuteable cells per gate, so we can compute the maximum number of limbs that fit within a single gate like this. + +$$ +\begin{aligned} +limbs_{max} &= \lfloor cells/4 \rfloor \\ + &= \lfloor 14/4 \rfloor \\ + &= 3 \\ +\end{aligned} +$$ + +Thus, the maximum number of limbs possible in a single gate configuration is 3. + +Using $limbs_{max}=3$ and $t=264$ that covers our use cases (see the previous section), we are finally able to derive our limbs size + +$$ +\begin{aligned} +\ell &= \frac{t}{limbs_{max}} \\ +&= 264/3 \\ +&= 88 +\end{aligned} +$$ + +Therefore, our limb configuration is: + + * Limb size $\ell = 88$ bits + * Number of limbs is $3$ + +## Avoiding borrows + +When we constrain $a \cdot b - q \cdot f = r \mod 2^t$ we want to be as efficient as possible. + +Observe that the expansion of $a \cdot b - q \cdot f$ into limbs would also have subtractions between limbs, requiring our constraints to account for borrowing. Dealing with this would create undesired complexity and increase the degree of our constraints. + +In order to avoid the possibility of subtractions we instead use $a \cdot b + q \cdot f'$ where + +$$ +\begin{aligned} +f' &= -f \mod 2^t \\ + &= 2^t - f +\end{aligned} +$$ + +The negated modulus $f'$ becomes part of our gate coefficients and is not constrained because it is publicly auditable. + +Using the substitution of the negated modulus, we now must constrain $a \cdot b + q \cdot f' = r \mod 2^t$. + +> Observe that $f' < 2^t$ since $f < 2^t$ and that $f' > f$ when $f < 2^{t - 1}$. + +## Intermediate products + +This section explains how we expand our constraints into limbs and then eliminate a number of extra terms. + +We must constrain $a \cdot b + q \cdot f' = r \mod 2^t$ on the limbs, rather than as a whole. As described above, each foreign field element $x$ is split into three 88-bit limbs: $x_0, x_1, x_2$, where $x_0$ contains the least significant bits and $x_2$ contains the most significant bits and so on. + +Expanding the right-hand side into limbs we have + +$$ +\begin{aligned} +&(a_0 + a_1 \cdot 2^{\ell} + a_2 \cdot 2^{2\ell}) \cdot (b_0 + b_1 \cdot 2^{\ell} + b_2 \cdot 2^{2\ell}) + (q_0 + q_1 \cdot 2^{\ell} + q_2 \cdot 2^{2\ell}) \cdot (f'_0 + f'_1 \cdot 2^{\ell} + f'_2 \cdot 2^{2\ell}) \\ +&=\\ +&~~~~~ a_0 \cdot b_0 + a_0 \cdot b_1 \cdot 2^{\ell} + a_0 \cdot b_2 \cdot 2^{2\ell} \\ +&~~~~ + a_1 \cdot b_0 \cdot 2^{\ell} + a_1 \cdot b_1 \cdot 2^{2\ell} + a_1 \cdot b_2 \cdot 2^{3\ell} \\ +&~~~~ + a_2 \cdot b_0 \cdot 2^{2\ell} + a_2 \cdot b_1 \cdot 2^{3\ell} + a_2 \cdot b_2 \cdot 2^{4\ell} \\ +&+ \\ +&~~~~~ q_0 \cdot f'_0 + q_0 \cdot f'_1 \cdot 2^{\ell} + q_0 \cdot f'_2 \cdot 2^{2\ell} \\ +&~~~~ + q_1 \cdot f'_0 \cdot 2^{\ell} + q_1 \cdot f'_1 \cdot 2^{2\ell} + q_1 \cdot f'_2 \cdot 2^{3\ell} \\ +&~~~~ + q_2 \cdot f'_0 \cdot 2^{2\ell} + q_2 \cdot f'_1 \cdot 2^{3\ell} + q_2 \cdot f'_2 \cdot 2^{4\ell} \\ +&= \\ +&a_0 \cdot b_0 + q_0 \cdot f'_0 \\ +&+ 2^{\ell} \cdot (a_0 \cdot b_1 + a_1 \cdot b_0 + q_0 \cdot f'_1 + q_1 \cdot f'_0) \\ +&+ 2^{2\ell} \cdot (a_0 \cdot b_2 + a_2 \cdot b_0 + q_0 \cdot f'_2 + q_2 \cdot f'_0 + a_1 \cdot b_1 + q_1 \cdot f'_1) \\ +&+ 2^{3\ell} \cdot (a_1 \cdot b_2 + a_2 \cdot b_1 + q_1 \cdot f'_2 + q_2 \cdot f'_1) \\ +&+ 2^{4\ell} \cdot (a_2 \cdot b_2 + q_2 \cdot f'_2) \\ +\end{aligned} +$$ + +Since $t = 3\ell$, the terms scaled by $2^{3\ell}$ and $2^{4\ell}$ are a multiple of the binary modulus and, thus, congruent to zero $\mod 2^t$. They can be eliminated and we don't need to compute them. So we are left with 3 *intermediate products* that we call $p_0, p_1, p_2$: + +| Term | Scale | Product | +| ----- | ----------- | -------------------------------------------------------- | +| $p_0$ | $1$ | $a_0b_0 + q_0f'_0$ | +| $p_1$ | $2^{\ell}$ | $a_0b_1 + a_1b_0 + q_0f'_1 + q_1f'_0$ | +| $p_2$ | $2^{2\ell}$ | $a_0b_2 + a_2b_0 + q_0f'_2 + q_2f'_0 + a_1b_1 + q_1f'_1$ | + +So far, we have introduced these checked computations to our constraints + +> 1. Computation of $p_0, p_1, p_2$ + +## Constraining $\mod 2^t$ + +Let's call $p := ab + qf' \mod 2^t$. Remember that our goal is to constrain that $p - r = 0 \mod 2^t$ (recall that any more significant bits than the 264th are ignored in $\mod 2^t$). Decomposing that claim into limbs, that means + +$$ +\begin{align} +2^{2\ell}(p_2 - r_2) + 2^{\ell}(p_1 - r_1) + p_0 - r_0 = 0 \mod 2^t. +\end{align} +$$ + +We face two challenges + +* Since $p_0, p_1, p_2$ are at least $2^{\ell}$ bits each, the right side of the equation above does not fit in $\mathbb{F}_n$ +* The subtraction of the remainder's limbs $r_0$ and $r_1$ could require borrowing + +For the moment, let's not worry about the possibility of borrows and instead focus on the first problem. + +## Combining multiplications + +The first problem is that our native field is too small to constrain $2^{2\ell}(p_2 - r_2) + 2^{\ell}(p_1 - r_1) + p_0 - r_0 = 0 \mod 2^t$. We could split this up by multiplying $a \cdot b$ and $q \cdot f'$ separately and create constraints that carefully track borrows and carries between limbs. However, a more efficient approach is combined the whole computation together and accumulate all the carries and borrows in order to reduce their number. + +The trick is to assume a space large enough to hold the computation, view the outcome in binary and then split it up into chunks that fit in the native modulus. + +To this end, it helps to know how many bits these intermediate products require. On the left side of the equation, $p_0$ is at most $2\ell + 1$ bits. We can compute this by substituting the maximum possible binary values (all bits set to 1) into $p_0 = a_0b_0 + q_0f'_0$ like this + +$$ +\begin{aligned} +\mathsf{maxbits}(p_0) &= \log_2(\underbrace{(2^{\ell} - 1)}_{a_{0}} \underbrace{(2^{\ell} - 1)}_{b_{0}} + \underbrace{(2^{\ell} - 1)}_{q_{0}} \underbrace{(2^{\ell} - 1)}_{f'_{0}}) \\ +&= \log_2(2(2^{2\ell} - 2^{\ell + 1} + 1)) \\ +&= \log_2(2^{2\ell + 1} - 2^{\ell + 2} + 2). +\end{aligned} +$$ + +So $p_0$ fits in $2\ell + 1$ bits. Similarly, $p_1$ needs at most $2\ell + 2$ bits and $p_2$ is at most $2\ell + 3$ bits. + + +| Term | $p_0$ | $p_1$ | $p_2$ | +| -------- | ----------- | ----------- | ----------- | +| **Bits** | $2\ell + 1$ | $2\ell + 2$ | $2\ell + 3$ | + +The diagram below shows the right hand side of the zero-sum equality from equation (2). That is, the value $p - r$. Let's look at how the different bits of $p_0, p_1, p_2, r_0, r_1$ and $r_2$ impact it. + +```text +0 L 2L 3L 4L +|-------------|-------------|-------------|-------------|-------------| + : +|--------------p0-----------:-| 2L + 1 + : + |-------------:-p1------------| 2L + 2 + p10➚ : p11➚ + |----------------p2-------------| 2L + 3 + : +|-----r0------| : + : + |-----r1------| + : + |-----r2------| +\__________________________/ \______________________________/ + ≈ h0 ≈ h1 +``` + +Within our native field modulus we can fit up to $2\ell + \delta < \log_2(n)$ bits, for small values of $\delta$ (but sufficient for our case). Thus, we can only constrain approximately half of $p - r$ at a time. In the diagram above the vertical line at 2L bisects $p - r$ into two $\approx2\ell$ bit values: $h_0$ and $h_1$ (the exact definition of these values follow). Our goal is to constrain $h_0$ and $h_1$. + +## Computing the zero-sum halves: $h_0$ and $h_1$ + +Now we can derive how to compute $h_0$ and $h_1$ from $p$ and $r$. + +The direct approach would be to bisect both $p_0$ and $p_1$ and then define $h_0$ as just the sum of the $2\ell$ lower bits of $p_0$ and $p_1$ minus $r_0$ and $r_1$. Similarly $h_1$ would be just the sum of upper bits of $p_0, p_1$ and $p_2$ minus $r_2$. However, each bisection requires constraints for the decomposition and range checks for the two halves. Thus, we would like to avoid bisections as they are expensive. + +Ideally, if our $p$'s lined up on the $2\ell$ boundary, we would not need to bisect at all. However, we are unlucky and it seems like we must bisect both $p_0$ and $p_1$. Fortunately, we can at least avoid bisecting $p_0$ by allowing it to be summed into $h_0$ like this + +$$ +h_0 = p_0 + 2^{\ell}\cdot p_{10} - r_0 - 2^{\ell}\cdot r_1 +$$ + +Note that $h_0$ is actually greater than $2\ell$ bits in length. This may not only be because it contains $p_0$ whose length is $2\ell + 1$, but also because adding $p_{10}$ may cause an overflow. The maximum length of $h_0$ is computed by substituting in the maximum possible binary value of $2^{\ell} - 1$ for the added terms and $0$ for the subtracted terms of the above equation. + +$$ +\begin{aligned} +\mathsf{maxbits}(h_0) &= \log_2(\underbrace{(2^{\ell} - 1)(2^{\ell} - 1) + (2^{\ell} - 1)(2^{\ell} - 1)}_{p_0} + 2^{\ell} \cdot \underbrace{(2^{\ell} - 1)}_{p_{10}}) \\ +&= \log_2(2^{2\ell + 1} - 2^{\ell + 2} + 2 + 2^{2\ell} - 2^\ell) \\ +&= \log_2( 3\cdot 2^{2\ell} - 5 \cdot 2^\ell +2 ) \\ +\end{aligned} +$$ + +which is $2\ell + 2$ bits. + +N.b. This computation assumes correct sizes values for $r_0$ and $r_1$, which we assure by range checks on the limbs. + +Next, we compute $h_1$ as + +$$ +h_1 = p_{11} + p_2 - r_2 +$$ + +The maximum size of $h_1$ is computed as + +$$ +\begin{aligned} +\mathsf{maxbits}(h_1) &= \mathsf{maxbits}(p_{11} + p_2) +\end{aligned} +$$ + +In order to obtain the maximum value of $p_{11}$, we define $p_{11} := \frac{p_1}{2^\ell}$. Since the maximum value of $p_1$ was $2^{2\ell+2}-2^{\ell+3}+4$, then the maximum value of $p_{11}$ is $2^{\ell+2}-8$. For $p_2$, the maximum value was $6\cdot 2^{2\ell} - 12 \cdot 2^\ell + 6$, and thus: + +$$ +\begin{aligned} +\mathsf{maxbits}(h_1) &= log_2(\underbrace{2^{\ell+2}-8}_{p_{11}} + \underbrace{6\cdot 2^{2\ell} - 12 \cdot 2^\ell + 6}_{p_2}) \\ +&= \log_2(6\cdot 2^{2\ell} - 8 \cdot 2^\ell - 2) \\ +\end{aligned} +$$ + +which is $2\ell + 3$ bits. + +| Term | $h_0$ | $h_1$ | +| -------- | ----------- | ----------- | +| **Bits** | $2\ell + 2$ | $2\ell + 3$ | + +Thus far we have the following constraints +> 2. Composition of $p_{10}$ and $p_{11}$ result in $p_1$ +> 3. Range check $p_{11} \in [0, 2^{\ell + 2})$ +> 4. Range check $p_{10} \in [0, 2^{\ell})$ + +For the next step we would like to constrain $h_0$ and $h_1$ to zero. Unfortunately, we are not able to do this! + +* Firstly, as defined $h_0$ may not be zero because we have not bisected it precisely at $2\ell$ bits, but have allowed it to contain the full $2\ell + 2$ bits. Recall that these two additional bits are because $p_0$ is at most $2\ell + 1$ bits long, but also because adding $p_{10}$ increases it to $2\ell + 2$. These two additional bits are not part of the first $2\ell$ bits of $p - r$ and, thus, are not necessarily zero. That is, they are added from the second $2\ell$ bits (i.e. $h_1$). + +* Secondly, whilst the highest $\ell + 3$ bits of $p - r$ would wrap to zero $\mod 2^t$, when placed into the smaller $2\ell + 3$ bit $h_1$ in the native field, this wrapping does not happen. Thus, $h_1$'s $\ell + 3$ highest bits may be nonzero. + +We can deal with this non-zero issue by computing carry witness values. + +## Computing carry witnesses values $v_0$ and $v_1$ + +Instead of constraining $h_0$ and $h_1$ to zero, there must be satisfying witness $v_0$ and $v_1$ such that the following constraints hold. +> 5. There exists $v_0$ such that $h_0 = v_0 \cdot 2^{2\ell}$ +> 6. There exists $v_1$ such that $h_1 = v_1 \cdot 2^{\ell} - v_0$ + +Here $v_0$ is the last two bits of $h_0$'s $2\ell + 2$ bits, i.e., the result of adding the highest bit of $p_0$ and any possible carry bit from the operation of $h_0$. Similarly, $v_1$ corresponds to the highest $\ell + 3$ bits of $h_1$. It looks like this + +```text +0 L 2L 3L 4L +|-------------|-------------|-------------|-------------|-------------| + : +|--------------h0-----------:--| 2L + 2 + : ↖v0 + :-------------h1-------------| 2L + 3 + : \____________/ + : v1➚ +``` + + +Remember we only need to prove the first $3\ell$ bits of $p - r$ are zero, since everything is $\mod 2^t$ and $t = 3\ell$. It may not be clear how this approach proves the $3\ell$ bits are indeed zero because within $h_0$ and $h_1$ there are bits that are nonzero. The key observation is that these bits are too high for $\mod 2^t$. + +By making the argument with $v_0$ and $v_1$ we are proving that $h_0$ is something where the $2\ell$ least significant bits are all zeros and that $h_1 + v_0$ is something where the $\ell$ are also zeros. Any nonzero bits after $3\ell$ do not matter, since everything is $\mod 2^t$. + +All that remains is to range check $v_0$ and $v_1$ +> 7. Range check $v_0 \in [0, 2^2)$ +> 8. Range check $v_1 =\in [0, 2^{\ell + 3})$ + +## Subtractions + +When unconstrained, computing $u_0 = p_0 + 2^{\ell} \cdot p_{10} - (r_0 + 2^{\ell} \cdot r_1)$ could require borrowing. Fortunately, we have the constraint that the $2\ell$ least significant bits of $u_0$ are `0` (i.e. $u_0 = 2^{2\ell} \cdot v_0$), which means borrowing cannot happen. + +Borrowing is prevented because when the the $2\ell$ least significant bits of the result are `0` it is not possible for the minuend to be less than the subtrahend. We can prove this by contradiction. + +Let + +* $x = p_0 + 2^{\ell} \cdot p_{10}$ +* $y = r_0 + 2^{\ell} \cdot r_1$ +* the $2\ell$ least significant bits of $x - y$ be `0` + +Suppose that borrowing occurs, that is, that $x < y$. Recall that the length of $x$ is at most $2\ell + 2$ bits. Therefore, since $x < y$ the top two bits of $x$ must be zero and so we have + +$$ +x - y = x_{2\ell} - y, +$$ + +where $x_{2\ell}$ denotes the $2\ell$ least significant bits of $x$. + +Recall also that the length of $y$ is $2\ell$ bits. We know this because limbs of the result are each constrained to be in $[0, 2^{\ell})$. So the result of this subtraction is $2\ell$ bits. Since the $2\ell$ least significant bits of the subtraction are `0` this means that + +$$ +\begin{aligned} +x - y & = 0 \\ +x &= y, +\end{aligned} +$$ + +which is a contradiction with $x < y$. + +## Costs + +Range checks should be the dominant cost, let's see how many we have. + +Range check (3) requires two range checks for $p_{11} = p_{111} \cdot 2^\ell + p_{110}$ + * a) $p_{110} \in [0, 2^\ell)$ + * b) $p_{111} \in [0, 2^2)$ + +Range check (8) requires two range checks and a decomposition check that is merged in (6). + * a) $v_{10} \in [0, 2^{\ell})$ + * b) $v_{11} \in [0, 2^3)$ + +The range checks on $p_0, p_1$ and $p_2$ follow from the range checks on $a,b$ and $q$. + +So we have 3.a, 3.b, 4, 7, 8.a, 8.b. + +| Range check | Gate type(s) | Witness | Rows | +| ----------- | ------------------------------------------------ | ------------------------- | ---- | +| 7 | $(v_0 - 3)(v_0 - 2)(v_0 - 1)v_0$ | $v_0$ | < 1 | +| 3.a | $(p_{111} - 3)(p_{111} - 2)(p_{111} - 1)p_{111}$ | $p_{111}$ | < 1 | +| 8.b | degree-8 constraint or plookup | $v_{11}$ | 1 | +| 3.b, 4, 8.a | `multi-range-check` | $p_{10}, p_{110}, v_{10}$ | 4 | + +So we have 1 multi-range-check, 1 single-range-check and 2 low-degree range checks. This consumes just over 5 rows. + +## Use CRT to constrain $a \cdot b - q \cdot f - r \equiv 0 \mod n$ + +Until now we have constrained the equation $\mod 2^t$, but remember that our application of the CRT means that we must also constrain the equation $\mod n$. We are leveraging the fact that if the identity holds for all moduli in $\mathcal{M} = \{n, 2^t\}$, then it holds for $\mathtt{lcm} (\mathcal{M}) = 2^t \cdot n = M$. + +Thus, we must check $a \cdot b - q \cdot f - r \equiv 0 \mod n$, which is over $\mathbb{F}_n$. + +This gives us equality $\mod 2^t \cdot n$ as long as the divisors are coprime. That is, as long as $\mathsf{gcd}(2^t, n) = 1$. Since the native modulus $n$ is prime, this is true. + +Thus, to perform this check is simple. We compute + +$$ +\begin{aligned} +a_n &= a \mod n \\ +b_n &= b \mod n \\ +q_n &= q \mod n \\ +r_n &= r \mod n \\ +f_n &= f \mod n +\end{aligned} +$$ + +using our native field modulus with constraints like this + +$$ +\begin{aligned} +a_n &= 2^{2\ell} \cdot a_2 + 2^{\ell} \cdot a_1 + a_0 \\ +b_n &= 2^{2\ell} \cdot b_2 + 2^{\ell} \cdot b_1 + b_0 \\ +q_n &= 2^{2\ell} \cdot q_2 + 2^{\ell} \cdot q_1 + q_0 \\ +r_n & = 2^{2\ell} \cdot r_2 + 2^{\ell} \cdot r_1 + r_0 \\ +f_n &= 2^{2\ell} \cdot f_2 + 2^{\ell} \cdot f_1 + f_0 \\ +\end{aligned} +$$ + +and then constrain + +$$ +a_n \cdot b_n - q_n \cdot f_n - r_n = 0 \mod n. +$$ + +Note that we do not use the negated foreign field modulus here. + +This requires a single constraint of the form + +> 9. $a_n \cdot b_n - q_n \cdot f_n = r_n$ + +with all of the terms expanded into the limbs according the the above equations. The values $a_n, b_n, q_n, f_n$ and $r_n$ do not need to be in the witness. + +## Range check both sides of $a \cdot b = q \cdot f + r$ + +Until now we have constrained that equation $a \cdot b = q \cdot f + r$ holds modulo $2^t$ and modulo $n$, so by the CRT it holds modulo $M = 2^t \cdot n$. Remember that, as outlined in the "Overview" section, we must prove our equation over the positive integers, rather than $\mod M$. By doing so, we assure that our solution is not equal to some $q' \cdot M + r'$ where $q', r' \in F_{M}$, in which case $q$ or $r$ would be invalid. + +First, let's consider the right hand side $q \cdot f + r$. We have + +$$ +q \cdot f + r < 2^t \cdot n +$$ + +Recall that we have parameterized $2^t \cdot n \ge f^2$, so if we can bound $q$ and $r$ such that + +$$ +q \cdot f + r < f^2 +$$ + +then we have achieved our goal. We know that $q$ must be less than $f$, so that is our first check, leaving us with + +$$ +\begin{aligned} +(f - 1) \cdot f + r &< f^2 \\ +r &< f^2 - (f - 1) \cdot f = f +\end{aligned} +$$ + +Therefore, to check $q \cdot f + r < 2^t \cdot n$, we need to check +* $q < f$ +* $r < f$ + +This should come at no surprise, since that is how we parameterized $2^t \cdot n$ earlier on. Note that by checking $q < f$ we assure correctness, while checking $r < f$ assures our solution is unique (sometimes referred to as canonical). + +Next, we must perform the same checks for the left hand side (i.e., $a \cdot b < 2^t \cdot n$). Since $a$ and $b$ must be less than the foreign field modulus $f$, this means checking +* $a < f$ +* $b < f$ + +So we have + +$$ +\begin{aligned} +a \cdot b &\le (f - 1) \cdot (f - 1) = f^2 - 2f + 1 \\ +\end{aligned} +$$ + +Since $2^t \cdot n \ge f^2$ we have + +$$ +\begin{aligned} +&f^2 - 2f + 1 < f^2 \le 2^t \cdot n \\ +&\implies +a \cdot b < 2^t \cdot n +\end{aligned} +$$ + +### Bound checks + +To perform the above range checks we use the *upper bound check* method described in the [Foreign Field Addition RFC](](https://github.com/o1-labs/proof-systems/blob/master/book/src/rfcs/ffadd.md#upper-bound-check)). + +The upper bound check is as follows. We must constrain $0 \le q < f$ over the positive integers, so + +$$ +\begin{aligned} +2^t \le q &+ 2^t < f + 2^t \\ +2^t - f \le q &+ 2^t - f < 2^t \\ +\end{aligned} +$$ + +Remember $f' = 2^t - f$ is our negated foreign field modulus. Thus, we have + +$$ +\begin{aligned} +f' \le q &+ f' < 2^t \\ +\end{aligned} +$$ + +So to check $q < t$ we just need to compute $q' = q + f'$ and check $f' \le q' < 2^t$ + +Observe that + +$$ +0 \le q \implies f' \le q' +$$ + +and that + +$$ +q' < 2^t \implies q < f +$$ + +So we only need to check + +- $0 \le q$ +- $q' < 2^t$ + +The first check is always true since we are operating over the positive integers and $q \in \mathbb{Z^+}$. Observe that the second check also constrains that $q < 2^t$, since $f \le 2^{259} < 2^t$ and thus + +$$ +\begin{aligned} +q' &\le 2^t \\ +q + f' &\le 2^t \\ +q &\le 2^t - (2^t - f) = f\\ +q &< 2^t +\end{aligned} +$$ + +Therefore, to constrain $q < f$ we only need constraints for + +- $q' = q + f'$ +- $q' < 2^t$ + +and we don't require an additional multi-range-check for $q < 2^t$. + +### Cost of bound checks + +This section analyzes the structure and costs of bounds checks for foreign field addition and foreign field multiplication. + +#### Addition + +In our foreign field addition design the operands $a$ and $b$ do not need to be less than $f$. The field overflow bit $\mathcal{o}$ for foreign field addition is at most 1. That is, $a + b = \mathcal{o} \cdot f + r$, where $r$ is allowed to be greater than $f$. Therefore, + +$$ +(f + a) + (f + b) = 1 \cdot f + (f + a + b) +$$ + +These can be chained along $k$ times as desired. The final result + +$$ +r = (\underbrace{f + \cdots + f}_{k} + a_1 + b_1 + \cdots a_k + b_k) +$$ + +Since the bit length of $r$ increases logarithmically with the number of additions, in Kimchi we must only check that the final $r$ in the chain is less than $f$ to constrain the entire chain. + +> **Security note:** In order to defer the $r < f$ check to the end of any chain of additions, it is extremely important to consider the potential impact of wraparound in $\mathbb{F_n}$. That is, we need to consider whether the addition of a large chain of elements greater than the foreign field modulus could wrap around. If this could happen then the $r < f$ check could fail to detect an invalid witness. Below we will show that this is not possible in Kimchi. +> +> Recall that our foreign field elements are comprised of 3 limbs of 88-bits each that are each represented as native field elements in our proof system. In order to wrap around and circumvent the $r < f$ check, the highest limb would need to wrap around. This means that an attacker would need to perform about $k \approx n/2^{\ell}$ additions of elements greater than then foreign field modulus. Since Kimchi's native moduli (Pallas and Vesta) are 255-bits, the attacker would need to provide a witness for about $k \approx 2^{167}$ additions. This length of witness is greater than Kimchi's maximum circuit (resp. witness) length. Thus, it is not possible for the attacker to generate a false proof by causing wraparound with a large chain of additions. + +In summary, for foreign field addition in Kimchi it is sufficient to only bound check the last result $r'$ in a chain of additions (and subtractions) + +- Compute bound $r' = r + f'$ with addition gate (2 rows) +- Range check $r' < 2^t$ (4 rows) + +#### Multiplication + +In foreign field multiplication, the situation is unfortunately different, and we must check that each of $a, b, q$ and $r$ are less than $f$. We cannot adopt the strategy from foreign field addition where the operands are allowed to be greater than $f$ because the bit length of $r$ would increases linearly with the number of multiplications. That is, + +$$ +(a_1 + f) \cdot (a_2 + f) = 1 \cdot f + \underbrace{f^2 + (a_1 + a_2 - 1) \cdot f + a_1 \cdot a_2}_{r} +$$ + +and after a chain of $k$ multiplication we have + +$$ +r = f^k + \ldots + a_1 \cdots a_k +$$ + +where $r > f^k$ quickly overflows our CRT modulus $2^t \cdot n$. For example, assuming our maximum foreign modulus of $f = 2^{259}$ and either of Kimchi's native moduli (i.e. Pallas or Vesta), $f^k > 2^t \cdot n$ for $k > 2$. That is, an overflow is possible for a chain of greater than 1 foreign field multiplication. Thus, we must check $a, b, q$ and $r$ are less than $f$ for each multiplication. + +Fortunately, in many situations the input operands may already be checked either as inputs or as outputs of previous operations, so they may not be required for each multiplication operation. + +Thus, the $q'$ and $r'$ checks (or equivalently $q$ and $r$) are our main focus because they must be done for every multiplication. + +- Compute bound $q' = q + f'$ with addition gate (2 rows) +- Compute bound $r' = r + f'$ with addition gate (2 rows) +- Range check $q' < 2^t$ (4 rows) +- Range check $r' < 2^t$ (4 rows) + +This costs 12 rows per multiplication. In a subsequent section, we will reduce it to 8 rows. + +### 2-limb decomposition + +Due to the limited number of permutable cells per gate, we do not have enough cells for copy constraining $q'$ and $r'$ (or $q$ and $r$) to their respective range check gadgets. To address this issue, we must decompose $q'$ into 2 limbs instead of 3, like so + +$$ +q' = q'_{01} + 2^{2\ell} \cdot q'_2 +$$ + +and + +$$ +q'_{01} = q'_0 + 2^{\ell} \cdot q'_1 +$$ + +Thus, $q'$ is decomposed into two limbs $q'_{01}$ (at most $2\ell$ bits) and $q'_2$ (at most $\ell$ bits). + +Note that $q'$ must be range checked by a `multi-range-check` gadget. To do this the `multi-range-check` gadget must + +- Store a copy of the limbs $q'_0, q'_1$ and $q'_2$ in its witness +- Range check that they are $\ell$ bit each +- Constrain that $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ (this is done with a special mode of the `multi-range-check` gadget) +- Have copy constraints for $q'_{01}$ and $q'_2$ as outputs of the `ForeignFieldMul` gate and inputs to the `multi-range-check` gadget + +Note that since the foreign field multiplication gate computes $q'$ from $q$ which is already in the witness and $q'_{01}$ and $q'_2$ have copy constraints to a `multi-range-check` gadget that fully constrains their decomposition from $q'$, then the `ForeignFieldMul` gate does not need to store an additional copy of $q'_0$ and $q'_1$. + +### An optimization + +Since the $q < f$ and $r < f$ checks must be done for each multiplication it makes sense to integrate them into the foreign field multiplication gate. By doing this we can save 4 rows per multiplication. + +Doing this doesn't require adding a lot more witness data because the operands for the bound computations $q' = q + f'$ and $r' = r + f'$ are already present in the witness of the multiplication gate. We only need to store the bounds $q'$ and $r'$ in permutable witness cells so that they may be copied to multi-range-check gates to check they are each less than $2^t$. + +To constrain $x + f' = x'$, the equation we use is + +$$ +x + 2^t = \mathcal{o} \cdot f + x', +$$ + +where $x$ is the original value, $\mathcal{o}=1$ is the field overflow bit and $x'$ is the remainder and our desired addition result (e.g. the bound). Rearranging things we get + +$$ +x + 2^t - f = x', +$$ + +which is just + +$$ +x + f' = x', +$$ + +Recall from the section "Avoiding borrows" that $f'$ is often larger than $f$. At first this seems like it could be a problem because in multiplication each operation must be less than $f$. However, this is because the maximum size of the multiplication was quadratic in the size of $f$ (we use the CRT, which requires the bound that $a \cdot b < 2^t \cdot n$). However, for addition the result is much smaller and we do not require the CRT nor the assumption that the operands are smaller than $f$. Thus, we have plenty of space in $\ell$-bit limbs to perform our addition. + +So, the equation we need to constrain is + +$$ +x + f' = x'. +$$ + +We can expand the left hand side into the 2 limb format in order to obtain 2 intermediate sums + +$$ +\begin{aligned} +s_{01} = x_{01} + f_{01}' \\ +s_2 = x_2 + f'_2 \\ +\end{aligned} +$$ + +where $x_{01}$ and $f'_{01}$ are defined like this + +$$ +\begin{aligned} +x_{01} = x_0 + 2^{\ell} \cdot x_1 \\ +f'_{01} = f'_0 + 2^{\ell} \cdot f'_1 \\ +\end{aligned} +$$ + +and $x$ and $f'$ are defined like this + +$$ +\begin{aligned} +x = x_{01} + 2^{2\ell} \cdot x_2 \\ +f' = f'_{01} + 2^{2\ell} \cdot f'_2 \\ +\end{aligned} +$$ + +Going back to our intermediate sums, the maximum bit length of sum $s_{01}$ is computed from the maximum bit lengths of $x_{01}$ and $f'_{01}$ + +$$ +\underbrace{(2^{\ell} - 1) + 2^{\ell} \cdot (2^{\ell} - 1)}_{x_{01}} + \underbrace{(2^{\ell} - 1) + 2^{\ell} \cdot (2^{\ell} - 1)}_{f'_{01}} = 2^{2\ell+ 1} - 2, +$$ + +which means $s_{01}$ is at most $2\ell + 1$ bits long. + +Similarly, since $x_2$ and $f'_2$ are less than $2^{\ell}$, the max value of $s_2$ is + +$$ +(2^{\ell} - 1) + (2^{\ell} - 1) = 2^{\ell + 1} - 2, +$$ + +which means $s_2$ is at most $\ell + 1$ bits long. + +Thus, we must constrain + +$$ +s_{01} + 2^{2\ell} \cdot s_2 - x'_{01} - 2^{2\ell} \cdot x'_2 = 0 \mod 2^t. +$$ + +The accumulation of this into parts looks like this. + +```text +0 L 2L 3L=t 4L +|-------------|-------------|-------------|-------------|-------------| + : +|------------s01------------:-| 2L + 1 + : ↖w01 + |------s2-----:-| L + 1 + : ↖w2 + : +|------------x'01-----------| + : + |------x'2----| + : +\____________________________/ + ≈ z01 \_____________/ + ≈ z2 +``` + +The two parts are computed with + +$$ +\begin{aligned} +z_{01} &= s_{01} - x'_{01} \\ +z_2 &= s_2 - x'_2. +\end{aligned} +$$ + +Therefore, there are two carry bits $w_{01}$ and $w_2$ such that + +$$ +\begin{aligned} +z_{01} &= 2^{2\ell} \cdot w_{01} \\ +z_2 + w_{01} &= 2^{\ell} \cdot w_2 +\end{aligned} +$$ + +In this scheme $x'_{01}, x'_2, w_{01}$ and $w_2$ are witness data, whereas $s_{01}$ and $s_2$ are formed from a constrained computation of witness data $x_{01}, x_2$ and constraint system public parameter $f'$. Note that due to carrying, witness $x'_{01}$ and $x'_2$ can be different than the values $s_{01}$ and $s_2$ computed from the limbs. + +Thus, each bound addition $x + f'$ requires the following witness data + +- $x_{01}, x_2$ +- $x'_{01}, x'_2$ +- $w_{01}, w_2$ + +where $f'$ is baked into the gate coefficients. The following constraints are needed + +- $2^{2\ell} \cdot w_{01} = s_{01} - x'_{01}$ +- $2^{\ell} \cdot w_2 = s_2 + w_{01} - x'_2$ +- $x'_{01} \in [0, 2^{2\ell})$ +- $x'_2 \in [0, 2^{\ell})$ +- $w_{01} \in [0, 2)$ +- $w_2 \in [0, 2)$ + +Due to the limited number of copyable witness cells per gate, we are currently only performing this optimization for $q$. + +The witness data is + +- $q_0, q_1, q_2$ +- $q'_{01}, q'_2$ +- $q'_{carry01}, q'_{carry2}$ + +The checks are + +1. $q_0 \in [0, 2^{\ell})$ +2. $q_1 \in [0, 2^{\ell})$ +3. $q'_0 = q_0 + f'_0$ +4. $q'_1 = q_1 + f'_1$ +5. $s_{01} = q'_0 + 2^{\ell} \cdot q'_1$ +6. $q'_{01} \in [0, 2^{2\ell})$ +7. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ +8. $q'_{carry01} \in [0, 2)$ +9. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ +10. $q_2 \in [0, 2^{\ell})$ +11. $s_2 = q_2 + f'_2$ +12. $q'_{carry2} \in [0, 2)$ +13. $2^{\ell} \cdot q'_{carry2} = s_2 + w_{01} - q'_2$ + + +Checks (1) - (5) assure that $s_{01}$ is at most $2\ell + 1$ bits. Whereas checks (10) - (11) assure that $s_2$ is at most $\ell + 1$ bits. Altogether they are comprise a single `multi-range-check` of $q_0, q_1$ and $q_2$. However, as noted above, we do not have enough copyable cells to output $q_1, q_2$ and $q_3$ to the `multi-range-check` gadget. Therefore, we adopt a strategy where the 2 limbs $q'_{01}$ and $q'_2$ are output to the `multi-range-check` gadget where the decomposition of $q'_0$ and $q'_2$ into $q'_{01} = p_0 + 2^{\ell} \cdot p_1$ is constrained and then $q'_0, q'_1$ and $q'_2$ are range checked. + +Although $q_1, q_2$ and $q_3$ are not range checked directly, this is safe because, as shown in the "Bound checks" section, range-checking that $q' \in [0, 2^t)$ also constrains that $q \in [0, 2^t)$. Therefore, the updated checks are + +1. $q_0 \in [0, 2^{\ell})$ `multi-range-check` +2. $q_1 \in [0, 2^{\ell})$ `multi-range-check` +3. $q'_0 = q_0 + f'_0$ `ForeignFieldMul` +4. $q'_1 = q_1 + f'_1$ `ForeignFieldMul` +5. $s_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `ForeignFieldMul` +6. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` +7. $q'_{carry01} \in [0, 2)$ `ForeignFieldMul` +8. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ `ForeignFieldMul` +9. $q_2 \in [0, 2^{\ell})$ `multi-range-check` +10. $s_2 = q_2 + f'_2$ `ForeignFieldMul` +11. $q'_{carry2} \in [0, 2)$ `ForeignFieldMul` +12. $2^{\ell} \cdot q'_{carry2} = s_2 + q'_{carry01} - q'_2$ `ForeignFieldMul` + +Note that we don't need to range-check $q'_{01}$ is at most $2\ell + 1$ bits because it is already implicitly constrained by the `multi-range-check` gadget constraining that $q'_0, q'_1$ and $q'_2$ are each at most $\ell$ bits and that $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$. Furthermore, since constraining the decomposition is already part of the `multi-range-check` gadget, we do not need to do it here also. + +To simplify things further, we can combine some of these checks. Recall our checked computations for the intermediate sums + +$$ +\begin{aligned} +s_{01} &= q_{01} + f'_{01} \\ +s_2 &= q_2 + f'_2 \\ +\end{aligned} +$$ + +where $q_{01} = q_0 + 2^{\ell} \cdot q_1$ and $f'_{01} = f'_0 + 2^{\ell} \cdot f'_1$. These do not need to be separate constraints, but are instead part of existing ones. + +Checks (10) and (11) can be combined into a single constraint $2^{\ell} \cdot q'_{carry2} = (q_2 + f'_2) + q'_{carry01} - q'_2$. Similarly, checks (3) - (5) and (8) can be combined into $2^{2\ell} \cdot q'_{carry01} = q_{01} + f'_{01} - q'_{01}$ with $q_{01}$ and $f'_{01}$ further expanded. The reduced constraints are + +1. $q_0 \in [0, 2^{\ell})$ `multi-range-check` +2. $q_1 \in [0, 2^{\ell})$ `multi-range-check` +3. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` +4. $q'_{carry01} \in [0, 2)$ `ForeignFieldMul` +5. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ `ForeignFieldMul` +6. $q_2 \in [0, 2^{\ell})$ `multi-range-check` +7. $q'_{carry2} \in [0, 2)$ `ForeignFieldMul` +8. $2^{\ell} \cdot q'_{carry2} = s_2 + w_{01} - q'_2$ `ForeignFieldMul` + +Finally, there is one more optimization that we will exploit. This optimization relies on the observation that for bound addition the second carry bit $q'_{carry2}$ is always zero. This this may be obscure, so we will prove it by contradiction. To simplify our work we rename some variables by letting $x_0 = q_{01}$ and $x_1 = q_2$. Thus, $q'_{carry2}$ being non-zero corresponds to a carry in $x_1 + f'_1$. + +> **Proof:** To get a carry in the highest limbs $x_1 + f'_1$ during bound addition, we need +> +> $$ +> 2^{\ell} < x_1 + \phi_0 + f'_1 \le 2^{\ell} - 1 + \phi_0 + f'_1 +> $$ +> +> where $2^{\ell} - 1$ is the maximum possible size of $x_1$ (before it overflows) and $\phi_0$ is the overflow bit from the addition of the least significant limbs $x_0$ and $f'_0$. This means +> +> $$ +> 2^{\ell} - \phi_0 - f'_1 < x_1 < 2^{\ell} +> $$ +> +> We cannot allow $x$ to overflow the foreign field, so we also have +> +> $$ +> x_1 < (f - x_0)/2^{2\ell} +> $$ +> +> Thus, +> +> $$ +> 2^{\ell} - \phi_0 - f'_1 < (f - x_0)/2^{2\ell} = f/2^{2\ell} - x_0/2^{2\ell} +> $$ +> +> Since $x_0/2^{2\ell} = \phi_0$ we have +> +> $$ +> 2^{\ell} - \phi_0 - f'_1 < f/2^{2\ell} - \phi_0 +> $$ +> +> so +> +> $$ +> 2^{\ell} - f'_1 < f/2^{2\ell} +> $$ +> +> Notice that $f/2^{2\ell} = f_1$. Now we have +> +> $$ +> 2^{\ell} - f'_1 < f_1 \\ +> \Longleftrightarrow \\ +> f'_1 > 2^{\ell} - f_1 +> $$ +> +> However, this is a contradiction with the definition of our negated foreign field modulus limb $f'_1 = 2^{\ell} - f_1$. $\blacksquare$ + +We have proven that $q'_{carry2}$ is always zero, so that allows use to simplify our constraints. We now have + +1. $q_0 \in [0, 2^{\ell})$ `multi-range-check` +2. $q_1 \in [0, 2^{\ell})$ `multi-range-check` +3. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` +4. $q'_{carry01} \in [0, 2)$ `ForeignFieldMul` +5. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ `ForeignFieldMul` +6. $q_2 \in [0, 2^{\ell})$ `multi-range-check` +7. $q'_2 = s_2 + w_{01}$ `ForeignFieldMul` + +In other words, we have eliminated constraint (7) and removed $q'_{carry2}$ from the witness. + +Since we already needed to range-check $q$ or $q'$, the total number of new constraints added is 4: 3 added to to `ForeignFieldMul` and 1 added to `multi-range-check` gadget for constraining the decomposition of $q'_{01}$. + +This saves 2 rows per multiplication. + +## Chaining multiplications + +Due to the limited number of cells accessible to gates, we are not able to chain multiplications into multiplications. We can chain foreign field additions into foreign field multiplications, but currently do not support chaining multiplications into additions (though there is a way to do it). + +## Constraining the computation + +Now we collect all of the checks that are required to constrain foreign field multiplication + +### 1. Range constrain $a$ + +If the $a$ operand has not been constrained to $[0, f)$ by any previous foreign field operations, then we constrain it like this +- Compute bound $a' = a + f'$ with addition gate (2 rows) `ForeignFieldAdd` +- Range check $a' \in [0, 2^t)$ (4 rows) `multi-range-check` + +### 2. Range constrain $b$ + +If the $b$ operand has not been constrained to $[0, f)$ by any previous foreign field operations, then we constrain it like this +- Compute bound $b' = b + f'$ with addition gate (2 rows) `ForeignFieldAdd` +- Range check $b' \in [0, 2^t)$ (4 rows) `multi-range-check` + +### 3. Range constrain $q$ + +The quotient $q$ is constrained to $[0, f)$ for each multiplication as part of the multiplication gate +- Compute bound $q' = q + f'$ with `ForeignFieldMul` constraints +- Range check $q' \in [0, 2^t)$ (4 rows) `multi-range-check` +- Range check $q \ge 0$ `ForeignFieldMul` (implicit by storing `q` in witness) + +### 4. Range constrain $r$ + +The remainder $r$ is constrained to $[0, f)$ for each multiplication using an external addition gate. +- Compute bound $r' = r + f'$ with addition gate (2 rows) `ForeignFieldAdd` +- Range check $r' \in [0, 2^t)$ (4 rows) `multi-range-check` + +### 5. Compute intermediate products + +Compute and constrain the intermediate products $p_0, p_1$ and $p_2$ as: + +- $p_0 = a_0 \cdot b_0 + q_0 \cdot f'_0$ `ForeignFieldMul` +- $p_1 = a_0 \cdot b_1 + a_1 \cdot b_0 + q_0 \cdot f'_1 + q_1 \cdot f'_0$ `ForeignFieldMul` +- $p_2 = a_0 \cdot b_2 + a_2 \cdot b_0 + a_1 \cdot b_1 + q_0 \cdot f'_2 + q_2 \cdot f'_0 + q_1 \cdot f'_1$ `ForeignFieldMul` + +where each of them is about $2\ell$-length elements. + +### 6. Native modulus checked computations + +Compute and constrain the native modulus values, which are used to check the constraints modulo $n$ in order to apply the CRT + +- $a_n = 2^{2\ell} \cdot a_2 + 2^{\ell} \cdot a_1 + a_0 \mod n$ +- $b_n = 2^{2\ell} \cdot b_2 + 2^{\ell} \cdot b_1 + b_0 \mod n$ +- $q_n = 2^{2\ell} \cdot q_2 + 2^{\ell} \cdot q_1 + q_0 \mod n$ +- $r_n = 2^{2\ell} \cdot r_2 + 2^{\ell} \cdot r_1 + r_0 \mod n$ +- $f_n = 2^{2\ell} \cdot f_2 + 2^{\ell} \cdot f_1 + f_0 \mod n$ + +### 7. Decompose middle intermediate product + +Check that $p_1 = 2^{\ell} \cdot p_{11} + p_{10}$: + +- $p_1 = 2^\ell \cdot p_{11} + p_{10}$ `ForeignFieldMul` +- Range check $p_{10} \in [0, 2^\ell)$ `multi-range-check` +- Range check $p_{11} \in [0, 2^{\ell+2})$ + - $p_{11} = p_{111} \cdot 2^\ell + p_{110}$ `ForeignFieldMul` + - Range check $p_{110} \in [0, 2^\ell)$ `multi-range-check` + - Range check $p_{111} \in [0, 2^2)$ with a degree-4 constraint `ForeignFieldMul` + +### 8. Zero sum for multiplication + +Now we have to constrain the zero sum + +$$ +(p_0 - r_0) + 2^{88}(p_1 - r_1) + 2^{176}(p_2 - r_2) = 0 \mod 2^t +$$ + +We constrain the first and the second halves as + +- $v_0 \cdot 2^{2\ell} = p_0 + 2^\ell \cdot p_{10} - r_0 - 2^\ell \cdot r_1$ `ForeignFieldMul` +- $v_1 \cdot 2^{\ell} = (p_{111} \cdot 2^\ell + p_{110}) + p_2 - r_2 + v_0$ `ForeignFieldMul` + +And some more range checks + +- Check that $v_0 \in [0, 2^2)$ with a degree-4 constraint `ForeignFieldMul` +- Check that $v_1 \in [0, 2^{\ell + 3})$ + - Check $v_1 = v_{11} \cdot 2^{88} + v_{10}$ `ForeignFieldMul` + - Check $v_{11} \in [0, 2^3]$ `ForeignFieldMul` + - Check $v_{10} < 2^\ell$ with range constraint `multi-range-check` + +To check that $v_{11} \in [0, 2^3)$ (i.e. that $v_{11}$ is at most 3 bits long) we first range-check $v_{11} \in [0, 2^{12})$ with a 12-bit plookup. This means there can be no higher bits set beyond the 12-bits of $v_{11}$. Next, we scale $v_{11}$ by $2^9$ in order to move the highest $12 - 3 = 9$ bits beyond the $12$th bit. Finally, we perform a 12-bit plookup on the resulting value. That is, we have + +- Check $v_{11} \in [0, 2^{12})$ with a 12-bit plookup (to prevent any overflow) +- Check $\mathsf{scaled}_{v_{11}} = 2^9 \cdot v_{11}$ +- Check $\mathsf{scaled}_{v_{11}}$ is a 12-bit value with a 12-bit plookup + +Kimchi's plookup implementation is extremely flexible and supports optional scaling of the lookup target value as part of the lookup operation. Thus, we do not require two witness elements, two lookup columns, nor the $\mathsf{scaled}_{v_{11}} = 2^9 \cdot v_{11}$ custom constraint. Instead we can just store $v_{11}$ in the witness and define this column as a "joint lookup" comprised of one 12-bit plookup on the original cell value and another 12-bit plookup on the cell value scaled by $2^9$, thus, yielding a 3-bit check. This eliminates one plookup column and reduces the total number of constraints. + +### 9. Native modulus constraint + +Using the checked native modulus computations we constrain that + +$$ +a_n \cdot b_n - q_n \cdot f_n - r_n = 0 \mod n. +$$ + +### 10. Compute intermediate sums + +Compute and constrain the intermediate sums $s_{01}$ and $s_2$ as: + +- $s_{01} = q_{01} + f'_{01}$ +- $s_2 = q_2 + f_2'$ +- $q_{01} = q_0 + 2^{\ell} \cdot q_1$ +- $f'_{01} = f'_0 + 2^{\ell} \cdot f'_1$ + +### 11. Decompose the lower quotient bound + +Check that $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$. + +Done by (3) above with the `multi-range-check` on $q'$ +- $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ +- Range check $q'_0 \in [0, 2^\ell)$ +- Range check $q'_1 \in [0, 2^\ell)$ + +### 12. Zero sum for quotient bound addition + +We constrain that + +$$ +s_{01} - q'_{01} + 2^{2\ell} \cdot (s_2 - q'_2) = 0 \mod 2^t +$$ + +by constraining the two halves + +* $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ +* $2^{\ell} \cdot q'_{carry2} = s_2 + q'_{carry01} - q'_2$ + +We also need a couple of small range checks + +- Check that $q'_{carry01}$ is boolean `ForeignFieldMul` +- Check that $q'_2$ is boolean `ForeignFieldMul` + +# Layout + +Based on the constraints above, we need the following 12 values copied from the range check gates. + +``` +a0, a1, a2, b0, b1, b2, q0, q1, q2, r0, r1, r2 +``` +Since we need 12 copied values for the constraints, they must span 2 rows. + +The $q < f$ bound limbs $q'_0, q'_1$ and $q'_2$ must be in copyable cells so they can be range-checked. Similarly, the limbs of the operands $a$, $b$ and the result $r$ must all be in copyable cells. This leaves only 2 remaining copyable cells and, therefore, we cannot compute and output $r' = r + f'$. It must be deferred to an external `ForeignFieldAdd` gate with the $r$ cells copied as an argument. + +NB: the $f$ and $f'$ values are publicly visible in the gate coefficients. + +| | Curr | Next | +| ---------- | ---------------------------------------- | ---------------- | +| **Column** | `ForeignFieldMul` | `Zero` | +| 0 | $a_0$ (copy) | $r_0$ (copy) | +| 1 | $a_1$ (copy) | $r_1$ (copy) | +| 2 | $a_2$ (copy) | $r_2$ (copy) | +| 3 | $b_0$ (copy) | $q'_{01}$ (copy) | +| 4 | $b_1$ (copy) | $q'_2$ (copy) | +| 5 | $b_2$ (copy) | $p_{10}$ (copy) | +| 6 | $v_{10}$ (copy) | $p_{110}$ (copy) | +| 7 | $v_{11}$ (scaled plookup) | | +| 8 | $v_0$ | | +| 9 | $q_0$ | | +| 10 | $q_1$ | | +| 11 | $q_2$ | | +| 12 | $q'_{carry01}$ | | +| 13 | $p_{111}$ | | +| 14 | | | + +# Checked computations + +As described above foreign field multiplication has the following intermediate computations + +1. $p_0 = a_0b_0 + q_0f'_0$ +2. $p_1 = a_0b_1 + a_1b_0 + q_0f'_1 + q_1f'_0$ +3. $p_2 = a_0b_2 + a_2b_0 + a_1b_1 + q_0f'_2 + q_2f'_0 + q_1f'_1$. + +For the $q$ bound addition we must also compute + +1. $s_{01} = q_{01} + f'_{01}$ +2. $s_2 = q_2 + f_2'$ +3. $q_{01} = q_0 + 2^{\ell} \cdot q_1$ +4. $f'_{01} = f'_0 + 2^{\ell} \cdot f'_1$ + +> Note the equivalence +> +> $$ +> \begin{aligned} +> s_{01} &= q_{01} + f'_{01} \\ +> &= q_0 + 2^{\ell} \cdot q_1 + f'_0 + 2^{\ell} \cdot f'_1 \\ +> &= q_0 + f'_0 + 2^{\ell} \cdot (q'_1 + f'_1) \\ +> &= q'_0 + 2^{\ell} \cdot q'_1 +> \end{aligned} +> $$ +> +> where $q'_0 = q_0 + f'_0$ and $q'_1 = q_1 + f'_1$ can be done with checked computations. + +Next, for applying the CRT we compute + +1. $a_n = 2^{2\ell} \cdot a_2 + 2^{\ell} \cdot a_1 + a_0 \mod n$ +2. $b_n = 2^{2\ell} \cdot b_2 + 2^{\ell} \cdot b_1 + b_0 \mod n$ +3. $q_n = 2^{2\ell} \cdot q_2 + 2^{\ell} \cdot q_1 + q_0 \mod n$ +4. $r_n = 2^{2\ell} \cdot r_2 + 2^{\ell} \cdot r_1 + r_0 \mod n$ +5. $f_n = 2^{2\ell} \cdot f_2 + 2^{\ell} \cdot f_1 + f_0 \mod n$ + +# Checks + +In total we require the following checks + +1. $p_{111} \in [0, 2^2)$ +2. $v_{10} \in [0, 2^{\ell})$ `multi-range-check` +3. $p_{110} \in [0, 2^{\ell})$ `multi-range-check` +4. $p_{10} \in [0, 2^{\ell})$ `multi-range-check` +5. $p_{11} = 2^{\ell} \cdot p_{111} + p_{110}$ +6. $p_1 = 2^{\ell} \cdot p_{11} + p_{10}$ +7. $v_0 \in [0, 2^2)$ +8. $2^{2\ell} \cdot v_0 = p_0 + 2^{\ell} \cdot p_{10} - r_0 - 2^{\ell} \cdot r_1$ +9. $v_{11} \in [0, 2^{3})$ +10. $v_1 = 2^{\ell} \cdot v_{11} + v_{10}$ +11. $2^{\ell} \cdot v_1 = v_0 + p_{11} + p_2 - r_2$ +12. $a_n \cdot b_n - q_n \cdot f_n = r_n$ +13. $q'_0 \in [0, 2^{\ell})$ `multi-range-check` +14. $q'_1 \in [0, 2^{\ell})$ `multi-range-check` +15. $q'_2 \in [0, 2^{\ell})$ `multi-range-check` +16. $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$ `multi-range-check` +17. $q'_{carry01} \in [0, 2)$ +18. $2^{2\ell} \cdot q'_{carry01} = s_{01} - q'_{01}$ +19. $q'_2 = s_2 + q'_{carry01}$ + +# Constraints + +These checks can be condensed into the minimal number of constraints as follows. + +First, we have the range-check corresponding to (1) as a degree-4 constraint + +**C1:** $p_{111} \cdot (p_{111} - 1) \cdot (p_{111} - 2) \cdot (p_{111} - 3)$ + +Checks (2) - (4) are all handled by a single `multi-range-check` gadget. + +**C2:** `multi-range-check` $v_{10}, p_{10}, p_{110}$ + +Next (5) and (6) can be combined into + +**C3:** $2^{\ell} \cdot (2^{\ell} \cdot p_{111} + p_{110}) + p_{10} = p_1$ + +Now we have the range-check corresponding to (7) as another degree-4 constraint + +**C4:** $v_0 \cdot (v_0 - 1) \cdot (v_0 - 2) \cdot (v_0 - 3)$ + +Next we have check (8) + +**C5:** $2^{2\ell} \cdot v_0 = p_0 + 2^{\ell} \cdot p_{10} - r_0 - 2^{\ell} \cdot r_1$ + +Up next, check (9) is a 3-bit range check + +**C6:** Plookup $v_{11}$ (12-bit plookup scaled by $2^9$) + +Now checks (10) and (11) can be combined into + +**C7:** $2^{\ell} \cdot (v_{11} \cdot 2^{\ell} + v_{10}) = p_2 + p_{11} + v_0 - r_2$ + +Next, for our use of the CRT, we must constrain that $a \cdot b = q \cdot f + r \mod n$. Thus, check (12) is + +**C8:** $a_n \cdot b_n - q_n \cdot f_n = r_n$ + +Next we must constrain the quotient bound addition. + +Checks (13) - (16) are all combined into `multi-range-check` gadget + +**C9:** `multi-range-check` $q'_0, q'_1, q'_2$ and $q'_{01} = q'_0 + 2^{\ell} \cdot q'_1$. + +Check (17) is a carry bit boolean check + +**C10:** $q'_{carry01} \cdot (q'_{carry01} - 1)$ + +Next, check (18) is + +**C11:** $2^{2\ell} \cdot q'_{carry10} = s_{01} - q'_{01}$ + +Finally, check (19) is + +**C12:** $q'_2 = s_2 + q'_{carry01}$ + +The `Zero` gate has no constraints and is just used to hold values required by the `ForeignFieldMul` gate. + +# External checks + +The following checks must be done with other gates to assure the soundness of the foreign field multiplication + +- Range check input + - `multi-range-check` $a$ + - `multi-range-check` $b$ +- Range check witness data + - `multi-range-check` $q'$ and check $q_{01}' = q'_0 + 2^{\ell} q'_1$ + - `multi-range-check` $\ell$-bit limbs: $p_{10}, p_{110}, p_{111}$ +- Range check output + - `multi-range-check` either $r$ or $r'$ +- Compute and constrain $r'$ the bound on $r$ + - `ForeignFieldAdd` $r + 2^t = 1 \cdot f + r'$ + +Copy constraints must connect the above witness cells to their respective input cells within the corresponding external check gates witnesses. diff --git a/book/src/rfcs/keccak.md b/book/src/kimchi/keccak.md similarity index 100% rename from book/src/rfcs/keccak.md rename to book/src/kimchi/keccak.md diff --git a/book/src/kimchi/lookup.md b/book/src/kimchi/lookup.md index 9989ac5557..a261672716 100644 --- a/book/src/kimchi/lookup.md +++ b/book/src/kimchi/lookup.md @@ -1,3 +1,318 @@ -## Lookup +# Plookup in Kimchi -TO-DO \ No newline at end of file +In 2020, [plookup](https://eprint.iacr.org/2020/315.pdf) showed how to create lookup proofs. Proofs that some witness values are part of a [lookup table](https://en.wikipedia.org/wiki/Lookup_table). Two years later, an independent team published [plonkup](https://eprint.iacr.org/2022/086) showing how to integrate Plookup into Plonk. + +This document specifies how we integrate plookup in kimchi. It assumes that the reader understands the basics behind plookup. + +## Overview + +We integrate plookup in kimchi with the following differences: + +* we snake-ify the sorted table instead of wrapping it around (see later) +* we allow fixed-ahead-of-time linear combinations of columns of the queries we make +* we only use a single table (XOR) at the moment of this writing +* we allow several lookups (or queries) to be performed within the same row +* zero-knowledgeness is added in a specific way (see later) + +The following document explains the protocol in more detail + +### Recap on the grand product argument of plookup + +As per the Plookup paper, the prover will have to compute three vectors: + +* $f$, the (secret) **query vector**, containing the witness values that the prover wants to prove are part of the lookup table. +* $t$, the (public) **lookup table**. +* $s$, the (secret) concatenation of $f$ and $t$, sorted by $t$ (where elements are listed in the order they are listed in $t$). + +Essentially, plookup proves that all the elements in $f$ are indeed in the lookup table $t$ if and only if the following multisets are equal: + +* $\{(1+\beta)f, \text{diff}(t)\}$ +* $\text{diff}(\text{sorted}(f, t))$ + +where $\text{diff}$ is a new set derived by applying a "randomized difference" between every successive pairs of a vector. For example: + +* $f = \{5, 4, 1, 5\}$ +* $t = \{1, 4, 5\}$ +* $\{\color{blue}{(1+\beta)f}, \color{green}{\text{diff}(t)}\} = \{\color{blue}{(1+\beta)5, (1+\beta)4, (1+\beta)1, (1+\beta)5}, \color{green}{1+\beta 4, 4+\beta 5}\}$ +* $\text{diff}(\text{sorted}(f, t)) = \{1+\beta 1, 1+\beta 4, 4+\beta 4, 4+\beta 5, 5+\beta 5, 5+\beta 5\}$ + +> Note: This assumes that the lookup table is a single column. You will see in the next section how to address lookup tables with more than one column. + +The equality between the multisets can be proved with the permutation argument of plonk, which would look like enforcing constraints on the following accumulator: + +* init: $\mathsf{acc}_0 = 1$ +* final: $\mathsf{acc}_n = 1$ +* for every $0 < i \leq n$: + $$ + \mathsf{acc}_i = \mathsf{acc}_{i-1} \cdot \frac{(\gamma + (1+\beta) f_{i-1}) \cdot (\gamma + t_{i-1} + \beta t_i)}{(\gamma + s_{i-1} + \beta s_{i})(\gamma + s_{n+i-1} + \beta s_{n+i})} + $$ + +Note that the plookup paper uses a slightly different equation to make the proof work. It is possible that the proof would work with the above equation, but for simplicity let's just use the equation published in plookup: + +$$ +\mathsf{acc}_i = \mathsf{acc}_{i-1} \cdot \frac{(1+\beta) \cdot (\gamma + f_{i-1}) \cdot (\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})(\gamma(1+\beta) + s_{n+i-1} + \beta s_{n+i})} +$$ + +> Note: in plookup $s$ is longer than $n$ ($|s| = |f| + |t|$), and thus it needs to be split into multiple vectors to enforce the constraint at every $i \in [[0;n]]$. This leads to the two terms in the denominator as shown above, so that the degree of $\gamma (1 + \beta)$ is equal in the nominator and denominator. + +### Lookup tables + +Kimchi uses a single **lookup table** at the moment of this writing; the XOR table. The XOR table for values of 1 bit is the following: + + +| l | r | o | +| --- | --- | --- | +| 1 | 0 | 1 | +| 0 | 1 | 1 | +| 1 | 1 | 0 | +| 0 | 0 | 0 | + +Whereas kimchi uses the XOR table for values of $4$ bits, which has $2^{8}$ entries. + +Note: the $(0, 0, 0)$ **entry** is at the very end on purpose (as it will be used as dummy entry for rows of the witness that don't care about lookups). + +### Querying the table + +The plookup paper handles a vector of lookups $f$ which we do not have. So the first step is to create such a table from the witness columns (or registers). To do this, we define the following objects: + +* a **query** tells us what registers, in what order, and scaled by how much, are part of a query +* a **query selector** tells us which rows are using the query. It is pretty much the same as a [gate selector](). + +Let's go over the first item in this section. + +For example, the following **query** tells us that we want to check if $r_0 \oplus r_2 = 2\cdot r_1$ + +| l | r | o | +| :---: | :---: | :---: | +| 1, $r_0$ | 1, $r_2$ | 2, $r_1$ | + +The grand product argument for the lookup consraint will look like this at this point: + +$$ +\mathsf{acc}_i = \mathsf{acc}_{i-1} \cdot \frac{(1+\beta) \cdot {\color{green}(\gamma + w_0(g^i) + j \cdot w_2(g^i) + j^2 \cdot 2 \cdot w_1(g^i))} \cdot (\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})(\gamma(1+\beta) + s_{n+i-1} + \beta s_{n+i})} +$$ + +Not all rows need to perform queries into a lookup table. We will use a query selector in the next section to make the constraints work with this in mind. + +### Query selector + +The associated **query selector** tells us on which rows the query into the XOR lookup table occurs. + +| row | query selector | +| :---: | :------------: | +| 0 | 1 | +| 1 | 0 | + + +Both the (XOR) lookup table and the query are built-ins in kimchi. The query selector is derived from the circuit at setup time. Currently only the ChaCha gates make use of the lookups. + +With the selectors, the grand product argument for the lookup constraint has the following form: + +$$ +\mathsf{acc}_i = \mathsf{acc}_{i-1} \cdot \frac{(1+\beta) \cdot {\color{green}\mathsf{query}} \cdot (\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})} +$$ + +where $\color{green}{\mathsf{query}}$ is constructed so that a dummy query ($0 \oplus 0 = 0$) is used on rows that don't have a query. + +$$ +\begin{align} +\mathsf{query} := &\ \mathsf{selector} \cdot (\gamma + w_0(g^i) + j \cdot w_2(g^i) + j^2 \cdot 2 \cdot w_1(g^i)) + \\ +&\ (1- \mathsf{selector}) \cdot (\gamma + 0 + j \cdot 0 + j^2 \cdot 0) +\end{align} +$$ + +### Supporting multiple queries + +Since we would like to allow multiple table lookups per row, we define multiple **queries**, where each query is associated with a **lookup selector**. + +At the moment of this writing, the `ChaCha` gates all perform $4$ queries in a row. Thus, $4$ is trivially the largest number of queries that happen in a row. + +**Important**: to make constraints work, this means that each row must make $4$ queries. Potentially some or all of them are dummy queries. + +For example, the `ChaCha0`, `ChaCha1`, and `ChaCha2` gates will jointly apply the following 4 XOR queries on the current and following rows: + +| l | r | o | - | l | r | o | - | l | r | o | - | l | r | o | +| :---: | :---: | :----: | --- | :---: | :---: | :----: | --- | :---: | :---: | :----: | --- | :---: | :----: | :----: | +| 1, $r_3$ | 1, $r_7$ | 1, $r_{11}$ | - | 1, $r_4$ | 1, $r_8$ | 1, $r_{12}$ | - | 1, $r_5$ | 1, $r_9$ | 1, $r_{13}$ | - | 1, $r_6$ | 1, $r_{10}$ | 1, $r_{14}$ | + +which you can understand as checking for the current and following row that + +$$ +\begin{align} +r_3 \oplus r_7 &= r_{11}\\ +r_4 \oplus r_8 &= r_{12}\\ +r_5 \oplus r_9 &= r_{13}\\ +r_6 \oplus r_{10} &= r_{14} +\end{align} +$$ + +The `ChaChaFinal` also performs $4$ (somewhat similar) queries in the XOR lookup table. In total this is $8$ different queries that could be associated to $8$ selector polynomials. + +### Grouping queries by queries pattern + +Associating each query with a selector polynomial is not necessarily efficient. To summarize: + +* the `ChaCha0`, `ChaCha1`, and `ChaCha2` gates that in total make $4$ queries into the XOR table +* the `ChaChaFinal` gate makes another $4$ different queries into the XOR table + +Using the previous section's method, we'd have to use $8$ different lookup selector polynomials for each of the different $8$ queries. Since there's only $2$ use-cases, we can simply group them by **queries patterns** to reduce the number of lookup selector polynomials to $2$. + +The grand product argument for the lookup constraint looks like this now: + +$$ +\mathsf{acc}_i = \mathsf{acc}_{i-1} \cdot \frac{{\color{green}(1+\beta)^4 \cdot \mathsf{query}} \cdot (\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})\times \ldots} +$$ + +where $\color{green}{\mathsf{query}}$ is constructed as: + +$$ +\begin{align} +\mathsf{query} = &\ \mathsf{selector}_1 \cdot \mathsf{pattern}_1 + \\ +&\ \mathsf{selector}_2 \cdot \mathsf{pattern}_2 + \\ +&\ (1 - \mathsf{selector}_1 - \mathsf{selector}_2) \cdot (\gamma + 0 + j \cdot 0 + j^2 \cdot 0)^4 +\end{align} +$$ + +where, for example the first pattern for the `ChaCha0`, `ChaCha1`, and `ChaCha2` gates looks like this: + +$$ +\begin{align} +\mathsf{pattern}_1 = &\ (\gamma + w_3(g^i) + j \cdot w_7(g^i) + j^2 \cdot w_{11}(g^i)) \cdot \\ +&\ (\gamma + w_4(g^i) + j \cdot w_8(g^i) + j^2 \cdot w_{12}(g^i)) \cdot \\ +&\ (\gamma + w_5(g^i) + j \cdot w_9(g^i) + j^2 \cdot w_{13}(g^i)) \cdot \\ +&\ (\gamma + w_6(g^i) + j \cdot w_{10}(g^i) + j^2 \cdot w_{14}(g^i)) \cdot \\ +\end{align} +$$ + +Note that there's now $4$ dummy queries, and they only appear when none of the lookup selectors are active. +If a pattern uses less than $4$ queries, it has to be padded with dummy queries as well. + +Finally, note that the denominator of the grand product argument is incomplete in the formula above. +Since the nominator has degree $5$ in $\gamma (1 + \beta)$, the denominator must match too. +This is achieved by having a longer $s$, and referring to it $5$ times. +The denominator thus becomes $\prod_{k=1}^{5} (\gamma (1+\beta) + s_{kn+i-1} + \beta s_{kn+i})$. + +## Back to the grand product argument + +There are two things that we haven't touched on: + +* The vector $t$ representing the **combined lookup table** (after its columns have been combined with a joint combiner $j$). The **non-combined loookup table** is fixed at setup time and derived based on the lookup tables used in the circuit (for now only one, the XOR lookup table, can be used in the circuit). +* The vector $s$ representing the sorted multiset of both the queries and the lookup table. This is created by the prover and sent as commitment to the verifier. + +The first vector $t$ is quite straightforward to think about: + +* if it is smaller than the domain (of size $n$), then we can repeat the last entry enough times to make the table of size $n$. +* if it is larger than the domain, then we can either increase the domain or split the vector in two (or more) vectors. This is most likely what we will have to do to support multiple lookup tables later. + +What about the second vector $s$? + +## The sorted vector $s$ + +We said earlier that in original plonk the size of $s$ is equal to $|s| = |f|+|t|$, where $f$ encodes queries, and $t$ encodes the lookup table. +With our multi-query approach, the second vector $s$ is of the size + +$$n \cdot |\#\text{queries}| + |\text{lookup\_table}|$$ + +That is, it contains the $n$ elements of each **query vectors** (the actual values being looked up, after being combined with the joint combinator, that's $4$ per row), as well as the elements of our lookup table (after being combined as well). + +Because the vector $s$ is larger than the domain size $n$, it is split into several vectors of size $n$. Specifically, in the plonkup paper, the two halves of $s$, which are then interpolated as $h_1$ and $h_2$. +The denominator in plonk in the vector form is +$$ +\big(\gamma(1+\beta) + s_{i-1} + \beta s_{i}\big)\big(\gamma(1+\beta)+s_{n+i-1} + \beta s_{n+i}\big) +$$ +which, when interpolated into $h_1$ and $h_2$, becomes +$$ +\big(\gamma(1+\beta) + h_1(x) + \beta h_1(g \cdot x)\big)\big(\gamma(1+\beta) + h_2(x) + \beta h_2(g \cdot x)\big) +$$ + +Since one has to compute the difference of every contiguous pairs, the last element of the first half is the replicated as the first element of the second half ($s_{n-1} = s_{n}$). +Hence, a separate constraint must be added to enforce that continuity on the interpolated polynomials $h_1$ and $h_2$: + +$$L_{n-1}(X)\cdot\big(h_1(X) - h_2(g \cdot X)\big) \equiv 0$$ + +which is equivalent to checking that $h_1(g^{n-1}) = h_2(1)$. + +## The sorted vector $s$ in kimchi + +Since this vector is known only by the prover, and is evaluated as part of the protocol, zero-knowledge must be added to the corresponding polynomial (in case of plookup approach, to $h_1(X),h_2(X)$). To do this in kimchi, we use the same technique as with the other prover polynomials: we randomize the last evaluations (or rows, on the domain) of the polynomial. + +This means two things for the lookup grand product argument: + +1. We cannot use the wrap around trick to make sure that the list is split in two correctly (enforced by $L_{n-1}(h_1(x) - h_2(g \cdot x)) = 0$ which is equivalent to $h_1(g^{n-1}) = h_2(1)$ in the plookup paper) +2. We have even less space to store an entire query vector. Which is actually super correct, as the witness also has some zero-knowledge rows at the end that should not be part of the queries anyway. + +The first problem can be solved in two ways: + +* **Zig-zag technique**. By reorganizing $s$ to alternate its values between the columns. For example, $h_1 = (s_0, s_2, s_4, \cdots)$ and $h_2 = (s_1, s_3, s_5, \cdots)$ so that you can simply write the denominator of the grand product argument as + $$(\gamma(1+\beta) + h_1(x) + \beta h_2(x))(\gamma(1+\beta)+ h_2(x) + \beta h_1(x \cdot g))$$ + Whis approach is taken by the [plonkup](https://eprint.iacr.org/2022/086) paper. +* **Snake technique**. By reorganizing $s$ as a snake. This is what is currently implemented in kimchi. + +The snake technique rearranges $s$ into the following shape: + +``` + __ _ + s_0 | s_{2n-1} | | | | + ... | ... | | | | + s_{n-1} | s_n | | | | + ‾‾‾‾‾‾‾‾‾‾‾ ‾‾ ‾ + h1 h2 h3 ... +``` + +Assuming that for now we have only one bend and two polynomials $h_1(X),h_2(X)$, the denominator has the following form: + +$$\big(\gamma(1+\beta) + h_1(x) + \beta h_1(x \cdot g)\big)\big(\gamma(1+\beta)+ h_2(x \cdot g) + \beta h_2(x)\big)$$ + +and the snake doing a U-turn is constrained via $s_{n-1} = s_n$, enforced by the following equation: + +$$L_{n-1} \cdot (h_1(x) - h_2(x)) = 0$$ + +In practice, $s$ will have more sections than just two. +Assume that we have $k$ sections in total, then the denominator generalizes to + +$$ +\prod_{i=1}^k \gamma(1+\beta) + h_i(x \cdot g^{\delta_{0,\ i \text{ mod } 2}}) + \beta h_i(x \cdot g^{\delta_{1,\ i \text{ mod } 2}}) +$$ + +where $\delta_{i,j}$ is Kronecker delta, equal to $1$ when $i$ is even (for the first term) or odd (for the second one), and equal to $0$ otherwise. + +Similarly, the U-turn constraints now become + +$$ +\begin{align*} +L_{n-1}(X) \cdot (h_2(X) - h_1(X)) &\equiv 0\\ +\color{green}L_{0}(X) \cdot (h_3(X) - h_2(X)) &\color{green}\equiv 0\\ +\color{green}L_{n-1}(X) \cdot (h_4(X) - h_3(X)) &\color{green}\equiv 0\\ +\ldots +\end{align*} +$$ + +In our concrete case with $4$ simultaneous lookups the vector $s$ has to be split into $k= 5$ sections --- each denominator term in the accumulator accounts for $4$ queries ($f$) and $1$ table consistency check ($t$). + +## Unsorted $t$ in $s$ + +Note that at setup time, $t$ cannot be sorted lexicographically as it is not combined yet. Since $s$ must be sorted by $t$ (in other words sorting of $s$ must follow the elements of $t$), there are two solutions: + +1. Both the prover and the verifier can sort the combined $t$ lexicographically, so that $s$ can be sorted lexicographically too using typical sorting algorithms +2. The prover can directly sort $s$ by $t$, so that the verifier doesn't have to do any pre-sorting and can just rely on the commitment of the columns of $t$ (which the prover can evaluate in the protocol). + +We take the second approach. +However, this must be done carefully since the combined $t$ entries can repeat. For some $i, l$ such that $i \neq l$, we might have + +$$ +t_0[i] + j \cdot t_1[i] + j^2 \cdot t_2[i] = t_0[l] + j \cdot t_1[l] + j^2 \cdot t_2[l] +$$ + +For example, if $f = \{1, 2, 2, 3\}$ and $t = \{2, 1, 2, 3\}$, then $\text{sorted}(f, t) = \{2, 2, 2, 1, 1, 2, 3, 3\}$ would be a way of correctly sorting the combined vector $s$. At the same time $\text{sorted}(f, t) = \{ 2, 2, 2, 2, 1, 1, 3, 3 \}$ is incorrect since it does not have a second block of $2$s, and thus such an $s$ is not sorted by $t$. + + +## Recap + +So to recap, to create the sorted polynomials $h_i$, the prover: + +1. creates a large query vector which contains the concatenation of the $4$ per-row (combined with the joint combinator) queries (that might contain dummy queries) for all rows +2. creates the (combined with the joint combinator) table vector +3. sorts all of that into a big vector $s$ +4. divides that vector $s$ into as many $h_i$ vectors as a necessary following the snake method +5. interpolate these $h_i$ vectors into $h_i$ polynomials +6. commit to them, and evaluate them as part of the protocol. diff --git a/book/src/plonk/maller_15.md b/book/src/kimchi/maller_15.md similarity index 100% rename from book/src/plonk/maller_15.md rename to book/src/kimchi/maller_15.md diff --git a/book/src/plonk/zkpm.md b/book/src/kimchi/zkpm.md similarity index 100% rename from book/src/plonk/zkpm.md rename to book/src/kimchi/zkpm.md diff --git a/book/src/rfcs/3-lookup.md b/book/src/rfcs/3-lookup.md deleted file mode 100644 index 6f256e6a6f..0000000000 --- a/book/src/rfcs/3-lookup.md +++ /dev/null @@ -1,291 +0,0 @@ -# RFC: Plookup in kimchi - -In 2020, [plookup](https://eprint.iacr.org/2020/315.pdf) showed how to create lookup proofs. Proofs that some witness values are part of a [lookup table](https://en.wikipedia.org/wiki/Lookup_table). Two years later, an independent team published [plonkup](https://eprint.iacr.org/2022/086) showing how to integrate Plookup into Plonk. - -This document specifies how we integrate plookup in kimchi. It assumes that the reader understands the basics behind plookup. - -## Overview - -We integrate plookup in kimchi with the following differences: - -* we snake-ify the sorted table instead of wrapping it around (see later) -* we allow fixed-ahead-of-time linear combinations of columns of the queries we make -* we only use a single table (XOR) at the moment of this writing -* we allow several lookups (or queries) to be performed within the same row -* zero-knowledgeness is added in a specific way (see later) - -The following document explains the protocol in more detail - -### Recap on the grand product argument of plookup - -As per the Plookup paper, the prover will have to compute three vectors: - -* $f$, the (secret) **query vector**, containing the witness values that the prover wants to prove are part of the lookup table. -* $t$, the (public) **lookup table**. -* $s$, the (secret) concatenation of $f$ and $t$, sorted by $t$ (where elements are listed in the order they are listed in $t$). - -Essentially, plookup proves that all the elements in $f$ are indeed in the lookup table $t$ if and only if the following multisets are equal: - -* $\{(1+\beta)f, \text{diff}(t)\}$ -* $\text{diff}(\text{sorted}(f, t))$ - -where $\text{diff}$ is a new set derived by applying a "randomized difference" between every successive pairs of a vector. For example: - -* $f = \{5, 4, 1, 5\}$ -* $t = \{1, 4, 5\}$ -* $\{\color{blue}{(1+\beta)f}, \color{green}{\text{diff}(t)}\} = \{\color{blue}{(1+\beta)5, (1+\beta)4, (1+\beta)1, (1+\beta)5}, \color{green}{1+\beta 4, 4+\beta 5}\}$ -* $\text{diff}(\text{sorted}(f, t)) = \{1+\beta 1, 1+\beta 4, 4+\beta 4, 4+\beta 5, 5+\beta 5, 5+\beta 5\}$ - -> Note: This assumes that the lookup table is a single column. You will see in the next section how to address lookup tables with more than one column. - -The equality between the multisets can be proved with the permutation argument of plonk, which would look like enforcing constraints on the following accumulator: - -* init: $acc_0 = 1$ -* final: $acc_n = 1$ -* for every $0 < i \leq n$: - $$ - acc_i = acc_{i-1} \cdot \frac{(\gamma + (1+\beta) f_{i-1})(\gamma + t_{i-1} + \beta t_i)}{(\gamma + s_{i-1} + \beta s_{i})} - $$ - -Note that the plookup paper uses a slightly different equation to make the proof work. I believe the proof would work with the above equation, but for simplicity let's just use the equation published in plookup: - -$$ -acc_i = acc_{i-1} \cdot \frac{(1+\beta)(\gamma + f_{i-1})(\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})} -$$ - -> Note: in plookup $s$ is too large, and so needs to be split into multiple vectors to enforce the constraint at every $i \in [[0;n]]$. We ignore this for now. - -### Lookup tables - -Kimchi uses a single **lookup table** at the moment of this writing; the XOR table. The XOR table for values of 1 bit is the following: - - -| l | r | o | -| --- | --- | --- | -| 1 | 0 | 1 | -| 0 | 1 | 1 | -| 1 | 1 | 0 | -| 0 | 0 | 0 | - -Whereas kimchi uses the XOR table for values of 4 bits, which has $2^{8}$ entries. - -Note: the (0, 0, 0) **entry** is at the very end on purpose (as it will be used as dummy entry for rows of the witness that don't care about lookups). - -### Querying the table - -The plookup paper handles a vector of lookups $f$ which we do not have. So the first step is to create such a table from the witness columns (or registers). To do this, we define the following objects: - -* a **query** tells us what registers, in what order, and scaled by how much, are part of a query -* a **query selector** tells us which rows are using the query. It is pretty much the same as a [gate selector](). - -Let's go over the first item in this section. - -For example, the following **query** tells us that we want to check if $r_0 \oplus r_2 = 2r_1$ - -| l | r | o | -| :---: | :---: | :---: | -| 1, r0 | 1, r2 | 2, r1 | - -The grand product argument for the lookup consraint will look like this at this point: - -$$ -acc_i = acc_{i-1} \cdot \frac{\color{green}{(1+\beta)(\gamma + w_0(g^i) + j \cdot w_2(g^i) + j^2 \cdot 2 \cdot w_1(g^i))}(\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})} -$$ - -Not all rows need to perform queries into a lookup table. We will use a query selector in the next section to make the constraints work with this in mind. - -### Query selector - -The associated **query selector** tells us on which rows the query into the XOR lookup table occurs. - -| row | query selector | -| :---: | :------------: | -| 0 | 1 | -| 1 | 0 | - - -Both the (XOR) lookup table and the query are built-ins in kimchi. The query selector is derived from the circuit at setup time. Currently only the ChaCha gates make use of the lookups. - -The grand product argument for the lookup constraint looks like this now: - -$$ -acc_i = acc_{i-1} \cdot \frac{\color{green}{(1+\beta) \cdot query} \cdot (\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})} -$$ - -where $\color{green}{query}$ is constructed so that a dummy query ($0 \oplus 0 = 0$) is used on rows that don't have a query. - -$$ -\begin{align} -query = &\ selector \cdot (\gamma + w_0(g^i) + j \cdot w_2(g^i) + j^2 \cdot 2 \cdot w_1(g^i)) + \\ -&\ (1- selector) \cdot (\gamma + 0 + j \cdot 0 + j^2 \cdot 0) -\end{align} -$$ - -### Queries, not query - -Since we allow multiple queries per row, we define multiple **queries**, where each query is associated with a **lookup selector**. - -At the moment of this writing, the `ChaCha` gates all perform $4$ queries in a row. Thus, $4$ is trivially the largest number of queries that happen in a row. - -**Important**: to make constraints work, this means that each row must make 4 queries. (Potentially some or all of them are dummy queries.) - -For example, the `ChaCha0`, `ChaCha1`, and `ChaCha2` gates will apply the following 4 XOR queries on the current and following rows: - -| l | r | o | - | l | r | o | - | l | r | o | - | l | r | o | -| :---: | :---: | :----: | --- | :---: | :---: | :----: | --- | :---: | :---: | :----: | --- | :---: | :----: | :----: | -| 1, r3 | 1, r7 | 1, r11 | - | 1, r4 | 1, r8 | 1, r12 | - | 1, r5 | 1, r9 | 1, r13 | - | 1, r6 | 1, r10 | 1, r14 | - -which you can understand as checking for the current and following row that - -* $r_3 \oplus r7 = r_{11}$ -* $r_4 \oplus r8 = r_{12}$ -* $r_5 \oplus r9 = r_{13}$ -* $r_6 \oplus r10 = r_{14}$ - -The `ChaChaFinal` also performs $4$ (somewhat similar) queries in the XOR lookup table. In total this is 8 different queries that could be associated to 8 selector polynomials. - -### Grouping queries by queries pattern - -Associating each query with a selector polynomial is not necessarily efficient. To summarize: - -* the `ChaCha0`, `ChaCha1`, and `ChaCha2` gates that make $4$ queries into the XOR table -* the `ChaChaFinal` gate makes $4$ different queries into the XOR table - -Using the previous section's method, we'd have to use $8$ different lookup selector polynomials for each of the different $8$ queries. Since there's only $2$ use-cases, we can simply group them by **queries patterns** to reduce the number of lookup selector polynomials to $2$. - -The grand product argument for the lookup constraint looks like this now: - -$$ -acc_i = acc_{i-1} \cdot \frac{\color{green}{(1+\beta)^4 \cdot query} \cdot (\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})} -$$ - -where $\color{green}{query}$ is constructed as: - -$$ -\begin{align} -query = &\ selector_1 \cdot pattern_1 + \\ -&\ selector_2 \cdot pattern_2 + \\ -&\ (1 - selector_1 - selector_2) \cdot (\gamma + 0 + j \cdot 0 + j^2 \cdot 0)^4 -\end{align} -$$ - -where, for example the first pattern for the `ChaCha0`, `ChaCha1`, and `ChaCha2` gates looks like this: - -$$ -\begin{align} -pattern_1 = &\ (\gamma + w_3(g^i) + j \cdot w_7(g^i) + j^2 \cdot w_{11}(g^i)) \cdot \\ -&\ (\gamma + w_4(g^i) + j \cdot w_8(g^i) + j^2 \cdot w_{12}(g^i)) \cdot \\ -&\ (\gamma + w_5(g^i) + j \cdot w_9(g^i) + j^2 \cdot w_{13}(g^i)) \cdot \\ -&\ (\gamma + w_6(g^i) + j \cdot w_{10}(g^i) + j^2 \cdot w_{14}(g^i)) \cdot \\ -\end{align} -$$ - -Note: - -* there's now 4 dummy queries, and they only appear when none of the lookup selectors are active -* if a pattern uses less than 4 queries, they'd have to pad their queries with dummy queries as well - -## Back to the grand product argument - -There are two things that we haven't touched on: - -* The vector $t$ representing the **combined lookup table** (after its columns have been combined with a joint combiner $j$). The **non-combined loookup table** is fixed at setup time and derived based on the lookup tables used in the circuit (for now only one, the XOR lookup table, can be used in the circuit). -* The vector $s$ representing the sorted multiset of both the queries and the lookup table. This is created by the prover and sent as commitment to the verifier. - -The first vector $t$ is quite straightforward to think about: - -* if it is smaller than the domain (of size $n$), then we can repeat the last entry enough times to make the table of size $n$. -* if it is larger than the domain, then we can either increase the domain or split the vector in two (or more) vectors. This is most likely what we will have to do to support multiple lookup tables later. - -What about the second vector? - -## The sorted vector $s$ - -The second vector $s$ is of size - -$$n \cdot |\text{queries}| + |\text{lookup\_table}|$$ - -That is, it contains the $n$ elements of each **query vectors** (the actual values being looked up, after being combined with the joint combinator, that's $4$ per row), as well as the elements of our lookup table (after being combined as well). - -Because the vector $s$ is larger than the domain size $n$, it is split into several vectors of size $n$. Specifically, in the plonkup paper, the two halves of $s$ (which are then interpolated as $h_1$ and $h_2$). - -$$ -acc_i = acc_{i-1} \cdot \frac{\color{green}{(1+\beta)^4 \cdot query} \cdot (\gamma(1 + \beta) + t_{i-1} + \beta t_i)}{(\gamma(1+\beta) + s_{i-1} + \beta s_{i})(\gamma(1+\beta)+s_{n+i-1} + \beta s_{n+i})} -$$ - -Since you must compute the difference of every contiguous pairs, the last element of the first half is the replicated as the first element of the second half ($s_{n-1} = s_{n}$), and a separate constraint enforces that continuity on the interpolated polynomials $h_1$ and $h_2$: - -$$L_{n-1}(h_1(x) - h_2(g \cdot x)) = 0$$ - -which is equivalent with checking that - -$$h_1(g^{n-1}) = h_2(1)$$ - -## The sorted vector $s$ in kimchi - -Since this vector is known only by the prover, and is evaluated as part of the protocol, zero-knowledge must be added to the polynomial. To do this in kimchi, we use the same technique as with the other prover polynomials: we randomize the last evaluations (or rows, on the domain) of the polynomial. - -This means two things for the lookup grand product argument: - -1. we cannot use the wrap around trick to make sure that the list is split in two correctly (enforced by $L_{n-1}(h_1(x) - h_2(g \cdot x)) = 0$ which is equivalent to $h_1(g^{n-1}) = h_2(1)$ in the plookup paper) -2. we have even less space to store an entire query vector. Which is actually super correct, as the witness also has some zero-knowledge rows at the end that should not be part of the queries anyway. - -The first problem can be solved in two ways: - -* **Zig-zag technique**. By reorganizing $s$ to alternate its values between the columns. For example, $h_1 = (s_0, s_2, s_4, \cdots)$ and $h_2 = (s_1, s_3, s_5, \cdots)$ so that you can simply write the denominator of the grand product argument as - $$(\gamma(1+\beta) + h_1(x) + \beta h_2(x))(\gamma(1+\beta)+ h_2(x) + \beta h_1(x \cdot g))$$ - this is what the [plonkup](https://eprint.iacr.org/2022/086) paper does. -* **Snake technique**. by reorganizing $s$ as a snake. This is what is done in kimchi currently. - -The snake technique rearranges $s$ into the following shape: - -``` - _ _ - | | | | | - | | | | | - |_| |_| | -``` - -so that the denominator becomes the following equation: - -$$(\gamma(1+\beta) + h_1(x) + \beta h_1(x \cdot g))(\gamma(1+\beta)+ h_2(x \cdot g) + \beta h_2(x))$$ - -and the snake doing a U-turn is constrained via something like - -$$L_{n-1} \cdot (h_1(x) - h_2(x)) = 0$$ - -If there's an $h_3$ (because the table is very large, for example), then you'd have something like: - -$$(\gamma(1+\beta) + h_1(x) + \beta h_1(x \cdot g))(\gamma(1+\beta)+ h_2(x \cdot g) + \beta h_2(x))\color{green}{(\gamma(1+\beta)+ h_3(x) + \beta h_3(x \cdot g))}$$ - -with the added U-turn constraint: - -$$L_{0} \cdot (h_2(x) - h_3(x)) = 0$$ - -## Unsorted $t$ in $s$ - -Note that at setup time, $t$ cannot be sorted as it is not combined yet. Since $s$ needs to be sorted by $t$ (in other words, not sorted, but sorted following the elements of $t$), there are two solutions: - -1. both the prover and the verifier can sort the combined $t$, so that $s$ can be sorted via the typical sorting algorithms -2. the prover can sort $s$ by $t$, so that the verifier doesn't have to do any sorting and can just rely on the commitment of the columns of $t$ (which the prover can evaluate in the protocol). - -We do the second one, but there is an edge-case: the combined $t$ entries can repeat. -For some $i, l$ such that $i \neq l$, we might have - -$$ -t_0[i] + j t_1[i] + j^2 t_2[i] = t_0[l] + j t_1[l] + j^2 t_2[l] -$$ - -For example, if $f = \{1, 2, 2, 3\}$ and $t = \{2, 1, 2, 3\}$, then $\text{sorted}(f, t) = \{2, 2, 2, 1, 1, 2, 3, 3\}$ would be one way of sorting things out. But $\text{sorted}(f, t) = \{ 2, 2, 2, 2, 1, 1, 3, 3 \}$ would be incorrect. - - -## Recap - -So to recap, to create the sorted polynomials $h_i$, the prover: - -1. creates a large query vector which contains the concatenation of the $4$ per-row (combined with the joint combinator) queries (that might contain dummy queries) for all rows -2. creates the (combined with the joint combinator) table vector -3. sorts all of that into a big vector $s$ -4. divides that vector $s$ into as many $h_i$ vectors as a necessary following the snake method -5. interpolate these $h_i$ vectors into $h_i$ polynomials -6. commit to them, and evaluate them as part of the protocol. From 5c8d0d2a5ea21d715a1020cde43a174790c730a6 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Thu, 2 Nov 2023 11:18:04 +0000 Subject: [PATCH 172/173] Consistency pass on book chapters / styling --- book/src/SUMMARY.md | 8 ++-- book/src/kimchi/keccak.md | 60 ++++++++++++++-------------- book/src/kimchi/permut.md | 5 +-- book/src/kimchi/zkpm.md | 64 +----------------------------- book/src/pickles/passthrough.md | 1 - book/src/plonk/maller.md | 6 +-- book/src/plonk/zkpm.md | 70 +++++++++++++++++++++++++++++++++ 7 files changed, 109 insertions(+), 105 deletions(-) delete mode 100644 book/src/pickles/passthrough.md create mode 100644 book/src/plonk/zkpm.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 4cde7e7502..f3eb4bb0f3 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -48,22 +48,21 @@ - [Lagrange basis in multiplicative subgroups](./plonk/lagrange.md) - [Non-interaction with fiat-shamir](./plonk/fiat_shamir.md) - [Plookup](./plonk/plookup.md) -- [Maller's optimization](./plonk/maller.md) +- [Maller's Optimization](./plonk/maller.md) +- [Zero-Column Approach to Zero-Knowledge](./plonk/zkpm.md) # Kimchi - [Overview](./kimchi/overview.md) - [Arguments](./kimchi/arguments.md) -- [Zero-Column Approach to Zero-Knowledge](./kimchi/zkpm.md) - [Final Check](./kimchi/final_check.md) - [Maller's Optimization for Kimchi](./kimchi/maller_15.md) -- [Permutation](./kimchi/permut.md) - [Lookup Tables](./kimchi/lookup.md) - [Extended Lookup Tables](./kimchi/extended-lookup-tables.md) - [Custom Gates](./kimchi/gates.md) - [Foreign Field Addition](./kimchi/foreign_field_add.md) - [Foreign Field Multiplication](./kimchi/foreign_field_mul.md) - - [Keccak](./rfcs/keccak.md) + - [Keccak](./kimchi/keccak.md) # Pickles & Inductive Proof Systems @@ -71,7 +70,6 @@ - [Overview](./fundamentals/zkbook_ips.md) - [Accumulation](./pickles/accumulation.md) - [Deferred Computation](./pickles/deferred.md) -- [Passthough & Me-Only](./pickles/passthrough.md) # Technical Specifications diff --git a/book/src/kimchi/keccak.md b/book/src/kimchi/keccak.md index 457c247b97..9c00ad6d7c 100644 --- a/book/src/kimchi/keccak.md +++ b/book/src/kimchi/keccak.md @@ -1,4 +1,4 @@ -# RFC: Keccak +# Keccak Gate The Keccak gadget is comprised of 3 circuit gates (`Xor16`, `Rot64`, and `Zero`) @@ -45,9 +45,9 @@ Kimchi framework that dictated those approaches. ## Rot64 -It is clear from the definition of the rotation gate that its constraints are complete +It is clear from the definition of the rotation gate that its constraints are complete (meaning that honest instances always satisfy the constraints). It is left to be proven -that the proposal is sound. In this section, we will give a proof that as soon as we +that the proposal is sound. In this section, we will give a proof that as soon as we perform the range checks on the excess and shifted parts of the input, only one possible assignment satisfies the constraints. This means that there is no dishonest instance that can make the constraints pass. We will also give an example where one could find wrong @@ -55,10 +55,10 @@ rotation witnesses that would satisfy the constraints if we did not check the ra ### Necessity of range checks -First of all, we will illustrate the necessity of range-checks with a simple example. -For the sake of readability, we will use some toy field lengths. In particular, let us -assume that our words have 4 bits, meaning all of the elements between `0x0` and `0xF`. -Next, we will be using the native field $\mathbb{F}_{32}$. +First of all, we will illustrate the necessity of range-checks with a simple example. +For the sake of readability, we will use some toy field lengths. In particular, let us +assume that our words have 4 bits, meaning all of the elements between `0x0` and `0xF`. +Next, we will be using the native field $\mathbb{F}_{32}$. As we will later see, this choice of field lengths is not enough to perform any 4-bit rotation, since the operations in the constraints would overflow the native field. @@ -85,52 +85,50 @@ $$ $$ We can easily check that the proposed values of the shifted `0b1010=10` and the excess -`0b1=1` values satisfy the above constraint because $26 = 1 \cdot 16 + 10$ and $11 = 1 + 10$. +`0b1=1` values satisfy the above constraint because $26 = 1 \cdot 16 + 10$ and $11 = 1 + 10$. Now, the question is: _can we find another value for excess and shifted, such that their addition results in an incorrect rotated word?_ The answer to this question is yes, due to __diophantine equations__. We basically want to find $x,y$ such that $26 = x \cdot 16 + y (\text{ mod } 32)$. The solution to this equation is: $$ \begin{align*} -\forall k \in [0..31]: & \\ -x &= k \\ +\forall k \in [0 \ldots 31]: x &= k \ \land \\ y &= 26 - 16 \cdot k \end{align*} $$ We chose these word and field lengths to better understand the behaviour of the solution. Here, we can see two "classes" of evaluations. -- If we choose an even $k$, then $y$ will have the following shape: +- If we choose an even $k$, then $y$ will have the following shape: - $$26 - 16 \cdot (2 \cdot n) \iff 26 - 32n \equiv_{32} 26 $$ - Meaning, if $x = 2n$ then $y = 26$. - If on the other hand, we chose an odd $k$, then $y$ will have the following shape instead: - $$26 - 16 \cdot (2 \cdot n + 1) \iff 26 - 32n - 16 \equiv_{32} 26 - 16 = 10$$ - - Meaning, if $x = 2n+1$ then $y = 10$. + - Meaning, if $x = 2n+1$ then $y = 10$. Thus, possible solutions to the diophantine equation are: $$ \begin{align*} -x &= 0, 1, 2, 3, 4, 5... \\ -y &= 26, 10, 26, 10, 26, 10... +x &= 0, 1, 2, 3, 4, 5 \ldots \\ +y &= 26, 10, 26, 10, 26, 10 \ldots \end{align*} $$ -Note that our valid witness is part of that set of solutions, meaning $x=1$ and $y=10$. Of course, we can also find another dishonest instantiation such as $x=0$ and $y=26$. Perhaps one could think that we do not need to worry about this case, because the resulting rotation word would be $0+26=26$, and if we later use that result as an input to a subsequent gate such as XOR, the value $26$ would not fit and at some point the constraint system would complain. Nonetheless, we still have other solutions to worry about, such as $(x=3, y=10)$ or $(x=5, y=10)$, since they would result in a rotated word that would fit in the word length of 4 bits, yet would be incorrect (not equal to $11$). +Note that our valid witness is part of that set of solutions, meaning $x=1$ and $y=10$. Of course, we can also find another dishonest instantiation such as $x=0$ and $y=26$. Perhaps one could think that we do not need to worry about this case, because the resulting rotation word would be $0+26=26$, and if we later use that result as an input to a subsequent gate such as XOR, the value $26$ would not fit and at some point the constraint system would complain. Nonetheless, we still have other solutions to worry about, such as $(x=3, y=10)$ or $(x=5, y=10)$, since they would result in a rotated word that would fit in the word length of 4 bits, yet would be incorrect (not equal to $11$). -All of the above incorrect solutions differ in one thing: they have different bit lengths. This means that we need to range check the values for the excess and shifted witnesses to make sure they have the correct length. +All of the above incorrect solutions differ in one thing: they have different bit lengths. This means that we need to range check the values for the excess and shifted witnesses to make sure they have the correct length. ### Sufficiency of range checks -In the following, we will give a proof that performing range checks for these values is not only necessary but also sufficient to prove that the rotation gate is sound. In other words, we will prove there are no two possible solutions of the decomposition constraint that have the correct bit lengths. Now, for the sake of robustness, we will consider 64-bit words and fields with at least twice the bit length of the words (as is our case). +In the following, we will give a proof that performing range checks for these values is not only necessary but also sufficient to prove that the rotation gate is sound. In other words, we will prove there are no two possible solutions of the decomposition constraint that have the correct bit lengths. Now, for the sake of robustness, we will consider 64-bit words and fields with at least twice the bit length of the words (as is our case). We will proceed by __contradiction__. Suppose there are two different solutions to the following diophantic equation: $$ \begin{align*} -\forall k \in \mathbb{F}_n: & \\ -x &= k \\ +\forall k \in \mathbb{F}_n: x &= k \ \land \\ y &= w \cdot 2^r - 2^{64} \cdot k \end{align*} $$ @@ -139,7 +137,7 @@ where $k$ is a parameter to instantiate the solutions, $w$ is the word to be rot Then, that means that there are two different solutions, $(0\leq x=a<2^r, 0\leq y=b<2^{64})$ and $(0\leq x=a'<2^r, 0\leq y=b'<2^{64})$ with at least $a \neq a'$ or $b \neq b'$. We will show that this is impossible. -If both are solutions to the same equation, then: +If both are solutions to the same equation, then: $$ \begin{align*} w \cdot 2^r &= a \cdot 2^{64} + b \\ @@ -149,18 +147,22 @@ $$ means that $a \cdot 2^{64} + b = a'\cdot 2^{64} + b'$. Moving terms to the left side, we have an equivalent equality: $2^{64}(a-a') + (b-b')=0 \mod{n}$. There are three cases to consider: - $a = a'$ and $b \neq b'$: then $(b - b') \equiv_n 0$ and this can only happen if $b' = b + kn$. But since $n > 2^{64}$, then $b'$ cannot be smaller than $2^{64}$ as it was assumed. CONTRADICTION. - + - $b = b'$ and $a \neq a'$: then $2^{64}(a - a') \equiv_n 0$ and this can only happen if $a' = a + kn$. But since $n > 2^r$, then $a'$ cannot be smaller than $2^r$ as it was assumed. CONTRADICTION. -- $a\neq a'$ and $b \neq b'$: then we have something like $2^{64} \alpha + \beta \equiv_n 0$. +- $a\neq a'$ and $b \neq b'$: then we have something like $2^{64} \alpha + \beta \equiv_n 0$. - This means $\beta \equiv_n -2^{64} \alpha = k \cdot n - 2^{64} \alpha$ for any $k$. - - According to the assumption, both $0\leq a<2^r$ and $0\leq a'<2^r$. This means, the difference $\alpha:=(a - a')$ lies anywhere in between the following interval: - - $$1 - 2^r \leq \alpha \leq 2^r - 1$$ + - According to the assumption, both $0\leq a<2^r$ and $0\leq a'<2^r$. This means, the difference $\alpha:=(a - a')$ lies anywhere in between the following interval: + $$1 - 2^r \leq \alpha \leq 2^r - 1$$ - We plug in this interval to the above equation to obtain the following interval for $\beta$: - - $$k\cdot n - 2^{64}(1-2^r)\leq \beta \leq k\cdot n - 2^{64}(2^r - 1) $$ + $$k\cdot n - 2^{64}(1-2^r)\leq \beta \leq k\cdot n - 2^{64}(2^r - 1) $$ - We look at this interval from both sides of the inequality: $\beta \geq kn - 2^{64} + 2^{64+r}$ and $\beta \leq kn + 2^{64} - 2^{64+r}$ and we wonder if $kn - 2^{64} + 2^{64+r} \leq kn + 2^{64} - 2^{64+r}$ is at all possible. We rewrite as follows: - - $$2^{64+r} - 2^{64} \leq 2^{64} - 2^{64+r}$$ - - $$2\cdot2^{64+r} \leq 2\cdot2^{64} $$ - - $$2^{64+r} \leq 2^{64} $$ - - But this can only happen if $r\leq 0$, which is impossible since we assume $0 < r < 64$.CONTRADICTION. + $$ + \begin{align*} + 2^{64+r} - 2^{64} &\leq 2^{64} - 2^{64+r}\\ + 2\cdot2^{64+r} &\leq 2\cdot2^{64} \\ + 2^{64+r} &\leq 2^{64} + \end{align*} + $$ + - But this can only happen if $r\leq 0$, which is impossible since we assume $0 < r < 64$. CONTRADICTION. - EOP. diff --git a/book/src/kimchi/permut.md b/book/src/kimchi/permut.md index 3d62293eae..cc82360ef6 100644 --- a/book/src/kimchi/permut.md +++ b/book/src/kimchi/permut.md @@ -1,4 +1 @@ -## Permutation - -TO-DO - +# Permutation diff --git a/book/src/kimchi/zkpm.md b/book/src/kimchi/zkpm.md index a4a1ba291c..947a334538 100644 --- a/book/src/kimchi/zkpm.md +++ b/book/src/kimchi/zkpm.md @@ -1,63 +1 @@ -# A More Efficient Approach to Zero Knowledge for PLONK - -In PLONK (considered as an interactive oracle proof), the prover sends the verifier several polynomials. They are evaluated at some $k$ points during the course of the protocol. Of course, if we want zero-knowledge, we would require that those evaluations do not reveal anything about the proof's underlying witness. - -PLONK as described here achieves zero knowledge by multiplying the polynomials with a small degree polynomial of random coefficients. When PLONK is instantiated with a discrete-log base Bootle et al type polynomial commitment scheme, the polynomial degrees must be padded to the nearest power of two. As a result, since several of the polynomials in PLONK already have degree equal to a power of two before the zero-knowledge masking, the multiplication with a random polynomial pushes the degree to the next power of two, which hurts efficiency. In order to avoid it, we propose an alternative method of achieving zero knowledge. - -## Zero Knowledge for the Column Polynomials - -Let $w$ be the number of rows in a PLONK constraint system. For a typical real-world circuit, $w$ will not be equal to a power of two. - -Let the witness elements from one column be $s_1, s_2, \ldots, s_w$. Let $n$ be the closest power of two to $w$ such that $n \geq w$. Let $\mathbb{F}$ be the field that witness elements belong to. - -Now, in vanilla PLONK, we pad the $s_i$ with $n - w$ elements, interpolate the polynomial over a domain of size $n$, scale it by a low degree random polynomial, and commit to the resulting polynomial. We want to avoid increasing the degree with the scaling by a low degree polynomial, so consider the following procedure. - -**Procedure.** Sample $k$ elements uniformly from $\mathbb{F}$: $r_{w+1}, \ldots, r_{w+k}$. Append them to the tuple of witness elements and then pad the remaining $n - (w+k)$ places as zeroes. The resulting tuple is interpolated as the witness polynomial. This approach ensures zero knowledge for the witness polynomials as established by Lemma 1. - -**Lemma 1.** Let $H \subset \mathbb{F}$ be a domain of size $n$. Let $s_1, s_2, \ldots, s_w\in \mathbb{F}$. Let $r_{w+1}, \ldots, r_{w+k}$ be $k$ uniformly and independently random elements in $\mathbb{F}.$ Let $\mathbf{v}$ be the $n$-tuple $\mathbf{v} = (s_1, s_2, \ldots, s_w, r_{w+1}, \ldots, r_{w+k}, 0,\ldots_{\text{n - (w+k) times}})$. -Let $f(X)$ be an interpolation polynomial of degree $n-1$ such that $f(h_i) = v_i$, where $h_i \in H$. Let $c_1, \ldots, c_k$ be any elements in $\mathbb{F}$ such that $c_i \neq v_j$ for every $i,j$. Then, $(f(c_1), \ldots, f(c_k))$ is distributed uniformly at random in $\mathbb{F}^k$. - -**Proof sketch.** Recall that the interpolation polynomial is - -$f(X) = \sum_{j = 1}^n \prod_{k \neq j} \frac{(X-h_k)}{(h_j-h_k)} v_j$ - -With $V_{w+1}, \ldots, V_{w+k}$ as random variables, we have, -$f(X) = a_{w+1} V_{w+1} + a_{w+2} V_{w+2} + \ldots + a_{w+k} V_{w+k} + a$ -for some constant field elements $a, a_{w+1}, \ldots, a_{w+k}$. Therefore, assigning random values to $V_{w+1}, \ldots, V_{w+k}$ will give $k$ degrees of freedom that will let $(f(c_1), \ldots, f(c_k))$ to be distributed uniformly at random in $\mathbb{F}^k$. - -## Zero Knowledge for the Permutation Polynomial - -The other polynomial in PLONK for which we need zero-knowledge is the "permutation polynomial" $z$. -The idea here is to set the last $k$ evaluations to be uniformly random elements $t_1, \ldots, t_k$ in $\mathbb{F}$. Then, we'll modify the verification equation to not check for those values to satisfy the permutation property. - -**Modified permutation polynomial.** Specifically, set $z(X)$ as follows. - -$z(X) = L_1(X) + \sum_{i = 1}^{\blue{n-k-2}} \left(L_{i+1} \prod_{j=1}^i \mathsf{frac}_{i,j} \right) + \blue{t_1 L_{n-k}(X) + \ldots + t_k L_{n}(X) }$ - -From Lemma 1, the above $z(X)$ has the desired zero knowledge property when $k$ evaluations are revealed. However, we need to modify the other parts of the protocol so that the last $k$ elements are not subject to the permutation evaluation, since they will no longer satisfy the permutation check. Specifically, we will need to modify the permutation polynomial to disregard those random elements, as follows. - -$ \begin{aligned} & t(X) = \\ - & (a(X)b(X)q_M(X) + a(X)q_L(X) + b(X)q_R(X) + c(X)q_O(X) + PI(X) + q_C(X)) \frac{1}{z_H(X)} \\ - &+ ((a(X) + \beta X + \gamma)(b(X) + \beta k_1 X + \gamma)(c(X) + \beta k_2X + \gamma)z(X) \blue{(X-h_{n-k}) \ldots (X-h_{n-1})(X-h_n)} ) \frac{\alpha}{z_{H}(X)} \\ - & - ((a(X) + \beta S_{\sigma1}(X) + \gamma)(b(X) + \beta S_{\sigma2}(X) + \gamma)(c(X) + \beta S_{\sigma3}(X) + \gamma)z(X\omega) \blue{(X-h_{n-k}) \ldots (X-h_{n-1})(X-h_n)}) \frac{\alpha}{z_{H}(X)} \\ - & + (z(X)-1)L_1(X) \frac{\alpha^2}{z_H(X)} \\ - & + \blue{(z(X)-1)L_{n-k}(X) \frac{\alpha^3}{z_H(X)} } \end{aligned} $ - -**Modified permutation checks.** To recall, the permutation check was originally as follows. For all $h \in H$, - -* $L_1(h)(Z(h) - 1) = 0$ -* $Z(h)[(a(h) + \beta h + \gamma)(b(h) + \beta k_1 h + \gamma)(c(h) + \beta k_2 h + \gamma)] \\ - = Z(\omega h)[(a(h) + \beta S_{\sigma1}(h) + \gamma)(b(h) + \beta S_{\sigma2}(h) + \gamma)(c(h) + \beta S_{\sigma3}(h) + \gamma)]$ - - - -The modified permuation checks that ensures that the check is performed only on all the values except the last $k$ elements in the witness polynomials are as follows. - -* For all $h \in H$, $L_1(h)(Z(h) - 1) = 0$ -* For all $h \in \blue{H\setminus \{h_{n-k}, \ldots, h_n\}}$, $\begin{aligned} & Z(h)[(a(h) + \beta h + \gamma)(b(h) + \beta k_1 h + \gamma)(c(h) + \beta k_2 h + \gamma)] \\ - &= Z(\omega h)[(a(h) + \beta S_{\sigma1}(h) + \gamma)(b(h) + \beta S_{\sigma2}(h) + \gamma)(c(h) + \beta S_{\sigma3}(h) + \gamma)] \end{aligned}$ - -* For all $h \in H$, $L_{n-k}(h)(Z(h) - 1) = 0$ - - -In the modified permutation polynomial above, the multiple $(X-h_{n-k}) \ldots (X-h_{n-1})(X-h_n)$ ensures that the permutation check is performed only on all the values except the last $k$ elements in the witness polynomials. +# Zero-Column Approach to Zero-Knowledge diff --git a/book/src/pickles/passthrough.md b/book/src/pickles/passthrough.md deleted file mode 100644 index cc182c4e51..0000000000 --- a/book/src/pickles/passthrough.md +++ /dev/null @@ -1 +0,0 @@ -# Passthrough and Me-Only diff --git a/book/src/plonk/maller.md b/book/src/plonk/maller.md index b2d97c10c8..d5e0d26965 100644 --- a/book/src/plonk/maller.md +++ b/book/src/plonk/maller.md @@ -1,4 +1,4 @@ -# Maller optimization to reduce proof size +# Maller's Optimization to Reduce Proof Size In the PLONK paper, they make use of an optimization from Mary Maller in order to reduce the proof size. @@ -19,7 +19,7 @@ Let's see the protocol where _Prover_ wants to prove to _Verifier_ that $$\forall x \in \mathbb{F}, \; h_1(x)h_2(x) - h_3(x) = 0$$ -given commitments of $h_1, h_2, h_3$, +given commitments of $h_1, h_2, h_3$, ![maller 1](../img/maller_1.png) @@ -37,7 +37,7 @@ Note right of Verifier: verifies that \n h1(s)h2(s) - h3(s) = 0 ``` --> -A shorter proof exists. Essentially, if the verifier already has the opening `h1(s)`, they can reduce the problem to showing that +A shorter proof exists. Essentially, if the verifier already has the opening `h1(s)`, they can reduce the problem to showing that $$ \forall x \in \mathbb{F}, \; L(x) = h_1(s)h_2(x) - h_3(x) = 0$$ diff --git a/book/src/plonk/zkpm.md b/book/src/plonk/zkpm.md new file mode 100644 index 0000000000..84f46f3ec0 --- /dev/null +++ b/book/src/plonk/zkpm.md @@ -0,0 +1,70 @@ +# A More Efficient Approach to Zero Knowledge for PLONK + +In PLONK (considered as an interactive oracle proof), the prover sends the verifier several polynomials. They are evaluated at some $k$ points during the course of the protocol. Of course, if we want zero-knowledge, we would require that those evaluations do not reveal anything about the proof's underlying witness. + +PLONK as described here achieves zero knowledge by multiplying the polynomials with a small degree polynomial of random coefficients. When PLONK is instantiated with a discrete-log base Bootle et al type polynomial commitment scheme, the polynomial degrees must be padded to the nearest power of two. As a result, since several of the polynomials in PLONK already have degree equal to a power of two before the zero-knowledge masking, the multiplication with a random polynomial pushes the degree to the next power of two, which hurts efficiency. In order to avoid it, we propose an alternative method of achieving zero knowledge. + +## Zero Knowledge for the Column Polynomials + +Let $w$ be the number of rows in a PLONK constraint system. For a typical real-world circuit, $w$ will not be equal to a power of two. + +Let the witness elements from one column be $s_1, s_2, \ldots, s_w$. Let $n$ be the closest power of two to $w$ such that $n \geq w$. Let $\mathbb{F}$ be the field that witness elements belong to. + +Now, in vanilla PLONK, we pad the $s_i$ with $n - w$ elements, interpolate the polynomial over a domain of size $n$, scale it by a low degree random polynomial, and commit to the resulting polynomial. We want to avoid increasing the degree with the scaling by a low degree polynomial, so consider the following procedure. + +**Procedure.** Sample $k$ elements uniformly from $\mathbb{F}$: $r_{w+1}, \ldots, r_{w+k}$. Append them to the tuple of witness elements and then pad the remaining $n - (w+k)$ places as zeroes. The resulting tuple is interpolated as the witness polynomial. This approach ensures zero knowledge for the witness polynomials as established by Lemma 1. + +**Lemma 1.** Let $H \subset \mathbb{F}$ be a domain of size $n$. Let $s_1, s_2, \ldots, s_w\in \mathbb{F}$. Let $r_{w+1}, \ldots, r_{w+k}$ be $k$ uniformly and independently random elements in $\mathbb{F}.$ Let $\mathbf{v}$ be the $n$-tuple $\mathbf{v} = (s_1, s_2, \ldots, s_w, r_{w+1}, \ldots, r_{w+k}, 0,\ldots_{\text{n - (w+k) times}})$. +Let $f(X)$ be an interpolation polynomial of degree $n-1$ such that $f(h_i) = v_i$, where $h_i \in H$. Let $c_1, \ldots, c_k$ be any elements in $\mathbb{F}$ such that $c_i \neq v_j$ for every $i,j$. Then, $(f(c_1), \ldots, f(c_k))$ is distributed uniformly at random in $\mathbb{F}^k$. + +**Proof sketch.** Recall that the interpolation polynomial is + +$$ +f(X) = \sum_{j = 1}^n \prod_{k \neq j} \frac{(X-h_k)}{(h_j-h_k)} v_j +$$ + +With $V_{w+1}, \ldots, V_{w+k}$ as random variables, we have, +$f(X) = a_{w+1} V_{w+1} + a_{w+2} V_{w+2} + \ldots + a_{w+k} V_{w+k} + a$ +for some constant field elements $a, a_{w+1}, \ldots, a_{w+k}$. Therefore, assigning random values to $V_{w+1}, \ldots, V_{w+k}$ will give $k$ degrees of freedom that will let $(f(c_1), \ldots, f(c_k))$ to be distributed uniformly at random in $\mathbb{F}^k$. + +## Zero Knowledge for the Permutation Polynomial + +The other polynomial in PLONK for which we need zero-knowledge is the "permutation polynomial" $z$. +The idea here is to set the last $k$ evaluations to be uniformly random elements $t_1, \ldots, t_k$ in $\mathbb{F}$. Then, we'll modify the verification equation to not check for those values to satisfy the permutation property. + +**Modified permutation polynomial.** Specifically, set $z(X)$ as follows. + +$$ +z(X) = L_1(X) + \sum_{i = 1}^{\blue{n-k-2}} \left(L_{i+1} \prod_{j=1}^i \mathsf{frac}_{i,j} \right) + \blue{t_1 L_{n-k}(X) + \ldots + t_k L_{n}(X) } +$$ + +From Lemma 1, the above $z(X)$ has the desired zero knowledge property when $k$ evaluations are revealed. However, we need to modify the other parts of the protocol so that the last $k$ elements are not subject to the permutation evaluation, since they will no longer satisfy the permutation check. Specifically, we will need to modify the permutation polynomial to disregard those random elements, as follows. + +$$ +\begin{aligned} & t(X) = \\ + & \Big(a(X)b(X)q_M(X) + a(X)q_L(X) + b(X)q_R(X) + c(X)q_O(X) + PI(X) + q_C(X)\Big) \frac{1}{z_H(X)} \\ + &+ \Big((a(X) + \beta X + \gamma)(b(X) + \beta k_1 X + \gamma)(c(X) + \beta k_2X + \gamma)z(X)\\ + &\qquad\qquad\qquad\times{\blue{(X-h_{n-k}) \ldots (X-h_{n-1})(X-h_n)}} \Big) \frac{\alpha}{z_{H}(X)} \\ + & - \Big((a(X) + \beta S_{\sigma1}(X) + \gamma)(b(X) + \beta S_{\sigma2}(X) + \gamma)(c(X) + \beta S_{\sigma3}(X) + \gamma)z(X\omega)\\ + &\qquad\qquad\qquad\times{\blue{(X-h_{n-k}) \ldots (X-h_{n-1})(X-h_n)}}\Big) \frac{\alpha}{z_{H}(X)} \\ + & + \Big(z(X)-1\Big)\cdot L_1(X) \frac{\alpha^2}{z_H(X)} \\ + & + \blue{\Big(z(X)-1\Big)\cdot L_{n-k}(X) \frac{\alpha^3}{z_H(X)} } \end{aligned} +$$ + +**Modified permutation checks.** To recall, the permutation check was originally as follows. For all $h \in H$, + +* $L_1(h)(Z(h) - 1) = 0$ +* $Z(h)[(a(h) + \beta h + \gamma)(b(h) + \beta k_1 h + \gamma)(c(h) + \beta k_2 h + \gamma)] \\ + = Z(\omega h)[(a(h) + \beta S_{\sigma1}(h) + \gamma)(b(h) + \beta S_{\sigma2}(h) + \gamma)(c(h) + \beta S_{\sigma3}(h) + \gamma)]$ + + +The modified permuation checks that ensures that the check is performed only on all the values except the last $k$ elements in the witness polynomials are as follows. + +* For all $h \in H$, $L_1(h)(Z(h) - 1) = 0$ +* For all $h \in \blue{H\setminus \{h_{n-k}, \ldots, h_n\}}$, $\begin{aligned} & Z(h)[(a(h) + \beta h + \gamma)(b(h) + \beta k_1 h + \gamma)(c(h) + \beta k_2 h + \gamma)] \\ + &= Z(\omega h)[(a(h) + \beta S_{\sigma1}(h) + \gamma)(b(h) + \beta S_{\sigma2}(h) + \gamma)(c(h) + \beta S_{\sigma3}(h) + \gamma)] \end{aligned}$ + +* For all $h \in H$, $L_{n-k}(h)(Z(h) - 1) = 0$ + + +In the modified permutation polynomial above, the multiple $(X-h_{n-k}) \ldots (X-h_{n-1})(X-h_n)$ ensures that the permutation check is performed only on all the values except the last $k$ elements in the witness polynomials. From 5005343e99869676e57c41d4180c2198c2e7faab Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Tue, 7 Nov 2023 21:06:43 +0000 Subject: [PATCH 173/173] Prettify sections more --- book/src/SUMMARY.md | 19 ++++++--------- book/src/fundamentals/proof_systems.md | 24 +++++++++---------- .../custom_constraints.md | 2 +- book/src/plonk/fiat_shamir.md | 8 +++---- book/src/plonk/glossary.md | 14 +++++------ 5 files changed, 30 insertions(+), 37 deletions(-) rename book/src/{fundamentals => kimchi}/custom_constraints.md (98%) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index f3eb4bb0f3..2ac4a2d8bb 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,7 +12,7 @@ - [Multiplying polynomials](./fundamentals/zkbook_multiplying_polynomials.md) - [Fast Fourier transform](./fundamentals/zkbook_fft.md) -# Cryptographic tools +# Cryptographic Tools - [Commitments](./fundamentals/zkbook_commitment.md) - [Polynomial commitments](./plonk/polynomial_commitments.md) @@ -27,26 +27,20 @@ - [Half Gate](./fundamentals/zkbook_2pc/halfgate.md) - [Full Description](./fundamentals/zkbook_2pc/fulldesc.md) - [Fixed-Key-AES Hashes](./fundamentals/zkbook_2pc/fkaes.md) - - [Oblivious Transfer](./fundamentals/zkbook_2pc/ot.md) - [Base OT](./fundamentals/zkbook_2pc/baseot.md) - [OT Extension](./fundamentals/zkbook_2pc/ote.md) - - [Full Protocol](./fundamentals/zkbook_2pc/2pc.md) - -# Proof systems - -- [Overview](./fundamentals/proof_systems.md) -- [zk-SNARKs](./fundamentals/zkbook_plonk.md) -- [Custom constraints](./fundamentals/custom_constraints.md) +- [Proof Systems](./fundamentals/proof_systems.md) + - [zk-SNARKs](./fundamentals/zkbook_plonk.md) # Background on PLONK - [Overview](./plonk/overview.md) -- [Glossary](./plonk/glossary.md) + - [Glossary](./plonk/glossary.md) - [Domain](./plonk/domain.md) -- [Lagrange basis in multiplicative subgroups](./plonk/lagrange.md) -- [Non-interaction with fiat-shamir](./plonk/fiat_shamir.md) +- [Lagrange Basis in Multiplicative Subgroups](./plonk/lagrange.md) +- [Non-Interactivity via Fiat-Shamir](./plonk/fiat_shamir.md) - [Plookup](./plonk/plookup.md) - [Maller's Optimization](./plonk/maller.md) - [Zero-Column Approach to Zero-Knowledge](./plonk/zkpm.md) @@ -59,6 +53,7 @@ - [Maller's Optimization for Kimchi](./kimchi/maller_15.md) - [Lookup Tables](./kimchi/lookup.md) - [Extended Lookup Tables](./kimchi/extended-lookup-tables.md) +- [Custom Constraints](./kimchi/custom_constraints.md) - [Custom Gates](./kimchi/gates.md) - [Foreign Field Addition](./kimchi/foreign_field_add.md) - [Foreign Field Multiplication](./kimchi/foreign_field_mul.md) diff --git a/book/src/fundamentals/proof_systems.md b/book/src/fundamentals/proof_systems.md index 84b5f8fe97..d281048974 100644 --- a/book/src/fundamentals/proof_systems.md +++ b/book/src/fundamentals/proof_systems.md @@ -1,31 +1,29 @@ -# Overview +# Proof Systems Design Overview Many modern proof systems (and I think all that are in use) are constructed according to the following recipe. 1. You start out with a class of computations. 2. You devise a way to *arithmetize* those computations. That is, to express your computation as a statement about polynomials. - + More specifically, you describe what is often called an "algebraic interactive oracle proof" (AIOP) that encodes your computation. An AIOP is a protocol describing an interaction between a prover and a verifier, in which the prover sends the verifier some "polynomial oracles" (basically a black box function that given a point evaluates a polynomial at that point), the verifier sends the prover random challenges, and at the end, the verifier queries the prover's polynomials at points of its choosing and makes a decision as to whether it has been satisfied by the proof. 3. An AIOP is an imagined interaction between parties. It is an abstract description of the protocol that will be "compiled" into a SNARK. There are several "non-realistic" aspects about it. One is that the prover sends the verifier black-box polynomials that the verifier can evaluate. These polynomials have degree comparable to the size of the computation being verified. If we implemented these "polynomial oracles" by having the prover really send the $O(n)$ size polynomials (say by sending all their coefficients), then we would not have a zk-SNARK at all, since the verifier would have to read this linearly sized polynomial so we would lose succinctness, and the polynomials would not be black-box functions, so we may lose zero-knowledge. - + Instead, when we concretely instantiate the AIOP, we have the prover send constant-sized, hiding *polynomial commitments*. Then, in the phase of the AIOP where the verifier queries the polynomials, the prover sends an *opening proof* for the polynomial commitments which the verifier can check, thus simulating the activity of evaluating the prover's polynomials on your own. - + So this is the next step of making a SNARK: instantiating the AIOP with a polynomial commitment scheme of one's choosing. There are several choices here and these affect the properties of the SNARK you are constructing, as the SNARK will inherit efficiency and setup properties of the polynomial commitment scheme used. -4. An AIOP describes an interactive protocol between the verifier and the prover. In reality, typically, we also want our proofs to be non-interactive. - +4. An AIOP describes an interactive protocol between the verifier and the prover. In reality, typically, we also want our proofs to be non-interactive. + This is accomplished by what is called the [Fiat--Shamir transformation](). The basic idea is this: all that the verifier is doing is sampling random values to send to the prover. Instead, to generate a "random" value, the prover simulates the verifier by hashing its messages. The resulting hash is used as the "random" challenge. - + At this point we have a fully non-interactive proof. Let's review our steps. - + 1. Start with a computation. - + 2. Translate the computation into a statement about polynomials and design a corresponding AIOP. - - 3. Compile the AIOP into an interactive protocol by having the prover send hiding polynomial commitments instead of polynomial oracles. - - 4. Get rid of the verifier-interaction by replacing it with a hash function. I.e., apply the Fiat--Shamir transform. + 3. Compile the AIOP into an interactive protocol by having the prover send hiding polynomial commitments instead of polynomial oracles. + 4. Get rid of the verifier-interaction by replacing it with a hash function. I.e., apply the Fiat--Shamir transform. diff --git a/book/src/fundamentals/custom_constraints.md b/book/src/kimchi/custom_constraints.md similarity index 98% rename from book/src/fundamentals/custom_constraints.md rename to book/src/kimchi/custom_constraints.md index 038ec9a2f9..e2c7256d68 100644 --- a/book/src/fundamentals/custom_constraints.md +++ b/book/src/kimchi/custom_constraints.md @@ -20,7 +20,7 @@ Then, given such a circuit, PLONK lets you produce proofs for the statement ## Specifying a constraint -Mathematically speaking, a constraint is a multivariate polynomial over the variables $v_{\mathsf{Curr},0}, \dots, v_{\mathsf{Curr}, W+A-1}, v_{\mathsf{Next}, 0}, \dots, v_{\mathsf{Next}, W+A-1}$. In other words, there is one variable corresponding to the value of each column in the "current row" and one variable correspond to the value of each column in the "next row". +Mathematically speaking, a constraint is a multivariate polynomial over the variables $c_{\mathsf{Curr},i}, \dots, v_{\mathsf{Curr}, W+A-1}, v_{\mathsf{Next}, 0}, \dots, v_{\mathsf{Next}, W+A-1}$. In other words, there is one variable corresponding to the value of each column in the "current row" and one variable correspond to the value of each column in the "next row". In Rust, $v_{r, i}$ is written `E::cell(Column::Witness(i), r)`. So, for example, the variable $v_{\mathsf{Next}, 3}$ is written `E::cell(Column::Witness(3), CurrOrNext::Next)`. diff --git a/book/src/plonk/fiat_shamir.md b/book/src/plonk/fiat_shamir.md index fd486f6aba..f1e2b264ef 100644 --- a/book/src/plonk/fiat_shamir.md +++ b/book/src/plonk/fiat_shamir.md @@ -1,4 +1,4 @@ -# non-interaction with fiat-shamir +# Non-Interactivity via Fiat-Shamir So far we've talked about an interactive protocol between a prover and a verifier. The zero-knowledge proof was also in the honest verifier zero-knowedlge (HVZK) model, which is problematic. @@ -15,7 +15,7 @@ This is important as our technique to transform an interactive protocol to a non The whole idea is to replace the verifier by a random oracle, which in practice is a hash function. Note that by doing this, we remove potential leaks that can happen when the verifier acts dishonestly. -Initially the Fiat-Shamir transformation was only applied to sigma protocols, named after the greek letter $\Sigma$ due to its shape resembling the direction of messages (prover sends a commit to a verifier, verifier sends a challenge to a prover, prover sends the final proof to a verifier). +Initially the Fiat-Shamir transformation was only applied to sigma protocols, named after the greek letter $\Sigma$ due to its shape resembling the direction of messages (prover sends a commit to a verifier, verifier sends a challenge to a prover, prover sends the final proof to a verifier). A $Z$ would have made more sense but here we are. ## Generalization of Fiat-Shamir @@ -27,6 +27,6 @@ This is simple: every verifier move can be replaced by a hash of the transcript While we use a hash function for that, a different construction called the [duplex construction](https://keccak.team/sponge_duplex.html) is particularly useful in such situations as they allow to continuously absorb the transcript and produce challenges, while automatically authenticating the fact that they produced a challenge. -[Merlin](https://merlin.cool/) is a standardization of such a construction using the [Strobe protocol framework](https://strobe.sourceforge.io/) (a framework to make use of a duplex construction). -Note that the more recent [Xoodyak](https://keccak.team/xoodyak.html) (part of NIST's lightweight competition) could have been used for this as well. +[Merlin](https://merlin.cool/) is a standardization of such a construction using the [Strobe protocol framework](https://strobe.sourceforge.io/) (a framework to make use of a duplex construction). +Note that the more recent [Xoodyak](https://keccak.team/xoodyak.html) (part of NIST's lightweight competition) could have been used for this as well. Note also that Mina uses none of these standards, instead it simply uses Poseidon (see section on poseidon). diff --git a/book/src/plonk/glossary.md b/book/src/plonk/glossary.md index 4880894fb4..6769ac5f83 100644 --- a/book/src/plonk/glossary.md +++ b/book/src/plonk/glossary.md @@ -1,9 +1,9 @@ # Glossary -* size = number of rows -* columns = number of variables per rows -* cell = a pair (row, column) -* witness = the values assigned in all the cells -* gate = polynomials that act on the variables in a row -* selector vector = a vector of values 1 or 0 (not true for constant vector I think) that toggles gates and variables in a row -* gadget = a series of contiguous rows with some specific gates set (via selector vectors) +* Size: number of rows +* Columns: number of variables per rows +* Cell: a pair (row, column) +* Witness: the values assigned in all the cells +* Gate: polynomials that act on the variables in a row +* Selector vector: a vector of values 1 or 0 (not true for constant vector I think) that toggles gates and variables in a row +* Gadget: a series of contiguous rows with some specific gates set (via selector vectors)