New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Loading player…

ZK Verifiers Exposed: Lessons from Real Bugs and Fixes | Tejaswa Rastogi, ZKsync | ETHTaipei 2025

ETHTaipeiThu, Oct 9, 2025, 12:00 AM

ZK Verifiers Exposed: Lessons from Real Bugs and Fixes | Tejaswa Rastogi, ZKsync | ETHTaipei 2025

Transcript

Hello hello hello. How you guys doing? Are you enjoying the sessions? Uh so uh in my session I would love to talk about ZK and security. Uh so we'll keep this session a little more interactive.

So I just want to know uh how many of you guys are directly or indirectly involved into security or have previously or currently working with a proving system or something like that? Anyone? That's great. There are some people it's good to know. All right.

Uh so the ZK verifiers um the security of ZK verifiers uh is a very less explored topic uh right now uh because uh there are different different implementations like uh protocols have their own version of implementations which I think are secure uh based on understanding research papers or uh having their own uh understanding of the design or the construct, right? uh but in that case uh there are like these implementations are tend to suppose uh different bugs different implementation bugs right so these bugs uh which I'm going to explain are uh coming from my practical experience of working with different uh zero knowledge protocols and uh auditing or reviewing uh their verifiers and the proving systems right so in the past I worked and audited u the lineas verifier and uh right now I'm working in uh uh at zqing so I'm uh getting the chance to work uh with the zasync proving system and the verifiers right cool uh a quick introduction uh who am I uh uh pretty weird name uh it's called as teshwar soi uh please do not pronounce it, you can just call me Razer. It's uh super simple and uh right. Uh who am I? I'm a security engineer and researcher uh at Zika Sync.

Um my roles and responsibilities uh involve working around anything and everything uh in security like uh conducting auditing connect cond connecting audits uh for the code base um researching about new attack vectors or the current existing schemes or new constructs uh helping the team uh build some tools or something like that right uh yeah uh love cats you will see Okay. Uh so we'll explore uh practical bugs or the real real life bugs uh that may exist uh while constructing these kind of verifiers. Right? So I tried to generalize uh these bugs around snots stocks. These could be anywhere like these are not limited to just snugs or just stocks.

uh these are pretty generalized and uh totally depends upon how you take these and implement them right so let's talk about the starting point right for for the proving system like uh plon right you must have heard about the plonish uh proving system right uh so they require something called as trusted setup so what is a trusted setup Um basically uh those who don't know it's just like a step where you find a random random element in a field right you raise some powers of it right uh of uh here you can see a toao right it's called a toao uh it's a random field element uh that you find uh of course belonging to the field we raise some powers of it right to 1 2 3 now you might ask like Why we are raising the powers and how many powers we supposed to raise right that totally depends upon the polinomial uh we want to work with or the business we want to satisfy with the polinomial right so every polinomial may require a different uh set of the powers of toao right uh here you can see like g raised to toao so g is nothing like just a generator right So we are encoding the powers of toao with the generator. Right? Now um toao to to right. So this depends upon the polinomial as you as you talked about uh different polinomial different requirement for the powers of right and now what is the need of u uh these powers of right. So when a prover want to commit to a polinomial right so how how you how you commit uh to a polinomial uh let's let's assume like this five of x uh is a polinomial uh which can be represented as in the form of coefficients uh the summation of the product of coefficients and the variable raised to exponents right uh that's basic algebra right uh basic maths uh that's how a polinomial looks So if a prover want to commit to this polinomial uh he or she uh just want to raise uh or uh just want to evaluate the polinomial at toao right now the basic requirement uh of this string we call it srs structure reference string uh the powers of toao the basic requirement or the atmost requirement of uh the powers of toao that after raising the powers or after finding the powers you're supposed to delete toao you're supposed to like waste or discard toao that's why it's called as toxic waste why uh we'll come uh to that point right so uh let's consider first that we have discarded toao right so if we have to commit uh to this polinomial Uh again back to the basic maths uh how you how you will find uh g raised to phi of toao uh it's simple uh now we don't have to right but we do have the powers of toao right so what we'll do we'll again do the same mass like because five of 0 to 1 uh to of 0 5 1 to 1 52 to 2 depends upon the degree of polinomial Let's say you have a polinomial of degree 2, right?

So just feed in all the values here. 50 to0 we have we calculated 51 to1 we raised the power of toao to1 we have similarly other terms we have. So without having the knowledge of toao we can still compute uh this term which is exactly what we needed right. So we can say we can commit to a polinomial without even knowing what is toao. Now imagine if we do not discard TOAO and the attacker gets the excess of this TOAO right what he or she can do um without even knowing the polinomial or for any different polinomial right uh okay now let's try to let's try to go back like okay we calculated these strings right so let's say in the But we knew that our polinomial was of degree 2.

