ZK Voting App Deep Dive: Mixers, Merkel Trees, and Build with Noir - BuidlGuidl's Builder Bootcamp
BuidlGuidl's Builder Bootcamp at Devconnect Argentina 2025 · 18 Nov 2025 · 59:02
Philip Krause walks through how to build a fully private ZK voting application, starting with how a mixer similar to Tornado Cash breaks the link between a voter's address and their vote. He explains why a lean incremental Merkle tree suits on-chain registration, then shows how to define the zero-knowledge circuit in Noir, use nullifiers to stop double voting and generate a Solidity verifier contract. The session is part of BuidlGuidl's Builder Bootcamp at Devconnect Argentina 2025.
Speakers
Topics
From BuidlGuidl's Builder Bootcamp at Devconnect Argentina 2025, hosted by BuidlGuidl.
Transcript
Oh, and how does the screen sharing work? Ah, perfect. Hi, welcome to our next challenge or our next topic we want to speak to. It's about CK. But before we going to um start with that, I just want to chill quickly our speedrunners event on Thursday night. Um we have kind of organized a bar with some free beer, some free drinks and would be happy if some of you will going to join. So you have also the chance to talk the build guild crew, the build guild team and yeah met us all in person. I think that's our first time that we're doing something like that. So it would be very nice if you would come. Here's kind of our QR code. So if you want to if if you want to scan it, then you will find a link to our Luma event and sign up for that.
Okay. But here how do you I don't know how this work. Okay. But it's okay. Okay. Let's start with the secret. So I'm Philip from the build guild. I'm um happy to have all here. I had the honor of the last couple of weeks to go a little bit deeper into the Zika route and see what kind of apps we can build with it. I mean we have all these mathematics about CK very complicated stuff but what actually you could do with it and yeah and that's how the the CK voting app um um came up with and the next hour I just want to walk you through the CK voting app all the basic concept how it is done and then in the
end leave you out of the room that you can build your own app that will be my my my goal for today but first things first who of you have built a CK app so are one person. Okay. Not so many. So it's per two persons. Nice. So the the work workshop is perfect for you. And what about who of you have kind of voted in some onchain proposal or some voting or whatever. Yeah. Quite quite a ton. And this is kind of what happened also to me because the thing is when you vote on chain and anyone kind of knows your address all the votes you're doing are just out there in the public and everybody knows on which proposal you you voted at and that exactly why we why we why we're here today. So kind of the the presentation is kind of split. I walk you through some things. First we're
going to touch some how mixers work. That's kind of like tornado cache and how how these things are done. Then we need to touch on very important concept. Maybe for you it's sometimes it's it's Merkel trees but it's a special version of a Merkel tree. It's a lean incremental Merkel tree. Then we going into the CK circuits with Noah. What sounds very fancy and very complicated but actually it isn't. So it's pretty cool to see that and how we can get a very nice solidity verifier very easy out of that and then in the end all kind of how we putting everything together. But so first let's go with the the the mixer part. And the thing is is everything is kind of on a on a blockchain is transparent. I mean that's very good in in some sense because it's it's just very transparent and it's just no trust needed. Huh? Because you can see where your funds go, how many
funds are in a protocol, in a smart contract. This is just open to the public and so nobody really can scam scam you because you know exactly where the money flows. Of course, they can take it away, but you see at which address then has the money. So, it's completely different than in the banking system. But the issue is also for you. Imagine it's me. I going to get paid um some salary on a address, Ethereum address that I own and my employee employer already knows that address. So, they sends the fund to that and what I'm going to do now. So, these people in the company know my address and if I even send it to another address, the employee can just look into EA scan, see the transfer and see it. Okay, the account the new account. So I really cannot all my all my
wealth everything I have onchain is kind of public as soon as anyone knows the address. So they see all transactions, they see kind of the balances of all ESC20 tokens, just everything everything open and I think I mean that's an issue u especially for me and also all kind of other things you do on chain. It's not only sending money, you can also call functions in some smart contracts, you're playing games, whatever. Everything is just open. And that's kind of a huge issue. And so the the main issue really is how do you kind of break this connection between one address? It's kind of the the sender address and the receiver address. And in this case there there's one way and that's kind of mixers. So that's kind of a like a mixer in a basic concept. So you have on the one side you have kind of many people. they come all together and they and they kind deposit
something or put a secret into kind of a mixer. So all in the pool together and then in the end it happens that's kind of the last step. They withdraw or going to do something out of the pool. But the thing is everything in the mixer gets mixed. So there's no connection anymore from the one group who started to the other group. You only know okay that were the people but you don't know what each individual in there has done. This is kind of the the the concept of a of a mixer. And and like like I said, it has three phases. So the first phase is the deposit phase. And here um we we we kind of have a user that's you that's me kind of we going to generate some kind of secret. And this is only known to us. So we keep this secret. This is like our we should never head
it out. It's a little bit like our private key for for for any wallet. And um this secret kind of gets hashed into a commitment. So this could this commitment gets onchain and I could pair it in this sense with some ETH together and these go into into the mixer and the secret stays with me. So I can at some point prove because I have the secret I can hash it and I can say okay I have the commitment to that one and in the second phase many more users doing that and they all putting their EE into one pot. So actually there's just a huge pond where everyone throws in their their Eve and in the end everybody thinks okay that's my Eve and that's my Eve and that's my Eve but in the end nobody knows whose E is what. So so that's kind of the whole thing and the only thing what you
have you have kind of a note somehow you created before and this note can then prove that you are an owner of one of these Eve but e all these Eve in there are kind of the same. So how so how how is it done? I'm the user. I using a complete different account. So it's important that it's not my account address because if I would use again my same account address everybody knows that's me. So using a different address I somehow have a secret with the secret I proof in the in in in in the background to the smart contract that I can pass the gate in case this dog and I can take my Eve out. That's kind of the basic concept of a mixer. And here I gave the example with some Eve,
but you could also use the gate instead of the E withdraw. You can call whatever function could sit here in the back. What whatever you want. And in this case for our voting app, it's the it's a vote function, but we will see that later in a little bit. So that's kind of the the basic idea of how how a mixer works. One important thing about it because I was talking about if you imagine you have this whole group here here on the left side of course if if we would imagine in the in the in the in the in the extreme case there's only one person using this protocol I mean this whole privacy wouldn't make sense because there would be here one person put something in what should we get mixed there is only one Eve in there and I take it out of course everybody would know
that's me so it's very important the bigger you're getting this size on this side the better is your privacy because imagine If you have two people, you still need okay maybe these two people but if it's 10 people it's thousand people it's 100,000 people the privacy just kind of grows and now an important thing here to know is we came with the example of one fixed domination one E here I put the example if you would go with a some weird number I going to put in there uh 378 E and in the end to later point I will withdraw 3.78 E it's kind of very obvious who's this person is hu everyone else puts one Eve and I'm one person who puts that Eve in and then everybody would know okay the new address that's exactly that address to put it in and you have immediately a link again
so the connection is not really broken and that why you have in these protocols I mean there are some some ways you have a fixed domination so if you look for instance for some privacy pools or tornado cache they have one pool with one Eve another pool with five E and then the other Eve a pool with 10 Eve and that's kind of is it done I mean there are the other things I don't want to get any advances right now. You can also use some UTXO and then you split it up and then you cannot mix it anymore. Again, that's like newer things, but this is the the core thing and actually to say the bigger the people are, you have a fixed domination, the better it is in general. That's kind of the the base for our CK voting app. We're using a mixer, but instead of withdrawing
some Eve, we're going to call a function in here. And therefore um I going to split it into here in into in two sides. So we have kind of the back end side I just call the back end on this side and we have kind of the front end side on the back end side. This is everything has to do with uh solidity and no on this side and getting our solidity verifier contract out because we need that to plug it in. It's kind of this dog you can imagine we we have seen. And then on the other side we have the front end. Normally in the other speedrun challenges we always focus on solidity but here it's a very important part the front end because we need to generate the proof of chain on our machine because this is all secret it's kind of the private we don't want to leave that
out because that's us so we have to touch in this challenge also kind of the front end and generate there the proof so we can later then plug it in and send it to the to to to the verifier contract. Okay, first things first, we need to do the registration. That's kind of the first phase we saw in this in in in this mix of things. So somehow we need to put our commitment somewhere that we can later prove that we are part of this whole group. That's what we're going to do first and therefore we're going to use a Merkel tree. I don't know. I mean Merkel tree in the first place sounds very easy. If you go for me, I mean it's it's very basic. If you go deeper in it, it gets more complicated. And then if you think about other things like I don't know it gets very
complicated. So I I I kind of tried to break it a little bit further down. So I don't know all who of you is familiar with Merkel trees and how they work. Yeah a couple of people. So I just just just for the other ones I just going to summarize it and try to do it quickly before we going to go then to the incremental Merkel trees and how this is different to a normal Merkel tree. Okay. Here on the one side we have a normal normal Merkel tree. we have kind of four values in that. So that's kind of the registration. So you could say okay we have four users reg registered and the first user has a secret one two three four that's kind of the values could be hashed things and this merkele tree has a depth of two now because we have we we we always count from zero
so it's kind of a depth of two so we have three levels 0 one two that's kind of the thing so when we want to have a new party registering to that Merkel tree because we need to somehow track the the the data structure who has registered um what will happen to this normal Merkel tree is it gets blur probably very big much bigger because we need to add another node that would be the five but we cannot plug it in here because we we we're looking at that we're going to create the whole right side of the Merkel tree again so this one then to calculate we have to go calculate we have to increase the depth this is kind of complicated and put everything on chain I mean a normal Merkel tree for instance is good when you when you consider Ethereum block you have an Ethereum block
you're getting all the transactions in and then there is the final state of the block and then you after transaction nothing changes anymore it gets proposed and the whole mercury is calculated but we won't change that that merkele tree anymore but here and and then it can be there and then and then it's good so for so so for fear blocks is it's very nice to have just a merkel tree but um but to add more um to to more things to register our people that doesn't work because we have over and over recalculate the whole Merkel tree and that's kind of very very very very cumbersome and just not the best way to do and that's There are in there are incremental Merkel trees and the difference is that we go in and we say okay we want to have a certain amount of leaves that's all these numbers and we're going to fix the tree already. So it's already
there. So as you can see we have here eight leaves that would le would mean we have place for eight users that could use our protocol kind of and but these leaves here are already empty like before they're already there and what we're going to do we're going to preopulate them all with zeros or whatever we want to have for instance for tornado it's tornado the the word tornado it's hashed and it's put in here and now if you're going to put one uh one new value in here we only have to recalculate the the kind of the frontier is it I'm going to explain in a second what that exactly is. And the the cool thing about this one, you kind of can track the the frontier more or less and put the whole thing on chain and you always know where you at.
So in this incremental Merkel tree here, you can see now we're going to add the five. So we're going to put the five in. We hash it together with the zero and we just go up. And this is kind of the front where I meant. So if we then would put populate this one, we only we do the we do the cell calculation, but the tree itself always stays the same. So it need it needs to kind of um prefix from the beginning. So and I was talking about the I mean that that that that's important thing about the frontier how how it gets calculated so that we can save the store on chain and this is exactly how it happens here. So because we say okay we have um a depth of three in the other one in the mer tree we saw before just go back quickly here we have uh up up to level three. So that's kind of a depth
um has three four levels more or less that's why we starting here and we're having this little array here in in in this corner and now how how it happens. So we're going to add the number one we're going to get the I don't know we're looking at the array and say okay the slot is empty so we can just put it there. So we have the number one stored. This is how when the first user adds it to the adds himself to the Merkel tree or the commitment registers for the application, it's here. So the second one adds the two. Now it comes now now it gets a little bit complicated because we already have in the first position we have the number one. What we're going to do, we're going to use the number one hash it together with the number two. We clear the spot the the slot and
carry it forward to the next one. So we have it stored. Now only with this little array we know exactly how the merkel trick uh tree looks like and where we have to add the next next value to it and so on. Number three, it's very easy because we have the zero. We just put it here. The four, it gets then a little bit more complicated because um we have the the three is kind of um occupied. The fif the the the zero slot is occupied. The first slot is occupied. So what we're going to do, we take the four, we hash it with the three. We need to go to the next slot. The next slot is also full here. So we hash these two together and we're going to move it forward to this one.
And that's then how our frontier like this this kind of this border around would look like. And we know exactly only storing this array how our merkel tree looks like. And you could imagine when we when we would extend that to a depth of 16 or whatever, we would have already I don't know 60 70,000 something in between of leaves in there when we have the fixed depth. But then you just have an array with 16 numbers. So it's pretty cool actually to track the the state of the of the merkelene. That's what we're using for registering the people. This is very gas efficient and it also works very good with CK. That's why we're using this incremental mer tree in there.
Yeah, maybe. Okay. I I'm I'm kind of switching now from the presentation to the code to show you actually how how our codebase is set up and how we work in the different circuits and how we going to implement it in the solidity code. So let's go into cursor. I have already installed everything and the the the thing is I have already put in all the solutions in there. So the code is full full. If you would do the challenge yourself, you you find an empty spots like like you maybe know it from other challenges in there. So what you can see here we have another another package. Is it big enough to see? No. No. It's better.
Okay. I mean that's that's kind of the normal normal scaffold Eve um um setup. The the only thing you can see we have another folder in here that's a circuits here. Here will everything happened with the with the NAR in a little bit. Then we have here the hardhead and then we have the next chairs. Our contract looks pretty easy because I mean probably many of you have just a very counter app voting app. It's just pretty pretty basic go going forward. So if you're going into the voting app we close all this stuff. I mean we have to we have the normal normal uh the voting app as you can see we're using the lean IMT from here so a library is integrated so it's very easy we don't we we don't write this oursel we're going to use that from the from the CKit that's a very nice library out there
we're using that one so then we can initialize more or less the the tree here as a empty empty empty variable in the beginning and um regarding the functions so we have kind of how should I say for for for the voting we doesn't want to vote let vote everyone because otherwise we could just create random addresses let everyone vote and we had kind of a civil attack our voting attack we want to make sure that there are only dedicated people can vote I mean there there are many ways and the easiest one is just have a allow list just send all of my colleagues just send me their addresses when and I add them to the allow list and then know these addresses can only vote on this question for instance. This the thing that's why we have the add voters here and um then here
we have the register contract uh function sorry for that and this is kind of a normal non normal register like like we do and the thing is um as you can see we have embedded here the tree and we're going to inserting the comments that get in from the from the register function and this nice library tracks it. So you're going to add that and everything is kind of calculated already and they will do the the frontier calculation for you. So it's in there and we keeping with that track of the whole registration and which are the users who have registered already and of course then we're going to emit a leave and we're going to um and and we also going to set um the commitments um to true if it's the case and and they and they have registered. That's kind of the thing.
I mean that's pretty straightforward to do that after you have understood how kind of this incremental thing works because I think it's it's pretty important to build the whole thing. So when we have do that kind of the registration effect now comes the fun now we going to we we we going to jump into no and that's kind of the circuits. So okay maybe maybe in the beginning I mean there a couple of words I mean I I just took them out. I mean the one thing is kind of an zero knowledge proof. Probably everyone of knows of you knows already what it is. It's kind of you have kind of a secret and you are kind you are the prover with the secret or whatever you know and you want to prove to another person that's kind of the verifier that you know the secret without
without revealing the secret. That's exactly what we need to do there. So we know we we we have the secret we have the commitment and we we have the secret we don't want to reveal it but we have to make somehow sure that the verifier believes that one commitment in the whole mer tree belongs to us that's kind of the most important thing and that's exactly where why we use a zero knowledge proof for that and then we have I just pointed out four words they always appear somewhere and they're a little bit confusing to me because I I didn't know that's why I just I mean a circuit what what is a circuit it always sounds so fancy in the end it's just a program you right kind of in Noah and that program you use then to create a verify contract or whatever. It's kind of the the the the set of
rules you define something with a proof needs to pass. Then we have the constraints or the assert function that's actually in the contract. It's like um assert just a normal assert function if everybody knows you in the in the in the circuit itself in the program. Then we have the witness and the witness is kind of the you could have many inputs in going into a proof for instance and using these proofs the these inputs to create the witness. This kind of is a secret to you but with this witness you can you can kind of convince the the back end to create the proof that you know these inputs and and out of that comes the proof. So you can imagine you're going to build the the circuit then you're going to get the the witness out of it and with the witness then you can have
the proof out of that but we will we're going to see it in a in in a in a little bit. Yeah. And that's kind of the most important the terms I would say for general understanding what's going on. Okay. And the next thing I want to talk quickly about is no. I mean I have mentioned it. I don't know how how many of you know it already. that's kind of a a a programming language kind of adopted from Rust. So it has a Rust base more or less. It's developed by the AdSec labs. They're also coming up with this little privacy layer and so on. And and and this one you can use to create um all kind of um circuits yourself and write them in a very easy way because it's just a rust base and not something other fancy. And with that there comes Nago. I don't know that's kind of the the
cargo like the the CLI tool for Rust but just for na for for Noah that's why it's called Nago and we have always this Barrettenburg I don't know in the beginning I was a little bit confused what is this Barretenburg is the Barrettenburg more or less is the kind of the back end who who runs all the difficulties in the back end to create the proof and so on that's what that's what it does so you can imagine the the Noah writes you the circuits and you're going to pass in the circuits with the inputs to the Bretonbeck they do some magic fancy mathematic stuff out and and the proof comes out. Yeah. And maybe because I mentioned it, let's jump into into how a NR um circuit looks like.
So I have a set it up and this is kind of the it's a little bit difficult. We will go through step by step to each of the different things the lines why they're in there. But it's more or less like you can see it looks like Rust, doesn't it? It's just like the normal Rust thing. And um what is the important thing here when you when you're creating these circuits, you're having two different types of inputs. So you have public inputs and you have private inputs. The public inputs are visible to everyone. So everyone sees what are the public inputs and you even need to pass that in into the verifier to prove the proof. But there are also these private inputs and these are kind of the secrets or the things you doesn't want to let know anyone. And as you can see here or it has no
color really here all these pop these pop words means it's it's it's public and if they have no like these two here they are kind of private and here comes already the third the the first thing I kind of want to talk to is kind of our secret because we have the secret together. So we have an um hashed of two values. It's one time the nullifier and it's the secret to make it a little bit because we want to make sure that um only one commitment can go onto the onto the um onto the Merkel tree. That's why we're going to track the nullifier as well and we're going to store this nullifier also then in our contract so that no not many people can just over and over and over again vote in the whole thing or create or in there. That's why we're going to we're going to
store it. And let me go back to the presentation a little bit. So that's kind of maybe the commitment scheme we having. I mean this is pretty that's pretty straightforward. We have the secret, we have the nullify, we hash it. That's a commitment and that ends up in here. And like I said before, the secret and the nullify shouldn't leave shouldn't leave your laptop. Another thing with the nullifier is we also have to kind of we have the nullifier hash. So we don't use the nullifier itself. We want to use the nullifier hash. And maybe it gets a little bit too complicated. I hope I don't don't confuse you all. Maybe I don't know you can give me a quick sign. Is it too fast? Is it too slow? Is it all good? Okay, cool. Because the important thing and that took me some time to wrap my head around and hopefully I get it right again is
um you need to track the nullifier somehow like like I said because you don't you want to prevent the double spending stuff but you also don't want to have because it's a secret you don't want to have it out there. So you going to use the nullifier hash and inside the circuit what you want to check you want to always check against the nullifier hash so that a user because you can you can you can imagine you're using your nullifier you're going into the proof um a valid commitment is is kind of computed and you're passing the proof and everything in there. But if you don't check against the nullifier, you can do the same again, but you just choose a completely random nullifier that is not there and the proof just passes. That's why you want to compute inside the um inside the circuit your nullifier and compare it with the public nullifier hash that comes into
here. Does it make sense? Probably was a little bit complicated. You can ask me again afterwards. Does it make sense? Okay, let me go in here. So we have a we we have a nullifier hash in here. This is public because you have this in the in the in the solidity contract to track which nullifier gots already um um voted on. So you want to you want to you want to track that. But the nullifier is kind of a secret. You want to you want to have it secret away. That's why here here you see it's private this one. So what we're going to do the nullifier um the the nullifier but you can see kind of the nullifier hashes you can you can kind of you can kind of see because they are there
they're public but you want to make sure that no one um can many more times call the same function. That's why you're going to use the nullifier. You hash it in here so the the person needs to pass in the nullifier but it's private and you cannot guess you cannot guess a nullifier. And then you compare the computed nullifier hash with the nullifier hash you have passed in. Okay, good. Yeah, this is one this is this is what one thing to keep in mind in here. And I mean it's also pretty easy to calculate a hash. Maybe I have to mention that there are there very nice libraries in here you can just use and you can use from from the Poseidon. This is a hash function. This is
kind of catch for Ethereum. you see many times, but this is kind of made for for for CK. So, it's just more efficient and better to use. And you can have these libraries and then you're calling in these these function more or less. Yeah, that's the one thing you have to keep in mind. Let me let me jump this a little bit. I mean, here you can um because you're going to pass in if you go back in here in the function, we're going to pass in the siblings. The siblings are kind of the nodes in the Merkel tree. So you always have the siblings but these these siblings need to be private because as soon these gets public you know exactly where you are in the Merkel tree and you don't want to give up your position because then everybody knows who puts the
commitment in because you signed the transaction before put the commitment somewhere but you don't want to you want to do that. I have to keep the siblings always private and to calculate the depth it's like we have seen in the front you can and that you can you can kind of um count you take out all the the zeros because it's a fixed array we have here a fixed array of 16 you're taking out the zeros and then you're having the the the the length of the array and then you know exactly the depth of the of the mer tree and this one I going to do here I I let compute there has some some advantages and then we pass it later in if you go here into the computed root thing that's what we kind of do in here and then there is a little bit weird thing in here because um we need
to let the the function here this is the burner burner Merkel um root function that comes from the from a library I import similar to the solidity um um library but this is for made for Noah and um here we need to let it know which exact index we are at. And what we if you go up to the inputs we see we have um the index is a field. A field is kind of a integer in in in that way. It's just the the very basic um unit you will put in and whatever you don't know you put a field in first kind of. So that's a UN 256 in in in solidity more or less. And there there's a very very nice trick and that you cast down the number into into kind of bits. So you break it so so you break it down.
And how this works also here made um a little a little explanation to that. So when we want to get the the index in binary what we have we have the index. So let's see. Okay we have number four. That's kind of our fifth value you put in there. It's a it's a number four because we start counting with a zero. That's a zero one 0 0. Then what we do, we're going to reverse the order and we pat everything um to reach the length of 16 because we predefined that that's kind of our length of the our our predefined depth of our Merkel tree in that sense. And then when we want compute now the root in here we're getting in the siblings from here because we have zero and zero and 1 2 3 4 when we go here we have we be putting the five. So one sibling is zero. This is the next
one. The other zero and from this the sibling is 1 2 3 4. And then we know the depth and we have the index we just we just recently calculated. And then we have two rules to follow. If it's a zero, we had the the the hash looks like this. We have it on the left side, the current what comes in and the sibling to the right side. And if it's a one, we we we have it the opposite around. This is exactly what they say here. So when we go for the zero, we're going to go over here, we say the current goes to the left, goes to the left, and the sibling here. So we know we need to hash these because when you're hashing two values, the order matters a lot. So if you change that, it's a completely different hash. So you always need to make sure you really hash in the same order.
Yes, that will be kind of do. And when we go now back here to the um to to to our circuit, you see in here that we kind of then asserting I just I just going to sum it up a little bit because that will be take now probably a little bit longer. Um here the the most important asset we do in here is the computed route um equals the other route that we're going to do. We're going to because we're going to pass in the root what is the latest route stored in our in in our solidity contract and compare it to the to the normal route. If if this is correct then the mer tree is good and the merke proof is good and then we're going to let it let it go. Otherwise we just reject it because it's just [ __ ] what the person said because that doesn't
can calculate the tree all all the way up because they don't have the right things. This is kind of here it works. And then there is maybe a um an important thing to mention. As you can see when we go up here I see the vote and it's kind of a um um the the the question if it's a true or a no you want to vote on. So it's kind of a binary question with a bool we chose and and in here we need to have it in here because if the and bind it to the proof because if we wouldn't do that we wouldn't have it in there then someone can just jump on the blockchain on just frontr run us and just change the vote that would be possible but the thing is since we bind the answer of the vote what we want to vote into the in into the proof nobody afterwards can change it.
Yes, that's kind of the the circuit how how it looked like. Maybe if somebody has a question afterwards um we can discuss it or you can come to me. I'm I'm the whole day here and we can just discuss. I'm also happy to learn more about the these things. But it's definitely very cool. And now we coming to the coolest part, what I really enjoyed the most and that is generating the solidity verifier contract because this is just magic for me and that's pretty cool and I think that makes it very easy to use these things because you saw it's just this little rust written program you have in the end you're getting a verifier contract you deploy and you can just integrate into your in into your smart contract. Maybe before we doing this I just wanted to give you a a a quick heads up. So, um, if you want to do it in the CLI,
we can we going to CD to our circuits folder and and then we just going to run a nago check. Ah, maybe we need to do a nago compile before because I deleted the folder. Okay, now we have it. Okay, what nago compiles does it compiles the whole thing down and we have a JSON and um and all about this the circuit and you can use it then for the for for for the front end to call all the libraries in there. This is this is another thing but here when you run the nago check here you see all the um the inputs you have to make in order to create your witness and then you prove in the end and this is exactly kind of as you as you saw before our in public and
private inputs in here and when you fill these in I mean I don't don't do this now because it's just getting complicated with the siblings because you have to create all the different hashes and you need to to to add it to it and you need to pre-populate it up front and so on. But this is kind of what you would do in a CLI form. So to in order to try out the circuit how it works and then you can run against that you can create a witness and then out of it in the CLI you can create a proof and then you have a just a big a big proof of of a lot of numbers and you could use it to do something with it kind of that that would be in the CLI but the cool thing is we don't have to do that in the CLI because we're going to do this in a
second in the front end where you're just calling the the the right libraries from CKit for that. So what we would do now in order to create the um the solidity contract is actually just these two commands you're going to run. So here we're going to start we're going to use the the Barrenburg and um we we first kind of need to create a verifier key is it called and this is kind of the base and then out of that they going to create the the the solidity contract. But let's run this in a second.
Hopefully this works now. Yes. And as you can see here over there, we we we we have the verifier key. That's it. And now let's go call Barrenburg again. And we call the function write solidity uh verifier and we're going to pass in the verifier key and where it should put the verify contract. And as you can see, we have now a full verify contract very easily created in here. It's a very long one. And this just gets created automatically in there. And there's only one important thing in here. That's the verify function. So we have a nice interface here. But if you look at the verify function, this is exactly what we need to call. And we can just use it as a condition in our function. We want to call that in a second.
and one yeah let's maybe we we have the verify contract now let's jump into our contract that's the voting dossole contract so the register function I won't touch further on and then we here we have the voting function this kind of the voting function and here you can see this line here is where we call the verify contract and we're going to pass in the proof and we're going to pass in the public inputs. So the public input was everything that has a pop keyword before that is that is open and the proof is here and before that you can do that you have to of course define getting your public inputs and in the right format this is always the the main or that could be some some issues how you get it from the front end then into the solid
cont that all the formats formatting is right I was struggling there a bit with it one very thing one very important thing and I forgot it first is we want to check for the root. So in a solidity context whenever we going to add with the insert function up here insert the root changes because it recalculates above the root and it gets stored in the contract. And what we're going to do the the the person creating the proof is calculating the proof kind of offchain and um and then asserts in inside the proof also has to like like we saw we we calculate the roof and we we put the proof in. But we have we need to make a connection to the proof in the smart contract because if you wouldn't do that I could just come up with a complete random tree I
created offchain and just pass it in. It will work but it's completely disconnected from the tree. So everyone my my my whole contract wouldn't work anymore and everyone can just do whatever they want and just vote as as they want. And that's a and that and that's a very important thing to to keep in mind. Yeah, I think I think that's it. That's kind of the most important thing in here. I mean you're going to check here as well. You're going to set the the the nullifier hash so that a person cannot vote again. So it's only one time voting. So you want you want to track that. And then in the end you just just count count the votes on on these sites.
So are there maybe I just going to make I looking very confused faces. So I just want to give just wanna are there any questions the mid time in the meantime? Anything that's unclear, confusing test. Yeah. So, can I get um clarification on the nullifier hash? So, what's to stop a voter from using a separate nullifier?
Sorry again. So what's so so you have the public nullifier hash, right? And then you have the the voter has a private nullifier. So what's to stop uh a voter from using another private nullifier? I mean every person who is registered has with the commitment put on the Merkel tree and the registration consists of a secret and a nullifier. So they unique. So as a person I'm register having only one nullifier. All the other nullifiers won't work because I cannot come up with a random nullifier because I don't have the commitment on the chain on the chain in the Merkel tree. Does it make sense? Yes. I guess I can also later then when when you want come to me we can just go through and I can show you the flow inside if you want.
Okay. So what we Oh, another question. Yes. So my question will be initially testing. Yeah. I was trying to speak loud so you can everyone can hear me. Um yes. So my question will be you said initially you need to send a list of address to be what list in order to be included in the marker tree. Uh can be compute to figure out if they have common root. My question if I send my address to that person to be white list they already know who I am right so in that case you know realistic um voting scenario I don't want anyone know who I am but I still want to vote do you know what I mean like I don't want like to give someone at least there's a single authority in this case
they know I've been whitelist so how prevent being reveal my identity to even that single authority to be included in the first place if that makes sense to you. Um I'm not sure if I understood it correctly. You mean that um with the allow what is kind of the benefit of the allow list? Yeah. Like I Exactly. Yeah. Can you like do you think allow list in that sense? I mean you want to kind of create the voters. So imagine um a a country election for instance. You only want to have your citizens vote. you don't want to have anyone else vote because yeah I'm I'm from Germany so only German citizens are allowed to vote but if you're from the US you're not allowed if you don't hold a German pass so you kind of restrict the voters in this sense so you could have kind
of different requirements for the voters coming in and the easiest one is you have an owner of the contract and that adds the addresses to the allow list and these persons who are in the allow list can register themselves with registering putting their commit commitments on the chain on in the Miracle tree. So does that mean in the case of um citizenship verification the whole market trail including the whole German population like in term like on the whole tree pre pre-white list in that sense yes they're pre-whit listed I mean that goes a little bit further away that's kind of use case I thought also about it might be happen that you kind of get sending to each of every German you would send a letter and there is a private number in it and you use that private
number maybe as the registration to the contract kind of but I mean there are there are different ways I mean I can go later you the different things you could use to have people registered you can look for balances of ESC20 token or NFTTS or whatever you want in there but the thing is as soon as you open it up to everyone everyone can just vote creating addresses you don't get the effect anymore because you can just have civil attack the whole thing so you need to have some restriction correct for for the application yeah The reason I'm confused is like during the first step of verification preet listing process um how can you award to be not identified that is the kind of the confusion I have so I'm more about the single point of failure during their
initial verification of setting up their mercury tree if that maybe I can show you let me See, I can walk you through the um if it loads. Okay, maybe. Okay, sorry for the maybe. Okay, I see I don't have so much time anymore. Um the thing is um like I said the the I mean there there's one time you're going to create the
commitment offchain. That's just a thing. I just going to touch it briefly. As you see the secret and nullifier are important because anyone who has these two can prove that they are on on the Merkel tree and then your whole privacy is gone. So you keep that on your on your device and that's it and you're going to generate this offchain and you only put then the hash version of these two onchain that's kind of the commit commit scheme we having and then um there is a proof generation so the the the generating commitment is for registration and then when you want to vote you need to generate a proof that you're on the Merkel tree and you're and you're there and I just going to show quickly in the in the nextjs to the component Oh no, where is it?
The gener the generate proof. I mean what we want to do we as you can see maybe the first thing we we need to create the tree offchain. So we need what we need to to do on the um for the for for the proof generation. We are just on our laptop and we need to reconstruct now for ourselves because we know where we are and we have the inputs. we need to reconstruct the whole tree and that's what we're going to do also with a with a lean IMT like a lean incremental merry library in here and then we're going to form all the inputs and we're going to um because we have them the the the private ones and then we're going to let no use no we're going to um we're going to generate the witness and then we're going to pass
it in into the Brenenburg back end similar like I said before and then we're going to generate the proof and that proof then we're going to pass into the voting function the functionality that's kind of in a in a nutshell one one important point I want to mention and sorry that I have to to to cut that short right now but it's just because the time is the separation of the addresses because you need another address in order to call the vote function when you have the proof because if you would use the exact same address again what is the whole point I create any proof then we could just leave all this the the SEO knowledge proof away. So, we need a way to have a a fresh address that is not linked to your address because even like like I said in the beginning,
if you just have a new an address, there's your funds. You're going to generate a new address and you're going to set funds to it. Everyone who got can do a little bit of chain analysis would know this account also belongs to you. So, you really need a complete fresh address to vote. And therefore, I mean, if you're using local chain and yarn and the yarn chain, just it's it's very simple. You're just going to spin it up and you just in a deploy strip, you say, "Okay, fund this account." But how does this work in a in a real world application? And the thing is that you're just going to that you use smart um smart account abstraction with a verifying pay master. So you have a pay master somewhere, you're going to spin up completely fresh a fresh account and that gets then paid with a verify pay master that belongs to the protocol
for instance. then you have no link to that and you don't even need to know this account who voted then for you because you will never use it again. Just one time created pay the transaction fee and then it goes in. This is also a very important point when you think about considering building these application that you always for the new address you need some way to sponsor the the transaction because you want to break this link between the um sender and the receiver address. Maybe a a little things I just want to elaborate on are kind of the final final considerations I had with the app. I mean I haven't touched it. The thing is you want to have some kind of a root history array because imagine many people interacting with your with with your
chain and they're going to add new commitments. Whenever they add a new commitment or get a register there is a new mer there's a new merkel route. But when you're only tracking this one, it happens many times. You generate the the proof offchain. You're going to send it to the mercury. But in the meantime, someone else already added the commitment to the mercury. That means a new merkel root would have been created. And so the comparison fails even if you have the secret everything well done because just the merk root changed. And that's why a good behavior is to have an array. So you're kind of having different the the the all that you're collecting kind of I don't know it depends how much you want 10 or whatever kind of these um the these different roots in an array and you're always letting drop out the oldest one so to make to to to to make it work and
another thing what is also very important because you need to build the whole Merkel tree yourself in the front end you need very good indexing for that because you need to look to all the um to to to to keep the thing running. Um then as I said really the privacy depends on the amount of people. So you're always making sure that you're having as many people using that when you want to achieve huge privacy. Of course it doesn't matter if you have a voting app like us and we're using it in the build guild and we have 50 people and 15 people and we just let them vote. We know that's all all the voters. But the more precarious it is or the more you want to really get the privacy on some withdrawal or or whatever then the the the the bigger amount is good. And
of course the gas sponsorship is another point I would like to mention that what I said with the with the account abstraction. There's also a thing you need to wrap around how you going to do that in order really to break break cleanly the the separation. Then another big point I put it here that was kind of what we're touching on is kind of the registration strategy because it's also a hassle. So imagine you want to I want to use now a hidden vote um voting application with with you all and I only have the the the the allow list where I add the addresses. What I need to do now first I need to collect each of you one address and then add it to the allow list and then you can vote but this is a very cumbersome process isn't it? So I mean there are there are other ways
we could have given out to all voters some NFTs already. Then I check for the NFTs and that's could be the allow list or whatever you come up with that that's another idea you can do and oh it turned and another limitation to the whole application is and I mean Vitalik has written a blog post about it and there's a protocol out there that's kind of the Macy protocol Macy framework or infrastructure and what it exactly means because in this way we constructed this challenge you can prove pro in the end to anyone what I have voted on because I have the proof. So if Austin asked me hey Philip I give you 10 Eve when you tell me what you have voted on I would then give him the proof and say yeah I have voted yes where are my 10 eaves kind of and the thing is
with the Macy with the Macy framework it kind of um protects you because it's another third party integrated in the whole thing I mean that also leads to more complicated and to more trusted setup kind of but this will will deal with the whole telly process and in that sense because um because CK you cannot prove what you have voted on. But on the other side it has also a downside because um they cannot fake your vote but they can withhold your vote then in the end. So you kind of um you kind of introduce some censorship resistant again in the whole thing. What you need or want.
So I think the real voting is not invented yet. There's there's definitely plenty room to um play around with and try out CK because I think it has so many possibilities. And if you're thinking of I just used now the vote function in there but actually that's kind of the basis for everything in the voting and you can just put whatever you want behind the behind behind the dog what people can do. Yeah, I think that's it. I mean I that's it more or less what we have what we have touched we have touched the commitments kind of how you how how using that to bring some secrets on the blockchain then um how we going to build the no and the circuits a little bit I didn't go too much deep into that I thought they had more time but there wasn't the
case merries why they're important and what what is the difference between a normal one and the lean incremental merkel tree and then the coolest thing how we getting the solidity verifiers out of it and we just can can use it for our smart contract. Yeah. And that's it from my side. I hope it was not too confusing. I think we still have a couple of minutes for any questions. If there are otherwise like I said is I'm here the whole day. So if there is anyone wanted to discuss with me something, I'm happy to do that because I really like the topic and I think has so much potential. It's pretty cool. And yeah, that's it. And I don't and don't forget our speedrunners event. Here's here's the Where's the Here's the Kuco to it. Thank you.
Oh, doesn't work. Okay, then then sorry. Oh, I had Oh, there's a weird address in there. Can it work? It's a Eve address. Ah, now there it is. I don't even know which address that was was now it should work. Sorry for that. Does it work? U one question. Um I understand Solidity smart contracts have a maximum size right and the contracts that Noir generates are pretty big. So I was wondering if um this is something that limits the use cases of of CK technology and also if there's a way to realize beforehand if if
your contract is going to be too big or just going to realize when you compile it. Yeah, I have heard that in the beginning that it's very big and I I mean I scrolled through the contract and you saw how long it is to verify a contract but um we were deploying also we we built some voting app with the contract. It's a little bit um bigger and but the circuit stay the same. It was no issue and not so expensive to to put that on base. I haven't done it with mainet that probably could be a different story but on base it was no issue deploy the smart contract. Thank you. Uh, hi Philip. I only want to add something to his question. H maybe you can check gracias. Maybe you can check semaphore. Semaphore allows you to hide the identity
of the people inside the voting group and you can combine with this uh example. Semaphur. I can share you the link. And what semaphro does it hashes the identity. You can use a sign a a firm from your wallet. You can use a secret word. You can use a hash that you hold an specific NFT or a specific token and then use that. The tricky part is how you design the mechanism to detect what kind of variables are you using into the entry group. But yeah, it's from PSC. Thank you. Hello. Well, thank you very much for your talk first of all. Then I don't know if my question
is uh kind of basic, but can you simply track I don't know if you can see on real time how the vote in advance is if you see there's a registration of course of a movement if an address votes for something and then you see like the pool moving forward if you can somehow in that like link. Is it okay speak a little bit louder? Yeah, sorry. So, isn't there a way in which I could link if I see a transaction registering from an address that it's voting and then I see like the boat pool somehow moving, isn't there like couldn't I simply like track or trace back to which is the address that actually voted? Yeah, that's a very good point actually. And yeah, when we did we we we built a kind of a more productive app. It's kind of hidden vote. we we we started to build out and there we integrated a registration period. So before you
can vote I mean I haven't even shown now the app um you have a registration period because exactly what you said you want to you don't want to allow that someone registers and immediately vote because this causality you can just link and then you know exactly that person might be exactly that voter. Yeah, that's why we if you want to host a vote for instance, you can say, "Okay, the voting period, the registration period is open for one week, everyone registers and then you you you open it up afterwards and then let the people vote and then you don't have this issue anymore because you don't know who then voted and which could relate to which address.
Okay, thank you very much. [Applause] [Music]