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

Loading player…

Best Mina application or library built using Protokit - ETHGlobal Singapore 2024

ETHGlobalWed, Oct 9, 2024, 12:00 AM

Join us for a workshop with Matej sima titled, "Building ZK App chains with Mina" This workshops anticipates ETHGlobal Singapore, a 3-day hackathon happening from September 20 to 22, 2024, bringing together the most skilled web3 developers, designers and product builders from all around the globe for a weekend-long adventure to advance the Ethereum ecosystem! ----------------- Follow us: https://x.com/ethglobal Check us out: https://ethglobal.com/ Email us at: hello@ethglobal.com

Transcript

[Music] Welcome to our Workshop introduction to prot kit today we'll be talking about how you can start building privacy enabled blockchains using a typescript cdsl for those of you who don't follow us yet on Twitter um you can see the handle um at the very start of the slide you can also join our Discord server to keep up with the development work or just head over to our website to to learn more about produ um this Workshop is going to take roughly one hour to complete we have around 30 slides of various content and chapters and we also have a live coding session at the end um using all the knowledge that we have obtained through the workshop um plus I'll stop every now and then um to give you room to ask questions so please leave your questions in the chat um I let Rafa collect um let's say the most important ones and we'll stop every now and then to end is so let's get started with the first chapter um the first chapter is called from zero to ZK hero um and it will contain all the basics that you might need to start building zero knowledge applications so um first question is you know what is a ZK proof right um ZK proof is a mathematical proof of computation using inputs public or private uh that results in a publicly available output so it might include a public output too we just said that and what kind of computation can you mathematically prove so it could be various sorts of computation uh you can prove that you have verified the signature using the correct public key um you could do a meracle tree proof or a meracle Tre inclusion um and many more other kinds of computations you can do like addition multiplication and bunch of other stuff um within a ZK proof so to compute a proof you first need to start with a proof system um the proof system that we are using um in everything that we buil is called kimchi and it's the same proof system that powers the Minas L1 um to obtain a proof you first need to execute something that we call a circuit and a circuit is basically a set of constraints or it's basically the code that you are using um which will be used to create the proof so like if you want to do your addition you will write your code inside of a circuit and once you run your code or run your circuit you will uh be given approv by the proove system so um executing the circuit to receive approve is one thing uh but before you can do that you have to compile the circuit to get two kinds of artifacts so you need a prover key to be able to run the circuit and generate proofs and you also need a verifier key or a verification key which will allow you to verify proves one important thing to mention here is that uh proving takes much longer than verifying so once you run an expensive computation within your circuit you can verify very quickly and very cheaply um so yeah that's what we just said um so what's the zero knowledge part of of of proving something of proving some computation it's the private inputs so the proof does not contain um in any publicly accessible form the private inputs that were used um as an input into your let's say calculation or into your circuit function so that allows you to prove that you have done a computation on top of a private input that is forgotten and never exported and that resulted into a certain public output um you can learn more either by like Googling stuff about Zer knowledge proof so there's a Wikipedia link and there's also a link for for something called the Mina book where you can learn more about the proof system itself um to write circuits um you first need some form of like a proof system language um what we use is a library called OJs OJs is effectively um a ZK DSL for writing circuits and the code snippet that you see right now is um basically a snippet that shows you how to write a very simple counter circuit so first you have to install OJs then you have to exp um import the experimental namespace and on the experimental namespace you will find something called the ZK program and the ZK program allows you to define a circuit so our circuit is going to be called counter so we'll just save it in a variable and let's go through the structure of the circuit or the program so every ZK program has a public input and a public output you can omit both of these you don't actually have to use them but both of them are super valuable when constructing a circuit so um apart from public inputs and outputs um every program can have multiple different methods and our program only has one method called an increment which will be used to increment the counter that we will work with every method can additionally also have different private inputs these are the inputs that will not be publicly available as part of the proof and our increment method contains to private inputs so um let's talk a bit more about what does the circuit actually do so the circuit inputs and outputs a field a field is the the most primitive or the simplest type of the kimch Pro System um it's 256 bits uh large and it allows us to store multiple you know different types of data like for example hashes right so the buildin hashing Library uh once you create a hash it results into a single field so what do we want to achieve with this circuit so we want to write a circuit that allows some one to store a hidden number or like a hash of a number and it allows you to increment the number only if you can guess the pre-image of the hash of that original number so let's go through the codes to make it more obvious um the public input would be the hash of the number that is the current state so let's say it's zero so that would be the hash of zero then we would have a and b a being the real value of the secret that you're providing as a private input um attesting to the fact that you actually know the value and B would be the value by which you want to increment the number within the circuit prove that you have incremented the number by the given uh number B and as an output we would have another field which would be the hash of the new incremented number so this would create basically like a state machine where we can go from one secret number to another secret number in a proven way using a Z proof and a circuit so going line by line through the circuit we have this increment method we have these inputs the public input and the private inputs the first thing that happens is that we have to verify if the user has provided the correct number as a private input so if the original state is zero um we would have to first turn it into fields and hash it to get the hash then we would have to assert that the hash calculated from the provided private input matches the public input which means that the user actually knows the number that he needs to like yes then we will increment the provided number and return back a hash of the incremented number so um let's just move to the next slide where I will show you how this program can be executed and how it can be used to prove State transitions from one number to another so um to run the circuit we first have to compile it we already discuss Pro and verifier keys so that's that's the easy part then we Define the initial state which would be a un 64 using the OJs SDK and that would be zero the public input the starting one or the initial state would be the hash of the state the number we want to increment by is going to be going to be one and then we just have to run the counter contract or not the contract sorry just the counter program so counter. increment we provide the public input so the state that we want to transition from then we provide the state in the unhashed format which is just zero and then the number that we want to increment by um by waiting for the proof system to create a proof of the execution of this increment method we receive a proof of increment or just the proof um the proof will contain then the public output which will be the hash of the State plus by which which in our case is one so if we started at zero the increment adds one the public output of running that function and the public output of the proof would be the hash of one because zero plus one and then a hash so um after we run that we can um extract the new public input or the new state from the proof and also update the state because we have just moved by one um now the important part is that after you run this computation here you can very cheaply verify it using the verification key so counter the verify we the provided Pro of increment will verify as valid so it's like the simple round trip of how to write the circuit to be able to obtain a proof of some computation on inputs and outputs and then how to how to verify the proof so moving on um another important thing here is that um circuits or methods or proofs can be recursive because verification of a proof is just another computation that can be proed proven right so if you look at the original counter uh circuit which had the increment or the add method um we can add another private input which would be another proof from a different ZK program that can be verified within the counter ZK program so that could mean that in order to increment the counter not only you would have to know the original number that the counter is at but you would also have to provide let's say a credential proof that would attest that you are um eligible to even modify the counter or to provide the proof of a valid State transition so um recursive proes are probably the single most powerful primitive in the whole stack so um we'll be working with them throughout the workshop pretty extensively so um how is this helpful for blockchains like what kind of benefits do we have if we try to architect blockchains using Serge proofs so we know that blockchain is basically just like a distributed database with a consensus and it's like a transactional database in effect so how does a traditional like round through for transaction look like so users sign transactions which authorize their intent to like perform a certain action on the chain and then notes or blog producers execute these transactions to change the state on the Chain itself um these transactions are then bundled into a block executed you have state transitions coming out of them and then you have the new state of the chain after the blog has been produced um if you want to verify the block you have to rerun all of the transactions this is the case for basically um any non recursive proof blockchain that is out there um okay so what does this mean for the network itself it means that anyone who wants to run a node needs to meet certain relatively High Hardware requirements in order to be able to verify blocks um this makes it more and more difficult to decentralize the chain since the entry barrier is pretty high for people to run notes um with Mina the case is however um quite a bit different um what Mina does it takes uh it turns or like it turns the whole block creation process into a huge gener circuit or actually multiple different Zer circuits um and these block proofs are then recursively composed to basically so what this means is that since all of the block production is a series of recursive circuits it means that uh the barrier of entry for running a note gets lower and lower because to verify a block you don't have to re-execute the transactions again you can just verify the proof provided by the block producer that uh with like certainty pretty much tells you that the transactions have been included correctly and that the blog is valid so you don't have to reexecute transactions again you just have to verify a in a blog proof and a consensus proof and probably bunch of other proofs um and you have then um the latest state so yeah blog is now a single recursive proof which is really amazing for multiple reasons um all right so um you can find more information in the protocol architecture I'll share the slides afterwards so you can find all the links um um let's talk about how do smart contracts work on the Minas native L1 so we already know that the L1 itself is basically like a huge recursive set of proofs so to verify the latest State you just need a single proof now let's take a look at how does it affect the smart contract design and what can you do with the L1 smart contract so um smart contracts Oma are built using the same OJs Library as the ZK programs that we have seen before um there's a smart contract class that you can use and people often refer to these as TK apps um smart contract is effectively just again another circuit that can produce a proof of its own execution it is executed not on chain like you would normally have like in an evbm model but instead it is executed of chain since we can then verify the proof of it execution on the Chain without having to execute it again um so before I've said that originally transactions would be assigned intent that would tell the node the user is trying to um execute let's say a smart contract method here it's different um the user executes the smart contract themselves in their browser or of chain and send the pro of execution to the chain itself okay so um since the note now doesn't know which code to execute on behalf of the user um it is sufficient that the note only receives a list of called account soal like account updates which are effectively the state transitions for the chain um and these come authorized by the proof of the execution of the smart contract so you execute the smart contract you generate the proof that you have executed it correctly and that execution results into account updates or state Transitions and these are authorized by the proofs and then sent to the chain to be applied to the chain State all right um they can also be authorized by signature so not just the proof um the blog producer order chain then verifies the proofs and updates the chain State we just discuss that and all of this is then recursively boundled into a single block proof so out of all of these recursive proofs you end up with a single block proof um and you can carry on with your day so it's really amazing um however there are also like U downsides to this approach so um running all the computation on the clients effectively results into uh race conditions because these clients are not aware of each other and they have no way of organizing of the chain so um let's take a look at how you would Implement a smart contract um using OJs so the example we're using is the same as the one that we have used for the ZK program uh but this time it's going to be a smart contract and the public input that we have used before is going to be saved on chain as part of the smart contract logic so we define a class called counter it extends the fire contract class then we have to Define some storage that will be stored on chain keep in mind that you can only store eight times 256 bits of storage which means eight Fields wor of storage so this contract is only using one so for this example it's completely fine it will work uh just okay so what we are storing on the Chain using this decorator and this public variable or the public proper key of the class we call it commitment and it's just going to store a single field then we will have an increment method that will accept two private inputs A and B same as the ZK program um the state that is stored on chain will become the public input into our smart contract um the business logic is identical to the ZK program so we first hash the input that you're supposed to know then we fetch the onchain state and um create something called a precondition which we will explain later um and then we make sure that the calculated hash matches the onchain commitment if it doesn't we throw an error which prevents the proof from being created um then we increment the numbers again A plus b and we create an incremented commitment and we update the onchain state with the incremented commitment so um I want to talk a bit more about preconditions and what exactly do they mean so when your smart contract runs offchain in your browser on your client it has to fetch the onchain state and then the proof that is Created from calling the increment method has to carry public input or output doesn't matter in this case um basically telling the chain when it goes to verify the proof which on chain State you have used for your transaction so when then there's two users creating the same proof using the same onchain state one of them one of their transactions gets in first it will invalidate the second proof because one of these proofs were based on the same initial state which is no longer valid so that is a precondition it is an additional validation for your offchain proof um so let's see how we would run this smart contract so you would just create a new instance pointing to an address of the deployed contract then you would do a Mina transaction ction by calling counter. increment with the number one parameter is missing here doesn't matter um and this call would then create a series of account updates as we have discussed before and this will be authorized by approve we'll create a state update that will change the commitment to the incremented commitment and we'll also have the original commitment that we have used in the computation as a precondition and it will tell the chain that this account update is meant for the address of our smart contract so when the chain goes to process that address it knows the verification key of our Circuit of the smart contract and can verify the authorization of the account update and apply all of the things listed in the account update to the chain State um so you have to prove the transaction that is where the authorization of the proof is added to the account updates and then you of course have to send it to the note so that's like the tldr on like ZK programs and smart contracts and let's take a brief look at a diagram of like a life cycle of a transaction on a smart contract so we start the life cycle by Alis executing the smart contract in the transaction API uh she provides the parameters needed for the contract execution the contract produces a list of account updates then it authorizes them by proving the counter. increment method within the Pro system and then sends the transaction to the chain so and when the chain eventually goes to prod a block with this transaction and many others it will do the following so it starts at initial chain state where the um commitment was I don't know commitment a then it goes to apply the account update it checks if the account updates are authorized by verifying the proofs associated with the Circuit of the SP contract then if the proof is not valid um it throws away the account updates and the transaction is reverted if the proof proof is valid it proceeds with checking the preconditions so it checks if the precondition the fact that we have used the initial commitment value to calculate this proof matches the onchain state so if the initial State matches the commitment we have used to calculate the proof then the precondition will be considered valid and then the state will be uh State updates will be applied and we will end up at a resulting state so in our case we go from commitment a to Commitment B by proving that the contract is really meant to create this kind of a state transition and then the chain applying that state transition to the onchain state okay um let's talk about key value storage in Sp contracts so far we've only seen a state that stores a single field we can use eight of those but this is um essentially not enough if you want to have a more complex application that stores like let's say user deposits in a key value storage so the following syntax is uh not possible with the L1 SP contract you cannot just Define a key and a value type and then just store multiple values on the Chain itself um this is due to like multiple reasons like probably the main reason is that every circuit calculation or computation has to be fixed size so users cannot just arbitrarily Define um any storage shapes that they want right so yeah um so this is not possible so what can we do about it like how can we build apps that have more States than possible out of the box so um instead of storing like a key value map on chain we can store a single commitment to a rud hash of a merco tree which fits into a single field that would look like this so let's explain a bit more about what a merco tree is so uh yeah here we go wrong slide so what is a Merk tree so Merk tree is a data structure that allows us to cryptoglyph cryptographically verify that a certain Le is part of the enre structure so imagine that you have a huge database full of key value records and you want to do some computation on top of a single Row in the datab tase so the row in this case would be the leaf and to verify the Integrity of the data that the leaf is really part of database of your table or of your tree um you have to do what is called a miracle proof which means going from this Le and calculating the root through cryptographic hashing to ensure that um the data that you are using is really part of that uh of the three structure and the root is the commitment that we are storing on the Chain um so let's move on um the simplest way to use Merkle trees in my opinion is uh what's it's called the mirle map it's an API provided by OJs so all mer Tre leaves can be identified by an index um if you use a Merle tree with a sufficient depth of 256 it means that there's going to be two to the power of 256 leaves which is like um a lot um I mean by saying a lot I really mean a lot like it's a huge number um of all the record that you can have in your key value storage so thanks to the depth of the tree it means that the size price of the index or the amount of the leaves will be from 0 to 2 to the^ of 256 and this means that the index can be represented by a field because uh field has the same like range of values pretty much in binary um so this allows us to treat the tree like key value storage where the key can be a field the value can be a field and the root will also be a field um that's the merco map so here's how you can use the mer map so first you have to create a new instance of the merco map then Define a key and a value which both have to be fied um you can take any data type in sjs and turn it into an array of fields which will then allow you to Hash that and you will arrive at a single field so we do it for both of our key and the value and we can then set that to the mer map so the merco map will now be the tree of hashes of our values which we'll be able to find by an index or a key um to prove that a certain value and a key are part of the of the tree we have to do it through a witness so we can get a merom witness and then using the real value that's supposed to be stored under um that index we can calculate the root has so if you remember going back to Here by providing the value at the Le you uh and receiving this value and this value you can then compute the first green node and then using this Noe and this note you can then compute the root which tells you that you're using the correct value for Del Le so going back to here um computed roote and then computed key um which we can then use for our proing so um let's try to write a smart contract that uses a merom map based storage um for storing user counters right so we want to have a contract which allows us to store a C for a specific public key and then increment it as we uh c f so um we Define a counter's commitment this is a onchain State field that will store the root hash of our counter uh Merle tree or mer map and a single increment method that tells us for which public key we want to increment and buy what value keeping in mind that those are like private inputs again so first we have to uh do the precondition then where we get the state and then we assert that it equals the onchain state that's called the precondition then we have to turn these data types into something more compatible so we have to turn them single fields we have this helper that I've just used here but it does the hashing that we've seen before so the key for the public key will be a hash of the public key then U we want to get the current counter for the user um and we do that for the specific key of like which is the public key um and that gives us again another abstract helper method very is for to say the this example that will give us the actual value of the counter which would be zero for the first run and then a witness that points to that counter in the M tree so we have the value and the witness now we have to take the witness and has representation of the real value to calculate the root hash of the tree and to the key that the value belongs for according to the witness if the counter commitment again a mistake here should be the counter computed counter commitment matches the uh counter commitment that's stored on chain that means that the counter value that we have received as a private input uh right here is valid and we can trust the value um we have to do the same for the key and the computed key to make sure that the witness was really for the key that we are actually meant to be using so that's the publicly provided by the user um that's how do you validate data coming from a tree into your smart contract and in the second part we will take a look at how do you write data to the tree so let's move on um updating the three data so um this is a continuation of the increment method so we will take the original counter value incremented by the number that we have provided by the user that will give us the new counter and then we will use the same witness to compute a new route using the new value right so before we used the old value that was supposed to be in the tree that gave us the same room has so we could verify the Integrity of the data and if you want to upgrade or sorry if you want to write to the same lead we will just use the same witness calc the new root hash using the new value that's supposed to be in the tree and then we will take the new calculated commitment and we will set it as on chain State um Rafel you have any questions in the in the meantime um you can start it basically anywhere you want so one of the first things that um like I've tried to build in the ecosystem was an offchain storage Library um we will go through explaining why that might be difficult or not possible to use offchain storage libraries um in the next slides any other questions I assume not okay so um let's build on of what ston was asking and let's take a look at the design limitations of this approach of using merries for data together with smart contracts so we know that users will execute smart contracts on the client side um independently of other clients we know that this leads to preconditions oring to the route of the tree we've discussed it just now um and this means that if there are two users transferring at the same time their preconditions will be the same the conditions will be identical so this means that when the chain the1 goes to process these two user transactions one will invalidate the other so the first one that succeeds will update the onchain state and when the second one is being processed it will have a stale precondition pointing to the Old onchain State so this makes it pretty impossible to achieve any meaningful congruency if you write your smart contract in the way that we have seen in the example so far um so yeah um we just discussed this how the second transaction is invalidated and we also have a diagram that shows you how this actually happens right so we have this all happens on the client we have Alis that is executing counters. increment um then the increment method uh pulls in the actual data and the witness from some form of an offchain storage would be an API could be some Distributing Network like we wrote libraries before for that um before we started writing Proto kit so um we have some experience there um this produces again account updates and the account update will try to update the chain state to a un hash and it will also have a precondition to the current onchain rud has commitment and uh the red part the current R has commitment will match the current pre-state um but after the first transaction is created and if you have two in parallel um the first one will set it to the new Ro hash therefore for the second precondition second transaction precondition will no longer be valid making the transaction fails um creating concurrency issues so um that was like the tldr on on DK program and smart contract um I hope you guys went from zero to DK hero in the in the in the short span of time and now we can move forward with the actual subject of the workshop which is U trying to build a private airdrop on Mina so um what is a private airdrop and what kind of architecture do we need to to build one so we will be storing all addresses eligible um we have a question yes let's go through the other example then explain how actions or sequence State could help you with that and what kind of bottle legs would you run into using actions and sequence State and then what else can we do to solve these things right so the architecture um we will store all the all the addresses eligible for our air drop in a miracle tree so we've just seen how to work with Merle trees how to read and write data to them then um one important thing very important actually uh to keep this as private as possible is that everybody will receive the same amount of tokens from the airdrop if that wasn't the case then we would basically create a link between an address receiving a certain amount that information being public and then when the claiming happens you will be able to pinpoint which address received the tokens based on the amount to the original address that was eligible so we want to avoid that therefore everybody gets the same amount um we will also need to build an airdrop smart contract um which will have a claim method that will only give you tokens from the airdrop if you can prove that your address is part of the a drop parle tree we will also have to utilize something called the nullifiers to make sure that you don't claim twice by proving two times in a row that your address is part of the airdrop Mer tree we could just store the addresses that have already claimed in a separate Mer tree but that would defeat the Privacy purpose of the private aor therefore we use a nullifier which cannot be backtracked to the original address that was claiming from the air drop so this effectively allows us to break the link between the address that was eligible and then the address that receiv received the airr tokens in the end so um just a little bit of a disclaimer um this architecture is pretty similar to tornado cach so um it's something that you definitely shouldn't be doing because of money laundering concerns um stuff like that I also have to say that um we have limited time in the room and we will have to move to a separate room in about 10 minutes so I will share another link okay so moving on we have this architecture we want to build a private airdrop so how would the smart contract look like um we will define an airdrop smart contract it would store two fields on the Chain it would store the airdrop tree commitment which is a Mar tree that contains all the address eligible for the a then it would also store a root has of the n Fire tree which contains the nullifiers that have already been used to claim from the airdrop then we need a single method called claim that accepts several parameters so it accepts the air witness that's a witness that allows us to calculate that a certain address is really part of the air tree then the nullifier which has been signed and created outside of the contract using the m assigner and then the nullifier witness which will allow us to prove that the nullifier has no not yet been used as part of the NF fire tree so um we first have to get the onchain state and create the conditions so we do that in the first two lines by using this fancy method called get an assert equals um which allows us to save some uh some space and then we have to check if the user is part of the airdrop so we have to take the air drop witness that we have for a specific key um which would be your index in the of tree commitment and we compute a rot and key using the value bulling through right so if you are in the tree the leave that's represented by your address will have a value one which is the field representation of the boing if it would have the value zero it means you are not in the tree so um we compute the good has and we also compute the key now we have to do two things so before we talk about verifying nullifier let's talk about verifying these two variables um so we have the airdrop three commitment which is the on chain State coming from here and we have to assert that it equals the computed air drop tree Commitment if we uh do this it makes sure that whatever address used to uh generate this witness was actually in the tree because you managed to arrive at the same root hash as is stored from chain hope that makes sense um if not um we can try to explain it more but um just let me know in the questions then we also have to check the computed airdrop key and make sure that it asserts or it is equal we have to assert that it is equal to the public key used to generate the nuli right so we take the nullifier we get the public of the nullifier we turn it to field we hash it and we make sure that the witness is a witness for a key and that key is the public key that was used to generate a NF fire so we have all of these links um basically a testing that we can like it's basically a link in the proof or in the circuit that these variables um are arriving at the correct like the variables and the computation arrives at the correct values so U moving on forward with the logic so the second part of the smart contract would be um ensuring that the nullifier has not been used yet and then adding the nullifier to the tree of nullifiers to make sure that it cannot be double spined again right so we have to update the nullifier tree so uh what can you do here so you take the nullifier witness which points to the uh nullifier that you are currently using we compute the roten key using the field zero so we are checking that the nullifier is not in the 3 um we get the computed nullifier Tre commitment which we will again compare with the onchain state to make sure that um the tree which we are proving against is the same as the tree or the state that is committed on chain um then we also have to take the nullifier key which is the unique message that can be like stored publicly and won't allow anyone to trace the key back to your actual address that you use to create a nullifier and we assert that it equals the computed nullifier key so uh we use the nullifier key as the index in the nullifier street uh so this three lines of codes allowed us to check that the Nary has not been used yet now what we have to do is we have to write a nullifier to the tree and that's the last piece of the code in the contract so we take the same nullifier witness and we compute a new route using bulling true we should have used field one or bulling false here as well to to keep it consistent but uh at the end of the day it's the same result so um we use the nullifier key and its nullifier witness to write bulling through to the leaf that is identified by the nul fire key and it gives us a new threee commitment so effectively like I know I'm talking a lot about commitments Computing a root checking key or this or that but effectively it's just key value storage and you always have to uh take your data type turn it into a single field and then save that into the tree and store the actual value separately so in reality it's pretty straightforward um but you have to do all of this um fency mat to verify the Integrity of the data and to prove the update of the tree data as well so we have the updated NFI tree commitment and we can store it on chain using the account update uh State and then once we've done all of this and the proof didn't fail because the mulier has not been used yet and the airr inclusion air inclusion was proven as well we can then uh use the send API of the SP contract to send Mina to the sender of the transaction and give him a fixed amount the important part here is that the address that will be used uh in the a of witness and to sign the nullifier or to generate the nullifier will be different from the sender and this will allow us to send the tokens to the sender address without creating a link to the original address that was eligible for the in the first place um so yeah um that's it for smart contracts yeah so Gregor is saying that the nullifier has convenience methods for all of this stuff uh that is true the reason why I wrote the contracts like this uh is because I'm not familiar with them in the first place and the second place is because later down in the workshop we will go through um extracting this into a SE Pro so um before we move on I'm going to ask everybody to hop into a separate uh into a different meeting room because we are running out of time in this one so um we have gone through the part of the workshop where we explain what the proof is what a circuit is how you can use circuits to create Spar contracts that can have their execution proven of chain uh we've seen a diagram how that relates to Min and L1 and how it is possible to verify this offchain computation of these fire contracts and uh wait uh one moment I think I have a mix up in my slides wait so we've been through the design limitations we've been through the airdrop I think it's now time to answer bandon's question so how do we like is there any building L on the1 to address the concurrency issues that we have been describing um yes there is um but before we talk about how you can address this I want to talk about like the general approach so the approach that we believe um everyone should be taking to address these concurrency issues and also the approach that the um official dogs of Min are suggesting is effectively to write your own rollups right so what would a rollup actually be it would be that instead of users running their um smart contracts of chain themselves the users would be effectively sending transactions or referred to as actions in the one protocol um and then someone would roll all of these actions up which would effectively create like a rollup block so um the el1 already has like Primitives for like these microps called sequence State actions and the reducer API um so the next chapter we will talk about Proto kit Proto kit is a framework that allows you to use the same Concepts as sequence State actions and a reducer and it provides you a very convenient API to write your circuits so that they can be rolled up using an approach like sequence State and a reducer so we have these concurrency issues we've seen a fire contract and how it can be implemented now we need to learn how to implement circuits in a way that we can roll them up into individual blocks and get them settled on the L1 so um time to talk about proit um the crem of the of the workshop so uh prot is a protocol development framework for privacy enabled application chains also referred to as ZK rollups or ck validium or like depends on how you deploy them um to get started with proit um you just have to run the simple command below here uh we won't do that now um we'll walk through the workshop um and then we'll do a live uh coding session afterwards so what is proit um prot is a typescript based framework for building ZK chains ZK rollups application chains um you name it it's not another ZK evm based ZK rollup instead it uses a Custom Tailor zkm that is like privacy friendly sorry um it offers a hybrid execution model um that allows both off and onchain execution option execution means the same as we've seen with smart contracts before it allows you to consume client side your knowledge proofs same way as the smart contracts on the L1 um chains running with the prod framework also get the direct benefit of end to end frent execution so it means when your sequencer or the node operator runs your chain and processes user transactions the sequence operat does not have any room for any malicious activity because to produce a valid block proof at the end he has to execute all the circuits that create their chain correctly with no room for anything malicious um also everything that you build with perket is going to be completely customizable and modular so it means that if you don't like a certain part of the system um you can replace it very very easily um one of the main design goals of the framework was to to have a virtually no learning curve um but you should still be paying attention because there's going to be a lot of interesting stuff um so what I mean by no learning curve um can be expressed very simply so lot of the apis are similar to the SP contract apis and it's using the same OJs DSL so it means that if you've been using circuits or SP contracts before transitioning to Proto kit and getting rid of the concurrency issues by writing recursive rollups instead of just simple SP contracts should be fairly simple so um we want to build our like you want to build your own Z right you have your smart contract you have your concurrency issues and you want to address those somehow so you can build a recursive zop that's going to be a series of recursive circuits that prove that transaction or as Brandon was asking action execution and the state transitions Behind these uh behind the business logic that's being executed so a state transition is effectively merry operations so um we already know that the L1 offers sequence date and actions to help you build zops but you still have to write your own circuits and even if L1 didn't offer sequence dat actions as part of protocol um you would still have to like write your own man pool so um I will just detour a little bit to explain how this works so with protate we are not using sequence State and actions directly but we are using the same identical Concepts behind sequence State and actions which ensure um basically endtoend Pro and execution of all of your actions without like any uh any break in the system so it means that if a user dispatches an action and then there's a bundle of actions that means there are at least 10 transactions that have to be executed when the sequencer goes and executes the bundle of the transactions the sequencer cannot skip transaction transactions at their own will which is the same concept as sequence State effectively is um another reason for us to go with a private manle over sequence State and actions is that just to dispatch an action asir of the sequence late on DL one uh you would still have to create a proof um don't quote me on that like we have Gregor and Brandon here who can tell us more about it but in my experience or in my understanding just to dispatch an action you have to create a proof which is much more time and resour intensive uh than just signing a transaction as you would in our private mle um it it still doesn't mean that you cannot use sequence dates and actions if you want you can write a mle backend or a mle module for Proto kit uh that would use sequence dat in actions directly and you wouldn't have to use our private signature based mle anymore okay so prot enables you to build your knowledge interoperable and privacy preserving application chains with a minimum loing curve right you already know that so you can roll your own Z roll up in a few lines of code and uh it's also important to mention that these ZK rups are effectively nothing more than a super set of ZK apps so it means that you get the opin Privacy features with public onchain execution that allows you to deliver the best possible experience so with smart contracts the Privacy is pretty much um always there let's say um and you don't have any room for onch execution so there's no onchain execution in the El fire contract but with a prot chain you get both you get both the offchain execution with privacy and onchain public execution this allows us to uh combine the business logic in a way that makes the most sense to get the most out of the user experience so um what is a prot z up or an application chain made of U it consists of Fran time which is a series of circuits that Define your application logic these could be auctions dexes balances vesting modules or whatever else that your application might require in terms of BAS logic then we have the protocol circuits so that's the underlying virtual machine the block production transaction fees are handled here uh account State stuff like nonis all of that is handled under the protocol and then we have the sequencer which is like the note or the server that has like offers mle Services um and orchestrates all the block production and handles most importantly also the settlement to the L1 so once transactions are processed block proofs are created they have to be settled to tl1 but we'll have a diagram explaining that in in more detail so yeah also the L1 settlement contract okay um what kind of features can you expect if you use protok kit so um main focus having a supercharged developer experience you just have to know typescripts and a little bit of OJs here and there um you can write and use reusable modules um you can use open source libraries that offer protoid modules like for example we wrot a DEX that Dex is open source so you can take the balances module uh which is effectiv The Ledger for the chain um from our de and use it as part of your project uh with no limitations um also the chains build the prot will have no data race conditions unless you choose to specifically engineer your system in a way that would recreate the uh issue with preconditions known from B1 right so if you want you can choose to do that since this is just a super set of zcs but um the main purpose of the tool was to allow you to build in a way that will not have race conditions um the onchain storage uh that we will see in a moment is effectively bottomless because we use the largest Merkle tree possible and the amount of records that you can store there um is not even worth mentioning because before you run out of available records you will probably run out of memory anyways or like the number is infinitely huge so um you still get your offchain privacy because you can create proofs from circuits on the client side same as with ZK apps and send these as Arguments for onchain transactions to be executed in a sequence manner onchain um you get to focus on your business logic you don't have to do all the heavy lifting so the operations that you have seen for example to verify data or to roll your own rollups um you wouldn't have to do that anymore because we all the circuits and all the Machinery behind the scenes all you have to do is just focus on your business logic and write your run time um proty chains or proty transactions are compatible with user valids in the ecosystem because we rely on features from the mean assigner directly um as we said before it's not another zkm but it's a succinct ZK VM and I hope I pronounced it correctly um I've been saying this for a while now but you can reimplement the zkf preconditions in your runtime if you want to but out of the box um you can just treat proty chains like you would treat development on another like on any other evm like ecosystem that you already familiar with so this also makes it super easier for newcoms to build because they don't have to learn how smart contracts or ZK apps work because they are super specific to to to dl1 um and instead they can reuse their existing Knowledge from evm like ecosystems to start building this prod um since this is a super set of ZK apps um and you need some specific features that are not yet available in the ZK apps on the L1 you can um completely customize the behavior of your own chain therefore you can get access to Features faster than let's say the L1 would be able to iterate on because the L1 Bears a lot more responsibility towards the whole ecosystem and features specific to your app might not be necessary for everybody else therefore belong on your app only so you can do that with prot um features that will be available um soon so um the chains need to be interoperable with each other and also with the L1 um with like varying degrees of security guarantees compared to the ux available and you also need data availability unless uh you want to stay a validium instead of a rollup so we have plans to address all of these um but don't tell anyone just yet so moving on um how does the protate architecture look like or what is the life cycle of a transaction so before we had the same diagram for smart contract and now we'll take a look at how to basically achieve the same things as before but with prodoc so the only thing that needs to happen on the client side is that Ellis needs to sign a transaction she doesn't need to prove anything on the client which makes it um basically faster right so we've discussed before that even if you want to use sequence dat con actions you still have to prove the change to the sequence state so um Ellis creates a transaction specifying which runtime module and Method and what arguments she wants to use that is signed and sent to the ZK rollup or to the sequencer so it's just a simple transaction then um the sequencer takes all the transactions and executes them by running the runtime uh method which is which means that it creates a proof of the runtime method execution that also produces State transitions similar to account updates that we have seen before and then these State transitions are applied by the protocol so the protocol writes the CLE Tre data updates and then the block proof takes together the proof of the runtime execution and the proof of applying all the state transitions merges them together and creates a block proof this happens for all the transactions until we have a final block proof that block proof is then settled on the Mina L1 so every product chain will have a smart contract running on the L1 which will receive block proofs only from your rup which means that you can settle to the Mina L1 so like what does settlement mean like you could run it without the L1 that would be like a sovereign roll up um that unfortunately doesn't have much security because you have like a centralized sequencer on the current model um and the only thing that you can rely on in this case with a zking rollup would be that the execution would be correct right because there's a circuit and everyone can verify the block proof this is effectively how the Mina L1 works plus you also have consensus uh which makes it like a full uh proper L1 so since the your z r doesn't have consensus you need settlement instead and you reuse the consensus of dl1 to settle your block proofs right here so there's a block proof created that tells the chain that we are transitioning from State a to State B on the Rob and that is sent to the L1 let's move on okay so um before we were writing smart contracts in circuits in in proit you won't write smart contracts anymore you will write runtime modules which will be the bread and butter of your chain so um users cannot just deploy smart contracts on their own to your chain your chain is built for a specific purpose a specific application and therefore the business logic is baked into the core run time of the chain itself so let's take a look at example at an example of how you could write a run time to handle balances and Native and third party token transfers in your chain so you would write a class called balances that would extend the provided class runtime module don't worry about the unknowne this is a config related thing and since we are not using any config for the runtime we're just going to leave it empty as is you also have to decorate it with the runtime module decorator that does a bunch of like interesting things behind the scenes that we might explain at another Workshop then um same as on the smart contracts you have uh you have to Define some state with the difference that a perit run time is not limited to PID field so and you can also use key value storage we call that a map so we want to store a map of balances it's going to be a state map where the key is going to be a public key of the user and the balance is going to be a un 64 then we need to Define methods that users can call in transactions we will label them with the runtime method decorator and we'll just call this function transfer um what is important to not here um if you remember the previous diagram this function is going to be triggered by user signing a transaction and execute it on the sequencer so there's not going to be any congruency issues because the sequencer will order all the user transactions and execute them and the save transitions in an ordered or a sequenced manner so um what does the transfer method do it transfers from a public key to a public key a certain amount um so how would the implementation look like so first we have to obtain the from and the two balance which is do that using the state map get API which is relatively straightforward any calls to the state API will produce State transitions behind the scenes which are like quasy equivalent to the account updates then we have to check like some Sello transfer logic we have to check if the front from balance of that from Key the front balance is greater or equal than the amount being sent we check that here this gives us a Boolean from balance is sufficient one important thing here is to note that we cannot uh use assert apis from OJs because we don't want this onchain execution to fail forcefully we want to fail it in a in like a silent manner that can be then processed by the protocol so we use a custom assert API that's available from uh from protoc So You assert that the front balance is sufficient and if it's not you will throw an error that the balance is insufficient this creates a flag behind the scenes that tells the protocol that the method was not executed correctly and the state transitions for it should be discarded but the transaction will still be included in the block but the status will be that it failed um this is something that we've seen for example Stark net only had recently um or starware I don't know um like they were not able to even Pro like process failed transactions before so um then we have to decrease the balance from by the amount that is being sent and we have to set it to the uh chain API there's a little error here let me just um fix that real quick so when you are setting the balance you have to set it for an address and then a value so it would actually look like this ra do we have any questions sure sure let me let me do it just really quickly so um we use a dependency injection framework called T range to orchestrate um the chain and the dependencies and all the modules that come come with it runtime module makes the class injectable as part of the dependency injection framework um that's like the main feature that it does um the state decorator I'm not sure if you asked about that but so the state decorator basically helps us identify which index in the global Miracle tree State uh the balances are stored under so if you store a balance for the from public key the the path or the index would be created from balances dot balances do the from address and that is what we would compute the leaf index in the global three state from um hope that makes sense and then the runtime method is probably the most veros like internally verbos part of code in the entire system um if we think about like the module API itself um and what it actually does it wraps your function into another function that ensures that the protocol execution was correct so um you can imagine your function being nested into another parent or a higher order function that also checks your execution status extracts all the state transitions uh creates the public outputs for the underlying ZK circuits and all the other things that you don't want to care about as a developer unless you really want to go down to that level like of of the tooling itself so this is pretty much a heavy lift from the shoulders of the users so that they can focus on only writing the piece of logic That Matters to them and they don't have to carry about the protocol behind the scenes is there any other questions Rafael yeah I mean we don't have that much time I don't know how long people want to stay um dldr um you get onen execution you don't get that with smart contract that's like the main selling point um from the slides before all right uh let's move on so writing your own runtime so now you wrote runtime module now how the hell do you create a roll out of it how the hell do you create an OP chain out of it so writing your run time now testing your R time right so if you want to execute the run time let's uh first do a little bit of a setup so we first create some keys for alies and Bob so that we can do the transfer from and to then we create a testing app chain from runtime modules and the only thing we have to do is pass in the balances class that we have created with a config that's empty because we are currently not providing any config for the module so now we have an app chain and we can start it very simple one line of code and we also can set a signer for the app chain API which will be Alis um this will be obvious in the next uh slide where we have to create a transaction to the transfer method of the balances runtime module we first do that by extracting or res ing the balance's runtime module this gives us an instance of the balance's runtime module then we make a transaction using the ab transaction API using Alis as the sender and we make a transfer using balance is the transfer from Alis to bul for a certain amount of tokens now we have to sign the transaction and send it and then in the test what you would have to do is to produce a block this basically triggers the block production pipeline in your tests as part of your testing app chain uh processes the transactions that you have signed and sent uh processes all of the state Transitions and gives you a block proof even though it's just a simulated block proof because in your tests you are not running in approv rem mode because it will be uh slow etc etc um so you eventually produce a block now the last step would be to actually take a look at the onchain state and make sure that pop has really received the balance that we have transfered as part of our logic so you can use the query API for that so app chain query we are quering the Run time State the balance is module the balance is state map uh uh the balance is state map and we are getting an entry from the key value of the state map identified by the B's public key and if our transfer was successful and the blog was produced correctly uh the balance of Bob that we would receive would be an instance of un 64 data type holding 100 right um to be able to actually do this you would have to me the balance for Alice first otherwise the trans action would fail but like to keep things simple uh we've only demonstrated how the transfer would work all right so um the last part of the workshop um I believe we have about 50 minutes left so we might not do like a live coding session but I can walk you through how the airdrop code that we have seen for the SM contract would look like in a protoc one time what kind of components would you have to build for that and how the test would look like and how the overall developer experience would be okay so let me just stop sharing and prepare the workshop materials okay so going to re share my screen real quick enter stream this one okay so I hope I hope you can guys um see my screen let me know if I should like zoom in or anything okay so um I'm inan my editor um you see the workshop repository you have two folders one for the workshop slides I'll showare the link to the rep later so you can check it out yourself but the folder that we are after right now um would be the one which tells us how to build the same uh private a functionality as we've seen with the smart contract but with the prod runtime and what kind of benefits would we get if we build this using Proto kit and um what kind of things would not work with the smart contract on its own so um the components that we will need for our runtime would be a balances runtime that allows us to Mint a certain balance so when the user claims for the air drop we will make a balance for them the only thing that it does is the same as the balances example we've seen just now it gets the balance increments it by the amount that's necessary and it updates the user's balance keep in mind that mint is not tacked with the runtime uh method decorator that makes it not publicly callable through a user transaction and it makes it only callable internally from other onetime modules which make sure that users are not meaning freely as they see fit but u based on some business logic that needs to be applied beforehand then with we will also have a airdrop runtime module that will do a bunch of different things so it will store a commitment to the airdrop tree the same as we had in The Spar contract but this time it will not store a commitment to a nullifier tree instead it will store a key value state map of nullifiers and an indicator if they have been used or not so um the airdrop runtime module will do also one interesting thing and that's create a dependency to the balances module so if your testing app chain would have a runtime consisting of balances and airdrop it would enable the airdrop runtime module to inject the balances module and use it directly so we do that by the Constructor and that is why we have the runtime module decorator so we can use the dependency injection library and so on and so forth so um going through the methods like the first one that we would implement would be a set commitment what this does it tells the airdrop runtime which commitment or which airdrop three roash to use so um let's imagine that we wrote this class we wrote the uh onchain State for the commitment and then we wrote the method for setting the commitment now we would have to write a test for it right so let's move back to the test and see how a test for this would look like we don't need this um and we only need these two okay so before all of the tests will run we will have to again set up the testing app chain that will consist of the two runtime modules that we have built um and an empty config then we will set L as Aigner start the app chain and we solve the runtime modules so that you can use them within our tests and within our transactions so the first thing you have to do is um set the commitment to the airdrop tree root has so to do that we have to also Define the airdrop tree we do that above in the test we create a new Merle map uh we create a new key using the alysis address and we update the Merle map U this is what stepen was asking before like where do you store the Merkle tree for the purpose of the test it lives only in the memory here U keep in mind that this is the airdrop three that is not part of the on chain State uh we just use the commitment from it so uh we now have a map where Ellis is stored and that indicates that Ellis is eligible for the drop and we also obtain a witness from that that's useful for later so um to set the commitment we get the root has out of the map where Ellis is already stored and we send the transaction sign and send it produce a block and then in the test we ensure that the commitment actually made it to the chain state by querying the runtime air drop commitment State and running an expect that ensures that the onchain commitment onchain meaning on theal two matches the root hash from the map that we are using so that would be the first test right now we have an runtime that stores a commitment so now we have to move on uh we have to move on to the implementation of the claim method so before in the smart contract we provided all the witnesses including the nullifier to the smart contract method itself because it was executed off chain however runtime methods as we already know are executed on Chain by the sequencer not on chain on the L1 but on chain on the L2 so we cannot uh tell the sequencer which address we are claiming for because that would uh break the purpose of the private airdrop and it would expose which address we are claiming for which is the only thing that we are trying to avoid by Design so instead to preserve the Privacy aspects the claim method will accept an air drop proof the air drop proof will do the exact same thing of verifying the nullifier and the Merkle proofs of the air drop tree of the chain on the client side in the browser it's going to be signed and sent as part of the transaction to the sequencer and then it's going to be executed by the sequencer when the claim method runs and it's going to run all of these other additional uh business logic so um let's take a look at how the air drop proof is constructed first before we take a look at uh what else does the claim method do so our a program is really simple uh it results into an air drop proof and it has a single method called can claim that has two private inputs the Merle witness for the air drop three and the nullifier as before um as an output it has airdrop public uh output which is the root hash that we have been able to calculate from the um air drop Tre witness and it also has a nullifier key in the output so that we can then process the nullifier key on chain and store it in the state map of nullifiers in a sequenced way without having the same precondition issues as with the L1 smart contract so this is the pretty much the most important part and it should be like the key takeaway uh in terms of differences between smart contracts and runtime modules runtime modules allow you to overcome the uh uh the concurrency issues and the rate conditions by moving part of execution on chain where uh the state transitions that need to be sequential are done in a sequential manner by the sequencer so uh we have the root and the nullifier key and now let's take a look at the implementation of the ZK program so we know the private inputs are the witness to the air drop tree and then the nullifier itself and it Returns the public output which we have just uh discussed so we take the nullifier this happens of chain and we take a look at the public key that was used to calculate a nullifier and we turn into fields and we hash it again this will create a single field which will be the key used um to calculate the root hash from the from the witness right so then we take the witness for the air drop tree we compute the rot and key expecting this address to have a bull tree value in the tree that gives us a root hash and a computed key so um we have to assert that the computed key from the witness or the key that identifies the leave that the witness is SC for is the same as the key derived from the nullifier public key so effectively effectively what this does even though I like probably over complicated the explanation by tenfold is that it creates a link by the witness being for a specific Le which means being for a specific address and the nullifier being for a specific address this is very important um additionally it also verifies that the nullifier was created for specific purpose we do that by using a message that needs to be unique enough like for the test purposes it's very simple it's not unique at all um this would mean that as an application developer you are meant to create a nullifier that's uh using a message that's unique enough so you can prevent collisions with other applications by using the same message because then the nullifier could be misused or double spin for different purpose and you want to avoid that so the message needs to be unique not like in our test um we we've computed the root we verified the key we verified the nullifier and now what we can return from the proof is the computed route to make sure that the proof was done on top of the correct witness and using the correct Merle Tre data and we also export the nullifier key so that we can break the link between the address used to calculate the Merle proof and uh the address that claims the funds in the end and we do that by connecting them basically by Nile which is something that cannot be back traced to the original address and will be stored by the nullifier street this nullifier here so this gives us an airdrop proof and the air drop proof has the public output of the root and the nullifier so uh going back to the run time and how does claiming work so you provide the air drop proof um this shouldn't be there obviously right um you verify the a proof because we know that verifying and another recursive proof is just another computation that needs to be verified then we get the commitment from the onchain state we make sure that we create a soft assertion so we don't assert equals we create this soft assertion and a soft failure potentially that the eror proof and its public input and the root that it contains equals the commitment and therefore matches the same tree as as it stored on chain if it doesn't you get this custom error um now we need to check if the nullifier has already been used so we take the nullifier key from the public output of the proof we try to get it from the nullifier state map and if we are able to assert that um the value for the nullifier is false and not true which means it hasn't been used um then we can continue if at this point we figure out that this nullifier has already been used we need to throw an error and that's why we have this assertion here so then um we if it wasn't used and the transaction did an error out we will set the nullifier key to the nullifier state map to make sure that it cannot be like reused again which is you know what the logic above uh checks as well if you ran this the second time using the same a proof using the same nullifier it would eror out because the nullifier has already been used and that's only that's also possible without like having to keep track of who the original uh address was so um once you've completed all of these checks and you know that the user is eligible for the air drop and new his nullifier hasn't been used yet uh you can Min the balance for the user and we do that by minting the balance for the center of the transaction which is hopefully a different address if you want to preserve privacy than the address that you're approving for and we will mean you a th000 tokens um and that's pretty much it now we have to take a look at how it looks in the tests and then um we can use the rest of the time to discuss um additional questions so um we've seen how setting the commitment works there was this this test now we have to see how to construct the proof um how to create a nullifier so that we can make the transaction here so um we first have to create a test nullifier using the OJs API for specific message signed by Ellis um that's important so we have a nullifier for for Ellis now we have to execute the can claim method of the circuit using the provided Witness and the nullifier and we have to turn it into a mo proof so let's take a look at how that that looks so a mo proof um is going to return us an airdrop proof which is going to type check and be a valid proof for the airdrop program so it's just like syntactic sugar for for like testing purposes U that we can use to calculate the dami proof and then return an instance of the proof class that would contain the correct public output that we are using for testing so we don't actually have to run the proving we just have a mock proof and we can uh do the tests pretty fast so um we have the nullifier we have the proof now it's time to do the transaction so we send this mock proof to the uh air drop claim method and we we sign it and send it then we again produce a block and then we have to do some checks to see if the transaction was successful or not first we can check the transaction status to be true that is what we are expecting to to work then we can check if we have have the stored nullifier and if it's if it's true or not if it was false it means that the nullifier wouldn't be stored now the value of the nullifier is true under the nullifier key so we know that the nullifier has been stored and then we have to check uh the balance for Lis in this case because we are sending it to the same address as is in the airdrop ID list which is not exactly what you want to be doing you should be uh you should be a separate address right but for testing purposes it is the same address so we check the balance of Alis by looking for the runtime balances balances state for Alis and that is a th000 because the brand time has transferred 1,000 tokens or actually Meed 1,000 tokens for us as a result of the successful air drop claiming um and then we also have a test where we have a negative like a negative case where we try to claim using uh the same nullifier and the same air drop proof and this returns uh this results into a failed transaction where the error is that the nullifier has already been used um the nullifier still stays true and the balance doesn't change so that's pretty much it as far as the workshop goes I believe we uh exhausted the time precisely so if you have any questions um I would be more than happy to to answer [Music]

Automatic transcript — names and jargon may be misspelled.