ZK10: Halo2, Noir or Circom? Exploring DSLs as an app developer - Richard Liang
4 Oct 2023 · 1:00:22
Richard Liang compares three ways for an application developer to write zero-knowledge circuits, Circom, Halo2 (through Axiom's halo2-lib) and Noir, by coding the same integer division circuit live in each. In Circom, the quotient and remainder are computed outside the circuit and then constrained by rebuilding x and adding range checks, so a dishonest prover cannot slip in other field values. Halo2 is framed as filling in cells of a spreadsheet, where even addition is not free, while Noir needs only casts because all computation is constrained by default. His closing comparison recommends Noir to newcomers and Circom as the faster route to production.
Speakers
Topics
Transcript
For our last workshop of the day, I'm going to hand off to Richard to talk about options Halo 2 Circom Noir. What should you use? So. Oh. Yeah, thanks Paul for the intro. And yeah, thanks guys for coming to last workshop of the day. I was joking before this that the people the amount of people in this room is representative of the number of app devs currently in the ZK space. So, kind of makes sense. So, yeah.
But yeah, title of my talk uh is Halo 2 Noir Circom and we're going to be exploring it from more of an app developers kind of perspective. Um yeah, so just a little bit about me. I'm Richard. Um I'm part of a project called Set Labs where we previously built a a DeFi asset management protocol and launched some popular I guess DeFi financial products as well. There I mostly did smart contracts and also did a lot of like product managing as well. And right now I'm learning ZK and writing a lot of ZK circuits. And I'm primarily interested in using ZK to bring off-chain data on-chain to build more meaningful
applications. Uh some of the open source projects that I'm working on and definitely reach out or or ping me if you want to collab or have any ideas. One is a ZK P2P. It's a trustless P2P on-ramp using ZK proofs of email DKIM signatures and we're doing it with some grant support from the Ethereum Foundation. Um also working on like a implementation of RSA in Noir and you can see that here as as And lastly, um Halo 2 doing some proving of a ZK certificate chain, I guess ZK certificate authority chaining.
Cool. Yeah, just quickly this workshop is going to comprise of these following like topics. I'm going to give a brief overview of like ZK. Um and yeah, disclaimer, these are kind of personal opinions and personal experience working with these DSLs. I'm a third party not affiliated with any of the teams developing these tools above. Uh so we're going to start with Circom first, uh do a do do a little um exercise implementation in Circom. Then we're going to move to Halo 2 and do the similar implementate implementation in Halo 2 and then finally Noir. And then lastly, I just have some summary um points and yeah, if there's time, we can have some
discussions. Uh yeah, so before getting into the meat of the DSL, so why even build ZK apps in the first place? Um yeah, if you can see like uh off-chain apps uh or yeah, why even build ZK apps in the first place? Uh if you look at all these dimensions, off-chain apps right now uh have great UX. They're very scalable, very customizable, like private between users at least, but they're terrible at being composable. And if you lose your data, you or you get you lose your access to the platform, you lose your data, right? Um
whereas if you look at on-chain apps today, uh UX sucks. Uh you got to use EOAs, manage your private keys. They're not scalable. Uh they're not customizable. Um they're yeah, the whole blockchain's transparent. Um but they do have a great property of composability and you do own your own data, right? And lastly, if you look at ZK apps, ZK ZK apps basically combine the best of off-chain apps with the best of on-chain apps.
So with UX, you can see more and more, I guess, account abstraction solutions being developed using ZK. There's like a lot of co-processors, roll-ups building stuff that offloads compute off-chain. There's customization that can be built as well, which can build more robust on-chain loyalty programs. As well as transaction privacy enabling like hidden information games, while at the same time maintaining the composability and data ownership that on-chain apps offer.
Yeah, this is a TLDR, pretty much. Um. Web2 is left curve. Yeah, so now that I've man Where is this? Oh, yeah, now that I've shilled you on ZK apps, you realize there's a massive problem cuz it's really easy to get overwhelmed entering the ZK space today. There's new DSLs, frameworks, proving stacks, and research almost released like every week or daily even. There's like unique engineering quirks and gotchas because all these DSLs or languages are closely tied to an underlying proving stack.
So yeah, you as an app dev basically need to make a bet on the ecosystem you want to build on, right? And honestly pretty similar to like back in the day you're like betting on the EVM and building on Ethereum, right? Okay, yeah. Uh, I think we can go pretty fast through these slides cuz I'm sure everyone knows like ZK basics and DSL basics. Um, so, yeah, we're we're we're working with finite field elements rather than bits.
Um, in Halo 2, Circom, and Noir, we're working with 254-bit integers, whereas in Solidity we work with 256-bit integers, uh, which is different. Uh, I guess this is also caveat with their most common backends. Uh, yeah, one concept I do want to bring up is the the concept of having like a cryptography brain versus a dev brain when you're building, uh, circuits. Um, if you're a regular software dev, you basically, uh, have some inputs, you apply some algorithm, and then you return the outputs. Uh, whereas when you're building cryptographic, I guess ZK circuits, uh, you do have access to the entire
computation, uh, trace including the outputs right off the bat. Um, and that requires a little bit like different kind of thinking cuz when you think about like when you want to square root something, it's just equivalent to like taking the output, um, multiplying by itself equals the input, right? Uh, you basically have to also flatten all computation. Uh, there's no branching allowed. This is all true for all the languages and proving stacks. So, there's no ifs, no no breaks in loops, uh, when you're dealing I guess under the hood, right? Um, and lastly, yeah, you do have to apply a lot of adversarial thinking. So, the prover is not honest. It's honestly pretty similar to smart contract engineering as
well. Uh, yeah, just I can quickly just go over this. Basically, uh, the diagram shows the ZK modern ZK proving stack. And as an app developer, um, you mainly just need to know like what's going on in the front end. And the DSL the yeah, the DSL is crucial to ZK as it translates the business logic that you have to the initial computation trace to be fed to the rest of the stack. Um.
Okay, yeah, that was a speed run through I guess the intro. Uh, just moving on to I guess Circom. Um, yeah, Circom at a high level is the main language used in production apps today. Uh, like examples include like Tornado Cash, which is probably the killer app of ZK right now. In production and also Dark Forest. Um. And yeah, Circom offers pretty pretty fast browser proving time because they spent a bunch of time optimizing the WASM proofs in the browser. Um, and it also has a really fast server-side proving uh, by using the Rapid Snark uh, framework.
And Circom's relatively easy to pick up as a dev and prototype new circuits. Um, there's this tool called or Woah, woah. That did not Okay, yeah, yeah. That that's the slide I'm on. Um, yeah, Circom's relatively easy to pick up and prototype uh, using uh, ZK repple.dev, which is a great tool. Um. Uh, yeah, in terms of the mental model of writing uh, Circom um, for me at least, writing Circom is basically like constructing a tree. Um, you have like your initial signals, say it's like A, B, and C. Uh, you can add B and C together,
uh, multiply by A, it forms D, and D multiplied by E forms out. Um, which is the final output, I guess. Um. Yeah, Circom at a of sir come uses the R1CS or arithmetization scheme which basically expresses these gates you see in the top diagram as linear combinations. Um so yeah, linear combinations basically only allow plus and multiply uh like operators and yeah, only quadratic constraints are allowed.
Um but if you if you can see like B and C are bundled together in addition. This is because of the R1CS property where addition gates are like free when you use this a scheme and sir come. And we'll see that like addition isn't free in other I guess proving languages like Halo 2 or Noir later. Uh yeah, just a quick like I guess slide on the syntax. Sir come basically looks like a flattened kind of assembly language plus like JavaScript. So it's very I guess approachable for new devs.
Um this like fancy like uh arrow you can see is basically assigning constraints um for yeah, for for these for this circuit on the on the right. Um There's there's this concept of like a single arrow you can see. It's basically uh you're assigning the witness but you're not assigning the constraints which means anything right of the single arrow the prover can actually replace when it's actually um uh inserting inputs into the circuit. So yeah, we're going to also see like an example where we use we have to basically assign witnesses and constrain
it later uh in this exercise. Uh yeah, there's no if statements um, in sir come so it's very annoying to work with, I guess, um, as a dev- developer. Uh, you can see like in the multi-mux example right here. I don't know if you guys can see. Uh, it's pretty small. But, uh, yeah, you're basically, uh, you CI1, uh, and you have to subtract CI0 from CI1 and multiply it by like a selector, uh, which is a Boolean of one or zero, and then you have to add back CI- uh, CI0. So, if the selector is turned off,
then, um, then you return CI0, but if selector is turned on, you return CI1. So, some like weird like math you got to do um when building in Circom. And lastly, what what really annoys me about Circom is that you can't index into an array. Uh, so for example, like you have a signal B, which is calculated in circuit, you can't just array index into, um, the array that way. You have to do some like weird Yeah, weird thing, um, that I'm not going to go over today.
Okay, cool. That was a speed run through, uh, Circom. So, I guess, yeah, uh, we can kind of like uh, live code, I guess, uh, a circuit together. Um, so, yeah, the exercise I have here, you guys can scan the QR code. Um, I'm also going to pull up um uh, my my editor as well. Um, so, I'll give it a minute. Yeah, basically, um, the exercise is implementing integer division in Circom.
So, I think the meme on the right kind of summarizes our task Uh, pretty perfectly. So, when you're dealing with Python, you obviously have like floating points at the end. When you're dealing with Solidity, it truncates all the decimals. But, when you're dealing with ZK, you can't just do like 10 / 3 cuz it's just going to return like the multiplicative inverse of the finite field. So, let me switch over Okay, yeah. This is the Yeah, this is the GitHub. Um some instructions here, but we'll uh
start with Circom. And with Circom, I think we're just going to use the ZK REPL tool cuz it's much faster to prototype um than installing, I guess, Circom and snarkJS locally. Oh, yeah. The other other, I guess, um assumption here is we have to assume where at X and Y uh where we feed in as inputs are non-negative integers uh that are less than 2 to 32 bits. And we can we'll see that is important um in a minute.
Okay, cool. So, yeah. I've already pre-copied over the um the template that we're going to like build together. Um I guess like since it's a pretty small group of people, feel free to like jump in if you have any ideas like how to how to start. And what what we what you guys want to do. Um Okay, I guess I can just start.
Um Can you zoom in a little bit? Yeah, yeah, yeah. That's good. Yeah. Perfect. Perfect. Okay. You can kind of see in this template that it's basically taking in uh two inputs, X and Y. You basically want to calculate the quotient which is the integer uh of when X / Y. And you want to do that um within the circuit, right? Um so I think like the first thing that you might want to do you might try to do is you want to do this, right?
Like ideally that is what like as a software dev you would want to do when you want to divide integers, right? But yeah, as you run the code you can kind of see yeah, non-quadratic constraints are not allowed. And that is I guess the number one error that Circom devs run into. Um and it's the most annoying I guess error as well. I think that's because of the reason that when Circom compiles uh X / Y, uh it thinks it's trying to calculate the multiplicative um inverse of it. So you can't do that.
Uh luckily Circom has like an operator which is this. It's the forward slash. Which basically means like do integer like division. Um but like if you run the code again um it's still showing this error. Um this is because and the circuit doesn't realize doesn't know like what integer division is, right? At the lowest level. So, you need to come up with an alternative way to do that do that.
Um and yeah, I guess this is where uh it's important this is where like you kind of need to use like the single arrow. And as I kind of mentioned earlier, single arrow means like you basically do some computation outside the circuit and then feed it in as input here. So, in this case X and X like I guess forward slash divide by Y is not constrained I guess in the circuit. So, when you actually um when you actually prove it, like the prover can just insert whatever values they want in to the right of this arrow, right? But for our purposes, we need to first calculate the quotient and then constrain it later. So, that that is
important. Um I guess yeah, so given that I think if you run it, it's not going to fail. So, that that's already good. Um the second thing you need to do is you need to uh kind of calculate the remainder um because all integer divisions kind of uh return a remainder. Um so, you would do modulus Y in this case and you would also use single arrow because it gives the pre same like error previously uh of non-quadratic constraints because modulus isn't recognized as a valid operation in the circuit, right?
And then yeah, so you kind of have these two values that you have or you kind of have this quotient and remainder value but um but you haven't done any constraints on it uh quite yet. So, you got to find a way to constrain it. And I guess the the the way to do it is you want to kind of reconstruct Oh, wait. I need to We to declare a declare an immediate signal. All right. Yeah, the way to kind of do it is you you want to reconstruct X uh by taking the quotient uh multiplied by Y and then add back the remainder.
And then you want to constrain that to be X, right? Okay, that also you also run into that. With ZK-Raffle Dev. Okay, cool. Okay, run it again. It passes and you can see that output is two. So, we've done integer division correctly. Um but there's another I guess issue with this. Uh I guess do any of you guys have any ideas? What about negative numbers?
Uh yeah, exactly. Um so, we're operating I guess first off we're operating in a finite field. So, you can think of the prover being able to insert quote any quotient or any remainder um uh I guess inside the circuit because we've only used single arrows here, right? So, there's no constraints to quotient or remainder. So, basically prover has two variables to play with. And since we're in a finite field, there's like an infinite number of like or a a large amount of quotients that are valid and a large amount of remainders that are valid that they can
insert into this. Um I think I have an example here. I need to I need to copy. I can do that really quick. Okay, first I need to do this. This is all live coding, so So this is some like random large field
element that is the inverse of the of Y. And I think if we plug in or or I guess of X. If we plug in X, it would pass. Oh, no, that's not right. Remainder zero, quotient
Remainder quotient one if you want. Yeah, I think it basically just I mean All right, yeah, yeah, I think What What was I stuck Yeah, I think basically if you multiply it this way, then um then you can kind of insert infinite amount of or like any kind of quotient uh where it's it satisfies this and then where the remainder is zero. Um you can plug this in and I reconstruct and it'll make this test or make this input pass um still. And so yeah, I guess the to
remedy this is uh you do need to add range checks at the end to ensure that the number is not negative and it's not greater than like 32 bits uh as part of the uh as part of the inputs. So, for that, we need to introduce uh components. And there's they have So, Circom has this less than chip or less than template uh where you can use uh to pretty much um check the range.
Yeah, you basically have to check the quotient is less than uh X or less than or equal to X and that the remainder is also uh less than or equal to X. Uh And then that should Yeah, uh it doesn't show up in the I guess in the outputs, but that basically means like that you've sufficiently constrained it properly. Um and I guess if you look into the less than uh template itself, it ensures that like the values cannot be
uh uh negative as well. Um so, yeah, that's kind of the solution for Circom. And it's a very roundabout way uh to implementing integer division. Uh, and it yeah, it kind of sucks if you're like a software engineer trying to learn ZK. Cool. Um, yeah, and I guess like the solutions are in GitHub if you switch the branch to solutions as well. So, yeah, you guys can take a look at it on your own time.
Cool. Um, all right, so that's that's Circom. Can I ask a question? Yeah, yeah, go ahead. How much does it actually cost to load the data Uh, yeah, it's around uh so, Circom with a Groth16 back end takes around like 300k like base, uh and it depends on the number of public inputs you have. The more you have, the more like it increases from there. That's the call data, right? Yeah, the call data. Yeah, yeah, 300k. So, 300 and we're talking about Groth16 then.
Yeah, yeah, Groth16 in particular. I saw there is a Plonk option as well. Yeah, yeah, there's Plonk and then there's also like Nova Scotia, which is Nova in the back end coming out soon. Um, but yeah, they're different and how they differ in Nova Scotia? Uh, I think I don't know I don't have the specific numbers for uh Groth or Circom plus Plonk. Um, but it's more uh than Groth16. Other than that, it's pretty other than call data is pretty constant cost, right? Yeah, yeah, yeah, 300k is the constant. Yep.
And the constant for Groth16 as well, right? Yeah yeah Groth16 yeah. Yeah. Awesome, thank you. Yep. Cool. Um, I'll move on to Halo 2 now. Uh so Halo 2's originally developed by Zcash. Uh and the current version right now is maintained uh by PSE uh in the Ethereum Foundation. And there's a couple projects building libraries and tooling on top of uh Halo 2. And these include ZK email, Axiom, Scroll, and EZKL.
Uh strictly speaking, Halo 2 is a library in Rust, not a DSL in the strictest sense. Um but yeah, for our purposes, we'll treat it like one in this workshop. And today we're also going to be focusing on what Axiom developed, um their Halo 2 lib, which is considered as a e DSL or embedded DSL, which basically means it's a library built on top of vanilla Halo 2 uh that abstract abstracts away um a lot of complexity with dealing with Halo 2. And I guess, in my opinion, much easier to get started with and actually makes Halo 2 uh a lot more approachable.
Yeah, so Halo 2 is good because it has a very fast prover, uh good recursion, um and yeah, very customizable like front end um that you can plug into multiple back ends. So, compared to StarkCom, uh the mental model for me for writing Halo 2 is like assigning cells in a gigantic spreadsheet and specifying the relations between those cells. Or um another another alternative way to think about it is kind of like building a Sudoku puzzle. Like there's only one way to get the right answer uh given like all these predefined, I guess numbers right?
Uh so in vanilla Halo 2, uh you can en- that enables you to create custom gates with many, uh, columns in the spreadsheet. Um, so but today, uh, we're going to focus on Halo 2 lib, uh, which is by Axiom. So it only and their specific custom gate that they offer is a single vertical gate with a single selector. And if you look at the table, uh, on the right, um, you basically you can see there's only one advice column or a single column. And then the selector is like I guess the fixed pre-process column, which doesn't affect the witness uh, generation.
So you can see like, uh, on the table on the right, you first load in X, uh, which is just a value, and you copy X into row one and also row four. And then you also assign a constant one into the spreadsheet. And then for the very last row, you're basically specifying that I want to add, uh, row two plus the multiplication of row three and row four. And that's kind of is specified by the selector. Uh, uh, right here in bold. And as you can kind of see that addition and and subtraction aren't free cuz you do need to assign cells, um, for, I guess, yeah, for any kind of
uh, operation that requires these. And that's different from Circom and R1CS where if you have a lot of addition, it's basically free for you. In Circom, addition and subtraction is free? Yeah yeah yeah. It it it's independent from Groth16 or Groth13 right? Uh, only Groth16. Only Groth16. Yeah yeah. Uh, yeah, just a little bit about the Halo 2 lib syntax. Um, basically there's these two templates or or two libraries, gate and chip or a gate and range, uh, which implement helper functions. And
these helper functions are basically, uh, templates that you saw in Circom. And these basically interact with the custom gates, uh, under the hood, so you don't have to. Um, and it actually makes developing much easier. So, if you can I don't know if you can see on the right on the left, um, in the select, uh, helper function, you're basically assigning eight rows, uh, in the table. Um, yeah.
You're basically assigning eight rows in the table, right, to do a select. And select in this case means like like it an if statement inside the circuit. And yeah, the downside, I guess, I would say of building Halo 2 circuits is it's easier to mix up writing in circuit versus out of circuit logic because all Rust is valid and Halo 2 is a Rust library, right? So, you got to remember to use select, uh, which is has that API versus using like if in Rust.
Whereas in Circom, if you tried to use an if statement, it'll just error on compile for you. So, you got to be a lot more careful, I guess, writing Halo 2 circuits. Yeah so uh because Halo 2 is kind of like, uh, you're assigning cells on a spreadsheet, you can kind of plot out what it looks like. And this is just the simple, I guess, plot of what the previous circuit looks like. Um, all the the column on the left is all the witness values that you sign.
And you can kind of see that number of cells will always be the same in Halo 2. Um, Um but the rows and columns can be configurable. Um and so what that means is you can make it like I guess two columns for example and reduce the number of rows you want to use. Um and the trade-off is that more rows means higher higher cost to prover and more columns means uh higher cost to the verifier for the most part. Um there's some additional I guess nuance to that. And rows additionally must be like a power of two. Uh in this case in this diagram, I guess you can't really see but um k equals four in this case so there's 16 rows in this table. Um and
the red is like the last seven rows and it's like blinding factors and basically uh you need that to uh preserve zero knowledge, I guess. in Halo 2. Okay, yeah, that's that's a little overview of Halo 2 lib and I guess we can move on to our second exercise which is implementing integer division but this time uh inside Halo 2. And I think I need to switch to my screen.
Would you be able to increase font a little bit? Yeah. Okay.
So if you guys have your um laptops and are following, I'm in the lib.rs uh file uh within the Halo 2 in div. And yeah, you just have to CD into the um Halo 2 in div uh folder to run any, I guess, cargo commands. Um as you can see, I guess, already with the scaffold, there's already a bunch of code uh that you need to like set up even before building any kind of circuit in circuit logic.
Um yeah, so I'm basically just specifying a new chip uh instantiating, I guess, some of the axioms um yeah, axioms helper functions. And yeah, just created a function here for integer division. And yeah, yeah, basically, you guys kind of already saw how to implement it in Sircom, that you need to assign witnesses first and then constrain it later. So, I'm just going to type that using Halo 2 syntax.
Uh so, you basically need to uh yeah, you basically need to special- uh specify quotient um and it's just x divided by y. Yeah, and the benefit of, I guess, using Rust is you can just use Rust. Or I use benefit of using, I guess, Halo 2 is that you can just use Rust to do division and modulus.
Um Yeah, so you implemented or so you first do the calculation of quotient and remainder. But then you need to load I guess the quotient and remainder into the table. Uh and that requires calling Let me see what API that I use. Oh yeah, self combination. All right. All right, sorry.
Loading is different. You need to load the witness into the table. So quotient and remainder are tracked. Um Okay, yeah. Now that now that you have quotient and remainder in the table, you kind of need to reconstruct uh X and constrain it that way. And so constructed X equals Um And when you reconstruct X, since you're
doing it within the circuit and not outside the circuit, you have to use halo2lib's helper functions. And so it goes self.range.gate. mul or I'll do it the easy way. mul And we'll do quotient times Okay, this is really hard.
You also need to load Y into the table because you're using it within the circuit. Okay, yeah. Once you multiplied quotient by Y, you need to add in the remainder. So, yeah, it's basically these two lines of code. And there's a mall and there's a ad. And uh And I just need to return Yeah, and I guess the test what you
built, you can just run it. And uh Oh, yeah. Yeah. Oh, there's no return. Oh yeah.
Is that for a field or something? Yeah, field. Uh so, field just wraps the value um into the hill two, I guess. Um, uh, prime field. Oh whoops.
Return quo. Okay, yeah. Test pass, but as with the circom example, um, you do need to range check. And range check is or, sorry, you need a first constraint equal reconstructed X and X.
Then you need to range check. Yeah, you basically need to make sure quotient is less than 32 bits and also rem is also less than 32 bits. And that ensures that you don't prover can't be dishonest. I don't know. Does that already check if it's a negative?
Uh yeah. Um, For the unsigned or Yeah. Yeah, yeah. Where Where's the docs? Yeah, it's somewhere in docs. Yeah, yeah, yeah. It's If you go just below, yes, on this line line 53 4.
Okay, perfect. Yeah, great. Yeah, if you look in the API, they have Yeah cool. Cool, yeah. And make sure that it's non-zero in the range check. Or non-negative, sorry. Um Yeah, really You have also have to load X into the witness table, so I'm doing that here.
Uh, yes, as you can see, there's a lot of overhead in converting types. And go to Cool, yeah. That should Oh, man, hell. That's close. Oh, oh, wait, wait, wait. I need I need to actually What's that? Uh, I think I just need to load X into the table. That will solve everything.
Okay cool. All right, that should be it and it should pass if no Okay, cool. Cool. So, yeah, this is basically Uh yeah, this is basically what you needed to implement um integer division in Halo 2. Uh it's I guess it's It's not great, but it's still I guess Uh it's getting better, I guess. I would I would say that.
Um Cool. Uh I guess like we're 45 minutes in. Um I Just enough time to go over Aztec Noir. And what would be the gas cost to implement In uh for Halo 2? Yeah. Uh it would be around I guess 400k uh all in. Um I think you could have you could optimize it to like two 280 at the minimum, but like I think for normal purposes it's probably four.
Uh it depends on like aggregation, recursion, and how how how many columns in a table you have. So, it's where the only linear in this case. What's that? It's only linear in this case Y- Yeah, I think it's uh yeah, I'm not sure. I need to double-check on that. Yeah. Cool. Yeah. Uh just quickly, Noir, I mean, if you guys attended the Noir workshop before this, you kind of already have a sense of what Noir is. Um it's Yeah, this new language by Aztec compiles to any number of backends, right? And kind of neat removes that need of the cryptography brain when
writing circuits. Um it does have if statements, casting, and bit shifts in the syntax level. So, it makes it easier, I guess, to program your apps. Um It has a lot of nice tooling, but the drawback is that depends on the size of the circuit, Noir is like could be up to a couple times slower than Circom and Halo 2 when proving. Oops. You're talking about the size of the circuits produced or the uh the Uh proving time.
Uh depending on the size of the circuits yeah produced. Uh the proving time may be a couple times slower for a similar circuit. So, if you use Halo 2 with Noir and and just Halo 2 by hand, is the circuit that bigger with Noir, is that what you're saying? Uh I I guess I'm saying like dependent on your business logic. Like given the same business logic for Halo 2, uh Circom and Noir, uh Halo 2 right now, or I mean Noir right now, may be like less efficient um depending on the circuit you're trying to write.
Uh yeah, in terms of arithmetization, it's kind of different from Halo 2 and Circom uh in that it doesn't actually compile down to the constraint system. Um it first enters this like intermediate thing where anyone can take the trace and apply any arithmetization to it to connect to a back end. So, by default, uh Noir uses like Aztec's like UltraPlonk back end. Um yeah, I'm not going to go into this too deeply, but basically it abstracts a lot away a lot of the complexity uh of writing, I guess, circuits from a like a circuit developer perspective.
And yeah, and yeah, like casting in this case, you see, is basically that range check code we wrote um, with less than in Circom and range check in Halo 2 lib, you can just do something like as U32 or as U64 to do that range check for you. Um, but the downside, I guess, is all computation is constrained by default, uh, which is safe, but uh, if you want to do what we did uh, where you do the single arrow in Circom or native Rust in Halo 2, you have to prefix the function with unconstrained.
And yeah, kind of like uh, we can kind of go to our last exercise for the day. Um, just implement the same thing, integer division in Noir, and I guess I'll switch to the editor again. Okay, so I am CDing into the Noir and div folder.
And I think there I've already installed Noir goal and I've set up the, I guess, template for integer division. And yeah, we just need to implement it. Um, and yeah, I think in Noir, it's actually pretty straightforward. Um, so the inputs are to the function up here is field. Uh, we kind of need to uh, range check that, ensure it's a U32, and then basically divide that. And so, Noir is kind of like this.
You first have to cast X as a U32, basically means it's range checking Y as U32. It range checks. I think I have the function signature return a field, so you have to convert it back to a field. Um, and I think that should be it. And yeah, just checking with the Noir test. Yep, that's it. Um, that's I guess that's all the lines of code you need to do uh, do integer division in Noir. And it's as you can see, it's much less overhead in um, yeah, in implementing I guess this.
Uh, and you don't really need to think about uh, any of that assigning witnesses or I guess yeah, assigning witnesses or manually calling range checks. Um, but yeah, this is a simple example. Um, it highlights I guess the benefits of Noir in implementing simple apps. But obviously, as I mentioned, there's some like tradeoffs to be made as of right now because the it is very experimental for larger circuits. Um, yeah, I guess I have a couple more minutes. I do have some more slides just summarizing kind of what we talked about today.
Um, I can go over them relatively quickly. Uh, yeah, in terms of like DevEx, as I just mentioned, Noir is great. Um, wins by far for building like uh, building ZK circuits as as like a beginning circuit circuit developer. There's great docs, there's a great package manager, and you can write stuff like normal software. And Circom, it's uh relatively easy to prototype still with ZK REPL, but syntax limitations force you to think like a cryptographer. Um and it's annoying. Uh Halo 2 is most flexible, but you do need to have experience uh with, I guess, switching between native Rust and then circuit logic. And
there is that overhead of scaffolding and imports that you need to be careful of. In terms of availability of primitives, uh Circom by far, I think, has the most libraries uh ready to use by devs, and also um uh yeah, l- libraries and the amount of libraries used in production. Uh Halo 2, there's an emergent ecosystem with Halo 2 lib. Uh there's less libraries in production, but yeah, I think it's rapidly increasing as well.
Uh but obviously, it's way less usable. Um and in terms of Noir, it's still very early. Libraries aren't production ready uh and very experimental, but I think the pace of development is really fast. And yeah, the team is great at helping debug issues for you. Uh in terms of privacy, if you're looking at it from an app, if you want to incorporate that into your app, uh Halo 2 is very fast. Uh you can if privacy is essential, you're able to generate a proof locally uh in browser, and then recurse that into a server.
Um that's like GPU accelerator or whatever uh without loss of privacy. But obviously, larger proof size, and there's way less WASM tooling. Um in terms of Circom, it's slow if you want to do privacy, but it's hyper-optimized in terms of WASM. You can prove up to 20 million dollar or 20 million constraints. Uh in terms of Noir, um, uh, it's also similar to Circom right now. Um, and in WASM, you can prove up to 512 K constraints, which is way less than Circom at the moment.
Is the goal of WASM proving is for it to run in the browser? Yeah, in browser. So, you can preserve the privacy. Um, cuz if you're running in a server, you're basically sending the server all your private inputs. Right. Uh, yeah, I guess talking about overall proving, regardless of server or in browser, Halo 2 is the fastest. Um, you can kind of customize your columns and use servers with high memory. Uh, Circom's also pretty fast with Rapid Snark. Um, it's 10 times faster than in browser.
Um, and Noir, uh, yeah, as I mentioned previously it's um, yeah, it's experimental. It's a lot is being built right now, but yeah, heavily depends on the circuit uh, you're trying to prove. Sorry, so in the case of Halo and Circom, you you need to do this computation in in the language, you witness the stuff, and then you build the constraints. Mhm. So, when you're trying to when you So, in my head, there are two steps. First, you want to generate the circuit.
Mhm. Then, once that's done, uh, I want to take my I want to generate my witnesses, and I'm going to do this a bunch of times. Mhm. Yeah. Yep. So, every time I'm going to generate a witness, I am also re-using the circuit, right? With that approach. You're revealing the circuit? No revealing. Oh. So, every time I'm going to rebuild the Or, you're not re-compiling, or you're not re-deploying the circuit. As in like, you're not re-generating proving keys. You're keeping the same proving key, but you're feeding different inputs into it. Um, but you are generating the and Um, it Is that Is that what you were asking?
So in in the body of the function you were defining for example multiplication Yeah. you were doing some computation for example in Halo Mhm. Yeah. Halo to Halo that's your witnessing and then you take your context and you start having constraints in the circuit. Mhm. Yeah. Uh, so adding the constraint part is useful only if I want to generate a circuit. Yeah. If I already done that then I only need the witnessing part. Yeah. Yeah. But in that function is going to be run anyway. So every time I want to generate a new witness I will populate this context and the character Yeah, exactly. You would have to repopulate the table The context is just the table in the back end.
Yeah, but I don't I don't think it's super expensive activity. Yeah yeah exactly. It would be nice to split the two processes into different phases. Yeah. Yeah. I think Yeah, I think it's two different processes right now. There's like the witness generation and then actually generating a proof. Um, that that is I guess kind of that is broken down uh, right now in like like uh, in Circom and Halo 2 right now. But yeah. I think that's a good point. Uh, yeah, I guess this is the the solidity verification cost that you you were asking. Um, Circom's by far the cheapest. Halo 2 Noir use Plonkish and are around the
same. Yeah, but this is I guess like this is almost the last slide. Um, basically overall I would recommend that if you're new circuit developer, you want to go from zero to one the fastest, I think you just pretty much should just use Noir. Um, the tooling's great. Um, very easy to onboard, too.
Um, but you trade off some efficient efficiency as you're writing large circuits um, right now. Uh, but the ecosystem is developing very quickly um, as I've seen. And Circom is kind of like that intermediate thing. If you want to go from 0.5 to like N N being like production um, it it's a good fit. Uh, it's beginner-friendly fastest to to productionize your app but performance deteriorates if you want to preserve privacy. And Halo 2 is even harder to use than Circom. Um, but I think there's some tooling and ecosystems being built that make it much more approachable.
And yeah, lastly uh, there's some stuff I don't cover today, but I'm interested in. Um, and they are Plonky2 uh, Risk Zero, and I guess the Nova Scotia uh, kind of back-end uh, that uses Nova and it's built uh, as a back-end for Circom. And yeah, that's Yeah, that's it. Um, yeah, thanks Thanks for coming. Uh, this is pretty long workshop. Had to cram a lot of content. And yeah, thank Thank you all for being app devs in ZK.
Cool. Yeah, um, I also do have like a like the resources list on the right, um, which I used as reference to construct these slides. So, I'll be posting everything there, as well. Cool. In Plonky2, uh, we are free to not use the ZK property which is makes the Plonky2 faster. Mhm. Uh, according to the ZK stuff. Uh, is Halo 2 providing this kind of option to us.
Or Non-ZK. Um You guys for example roll out something that doesn't require any kind of ZK features but Oh, yeah, yeah, yeah. Halo Yeah, I think the production apps using Halo 2 today don't preserve privacy. Like uh Scroll, obviously they're building on Halo 2. Axiom co-processor doesn't require privacy. They're also building on Halo 2. So, yeah, I think the production apps right now don't have privacy in Halo 2.
Cool. Um yeah, I think that's it. We're like at time, so thanks. Thank you.