So we calculated the powers raised to two to raised to two. And after that we discarded toao. Why? Because we can commit to the polinomial by just having the knowledge of toao right powers of toao. Right?

Now imagine uh the attacker gets access to TOAO right now does he or she need uh the powers of TOAO? Definitely not. What if it's a degree three polinomial, right? The attacker knows to he can he or she can directly go ahead and raise the power of tow three. Right?

Before that was possible. No. Right? Because nobody knows to. So you cannot raise the power of tow raised to three because if your polinomial changes if you try to forge the proofs if you try to trick the system you cannot because you don't have the knowledge of toao.

So from all this conversation um we can conclude that toao is the most important thing and once you are done with the powers of toao you're supposed to delete it discard it now imagine the toao the powers of toao was calculated or computed was uh by one single entity right that creates like issues of trust right uh because you're dependent on a single entity uh to be responsible of discarding the towel. It does not there are chance of uh the tow getting leaked or being used for malicious activities. So what's the solution? What if these powers of toao are being calculated in an MPC fashion? Now what is MPC?

Multi-party computation, right? Like five of us are calculating the powers of toao, right? So with that even if one of of the five people uh remains uh like good like a good actor you cannot cheat the system right because you need all of the five people going rogue to cheat or trick the system right so with that what we concluded single party SRS no what we want MPC See? Good. Right.

Cool. Uh the another cool thing is static calls, right? We all know about static calls, right? Pretty basic thing uh in the EVM structure, right? So when we write uh verifiers, right?

Uh we usually write them uh in ule. Why ule? uh to save uh computation cost because we know salty keeps or maintains many housekeeping of course right like many security checks are placed uh in by default right so if you are 100% sure what you are doing you can just write it in ule and place the required checks uh based on your need right so most of the verifiers or all the verifiers are usually written in ule um to save gas cost or competition cost. So a simple uh static call looks like this. Uh the first parameter is gas, the address and the argument and the rhythm of this, right?

Uh simple structure. Uh what is eight? Um 8 is the address of the pre-ompile. Uh easy pairing, right? Uh everyone knows about the pre-ompiles, right?

We have a certain set of pre-ompiles. efficient computation. So address 8 is a pre-ompile called as EC pairing right. Oh um so for the EC pairing we require certain kind of parameters right to pass in uh a simple uh structure of that EC pairing computation looks like this right uh storing all the values uh at a specific offsets in the memory and then calling the stat static call with all the memory offsets the complete region of memory and uh just wanted to check the result, right? Okay, going back.

Uh, yes. If I do my static call like this, what is the issue? Can anyone uh guess? If I just want to keep my static call like this, you just saw the next slide. You might have uh guessed it already.

Um basically static static call uh provides a return status right that whether the call was successful or not right if we are not checking the return status of static call it may happen the static call was fail right but since you're progressing in your code uh and you're not checking the return status it can happen that the result of the stat static call was false or invalid or failure or anything but you still consider this as valid right and moved ahead in your code right imagine the result of easy pairing right imagine the most important part of the verifier EC pairing failed right but it still proceeded because you're not checking the real status okay so what we can do now any guesses what we have to do to improve this come on now you can say that Of course, check the return status. Right. So, now we can check the return status. And now it's fine, right? Any guesses?

Is it fine now? We did what you said. Um, no. Basically because we are just checking the return status of the call, right? The return status can be true but the pairing can fail.

Right? What we are checking here is the status of the call whether the call was successful or not. Are we checking the result of the pairing? No. So even if we are checking the return status of the static call the pairing is still failing.

So what we have to do we have to check the yielded value from the pairing right. So the first thing we do we will check whether the call was successful. If yes, even even if the call was successful, now we have to check what is the yield result of the pairing because the pairing could have resulted false. Right? The call was successful.

The pair the pairing result could have been false as well. Right? Now we have to check both the status yields true. Right? Now we can consider that uh the static call are working fine or the implementation is fine.

Now imagine you're making a verifier like this and you miss these checks, right? Right. What an attacker can do, an attacker can pass any upgrade data or the final pairings, right? For the final uh opening proofs, right? Any garbage data, right?

