{"id":26653831,"url":"https://github.com/santiellena/circom-bootcamp","last_synced_at":"2025-06-16T02:05:28.122Z","repository":{"id":283850046,"uuid":"953078915","full_name":"santiellena/circom-bootcamp","owner":"santiellena","description":"My homework and notes on the Circom Bootcamp by RareSkills ","archived":false,"fork":false,"pushed_at":"2025-05-15T15:35:07.000Z","size":93,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-15T16:42:28.083Z","etag":null,"topics":["circom","zkp"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/santiellena.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-22T14:21:34.000Z","updated_at":"2025-05-15T15:35:12.000Z","dependencies_parsed_at":"2025-05-06T14:30:10.626Z","dependency_job_id":"4b0fd24d-ca81-4c69-ba67-252ff31631d4","html_url":"https://github.com/santiellena/circom-bootcamp","commit_stats":null,"previous_names":["santiellena/circom-bootcamp"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/santiellena/circom-bootcamp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santiellena%2Fcircom-bootcamp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santiellena%2Fcircom-bootcamp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santiellena%2Fcircom-bootcamp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santiellena%2Fcircom-bootcamp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/santiellena","download_url":"https://codeload.github.com/santiellena/circom-bootcamp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santiellena%2Fcircom-bootcamp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260083860,"owners_count":22956407,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["circom","zkp"],"created_at":"2025-03-25T04:47:57.323Z","updated_at":"2025-06-16T02:05:28.109Z","avatar_url":"https://github.com/santiellena.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Circom Bootcamp\nThis repository contains my homework and notes on the Circom Bootcamp by RareSkills.\n\n## Content\n- [Session 1](#session-1)\n    - [Introduction](#introduction)\n    - [Arithmetic Circuits](#arithmetic-circuits)\n    - [Homework S1](#homework-s1)\n- [Session 2](#session-2)\n   - [Modular Arithmetic](#modular-arithmetic)\n   - [Homework S2](#homework-s2)\n- [Session 3](#session-3)\n   - [Rank-1-Constraints-System](#rank-1-constraints-system)\n   - [Homework S3](#homework-s3)\n- [Session 4](#session-4)\n   - [Circom Basics](#circom-basics)\n   - [Homework S4](#homework-s4)\n- [Session 5](#session-5)\n   - [More Circom](#more-circom)\n   - [Homework S5](#homework-s5)\n- [Session 6](#session-6)\n   - [Even More Circom](#even-more-circom)\n   - [Homework S6](#homework-s6)\n- [Session 7](#session-7)\n   - [Compute then Constrain](#compute-then-constrain)\n   - [Alias Bug](#alias-bug)\n   - [Homework S7](#homework-s7)\n- [Session 8](#session-8)\n   - [Hacking Underconstrained Circuits with Fake Proofs](#hacking-underconstrained-circuits-with-fake-proofs)\n- [Session 9](#session-9)\n   - [Conditional Statements in Circom and The Quin Selector](#conditional-statements-in-circom-and-the-quin-selector)\n   - [Homework S9](#homework-s9)\n- [Session 10](#session-10)\n   - [Stateful Computation](#stateful-computation)\n   - [Homework S10](#homework-s10)\n- [Session 11](#session-11)\n   - [Swapping Two Items in an Array](#swapping-two-items-in-an-array)\n   - [Homework S11](#homework-s11)\n- [Session 12](#session-12)\n   - [Selection Sort](#selection-sort)\n   - [Homework S12](#homework-s12)\n- [Session 13](#session-13)\n   - [Stack Based zkVM](#stack-based-zkvm)\n   - [Homework S13](#homework-s13)\n- [Session 14](#session-14)\n   - [32-bit Emulation](#32-bit-emulation)\n   - [Homework S14](#homework-s14)\n- [Session 15](#session-15)\n   - [MD5 Hash Function](#md5-hash-function)\n   - [Homework S15](#homework-s15)\n- [Session 16](#session-16)\n   - [ZK-Friendly Hash Functions](#zk-friendly-hash-functions)\n- [Conclusions](#conclusions)\n\n****\n\n## Session 1\n\n### Introduction\n\nThe foundation of Circom is based on three key concepts:\n1. Arithmetic Circuits\n2. Rank 1 Constraint System (R1CS)\n3. Modular Arithmetic\n\n### Arithmetic Circuits\n\nZK is useful and has many use cases because proving you computed something correctly is usually simpler than computing it. Being more technical, we could say that given a P (polynomial) or an NP (non-deterministic polynomial) problem we can verify any solution by modeling the problem as a Boolean Formula.\n\nWithout going to much into details, the difference between P and NP problems is the computing time. P is the class of problems that can be solved and verified efficiently, while NP is the class of problems that can be verified efficiently. \n\nAll problems in P and NP can be verified by transforming them into boolean formulas and showing a solution to the formula. This is key for ZKPs because only problems that can be efficiently verified can be converted in a boolean formula. \n\nBoolean formulas are a helpful tool to model problems but as they are restricted to boolean inputs and basic boolean operations (\"boolean gates\", AND, OR and NOT), constructing those formulas can get complicated even for basic problems.\n\nIt would be simpler to model those boolean formulas as arithmetic circuits.\n\nBut.. what is an **Arithmetic Circuit**?\n\nAn arithmetic circuit is a system of equations using only addition, multiplication, and equality. Like a Boolean circuit, it checks that a proposed set of inputs is valid, but doesn’t compute a solution.\nThe arithmetic circuit is \"satisfied\" (meaning that a valid set of inputs was passed) when all of its equality constraints are satisfied.\n\nIt is useful to think about representing boolean gates in arithmetic circuits because this way we can get the advantage of the arithmetic of Arithmetic Circuits and the logic of Boolean Formulas.\n\n- The **AND** gate:\n   | x |  y | x and y == z |\n   |---|----|---------|\n   | 0 |  0 |   0     | \n   | 0 |  1 |   0     |\n   | 1 |  0 |   0     |\n   | 1 |  1 |   1     |\n\n   The equivalent artihmetic representation: `z = xy`\n\n- The **OR** gate:\n   | x |  y | x or y == z |\n   |---|----|---------|\n   | 0 |  0 |   0     | \n   | 0 |  1 |   1     |\n   | 1 |  0 |   1     |\n   | 1 |  1 |   1     |\n\n   The equivalent artihmetic representation: `z = x + y - xy`\n\n- The **NOT** gate:\n   | x | ¬x |\n   |---|----|\n   | 0 |  1 |\n   | 1 |  0 |\n\n   The equivalent artihmetic representation: `z = 1 - x`\n\n- The **XOR** gate: \n   | x |  y | x xor y == z |\n   |---|----|---------|\n   | 0 |  0 |   0     | \n   | 0 |  1 |   1     |\n   | 1 |  0 |   1     |\n   | 1 |  1 |   0     |\n\n   The equivalent artihmetic representation: `z = x + y - 2xy`\n\nIn conclusion, as any NP problem can be represented as a Boolean Formula, and ant Boolean Formula can be represented as an Arithmetic Circuit, then the solution to any NP problem can be modeled with an Arithmetic Circuit.\n\n### Homework S1\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session1.md) file.\n\n## Session 2\n\n### Modular Arithmetic\n\nGiven a `p` prime number we define a **finite field** with `p` elements `{0, 1, ..., p -1}`. The only allowed operations are addition and multiplication, modulo `p`.\n\nTo perform operations and play around with modular arithmetic, I used the [Rust playground](https://play.rust-lang.org/?version=stable\u0026mode=debug\u0026edition=2024):\n```rust\nfn main() { // 10 = 3 mod 7\n    println!(\"{}\", 10 % 7);\n}\n```\n\nWhen we do addition or multipication in a finite field, we might encounter **overflow**(the result is greater than `p`). This is typically considered a bad thing, but in modular arithmetic it's not a bug, it's a feature (lol). When an operation overflows, as it is done modulo `p`, we get the reminder of the division by `p` which is a number (better called \"element\") in the finite field.\n\nFor example, `3 + 5 = 1 mod 7`:\n   - `3 + 5` is `8`, \n   - but modulo `7`, `8 = 1`.\n\nIn the previous example, we would say that `8` is **congruent** to `1`, not equal. This is just mathematical vocabulary, but I think it is good to know it because we will hear that word a lot working with finite fields.\n\nThe same concept can be used with **underflow**. Altough our finite field does not have elements that represent negative numbers, we do have \"congruent\" elements from our finite field. \n\nFor example, in the Rust playground:\n```rust\nfn main() {\n   // -6 = 1 mod 7\n    println!(\"{}\", -6 % 7 + 7);\n    // note: the \"+ 7\" after the mod operation is there because of how Rust rounds division\n    // in another programming language as Python that wouldn't be necessary.\n}\n```\n- `-6` is conguent to `1`, so we have a way to represent negative numbers!\n\n**Addition entity:** Any element plus `p` is the same element. \n\n**Additive inverse:**\n`a + b = 0` -\u003e `b` is the additive inverse of `a`. \n - `0` is its own additive inverse.\n - every number has exactly one additive inverse\n\nHere the \"congruency\" of negative numbers with elements of the finite field start to make sense.\n\nIn normal math, finding the additive inverse of `a` is kind of easy, we just say `-a`, `a + (-a) = 0`. However, in modular arithmetic, we don't have a way to substract (because we just use addition or multiplication) or to directly represent a negative number.\n\n   - `5 - 5 mod 7` is NOT valid,\n   - however, `5 + 2 = 0 mod 7` is valid.\n\nTurns out that as `-5` is conguent to `2` they behave as the same element. Thus, `5 - 5 = 0` in normal math and `5 + 2 = 0` in the finite field where `p = 7`. Note that the congruency between numbers depends directly in the value assigned to `p`.\n\n**Multiplicative inverse:** \nThe multiplicative inverse for `a` is a number `b` such that `ab = 1`.\n - `0` logically doesn't have a multiplicative inverse.\n - every number has exactly one multiplicative inverse.\n\nNormally, we would say that the multiplicative inverse of `5` is `1/5` because `5 * 1/5 = 1`. However, we are doing modular arithmetic and fractions cannot be represented.\n\nHere the \"congruency\" between numbers outside of the field with elements of the fields also works, as with negative numbers.\nFor each element in the field, there is another element in the field that is congruent to the multiplicative inverse of that number. Essentially, they behave as the same number.\n\nFor example, the multiplicative inverse of `5 mod 7` is `3`.\n - `5 * 3 = 1 mod 7`\n - `15 = 1 mod 3`\n\nThus, `3` is congruent to `1/5` in the finite field when `p = 7`.\n\nA cool rule of multiplicative inverses is: `(p - 1) and (p + 1)` are their own multiplicative inverses.\n - `(p - 1)(p - 1) = 1 mod p`\n - `(p + 1)(p + 1) = 1 mod p`\n\nRemember that you can use Rust or Python to check it for yourself if you don't beleive me hahaha.\n\nThere is an explanation behind those curious behaviors, the easy way is to see that `(p - 1)` is congruent to `-1`, and `-1 * -1 = 1`. Same thing occurs with `(p + 1)`, it is congruent to `1`.\n\nThere is a theorem for computing the multiplicative inverse of a number (Fermat's Little Theorem), however, it is easier to use some tool that does the calculation for us. I guess it is to avoid innecesary complexity, BUT I wouldn't say that it will hurt to learn it anyways.\n\n### Homework S2\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session2.md) file.\n\n## Session 3\n\n### Rank-1-Constraints-System\n\nIn this session, it was covered how artimetic circuits are converted into R1CS. This is the last session where we review foundational Circom topics. \n\nI already have my own personal explanation on how the conversion works from algebraic circuits in [this](https://github.com/santiellena/zk-magic-square?tab=readme-ov-file#arithmetic-circuit-to-r1sc) repository, with easy examples and some good theory background. I won't repeat myself here, you can go and check this topic there.\n\n### Homework S3\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session3.md) file.\n\n## Session 4\n\n### Circom Basics\n\nThe knowledge from previous lessons starts to converge here. Circom is a language (unsure if it can be called a programming language) that allows us to write airthmetic circuits in the form of constraints that follows the rules of R1CS constraints.\n\nCircom compiler converts our constraints into a R1CS (low-level representation), which allows us to eliminate complexity in writing our own ZK systems.\n\nCircom has a syntax that is really familiar to common programming languages and allows us to use tools, such as for loops or conditional statements, to make the constraints declaration easier. However, its behavior is restricted and the logic behind writing circuits has nothing to do with the programming logic (although they seem simmilar).\n\nAfter some research, I found that Circom is both a DSL (Domain-Specific-Language) and a programming language as well. The programming language feature of Circom is used to \"populate\" the witness (inputs) into some other thing, such as an output or an intermediate signal.\n\nI don't think that going through the Circom syntax here would be helpful. Instead, refer to the following resource:\n- [Circom Docs](https://docs.circom.io/circom-language/signals/)\n\nIn order to test skills, I suggest not to go through all zero knowledge puzzles (mentioned in the homework file) and just stick to the ones listed in the homework file (there are 5).\n\n### Homework S4\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session4.md) file.\n\n## Session 5\n\n### More Circom\n\nHighlights of the session:\n\n- Most of the times an additional array of intermdiate signals is used to compute things.\n- Conditional statements are only allowed if they don't modify the static structure of the R1CS that Circom compiles.\n- The `output` keyword for signals has a strange behavior:\n   - In sub-components, it kind of behaves as a \"return\" value,\n   - In the main component, it makes the signal \"public\" and is denoted as \"public output\",\n   - However, it's presence (or absense) never modifies the R1CS.\n\nAt this point I am really comfortable with the Circom syntax and its \"intricate\" behavior. Writing Circom thinking that it will create a R1CS and nothing else is a change of paradigm that is hard at the beginning.\n\nIn the first homework exercise you will see a problem I encountered and, why it was triggered and how I was able to solve it (recommended).\n\n### Homework S5\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session5.md) file.\n\n## Session 6\n\n### Even More Circom\n\nConceptually this session didn't involve many topics, however, it helped a lot in the practical aspects of writing Circom circuits. \n\nWe saw how the [circomlib](https://github.com/iden3/circomlib) library can be used and some of its components. Additionally, we learned about how commonly Circom code is written and got comfortable with all the possible problems we could encounter. It is really basic but from now on, we won't be learning how Circom works and how we can use it. Instead, we will see real use cases for it. \n\nReally interested for what is next!\n\n### Homework S6\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session6.md) file.\n\n## Session 7\n\n### Compute then Constrain\n\n##### • What is this pattern, why is it used?\n\nThe idea behind \"compute then constrain\" is computing the solution of an algorithm and then constraining the invariants of the algorithm. This is specially useful because we don't need to only use addition and multiplication because of restirctions in how R1CS are built, instead, we can use Circom as a normal programming language to get results (or intermediate results) and then constrain them. This not only reduces the complexity when building circuits but also reduces the size of the R1CS, making the computation behind it less computationally expensive (the prover time and complexity is reduced). \n\n##### • The Circom `\u003c--` operator\n\nUnlike `\u003c==`, the `\u003c--` operator does not create constraints. It is an operator to indicate the precence of out-of-circuit computation, allowing us to compute things and assing a value to a signal which is function of other signals, without constraining it (this is specially important because the R1CS is not modified nor affected, so there aren't non-quadratic constraints). \n\n##### • An example with modular square roots\n\nThis example was the easiest for me to visualize the magic of this pattern. \n\nLet's say we want a circuit to prove that there is a valid modular square of `in`, `out`:\n```javascript\n// this is pseudo code\nsqrt(n) % p === out % p;\n```\nIf we want to write a circuit that constraints the calculation of `sqrt(in)` we will need many constraints and the complexity to write them with only addition and multiplication will be excesive.\n\nThere is when the \"compute then constrain\" patters is useful. We can use Circom as a programming language to **compute** the modular square of `in`(first part of the pattern) and then **constrain** the invariants of the problem (second part of the pattern).\nTry the following code in [zkRepl](https://zkrepl.dev/):\n```circom\npragma circom 2.1.6;\n\ninclude \"circomlib/pointbits.circom\";\n\ntemplate Example() {\n   signal input in;\n\tsignal output out;\n\t\n\tout \u003c-- sqrt(in); // COMPUTE (\u003c-- doesn't create a contraint)\n\tout * out === in; // CONSTRAIN INVARIANT\n   /*\n      it is nice to see that \n      sqrt(in) === out,\n      is the same than\n      out * out === in,\n      but the latter is super easy to fit in a R1CS \n   */\n}\n\ncomponent main = Example();\n\n/* INPUT = {\n    \"in\": 4\n} */\n// OUTPUT: 2\n```\nIn the previous example we used the [`sqrt`](https://github.com/iden3/circomlib/blob/35e54ea21da3e8762557234298dbb553c175ea8d/circuits/pointbits.circom#L27) function from circomlib. The most important detail is that it is a function and doesn't create constraints, it just computes. \n\n##### • `Num2Bits` circuit from the `circomlib`\n\nThis one is also a good example of the \"compute then constrain\" pattern (you will note the the circomlib library uses it a lot), but it also a good circuit to explain why `n` in this circuit and other circuits, such as [comparators](https://github.com/iden3/circomlib/blob/master/circuits/comparators.circom), should only be used when `n \u003c= 253` or `n \u003c= 252` depending on the case. Where `n` is the numbers of bits that represent the value passed in signals.\n\nLet's review the [`Num2Bits` implementation](https://github.com/iden3/circomlib/blob/35e54ea21da3e8762557234298dbb553c175ea8d/circuits/bitify.circom#L25-L39):\n```circom\ntemplate Num2Bits(n) {\n    signal input in;\n    signal output out[n];\n    var lc1=0;\n\n    var e2=1;\n    for (var i = 0; i\u003cn; i++) {\n        out[i] \u003c-- (in \u003e\u003e i) \u0026 1; // compute (picking the bit in position i)\n        out[i] * (out[i] -1 ) === 0; // then constrain\n        lc1 += out[i] * e2;\n        e2 = e2+e2; // incrementing the coefficient (esentially making them powers of 2 but easier)\n    }\n\n    lc1 === in; // constraning that the sum of 1 bits times its coefficient is equal to the signal in\n}\n```\nBecause of the comments I think it is very easy to visualize the pattern.\n\nNow the other relevant part is... why `n` should not be grater than `253`?\n\nAltough this was not explained in the bootcamp, I did my own research and in the next section, you will find what I learned.\n\n### Alias Bug\n\nThere is a type of bug in ZK circuits known as Alias. This bug occurs when trying to represent a value on the circuit with an amount of bits that could overflow `p`, the order of the field. \n\nThe main problem here is that the binary representation constraints silently fail when they overflow. It depends heavily in the logic on the circuit that is being built, but let's see an example with the [`Bits2Num`](https://github.com/iden3/circomlib/blob/35e54ea21da3e8762557234298dbb553c175ea8d/circuits/bitify.circom#L55-L67) circuit:\n```circom\ntemplate Bits2Num(n) {\n    signal input in[n];\n    signal output out;\n    var lc1=0;\n\n    var e2 = 1;\n    for (var i = 0; i\u003cn; i++) {\n        lc1 += in[i] * e2;\n        e2 = e2 + e2;\n    }\n\n    lc1 ==\u003e out;\n}\n```\n\nI could pass an array of many bits, create an overflow, and `out` won't represent actually what the array of bits represents. SILENTLY!\nLet's see an example:\n```circom\npragma circom 2.1.6;\n\ninclude \"circomlib/bitify.circom\";\n\ntemplate Example(n) {\n    signal in[n];\n\n    for(var i = 0; i \u003c n; i++){\n        in[i] \u003c== 1;\n    }\n\n    component bits2Num = Bits2Num(n);\n    bits2Num.in \u003c== in;\n    log(bits2Num.out);\n}\n\ncomponent main = Example(254);\n\n/* INPUT = {\n    \"in\": []\n} */\n```\nHere we pass an array of `254` bits set to 1, but the output we get in decimal numbers doesn't represent the actual value of `(2^254) - 1`.\n- Decimal output: 7059779437489773633646340506914701874769131765994106666166191815402473914366\n- Binary output:  111110011011101100011000110100\n                  01111011001110010111111101011 \n                  001000111101011111011101001001\n                  001011111100111111010100111101\n                  000101101011111001100000101111\n                  011011110000110010001101000111\n                  101101110101111000001111000001\n                  010011011000000111111111111111\n                  1111111111110 (252 digits)\n\nNow you see that we get a different decimal number given our initial array of bits.\n\nBut why and when this occurs?\n\nRemember that I previously mentioned something about the amount of bits `n` not being greater than `252 or 253`, depending on the circuit. These values have a reason, and the reason is `p`, the order of the field.\n\nCircom can represent values from `0` up to `p - 1`, obviously, because all calculations are done modulo `p`.\n\n`p`in binary is represented with `254` bits, but `p` itself is not `(2^254) - 1`. This means that all the values in the range `[p-1, (2^254) - 1]` cannot be represented in our field without overflowing.\n\nThis means that all decimal values represented with `253` bits fits in our group, but not all of the represented with `254` do.\n\nWait... but why I said that sometimes the limit is `252`?\n\nThere are special cases such as in `comparators` where the strategy to compare numbers is calculating the addition of a bigger middle number with the delta between the two compared numbers. Then, just by checking the `MSB` of the addition, we can see if the delta was negative or positive infering the value of the comparison. \nIf we compare two `253` bits values, we need the middle value of the values represented with `254` bits, which we saw that could raise a serious bug.\n\nSo we can compare numbers up to `252`. \nFor the specific algorith on how this comparison works, check the [first exercise of the session 1 homework](./homework/session1.md).\n\nTo prevent the **alias bug**, circomlib has a circuit that works as a check: the [AliasCheck circuit](https://github.com/iden3/circomlib/blob/master/circuits/aliascheck.circom).\n\n### Homework S7\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session7.md) file.\n\n## Session 8\n\nIn this session, in addition of the next title, we also reviewed the Alias Bug, but I reasearched about it in the last session (yes, I went ahead of the program, but it's ok).\n\n### Hacking Underconstrained Circuits with Fake Proofs\n\nIn circuits where the `\u003c--` operator is used to compute intermediate signals, there is a vulnerability because the intermediate signal is not constrained. Although in Circom we cannot directly decide the value of intermediate signals (because Circom does it for us), we can modify the bytes content of the witness generated by Circom (we can force our intermediate signals to be anything).\n\nThere isn't much theory behind this type of bug. It can present because of an overlook of the devs when using `\u003c--` to give values to intermediate signals thinking that the user cannot later change them. Remember, Circom just creates R1CS and then given a valid input, generates a witness (the witness is not the proof itself!!).\n\nCheck the homework file linked the next section for the whole context and explanation of the bug. \nSecurity Researcher mode: ON. Have fun!\n\n### Homework S8\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session8/session8.md) folder.\n\n## Session 9\n\n### Conditional Statements in Circom and The Quin Selector\n\nIn the session 5, I already introduced the intricate behavior of Circom regarding conditional statements. Luckily, today in the bootcamp we learned about it, why it happens and how to solve it(or at least mimic their behavior with signals).\n\nEssentially what creates the problem with conditional statements and signals is that the R1CS should never change. For a given circuit, it's always the same L, R and O matrices, what changes is the witness. If we have this in mind every time we write a circuit, we will never use signals to affect the behavior of a conditional statement, and the value of a signal cannot be assigned depending on a condition of other kind of values. The R1CS is generated at compile time, so the circuit’s structure (constraints) must be independent of signal values, which are only known at runtime.\n\n\nFrom the ZK book: \"If-statements are acceptable if they are not affected by any signals, and do not affect any signals.\"\n\nThe workaround to this problem is using the logic that we have been using with aritmetic circuits. The name of the technique is \"branching\", and there are two ways of seeing it. See this example:\n\nGiven an array `[10, 5, 3, 77]`, I want to input an index `x`, and I want the output to have the value of the array in the index.\n\n1) We can see this as a lagrange interpolation problem where the set of `x` values of the function will be the possible values for the index and the `y` values will be the values of the array. The set of point we want the polynomial will be: `{(0, 10), (1, 5), (2, 3), (3, 77)}`. Evaluating this polynomial at the input index `x` yields the corresponding array value. \n\nThis approach is too complex to be done in a circuit, it's impractical but theorically correct. It helps to see the pattern (at least to me).\n\n2) We can use the `IsEqual` template from `comparators.circom` of the `circomlib` and make our life easier (this is the standard when writing circuits):\n```circom\n  x_eq_0 \u003c== IsEqual()([x, 0]);\n  x_eq_1 \u003c== IsEqual()([x, 1]);\n  x_eq_2 \u003c== IsEqual()([x, 2]);\n  x_eq_3 \u003c== IsEqual()([x, 3]);\n  otherwise \u003c== IsZero()(x_eq_0 + x_eq_1 + x_eq_2 + x_eq_3); \n\n  out \u003c== x_eq_0 * 10 + x_eq_1 * 5 + x_eq_2 * 3 + x_eq_3 * 77;\n```\n\nThe otherwise signal is 1 if `x` is not 0, 1, 2, or 3 (i.e., an invalid index), and 0 otherwise. This can be used to enforce that the input index is valid or to provide a default output.\n\n\nAll this I mentioned was awesome to introduce the **Quin Selector** design pattern that says...\n\nThe Quin Selector pattern is implemented in the IsEqual approach, where x_eq_0, x_eq_1, etc., act as indicators (1 for the matching index, 0 otherwise). Multiplying these by the array values and summing them selects the correct value.\n\nWe multiply the desired index by 1 and the rest by zero, then sum the result.\n\nEssentially what we have been talking about!! So now you know the Quin Selector pattern haha.\n\n### Homework S9\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session9.md) file.\n\n## Session 10\n\n### Stateful Computation\n\nWe are not able to do stateful computation in Circom. We just simply cannot stop a computation in the middle of it and return a value. Circom compiles to an R1CS under the hood, and the underlying R1CS needs to have a fixed size. The logic behind why conditional statements are tricky in Circom is the same: the R1CS structure.\n\nHowever, as with conditional statements we have a workaround for this kind of problems. We will do all posible computations and then select one of those. As for example in the [homework](./homework/session10.md), I computed all the powers of an input `base` up to the `n`-th power, but selected the power in the input `power`, with a [Quin Selector](#conditional-statements-in-circom-and-the-quin-selector).\n\nThis session was quite short but the idea was to get comfortable with the DLS and its logic for writing circuits. Forgetting the programming logic to adapt to this logic of writing circuits that compile to a R1CS is challenging but not hard, it just takes practice and time.\n\n### Homework S10\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session10.md) file.\n\n## Session 11\n\n### Swapping Two Items in an Array\n\nSwaps in Circom are tricky because in a normal programming language we can access an array at an index and then modify it as we wish. Remember that in Circom:\n1) We cannot directly index an array of signals.\n2) Signals are immutable once assigned a value.\n\nAny array manipulation in Circom requires creating a new array and copying the old values to the new one, except where the update happens.\n\nIt's the same logic that we used in the previous session.\n\n### Homework S11\n\nProposed exercises and their solutions are in the homework folder in [this](./homework/session11.md) file.\n\n## Session 12\n\n### ZeleKtion Sort\n\nStateful computations are common but tricky in Circom due to immutable signals and fixed R1CS. We explored proving Selection Sort, which swaps each index with the minimum of the remaining sublist. \n\nKey points:\n\n- Selection Sort: Iterate through array, swap index `i` with min of sublist `i..n-1`.\n- Circom approach: Create new arrays for each swap, track intermediate states.\n- Built on prior sessions: Used `QuinSelector` for indexing, `Swap` from Session 11, and min-finding from Session 10.\n- Proved correctness by verifying min selection and swaps for each step.\n\nThis is a gentle intro to stateful ZK proofs, like hash functions, where intermediate states are key. Also it was said that understanding this is key knowledge for understanding zkVMs (I'm really interested in that specific topic).\n\nAs you can see now, all previous sessions converge here and they all make sense. The structure of this bootcamp is well designed.\n\n### Homework S12\n\nProposed exercises and solutions are in the homework folder in [this](./homework/session12.md) file.\n\n## Session 13\n\n### Stack Based zkVM\n\nWe've finally reached the part of the bootcamp that had most of my interest: zkVMs!\n\nFirst we reviewed all the content that is basically in this chapter of the ZK Book: https://www.rareskills.io/post/zk-stack\n\nKey points of that chapter:\n   - zkVMs use tables to represent everything that involves stateful computation (at least stack based VMs, I'm unsure about register based VMs)\n   - How the zkVM works is actually easy to understand. The hard part is writing its behavior so we enforce correctly the constraints. I we were about to program  the behavior of this simple stack, with a normal programming language (Rust for example🦀) it'd be easy. But here we are using a zk DLS (Circom) which makes it trickier.\n   - With all the previous knowledge and proficiency we gain in Circom from last sessions, writing the code for the zkVM is actually easy. We just have to take the pieces and put the puzzle together.\n   - The \"zk\" in zkVM is not because the execution of the code is private, but because we use zk tech to build proofs of execution of code that can be verified succintly. We know that somebody executed the code and got an output without the need of doing the execution ourselves and comparing the result.\n   - As we saw in session 12, for stateful computations in arrays, we use many arrays where each one of them represent an step or modification of that array. Signals in Circom are immutable so we found this trick to mimic that behavior. For zkVMs, each \"step\" would be an opcode or instruction, and we will have as many arrays representing the changes in the stack as opcodes we have.\n\nI strongly recommend checking out the homework and solving it yourself.\n\n### Homework S13\n\nProposed exercises and solutions are in the homework folder in [this](./homework/session13.md) file.\n\n## Session 14\n\n### 32-Bit Emulation\n\nLearned how to emulate 32-bit arithmetic in Circom, where signals are field elements (mod a large prime). \nKey points:\n\n- 32-bit words (mod 2^32) are needed for hash functions and VMs, unlike field elements.\n- Range check: Use `Num2Bits(32)` to ensure signals fit in 32 bits, more efficient than `LessThan`.\n- 32-bit addition: Range check inputs, add as field elements, convert sum to 33 bits, take 32 least significant bits.\n- 32-bit multiplication: Similar, but needs 64 bits before taking 32 bits.\n\nThis is useful for proving traditional computations. The end goal is to review hash functions.\n\nAs for homework we have to build a 32 bit division template. It was said that that kind of circuits are usually full of bugs.\nI will be sharing here my research about that.\n\nCounterintuitively, the following constraint is not enough for the ZK division to be safe:\n```circom\nquotient \u003c-- n \\ d;\nmodulus \u003c-- n % d;\n\n// not good enough\nn === quotient * d + modulus;\n```\n\nThis is because altough n (numerator) and d (denominator) are inmutable signals, quotient and modulus don't have constraints assigned. And altough Circom does compute a valid result for the quotient and reminder (modulus), nothing stops an attacker to modify the resulting proof and pass incorrect values for quotient and modulus that will satisfy the only constraint.\n\nReview [session 8](#session-8) for more clarity on the topic, especially the homework.\n\nOne of the solutions for this kind of circuits is quite simple. [Here](https://github.com/succinctlabs/sp1/issues/746) there is an example of a `Div` circuit bug that was found in Succinct SP1 VM.\n\nThe proposed solution is constraining that: `remainder \u003c denominator`.\n\nBut be cautious... this is a good solid solution for 32 bits division, where `d * n` is at most 64 bits, in a ~254 bits field (Circom field). If we had 128 bits computation in a 256 bits field, we'll be at risk of overflows.\n\n### Homework S14\n\nProposed exercises and solutions are in the homework folder in [this](./homework/session14.md) file.\n\n## Session 15\n\n### MD5 Hash Function\n\nImplemented MD5 hash in Circom to compute and prove correct execution. \nThough not secure, MD5 mechanics mirror secure hashes. \n\nKey points:\n\n- Bitwise AND/OR/XOR/NOT, LeftRotate, 32-bit addition (mod 2^32), `Func` for register logic, input padding; learned this in the last session.\n- Padding: Add 0x80, zeros, and length to 512-bit block.\n- Output: 128-bit hash in big-endian.\n- Built on Session 14’s 32-bit emulation (range checks, bitwise ops).\n- The whole MD5 template takes +52k R1CS constraints; ZK-friendly hashes are more efficient (way more) and that's why they are so relevant.\n\nPrevious sessions built the knowledge for this. In the neext (and last) session we will learn about ZK-friendly hash functions, which don't build on 32-bit words but on the native prime field of the DLS (Circom in this case).\n\n### Homework S15\n\nProposed exercises and solutions are in the homework folder in [this](./homework/session15.md) file.\n\n## Session 16\n\n### ZK-Friendly Hash Functions\n\nExplored ZK-friendly hashes (MiMC, Poseidon), which use native field elements for fewer constraints than SHA-256(or MD5 which reviewed last session). Key points:\n\n- Unlike 32-bit hashes, use field addition/multiplication, avoiding bit decomposition.\n- MiMC: Iterates input with exponentiation (e=7 for Circom), 91 rounds, 364 constraints for one input.\n- Poseidon: Adds matrix multiplication, fewer rounds, 213 constraints for one input, scales better for multiple inputs (240 for two inputs).\n- Properties: Preimage resistance, collision resistance, pseudorandomness.\n- Builds on Session 15’s MD5 (52k+ constraints) to show efficiency gains.\n\nThis was the final session of the bootcamp!\n\n## Conclusions\n\nAfter two months of lectures on topics I was unfamiliar with half a year ago, I’ve gained deep knowledge in building ZK circuits, from stateful constraints to optimizing R1CS. This foundational bootcamp covered ZK primitives and Circom’s DSL thoroughly, equipping me for practical applications.\n\nI now feel confident to advance in ZKPs, focusing on security aspects that fascinate me, like circuit vulnerabilities (Session 14’s division bugs). My next steps are learning Plonky3 and Halo2, widely used in industry, and competing in audits to test my skills.\n\nI 100% recommend this bootcamp to anyone passionate about ZK with a solid theoretical foundation in finite fields and R1CS. It’s a game-changer for aspiring ZK developers!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsantiellena%2Fcircom-bootcamp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsantiellena%2Fcircom-bootcamp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsantiellena%2Fcircom-bootcamp/lists"}