diff --git a/.gitignore b/.gitignore index 9dda4b0d..843d989a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__ *.so .ipynb_checkpoints +_build diff --git a/Appendix.md b/Appendix.md deleted file mode 100644 index 0e5eb6f8..00000000 --- a/Appendix.md +++ /dev/null @@ -1,82 +0,0 @@ -# Appendix - -## Design Principles - -**Abstraction**. As circuit complexity increases, abstraction is inevitable. By abstracting constraint building and column placement, Chiquito improves the readability and learnability of Halo2 circuits, which can not only standardize and simplify the code base for complex projects such as the zkEVM, but also onboard more circuit developers. - -**Composability**. Chiquito circuits are fully composable with Halo2 circuits, which allows developers to write any part or the entirety of a circuit in Chiquito and integrate with other Halo2 circuits. - -**Modularity**. The AST and IR representations of a circuit allow Chiquito to integrate any frontend that can compile into the AST data structure and any backend that can compile from the IR data structure. For example, we can have a Python frontend that compiles to Chiquito AST/IR, which compiles to a Sangria backend. Modularity allows for future extensions. - -**User Experience**. Chiquito simplifies and optimizes user experience. For example, annotations for constraints are automatically generated for debugging messages. - -## Architecture - -There are two major architectural differences between Chiquito and Halo2: - -- Chiquito circuit is composed of “step” instances. Each step type defines the constraints among witnesses, fixed columns, and lookup tables. Step instances are also called “super rows”, each composed of one or multiple rows in a PLONKish table. We made this design choice to allow for more complex constraints, which sometimes require allocating multiple Halo2 rows. -- Chiquito DSL is based on “signals” rather than columns in order to abstract away column placements. One signal can be placed in different columns across different steps, all handled by Chiquito’s compiler. - -Chiquito circuit has the following architecture - -- Circuit (with optional input from Halo2) - - Forward signals (signals with constraints across different steps) - - Fixed columns - - Step types - - Internal signals (signals with constraints only within one step) - - Internal constraints (constraints for internal signals only) - - Transition constraints (constraints involving forward signals) - - Witness generation function for one step type instance - - Trace (global circuit witness generation) - - Adds instances of step types - -## Overall Workflow - -Chiquito is a DSL that compiles Chiquito AST to an IR which can be parsed by a Chiquito Halo2 backend and integrated into a Halo2 circuit. Therefore, to create a Halo2 circuit using Chiquito, we need to: - -- Call `circuit` function in DSL, which returns an `ast::Circuit` object. The `circuit` function defines the `ast::Circuit` object by: - - Importing Halo2 columns - - Generating fixed columns - - Adding forward signals - - Defining and instantiating “steps”, which defines internal signals, constraints, lookups, and witness generation -- Create a `Compiler` object that compiles the `ast::Circuit` object to an `ir::Circuit` object -- Call `chiquito2Halo2` function in Halo2 backend to convert the `ir::Circuit` object to a `ChiquitoHalo2` object -- Integrate `ChiquitoHalo2` object into a Halo2 circuit by including it in the Halo2 circuit config struct -- Call `configure` and `synthesize` functions defined in the Halo2 backend on the `ChiquitoHalo2` object - -## Exposed User Functions - -The above section describes the high level process of building and integrating a Chiquito Halo2 backend object into a Halo2 circuit. However, when building a circuit using Chiquito, the developer mostly call DSL functions to manipulate the `ast::Circuit` object. - -DSL functions are defined on five different levels, with nested relationships: - -- Circuit level: define and manipulate circuit-level objects, such as forward signals, step types, fixed columns, and imported Halo2 columns - - Step type level: define and manipulate step-specific objects, such as internal signals, constraints, witness generations - - Witness generation level: allow user-defined Turing-complete function to manipulate witness generation inputs and assign witness values - - Fixed column generation level: allow user-defined Turing-complete function to manipulate fixed column generation inputs and assign fixed column values - - Trace level: create the main circuit by instantiating step types; allow user-defined Turing-complete function to manipulate external trace inputs and assign input values to step type instances - -## Project Status (as of April 2023) - -The current implementation includes: - -- A DSL in Rust -- A backend in Halo2 -- Composability with Halo2 circuits -- A working prototype that passes zkEVM bytecode circuit tests -- Hashing function circuit examples - -## Vision and Next Steps - -Modularity - -- Expand frontend to other programming languages, e.g. Python -- Integrate additional backends and proof systems - -Library - -- Add additional circuit examples - -Infrastructure - -- Cmd tool and npm package for developers diff --git a/book/_config.yml b/book/_config.yml new file mode 100644 index 00000000..06a9cbfc --- /dev/null +++ b/book/_config.yml @@ -0,0 +1,28 @@ +# Book settings +# Learn more at https://jupyterbook.org/customize/config.html + +title: Introduction to Chiquito +author: Leo Lara, Steve Wang +logo: pselogo.png + +# Force re-execution of notebooks on each build. +# See https://jupyterbook.org/content/execute.html +execute: + execute_notebooks: force + +# Define the name of the latex output file for PDF builds +latex: + latex_documents: + targetname: book.tex + +# Information about where the book exists on the web +repository: + url: https://github.com/privacy-scaling-explorations/chiquito # Online location of your book + path_to_book: book # Optional path to your book, relative to the repository root + branch: main # Which branch of the repository should be used when creating links (optional) + +# Add GitHub buttons to your book +# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository +html: + use_issues_button: true + use_repository_button: true diff --git a/book/_toc.yml b/book/_toc.yml new file mode 100644 index 00000000..7e5aa82b --- /dev/null +++ b/book/_toc.yml @@ -0,0 +1,30 @@ +# Table of contents +# Learn more at https://jupyterbook.org/customize/toc.html + +format: jb-book +root: landing_page +parts: + - caption: Part 1 - Intro to Chiquito + chapters: + - file: part1_chapter1 + - file: part1_chapter2 + - file: part1_chapter3 + - file: part1_chapter4 + - file: part1_chapter5 + - caption: Part 2 - Fibonacci Example + chapters: + - file: part2_chapter1 + - file: part2_chapter2 + - file: part2_chapter3 + - file: part2_chapter4 + - file: part2_chapter5 + - caption: Part 3 - MiMC7 Example + chapters: + - file: part3_chapter1 + - file: part3_chapter2 + - file: part3_chapter3 + - file: part3_chapter4 + - caption: Appendix + chapters: + - file: appendix_chapter1 + - file: appendix_chapter2 \ No newline at end of file diff --git a/book/appendix_chapter1.md b/book/appendix_chapter1.md new file mode 100644 index 00000000..7594b393 --- /dev/null +++ b/book/appendix_chapter1.md @@ -0,0 +1,9 @@ +# Design Principles + +**Abstraction**. As circuit complexity increases, abstraction is inevitable. By abstracting constraint building and column placement, Chiquito improves the readability and learnability of Halo2 circuits, which can not only standardize and simplify the code base for complex projects such as the zkEVM, but also onboard more circuit developers. + +**Composability**. Chiquito circuits are fully composable with Halo2 circuits, which allows developers to write any part or the entirety of a circuit in Chiquito and integrate with other Halo2 circuits. + +**Modularity**. The AST and IR representations of a circuit allow Chiquito to integrate any frontend that can compile into the AST data structure and any backend that can compile from the IR data structure. For example, we can have a Python frontend that compiles to Chiquito AST/IR, which compiles to a Sangria backend. Modularity allows for future extensions. + +**User Experience**. Chiquito simplifies and optimizes user experience. For example, annotations for constraints are automatically generated for debugging messages. diff --git a/book/appendix_chapter2.md b/book/appendix_chapter2.md new file mode 100644 index 00000000..ae3c0cff --- /dev/null +++ b/book/appendix_chapter2.md @@ -0,0 +1,22 @@ +# Chiquito vs Halo2 + +There are two major architectural differences between Chiquito and Halo2: + +- Chiquito circuit is composed of “step” instances. Each step type defines the constraints among witnesses, fixed columns, and lookup tables. Step instances are also called “super rows”, each composed of one or multiple rows in a PLONKish table. We made this design choice to allow for more complex constraints, which sometimes require allocating multiple Halo2 rows. +- Chiquito DSL is based on “signals” rather than columns in order to abstract away column placements. One signal can be placed in different columns across different steps, all handled by Chiquito’s compiler. + +Chiquito circuit has the following architecture + +- Circuit + - Setup + - Forward signals + - Shared signals + - Fixed signals + - Step types + - Setup + - Internal signals + - Constraints + - Transition constraints + - Lookup + - Witness generation + - Trace \ No newline at end of file diff --git a/book/landing_page.md b/book/landing_page.md new file mode 100644 index 00000000..ddfb7e10 --- /dev/null +++ b/book/landing_page.md @@ -0,0 +1,17 @@ +# Meet Chiquito + +Chiquito is a high-level structured language for implementing zero knowledge proof applications, currently being implemented in the DSL Working Group of PSE, Ethereum Foundation. It is a step-based zkDSL (zero knowledge domain specific language) that provides better syntax and abstraction for features like constraint building and column placement. Chiquito has a Halo2 backend, which is a low level zkDSL that writes circuits using the PLONKish arithemtization and is working on supporting additional backends. + +Chiquito can be written in both Python and Rust. This tutorial focuses on Python. + +The key advantages of Chiquito include: +- Abstraction and simplification on the readability and learnability of Halo2 circuits. +- Composabiity with other Halo2 circuits. +- Modularity of using multiple frontends (Python and Rust) and backends (Halo2 and beyond). +- Smooth user experience with a dynamically typed language (Python). + +