It will pass because you have not included a check, right? So what they have concluded from this thing ignoring return status bad thing validating return status good thing we are becoming good hackers right moving on uh to some rain checks right so if you have worked with a verifier or proving system right the transcript or the proof elements contain contain many components right many elements uh based on the construct right based on the proving system uh which are needed right consider some of the examples proof L at za proof R at za these are the evaluations at uh a point za right now uh uh how many of you know about these pre-ompile EC model EC add right we know we don't know come on so easy add and EC mull are pre-ompiles given to us for efficient addition and multiplication on elliptic curve right these are pre-ompiles so easy like as we know requires three points uh the first the three things first the uh the point on the curve, the x and y coordinates and the scalar uh with which you want to uh uh point addition which you want to do the point addition with the point u or scala multiplication with the point right like 1 comma 2 like generate a point raised to two right so it will do the easy multiplication now the problem uh with easy mull is that yes it checks the points are on the curve and they are in the field but uh for the scaler it does not check. So what you can do so this is the curve that it follows uh the ECL y² uh xq + 3 uh alt vn 128 the curve it follows uh this is the field uh with p uh uh denoted by p. So now imagine if I do proof L at ZA as proof L at ZA plus R mod uh R mod is the like u field uh a length of field right so what an attacker can leverage with this knowledge or this construct since you're not checking or not restricting the elements to be in the field attacker can like take the advantage and overflow the element Right? Out of the field, it will still be valid because S + R mod is still S.

Right? Like if you do 5 + 7 mod 7, it will be seven. Seven. Basic maths. Five, right?

Uh the same uh same same example. You can overflow these elements, take them out of the field since you're not checking or not the implementation is checking an attacker can take advantage and provide the same value in a different format. Right? And if you if you don't have enough checks, there is a high chance two things can happen. Either your implementation will not understand what's going on and will simply revert or it may happen that that an attacker may find a way to use the same proof in two different ways same value in two different two different ways and try to forge the proof right uh so pretty basic thing but uh it can do uh a lot of damage same goes for public inputs uh they're not they are supposed to be constrained in the field Right.

Uh another thing um same uh these are also proof elements uh proof L com X proof LC com like some of the proof elements right so for these elements at some point in the uh verifier design you're supposed to use EC add these are these values are supposed to go through EC add computation so now we know that like okay so in the previous slide I told you okay EC ad does the checks right itself so why do we need to add more checks right you're you're absolutely right uh yes it does but uh what if these computations are happening like after a lot of competition in the verifier right you're still wasting gas in the computation you could have implemented checks in the beginning and just rewarded right so uh not a like you can say a bug or something but some checks that you can add. So what we learned from this uh if you have no field you will get a goal right and if you have field checks you're a good person good boy like Ronaldo. No cool uh back to bats. Uh hope you guys don't uh don't hate maths. So this is the bug uh I found uh in line implementation uh lranch polinomial evaluation in za.

Okay. So in the implementation like I will not try to explain you the whole very fine meth methodology. We'll just stick to basic maths right. So just uh believe on my words right. So in the implementations at some point uh after doing computation we have to find some lagranch evaluations right at some point za right.

So this is an efficient formula like if you can uh uh like I will just read it like alli za uh is equal to omega omega to i / n * z to n minus one / za minus omega to i. So right like a long formula just efficient form to calculate the polinomial evaluation. Now what is omega? Uh oh. Okay.

Uh what is omega? Like root of unity. So what is root of unity? It just it's just nothing but a simple term like if you raise that term uh to a power it will yield one. So if it's the fourth root of unity omega raised to 4 will be one.

If it's fifth root immunity omega to 5 = to 1. So imagine if za za is a challenge right? If za comes out to be a root of root at unity right. So what will be the value 1 1 - 1 0 the whole term will be zero. So in that case what you will calculate will be a different or a wrong uh value uh as compared to which was supposed to be right.

So you're not uh and one more thing uh the research paper the plong paper tells you to calculate a lranch evaluation like this but you're not supposed to do it. So now you have two options. If the value of za comes out to be root of unity, you're supposed to make a check to revert it because you cannot proceed with that. Your verifier cannot deal uh the vanishing polinomial at zero. That's one case.

Second thing, just change the formula. Do it in a like primary fashion way like like we used to do in the in the like algebra classes, right? So this is the intended formula. If you calculate the value like this, you will get the right results and your verifier will work uh just as fine. Right?

