Private Transactions
Inside Aztec Labs · 20 Aug 2023 · 41:03
Aztec software engineer Alvaro Rodriguez presents the lifecycle of a private transaction and leads a discussion on it. He focuses on simulating the call graph generated when a private transaction is sent, covering an example contract and its ABI, the ACVM, solving nested calls and the private kernel. The session is part of the Inside Aztec Labs series of internal team talks.
Speakers
Topics
From Inside Aztec Labs, hosted by Aztec Network.
Transcript
okay okay so uh yeah this one this is going to be more of a short uh format presentation just to have more time for discussion if if it's needed so it's going to be about the private transactions life cycle specifically about the the simulation of the call graph that can be generated by by sending a private transaction so yeah we are going to to use some simple example contract but it will be useful to to visualize this so as you know this private secret functions can call other external functions in other contracts or might be in the same
contract it doesn't really matter for for this case so you can have multiple levels of of nesting you can be calling one function but it will call two more which in turn we call another Etc so but a user when trying to send a transaction is going to trigger the transaction against an entry point the same as in ethereum you send a transaction to a specific contract with some data that selects a function but that is my contract can trigger multiple messages are calling ethereum um pay multiple messages in the in the lifecycle of the transaction so we have these three contracts as an example so the user is going to interact with the
top level one which will will call a a b and and C in the middle one and the middle one in the B function we'll call the D1 so we have we have two level sleep um so this contract is going to get compiled to to an API okay the top level one that is the user is the one that the user is going to interact with so for example this is an example API of a contract it's going to have a list of functions but the function that we actually care about in this one that is the one that the user is going to interact with is the function a in the top level contract and some data that we have that we can have about the functions in the Avi would be the name
whether it's secret function or not right now we are only dealing with private functions secret functions so this is going to be a private one if it's a Constructor or not the list of parameters but for Simplicity we are not going to have parameters or return types but also in the API we need to have the byte code they say write code and as a well for Simplicity basically for efficiency we can have the verification key along with the white code but we could generate the verification key from the right code no problem so but this is a uh this make it makes it simpler to to prove execution if you already have the VK
okay so this would be the API for the top level one so how is this going to be used uh so we are going to have some examples app that is going to create a new uh contract class this contract class will get the ABI and will generate like this stop methods similar as in in ethereum you Auto generate a method a for the function a that accepts the same parameters and it takes the parameters and the knowledge that is in the API and converts it to the to the correct types and same as you have in web3 or ethers JS etc for ethereum you need to provide also some deployment details in this
case because we are not playing a contract we're calling the function a that is uh from a specific instance the deployment details I suppose that is going to be only the address of the contract but I don't know if we need also the portal address to reconstruct uh the contract lift uh by the way if uh any point I want this to be more of a discussion but so you can stop you can ask you can discuss whatever or else at the end we can we can discuss some points um yeah so you will the lab will create this contract abstraction and it will call the function a so it will generate a transaction request the transaction request is going to have these arguments
encoded but in this case we have no arguments so it's going to have just details metadata about what we are calling we are calling this contract in this address this function um some metadata about it that is not that relevant for for this presentation so we are going to ask the as the Aztec RPC server to uh uh simulated and then sign it and Etc but in this case we want to simulate it so two important pieces of this in the Aztec RPC server are the simulator itself and also the private DB and why is the private DB important for this simulation well the probability not only has all the private nodes or the
um some keys but also it has to have a like a bear seal divide code of then of some known contracts it has to Cache to have verified and stored the the right code of some contrast and you are going to see why in the in the next slide so uh what do we need when we simulate the transaction what do we need to to know about the transaction so first we need to figure out the execution graph what is going to call this this function okay so um in this case the execution graph will be a will call B which will call B and then after that is solved I would call C
as you see here in in this graph so since we are going to have this execution graph but we only have the byte code of the function a we need to have in this private DB some data about these other contracts that the function is interacting with the middle and the inner ones and also about the functions themselves so what data we need we need to know the asir of the b c and d functions which the user has not provided with the Avi of the of the top level contract but also we need to have some data uh to be able to generate um hash paths Witnesses about the function being a part of the contract
Leaf the where is the contract leave Etc some some data about these contracts that we have to Cache to store including the acid of the inner functions the the functions deep in the in the call 3 or or graph so what do we need to solve now that we have all this data we know the graph we have the set of all the functions so what really does the user need from the simulation so um basically needs to check the function succeeds and also what it's going to do is going to consume some nodes it's going to create some nodes so for this we can basically generate the intermediate Witness and intermediate witness uh generating
it is what is done right now for uh Noir circuits so uh when you generate the intermediate witness you for example would generate the return values in uh in another circuit if I'm not mistaken so uh and other other things such as directives Etc but yeah so this would allow generating intermittent witness to check if the function succeeds and also this intermediate witness is needed for the Brewer and includes the public inputs that is going to be needed for the Candlestick with that indicate in this case is the verifier of these functions so we can generate the intermediate weaknesses and the prayer images this pretty much is metadata about the execution for example in
intermediate witness you are going to have like yeah I created a new note with this commitment but you need to know the pre-image of that note to be able to use it later so metadata about this that is not in the witness but that you will need to story if you actually want to to execute this this column yeah yeah could you go over again how to generate this intermediate Witness yeah so right now uh the acbm that is a module of the NR team has a partial witness generation is called or intermediate with this generation and what it does is it takes all the op codes and basically tries to figure out all the missing data in the witness if
I'm not mistaken I'm not an expert in Noir at all but if you have a constraint that is only missing one variable then you can figure it out with the others right and for directives for example you can pre-calculate uh the inverse of one number and put it in the witness and that will be part of this intermediate witness generation it will also need some constraining but it would be part of this so or if you have for example the return value you can figure out the the return value by applying all the constraints of the circuit and that would be included in the witness a but I'm not a respirator he's just my rough understanding
so um what we need is this intermediate witness that includes all the uh that will include the public inputs and the pre images so how can we do it so uh one one proposal would be to extend this intermediate witness generation of the acbm to extend uh well in extend it to to support the features that Aztec 3 needs and which is the main one that Aztec 3 bytecode needs would be a loading um loading data from outside the the simulation itself so right now in the intermediate Windows generation you don't need to talk to anybody you want to know the inverse of a number the
CBN can solve that just do it in trust but now for example if you have some op code that could be a directive like for example loads of notes you will need to communicate with the color with in this case would be the car PC server for it to provide some notes or some secret or to call another function so the idea would be to have this separation where you will have this extension of the acbm this Aztec 3bm that would given a byte code the byte code of one function and the argument would solve the partial weakness and the prayer images with some callbacks for solving these special
directives these directives that need nodes that need secrets that need some standard calls this proposal is what I have in my mind but we can discuss this later for sure or write down if you want uh so one point that can be a little bit complicated is how are we going to solve the nested calls because if this is only uh solving it for one function and we have this callback for nested calls so one proposal that I wanted to discuss is uh basically doing with this that will be spinning multiple acbms uh for for when when you need to solve nested calls in this case for example that b called D you will be well a Col B for example that is a the simpler case so
you will the simulator would fetch the series of the function a no problem we have it available it will start the simulation of function a I need the function a will at some point need to call the function b as we saw previously so the simulator will fetch the acid of the function B and then it will spin a spin on the other acbm just to solve this this function call and this could be all the levels deep that is necessary I I wanted to discuss this because I don't know how visible it is with the wasm acbm to have this multiple execution context maybe have multiple virtual machines I don't know if the memory requirements
have to hide or is unfeasible for for any other reason so what you would do is you would accumulate this nested this nested execution contest uh context so what will be the result of all this so you you are able to solve one one function and provide callbacks to solve all the all the nested calls and what you will have at the end would be something like this where for one function you will have a context with the public inputs um that will be extracted from the partial witness so in reality this is just a an extraction for for the pressure from the partial witness you'll
have the brain images and you have also an array of nested context that is a self-reference so how would this graph be it would represent exactly what we saw previously this small graph so you will have the execution context for a that has two nested execution context that would be BNC and B has one nested execution contest that would be so it would be this graph but I mean in this structure and there's a cursive structure this is just as documentation the structure of the public inputs this is taken from the from the middle board um okay so you will have all the parts of witnesses solved and you will have this structure this graph so if you weren't
able to generate the partial witness then the simulation is some constraint is not met something is not solvable so the simulation has failed the same as right now in Noir if you if you try to generation and and you don't fulfill the requirements the constraints um so what can we do with the simulation result with this execution context structure that is nested so now it can be used to prove the execution of these four in this case in this example there is four function calls so it would be one proof per function call and in order to prove it you need the acid of the
function Witness and with that you can generate the proof and one thing that I also wanted to discuss is who is going to do this asir to proven key transformation who is going to have this uh knowledge because right now for example in Noir I think that it is the um it's called Aztec vacant I think it's called that has this knowledge of how to convert the azir to a simple constrained representations than the environmental can use so it's going to be um as a preprocessor like is that we can do it now or is going to be just directly sequence.js is way to understand us here it's something that we can discuss at the end so just to wrap when we have
this nested function execution context this structure this graph we can run it through the private kernel that will be the end to generate the final proof so all the function calls the four function calls have been proven previously so you will run the private kernel as many times as function calls that you have so in this case you will start with the function a that is the the the root of the of the tree you will start with the function a so the stack items will include among other things this is a simplification but would include the the public input okay that includes the hashes as you can see here of the private call stack
um and it will also include the private function proof that we have generated in the previous step so in the first education you will have a a as the previous stack and it would verify that the new stack that contains BNC corresponds with the private call stack of the public inputs of a uh then it will pop B in the second iteration and will push D that is the function that is called by B and then it will be popping until the stack is empty so when you have a well this hides the details about the specific function calls because if you see here in the public inputs there is a lot of metadata about what the
function is doing and we don't want that so since you have all this metadata the public the private kernel is going to have this these public inputs as a private input which is a mouthful but it's going to be a private input to the to the private kernel so since it's a private input it's going to hide all this metadata about what which functions we are calling actually so we preserve the the privacy and finally when you have a popped all the all the structural the terms of the tree and all the stack is and the new stack is empty you have the last iteration of the of the private kernel and this is my understanding of how the private kernel would work because I also
not an expert in this part but this is my understanding of how the simulator output will be used along with the proofs okay so yeah this is this was mainly the presentation and I written some points to discuss uh to see if people had some ideas on some stuff about this that would be these ones so if someone wants to discuss something about this about the for example the this operation between simulator and the acvm having these callbacks these callbacks to the typescript context and spinning out instances of the CBM I don't know how visible it is or if it was what you guys were thinking if it
fits with your idea for example I think that should be compatible with the interface I'm compiling to um unless there are any hidden gotchas with uh was in bindgen not liking multiple entries into the same async function but I guess we'll discover that sooner now [Music] yeah yeah I I one thing that I didn't touch that is related with the next point is something that we discussed on Friday about this how to run the unconstrain functions because on Friday we were discussing like yeah if the unconstrained functions are a JavaScript we can uh okay like uh Oracle calls fetching some data uh these callbacks in
the end can be done as a constraint function because it's basically that so you could just send JavaScript over to the to the caller and say hey evaluate this for me including fetching data from the private DB or whatever but the problem with that I was discussing that with with Laila after the meeting is that it's going to that will be very apart from the problems from developer experience because you have to write in GIS or unconstrain functions instead of of Noir it would be very very difficult to to run that in a secure manner isolating JavaScript coding and JavaScript environment can be done with workers you can do it with a lot of stuff but in reality this could be quite
dangerous if a contract developer can write and constrain functions in JavaScript and have it and evaluated that's a very good point and um given the context of what people were like what programmers people be writing and no I do think that's a very big concern I mean for example imagine if you could if your solidity contracts could inject JavaScript code into a um into your into your daps execution environment um introdemps runtime that would be um pretty bad uh so what was the consensus then like what's what's the the current consensus around JavaScript versus Russian constraint functions anymore
I I don't know because we were discussing last Friday I don't know if it has advanced a little bit um I I felt like the the it was like Noir was the desired um language but it could be but I think that it was a point made that it could be complicated because you will have to spin like individual virtual machines for the unconstrained functions you know are internally I correct me if I'm wrong because it's a little on top of me yes but probably just one VM right because
the the execution is serial um in in Noir but um the thing is that with no audio compiled to opcodes that relate if I'm not mistaken yes so you do the next so you do some kind of execution context um so like I mean like a fake weakness to operate in yeah yeah and so we were talking on on Friday in this talk that it would be simpler in JS but I think it for for production it would be very very uh easy to to be it will be very easy to be a problem it would be very difficult to properly
isolate in all environments in node in the browser in every yeah good shout out later for pointing pointing that out um I think for the for us tax free specifically like what what we need to implement as unconstrained functions for fx3 it is fine because we can write the buyers so I think it's it is fine for what we need for a stick 3 but when we want to provide this functionality like of course unconstrained functions to Sparkle effect developers so they can write their own then yeah then this is a problem if they can inject the JavaScript yeah but uh I don't think
there is a solution either it's they have something powerful and then they can do something that is really unsafe for you uh oh it's restricted and then it it's not powerful so I don't think I think there is a solution it's just there's you can create something which is powerful in in the context of something that's true and complete but just um you're very supportive a very clear separation of runtime environments so I don't I don't quite see I I do appreciate it would be slightly more complex but I don't see the fundamental problems of writing on the constraint functions in Noir you might need a slightly additional syntax um but like as in you know like if if assuming
Noir is functionally [Music] language and then yes that's easy but if you want your language to be able to access uh internet like a color um I don't know call uh web API to get the the weather for your s then it will for it will be unsafe I see what you're saying I see the thing Indian what what we want is like a limited virtual machine for for decent constraint functions something like that like the ecbm could be a
I think your mute unless I run out of battery so yeah for firesticks you need to access some database yeah but those could be implemented as for example this this database could be implemented as a specific directive for example that was my my natural like response of how this could be done and I might be completely wrong because but for example the the specific Aztec 3 and constrained piece of code that are fixed by the by astax3 it could be written as specific directives like no no load notes and these are a directive to to
load an unlimited number of nodes and then you have unconstrained pure functions that are executed within the acbm2 and reduce those nodes for example or or whatever but like I think I had an example here this might be completely unfeasible but that you have in in the byte code at some point these directives that are the ones that do the Oracle calls the database access and that for and construct user facing and constraint functions uh you just do pure functions like no no data outside the airport so I mean you raise an interesting point because it's not something I really
considered I guess what you're describing really is um like basically the ability to like have Oracle like basically to implement Oracles in directly inside a normal contract so if you want your own as you said like you want to ping some random server to get some information you could in theory do that directly from the normal contract if you could inject JavaScript um I guess I thought we won that I mean it's powerful uh I I was I wasn't assuming we wanted that um to start with um at least with V1 um yeah I think that's something we can
probably um well my gut feeling is something we can address in the future like once once Aztecs is a kind of released and there's an active developer ecosystem as an you want to recall access to things like external website servers Etc you can always program them at the app level and um carry this argument yeah exactly um which makes it obviously the the DivX for those particular applications is weak is worse but I think we need to prioritize security unfortunately uh well I mean unfortunately in terms of like features um because yeah like I feel like I feel like we're putting a big Target on our back if we allow for JavaScript injections into non-contracts um
uh it's just it yeah um yeah I feel like this yeah this This concerns around security we should we should follow the um we should we should take the secure path this is my my gut feeling on this Lassie was talking in the in the comments about how would the uncle strain functions uh work in a public function and I assume that they what like that in the end this unconstruate functions that are for Oracle uh fetches like load nodes Etc are only I assume for private functions because is the only ones that have real access
to to the secrets or yeah I'm not sure we would need interesting functions in the public functions because the only State you can access um is the public state tree which I don't think needs from the earlier like diagrams at least the public function can both access the public state but also the private State because you could have a case where you want to insert something in the um depending on so say you want to move something from public domain to private domain yeah good point um [Music] yes but could that be passed as arguments to the public function
and checking the color like if it's the same contract it's myself that is calling me I'm a public function I don't know it was more that if it's a public function the sequence of electrocuted so it needs to be available and If we have say we have the other script whatever then it might end up being like very large like code base that you have for the other constraints about it or something like that it's it's more than that actually it's the fact that you you simply can't have unconstrained functions in a public function because the purpose of a non-fiction function is supposed to act as a specific guide basically to to figure it's it's a program that describes how to create set
like the correct um witness assignments to to the constraints that that the normal program is creating uh but um you don't know that that's the case as in you use um when you for a private function you can assume that's the case because if it isn't then the user can't make a better transaction but for a public function which is sequencer is executing they cannot trust a directive to be honest therefore you cannot have directives in public functions because because the following directive mainly the secrets to create an a fake Internet proof and then they don't get paid for their um but I can't for them for their work I think so it has to be
um I didn't remember the word exactly um like all the witness has to be predictable this has yes direct derivable um I think yeah I think maybe we should need to chat about this a bit more next week because I think it'll it'll might affect the design of public functions in the public VM because yes um that is something that basically a Serial case they're not like regular up codes in a in a in a low level um language and that they're not deterministic as in um they can have more than one degree
freedom and that is a problem for public functions ah well if they are properly constrained the result I'm not sure it's the problem the problem is no no it's not about being properly constrained the problem is every noroc code must only have you know public function must only have one degree Freedom isn't everything must be derivable by the sequencer they must have enough information to derive everything um if that makes any sense um maybe I'm yeah I I don't I don't have enough I think I oh sorry my brain is Swiss cheese at the moment
um there are two problems with public functions one of them is giving the sequence to enough information to make the proof the other one is making sure that the sequencer has Faithfully provided the correct witness assignment it says has not chosen to create an invalid proof um in the presence of good data uh and I feel like the non-determinism and not Chris could be could cause a problem with the latter does that make any sense game well yes or no I mean like for me what you just explain is a feeling so maybe it's it's correct but maybe it's not so I'm not sure do you have an example of how that would
be a problem um that would be helpful so let's say that I am um okay so you've got an architected A person hash right where um the um um so you haven't answered your position so the idea is what you have is you have your inputs um and then a bunch of constraints are created to um like what the privatizes they provide what you're doing is you're doing basically the multiplication so you're multiplying a point by scalar and every step you're providing um the the results of your like um uh of of CarePoint Editions
uh what if the sequences just decides to to create invalid and valid witnesses there they're like actually like if the point the X1 is supposed to be five they Place three um and the proof fails um and and so even so the transaction Center is not nothing wrong um their transaction should be valid but the sequencer has through Menace chosen to make their transaction fail the the difficulty when executing public functions is you need to be able to tell the difference between um two things um you usually tell the difference between when the proof is invalid because the transaction execution logic has thrown
an error and the case where proof is invalid because the because incorrect values have been assigned to wires in your Gates um you need to make the decision for those two because the former is caused by the transaction sender giving the sequence of bad data and the latter is caused by the sequencer not doing the job properly and if you can't tell the difference between the two then you can basically that's that's then becomes you can sequences can launch censorship and griefing a text by basically making transactions fail and they shouldn't then what do you need is a digital machine right that is you need a virtual machine that's what we're going to do but my plan was to make the virtual machine evaluate nor up codes um and I'm starting to wonder that if
that's that that may be insufficient why is the user signature not sufficient to check against the witness it's been generated the user doesn't generate the awareness it's not no the data for the witness yeah they're generating that proof right like this this is after the secret the transact the user is set in that transaction none of the information like that's included in the witness assignments you know we're talking about like tens of hundreds of megabytes of information here that's derived from um state from from that that's that's right possibly from State reads and state accesses that the the transaction Center does not have access to
yeah because uh it cannot know for example the order of the transactions so that could affect this do you know the state of the system what that public function is going to be executed it feels a little bit to me like the in the Syrian transactions when you basically have a valid transaction but status failed so it like it's like it's asking for having a level of indirection but if I think it might be okay I mean it depends on the structure of the virtual machine because the virtual machine basically has to um ensure that when executing complex and nor up codes the whole point of it yeah so the point yeah the point of a virtual machine is that um
uh you can create a system where a valid transaction must have satisfiable satisfiable witnesses then um if the secretary chooses to create invalid witness assignments the proof isn't valid but therefore that means that that means that that proof cannot be put in the wrong block um you want you want to run a VMS for that um Faithfully executing the user's instructions does not result in a failed proof that results in a valid proof where the output of the VM is zero um and like so maybe so maybe I'm just going running around in circles here um and maybe it's not um a problem um it depends on the specific structure of the assyrop codes um but like but maybe it's not a problem uh
um but um yeah going back to directives uh um as in like I cannot think of an example where you need a directive in a poly function like what do you want to load um what information do you need that is not from the inputs of that public function yeah I'm hopefully we don't need direct to some public functions um and I don't think you can have direct photographs it's great go back to why it's some
what are directors used for directors are used to basically assign witness values y values to Gates in your circuit ultimately that's what happens from a directive um uh and uh but in the public function model um invalid y values create um uh uh in like they don't create failing failed transactions that they don't create transactions that revert that you can include in a lot of their create transactions where the proof isn't valid and therefore we they count can't be included therefore basically if a in a public function call if the resulting proof is invalid the sequencer has wasted their work um they don't get paid and so if the if the if the contract contains the
directive that deliberately signs um unsatisfiable witnesses to wires in the north circuit then that that basically become turned onto a griefing attack because you're then instructing the sequencer to create to spend all this work generating a proof for a transaction that is invalid and therefore they cannot get paid for does that make any sense and it's kind of the other the other way around not much the the sequencer wanted to create invalid yeah groups to to censor someone but also points to a bit
yeah okay so yeah I think basically we probably can't have characters in public functions um which means that well you can't read from the private state tree in a public function um but you never could anyways without leaking Secrets I still I think it should still be able to write to a I mean it's a private Stitch I mean but you should still be able to write to private state tree in a public function because that shouldn't require directives to create a note from scratch I think but everything you write is going to be public yeah yeah so so which maybe probably shouldn't um but but um it's important to have the option then because for example Aztec connects um in our D5 Bridge
um we effectively are writing to like there's a if he works to 109.3 set of contracts there is a part where we're right we're writing to the private state tree in a public function that's when you're kind of completing a D5 claim notes the idea is that in a private function a user generates a D5 came note but then in a later public function the rollout provider takes that note and adds on what is like a defy interaction not adds that into the note and then and then adds it into a into the private state tree um and does that mean the claim the claim note would need to be a public State like if you just go for the estimate no no no no no no no no it means the claim note would the the partial claimant would be a parameter
input parameter to a public function that public function would then compute the claim at an added a private state uh did the step before though when when you go from like a deposit um like a defined deposit like that's read by something isn't it could you could you clarify what your meaning so so like in uh to get to a point where you can actually send funds to a default protocol it before you have the claim note you create this partial partial claim notes mistakes um the user creates as a private function which can computes a partial claim node then that again that then gets put like that becomes the input parameter to a public function call
and the public function will write to the private state tree makes sense okay yeah so I think we we are running out of time so thank you for for the discussion I think it's I think we have clarified some points that are important for for this part of the prep functions so thanks