+ Image 2 +       + Image 3 +

diff --git a/book/part1_chapter1.md b/book/part1_chapter1.md new file mode 100644 index 00000000..fc818a50 --- /dev/null +++ b/book/part1_chapter1.md @@ -0,0 +1,58 @@ +# What is Zero Knowledge Proof (Developer POV)? + +Zero Knowledge proofs (or ZKP) allow developers to create mathematical proofs that a computation have been executed correctly, while not having to reveal all the data of this computation. + +## What are the applications of ZKP? + +There are three main areas of application: + + Privacy: because not all data has to be reveal we can proof computations and facts while preserving privacy or secrecy of some part of the data. + + Scalability and verified computation: usually to check that a computation has been executed correctly you would need to execute it and check the results are the same. With ZKP you can validate the mathematical proof of the computation, and in some systems this could require less time and space resources than executing the computation itself. + + Interoperability and trustlessness: because ZKPs are based on mathematical proofs, an organisation or individual can trust a ZKP proof without trusting other participants. + +## What are ZKP proving systems? + +Proving systems allow to create ZKP for arbitrary computations. They are like a computer system that can execute an arbitrary computation. A ZKP circuit is a definition for a proving system that proofs a specific computation. For example, for bubble sort algorithm you could write a circuit for a specific proving system that proofs bubble sort executions. + +## What are the elements of a ZKP circuit? + +The main elements are: + + The witness: which is the data of the computation. The witness is made of many elements, sometimes called signals or cells. Some signals are public and other private, the public part is sometimes called the **instance** and the private part is sometimes called the **advice**. The witness usually contains the input and output of the computation, but also most or all its intermediate values (the trace). + + Setup: it is a series of constraints or assertions over the witness data, that define which computations are valid. + + Witness generation: an algorithm that helps calculating the right values for all the signals. + + Proving and validation key: given a setup the proving system can generate proving and validation pair of keys. Both can be made public. + + Proof and prover: given a setup and a witness, the prover using the proving key can generate a proof. This proof will be valid if and only if the witness follows the setup constraints. + + Validator: given a proof and the public part of the witness and a validation key, the validator using the proving system can check that the proof is valid. That is, to generate that proof the prover had to have a witness (if which this public part is part of) that followed the setup. + +It is important to note that if the prover and validator have agreed on the setup will have the same proving and validation keys, and will not have to trust each other. + +![](../images/zkp-process-diagram.png) + +## What is a ZKP arithmetization? + +An arithmetization is the way to represent the setup to the proving system. They are very low level and based on polynomials. As a simile, if the proving system is a CPU, the arithmetization is its machine code. + +## What is a ZKP low-level DSL? + +It is a tool to write arithmetization in a more developer friendly way, but still very close to the arithmetization. If arithmetization is machine code, a ZKP low-level DSL is like assembly. + +## What is a ZKP high-level structured language? + +They allow to write the setup in a way that is closer to the way a developer thinks about the computation, and they get compiled to the arithmetization. Following the previous simile a ZKP high-level structured language is like Python. + +Chiquito is an example of ZKP high-level structured language that is based on steps. + +## What is a Finite Prime Field? + +In ZKP the witness signals are not made of bytes, they are elements (or values) of a Finite Prime Field. We need to understand how FPFs work in order to write circuits. + +As a developer you can see it as a unsigned integer type where the maximum value is a prime minus one (`p - 1` instead of `2^n-1`) and addition and multiplication overflows as expected, so `a + b = a + b mod p` and `a * b = a * b mod p`. + +Negative values are obtained by the formula `a + (-a) mod p = 0`. + +The prime `p` is usually a very big number when used in ZKP, but we can see an easy example with `p = 7`. + +The posible values would be `0, 1, 2, 3, 4, 5` and `6`. If we go over `p` it overflows around to `0`. So for example `6 + 1 = 0` or `5 + 3 = 1`. + +Then for example `-3 + 3 mod 7 = 0 ` which is satisfied by `4 + 3 mod 7 = 0 => -3 = 4`. + +This is a very short summary of what you need to know about FPFs. \ No newline at end of file diff --git a/book/part1_chapter2.md b/book/part1_chapter2.md new file mode 100644 index 00000000..f1002de3 --- /dev/null +++ b/book/part1_chapter2.md @@ -0,0 +1,111 @@ +# What is Chiquito? + +Chiquito is a high-level structured language for implementing zero knowledge proof applications. + +Chiquito is being implemented in the DSL Working Group of PSE, Ethereum Foundation. + +