So this was there in the Linux implementation. Uh it was fixed and uh yeah now if you're reading uh uh so so these uh these bugs or this knowledge is also very good for bug hunters, right? If you are hunting on eminify or any uh uh bug hunting platform or just reviewing uh the codebase of a protocol, right, you can go and and search for these kind of checks, right? What if you get a million dollar, right? So what we learned if no validations for lranch evaluation at za uh for the zero bad but if you have validations you're a good person and you're progressing and you're a good reviewer hacker mindset like a hacker cat right like good okay um one last thing right uh like there are many many bugs like that I want to discuss about but the time is short.

So I will talk about like this major problem. This is a very big problem in the in the constructs. It's called as frozen heart. Uh if I'm not wrong the name is given by trail abyss and uh they have uh a nice articles uh here. uh uh sure you cannot read them but if you just search frozen her heart rail base you'll find uh articles on that.

Um what is frozen heart like uh before understanding the bug let's understand uh why um it's even here right so how many of you know about fashi transformations great do you want to like try in a line what it does great answer okay so fe trans uh fiat uh showing transformations. So uh it's basically nothing just just an just a framework uh which was designed. So consider interactive proofs how they're supposed to work, right? Uh basically prover right you want to commit to the polinomial right? It will ask verifier now give me a challenge right the verifier will give a challenge right and the ver the prover will try to evaluate at that point and send back right so what happens here it involves or it needs interaction the prover needs to wait for the verifier to give me a challenge and then I try to uh evaluate so basically to avoid this thing uh what was designed uh and created called snucks means non-interactive uh argument ment of knowledge, right?

So what about design? Uh what if uh we avoid this interaction and is still able to do the verification just as is without compromising the security the cryptographic security of the construct. Right? So what was designed? Okay, whatever the public inputs are there like proof elements, whatever the inputs are there, I will just hash all of them down, right?

and then try to create the challenges which were needed. Right? Absolute right thing. Right? If you hash all of the thing down like in the public transcript, the attacker like how an attacker will try to force the proof because if he sending some inputs which are getting hashed, the attacker doesn't know what will be the resultant value.

Right? Now uh what if you miss something while hashing the inputs? Let's say there are 10 uh public inputs in the construct but you hashed only nine, right? You missed something, you forgot or maybe you thought uh it's not necessary to uh hash them down or hash it down because uh maybe like anything that you thought like okay not needed, not secure enough or something, right? avoid it, right?

The attacker will take advantage of that, right? And we'll try to find a way to forge a proof by sending malicious public input but still having the same proof or modifying the proof like let's say there is a proof, right? uh I do not know like uh what proof I have to send or what public input I have to send but I can put in any random data right for the public input and can figure out my way to find out the right constraints or the right data needed right so just giving you a hacker methodology like it's practical uh what I'm showing right now is the way hackers exploited uh this bug or you can probably go ahead and exploit uh by just checking out implementation right uh uh this can give you like easily a million dollars for sure like it's it's that uh critical buck so what you're supposed to do go ahead and find out implementations right any verifier implementations and just check whether all the public inputs are being hashed or not if you find something which is missing What you're supposed to do, take the construct, right? Take the construct, make a fork of the whole verifier implementation, right? And make an arbitrary public input, right?

Any random data because you don't know, you're an attacker, right? You're supposed to forge a proof. You're trying to uh attack the system. Okay? Any random public input, right?

Send all the public values, right? by uh your uh garbage public input to the fork of the verifier right and after that find out where it's reverting. For your convenience you can avoid the final pairing check so that you're easily able to uh proceed with certain steps. Find out where it's reverting. Avoid the checks right and just take out the values which you have found till that point which you needed like involves like lrange evaluations you challenge you something like that okay you got some values which were required initially which you do not which you did not have right now you have the all the values by modifying the verify implementation a little bit right send all those values back in the implementation because you have the correct values random public input but you find out the right values after sending to that right and then you can verify the proof It's that simple and uh you are $1 million rich at least.

Cool. What you what we have learned? Uh missing public values and fears from your hash computation bad. But if you hash all the public inputs, you are good hacker. More.

You want more more bugs? Well, we don't have enough time. So, uh next time. Okay. And uh what if you forget after forget me after the event and no discussions with me, nothing, no interactions, bad kitty, right?

But if you stay in touch with me and follow me on LinkedIn and Twitter, you're a good cat, right? So, that's it. And I hope you have liked it and learned something and I conclude my presentation. Thank you. So we don't have time for questions but remember to follow Razor on Twitter.

So it's razor tweet. It's razor. Yeah. Yeah. Yeah.

Okay. Thank you. Any very insightful any questions? I think we don't have time for question. Okay.

Thank you. Maybe. Yeah. Sorry. But remember to follow.

Praise the Lord.

Automatic transcript — names and jargon may be misspelled.