From e14cf2b133aa7ba4e8c60efd84dd93b77d181346 Mon Sep 17 00:00:00 2001 From: jotabulacios <45471455+jotabulacios@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:43:18 -0300 Subject: [PATCH 1/3] Add from Circom to Lambdaworks example (#931) * add example and modify Readme * added missing path to file * fixing typos * add missing spaces * fix clippy --------- Co-authored-by: diegokingston --- README.md | 1 + examples/README.md | 1 + .../circom_lambdaworks_tutorial.md | 284 ++++++++++++++++++ provers/stark/src/constraints/transition.rs | 5 + 4 files changed, 291 insertions(+) create mode 100644 examples/prove-verify-circom/circom_lambdaworks_tutorial.md diff --git a/README.md b/README.md index f95c93f66..1ed6a1206 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Below is a list of examples to understand lambdaworks and learn what you can bui - [BabySNARK](./examples/baby-snark/) - [Pinocchio](./examples/pinocchio/) - [Using Circom with lambdaworks's Groth16](./provers/groth16/circom-adapter/src/README.md) +- [Proving Fibonacci using Circom and lambdaworks](./examples/prove-verify-circom/circom_lambdaworks_tutorial.md) - You can use Circom to generate circuits and use lambdaworks's capabilities to prove the execution with [Groth16](./provers/groth16/README.md). - You can use the [Stark prover](./provers/stark/src/) to define an algebraic intermediate representation (AIR) and prove the execution of a program diff --git a/examples/README.md b/examples/README.md index 73cf271e7..c55d1094b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,6 +10,7 @@ Below is a list of all lambdaworks examples in the folder: - [Proving Miden using lambdaworks STARK Platinum prover](https://github.com/lambdaclass/lambdaworks/tree/main/examples/prove-miden): Executes a Miden vm Fibonacci program, gets the execution trace and generates a proof (and verifies it) using STARK Platinum. - [BabySNARK](https://github.com/lambdaclass/lambdaworks/tree/main/examples/baby-snark): a simple SNARK to start learning the basics of elliptic curve-based proof systems. - [Pinocchio](https://github.com/lambdaclass/lambdaworks/tree/main/examples/pinocchio): the first practical SNARK. A good starting point to start learning about zero-knowledge proofs. +- [Circom to Lambdaworks](https://github.com/lambdaclass/lambdaworks/tree/main/examples/prove-verify-circom/circom_lambdaworks_tutorial.md): A walkthrough to create a circuit in Circom, generate a proof, and verify it using with Groth16 using Lambdaworks. You can also check [lambdaworks exercises](https://github.com/lambdaclass/lambdaworks/tree/main/exercises) to learn more. diff --git a/examples/prove-verify-circom/circom_lambdaworks_tutorial.md b/examples/prove-verify-circom/circom_lambdaworks_tutorial.md new file mode 100644 index 000000000..3708f039c --- /dev/null +++ b/examples/prove-verify-circom/circom_lambdaworks_tutorial.md @@ -0,0 +1,284 @@ +# Groth16 from Circom to Lambdaworks tutorial + +In this small tutorial, we will perform all the steps needed to prove a computation using Groth16. + +As said above, our goal is to prove a computation. In this case we want to prove the Fibonacci sequence. + +If you don´t know what Groth16 does, you can read our [post](https://blog.lambdaclass.com/groth16/). + +Let's begin by creating the circuit and the input for the program using [Circom2](https://docs.circom.io/getting-started/installation/). + + +> [!IMPORTANT] +> This tutorial will work within `lambdaworks/provers/groth16` to make it easier to follow along. If you prefer, you can create a new rust project, but you will need to import all the crates from the library. + +As we want to cover all the steps from creating a program to prove it using Groth 16, the first thing to do is to create the folder for our program. + +Create a file named `fibonacci` inside `test_files`.This is where our program will be. + +The directory should look like this: +``` +provers/ +└── groth16/ + ├── arkworks-adapter/ + ├── circom-adapter/ + ├── src/ + │ ├── integration_tests.rs + │ ├── lib.rs + │ └── README.md + ├── test_files/ + │ ├── fibonacci/ (or any other name) + │ ├── poseidon/ + │ └── vitalik_example/ + └── Cargo.toml +``` + +### Understanding the Fibonacci Circuit +The Fibonacci sequence is defined as follows: + +$F(0) = a \quad \text{(starting value 1)}$ + +$F(1) = b \quad \text{(starting value 2)}$ + +$F(n) = F(n-1) + F(n-2) \quad \text{for } n \geq 2$ + +In our circuit, we start with two initial numbers, +a and b, and compute the nth Fibonacci number. The circuit checks if the output is correctly computed based on these inputs + +```circom +pragma circom 2.0.0; + +// Fibonacci with custom starting numbers +template Fibonacci(n) { + assert(n >= 2); + signal input in[2]; + signal output out; + + signal fib[n+1]; + fib[0] <== in[0]; + fib[1] <== in[1]; + for (var i = 2; i <= n; i++) { + fib[i] <== fib[i-2] + fib[i-1]; + } + + out <== fib[n]; +} + +// Instantiate Fibonacci as the main circuit with a specific n value +component main = Fibonacci(10); +``` +In our case this will be named `fibonacci.circom`. + + +In this circuit: +`in[0]` and `in[1]` are the initial values a and b + +`out` will hold F(10), the 10th number in the Fibonacci Sequence + +### Creating inputs for the Circuit + +Once we have defined the circuit, the next step is to provide the inputs needed for the proving process. In this case, our inputs consists of the starting numbers for the Fibonacci sequence. + +As we want to use F(0) = 1 and F(1) = 1, the input JSON file will look like this; + +```json +{ + "in": [1, 1] +} +``` + +The file should be named `input.json`. + +Inside the same directory where those files are run + +```bash +circom fibonacci.circom --r1cs --wasm -p bls12381 +``` +Here +`r1cs` generates the R1CS file +`wasm` generates the WebAssembly code +`-p bls12381` sets the elliptic curve. + +That will create a `fibonacci_js` directory and a `fibonacci_r1cs `file + +> [!WARNING] +> Do not skip the -p bls12381 flag, as this is the only field supported by the adapter right now. If not specified, the default is bn128 and the proving will not be possible + + +To compute the `witness` execute + +``` +node fibonacci_js/generate_witness.js fibonacci_js/fibonacci.wasm input.json witness.wtns +``` + +As our program inputs are json files we need to export the witness and r1cs files into the same format. To do that run: + +```bash +snarkjs wtns export json witness.wtns +``` +and +```bash +snarkjs r1cs export json fibonacci.r1cs fibonacci.r1cs.json +``` + +Now we have `witness.json` and `fibonacci.r1cs.json` + +The folder should look like: + + +``` +fibonacci/ +├── fibonacci_js/ +├── fibonacci.circom +├── fibonacci.r1cs +├── fibonacci.r1cs.json +├── input.json +├── witness.json +└── witness.wtns +``` + +We only need `fibonacci.r1cs.json` and `witness.json` so, if you want, you can delete the unnecessary files by running: +``` +rm -rf fibonacci_js fibonacci.circom fibonacci.r1cs witness.wtns input.json +``` + +All at once, you can copy-paste the following to the terminal in the same directory as your circuit + +```bash +circom fibonacci.circom --r1cs --wasm -p bls12381; +node fibonacci_js/generate_witness.js fibonacci_js/fibonacci.wasm input.json witness.wtns; +snarkjs wtns export json witness.wtns witness.json; +snarkjs r1cs export json fibonacci.r1cs fibonacci.r1cs.json; +rm -rf fibonacci_js fibonacci.circom fibonacci.r1cs witness.wtns input.json; # Delete unnecessary artifacts +``` + +## Using Lambdaworks Circom Adapter + +As mentioned in the blog post, the main goal of Groth16 (or any other prover) is to prove computation. +The function `circom_to_lambda` will take the `fibonacci.r1cs.json` and the `witness.json` data and translate it into the format needed for Lambdaworks. + +The `circom_to_lambda` function is responsible for converting the R1CS constraints and witness generated by Circom into a format that Lambdaworks can use to construct a Quadratic Arithmetic Program (QAP). The QAP representation is essential for Groth16 proof generation, as it represents the arithmetic circuit in terms of polynomials. + +### How `circom_to_lambda` works + +The function `circom_to_lambda` does the following: + +1. Parse JSON Data: It parses the R1CS constraints and witness files from JSON format into Rust data structures using serde_json. + +2. Build LRO Matrices: It extracts the L,R,O matrices which represent the variables involved in each constraint. + +3. Adjust Witness: It adjusts the order of inputs and outputs in the witness to match Lambdaworks's format. Circom and Lambdaworks have different conventions for witness ordering, so this adjustment ensures compatibility. + +4. Construct QAP: It uses the L,R,O matrices to build a Quadratic Arithmetic Program (QAP). This QAP is used in the Groth16 proving process. + + +## Generating the proof with Groth16 + +After converting the data using `circom_to_lambda`, you can use the resulting QAP and witness to generate a Groth16 proof. This process involves two key steps: creating the proving key and then using it to generate the proof based on the QAP. Here’s a detailed explanation of the process: + +We will put all together inside `integration_test`. + +Step 1: Read the R1CS and Witness Files. +First, you need to read the R1CS (Rank-1 Constraint System) file and the witness file generated by Circom. + +```rust +let test_dir = format!("{TEST_DIR}/fibonacci"); + +let (qap, w) = circom_to_lambda( + &fs::read_to_string(format!("{test_dir}/fibonacci.r1cs.json")) + .expect("Error reading the R1CS file"), + &fs::read_to_string(format!("{test_dir}/witness.json")) + .expect("Error reading the witness file"), +); +``` + +`test_dir`: Specifies the directory where the R1CS and witness files are stored. This allows for easy organization and reuse of test data. + +`circom_to_lambda` : Converts the content of the R1CS and witness files into a format compatible with Lambdaworks. This function parses the JSON content and produces a QAP and a corresponding witness vector. + +Step 2: Generate the Proving and Verifying Keys: + +```rust +let (pk, vk) = setup(&qap); +``` + +- Proving Key (pk): Used to create a proof that the computation was carried out correctly. +- Verifying Key (vk): Used to verify the validity of the proof, ensuring that it matches the expected computation. + + +Step 3: Generate the Proof + +With the proving key in hand, you can generate a proof using the `Prover::prove` function: + +```rust +let proof = Prover::prove(&w, &qap, &pk); +``` +`Prover::prove`: This function takes the witness, QAP, and proving key to generate a proof that certifies the correctness of the computation represented by the circuit. + +Step 4: Verify the Proof +To ensure the proof is valid, use the `verify` function: + +```rust +let accept = verify(&vk, &proof, &w[..qap.num_of_public_inputs]); +``` + +`verify`: Takes the verifying key, proof, and public inputs to check if the proof is valid. It ensures that the proof matches the expected output of the computation without revealing the private inputs. +Public Inputs: Extracted from the witness slice `&w[..qap.num_of_public_inputs]` to provide the data that is publicly visible during verification. + +Step 5: Assert the Verification Result + +Finally, check if the verification was successful + +```rust +assert!(accept, "Proof verification failed."); +``` +This line ensures that the proof verification returns `true`. If the verification fails, the test will produce an error, which helps catch issues during development. + + +#### Putting altogether +Inside `integration_tests.rs` add + +```rust +#[test] +fn fibonacci_verify() { + // Define the directory containing the R1CS and witness files + let test_dir = format!("{TEST_DIR}/fibonacci"); + + // Step 1: Parse R1CS and witness from JSON files + let (qap, w) = circom_to_lambda( + &fs::read_to_string(format!("{test_dir}/fibonacci.r1cs.json")) + .expect("Error reading the R1CS file"), + &fs::read_to_string(format!("{test_dir}/witness.json")) + .expect("Error reading the witness file"), + ); + + // Step 2: Generate the proving and verifying keys using the QAP + let (pk, vk) = setup(&qap); + + // Step 3: Generate the proof using the proving key and witness + let proof = Prover::prove(&w, &qap, &pk); + + // Step 4: Verify the proof using the verifying key and public inputs + let accept = verify(&vk, &proof, &w[..qap.num_of_public_inputs]); + + // Step 5: Assert that the verification is successful + assert!(accept, "Proof verification failed."); + + println!("Proof verification succeeded. All steps completed."); +} +``` +and run: + +```rust +cargo test -- --test fibonacci_verify +``` +If everything is set up correctly, you should see a success message confirming that the proof has been verified: + +```bah +Proof verification succeeded. All steps completed. +``` + +## Summary + +Congratulations! You have successfully set up a Groth16 proof for a Fibonacci circuit using Circom and Lambdaworks. This tutorial walked you through the entire process—from defining the circuit in Circom, generating the R1CS and witness, converting the data for use in Rust, and finally, creating and verifying the proof using Lambdaworks. + diff --git a/provers/stark/src/constraints/transition.rs b/provers/stark/src/constraints/transition.rs index 52fe0ba70..9d52b6a5b 100644 --- a/provers/stark/src/constraints/transition.rs +++ b/provers/stark/src/constraints/transition.rs @@ -105,6 +105,7 @@ where } /// Compute evaluations of the constraints zerofier over a LDE domain. + #[allow(unstable_name_collisions)] fn zerofier_evaluations_on_extended_domain(&self, domain: &Domain) -> Vec> { let blowup_factor = domain.blowup_factor; let trace_length = domain.trace_roots_of_unity.len(); @@ -120,7 +121,9 @@ where if let Some(exemptions_period) = self.exemptions_period() { // FIXME: Rather than making this assertions here, it would be better to handle these // errors or make these checks when the AIR is initialized. + debug_assert!(exemptions_period.is_multiple_of(&self.period())); + debug_assert!(self.periodic_exemptions_offset().is_some()); // The elements of the domain have order `trace_length * blowup_factor`, so the zerofier evaluations @@ -204,6 +207,7 @@ where /// Returns the evaluation of the zerofier corresponding to this constraint in some point /// `z`, which could be in a field extension. + #[allow(unstable_name_collisions)] fn evaluate_zerofier( &self, z: &FieldElement, @@ -214,6 +218,7 @@ where if let Some(exemptions_period) = self.exemptions_period() { debug_assert!(exemptions_period.is_multiple_of(&self.period())); + debug_assert!(self.periodic_exemptions_offset().is_some()); let periodic_exemptions_offset = self.periodic_exemptions_offset().unwrap(); From 5ad9656dfce69e17d31430efa8ce1225864ca311 Mon Sep 17 00:00:00 2001 From: feltroid Prime <96737978+feltroidprime@users.noreply.github.com> Date: Thu, 24 Oct 2024 01:04:25 +0700 Subject: [PATCH 2/3] Polynomial: differentiate & print methods. (#929) * add differentiate method to poly * print_as_sage_poly * avoid duplicate call to .degree(). * Fix wasm target compilation. --------- Co-authored-by: Diego K <43053772+diegokingston@users.noreply.github.com> --- math/src/polynomial/mod.rs | 78 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/math/src/polynomial/mod.rs b/math/src/polynomial/mod.rs index ee27923d0..679a83aec 100644 --- a/math/src/polynomial/mod.rs +++ b/math/src/polynomial/mod.rs @@ -1,8 +1,8 @@ use super::field::element::FieldElement; -use crate::field::traits::{IsField, IsSubFieldOf}; -use alloc::{borrow::ToOwned, vec, vec::Vec}; +use crate::field::traits::{IsField, IsPrimeField, IsSubFieldOf}; +use alloc::string::{String, ToString}; +use alloc::{borrow::ToOwned, format, vec, vec::Vec}; use core::{fmt::Display, ops}; - pub mod dense_multilinear_poly; mod error; pub mod sparse_multilinear_poly; @@ -140,6 +140,19 @@ impl Polynomial> { self.coefficients().len() } + /// Returns the derivative of the polynomial with respect to x. + pub fn differentiate(&self) -> Self { + let degree = self.degree(); + if degree == 0 { + return Polynomial::zero(); + } + let mut derivative = Vec::with_capacity(degree); + for (i, coeff) in self.coefficients().iter().enumerate().skip(1) { + derivative.push(FieldElement::::from(i as u64) * coeff); + } + Polynomial::new(&derivative) + } + /// Computes quotient with `x - b` in place. pub fn ruffini_division_inplace(&mut self, b: &FieldElement) { let mut c = FieldElement::zero(); @@ -302,6 +315,44 @@ impl Polynomial> { } } +impl Polynomial> { + // Print the polynomial as a string ready to be used in SageMath, or just for pretty printing. + pub fn print_as_sage_poly(&self, var_name: Option) -> String { + let var_name = var_name.unwrap_or('x'); + if self.coefficients.is_empty() + || self.coefficients.len() == 1 && self.coefficients[0] == FieldElement::zero() + { + return String::new(); + } + + let mut string = String::new(); + let zero = FieldElement::::zero(); + + for (i, coeff) in self.coefficients.iter().rev().enumerate() { + if *coeff == zero { + continue; + } + + let coeff_str = coeff.representative().to_string(); + + if i == self.coefficients.len() - 1 { + string.push_str(&coeff_str); + } else if i == self.coefficients.len() - 2 { + string.push_str(&format!("{}*{} + ", coeff_str, var_name)); + } else { + string.push_str(&format!( + "{}*{}^{} + ", + coeff_str, + var_name, + self.coefficients.len() - 1 - i + )); + } + } + + string + } +} + pub fn pad_with_zero_coefficients_to_length( pa: &mut Polynomial>, n: usize, @@ -1177,4 +1228,25 @@ mod tests { assert_eq!(lhs, g); assert_eq!(g, p3); } + + #[test] + fn test_differentiate() { + // 3x^2 + 2x + 42 + let px = Polynomial::new(&[FE::new(42), FE::new(2), FE::new(3)]); + // 6x + 2 + let dpdx = px.differentiate(); + assert_eq!(dpdx, Polynomial::new(&[FE::new(2), FE::new(6)])); + + // 128 + let px = Polynomial::new(&[FE::new(128)]); + // 0 + let dpdx = px.differentiate(); + assert_eq!(dpdx, Polynomial::new(&[FE::new(0)])); + } + + #[test] + fn test_print_as_sage_poly() { + let p = Polynomial::new(&[FE::new(1), FE::new(2), FE::new(3)]); + assert_eq!(p.print_as_sage_poly(None), "3*x^2 + 2*x + 1"); + } } From 3bb1fa1fde12e166952c53590b8ed82ef5c66fe2 Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Wed, 23 Oct 2024 22:27:51 -0300 Subject: [PATCH 3/3] Release v0.11.0: Hasty Hamburger (#932) --- Cargo.toml | 8 ++++---- crypto/README.md | 2 +- math/README.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52c7e1edf..eed7f1be3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,16 @@ exclude = ["ensure-no_std"] resolver = "2" [workspace.package] -version = "0.10.0" +version = "0.11.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/lambdaclass/lambdaworks" [workspace.dependencies] iai-callgrind = "0.3.1" -lambdaworks-crypto = { path = "./crypto", version = "0.10.0", default-features = false } -lambdaworks-gpu = { path = "./gpu", version = "0.10.0" } -lambdaworks-math = { path = "./math", version = "0.10.0", default-features = false } +lambdaworks-crypto = { path = "./crypto", version = "0.11.0", default-features = false } +lambdaworks-gpu = { path = "./gpu", version = "0.11.0" } +lambdaworks-math = { path = "./math", version = "0.11.0", default-features = false } stark-platinum-prover = { path = "./provers/stark" } lambdaworks-winterfell-adapter = { path = "./provers/winterfell_adapter"} lambdaworks-groth16 = { path = "./provers/groth16" } diff --git a/crypto/README.md b/crypto/README.md index 8633f5dde..f637d5e87 100644 --- a/crypto/README.md +++ b/crypto/README.md @@ -8,7 +8,7 @@ Add this to your `Cargo.toml` ```toml [dependencies] -lambdaworks-crypto = "0.8.0" +lambdaworks-crypto = "0.11.0" ``` ## Structure diff --git a/math/README.md b/math/README.md index e5e0830a0..bacdcc371 100644 --- a/math/README.md +++ b/math/README.md @@ -8,7 +8,7 @@ Add this to your `Cargo.toml` ```toml [dependencies] -lambdaworks-math = "0.8.0" +lambdaworks-math = "0.11.0" ``` ## Structure