+ Image 2 +       + Image 3 +

+ + +## Why is chiquito different from other ZKP DSLs? + +Most ZKP DSLs are based on writing constraints, witness generation and some abstraction for DRY like templates or gadgets. + +Chiquito allows the developer to think in more high-level and structured abstractions that most ZKP DSLs, while not sacrificing performance. + +## What is the chiquito programming model? + +Chiquito starts from the idea that every zero knowledge proof represents a program (the setup), which can have many computations (the trace) that is proven for a certain input, output and intermediate values (the witness). + +The main structured abstraction in chiquito is the **step**. Any computation can be divided in individual steps. A program is represented by a circuit that has one or many **step types**, a particular computation is represented as a series of **step instances** or **trace steps** that can have arbitrary order. + +A step type contains: + + Setup: a series of constraints or assertions that must hold for a step instance of this type to be valid. + + Witness generation: code that for a particular input sets the values of the witness for a particular step instance. + +A chiquito circuit contains a trace function that for a given input will generate the step instances in a particular order and use the step type witness generation. + +Another important piece of Chiquito are the signals. They represent elements of the witness. + +Chiquito has many more features, but these are enough to start writing basic circuits. + +## What proving system chiquito uses? + +Currently Halo2 backend is implemented, but we are looking into implementing other backends. + +Chiquito frontend comes in two flavours: rust and python, so you can write Chiquito applications in either Rust or Python. PyChiquito, and any other language interface in the future, uses ChiquitoCore for most of its work, so adding new languages is easy. + +## What are the features of chiquito? + + + Step-based, that abstracts out the computation that you want to prove. + + Signals, that abstract out the data (witness) and how it is placed and handled. + + Constraint builder, allows you to write the constraints of your application in a more readable and natural way. + + Trace based witness generation, a way to generate the data that you are trying to prove that matches how computation is done. + + Super circuits, that allow combining many circuits into one. + + Lookup tables, that allow sharing data between multiple circuits. + + Expose signals as public very easily. + + Automatic padding. + + Completely modular platform, that allows writing circuits in multiple languages and use different proving systems. + +PLONKish-specific features: + + Halo2 backend ready. + + PLONKish Cell Managers. These are modular strategies to place signals in the PLONKish columns and rotations. These allows for steps to use different numbers of rows and columns, in a configurable way, so you can find the most efficient structure for your circuit. + + PLONKish step selector builders. These are modular strategies to activate the steps in the witness. + +Planned: + + Nested steps, will allow more complex circuits and allow circuits coordination in proving systems without advice based lookup tables. + + Gadget abstraction. + + Arbitrary boolean assertions. + +In research: + + Signal typing system, which allows statically checking for soundness issues. + + Folding backend with ProtoStar, HyperNova, and/or others. + + Tracers + +## Fibonacci circtuit in PyChiquito. + +But better see for yourself: + +``` +class FiboStep(StepType): + def setup(self: FiboStep): + self.c = self.internal("c") + self.constr(eq(self.circuit.a + self.circuit.b, self.c)) + self.transition(eq(self.circuit.b, self.circuit.a.next())) + self.transition(eq(self.c, self.circuit.b.next())) + + def wg(self: FiboStep, args: Tuple[int, int]): + a_value, b_value = args + self.assign(self.circuit.a, F(a_value)) + self.assign(self.circuit.b, F(b_value)) + self.assign(self.c, F(a_value + b_value)) + +class Fibonacci(Circuit): + def setup(self: Fibonacci): + self.a: Queriable = self.forward("a") + self.b: Queriable = self.forward("b") + + self.fibo_step = self.step_type(FiboStep(self, "fibo_step")) + + self.pragma_num_steps(11) + + def trace(self: Fibonacci, args: Any): + self.add(self.fibo_step, (1, 1)) + a = 1 + b = 2 + for i in range(1, 11): + self.add(self.fibo_step, (a, b)) + prev_a = a + a = b + b += prev_a + +fibo = Fibonacci() +fibo_witness = fibo.gen_witness(None) +fibo.halo2_mock_prover(fibo_witness) +``` + +This is explained in more detail in the tutorial, but you can see already how concise and clear it is. \ No newline at end of file diff --git a/book/part1_chapter3.md b/book/part1_chapter3.md new file mode 100644 index 00000000..c5147876 --- /dev/null +++ b/book/part1_chapter3.md @@ -0,0 +1,113 @@ +# Chiquito Programming Model + +## ZKP as trace + +ZKP generates proofs that an algorithm has been execute correctly. + +The best way we can analyse a particular execution of an algorithm is by its trace. + +For example: + +```python= +def bubbleSort(arr): + n = len(arr) + + for i in range(n-1): + for j in range(0, n-i-1): + + if arr[j] > arr[j + 1]: + arr[j], arr[j + 1] = arr[j + 1], arr[j] +``` + +A trace for a particular input is the actual steps that are executed in order. + +For example if `arr = [64, 34, 25, 12, 22, 11, 90]`, the trace is: + +``` +set n to length of arr which is 7 +set i to 1 +set j to 0 +compare arr[j] > arr[j+1] => arr[0] > arr[1] => 64 > 34 => true +set arr[0] to arr[1] and arr[1] to arr[0] => arr[0] = 34, arr[1] = 64 +// and so on... +``` + +It is important to understand that each step has a pre-state, with the values of the variables before the execution of the step, and a post-state with the values of the variables after the execution of the step. The post-state of a step is the pre-state of the next one. + +If we include the states indented: +``` + arr = [64, 34, 25, 12, 22, 11, 90] + n = ?, i = ?, j = ? +set n to lenght of arr which is 7 + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = ?, j = ? +set i to 1 + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = 1, j = ? +set j to 0 + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = 1, j = 0 +compare arr[j] > arr[j+1] => arr[0] > arr[1] => 64 > 34 => true + arr = [64, 34, 25, 12, 22, 11, 90] + n = 7, i = 1, j = 0 +set arr[0] to arr[1] and arr[1] to arr[0] => arr[0] = 34, arr[1] = 64 + arr = [34, 64, 25, 12, 22, 11, 90] + n = 7, i = 1, j = 0 +// and so on... +``` + +## ZKP circuit as a trace checker + +We can see a ZKP circuit as a series of step types, and their constraints relative to the post-state and pre-state, and also potentially some internal values. + +For example, we can have the step type `set $variable to $value` and the constraint would be that the post state of `$variable` should be `$value`. + +## ZKP witness as a trace + +In the same way, we can see the witness as a the trace of a computation plus some information about the states between the steps. + +The programming model in chiquito follows the idea that every zero knowledge proof represents a program (the setup), which can have many computations (the trace) that is proven for a certain input, output and intermediate values (the witness). + +## Step types and step instances + +A trace is made of a sequence of step instances or trace steps, that contains the data about that step instance. + +Each step instance belong to a step type. A step types contains rules (or constraints) about whether a step instance is valid in relation to its data and the data of other step instances relative to it. + +Step types are part of the setup, which means that they cannot change between proofs, but this circuit can proof all possible traces, as they are part of the witness. + +## Signals and rotations + +Signals represent are elements of the witness that have a independent value for each trace step, but in chiquito paradigm you can see them as variables on the trace steps. + +Rotation refer to the capability of creating rules that involved the value of signals in other trace steps with an offset relative to the trace step, not just the value of a signal. A rotation of 0, represent the value of the signal in the same trace step, a rotation of 1 in the next step, a rotation of -1 in the previous step, in general any rotation number is posible. + +## Types of signals + +There are two types of signals that are shared between the step types, **forward** signals and **shared** signals. The difference is that forward signals can only rotate to the next step, while shared signals can rotate to any number of steps. However, circuits that only use forward signals are more efficient in some cases, so you should try to use only forward signals. + +**Internal** signals are not shared between step types, and belong to a specific step type and cannot be rotated. + +There is a special type of signal called **fixed**, that is not part of the witness and it is used to contain constants. + +| | Scope | Rotation | Role | +| -------- | ---------------- | --------- | ------- | +| Forward | All step types | Only next | Witness | +| Shared | All step types | Any | Witness | +| Internal | Single step type | None | Witness | +| Fixed | All step types | Any | Setup | + +## Putting everything together +- Circuit + - Setup + - Forward signals + - Shared signals + - Fixed signals + - Step types + - Setup + - Internal signals + - Constraints + - Transition constraints + - Lookup + - Witness generation + - Trace \ No newline at end of file diff --git a/book/part1_chapter4.md b/book/part1_chapter4.md new file mode 100644 index 00000000..52ccbeab --- /dev/null +++ b/book/part1_chapter4.md @@ -0,0 +1,157 @@ +# Python Syntax + +## Constraint builder + +One of the most important parts of defining a circuit is the constraints. The basis for writing constraints are arithmetic operations with signals. You use the operators `*`, `+` and `-` with numbers and signals. For example: + +`a + 1 * b` + +When you need to do a rotation on a signal you can use the operators `.next()`, `prev()` and or `.rot(n)`. For example: + +`a + 1 * b.next()` + + There are several helper functions to define constraints: + + `cb_and([a1 ... an])`: And operator + + `cb_or([a1 ... an])`: Or operator + + `xor(a, b)`: Xor operator + + `eq(a, b)`: Arithmetic equality. + + `select(selector, when_true, when_false)`: trinary operator + + `when(selector, when_true)`: Logical imply. + + `unless(selector, when_false)`: Logical imply when !selector. + + `cb_not(a)`: Not operator. + + isz(a): Equal to zero. + + `if_next_step(step_type, a)`: If next step is step_type, implies a. + + `next_step_must_be(step_type)`: Enforces next step must be step_type. + + `next_step_must_not_be(step_type)`: Enforces next step must not be step_type. + +In the current version of chiquito these operator cannot be combined arbitrarily, there is a solution in the works. For example `cb_and` cannot have operands that are `eq`. + +## Defining a step + +A chiquito circuit is made of steps, this is how we define steps: + +```python= +class AStep(StepType): + def setup(self): + # setup: rules about a valid step + def wg(self, a, b, c): + # witness generation: set data for a step instance +``` + +For the setup we need to define constraints that define which step witness is valid and also define the internal signals. For example: + +```python= +class AStep(StepType): + def setup(self): + self.c = self.internal("c") +``` + +To access shared signals we can do it with `self.circuit.signal_identifier`. + +For the constraints that we can use the method `constr`. For example: + +```python= +class AStep(StepType): + def setup(self): + self.constr(eq(self.circuit.a, self.circuit.b * 3)) + self.constr(when(self.circuit.a, eq(self.circuit.b, self.c)) + +# ... +``` + +The first constraint means that `a` must be equal to `b times 3`. The second that when `a` value is `1` then `b` must be equal to `c`. + +When you have a rotation you should use the method `transition`. For example: + +```python= +class AStep(StepType): + def setup(self): + self.transition(eq(self.circuit.a, self.circuit.b.next() * 3)) + self.transition(when(self.circuit.a, eq(self.circuit.b.next(), self.c)) + +# ... +``` + + +For the data of one step instance in the `wg` method we must use the `assign` method to assign all signals. + +```python= +class AStep(StepType): + def wg(self, a_val, b_val): + self.assign(self.circuit.a, a_val) + self.assign(self.circuit.b, b_val) + self.assign(self.c, a_val + b_val) + +# ... +``` + +The method `wg` can have any parameters, and do any computation to calculate the assignations. But signals cannot be rotated for assignation, you can only assign the current step. + +## Defining the circuit + +To put everything together we define the circuit, also with two methods. + +```python= +class ACircuit(Circuit): + def setup(self): + # Define shared signals and step types used, number of total steps plus more setup configuration. + def trace(self, arg1, arg2): + # Define algorithm to create the trace from the input. +``` + +For the setup we defined shared signals, step types and number of steps in the trace. For example: + +```python= +class ACircuit(Circuit): + def setup(self): + # shared signals + self.a = self.forward("a") + self.b = self.forward("b") + self.n = self.forward("n") + + # step types + self.a_step = self.step_type(AStep(self, "a_step")) + self.b_step = self.step_type(BStep(self, "b_step")) + self.c_step = self.step_type(CStep(self, "c_step")) + + # number of trace steps + self.pragma_num_steps(10) + +# ... +``` + +There are many optional things that you can also configure in the circuit setup: + +```python= +class ACircuit(Circuit): + def setup(self): + self.pragma_first_step(self.a_step) + self.pragma_last_step(self.b_step) + + self.expose(self.b, Last()) + self.expose(self.n, Last()) + +# ... +``` + +Then, we should define `trace`, this method add step instances or trace steps to create a particular trace. The most important method is `add()`, the first parameter is a step type, and the rest are the parameters to the step type `wg` method. This method adds a trace step + +For example: + +```python= +class ACircuit(Circuit): + def trace(self, n): + self.add(self.a_step, 1, 1, n) + a = 1 + b = 2 + for i in range(1, n): + self.add(self.b_step, a, b, n) + prev_a = a + a = b + b += prev_a + + while self.needs_padding(): + self.add(self.b_step, a, b, n) + +# ... +``` \ No newline at end of file diff --git a/book/part1_chapter5.ipynb b/book/part1_chapter5.ipynb new file mode 100644 index 00000000..9eb33613 --- /dev/null +++ b/book/part1_chapter5.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "11105438-f775-4ff5-a91c-b236aedd9eb1", + "metadata": {}, + "source": [ + "# Setup\n", + "\n", + "## User Setup\n", + "### Python\n", + "Simply run the following line in command:\n", + "```\n", + "pip install chiquito\n", + "```\n", + "Then import chiquito in `.py` file. See more in the `\\examples` folder.\n", + "\n", + "### Rust\n", + "Add the following line to `[dependencies]` in `Cargo.toml`:\n", + "```\n", + "chiquito = { git = \"https://github.com/privacy-scaling-explorations/chiquito.git\" }\n", + "```\n", + "Then use chiquito in `.rs` file. See more in the `\\examples` folder.\n", + "\n", + "## Contributor Setup\n", + "### Python\n", + "Clone this repo and navigate to the root directory of this repository:\n", + "```\n", + "git clone https://github.com/privacy-scaling-explorations/chiquito/\n", + "cd chiquito\n", + "```\n", + "\n", + "Chiquito's Python frontend uses PyO3 and Maturin to expose Rust APIs to Python. Maturin requires the user to locally build a Python virtual environment. Run the following script to create a Python virtual environment, install required packages, and build the project.\n", + "```\n", + "python3 -m venv .env # create virtual environment\n", + "source .env/bin/activate # activate virtual environment\n", + "pip install -r requirements.txt # install required python dependencies from requirements.txt\n", + "maturin develop # build the project with rust bindings\n", + "```\n", + "\n", + "If the above doesn't work, follow the guide here: https://pyo3.rs/main/getting_started#python\n", + "\n", + "To test if your setup is successful, run the `fibonacci.py` example file using the following script in the virtual environment:\n", + "```\n", + "python3 example/fibonacci.py\n", + "```\n", + "\n", + "You can also run the `mimc7.py` example:\n", + "```\n", + "python3 example/mimc7.py\n", + "```\n", + "\n", + "If setup is correct, you should see an `OK(())` message printed in the terminal.\n", + "\n", + "### Rust\n", + "Simply clone and modify this repo:\n", + "```\n", + "git clone https://github.com/privacy-scaling-explorations/chiquito/\n", + "```\n", + "\n", + "### Jupyter\n", + "To modify and contribute to the Jupyter book `.ipynb` files in this book, you can use an IDE like VSCode. Alternatively, you can modify `.ipynb` files in the browser according to the following steps:\n", + "\n", + "First, install jupyter:\n", + "```\n", + "pip install jupyter\n", + "```\n", + "\n", + "Then, make sure you've set up your local Python virtual environment as directed above and install it as a Jupyter Lab Kernel called `chiquito_kernel`:\n", + "```\n", + "python -m ipykernel install --user --name=chiquito_kernel\n", + "```\n", + "\n", + "After that, run the following:\n", + "```\n", + "cd book # navigate to the book subfolder\n", + "jupyter lab # launch jupyter notebook in browser\n", + "```\n", + "\n", + "In browser, make sure you are using the Kernel we just created called `chiquito_kernel`, which you can change by going to the `Kernel` tab of Jupyter Lab's top menu bar and click `Change Kernel...`\n", + "\n", + "Chiquito's Python frontend requires using a Python virtual environment for its dependencies, which you should have setup following the [Python README](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src/frontend/python/README.md).\n", + "\n", + "Specifically, after cloning Chiquito, you need to run the following commands in your local machine (NOT in Jupyter):\n", + "```bash\n", + "python3 -m venv .env # create virtual environment\n", + "source .env/bin/activate # activate virtual environment\n", + "pip install -r requirements.txt # install required python dependencies from requirements.txt\n", + "maturin develop # build the project with rust bindings\n", + "```\n", + "\n", + "Then install your local Python virtual environment as a Jupyter Lab Kernel called `chiquito_kernel`. \n", + "```bash\n", + "python -m ipykernel install --user --name=chiquito_kernel\n", + "```\n", + "\n", + "After that, run the following: \n", + "```bash\n", + "cd tutorials # navigate to the tutorials subfolder\n", + "jupyter lab # launch jupyter notebook in browser\n", + "```\n", + "\n", + "In browser, make sure you are using the Kernel we just created called `chiquito_kernel`, which you can change by going to the `Kernel` tab of Jupyter Lab's top menu bar and click `Change Kernel...`\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "chiquito_kernel", + "language": "python", + "name": "chiquito_kernel" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/part3_chapter1.ipynb b/book/part2_chapter1.ipynb similarity index 95% rename from tutorials/part3_chapter1.ipynb rename to book/part2_chapter1.ipynb index a9d6e74b..6d7a81a3 100644 --- a/tutorials/part3_chapter1.ipynb +++ b/book/part2_chapter1.ipynb @@ -6,8 +6,7 @@ "id": "14a9a617-1c05-40c8-b4ef-3cc2fbcf99fe", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", - "The best learning is by doing, In this Chapter, we will walk through the [fibonacci.py](https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/fibonacci.py) example.\n", + "The best learning is by doing, In this Part, we will walk through the [fibonacci.py](https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/fibonacci.py) example.\n", "# Chapter 1: Fibonacci and Chiquito Concepts\n", "The Fibonacci series is an infinite series, starting from \"1\" and \"1\", in which every number in the series is the sum of two numbers preceding it in the series. The first few rounds for the Fibonacci series are:\n", "- 1 + 1 = 2\n", diff --git a/tutorials/part3_chapter2.ipynb b/book/part2_chapter2.ipynb similarity index 99% rename from tutorials/part3_chapter2.ipynb rename to book/part2_chapter2.ipynb index cefb3443..33e22707 100644 --- a/tutorials/part3_chapter2.ipynb +++ b/book/part2_chapter2.ipynb @@ -5,7 +5,6 @@ "id": "8ab5d5e4-6d9e-4856-ad0b-0e2cf7bb13fb", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 2: StepType and Circuit\n", "In this Chapter, we code out the concepts learned in Chapter 1 in PyChiquito, but before that, let's import the dependencies first.\n", "## Imports" diff --git a/tutorials/part3_chapter3.ipynb b/book/part2_chapter3.ipynb similarity index 99% rename from tutorials/part3_chapter3.ipynb rename to book/part2_chapter3.ipynb index e202cb01..e9f53e0c 100644 --- a/tutorials/part3_chapter3.ipynb +++ b/book/part2_chapter3.ipynb @@ -5,7 +5,6 @@ "id": "aab29f93-4311-4bff-a123-38f412556922", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 3: Witness\n", "Now, we will generate multiple witnesses to test the soundness of our circuit constraints. Note that we only intend to accept the following set of values for signals \"a\", \"b\", and \"c\". \"Soundness\" in this context refers to faulty witness successfully verified against the constraints (false positives), so any set of witness assignments that is different from the table below but still passes the constraints incurs a \"soundness\" error.\n", "\n", diff --git a/tutorials/part3_chapter4.ipynb b/book/part2_chapter4.ipynb similarity index 99% rename from tutorials/part3_chapter4.ipynb rename to book/part2_chapter4.ipynb index 14996ecb..d301f6b4 100644 --- a/tutorials/part3_chapter4.ipynb +++ b/book/part2_chapter4.ipynb @@ -5,7 +5,6 @@ "id": "291ef2c6-bce5-4166-973d-d34d04f31bfb", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 4: Multiple StepTypes" ] }, diff --git a/tutorials/part3_chapter5.ipynb b/book/part2_chapter5.ipynb similarity index 99% rename from tutorials/part3_chapter5.ipynb rename to book/part2_chapter5.ipynb index e01f227e..b01795bc 100644 --- a/tutorials/part3_chapter5.ipynb +++ b/book/part2_chapter5.ipynb @@ -5,7 +5,6 @@ "id": "5ec470ae-aca1-4c7e-a048-d115633531d8", "metadata": {}, "source": [ - "# Part 3: Fibonacci Example\n", "# Chapter 5: Padding and Exposing Signals\n", "In prior examples, all Fibonacci circuit witnesses have the same number of step instances. According to our analogy where a circuit is considered a piece of hardware and its witnesses compatible softwares, it's natural to think that we'd allow for more flexibility for our witnesses. Most immediately, and we shouldn't limit all Fibonacci circuit witnesses to have the same number of step instances.\n", "\n", diff --git a/tutorials/part4_chapter1.ipynb b/book/part3_chapter1.ipynb similarity index 96% rename from tutorials/part4_chapter1.ipynb rename to book/part3_chapter1.ipynb index c1a04c4f..e6ec6f28 100644 --- a/tutorials/part4_chapter1.ipynb +++ b/book/part3_chapter1.ipynb @@ -5,8 +5,7 @@ "id": "2b08fd8e-65b8-433b-8a99-71aa13ad30ad", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", - "In this Chapter, we will walk through the [mimc7.py] (https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/mimc7.py) example.\n", + "In this Part, we will walk through the [mimc7.py](https://github.com/privacy-scaling-explorations/chiquito/blob/main/examples/mimc7.py) example.\n", "# Chapter 1: MiMC7 Concepts\n", "MiMC7 is a zk-friendly hash function. By \"zk-friendly\" we mean that the function is designed to minimize circuit size for zero knowledge proofs by using only additions and multiplications. Other common non-zk-friendly hash functions such as SHA256 requires many bit operations and therefore can be very costly in terms of circuit size.\n", "\n", diff --git a/tutorials/part4_chapter2.ipynb b/book/part3_chapter2.ipynb similarity index 99% rename from tutorials/part4_chapter2.ipynb rename to book/part3_chapter2.ipynb index e142379b..fd131294 100644 --- a/tutorials/part4_chapter2.ipynb +++ b/book/part3_chapter2.ipynb @@ -5,7 +5,6 @@ "id": "1c24b711-a68f-4215-a8e6-556c078dddec", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", "# Chapter 2: First Attempt\n", "In this Chapter, we will code out the MiMC7 circuit based on our understanding of the algorithm from the prior chapter. There are no new Chiquito concepts other than the ones introduced in **Part 3: Fibonacci Example**, which you are highly recommended to go through first before starting this Chapter.\n", "\n", diff --git a/tutorials/part4_chapter3.ipynb b/book/part3_chapter3.ipynb similarity index 99% rename from tutorials/part4_chapter3.ipynb rename to book/part3_chapter3.ipynb index b0c9975e..9ca84fcb 100644 --- a/tutorials/part4_chapter3.ipynb +++ b/book/part3_chapter3.ipynb @@ -5,8 +5,7 @@ "id": "f6da1ade-1de6-4921-9485-40f1c18b1098", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", - "# Chapter 2: Witness\n", + "# Chapter 3: Witness\n", "As is the best practice with circuit building, we will generate evil witnesses to test the soundness of our circuit constraints. It's always helpful to review the circuit table again:\n", "\n", "|Step Type|Round|||Signals||||||Setups||\n", diff --git a/tutorials/part4_chapter4.ipynb b/book/part3_chapter4.ipynb similarity index 99% rename from tutorials/part4_chapter4.ipynb rename to book/part3_chapter4.ipynb index 9f9792c8..5c9b0863 100644 --- a/tutorials/part4_chapter4.ipynb +++ b/book/part3_chapter4.ipynb @@ -5,7 +5,6 @@ "id": "be7fb603-4820-410e-8250-abc52e81a6b0", "metadata": {}, "source": [ - "# Part 4: MiMC7 Example\n", "# Chapter 4: Fixed Signals, Lookup Tables, and Super Circuit\n", "\n", "## Lookup Tables\n", diff --git a/book/pselogo.png b/book/pselogo.png new file mode 100644 index 00000000..a934b013 Binary files /dev/null and b/book/pselogo.png differ diff --git a/book/requirements.txt b/book/requirements.txt new file mode 100644 index 00000000..7e821e45 --- /dev/null +++ b/book/requirements.txt @@ -0,0 +1,3 @@ +jupyter-book +matplotlib +numpy diff --git a/examples/factorial.py b/examples/factorial.py index 7a5bc510..6963a3f9 100644 --- a/examples/factorial.py +++ b/examples/factorial.py @@ -40,7 +40,9 @@ def setup(self): # constrain i + 1 == i.next() self.transition(eq(self.circuit.i + 1, self.circuit.i.next())) # constrain the next `x` to be the product of the current `x` and the next `i` - self.transition(eq(self.circuit.x * (self.circuit.i + 1), self.circuit.x.next())) + self.transition( + eq(self.circuit.x * (self.circuit.i + 1), self.circuit.x.next()) + ) def wg(self, i_value, x_value): self.assign(self.circuit.i, F(i_value)) @@ -99,7 +101,9 @@ class Examples: def test_zero(self): factorial = Factorial() factorial_witness = factorial.gen_witness(0) - last_assignments = list(factorial_witness.step_instances[10].assignments.values()) + last_assignments = list( + factorial_witness.step_instances[10].assignments.values() + ) assert last_assignments[0] == 0 # i assert last_assignments[1] == 1 # x factorial.halo2_mock_prover(factorial_witness) @@ -107,7 +111,9 @@ def test_zero(self): def test_basic(self): factorial = Factorial() factorial_witness = factorial.gen_witness(7) - last_assignments = list(factorial_witness.step_instances[10].assignments.values()) + last_assignments = list( + factorial_witness.step_instances[10].assignments.values() + ) assert last_assignments[0] == 7 # i assert last_assignments[1] == 5040 # x factorial.halo2_mock_prover(factorial_witness) @@ -116,6 +122,9 @@ def test_basic(self): if __name__ == "__main__": x = Examples() for method in [ - method for method in dir(x) if callable(getattr(x, method)) if not method.startswith('_') + method + for method in dir(x) + if callable(getattr(x, method)) + if not method.startswith("_") ]: getattr(x, method)() diff --git a/examples/mimc7.py b/examples/mimc7.py index ce3a1ca5..f17028fc 100644 --- a/examples/mimc7.py +++ b/examples/mimc7.py @@ -167,4 +167,4 @@ def mapping(self, x_in_value, k_value): mimc7_super_witness = mimc7.gen_witness(F(1), F(2)) # for key, value in mimc7_super_witness.items(): # print(f"{key}: {str(value)}") -mimc7.halo2_mock_prover(mimc7_super_witness, k = 10) +mimc7.halo2_mock_prover(mimc7_super_witness, k=10) diff --git a/tutorials/part1.ipynb b/tutorials/part1.ipynb deleted file mode 100644 index 064d9234..00000000 --- a/tutorials/part1.ipynb +++ /dev/null @@ -1,48 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "0d703199-2067-4ebc-b6ed-b04470587279", - "metadata": {}, - "source": [ - "# Part 1: Introduction to Chiquito's Python Frontend\n", - "\n", - "Chiquito is a high-level structured language for implementing zero knowledge proof applications, currently being implemented in the DSL Working Group of PSE, Ethereum Foundation. It is a step-based zkDSL (zero knowledge domain specific language) that provides better syntax and abstraction for features like constraint building and column placement when writing PLONKish circuits, which supports custom gates and lookup arguments. Chiquito has a Halo2 backend, which is a low level zkDSL that writes circuits using the PLONKish arithemtization. Chiquito is working on supporting additional backends.\n", - "\n", - "Chiquito can be written in both Python and Rust. This tutorial focuses on Python.\n", - "\n", - "Chiquito's Python frontend is located in a [subfolder here](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src/frontend/python). It exposes Python functions that user writes circuits with. Chiquito's [Rust functions](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src) are called by the Python functions behind the scenes and converts what the user writes into a Halo2 circuit.\n", - "\n", - "The key advantages of Chiquito and its Python frontend include: \n", - "- Abstraction and simplification on the readability and learnability of Halo2 circuits.\n", - "- Composabiity with other Halo2 circuits.\n", - "- Modularity of using multiple frontends (Python and Rust) and backends (Halo2 and beyond).\n", - "- Smooth user experience with a dynamically typed language (Python).\n", - "\n", - "For more on why you need Chiquito and its Python frontend, refer to [What is Chiquito?](https://hackmd.io/h6innd6RTwex4aBExignhw), [Chiquito README](https://github.com/privacy-scaling-explorations/chiquito#readme), and the [Appendix](https://github.com/privacy-scaling-explorations/chiquito/blob/main/Appendix.md/#design-principles) on its design principles. For more on PLONKish concepts and Halo2 circuits, refer to the [Halo2 book](https://zcash.github.io/halo2/index.html)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "chiquito_kernel", - "language": "python", - "name": "chiquito_kernel" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tutorials/part2.ipynb b/tutorials/part2.ipynb deleted file mode 100644 index da969fe7..00000000 --- a/tutorials/part2.ipynb +++ /dev/null @@ -1,56 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "11105438-f775-4ff5-a91c-b236aedd9eb1", - "metadata": {}, - "source": [ - "# Part 2: Quick Start\n", - "Chiquito's Python frontend requires using a Python virtual environment for its dependencies, which you should have setup following the [Python README](https://github.com/privacy-scaling-explorations/chiquito/tree/main/src/frontend/python/README.md).\n", - "\n", - "Specifically, after cloning Chiquito, you need to run the following commands in your local machine (NOT in Jupyter):\n", - "```bash\n", - "python3 -m venv .env # create virtual environment\n", - "source .env/bin/activate # activate virtual environment\n", - "pip install -r requirements.txt # install required python dependencies from requirements.txt\n", - "maturin develop # build the project with rust bindings\n", - "```\n", - "\n", - "Then install your local Python virtual environment as a Jupyter Lab Kernel called `chiquito_kernel`. \n", - "```bash\n", - "python -m ipykernel install --user --name=chiquito_kernel\n", - "```\n", - "\n", - "After that, run the following: \n", - "```bash\n", - "cd tutorials # navigate to the tutorials subfolder\n", - "jupyter lab # launch jupyter notebook in browser\n", - "```\n", - "\n", - "In browser, make sure you are using the Kernel we just created called `chiquito_kernel`, which you can change by going to the `Kernel` tab of Jupyter Lab's top menu bar and click `Change Kernel...`\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "chiquito_kernel", - "language": "python", - "name": "chiquito_kernel" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -}