-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Miden Component Model MVP #1624
Comments
Since the Wasm CM does not have any access control tools baked in, we need to be able to encode the "all allowed" value in the "allow list". Empty "allow list" sounds good for this. |
In this issue, I'd like to propose a concrete MVP for a "Miden Component Model" as introduced by @bitwalker in #1171 and subsequent issues. Concretely speaking, this calls for a change in how execution contexts work in the Miden VM, among a few other things. The goals of this post are to start the conversation on a more concrete implementation plan of a Miden Component model, confirm that the current proposal solves the problems on the compiler side while still being correct from the rollup's perspective, and evaluate if and when we would want to move forward with the proposal (or a subset of it).
The problems with the current design of the VM that this proposal tackles are:
syscall
), and hence is the only context in which we can persist state for the duration of the program.call
,exec
,dyncall
ordynexec
), leading us to document the correct way to call each procedure, which is awkward and error-prone.caller
-based mechanism used in the rollup, where onlyAccount
-based methods are allowed to call into the kernel.caller
instruction)This issue is structured as follows:
miden-base
The Miden component
A Miden component is simply
CALL
into,Think of it as a module with its own address space. Intuitively, the "component" is a generalization of our current kernel concept, with the modifications that
syscall
instruction to call into a procedure exposed by the kernel. In the new model, you just usecall
, because there is no kernel anymore (or, "every component is a kernel").call
instruction slightly so that you can specify which component you're calling into.caller
instruction. Therefore the Miden component defines an access control system to let a component (e.g. the transaction kernel) define who is allowed to call it (e.g. theAccount
component)To talk to another component, we use the
call
instruction on one of the callee's public procedure roots (otherwise the call results in an error). As in the current design, components can use the stack to pass procedure arguments. If 16 elements is not enough, they can use the advice provider to un-hash the data (i.e. caller passes the data hash on the stack, while the callee reads the data from advice provider, hashes it, and verifies that the hash matches the one passed on the stack).The VM will also still need to know which component is the "kernel" - that is, the component that defines the entrypoint of the program (encoded in the public inputs). Other than this small edge case, all components in the system are identical (and the kernel is just another component). Hence, a
Program
becomes a set of interacting components, with a single component defining the entrypoint.Changes to the VM
The new responsibilities required from the VM are:
call
-ablecall
is proceduresBoth require the
call
instruction (the mechanism for cross-component calls) to be modified to take a "component id":call.component_id.procedure_root
. That is, in the current system, the memory execution context is determined by the clock cycle; in the new system, it is written directly in the instruction.(1) is easily solvable by re-using the existing kernel ROM machinery. The general idea is that a program would declare all components and their exposed procedure roots in the public inputs, in a similar way to our current
Kernel
declaration. Then, on everycall
instruction, the (modified) kernel ROM would ensure that the(component_id, procedure_root)
pair is present in the public inputs (in an analogous way to how it currently checks that allsyscall
s are indeed made to an exposed kernel procedure).As mentioned previously, we will no longer need the
syscall
andcaller
instructions.(2) would be solved in a similar way. Every component defines a list of components that are allowed to call it (where maybe "empty list" means "all"). We would define a new lookup table similar in spirit to the kernel ROM (converted to a lookup table as described in #1518), but instead would look like
source_id, dest_id, multiplicity
. The table is initialized from the public inputs with all allowed calls (e.g. if component 0 only wants to allow calls from component 1, we would have an entry1, 0, ?
where the multiplicity is determined after running the program).As for (3), there are a few ways to do it. I propose that we use a privileged "bootloader" that would run at the beginning of all programs and initialize all the memory contexts (thanks to @bitwalker for pointing me in this direction). Hence, this requires
2.1 Adding a "privileged mode" to the VM that would allow the bootloader to write to any component's memory (along with privileged versions of the
mem_store
andmem_storew
instructions), and2.2 a new
INIT
MastNode
, for whichINIT
INIT
works just likeJOIN
, except that the left child is always the bootloader.INIT
runs in privileged mode, while the right child (i.e. the user program) runs in non-privileged mode.2.1 Privileged mode
This requires adding a bit column, which is set to
1
at the beginning of the program (i.e. when executing the left child ofINIT
), and turned off when transitioning to the right child ofINIT
(and for the rest of the program).We will also need two new instructions
mem_store_p
andmem_storew_p
, which work analogously tomem_store
andmem_storew
, but that take an extracomponent_id
parameter. They will be constrained to only be allowed when theprivileged
bit is set to1
.2.2 the
INIT
node, and the bootloaderThe VM will constrain the first node to be the
INIT
node, which as mentioned previously, works like aJOIN
node: it has two children, and executes the left followed by the right. However, it also manages theprivileged
bit, which is turned on at the beginning of the program, and turned off after executing the left child (that we call "bootloader").We will also want to constrain the bootloader that is executed, by having the user place the MAST root of the bootloader that it expects in public inputs, as well as a constraint that checks that the left child of
INIT
is indeed set to that value.The bootloader would unhash from the advice provider the description of the memory images for each component (and compare the resulting hash against the expected hash provided in public inputs). For example, a possible format could be a serialized version of
The bootloader would run through this data, use
mem_storew_p
to write the words to the appropriate addresses in the specified component, and similarlymem_store_p
to write the elements.Changes to
miden-base
I don't think many changes to
miden-base
are needed. For transaction execution, the transaction kernel would still the "kernel component" defining the entrypoint, and would store the account's data. TheAccount
component would presumably always be component 1, such that the allow-list of the transaction kernel can always be "only component 1". Then, all note scripts can run in their own component (with component IDs 2+). Everything else pretty much is structured as it currently is.As previously mentioned, I'm not sure if having a static access control list works; @bobbinth would love your input on this. Also, the access control list has no analog in the Wasm component model, so @bitwalker I would love to know if this causes issues when compiling down from the Wasm component model.
(Optional) The "call indirect" instruction
Currently, the rollup uses exec_kernel_proc so that changes to the kernel doesn't result in changes to an
Account
's MAST root. For example, if the kernel method was called directly usingsyscall.kernel_proc_hash
, and the kernel method changed, then thesyscall.kernel_proc_hash
MAST hash would also change, and therefore theAccount
's procedure containing the syscall's MAST root would change.This shows the desire to have both "content-addressable" calls (e.g. with
call.proc_hash
), as well as "dynamic/indirect" calls (usingexec_kernel_proc
). Adding a "call indirect" instruction would fit pretty nicely in the proposed component model. In MASM it would look likecalli.component_id.proc_idx
, whereproc_idx
is the index of the procedure exposed by the component. This would have the desired effect that the target component's procedure can change its MAST root without changing the MAST root of the caller.Conclusion
Although I tried to design this so that it be the smallest possible distance from our current state, it still seems like it would be a significant amount of work to fully implement. Assuming that there aren't major flaws that completely invalidate the design, we should discuss if and when we want to start working on this.
The text was updated successfully, but these errors were encountered: