 Thanks all for coming today, and in this talk, we are going to be talking about how we improve the performance of approval computations using Rust, and specifically, how we implement the Cairo VM using Rust. So OK, let's go. First, I want to give a little words on who we are. We are Lambda Class. Lambda Class is a Latin-based software company. It's in the industry more than three years. And yes, basically, we love solving difficult problems. So if you have any problem, just give us a call. And OK, let's go straight to the talk. And the first thing I want to start with is to give a little bit of context and talk a little bit about Ethereum. And as you all may know, Ethereum is a decentralized network, meaning that when you run a transaction, the transaction is not run in only one computer, but in every computer that forms part of Ethereum or the network. So that's really cool, because the verification of the transaction does not depend on only one computer, on a centralized entity, but on every computer inside the network. But it comes with a downside. The downside is that the computation capacity of the network depends on your slowest node. So we have a situation that we have a high demand of running transactions, and our computation capacity is limited by our slowest node. So how can we solve this problem? And here is where StarNet comes into play. StarNet is a secret roll-up, and what roll-ups allows us is to run transactions off-chain, then make a batch of the transaction, and send to Ethereum the updated state. And what CK adds to roll-up is that we can run the transaction off-chain, generate a proof of the integrity of the execution of the transactions, and then we send to Ethereum the updated state and the proof of the computation integrity. So then on-chain, the proof is verified by a verifier, and that verification is running all the computers inside Ethereum. So the coolest thing about this verification is that the verification cost of the proof grows logarithmically with respect to the number of transactions inside the batch. So as the cost of verification is distributed between all the transactions, including the batch, as we add more transactions in the batch, the cost per transaction, the average cost per transaction, goes to zero. So yeah, here is a slide that I was supposed to say that the cost of verification grows logarithmically with respect to the number of transactions. And yeah, if we tend to infinity, if we take this to an extreme, and we tend to infinity, the number of transactions inside the batch, the average cost per transaction, go to zero. So OK, we say that in the start net, we can generate a proof on the execution of our transaction. But what kind of proof? Well, it's a similar proof, and specifically a stark. And your knowledge allows us to prove the veracity of a statement without revealing anything beyond the fact that the statement is true. And in this specific case, Starks allow us to prove the computation integrity of the transaction that would run off-chain. We are having to run it all again. So this is kind of a game changer, because before zero knowledge, the only way we had to prove the computation integrity of a computation was to run that computation ourselves. But with zero knowledge and Starks, now an untrusted entity can run transactions, generate a proof on the integrity of that execution, and then the proof has to be verified. And we can be sure that everything is OK. So OK, how do we write a program that is provable, meaning that its execution can be proven, the integrity of the execution? And OK, we have Cairo. And Cairo is a programming language, specifically designed to do this. When we run a program in Cairo, it's run in the Cairo VM. That Cairo VM executes a program, and also generates a trace of the execution. And that trace is then going to be sent to a prover and generate the Starks proof. And that proof is going to be verified by a verifier. So OK, how does the Cairo VM works? And now Ferica will guide us through the internals of the Cairo VM. So Ferica, it's on yours. Well, now let's talk about a bit how the Cairo VM works. Well, you already know how a virtual machine works. Basically, you write your source code, you compile it, and then you use a virtual machine to interpret that compile code and execute the instructions. But what sets apart this Cairo virtual machine is that it also generates this trace that can be used to generate the proof. So let's go a bit into the architecture of the Cairo virtual machine. Well, first we have the memory model. It is a write-once-read-only memory that is divided into different segments. These segments have a known size at runtime. And once we finish the VM run, these segments go through a relocation process, and we end up with a continuous memory. So well, the segments consist of the program segment, which contains the code of the program, the execution segment, which gets filled up as the VM runs, the built-in segment, which we'll talk about later, and the user segment, which contains structures defined by the user, such as arrays or dictionaries. Well, let's explain a bit about this relocation process. The first step is to assign a size to each segment. For example, the segment zero has five elements, so it has size five. And then we will use these sizes to assign a base to each segment. What we say with base is the first address of each segment in the relocated memory. So for example, for segment one, we have the previous segment base is one, because it is the first segment. And we add five, which is the size, and we get six. Now, with this base is calculated, we'll proceed to relocate all the addresses. We'll do this by adding the base of the segment with the offset of the address. So for example, here we have the address zero zero. The base of the segment zero is one, and we add zero and get one. We'll do this with all the addresses, and also with the addresses that are contained inside the memory. For example, you see here that we have two zero and three zero, and they both relocate to nine. As the base of the segment two and segment three is nine. And this is how the relocated memory looks like. It's a set of continuous addresses with elements. Well, now we'll talk about the registers. Cairo has three general purpose registers. The first one is the program counter, which iterates over the program segments and points to the next instruction to be executed. Then we have the frame pointer and allocation pointer in the execution segment. The allocation pointer will point to the next and use memory cell, while the frame pointer points to the current frame. But this means is that when we want to execute a Cairo function, the frame pointer takes the value of the allocation pointer and remains constant while the allocation pointer keeps increasing as we add elements into memory. And when we exit this function, the frame pointer will change its value. This is how the BM operates. This is the main execution loop. We first get the next instruction. We decode it. We compute the operands. Then we add the current register values to a trace and then we update the registers. So, okay, this is how the trace looks like. It keeps track of how the registers change for the execution of the program. And it also goes through the same relocation process as memory. Well, as we can see, the size of this trace will depend on the amount of steps that a Cairo program takes. So, if a Cairo program takes a lot of steps, this trace will get very big and will take more time to generate the proof. So, sometimes there are some computations which are very expensive, take a lot of steps, but maybe the information provided by each of the steps is not that relevant. For example, if we want to compute a Pedersen hash, this will take many, many steps. So, in order to solve this, what we have is, oh, sorry. We have built-ins. Built-ins are low-level optimizations that are integrated in the core loop of the VM and they allow otherwise expensive computation to be performed more efficiently. And, well, each of these segments, each of the built-ins have a segment in memory. Okay, but where do they come into play? Well, we see here that we have our main execution loop and we have this deduced operand added. What this means is that sometimes we might want to compute an operand and we can't really compute it directly and we will rely on some deduction rules. Sample here, we have the Pedersen built-ins out-of-the-action rule. What it does is it takes the true previous values in memory and computes the Pedersen hash of them. This rule comes into play when the address of the operand that we cannot compute belongs to the built-in segment and that built-in has an out-of-the-action rule. So, what this allows us to do is, for example, here, we want to compute the hash of x and y. So, what we do is insert x and y to memory and then we ask for the next memory location. As we haven't inserted anything into this memory location, we won't be able to compute the operand and we will fall back on this deduction rule that will be the one computing the hash. So, we computed a Pedersen hash, but there is no sign of the computation in the Cairo memory or the steps or the trace. Another feature that Cairo offers are hints. These are blocks of Python code embedded into a Cairo program and they allow Python context to access the VM state and modify it, also access local Cairo variables and can communicate between each other for execution scopes. What we see on screen is the alloc function from the common library. This function is commonly used to generate arrays and what it does is it creates a new segment and it inserts it into memory so we can use it from Cairo. We mentioned execution scopes. These scopes are a stack of dictionaries that can hold variables that are created inside a hint. These variables are not seen by Cairo, just by the hints and this can be created and removed inside hints and multiple hints can access the same scope. For example, this code is a bit long, but we could just focus on, for example, we first enter the scope with an n variable then we use that n variable, we modify it for various situations and then we exit the scope deleting this n variable. Well, so now let's talk about how we implemented hints in Cairo RS. That is our implementation of the VM. Well, first, why we chose Rust? We chose Rust because it offers very good performance, memory safety guarantees and it also has an amazing community. Well, our first iteration of hints in Cairo RS was basically to implement Rust functions that would imitate the behavior of hints and match these blocks of Python code to our hints. For example, the constants we see here are the blocks of Python code. This is again the example of the unlock function. This is the block of Python code and this is how we replaced it with a Rust native function. Well, these first iteration had pros and cons. On the pros side, we have that it was very easy to implement as we didn't need any new tools and it also offered better performance as calling functions within the VM was much faster than compiling on running code during the VM's run. But on the cons side is that we needed to watch out for changes. We first applied this to the common library so if a common library hand were to change, we needed to change our implementation in order to keep supporting it. And it also wasn't really extensible as if we wanted to support more hints we'd had to code it inside our VM. So as we wanted to support user-defined hints and we also wanted to integrate our VM with the current Python infrastructure we decided to go with the next iteration that consisted of integrating Python with our VM. In order to achieve this, we used the create pyurefree. It is a query that provides Rust bindings for Python and it allowed us to share our VM state with a Python context. Allowed that Python context to modify our VM and also allows to define a strict interface between our VM and Python by using PyClasses and PyMethods. PyClasses are Rust structs that can be interpreted as Python objects and PyMethods are Rust methods that can be called from within Python. So let's take a look at the code. Well, this is how we allowed hints to modify memory. We created this PyMemory object and when we want to get an item, what actually happens is that we call our VMs memory and we use our Rust get function and the same happens when we want to set an item. Well, how did we allow it to modify the Karyolocal variables? Karyolocal variables can be accessed inside hints by writing ideas.name of the variable. So what we did was override these ideas, objects, set attributes, and get attribute methods. So when we want to get an attribute, we are actually calculating the value of that attribute inside during the hint run and when we want to modify a local variable, we calculate its address and insert the new value into memory. The next step was execution scopes. In order to implement this, we took a bunch of the local Python variables. So what we did was take our execution scopes variables, convert them to Python objects and inject them into the Python locals. And when the hint was done running, we extracted the Python objects from the locals and inserted them into our execution scopes. This is how executing a hint currently looks like. We first take our local variables and inject them into Python locals. We create these Py classes that will allow hints to interact with our VM and insert them into globals. And then we, again, retrieve these values from Python locals and update our execution scopes. But well, how does this all look like? We created a separate crate for this called KyroRSpy in which we instantiate the VM and we execute the Kyro methods, the VMs methods, such as instant initialization running instructions. And when a hint cannot be run by our VM, we fall back to this Python hint execution. So with this approach, we were able to maintain the Rust functions that we created in the first iteration and also add this Python integration. So when you use the common library hints, you can still enjoy the boosting performance from the Rust native functions. Okay, awesome. Thanks, Fede. Oh, let's go. Sorry. Okay. It's okay. Well, the name of our talk is how we improve the performance of proven computation. So showing some benchmark was a must. And here we can see how our implementation of KyroRS is about 100 times faster than the Kyro VM using the CPython interpreter and about 20 times faster than the Kyro VM using the Pypy interpreter. And the coolest thing is that our implementation is 100 times faster than Kyro VM using CPython but also consumes half the memory. And our implementation is 20 times faster than the Pypy version but consumes 12 times less memory than Pypy. So, yeah, it's really cool. And yeah, okay. I hope that standard developers embrace KyroRS and that helps building and accelerating the ecosystem. So, yeah. That's it. Thanks for being here. Questions? We still have time for some questions. So we have our volunteers in the room. Just raise your hand if you have any questions. Compliments for the great presentation. Hello. Hello. Well, I've seen you before. My question is, how stable is this? Is this available to use right now? Or... If it's available to use right now? Yeah. Yes. We are still developing the integration with Python. But, yes, KyroRS is open source and you can use it. But we are finishing, I think, in maybe two weeks the integration with Python. So after that, we are going to start testing it and making it faster. So we can be sure that everything is okay. And then it is going to start using it in production. So we are really excited. You let them speechless about the representation. Well, big round of applause. Thank you so much, Federica and Herman.